From 425f95af23ed2d1d0772392e6c5f0f7b4b7dfd1f Mon Sep 17 00:00:00 2001 From: Izan Gil <66965250+SrIzan10@users.noreply.github.com> Date: Sat, 4 Apr 2026 23:45:55 +0200 Subject: [PATCH] feat: proxy chillhop requests and add turnstile to account creation --- .env.example | 4 ++ bun.lock | 5 ++ package.json | 1 + src/lib/auth-client.ts | 3 +- src/lib/components/app/auth-dialog.svelte | 57 ++++++++++++++++-- src/lib/server/auth.ts | 58 ++++++++++++++++++- src/lib/stations/chillhop.ts | 2 +- src/routes/api/chstream/[fileId]/+server.ts | 58 +++++++++++++++++++ src/routes/demo/+page.svelte | 5 -- src/routes/demo/better-auth/+page.server.ts | 21 ------- src/routes/demo/better-auth/+page.svelte | 35 ----------- .../demo/better-auth/login/+page.server.ts | 58 ------------------- .../demo/better-auth/login/+page.svelte | 43 -------------- 13 files changed, 181 insertions(+), 169 deletions(-) create mode 100644 src/routes/api/chstream/[fileId]/+server.ts delete mode 100644 src/routes/demo/+page.svelte delete mode 100644 src/routes/demo/better-auth/+page.server.ts delete mode 100644 src/routes/demo/better-auth/+page.svelte delete mode 100644 src/routes/demo/better-auth/login/+page.server.ts delete mode 100644 src/routes/demo/better-auth/login/+page.svelte diff --git a/.env.example b/.env.example index c145e27..41b7b94 100644 --- a/.env.example +++ b/.env.example @@ -9,3 +9,7 @@ ORIGIN="" # For production use 32 characters and generated with high entropy # https://www.better-auth.com/docs/installation BETTER_AUTH_SECRET="" + +# Cloudflare Turnstile +PUBLIC_TURNSTILE_SITE_KEY="" +TURNSTILE_SECRET_KEY="" diff --git a/bun.lock b/bun.lock index b811409..9afb461 100644 --- a/bun.lock +++ b/bun.lock @@ -25,6 +25,7 @@ "svelte": "^5.0.0", "svelte-check": "^4.0.0", "svelte-sonner": "^0.3.28", + "svelte-turnstile": "^0.11.0", "tailwind-merge": "^3.2.0", "tailwind-variants": "^1.0.0", "tailwindcss": "^3.4.17", @@ -810,6 +811,8 @@ "svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="], + "svelte-turnstile": ["svelte-turnstile@0.11.0", "", { "dependencies": { "turnstile-types": "^1.2.3" }, "peerDependencies": { "svelte": "^3.58.0 || ^4.0.0 || ^5.0.0" } }, "sha512-2LFklx9JVsR3fJ7e3fGG1HEAWWEqRq1WfNaVrKgZJ+pzfY2NColiH+wH0kK2yX3DrcGLiJ9vBeTyiLFWotKpLA=="], + "tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="], "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], @@ -844,6 +847,8 @@ "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + "turnstile-types": ["turnstile-types@1.2.3", "", {}, "sha512-EDjhDB9TDwda2JRbhzO/kButPio3JgrC3gXMVAMotxldybTCJQVMvPNJ89rcAiN9vIrCb2i1E+VNBCqB8wue0A=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici": ["undici@7.24.4", "", {}, "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w=="], diff --git a/package.json b/package.json index 3bf2968..8437c90 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "svelte": "^5.0.0", "svelte-check": "^4.0.0", "svelte-sonner": "^0.3.28", + "svelte-turnstile": "^0.11.0", "tailwind-merge": "^3.2.0", "tailwind-variants": "^1.0.0", "tailwindcss": "^3.4.17", diff --git a/src/lib/auth-client.ts b/src/lib/auth-client.ts index 92501a0..33e3a6a 100644 --- a/src/lib/auth-client.ts +++ b/src/lib/auth-client.ts @@ -7,11 +7,12 @@ import type { auth } from '$lib/server/auth'; const accountNumberClient = { id: 'account-number', getActions: ($fetch) => ({ - createAccount: async (name?: string) => + createAccount: async (name?: string, turnstileToken?: string) => $fetch('/create-account', { method: 'POST', body: { name, + turnstileToken, }, }), signInAccountNumber: async (accountNumber: string) => diff --git a/src/lib/components/app/auth-dialog.svelte b/src/lib/components/app/auth-dialog.svelte index 8dd8cf8..ea932d4 100644 --- a/src/lib/components/app/auth-dialog.svelte +++ b/src/lib/components/app/auth-dialog.svelte @@ -13,6 +13,9 @@ import Plus from '@lucide/svelte/icons/plus'; import Loader2 from '@lucide/svelte/icons/loader-2'; import type { Passkey } from '@better-auth/passkey'; + import { Turnstile } from 'svelte-turnstile'; + import { dev } from '$app/environment'; + import { env as publicEnv } from '$env/dynamic/public'; const session = authClient.useSession(); @@ -27,6 +30,11 @@ let loadedPasskeysForUserId = $state(null); let authScreen = $state<'login' | 'create'>('login'); let passkeyName = $state(''); + let turnstileToken = $state(''); + let resetTurnstile = $state<(() => void) | undefined>(); + const turnstileSiteKey = $derived( + dev ? '1x00000000000000000000AA' : (publicEnv.PUBLIC_TURNSTILE_SITE_KEY ?? '') + ); const loadPasskeys = async () => { if (!user) { @@ -84,18 +92,51 @@ } ); - const createAccount = () => - runAuthAction( + const clearTurnstile = () => { + turnstileToken = ''; + resetTurnstile?.(); + }; + + const handleTurnstileCallback = ( + event: CustomEvent<{ token: string; preClearanceObtained: boolean }> + ) => { + turnstileToken = event.detail.token; + authMessage = ''; + }; + + const handleTurnstileError = () => { + clearTurnstile(); + authMessage = 'Turnstile verification failed. Please try again.'; + }; + + const handleTurnstileExpired = () => { + clearTurnstile(); + authMessage = 'Turnstile check expired. Please try again.'; + }; + + const createAccount = async () => { + if (!turnstileToken) { + authMessage = 'Please complete the Turnstile check before creating an account.'; + return; + } + + await runAuthAction( 'create-account', - () => authClient.createAccount(name), + () => authClient.createAccount(name, turnstileToken), 'Account creation failed', async () => { await session.get().refetch(); name = ''; authScreen = 'login'; + clearTurnstile(); } ); + if (busyAction !== 'create-account') { + clearTurnstile(); + } + }; + const signInWithPasskey = () => runAuthAction( 'passkey-sign-in', @@ -382,10 +423,18 @@ /> + + -

