From b8352566afab18916eb53fef55b7a8a7ad8c1166 Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Wed, 8 Oct 2025 16:11:18 +0200 Subject: [PATCH 1/5] refactor: sign up verification and schema --- bun.lock | 3 +++ package.json | 1 + src/instrumentation-client.ts | 10 ++++++++++ src/server/auth/auth-actions.ts | 28 ++++++++++++++++++++++++++-- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/instrumentation-client.ts diff --git a/bun.lock b/bun.lock index c3edf056c..be36d61e0 100644 --- a/bun.lock +++ b/bun.lock @@ -52,6 +52,7 @@ "@vercel/otel": "^1.13.0", "@vercel/speed-insights": "^1.2.0", "ansis": "^3.17.0", + "botid": "^1.5.8", "cheerio": "^1.0.0", "chrono-node": "^2.8.4", "class-variance-authority": "^0.7.1", @@ -1316,6 +1317,8 @@ "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + "botid": ["botid@1.5.8", "", { "peerDependencies": { "next": "*", "react": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["next", "react"] }, "sha512-1A/TvyoLtYLlncd30Uyp6ErAEHj4lSpOKqYTJSxX+aSaFUyYSX6rZuYDSX7Zp1kRnoraWHa/4K72mHcjRsUIlQ=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], diff --git a/package.json b/package.json index 042f5a8b8..0385e7cc9 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@vercel/otel": "^1.13.0", "@vercel/speed-insights": "^1.2.0", "ansis": "^3.17.0", + "botid": "^1.5.8", "cheerio": "^1.0.0", "chrono-node": "^2.8.4", "class-variance-authority": "^0.7.1", diff --git a/src/instrumentation-client.ts b/src/instrumentation-client.ts new file mode 100644 index 000000000..1ab8920e7 --- /dev/null +++ b/src/instrumentation-client.ts @@ -0,0 +1,10 @@ +import { initBotId } from 'botid/client/core' + +initBotId({ + protect: [ + { + path: '/sign-up', + method: 'POST', + }, + ], +}) diff --git a/src/server/auth/auth-actions.ts b/src/server/auth/auth-actions.ts index 56687b8ea..2c7f60427 100644 --- a/src/server/auth/auth-actions.ts +++ b/src/server/auth/auth-actions.ts @@ -12,17 +12,19 @@ import { shouldWarnAboutAlternateEmail, validateEmail, } from '@/server/auth/validate-email' -import { Provider } from '@supabase/supabase-js' +import { checkBotId } from 'botid/server' import { returnValidationErrors } from 'next-safe-action' import { headers } from 'next/headers' import { redirect } from 'next/navigation' import { z } from 'zod' import { forgotPasswordSchema, signInSchema, signUpSchema } from './auth.types' +const ProviderSchema = z.enum(['google', 'github']) + export const signInWithOAuthAction = actionClient .schema( z.object({ - provider: z.string() as unknown as z.ZodType, + provider: ProviderSchema, returnTo: relativeUrlSchema.optional(), }) ) @@ -89,6 +91,27 @@ export const signUpAction = actionClient }) } + // bot detection + const verification = await checkBotId() + + if (verification.isBot) { + l.warn( + { + key: 'sign_up_action:bot_detection_triggered', + context: { + email, + verification, + }, + }, + `Bot detection prevented sign up for: ${email}` + ) + + return returnServerError( + 'Access denied. Please contact support if this issue persists.' + ) + } + + // email validation const validationResult = await validateEmail(email) if (validationResult?.data) { @@ -103,6 +126,7 @@ export const signUpAction = actionClient } } + // sign up const { error } = await supabase.auth.signUp({ email, password, From 0b2a7f3e7b08d270265047cc57bf1db479313a9d Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Wed, 8 Oct 2025 16:15:56 +0200 Subject: [PATCH 2/5] add: flags --- src/configs/flags.ts | 1 + src/instrumentation-client.ts | 19 +++++++++++-------- src/server/auth/auth-actions.ts | 33 ++++++++++++++++++--------------- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/configs/flags.ts b/src/configs/flags.ts index 7171d183e..cac8bf13c 100644 --- a/src/configs/flags.ts +++ b/src/configs/flags.ts @@ -1,4 +1,5 @@ export const ALLOW_SEO_INDEXING = process.env.ALLOW_SEO_INDEXING === '1' +export const USE_BOT_ID = process.env.NEXT_PUBLIC_USE_BOT_ID === '1' export const VERBOSE = process.env.NEXT_PUBLIC_VERBOSE === '1' export const INCLUDE_BILLING = process.env.NEXT_PUBLIC_INCLUDE_BILLING === '1' export const USE_MOCK_DATA = diff --git a/src/instrumentation-client.ts b/src/instrumentation-client.ts index 1ab8920e7..e3c95a484 100644 --- a/src/instrumentation-client.ts +++ b/src/instrumentation-client.ts @@ -1,10 +1,13 @@ import { initBotId } from 'botid/client/core' +import { USE_BOT_ID } from './configs/flags' -initBotId({ - protect: [ - { - path: '/sign-up', - method: 'POST', - }, - ], -}) +if (USE_BOT_ID) { + initBotId({ + protect: [ + { + path: '/sign-up', + method: 'POST', + }, + ], + }) +} diff --git a/src/server/auth/auth-actions.ts b/src/server/auth/auth-actions.ts index 2c7f60427..f28aa767f 100644 --- a/src/server/auth/auth-actions.ts +++ b/src/server/auth/auth-actions.ts @@ -1,5 +1,6 @@ 'use server' +import { USE_BOT_ID } from '@/configs/flags' import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls' import { USER_MESSAGES } from '@/configs/user-messages' import { actionClient } from '@/lib/clients/action' @@ -92,23 +93,25 @@ export const signUpAction = actionClient } // bot detection - const verification = await checkBotId() - - if (verification.isBot) { - l.warn( - { - key: 'sign_up_action:bot_detection_triggered', - context: { - email, - verification, + if (USE_BOT_ID) { + const verification = await checkBotId() + + if (verification.isBot) { + l.warn( + { + key: 'sign_up_action:bot_detection_triggered', + context: { + email, + verification, + }, }, - }, - `Bot detection prevented sign up for: ${email}` - ) + `Bot detection prevented sign up for: ${email}` + ) - return returnServerError( - 'Access denied. Please contact support if this issue persists.' - ) + return returnServerError( + 'Access denied. Please contact support if this issue persists.' + ) + } } // email validation From 4302e39bf4d2b5447d7e886e73fd0e6b5e27ee99 Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Wed, 8 Oct 2025 16:24:14 +0200 Subject: [PATCH 3/5] fix: integrate in config --- next.config.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/next.config.mjs b/next.config.mjs index d813236b4..6904e5f70 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,3 +1,5 @@ +import { withBotId } from 'botid/next/config'; + /** @type {import('next').NextConfig} */ const config = { eslint: { @@ -82,4 +84,6 @@ const config = { skipTrailingSlashRedirect: true, } -export default config +const exportedConfig = process.env.NEXT_PUBLIC_USE_BOT_ID === '1' ? withBotId(config) : config + +export default exportedConfig From 0302ab59693636f209a79610c11ca170bc900b7e Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Wed, 8 Oct 2025 16:39:34 +0200 Subject: [PATCH 4/5] improve: logs --- src/server/auth/auth-actions.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/server/auth/auth-actions.ts b/src/server/auth/auth-actions.ts index f28aa767f..367625529 100644 --- a/src/server/auth/auth-actions.ts +++ b/src/server/auth/auth-actions.ts @@ -111,6 +111,17 @@ export const signUpAction = actionClient return returnServerError( 'Access denied. Please contact support if this issue persists.' ) + } else { + l.info( + { + key: 'sign_up_action:bot_detection_passed', + context: { + email, + verification, + }, + }, + `Bot detection passed sign up for: ${email}` + ) } } From 7eda0aae9809d0697599505fe9f107944881aabb Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Wed, 8 Oct 2025 17:42:35 +0200 Subject: [PATCH 5/5] fix: adjust for next for older next version --- src/app/layout.tsx | 9 +++++++++ src/instrumentation-client.ts | 13 ------------- 2 files changed, 9 insertions(+), 13 deletions(-) delete mode 100644 src/instrumentation-client.ts diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7ec7b1ee7..163a2d2d3 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -10,11 +10,19 @@ import { GTMHead } from '@/features/google-tag-manager' import { Toaster } from '@/ui/primitives/toaster' import { Analytics } from '@vercel/analytics/next' import { SpeedInsights } from '@vercel/speed-insights/next' +import { BotIdClient } from 'botid/client' import Head from 'next/head' import { Metadata } from 'next/types' import { Suspense } from 'react' import { Body } from './layout.client' +const protectedRoutes = [ + { + path: '/sign-up', + method: 'POST', + }, +] + export const metadata: Metadata = { metadataBase: new URL(BASE_URL), title: { @@ -42,6 +50,7 @@ export default function RootLayout({ + diff --git a/src/instrumentation-client.ts b/src/instrumentation-client.ts deleted file mode 100644 index e3c95a484..000000000 --- a/src/instrumentation-client.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { initBotId } from 'botid/client/core' -import { USE_BOT_ID } from './configs/flags' - -if (USE_BOT_ID) { - initBotId({ - protect: [ - { - path: '/sign-up', - method: 'POST', - }, - ], - }) -}