馃 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: 72 err instanceof Error ? err.message : "Failed to register passkey", 73 }; 74 } 75} 76 77/** 78 * Authenticate with a passkey 79 */ 80export async function authenticateWithPasskey( 81 email?: string, 82): Promise<{ success: boolean; error?: string }> { 83 try { 84 // Get authentication options from server 85 const optionsResponse = await fetch("/api/passkeys/authenticate/options", { 86 method: "POST", 87 headers: { "Content-Type": "application/json" }, 88 body: JSON.stringify({ email }), 89 }); 90 91 if (!optionsResponse.ok) { 92 const error = await optionsResponse.json(); 93 return { 94 success: false, 95 error: error.error || "Failed to get authentication options", 96 }; 97 } 98 99 const options: PublicKeyCredentialRequestOptionsJSON = 100 await optionsResponse.json(); 101 102 // Start browser passkey authentication 103 let credential: Awaited<ReturnType<typeof startAuthentication>>; 104 try { 105 credential = await startAuthentication({ optionsJSON: options }); 106 } catch (err) { 107 // User cancelled or no passkey available 108 return { 109 success: false, 110 error: 111 err instanceof Error 112 ? err.message 113 : "Passkey authentication was cancelled", 114 }; 115 } 116 117 // Verify with server 118 const verifyResponse = await fetch("/api/passkeys/authenticate/verify", { 119 method: "POST", 120 headers: { "Content-Type": "application/json" }, 121 body: JSON.stringify({ 122 response: credential, 123 challenge: options.challenge, 124 }), 125 }); 126 127 if (!verifyResponse.ok) { 128 const error = await verifyResponse.json(); 129 return { 130 success: false, 131 error: error.error || "Failed to verify passkey", 132 }; 133 } 134 135 return { success: true }; 136 } catch (err) { 137 return { 138 success: false, 139 error: 140 err instanceof Error ? err.message : "Failed to authenticate with passkey", 141 }; 142 } 143} 144 145/** 146 * Check if passkeys are supported in this browser 147 */ 148export function isPasskeySupported(): boolean { 149 return ( 150 window.PublicKeyCredential !== undefined && 151 typeof window.PublicKeyCredential === "function" 152 ); 153}