{passkeyMessage}

-
- -
diff --git a/src/routes/demo/better-auth/login/+page.server.ts b/src/routes/demo/better-auth/login/+page.server.ts deleted file mode 100644 index 107ef62..0000000 --- a/src/routes/demo/better-auth/login/+page.server.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { fail, redirect } from '@sveltejs/kit'; -import type { Actions, PageServerLoad } from './$types'; - -import { APIError } from 'better-auth'; - -const getErrorMessage = (error: unknown, fallback: string) => { - if (error instanceof APIError) return error.message || fallback; - if (error instanceof Error) return error.message || fallback; - if (typeof error === 'object' && error && 'message' in error && typeof error.message === 'string') { - return error.message || fallback; - } - return fallback; -}; - -export const load: PageServerLoad = (event) => { - if (event.locals.user) { - return redirect(302, '/demo/better-auth'); - } - return {}; -}; - -export const actions: Actions = { - signInAccountNumber: async (event) => { - const { auth } = event.locals; - - const formData = await event.request.formData(); - const accountNumber = formData.get('accountNumber')?.toString().replace(/\D/g, '') ?? ''; - - try { - await auth.api.signInAccountNumber({ - body: { - accountNumber, - }, - }); - } catch (error) { - console.error('Demo Better Auth account number sign-in failed', { accountNumber, error }); - return fail(error instanceof APIError ? 400 : 500, { - message: getErrorMessage(error, 'Account number sign-in failed'), - }); - } - - return redirect(302, '/demo/better-auth'); - }, - createAccount: async (event) => { - const { auth } = event.locals; - - try { - await auth.api.signInAnonymous(); - } catch (error) { - console.error('Demo Better Auth account creation failed', { error }); - return fail(error instanceof APIError ? 400 : 500, { - message: getErrorMessage(error, 'Account creation failed'), - }); - } - - return redirect(302, '/demo/better-auth'); - }, -}; diff --git a/src/routes/demo/better-auth/login/+page.svelte b/src/routes/demo/better-auth/login/+page.svelte deleted file mode 100644 index 7318b1c..0000000 --- a/src/routes/demo/better-auth/login/+page.svelte +++ /dev/null @@ -1,43 +0,0 @@ - - -

Account Login

-
- - - -
-

{form?.message ?? ''}

- -

{passkeyError}