document.addEventListener('DOMContentLoaded', () => { // If the browser doesn't support WebAuthn, remove the register elements. if (!window.PublicKeyCredential) { const registerEls = document.querySelectorAll('[data-webauthn-register]'); for (const regEl of registerEls) regEl.remove(); } document.getElementById('webAuthnRegister')?.addEventListener('submit', async (ev) => { ev.preventDefault(); if (!window.PublicKeyCredential) { alert('Your browser does not support WebAuthn.'); return; } const bgnRegisterRes = await fetch('/auth/webauthn/register'); const registerData = await bgnRegisterRes.json(); registerData.publicKey.challenge = base64ToArrayBuffer(registerData.publicKey.challenge); registerData.publicKey.user.id = base64ToArrayBuffer(registerData.publicKey.user.id); if (registerData.publicKey.excludeCredentials) { for (let i = 0; i < registerData.publicKey.excludeCredentials.length; i++) { registerData.publicKey.excludeCredentials[i].id = base64ToArrayBuffer(registerData.publicKey.excludeCredentials[i].id); } } const cred = await navigator.credentials.create({ publicKey: registerData.publicKey, }) if (!cred) { return; } const form = new FormData(ev.target! as HTMLFormElement); let name = form.get('name'); if (!name || name.length === 0) { name = 'WebAuthn Key'; } const credentialCreationRes = (cred as PublicKeyCredential).response as AuthenticatorAttestationResponse; const attestationObj = credentialCreationRes.attestationObject; const clientDataJSON = credentialCreationRes.clientDataJSON; const rawID = (cred as PublicKeyCredential).rawId; const fnRegisterRes = await fetch('/user/webauthn/register', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ id: cred.id, name: name, rawID: arrayBufferToBase64(rawID), type: cred.type, response: { attestationObject: arrayBufferToBase64(attestationObj), clientDataJSON: arrayBufferToBase64(clientDataJSON), }, }), }); if (fnRegisterRes.status != 200) { alert('Failed to register WebAuthn key.'); return; } }) }) function arrayBufferToBase64(arrayBuffer: ArrayBufferLike) { const base64String = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))); return base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } function base64ToArrayBuffer(base64: string) { const binaryString = atob(base64.replace(/-/g, '+').replace(/_/g, '/')); const length = binaryString.length; const bytes = new Uint8Array(length); for (let i = 0; i < length; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes.buffer; }