From 86aa48974d505c8d0dc43bbeb164a82670bdaf7b Mon Sep 17 00:00:00 2001 From: javayhu Date: Sat, 16 Aug 2025 10:38:16 +0800 Subject: [PATCH] chore: update better-auth package to version 1.3.6 and add captcha plugin --- package.json | 2 +- pnpm-lock.yaml | 170 ++++++++++++++++++++++++++---------------------- src/lib/auth.ts | 48 +++++++++----- 3 files changed, 125 insertions(+), 95 deletions(-) diff --git a/package.json b/package.json index 2eb576c..aafe9b5 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@vercel/speed-insights": "^1.2.0", "@widgetbot/react-embed": "^1.9.0", "ai": "^5.0.0", - "better-auth": "^1.1.19", + "better-auth": "^1.3.6", "canvas-confetti": "^1.9.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13a6594..1802785 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -186,8 +186,8 @@ importers: specifier: ^5.0.0 version: 5.0.0(zod@4.0.17) better-auth: - specifier: ^1.1.19 - version: 1.1.19 + specifier: ^1.3.6 + version: 1.3.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@4.0.17) canvas-confetti: specifier: ^1.9.3 version: 1.9.3 @@ -220,7 +220,7 @@ importers: version: 2.2.3 drizzle-orm: specifier: ^0.39.3 - version: 0.39.3(@neondatabase/serverless@0.10.4)(@opentelemetry/api@1.9.0)(@types/pg@8.11.11)(kysely@0.27.5)(pg@8.15.6(pg-native@3.4.5))(postgres@3.4.5) + version: 0.39.3(@neondatabase/serverless@0.10.4)(@opentelemetry/api@1.9.0)(@types/pg@8.11.11)(kysely@0.28.5)(pg@8.15.6(pg-native@3.4.5))(postgres@3.4.5) embla-carousel-react: specifier: ^8.5.2 version: 8.5.2(react@19.0.0) @@ -531,11 +531,8 @@ packages: '@types/react': optional: true - '@better-auth/utils@0.2.3': - resolution: {integrity: sha512-Ap1GaSmo6JYhJhxJOpUB0HobkKPTNzfta+bLV89HfpyCAHN7p8ntCrmNFHNAVD0F6v0mywFVEUg1FUhNCc81Rw==} - - '@better-fetch/fetch@1.1.12': - resolution: {integrity: sha512-B3bfloI/2UBQWIATRN6qmlORrvx3Mp0kkNjmXLv0b+DtbtR+pP4/I5kQA/rDUv+OReLywCCldf6co4LdDmh8JA==} + '@better-auth/utils@0.2.6': + resolution: {integrity: sha512-3y/vaL5Ox33dBwgJ6ub3OPkVqr6B5xL2kgxNHG8eHZuryLyG/4JSPGqjbdRSgjuy9kALUZYDFl+ORIAxlWMSuA==} '@better-fetch/fetch@1.1.18': resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==} @@ -1680,8 +1677,8 @@ packages: '@noble/ciphers@0.6.0': resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==} - '@noble/hashes@1.7.1': - resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} '@nodelib/fs.scandir@2.1.5': @@ -1797,20 +1794,20 @@ packages: cpu: [x64] os: [win32] - '@peculiar/asn1-android@2.3.15': - resolution: {integrity: sha512-8U2TIj59cRlSXTX2d0mzUKP7whfWGFMzTeC3qPgAbccXFrPNZLaDhpNEdG5U2QZ/tBv/IHlCJ8s+KYXpJeop6w==} + '@peculiar/asn1-android@2.4.0': + resolution: {integrity: sha512-YFueREq97CLslZZBI8dKzis7jMfEHSLxM+nr0Zdx1POiXFLjqqwoY5s0F1UimdBiEw/iKlHey2m56MRDv7Jtyg==} - '@peculiar/asn1-ecc@2.3.15': - resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==} + '@peculiar/asn1-ecc@2.4.0': + resolution: {integrity: sha512-fJiYUBCJBDkjh347zZe5H81BdJ0+OGIg0X9z06v8xXUoql3MFeENUX0JsjCaVaU9A0L85PefLPGYkIoGpTnXLQ==} - '@peculiar/asn1-rsa@2.3.15': - resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==} + '@peculiar/asn1-rsa@2.4.0': + resolution: {integrity: sha512-6PP75voaEnOSlWR9sD25iCQyLgFZHXbmxvUfnnDcfL6Zh5h2iHW38+bve4LfH7a60x7fkhZZNmiYqAlAff9Img==} - '@peculiar/asn1-schema@2.3.15': - resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==} + '@peculiar/asn1-schema@2.4.0': + resolution: {integrity: sha512-umbembjIWOrPSOzEGG5vxFLkeM8kzIhLkgigtsOrfLKnuzxWxejAcUX+q/SoZCdemlODOcr5WiYa7+dIEzBXZQ==} - '@peculiar/asn1-x509@2.3.15': - resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==} + '@peculiar/asn1-x509@2.4.0': + resolution: {integrity: sha512-F7mIZY2Eao2TaoVqigGMLv+NDdpwuBKU1fucHPONfzaBS4JXXCNCmfO0Z3dsy7JzKGqtDcYC1mr9JjaZQZNiuw==} '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -3301,11 +3298,11 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@simplewebauthn/browser@13.1.0': - resolution: {integrity: sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==} + '@simplewebauthn/browser@13.1.2': + resolution: {integrity: sha512-aZnW0KawAM83fSBUgglP5WofbrLbLyr7CoPqYr66Eppm7zO86YX6rrCjRB3hQKPrL7ATvY4FVXlykZ6w6FwYYw==} - '@simplewebauthn/server@13.1.1': - resolution: {integrity: sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==} + '@simplewebauthn/server@13.1.2': + resolution: {integrity: sha512-VwoDfvLXSCaRiD+xCIuyslU0HLxVggeE5BL06+GbsP2l1fGf5op8e0c3ZtKoi+vSg1q4ikjtAghC23ze2Q3H9g==} engines: {node: '>=20.0.0'} '@socket.io/component-emitter@3.1.2': @@ -3494,8 +3491,8 @@ packages: '@types/node@20.19.0': resolution: {integrity: sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==} - '@types/node@20.19.9': - resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==} + '@types/node@20.19.11': + resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==} '@types/pg@8.11.11': resolution: {integrity: sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==} @@ -3620,8 +3617,8 @@ packages: resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} engines: {node: '>=10'} - asn1js@3.0.5: - resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==} + asn1js@3.0.6: + resolution: {integrity: sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==} engines: {node: '>=12.0.0'} astring@1.9.0: @@ -3647,11 +3644,20 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - better-auth@1.1.19: - resolution: {integrity: sha512-OYf2oysca2+ASJ9hpGVkoLsz8b7bo65hHsVGpnIs9H5ORUnHJ7rKc6Wur4ZDCxuewBGUJMJyTAtXuo/cdAirjA==} + better-auth@1.3.6: + resolution: {integrity: sha512-zjwbz9GpgGt3LuvJ8ZXfQeowSRpzdGojVvkhxvXjhCLwGOaOrZmFiNdEVyIKWTraN4oBtgNimcxUIQTGs6OKYg==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true - better-call@0.3.3: - resolution: {integrity: sha512-N4lDVm0NGmFfDJ0XMQ4O83Zm/3dPlvIQdxvwvgSLSkjFX5PM4GUYSVAuxNzXN27QZMHDkrJTWUqxBrm4tPC3eA==} + better-call@1.0.13: + resolution: {integrity: sha512-auqdP9lnNOli9tKpZIiv0nEIwmmyaD/RotM3Mucql+Ef88etoZi/t7Ph5LjlmZt/hiSahhNTt6YVnx6++rziXA==} bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -4506,9 +4512,9 @@ packages: '@types/node': '>=18' typescript: '>=5.0.4' - kysely@0.27.5: - resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==} - engines: {node: '>=14.0.0'} + kysely@0.28.5: + resolution: {integrity: sha512-rlB0I/c6FBDWPcQoDtkxi9zIvpmnV5xoIalfCMSMCa7nuA6VGA3F54TW9mEgX4DVf10sXAWCF5fDbamI/5ZpKA==} + engines: {node: '>=20.0.0'} leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} @@ -5404,6 +5410,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -5998,12 +6007,10 @@ snapshots: optionalDependencies: '@types/react': 19.0.9 - '@better-auth/utils@0.2.3': + '@better-auth/utils@0.2.6': dependencies: uncrypto: 0.1.3 - '@better-fetch/fetch@1.1.12': {} - '@better-fetch/fetch@1.1.18': {} '@biomejs/biome@1.9.4': @@ -6757,7 +6764,7 @@ snapshots: '@noble/ciphers@0.6.0': {} - '@noble/hashes@1.7.1': {} + '@noble/hashes@1.8.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -6840,36 +6847,36 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.2.0': optional: true - '@peculiar/asn1-android@2.3.15': + '@peculiar/asn1-android@2.4.0': dependencies: - '@peculiar/asn1-schema': 2.3.15 - asn1js: 3.0.5 + '@peculiar/asn1-schema': 2.4.0 + asn1js: 3.0.6 tslib: 2.8.1 - '@peculiar/asn1-ecc@2.3.15': + '@peculiar/asn1-ecc@2.4.0': dependencies: - '@peculiar/asn1-schema': 2.3.15 - '@peculiar/asn1-x509': 2.3.15 - asn1js: 3.0.5 + '@peculiar/asn1-schema': 2.4.0 + '@peculiar/asn1-x509': 2.4.0 + asn1js: 3.0.6 tslib: 2.8.1 - '@peculiar/asn1-rsa@2.3.15': + '@peculiar/asn1-rsa@2.4.0': dependencies: - '@peculiar/asn1-schema': 2.3.15 - '@peculiar/asn1-x509': 2.3.15 - asn1js: 3.0.5 + '@peculiar/asn1-schema': 2.4.0 + '@peculiar/asn1-x509': 2.4.0 + asn1js: 3.0.6 tslib: 2.8.1 - '@peculiar/asn1-schema@2.3.15': + '@peculiar/asn1-schema@2.4.0': dependencies: - asn1js: 3.0.5 + asn1js: 3.0.6 pvtsutils: 1.3.6 tslib: 2.8.1 - '@peculiar/asn1-x509@2.3.15': + '@peculiar/asn1-x509@2.4.0': dependencies: - '@peculiar/asn1-schema': 2.3.15 - asn1js: 3.0.5 + '@peculiar/asn1-schema': 2.4.0 + asn1js: 3.0.6 pvtsutils: 1.3.6 tslib: 2.8.1 @@ -8491,17 +8498,17 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} - '@simplewebauthn/browser@13.1.0': {} + '@simplewebauthn/browser@13.1.2': {} - '@simplewebauthn/server@13.1.1': + '@simplewebauthn/server@13.1.2': dependencies: '@hexagon/base64': 1.1.28 '@levischuck/tiny-cbor': 0.2.11 - '@peculiar/asn1-android': 2.3.15 - '@peculiar/asn1-ecc': 2.3.15 - '@peculiar/asn1-rsa': 2.3.15 - '@peculiar/asn1-schema': 2.3.15 - '@peculiar/asn1-x509': 2.3.15 + '@peculiar/asn1-android': 2.4.0 + '@peculiar/asn1-ecc': 2.4.0 + '@peculiar/asn1-rsa': 2.4.0 + '@peculiar/asn1-schema': 2.4.0 + '@peculiar/asn1-x509': 2.4.0 '@socket.io/component-emitter@3.1.2': {} @@ -8670,7 +8677,7 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/node@20.19.9': + '@types/node@20.19.11': dependencies: undici-types: 6.21.0 optional: true @@ -8683,7 +8690,7 @@ snapshots: '@types/pg@8.11.6': dependencies: - '@types/node': 20.19.9 + '@types/node': 20.19.11 pg-protocol: 1.10.3 pg-types: 4.1.0 optional: true @@ -8756,7 +8763,7 @@ snapshots: dependencies: tslib: 2.8.1 - asn1js@3.0.5: + asn1js@3.0.6: dependencies: pvtsutils: 1.3.6 pvutils: 1.1.3 @@ -8782,27 +8789,30 @@ snapshots: base64id@2.0.0: {} - better-auth@1.1.19: + better-auth@1.3.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@4.0.17): dependencies: - '@better-auth/utils': 0.2.3 - '@better-fetch/fetch': 1.1.12 + '@better-auth/utils': 0.2.6 + '@better-fetch/fetch': 1.1.18 '@noble/ciphers': 0.6.0 - '@noble/hashes': 1.7.1 - '@simplewebauthn/browser': 13.1.0 - '@simplewebauthn/server': 13.1.1 - better-call: 0.3.3 + '@noble/hashes': 1.8.0 + '@simplewebauthn/browser': 13.1.2 + '@simplewebauthn/server': 13.1.2 + better-call: 1.0.13 defu: 6.1.4 jose: 5.10.0 - kysely: 0.27.5 + kysely: 0.28.5 nanostores: 0.11.4 - zod: 3.25.64 + zod: 4.0.17 + optionalDependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) - better-call@0.3.3: + better-call@1.0.13: dependencies: '@better-fetch/fetch': 1.1.18 rou3: 0.5.1 + set-cookie-parser: 2.7.1 uncrypto: 0.1.3 - zod: 3.25.64 bindings@1.5.0: dependencies: @@ -9080,12 +9090,12 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.39.3(@neondatabase/serverless@0.10.4)(@opentelemetry/api@1.9.0)(@types/pg@8.11.11)(kysely@0.27.5)(pg@8.15.6(pg-native@3.4.5))(postgres@3.4.5): + drizzle-orm@0.39.3(@neondatabase/serverless@0.10.4)(@opentelemetry/api@1.9.0)(@types/pg@8.11.11)(kysely@0.28.5)(pg@8.15.6(pg-native@3.4.5))(postgres@3.4.5): optionalDependencies: '@neondatabase/serverless': 0.10.4 '@opentelemetry/api': 1.9.0 '@types/pg': 8.11.11 - kysely: 0.27.5 + kysely: 0.28.5 pg: 8.15.6(pg-native@3.4.5) postgres: 3.4.5 @@ -9738,7 +9748,7 @@ snapshots: zod: 3.25.64 zod-validation-error: 3.5.2(zod@3.25.64) - kysely@0.27.5: {} + kysely@0.28.5: {} leac@0.6.0: {} @@ -11002,6 +11012,8 @@ snapshots: semver@7.7.1: optional: true + set-cookie-parser@2.7.1: {} + sharp@0.33.5: dependencies: color: 4.2.3 diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 5cbfb47..0071803 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -10,7 +10,7 @@ import { sendEmail } from '@/mail'; import { subscribe } from '@/newsletter'; import { type User, betterAuth } from 'better-auth'; import { drizzleAdapter } from 'better-auth/adapters/drizzle'; -import { admin } from 'better-auth/plugins'; +import { admin, captcha } from 'better-auth/plugins'; import { parse as parseCookies } from 'cookie'; import type { Locale } from 'next-intl'; import { getAllPricePlans } from './price-plan'; @@ -48,16 +48,19 @@ export const auth = betterAuth({ // https://www.better-auth.com/docs/concepts/email#2-require-email-verification requireEmailVerification: true, // https://www.better-auth.com/docs/authentication/email-password#forget-password - async sendResetPassword({ user, url }, request) { + async sendResetPassword( + data: { user: User; url: string; token: string }, + request?: Request + ) { const locale = getLocaleFromRequest(request); - const localizedUrl = getUrlWithLocaleInCallbackUrl(url, locale); + const localizedUrl = getUrlWithLocaleInCallbackUrl(data.url, locale); await sendEmail({ - to: user.email, + to: data.user.email, template: 'forgotPassword', context: { url: localizedUrl, - name: user.name, + name: data.user.name, }, locale, }); @@ -67,16 +70,19 @@ export const auth = betterAuth({ // https://www.better-auth.com/docs/concepts/email#auto-signin-after-verification autoSignInAfterVerification: true, // https://www.better-auth.com/docs/authentication/email-password#require-email-verification - sendVerificationEmail: async ({ user, url, token }, request) => { + sendVerificationEmail: async ( + data: { user: User; url: string; token: string }, + request?: Request + ) => { const locale = getLocaleFromRequest(request); - const localizedUrl = getUrlWithLocaleInCallbackUrl(url, locale); + const localizedUrl = getUrlWithLocaleInCallbackUrl(data.url, locale); await sendEmail({ - to: user.email, + to: data.user.email, template: 'verifyEmail', context: { url: localizedUrl, - name: user.name, + name: data.user.name, }, locale, }); @@ -85,13 +91,13 @@ export const auth = betterAuth({ socialProviders: { // https://www.better-auth.com/docs/authentication/github github: { - clientId: process.env.GITHUB_CLIENT_ID!, - clientSecret: process.env.GITHUB_CLIENT_SECRET!, + clientId: process.env.GITHUB_CLIENT_ID || '', + clientSecret: process.env.GITHUB_CLIENT_SECRET || '', }, // https://www.better-auth.com/docs/authentication/google google: { - clientId: process.env.GOOGLE_CLIENT_ID!, - clientSecret: process.env.GOOGLE_CLIENT_SECRET!, + clientId: process.env.GOOGLE_CLIENT_ID || '', + clientSecret: process.env.GOOGLE_CLIENT_SECRET || '', }, }, account: { @@ -118,7 +124,7 @@ export const auth = betterAuth({ // https://www.better-auth.com/docs/concepts/database#database-hooks user: { create: { - after: async (user) => { + after: async (user: User & Record) => { await onCreateUser(user); }, }, @@ -134,11 +140,23 @@ export const auth = betterAuth({ bannedUserMessage: 'You have been banned from this application. Please contact support if you believe this is an error.', }), + // https://www.better-auth.com/docs/plugins/captcha + captcha({ + provider: 'cloudflare-turnstile', // or google-recaptcha, hcaptcha + secretKey: process.env.TURNSTILE_SECRET_KEY!, + endpoints: [ + '/auth/login', + '/auth/register', + '/auth/forgot-password', + '/auth/reset-password', + '/about', + ], + }), ], onAPIError: { // https://www.better-auth.com/docs/reference/options#onapierror errorURL: '/auth/error', - onError: (error, ctx) => { + onError: (error: unknown, ctx: any) => { console.error('auth error:', error); }, },