|
|
@@ -1,19 +1,47 @@
|
|
|
|
|
|
-coerceToArrayBuffer = function (thing, name) {
|
|
|
- if (typeof thing === "string") {
|
|
|
- // base64url to base64
|
|
|
- thing = thing.replace(/-/g, "+").replace(/_/g, "/");
|
|
|
-
|
|
|
- // base64 to Uint8Array
|
|
|
- const str = atob(thing);
|
|
|
- const bytes = new Uint8Array(str.length);
|
|
|
- for (let i = 0; i < str.length; i++) {
|
|
|
- bytes[i] = str.charCodeAt(i);
|
|
|
- }
|
|
|
- return bytes;
|
|
|
+function base64UrlEncode(value) {
|
|
|
+ const encoded = btoa(value);
|
|
|
+ return encoded
|
|
|
+ .replace(/\+/g, "-") // '+' => '-'
|
|
|
+ .replace(/\//g, "_") // '/' => '_'
|
|
|
+ .replace(/=*$/g, ""); // trim trailing '=' padding
|
|
|
+}
|
|
|
+
|
|
|
+function base64UrlDecode(value) {
|
|
|
+ const base64 = value
|
|
|
+ .replace(/-/g, "+")
|
|
|
+ .replace(/_/g, "/");
|
|
|
+ return atob(base64);
|
|
|
+}
|
|
|
+
|
|
|
+function coerceToArrayBuffer(value) {
|
|
|
+ if (!(typeof value === "string")) {
|
|
|
+ throw new Error(`Invalid input type. Expected string but got ${typeof(value)}`);
|
|
|
+ }
|
|
|
+ // base64url to base64
|
|
|
+ const str = base64UrlDecode(value);
|
|
|
+
|
|
|
+ // base64 to Uint8Array
|
|
|
+ const bytes = new Uint8Array(str.length);
|
|
|
+ for (let i = 0; i < str.length; i++) {
|
|
|
+ bytes[i] = str.charCodeAt(i);
|
|
|
}
|
|
|
+ return bytes;
|
|
|
};
|
|
|
|
|
|
+function coerceToBase64Url(value) {
|
|
|
+ if (!(value instanceof Uint8Array)) {
|
|
|
+ throw new Error(`Invalid input type. Expected Uint8Array but got ${typeof(value)}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ let str = "";
|
|
|
+ for (let i = 0; i < value.byteLength; i++) {
|
|
|
+ str += String.fromCharCode(value[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return window.btoa(str);
|
|
|
+}
|
|
|
+
|
|
|
class Registration {
|
|
|
constructor() {
|
|
|
document.getElementById('start').addEventListener('click', this.registerAccount.bind(this));
|
|
|
@@ -21,10 +49,43 @@ class Registration {
|
|
|
|
|
|
async registerAccount(event) {
|
|
|
event.preventDefault();
|
|
|
-
|
|
|
const username = 'Foo! That Bar';
|
|
|
- const login = btoa(username);
|
|
|
+ const login = base64UrlEncode(username);
|
|
|
+
|
|
|
+ let options;
|
|
|
+
|
|
|
+ try {
|
|
|
+ options = await this.buildCredentialOptions(login);
|
|
|
+ } catch (e) {
|
|
|
+ console.error("Building create options failed with exception", e);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
+ // Create the Credential with navigator.credentials.create API
|
|
|
+ let newCredential;
|
|
|
+ try {
|
|
|
+ newCredential = await navigator.credentials.create({
|
|
|
+ publicKey: options
|
|
|
+ });
|
|
|
+ } catch (e) {
|
|
|
+ console.error("credentials.create failed with exception", e);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("PublicKeyCredential created", newCredential);
|
|
|
+
|
|
|
+ let result;
|
|
|
+ try {
|
|
|
+ result = this.register(login, newCredential);
|
|
|
+ } catch (e) {
|
|
|
+ console.error("Registering new credentials with the server failed with exception", e);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("New login created", result);
|
|
|
+ }
|
|
|
+
|
|
|
+ async buildCredentialOptions(login) {
|
|
|
const response = await fetch(`/buildCredentialOptions?login=${login}`, {
|
|
|
method: 'GET',
|
|
|
headers: {
|
|
|
@@ -35,12 +96,10 @@ class Registration {
|
|
|
const options = await response.json();
|
|
|
|
|
|
if (options.status !== "ok") {
|
|
|
- console.error("Error in buildCredentialOptions");
|
|
|
- console.error(options.errorMessage);
|
|
|
- return;
|
|
|
- } else {
|
|
|
- console.log("Got options", options);
|
|
|
+ throw new Error(`Error in buildCredentialOptions: ${options.errorMessage}`);
|
|
|
}
|
|
|
+
|
|
|
+ console.log("Got options", options);
|
|
|
|
|
|
// Turn the challenge back into the accepted format of padded base64
|
|
|
options.challenge = coerceToArrayBuffer(options.challenge);
|
|
|
@@ -53,28 +112,10 @@ class Registration {
|
|
|
});
|
|
|
|
|
|
console.log("Options Formatted", options);
|
|
|
-
|
|
|
- // Create the Credential with navigator.credentials.create API
|
|
|
-
|
|
|
- let newCredential;
|
|
|
- try {
|
|
|
- newCredential = await navigator.credentials.create({
|
|
|
- publicKey: options
|
|
|
- });
|
|
|
- } catch (e) {
|
|
|
- console.error("credentials.create failed with exception", e);
|
|
|
- }
|
|
|
-
|
|
|
- console.log("PublicKeyCredential created", newCredential);
|
|
|
-
|
|
|
- // try {
|
|
|
- // register(newCredential);
|
|
|
- // } catch (e) {
|
|
|
- // console.error("Registering new credentials with the server failed with exception", e);
|
|
|
- // }
|
|
|
+ return options;
|
|
|
}
|
|
|
|
|
|
- async register(newCredential) {
|
|
|
+ async register(login, newCredential) {
|
|
|
// Move data into Arrays in case it is super long
|
|
|
// TODO: check!
|
|
|
let attestationObject = new Uint8Array(newCredential.response.attestationObject);
|
|
|
@@ -83,7 +124,7 @@ class Registration {
|
|
|
|
|
|
const data = {
|
|
|
id: newCredential.id,
|
|
|
- //rawId: coerceToBase64Url(rawId),
|
|
|
+ rawId: coerceToBase64Url(rawId),
|
|
|
type: newCredential.type,
|
|
|
extensions: newCredential.getClientExtensionResults(),
|
|
|
response: {
|
|
|
@@ -93,9 +134,9 @@ class Registration {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- let response = await fetch(`/registerCredential?login=`, {
|
|
|
- method: 'POST', // or 'PUT'
|
|
|
- body: JSON.stringify(formData), // data can be `string` or {object}!
|
|
|
+ let response = await fetch(`/registerCredential?login=${login}`, {
|
|
|
+ method: 'POST',
|
|
|
+ body: JSON.stringify(data),
|
|
|
headers: {
|
|
|
'Accept': 'application/json',
|
|
|
'Content-Type': 'application/json'
|
|
|
@@ -109,6 +150,8 @@ class Registration {
|
|
|
if (result.status !== "ok") {
|
|
|
throw new Error(`Error creating credential: ${result.errorMessage}`);
|
|
|
}
|
|
|
+
|
|
|
+ return result;
|
|
|
}
|
|
|
}
|
|
|
|