This commit is contained in:
81
src/pages/api/auth/passkey/register/finish.ts
Normal file
81
src/pages/api/auth/passkey/register/finish.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import { verifyRegistrationResponse } from "@simplewebauthn/server";
|
||||
import { db } from "../../../../../db";
|
||||
import { passkeys, passkeyChallenges } from "../../../../../db/schema";
|
||||
import { eq, and, gt } from "drizzle-orm";
|
||||
|
||||
export const POST: APIRoute = async ({ request, locals }) => {
|
||||
const user = locals.user;
|
||||
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
||||
status: 401,
|
||||
});
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
|
||||
const clientDataJSON = Buffer.from(
|
||||
body.response.clientDataJSON,
|
||||
"base64url",
|
||||
).toString("utf-8");
|
||||
const clientData = JSON.parse(clientDataJSON);
|
||||
const challenge = clientData.challenge;
|
||||
|
||||
const dbChallenge = await db.query.passkeyChallenges.findFirst({
|
||||
where: and(
|
||||
eq(passkeyChallenges.challenge, challenge),
|
||||
eq(passkeyChallenges.userId, user.id),
|
||||
gt(passkeyChallenges.expiresAt, new Date()),
|
||||
),
|
||||
});
|
||||
|
||||
if (!dbChallenge) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Invalid or expired challenge" }),
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let verification;
|
||||
try {
|
||||
verification = await verifyRegistrationResponse({
|
||||
response: body,
|
||||
expectedChallenge: challenge,
|
||||
expectedOrigin: new URL(request.url).origin,
|
||||
expectedRPID: new URL(request.url).hostname,
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: (error as Error).message }), {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
if (verification.verified && verification.registrationInfo) {
|
||||
const { registrationInfo } = verification;
|
||||
const { credential, credentialDeviceType, credentialBackedUp } =
|
||||
registrationInfo;
|
||||
|
||||
await db.insert(passkeys).values({
|
||||
id: credential.id,
|
||||
userId: user.id,
|
||||
publicKey: Buffer.from(credential.publicKey).toString("base64"),
|
||||
counter: credential.counter,
|
||||
deviceType: credentialDeviceType,
|
||||
backedUp: credentialBackedUp,
|
||||
transports: body.response.transports
|
||||
? JSON.stringify(body.response.transports)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
await db
|
||||
.delete(passkeyChallenges)
|
||||
.where(eq(passkeyChallenges.challenge, challenge));
|
||||
|
||||
return new Response(JSON.stringify({ verified: true }));
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ verified: false }), { status: 400 });
|
||||
};
|
||||
Reference in New Issue
Block a user