diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-11-30 00:01:07 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2025-11-30 00:01:07 +0000 |
| commit | b12c1c3a82941f2767ade8f497db56933415b94d (patch) | |
| tree | b1fa65f111c21bfb996c8b99ebff2d60c11a5876 /packages/trpc/lib | |
| parent | 4898b6be87c6edec8c74d69317899ce918c550ad (diff) | |
| download | karakeep-b12c1c3a82941f2767ade8f497db56933415b94d.tar.zst | |
feat: add support for turnstile on signup
Diffstat (limited to 'packages/trpc/lib')
| -rw-r--r-- | packages/trpc/lib/turnstile.ts | 71 |
1 files changed, 71 insertions, 0 deletions
diff --git a/packages/trpc/lib/turnstile.ts b/packages/trpc/lib/turnstile.ts new file mode 100644 index 00000000..3ba25f05 --- /dev/null +++ b/packages/trpc/lib/turnstile.ts @@ -0,0 +1,71 @@ +import { z } from "zod"; + +import serverConfig from "@karakeep/shared/config"; +import logger from "@karakeep/shared/logger"; + +const TurnstileVerifyResponseSchema = z.object({ + success: z.boolean(), + challenge_ts: z.string().optional(), + hostname: z.string().optional(), + "error-codes": z.array(z.string()).optional(), +}); + +export async function verifyTurnstileToken( + token: string, + remoteIp?: string | null, +) { + if (!serverConfig.auth.turnstile.enabled) { + return { success: true }; + } + + if (!token) { + return { success: false, "error-codes": ["missing-input-response"] }; + } + + const body = new URLSearchParams(); + body.append("secret", serverConfig.auth.turnstile.secretKey!); + body.append("response", token); + if (remoteIp) { + body.append("remoteip", remoteIp); + } + + try { + const response = await fetch( + "https://challenges.cloudflare.com/turnstile/v0/siteverify", + { + method: "POST", + body, + }, + ); + + if (!response.ok) { + logger.warn( + `[Turnstile] Verification request failed with status ${response.status}`, + ); + return { success: false, "error-codes": ["request-not-ok"] }; + } + + const json = await response.json(); + const parseResult = TurnstileVerifyResponseSchema.safeParse(json); + + if (!parseResult.success) { + logger.warn("[Turnstile] Invalid response format", { + error: parseResult.error, + remoteIp, + }); + return { success: false, "error-codes": ["invalid-response"] }; + } + + const parsed = parseResult.data; + if (!parsed.success) { + logger.warn("[Turnstile] Verification failed", { + errorCodes: parsed["error-codes"], + remoteIp, + }); + } + return parsed; + } catch (error) { + logger.warn("[Turnstile] Verification threw", { error, remoteIp }); + return { success: false, "error-codes": ["internal-error"] }; + } +} |
