All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m57s
83 lines
2.4 KiB
TypeScript
83 lines
2.4 KiB
TypeScript
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) {
|
|
console.error("Passkey registration verification failed:", error);
|
|
return new Response(JSON.stringify({ error: "Verification failed" }), {
|
|
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 });
|
|
};
|