62 lines
2.1 KiB
TypeScript
62 lines
2.1 KiB
TypeScript
import { getSessionUser } from '$lib/server/auth/session';
|
|
import { consumeChallenge } from '$lib/server/auth/passkeys';
|
|
import { getDatabase } from '$lib/server/database';
|
|
import {
|
|
verifyRegistrationResponse,
|
|
type RegistrationResponseJSON,
|
|
} from '@simplewebauthn/server';
|
|
import { error, json } from '@sveltejs/kit';
|
|
import { Buffer } from 'node:buffer';
|
|
import type { RequestHandler } from './$types';
|
|
|
|
export const POST: RequestHandler = async ({ cookies, request }) => {
|
|
const user = await getSessionUser(cookies);
|
|
if (!user) error(401, 'Sign in before registering a passkey.');
|
|
const body = (await request.json()) as {
|
|
challengeId?: string;
|
|
name?: string;
|
|
response?: RegistrationResponseJSON;
|
|
};
|
|
if (!body.challengeId || !body.response)
|
|
error(400, 'Incomplete passkey registration.');
|
|
const database = await getDatabase();
|
|
const challenge = await consumeChallenge(
|
|
database,
|
|
body.challengeId,
|
|
'registration',
|
|
user.id,
|
|
);
|
|
if (!challenge) error(400, 'The passkey registration expired. Try again.');
|
|
const verification = await verifyRegistrationResponse({
|
|
response: body.response,
|
|
expectedChallenge: String(challenge.challenge),
|
|
expectedOrigin: String(challenge.origin),
|
|
expectedRPID: String(challenge.rp_id),
|
|
requireUserVerification: true,
|
|
});
|
|
if (!verification.verified || !verification.registrationInfo)
|
|
error(400, 'Passkey registration could not be verified.');
|
|
const { credential, credentialDeviceType, credentialBackedUp } =
|
|
verification.registrationInfo;
|
|
const name =
|
|
String(body.name || 'My passkey')
|
|
.trim()
|
|
.slice(0, 60) || 'My passkey';
|
|
await (
|
|
await database.prepare(`INSERT INTO passkeys(id,user_id,webauthn_user_id,public_key,counter,device_type,backed_up,transports,name,created_at)
|
|
VALUES(?,?,?,?,?,?,?,?,?,?)`)
|
|
).run(
|
|
credential.id,
|
|
user.id,
|
|
Buffer.from(user.id).toString('base64url'),
|
|
credential.publicKey,
|
|
credential.counter,
|
|
credentialDeviceType,
|
|
credentialBackedUp ? 1 : 0,
|
|
credential.transports ? JSON.stringify(credential.transports) : null,
|
|
name,
|
|
new Date().toISOString(),
|
|
);
|
|
return json({ verified: true });
|
|
};
|