馃 distributed transcription service thistle.dunkirk.sh
1import { 2 startAuthentication, 3 startRegistration, 4} from "@simplewebauthn/browser"; 5import type { 6 PublicKeyCredentialCreationOptionsJSON, 7 PublicKeyCredentialRequestOptionsJSON, 8} from "@simplewebauthn/types"; 9 10/** 11 * Register a new passkey for the current user 12 */ 13export async function registerPasskey( 14 name?: string, 15): Promise<{ success: boolean; error?: string }> { 16 try { 17 // Get registration options from server 18 const optionsResponse = await fetch("/api/passkeys/register/options", { 19 method: "POST", 20 }); 21 22 if (!optionsResponse.ok) { 23 const error = await optionsResponse.json(); 24 return { 25 success: false, 26 error: error.error || "Failed to get registration options", 27 }; 28 } 29 30 const options: PublicKeyCredentialCreationOptionsJSON = 31 await optionsResponse.json(); 32 33 // Start browser passkey creation 34 let credential: Awaited<ReturnType<typeof startRegistration>>; 35 try { 36 credential = await startRegistration({ optionsJSON: options }); 37 } catch (err) { 38 // User cancelled or browser doesn't support passkeys 39 return { 40 success: false, 41 error: 42 err instanceof Error 43 ? err.message 44 : "Passkey registration was cancelled", 45 }; 46 } 47 48 // Verify with server 49 const verifyResponse = await fetch("/api/passkeys/register/verify", { 50 method: "POST", 51 headers: { "Content-Type": "application/json" }, 52 body: JSON.stringify({ 53 response: credential, 54 challenge: options.challenge, 55 name, 56 }), 57 }); 58 59 if (!verifyResponse.ok) { 60 const error = await verifyResponse.json(); 61 return { 62 success: false, 63 error: error.error || "Failed to verify passkey", 64 }; 65 } 66 67 return { success: true }; 68 } catch (err) { 69 return { 70 success: false, 71 error: err instanceof Error ? err.message : "Failed to register passkey", 72 }; 73 } 74} 75 76/** 77 * Authenticate with a passkey 78 */ 79export async function authenticateWithPasskey( 80 email?: string, 81): Promise<{ success: boolean; error?: string }> { 82 try { 83 // Get authentication options from server 84 const optionsResponse = await fetch("/api/passkeys/authenticate/options", { 85 method: "POST", 86 headers: { "Content-Type": "application/json" }, 87 body: JSON.stringify({ email }), 88 }); 89 90 if (!optionsResponse.ok) { 91 const error = await optionsResponse.json(); 92 return { 93 success: false, 94 error: error.error || "Failed to get authentication options", 95 }; 96 } 97 98 const options: PublicKeyCredentialRequestOptionsJSON = 99 await optionsResponse.json(); 100 101 // Start browser passkey authentication 102 let credential: Awaited<ReturnType<typeof startAuthentication>>; 103 try { 104 credential = await startAuthentication({ optionsJSON: options }); 105 } catch (err) { 106 // User cancelled or no passkey available 107 return { 108 success: false, 109 error: 110 err instanceof Error 111 ? err.message 112 : "Passkey authentication was cancelled", 113 }; 114 } 115 116 // Verify with server 117 const verifyResponse = await fetch("/api/passkeys/authenticate/verify", { 118 method: "POST", 119 headers: { "Content-Type": "application/json" }, 120 body: JSON.stringify({ 121 response: credential, 122 challenge: options.challenge, 123 }), 124 }); 125 126 if (!verifyResponse.ok) { 127 const error = await verifyResponse.json(); 128 return { 129 success: false, 130 error: error.error || "Failed to verify passkey", 131 }; 132 } 133 134 return { success: true }; 135 } catch (err) { 136 return { 137 success: false, 138 error: 139 err instanceof Error 140 ? err.message 141 : "Failed to authenticate with passkey", 142 }; 143 } 144} 145 146/** 147 * Check if passkeys are supported in this browser 148 */ 149export function isPasskeySupported(): boolean { 150 return ( 151 window.PublicKeyCredential !== undefined && 152 typeof window.PublicKeyCredential === "function" 153 ); 154}