diff options
| author | Joe Carstairs <me@joeac.net> | 2025-12-18 11:03:32 +0000 |
|---|---|---|
| committer | Joe Carstairs <me@joeac.net> | 2025-12-18 11:03:32 +0000 |
| commit | 40d6c7f248c3fe506690ee3ccb3894f952e164ed (patch) | |
| tree | fe135c14cb58385d109a3bfcadde46dc5ffcafba /website/src/actions/otp/verify-otp.ts | |
| parent | a81d1de1e58ee2c314dbbea941dcb4e4a776ed84 (diff) | |
otp actions
Diffstat (limited to 'website/src/actions/otp/verify-otp.ts')
| -rw-r--r-- | website/src/actions/otp/verify-otp.ts | 48 |
1 files changed, 48 insertions, 0 deletions
diff --git a/website/src/actions/otp/verify-otp.ts b/website/src/actions/otp/verify-otp.ts new file mode 100644 index 0000000..726786e --- /dev/null +++ b/website/src/actions/otp/verify-otp.ts @@ -0,0 +1,48 @@ +import { randomBytes } from "node:crypto"; +import { z } from "astro/zod"; +import { defineAction } from "astro:actions"; +import { and, db, eq, gte, Otp, SendmailToken } from "astro:db"; + +export default defineAction({ + input: z.object({ + guess: z.string().length(6), + lenient: z.boolean().default(false), + userId: z.string().nonempty(), + }), + handler: verifyOtp, +}); + +async function verifyOtp({ guess, lenient, userId }: VerifyOtpParams) { + const leniency = lenient ? 1000 * 60 : 0; + const isOtpCorrect = + (await db.$count( + Otp, + and( + eq(Otp.userId, userId), + eq(Otp.value, guess), + gte(Otp.validUntil, Date.now() - leniency), + ), + )) > 0; + + if (!isOtpCorrect) { + return false; + } + + await db.delete(Otp).where(and(eq(Otp.userId, userId), eq(Otp.value, guess))); + + const token = randomBytes(256).toString("hex"); + await db.insert(SendmailToken).values({ + userId, + value: token, + createdAt: Date.now(), + validUntil: Date.now() + 60_000, + }); + + return token; +} + +type VerifyOtpParams = { + guess: string; + lenient: boolean; + userId: string; +}; |
