main.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. 
  2. function base64UrlEncode(value) {
  3. const encoded = btoa(value);
  4. return encoded
  5. .replace(/\+/g, "-") // '+' => '-'
  6. .replace(/\//g, "_") // '/' => '_'
  7. .replace(/=*$/g, ""); // trim trailing '=' padding
  8. }
  9. function base64UrlDecode(value) {
  10. const base64 = value
  11. .replace(/-/g, "+")
  12. .replace(/_/g, "/");
  13. return atob(base64);
  14. }
  15. function coerceToArrayBuffer(value) {
  16. if (!(typeof value === "string")) {
  17. throw new Error(`Invalid input type. Expected string but got ${typeof(value)}`);
  18. }
  19. // base64url to base64
  20. const str = base64UrlDecode(value);
  21. // base64 to Uint8Array
  22. const bytes = new Uint8Array(str.length);
  23. for (let i = 0; i < str.length; i++) {
  24. bytes[i] = str.charCodeAt(i);
  25. }
  26. return bytes;
  27. };
  28. function coerceToBase64Url(value) {
  29. if (!(value instanceof Uint8Array)) {
  30. throw new Error(`Invalid input type. Expected Uint8Array but got ${typeof(value)}`);
  31. }
  32. let str = "";
  33. for (let i = 0; i < value.byteLength; i++) {
  34. str += String.fromCharCode(value[i]);
  35. }
  36. return window.btoa(str);
  37. }
  38. class Registration {
  39. constructor() {
  40. document.getElementById('start').addEventListener('click', this.registerAccount.bind(this));
  41. }
  42. async registerAccount(event) {
  43. event.preventDefault();
  44. const username = 'Foo! That Bar';
  45. const login = base64UrlEncode(username);
  46. let options;
  47. try {
  48. options = await this.buildCredentialOptions(login);
  49. } catch (e) {
  50. console.error("Building create options failed with exception", e);
  51. return;
  52. }
  53. // Create the Credential with navigator.credentials.create API
  54. let newCredential;
  55. try {
  56. newCredential = await navigator.credentials.create({
  57. publicKey: options
  58. });
  59. } catch (e) {
  60. console.error("credentials.create failed with exception", e);
  61. return;
  62. }
  63. console.log("PublicKeyCredential created", newCredential);
  64. let result;
  65. try {
  66. result = this.register(login, newCredential);
  67. } catch (e) {
  68. console.error("Registering new credentials with the server failed with exception", e);
  69. return;
  70. }
  71. console.log("New login created", result);
  72. }
  73. async buildCredentialOptions(login) {
  74. const response = await fetch(`/buildCredentialOptions?login=${login}`, {
  75. method: 'GET',
  76. headers: {
  77. 'Accept': 'application/json'
  78. }
  79. });
  80. const options = await response.json();
  81. if (options.status !== "ok") {
  82. throw new Error(`Error in buildCredentialOptions: ${options.errorMessage}`);
  83. }
  84. console.log("Got options", options);
  85. // Turn the challenge back into the accepted format of padded base64
  86. options.challenge = coerceToArrayBuffer(options.challenge);
  87. // Turn ID into a UInt8Array Buffer for some reason
  88. options.user.id = coerceToArrayBuffer(options.user.id);
  89. options.excludeCredentials = options.excludeCredentials.map((c) => {
  90. c.id = coerceToArrayBuffer(c.id);
  91. return c;
  92. });
  93. console.log("Options Formatted", options);
  94. return options;
  95. }
  96. async register(login, newCredential) {
  97. // Move data into Arrays in case it is super long
  98. // TODO: check!
  99. let attestationObject = new Uint8Array(newCredential.response.attestationObject);
  100. let clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
  101. let rawId = new Uint8Array(newCredential.rawId);
  102. const data = {
  103. id: newCredential.id,
  104. rawId: coerceToBase64Url(rawId),
  105. type: newCredential.type,
  106. extensions: newCredential.getClientExtensionResults(),
  107. response: {
  108. AttestationObject: coerceToBase64Url(attestationObject),
  109. clientDataJSON: coerceToBase64Url(clientDataJSON),
  110. transports: newCredential.response.getTransports()
  111. }
  112. };
  113. let response = await fetch(`/registerCredential?login=${login}`, {
  114. method: 'POST',
  115. body: JSON.stringify(data),
  116. headers: {
  117. 'Accept': 'application/json',
  118. 'Content-Type': 'application/json'
  119. }
  120. });
  121. const result = await response.json();
  122. console.log("Credential Object", result);
  123. if (result.status !== "ok") {
  124. throw new Error(`Error creating credential: ${result.errorMessage}`);
  125. }
  126. return result;
  127. }
  128. }
  129. window.registration = new Registration();