diff --git a/src/actions/contact.ts b/src/actions/contact.ts index 807288a..b69426c 100644 --- a/src/actions/contact.ts +++ b/src/actions/contact.ts @@ -4,7 +4,7 @@ import type { ActionAPIContext } from "astro:actions"; import validator from "validator"; import SmsClient from "@lib/SmsGatewayClient.ts"; import Otp, { verifyOtp } from "@lib/Otp.ts"; -import CapServer from "@lib/CapAdapter"; +import { createCap } from "@lib/CapAdapter"; import { OTP_SUPER_SECRET_SALT, ANDROID_SMS_GATEWAY_RECIPIENT_PHONE, @@ -81,10 +81,12 @@ const submitActionDefinition = { }); } + const cap = createCap(context.session ?? null); + if ( !( /^[a-fA-F0-9]{16}:[a-fA-F0-9]{30}$/.test(input.captcha) && - (await CapServer.validateToken(input.captcha)) + (await cap.validateToken(input.captcha)) ) ) { throw new ActionError({ diff --git a/src/lib/CapAdapter.ts b/src/lib/CapAdapter.ts index 81fe97f..fa8a582 100644 --- a/src/lib/CapAdapter.ts +++ b/src/lib/CapAdapter.ts @@ -1,85 +1,42 @@ import Cap, { type ChallengeData } from "@cap.js/server"; -import { db, eq, and, gt, lte, Cap_Challenges, Cap_Tokens } from "astro:db"; +import type { AstroSession } from "astro"; -const cap = new Cap({ - storage: { - challenges: { - store: async (token: string, challengeData: ChallengeData) => { - const expires = challengeData.expires; - const data = challengeData.challenge; - await db - .insert(Cap_Challenges) - .values({ token: token, data: data, expires: expires }) - .onConflictDoUpdate({ - target: Cap_Challenges.token, - set: { data: data, expires: expires }, - }); +export function createCap(session: AstroSession | null) { + if (!session) { + throw new Error("Session context is required"); + } + return new Cap({ + storage: { + challenges: { + store: async (token: string, challengeData: ChallengeData) => { + session.set(`cap:challenge:${token}`, JSON.stringify(challengeData)); + }, + read: async (token: string) => { + const raw = await session.get(`cap:challenge:${token}`); + return raw ? (JSON.parse(raw) as ChallengeData) : null; + }, + delete: async (token: string) => { + session.delete(`cap:challenge:${token}`); + }, + deleteExpired: async () => { + // no-op: session store handles TTL itself + }, }, - read: async (token) => { - const result = await db - .select({ - challenge: Cap_Challenges.data, - expires: Cap_Challenges.expires, - }) - .from(Cap_Challenges) - .where( - and( - eq(Cap_Challenges.token, token), - gt(Cap_Challenges.expires, Date.now()), - ), - ) - .limit(1); - - const data = result[0] as ChallengeData; - - return result ? data : null; - }, - - delete: async (token) => { - await db.delete(Cap_Challenges).where(eq(Cap_Challenges.token, token)); - }, - - deleteExpired: async () => { - await db - .delete(Cap_Challenges) - .where(lte(Cap_Challenges.expires, Date.now())); + tokens: { + store: async (tokenKey: string, expires: number) => { + session.set(`cap:token:${tokenKey}`, String(expires)); + }, + get: async (tokenKey: string) => { + const raw = await session.get(`cap:token:${tokenKey}`); + return raw ? Number(raw) : null; + }, + delete: async (tokenKey: string) => { + session.delete(`cap:token:${tokenKey}`); + }, + deleteExpired: async () => { + // no-op: session store handles TTL itself + }, }, }, - tokens: { - store: async (tokenKey, expires) => { - await db - .insert(Cap_Tokens) - .values({ key: tokenKey, expires: expires }) - .onConflictDoUpdate({ - target: Cap_Tokens.key, - set: { expires: expires }, - }); - }, - - get: async (tokenKey) => { - const result = await db - .select({ expires: Cap_Tokens.expires }) - .from(Cap_Tokens) - .where( - and( - eq(Cap_Tokens.key, tokenKey), - gt(Cap_Tokens.expires, Date.now()), - ), - ) - .limit(1); - - return result ? result[0].expires : null; - }, - - delete: async (tokenKey) => { - await db.delete(Cap_Tokens).where(eq(Cap_Tokens.key, tokenKey)); - }, - - deleteExpired: async () => { - await db.delete(Cap_Tokens).where(lte(Cap_Tokens.expires, Date.now())); - }, - }, - }, -}); - -export default cap; + }); +} diff --git a/src/pages/cap/challenge.ts b/src/pages/cap/challenge.ts index 3dde79a..5417303 100644 --- a/src/pages/cap/challenge.ts +++ b/src/pages/cap/challenge.ts @@ -1,9 +1,10 @@ import type { APIRoute } from "astro"; -import cap from "@lib/CapAdapter"; +import { createCap } from "@lib/CapAdapter"; export const prerender = false; -export const POST: APIRoute = async () => { +export const POST: APIRoute = async (context) => { try { + const cap = createCap(context.session ?? null); return new Response(JSON.stringify(await cap.createChallenge()), { status: 200, }); diff --git a/src/pages/cap/redeem.ts b/src/pages/cap/redeem.ts index d8c078a..ed93e1c 100644 --- a/src/pages/cap/redeem.ts +++ b/src/pages/cap/redeem.ts @@ -1,12 +1,21 @@ import type { APIRoute } from "astro"; -import cap from "@lib/CapAdapter"; +import { createCap } from "@lib/CapAdapter"; export const prerender = false; -export const POST: APIRoute = async ({ request }) => { - const { token, solutions } = await request.json(); +export const POST: APIRoute = async (context) => { + if (!context.session) { + return new Response( + JSON.stringify({ success: false, error: "Session unavailable." }), + { status: 500 }, + ); + } + + const { token, solutions } = await context.request.json(); if (!token || !solutions) { return new Response(JSON.stringify({ success: false }), { status: 400 }); } + + const cap = createCap(context.session); return new Response( JSON.stringify(await cap.redeemChallenge({ token, solutions })), { status: 200 },