Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,39 @@ customElements.define('passkey-submit', class extends HTMLElement {
const formData = new FormData();
try {
const credential = await this.obtainCredential(useConditionalMediation, signal);
const credentialJson = JSON.stringify(credential);

let credentialJson = "";
try {
credentialJson = JSON.stringify(credential);
} catch (error) {
// Check for 'TypeError' instead of relying on the exact error message.
if (error.name !== 'TypeError') {
throw error;
}

// Some password managers do not implement PublicKeyCredential.prototype.toJSON correctly,
// which is required for JSON.stringify() to work.
// e.g. https://www.1password.community/discussions/1password/typeerror-illegal-invocation-in-chrome-browser/47399
// Try and serialize the credential to JSON manually.
credentialJson = JSON.stringify({
authenticatorAttachment: credential.authenticatorAttachment,
clientExtensionResults: credential.getClientExtensionResults(),
id: credential.id,
rawId: this.convertToBase64(credential.rawId),
response: {
attestationObject: this.convertToBase64(credential.response.attestationObject),
authenticatorData: this.convertToBase64(credential.response.authenticatorData ?? credential.response.getAuthenticatorData?.() ?? undefined),
clientDataJSON: this.convertToBase64(credential.response.clientDataJSON),
publicKey: this.convertToBase64(credential.response.getPublicKey?.() ?? undefined),
publicKeyAlgorithm: credential.response.getPublicKeyAlgorithm?.() ?? undefined,
transports: credential.response.getTransports?.() ?? undefined,
signature: this.convertToBase64(credential.response.signature),
userHandle: this.convertToBase64(credential.response.userHandle),
},
type: credential.type,
});
}

formData.append(`${this.attrs.name}.CredentialJson`, credentialJson);
} catch (error) {
if (error.name === 'AbortError') {
Expand All @@ -115,6 +147,40 @@ customElements.define('passkey-submit', class extends HTMLElement {
this.internals.form.submit();
}

convertToBase64(o) {
if (!o) {
return undefined;
}

// Normalize Array to Uint8Array
if (Array.isArray(o)) {
o = Uint8Array.from(o);
}

// Normalize ArrayBuffer to Uint8Array
if (o instanceof ArrayBuffer) {
o = new Uint8Array(o);
}

// Convert Uint8Array to base64
if (o instanceof Uint8Array) {
let str = '';
for (let i = 0; i < o.byteLength; i++) {
str += String.fromCharCode(o[i]);
}
o = window.btoa(str);
}

if (typeof o !== 'string') {
throw new Error("Could not convert to base64 string");
}

// Convert base64 to base64url
o = o.replace(/\+/g, "-").replace(/\//g, "_").replace(/=*$/g, "");

return o;
}

async tryAutofillPasskey() {
if (browserSupportsPasskeys && this.attrs.operation === 'Request' && await PublicKeyCredential.isConditionalMediationAvailable?.()) {
await this.obtainAndSubmitCredential(/* useConditionalMediation */ true);
Expand Down
Loading