Files
chronus/src/pages/api/auth/passkey/login/finish.ts
Atridad Lahiji e99e042eea
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m9s
Fixed Origin mismatch for passkeys
2026-02-13 11:35:06 -07:00

97 lines
2.6 KiB
TypeScript

import type { APIRoute } from "astro";
import { verifyAuthenticationResponse } from "@simplewebauthn/server";
import { db } from "../../../../../db";
import { users, passkeys, passkeyChallenges } from "../../../../../db/schema";
import { eq, and, gt } from "drizzle-orm";
import { setAuthCookie, getOrigin } from "../../../../../lib/auth";
export const POST: APIRoute = async ({ request, cookies }) => {
const body = await request.json();
const { id } = body;
const passkey = await db.query.passkeys.findFirst({
where: eq(passkeys.id, id),
});
if (!passkey) {
return new Response(JSON.stringify({ error: "Passkey not found" }), {
status: 400,
});
}
const user = await db.query.users.findFirst({
where: eq(users.id, passkey.userId),
});
if (!user) return new Response(null, { status: 400 });
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),
gt(passkeyChallenges.expiresAt, new Date()),
),
});
if (!dbChallenge) {
return new Response(
JSON.stringify({ error: "Invalid or expired challenge" }),
{
status: 400,
},
);
}
let verification;
try {
const { origin, hostname } = getOrigin();
verification = await verifyAuthenticationResponse({
response: body,
expectedChallenge: challenge as string,
expectedOrigin: origin,
expectedRPID: hostname,
credential: {
id: passkey.id,
publicKey: new Uint8Array(Buffer.from(passkey.publicKey, "base64")),
counter: passkey.counter,
transports: passkey.transports
? JSON.parse(passkey.transports)
: undefined,
},
});
} catch (error) {
console.error("Passkey authentication verification failed:", error);
return new Response(JSON.stringify({ error: "Verification failed" }), {
status: 400,
});
}
if (verification.verified) {
const { authenticationInfo } = verification;
await db
.update(passkeys)
.set({
counter: authenticationInfo.newCounter,
lastUsedAt: new Date(),
})
.where(eq(passkeys.id, passkey.id));
setAuthCookie(cookies, user.id);
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 });
};