// Cognito WebAuthn helpers for Flutter web (mirrors Amplify JS passkey serde). (function (global) { function base64UrlToBuffer(base64url) { var base64 = String(base64url).replace(/-/g, "+").replace(/_/g, "/"); while (base64.length % 4) base64 += "="; var binary = atob(base64); var bytes = new Uint8Array(binary.length); for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); return bytes.buffer; } function bufferToBase64Url(buffer) { var bytes = new Uint8Array(buffer); var binary = ""; for (var i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); } function deserializeGetOptions(input) { return Object.assign({}, input, { challenge: base64UrlToBuffer(input.challenge), allowCredentials: (input.allowCredentials || []).map(function (c) { return Object.assign({}, c, { id: base64UrlToBuffer(c.id) }); }), }); } function deserializeCreateOptions(input) { return Object.assign({}, input, { challenge: base64UrlToBuffer(input.challenge), user: Object.assign({}, input.user, { id: base64UrlToBuffer(input.user.id), }), excludeCredentials: (input.excludeCredentials || []).map(function (c) { return Object.assign({}, c, { id: base64UrlToBuffer(c.id) }); }), }); } function serializeAssertion(credential) { var response = { clientDataJSON: bufferToBase64Url(credential.response.clientDataJSON), authenticatorData: bufferToBase64Url(credential.response.authenticatorData), signature: bufferToBase64Url(credential.response.signature), }; if (credential.response.userHandle) { response.userHandle = bufferToBase64Url(credential.response.userHandle); } var result = { id: credential.id, rawId: bufferToBase64Url(credential.rawId), type: credential.type, clientExtensionResults: credential.getClientExtensionResults ? credential.getClientExtensionResults() : {}, response: response, }; if (credential.authenticatorAttachment) { result.authenticatorAttachment = credential.authenticatorAttachment; } return result; } function serializeAttestation(credential) { var response = { clientDataJSON: bufferToBase64Url(credential.response.clientDataJSON), attestationObject: bufferToBase64Url(credential.response.attestationObject), }; if (credential.response.getTransports) { response.transports = credential.response.getTransports(); } if (credential.response.getPublicKeyAlgorithm) { response.publicKeyAlgorithm = credential.response.getPublicKeyAlgorithm(); } if (credential.response.getAuthenticatorData) { response.authenticatorData = bufferToBase64Url( credential.response.getAuthenticatorData() ); } if (credential.response.getPublicKey) { var pk = credential.response.getPublicKey(); if (pk) response.publicKey = bufferToBase64Url(pk); } var result = { type: credential.type, id: credential.id, rawId: bufferToBase64Url(credential.rawId), clientExtensionResults: credential.getClientExtensionResults ? credential.getClientExtensionResults() : {}, response: response, }; if (credential.authenticatorAttachment) { result.authenticatorAttachment = credential.authenticatorAttachment; } return result; } // A DOMException crossing into Dart is an opaque object exposing only // `message`, so its `name` — the only thing that separates "the operator // dismissed the prompt" from "the ceremony is misconfigured" — would be lost. // Prefix it onto the message so the Dart side can classify the rejection // instead of reporting it as an application error (BUG-542). function passkeyError(e, fallbackName) { var name = (e && e.name) || fallbackName || "WebAuthnError"; var message = (e && e.message) || String(e || ""); return new Error("OwnaWebAuthn:" + name + ":" + message); } global.ownaWebAuthn = { getPasskey: async function (optionsJson) { var options = typeof optionsJson === "string" ? JSON.parse(optionsJson) : optionsJson; var credential; try { credential = await navigator.credentials.get({ publicKey: deserializeGetOptions(options), }); } catch (e) { throw passkeyError(e); } // A null credential is the same outcome as a dismissal: the ceremony // produced nothing, and nothing is broken. if (!credential) { throw passkeyError( { name: "NotAllowedError", message: "Passkey authentication cancelled" } ); } return JSON.stringify(serializeAssertion(credential)); }, createPasskey: async function (optionsJson) { var options = typeof optionsJson === "string" ? JSON.parse(optionsJson) : optionsJson; var credential; try { credential = await navigator.credentials.create({ publicKey: deserializeCreateOptions(options), }); } catch (e) { throw passkeyError(e); } if (!credential) { throw passkeyError( { name: "NotAllowedError", message: "Passkey registration cancelled" } ); } return JSON.stringify(serializeAttestation(credential)); }, }; })(window);