import { Hono } from "hono"; import * as swa from "@simplewebauthn/server"; import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { RP_ID, ORIGIN, db } from "./index.js"; import { sessionTable, userTable, webauthnChallenges } from "./db/schema.js"; import { stringify, parse } from "superjson"; import { setCookie } from "hono/cookie"; let app = new Hono(); export const LoginForm = () =>
; app.post("/register-begin", async c => { const username = randomUUID(); let options = await swa.generateRegistrationOptions({ rpName: "uneven", rpID: RP_ID, userName: username, authenticatorSelection: { residentKey: "required", userVerification: "preferred", }, }); await db.insert(webauthnChallenges).values({ key: "register:" + username, challenge: options.challenge }); return c.html(
); }); app.post("/register-finish", async c => { let { resp, username } = await c.req.json(); let [{ chall }] = await db.delete(webauthnChallenges).where(eq(webauthnChallenges.key, "register:" + username)).returning({ chall: webauthnChallenges.challenge }); let r = await swa.verifyRegistrationResponse({ response: resp, expectedChallenge: chall, expectedOrigin: ORIGIN }); if (!r.verified || !("registrationInfo" in r)) return c.html(

Could not verify registration response!

); await db.insert(userTable).values({ name: username, passkey: stringify(r.registrationInfo), passkeyId: r.registrationInfo!.credential.id }); return c.html(

You now have an account!

); }); app.post("/login-begin", async c => { let options = await swa.generateAuthenticationOptions({ rpID: RP_ID, userVerification: "preferred", }); let key = randomUUID(); await db.insert(webauthnChallenges).values({ challenge: options.challenge, key: "login:" + key }); return c.html(
); }); app.post("/login-finish", async c => { let { resp, key } = await c.req.json(); let [{ chall }] = await db.delete(webauthnChallenges).where(eq(webauthnChallenges.key, "login:" + key)).returning({ chall: webauthnChallenges.challenge }); let [{ id: userId, passkey }] = await db.select().from(userTable).where(user => eq(user.passkeyId, resp.id)); if (!passkey) return c.html(

Who are you?

); let r = await swa.verifyAuthenticationResponse({ response: resp, expectedChallenge: chall, expectedOrigin: ORIGIN, expectedRPID: RP_ID, requireUserVerification: false, credential: parse(passkey)!.credential, }); if (!r.verified) return c.html(

Could not verify authentication response!

); let uuid = randomUUID(); await db.insert(sessionTable).values({ userId, uuid }); setCookie(c, "session", uuid); return c.html(

Logged in!

); }); export default app;