import { ensureBootstrapAdmin } from '$lib/server/auth/bootstrap'; import { consumeChallenge } from '$lib/server/auth/passkeys'; import { createSession, recordLogin } from '$lib/server/auth/session'; import { getDatabase } from '$lib/server/database'; import { verifyAuthenticationResponse, type AuthenticationResponseJSON, type AuthenticatorTransportFuture } from '@simplewebauthn/server'; import { error, json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; function bytes(value: unknown): Uint8Array { if (value instanceof Uint8Array) { const copy = new Uint8Array(value.byteLength); copy.set(value); return copy; } if (value instanceof ArrayBuffer) return new Uint8Array(value.slice(0)); if (Array.isArray(value)) return new Uint8Array(value); throw new Error('Invalid passkey public key.'); } export const POST: RequestHandler = async ({ cookies, request }) => { const body = await request.json() as { challengeId?: string; response?: AuthenticationResponseJSON }; if (!body.challengeId || !body.response) error(400, 'Incomplete passkey authentication.'); const database = await getDatabase(); const challenge = await consumeChallenge(database, body.challengeId, 'authentication'); if (!challenge) error(400, 'The passkey request expired. Try again.'); const passkey = await (await database.prepare(`SELECT passkeys.*,user_status.blocked_at,user_status.banned_until,deleted_accounts.deleted_at FROM passkeys JOIN users ON users.id=passkeys.user_id LEFT JOIN user_status ON user_status.user_id=users.id LEFT JOIN deleted_accounts ON deleted_accounts.user_id=users.id WHERE passkeys.id=?`)).get(body.response.id); if (!passkey) error(400, 'This passkey is not registered with SHD.build.'); const unavailable = passkey.deleted_at || passkey.blocked_at || (typeof passkey.banned_until === 'string' && new Date(passkey.banned_until) > new Date()); if (unavailable) { await recordLogin(String(passkey.user_id), 'passkey', false); error(403, 'This account is not permitted to sign in.'); } const transports = passkey.transports ? JSON.parse(String(passkey.transports)) as AuthenticatorTransportFuture[] : undefined; const verification = await verifyAuthenticationResponse({ response: body.response, expectedChallenge: String(challenge.challenge), expectedOrigin: String(challenge.origin), expectedRPID: String(challenge.rp_id), credential: { id: String(passkey.id), publicKey: bytes(passkey.public_key), counter: Number(passkey.counter), transports }, requireUserVerification: true }); if (!verification.verified) { await recordLogin(String(passkey.user_id), 'passkey', false); error(400, 'Passkey authentication could not be verified.'); } const now = new Date().toISOString(); await (await database.prepare('UPDATE passkeys SET counter=?,last_used_at=? WHERE id=?')) .run(verification.authenticationInfo.newCounter, now, passkey.id); await ensureBootstrapAdmin(database, String(passkey.user_id)); await createSession(cookies, String(passkey.user_id)); await recordLogin(String(passkey.user_id), 'passkey', true); return json({ verified: true, redirectTo: '/account' }); };