create-nextblock 0.14.0 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docker-template/.dockerignore +23 -23
- package/docker-template/.env.docker.example +69 -69
- package/docker-template/docker/db/init/99-jwt.sql +6 -6
- package/docker-template/docker/db/init/99-roles.sql +25 -25
- package/docker-template/docker/kong/kong.yml +112 -112
- package/docker-template/scripts/docker-setup.mjs +310 -310
- package/libs/db/tsconfig.lib.json +3 -3
- package/libs/editor/tsconfig.lib.json +3 -3
- package/libs/ui/tsconfig.lib.json +3 -3
- package/libs/utils/tsconfig.json +3 -3
- package/package.json +1 -1
- package/project.json +19 -19
- package/templates/nextblock-template/.browserslistrc +11 -11
- package/templates/nextblock-template/.dockerignore +23 -23
- package/templates/nextblock-template/README.md +34 -34
- package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -9
- package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -27
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
- package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
- package/templates/nextblock-template/app/ToasterProvider.tsx +17 -17
- package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -7
- package/templates/nextblock-template/app/actions/consent.ts +57 -57
- package/templates/nextblock-template/app/actions/postActions.ts +146 -146
- package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
- package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
- package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
- package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
- package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
- package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
- package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
- package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3490 -3490
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +5749 -5749
- package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
- package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
- package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
- package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
- package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
- package/templates/nextblock-template/app/api/media/record/route.ts +160 -160
- package/templates/nextblock-template/app/api/search/route.ts +43 -43
- package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +441 -441
- package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
- package/templates/nextblock-template/app/cart/page.tsx +7 -7
- package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
- package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
- package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
- package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +4 -2
- package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +90 -90
- package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
- package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
- package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
- package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
- package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
- package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
- package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
- package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
- package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
- package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
- package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
- package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
- package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
- package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
- package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
- package/templates/nextblock-template/app/cms/layout.tsx +73 -73
- package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
- package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
- package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
- package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
- package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
- package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
- package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
- package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
- package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
- package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
- package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
- package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
- package/templates/nextblock-template/app/cms/revisions/service.ts +19 -19
- package/templates/nextblock-template/app/cms/revisions/utils.ts +132 -132
- package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
- package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
- package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
- package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +688 -688
- package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
- package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
- package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
- package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
- package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
- package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
- package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
- package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
- package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
- package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +23 -21
- package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
- package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
- package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
- package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -122
- package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +9 -7
- package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
- package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
- package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
- package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
- package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
- package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
- package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
- package/templates/nextblock-template/app/profile/account-links.ts +22 -22
- package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
- package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
- package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
- package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
- package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
- package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
- package/templates/nextblock-template/app/setup/layout.tsx +13 -13
- package/templates/nextblock-template/app/setup/page.tsx +111 -111
- package/templates/nextblock-template/app/sitemap.ts +130 -130
- package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
- package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
- package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
- package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
- package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
- package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
- package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
- package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
- package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
- package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
- package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
- package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
- package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
- package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
- package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +249 -249
- package/templates/nextblock-template/components/blocks/types.ts +7 -7
- package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
- package/templates/nextblock-template/components/form-message.tsx +32 -32
- package/templates/nextblock-template/components/media/YouTubeFacade.tsx +105 -105
- package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -32
- package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
- package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +471 -471
- package/templates/nextblock-template/components/submit-button.tsx +23 -23
- package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
- package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
- package/templates/nextblock-template/context/AuthContext.tsx +23 -23
- package/templates/nextblock-template/context/language-rest-client.ts +32 -32
- package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
- package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
- package/templates/nextblock-template/docker/kong/kong.yml +112 -112
- package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
- package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
- package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -202
- package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +246 -246
- package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +176 -176
- package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
- package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
- package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +173 -173
- package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +170 -170
- package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +151 -151
- package/templates/nextblock-template/docs/README.md +39 -39
- package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
- package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
- package/templates/nextblock-template/index.d.ts +7 -7
- package/templates/nextblock-template/lib/app-secrets.ts +39 -39
- package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
- package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
- package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
- package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
- package/templates/nextblock-template/lib/blocks/README.md +13 -13
- package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
- package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -2243
- package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
- package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
- package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
- package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
- package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
- package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
- package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
- package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
- package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
- package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
- package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
- package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
- package/templates/nextblock-template/lib/email/branding.ts +76 -76
- package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
- package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
- package/templates/nextblock-template/lib/full-backup/server.ts +743 -743
- package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
- package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +54 -54
- package/templates/nextblock-template/lib/media/youtube.ts +99 -99
- package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
- package/templates/nextblock-template/lib/onboarding/status.ts +222 -222
- package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
- package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
- package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
- package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
- package/templates/nextblock-template/lib/privacy/types.ts +69 -69
- package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
- package/templates/nextblock-template/lib/promotions/server.ts +741 -741
- package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
- package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
- package/templates/nextblock-template/lib/search/types.ts +27 -27
- package/templates/nextblock-template/lib/setup/actions.ts +460 -460
- package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
- package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
- package/templates/nextblock-template/lib/setup/migrations-bundle.ts +87 -87
- package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
- package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
- package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
- package/templates/nextblock-template/lib/setup/types.ts +18 -18
- package/templates/nextblock-template/lib/site-url.ts +48 -48
- package/templates/nextblock-template/lib/storage/provider.ts +66 -66
- package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
- package/templates/nextblock-template/lib/updates/check-upstream.ts +441 -441
- package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
- package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
- package/templates/nextblock-template/next-env.d.ts +1 -0
- package/templates/nextblock-template/package.json +6 -6
- package/templates/nextblock-template/postcss.config.js +6 -6
- package/templates/nextblock-template/scripts/backup.js +115 -115
- package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
- package/templates/nextblock-template/scripts/restore.js +385 -385
- package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
- package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
- package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
- package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
- package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
- package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
- package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
- package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
- package/templates/nextblock-template/tailwind.config.js +25 -25
- package/templates/nextblock-template/tools/build-migrate.mjs +209 -209
- package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
- package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
- package/templates/nextblock-template/types/jsdom.d.ts +6 -6
- package/tsconfig.base.json +3 -3
|
@@ -1,134 +1,134 @@
|
|
|
1
|
-
// lib/botProtection/verify.ts
|
|
2
|
-
//
|
|
3
|
-
// Shared server-side bot-protection verification: the honeypot check plus
|
|
4
|
-
// Cloudflare Turnstile / Google reCAPTCHA token verification. This is the single
|
|
5
|
-
// source of truth used by BOTH the page contact-form handler
|
|
6
|
-
// (app/actions/formActions.ts) and the account-signup action (app/actions.ts).
|
|
7
|
-
//
|
|
8
|
-
// Server-only: it reads the RLS-bypassing service-role client, which throws if
|
|
9
|
-
// imported into a Client Component.
|
|
10
|
-
|
|
11
|
-
import { getServiceRoleSupabaseClient } from '@nextblock-cms/db/server';
|
|
12
|
-
|
|
13
|
-
export type BotProtectionProvider = 'none' | 'turnstile' | 'recaptcha';
|
|
14
|
-
|
|
15
|
-
// Shared field names — the client widgets emit these, the verifier reads them.
|
|
16
|
-
export const HONEYPOT_FIELD = 'verification_secondary_email';
|
|
17
|
-
export const TURNSTILE_TOKEN_FIELD = 'cf-turnstile-response';
|
|
18
|
-
export const RECAPTCHA_TOKEN_FIELD = 'g-recaptcha-response';
|
|
19
|
-
|
|
20
|
-
export type BotProtectionResult =
|
|
21
|
-
// Passed (or nothing configured beyond the honeypot).
|
|
22
|
-
| { ok: true }
|
|
23
|
-
// The honeypot was filled — almost certainly a bot. Callers should silently
|
|
24
|
-
// discard the submission and fake a success so the bot learns nothing.
|
|
25
|
-
| { ok: false; reason: 'honeypot' }
|
|
26
|
-
// The captcha was missing, failed, or could not be checked. `message` is a
|
|
27
|
-
// human-readable explanation safe to surface to the user.
|
|
28
|
-
| { ok: false; reason: 'captcha'; message: string };
|
|
29
|
-
|
|
30
|
-
type VerifyOptions = {
|
|
31
|
-
// A block/form may pin a specific provider; otherwise the site-wide setting
|
|
32
|
-
// (site_settings.bot_protection_public.provider) is used.
|
|
33
|
-
botProtectionProvider?: BotProtectionProvider;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Verify a submitted FormData against the configured bot protection.
|
|
38
|
-
*
|
|
39
|
-
* Phase 1 (honeypot) needs no network/DB and always runs — it is the always-on
|
|
40
|
-
* baseline. Phase 2 (captcha) reads the global provider + secret from
|
|
41
|
-
* `site_settings` and calls the provider's siteverify endpoint.
|
|
42
|
-
*/
|
|
43
|
-
export async function verifyBotProtection(
|
|
44
|
-
formData: FormData,
|
|
45
|
-
options?: VerifyOptions
|
|
46
|
-
): Promise<BotProtectionResult> {
|
|
47
|
-
// Phase 1: Honeypot validation
|
|
48
|
-
const honeypot = formData.get(HONEYPOT_FIELD);
|
|
49
|
-
if (honeypot && typeof honeypot === 'string' && honeypot.length > 0) {
|
|
50
|
-
console.warn('[Bot Protection] Honeypot triggered. Discarding submission from bot.');
|
|
51
|
-
return { ok: false, reason: 'honeypot' };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Phase 2: Advanced captcha verification
|
|
55
|
-
try {
|
|
56
|
-
const supabase = getServiceRoleSupabaseClient();
|
|
57
|
-
|
|
58
|
-
const { data: publicSetting } = await supabase
|
|
59
|
-
.from('site_settings')
|
|
60
|
-
.select('value')
|
|
61
|
-
.eq('key', 'bot_protection_public')
|
|
62
|
-
.maybeSingle();
|
|
63
|
-
|
|
64
|
-
const { data: secretSetting } = await supabase
|
|
65
|
-
.from('site_settings')
|
|
66
|
-
.select('value')
|
|
67
|
-
.eq('key', 'bot_protection_secret')
|
|
68
|
-
.maybeSingle();
|
|
69
|
-
|
|
70
|
-
const publicVal = (publicSetting?.value || {}) as Record<string, any>;
|
|
71
|
-
const secretVal = (secretSetting?.value || {}) as Record<string, any>;
|
|
72
|
-
|
|
73
|
-
const pinnedProvider =
|
|
74
|
-
options?.botProtectionProvider === 'turnstile' || options?.botProtectionProvider === 'recaptcha'
|
|
75
|
-
? options.botProtectionProvider
|
|
76
|
-
: undefined;
|
|
77
|
-
const provider: BotProtectionProvider = pinnedProvider || publicVal.provider || 'none';
|
|
78
|
-
const secretKey =
|
|
79
|
-
secretVal.secretKey ||
|
|
80
|
-
(provider === 'turnstile'
|
|
81
|
-
? process.env.TURNSTILE_SECRET_KEY
|
|
82
|
-
: process.env.RECAPTCHA_SECRET_KEY) ||
|
|
83
|
-
'';
|
|
84
|
-
|
|
85
|
-
if (provider === 'turnstile') {
|
|
86
|
-
const token = formData.get(TURNSTILE_TOKEN_FIELD) as string;
|
|
87
|
-
if (!token) {
|
|
88
|
-
return { ok: false, reason: 'captcha', message: 'Security verification token is missing. Please try again.' };
|
|
89
|
-
}
|
|
90
|
-
if (!secretKey) {
|
|
91
|
-
console.error('[Bot Protection] Turnstile secret key is not configured.');
|
|
92
|
-
return { ok: false, reason: 'captcha', message: 'Bot protection is misconfigured. Please contact support.' };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
|
96
|
-
method: 'POST',
|
|
97
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
98
|
-
body: `secret=${encodeURIComponent(secretKey)}&response=${encodeURIComponent(token)}`,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const outcome = await res.json();
|
|
102
|
-
if (!outcome.success) {
|
|
103
|
-
console.warn('[Bot Protection] Turnstile verification failed:', outcome);
|
|
104
|
-
return { ok: false, reason: 'captcha', message: 'Security verification failed. Please try again.' };
|
|
105
|
-
}
|
|
106
|
-
} else if (provider === 'recaptcha') {
|
|
107
|
-
const token = formData.get(RECAPTCHA_TOKEN_FIELD) as string;
|
|
108
|
-
if (!token) {
|
|
109
|
-
return { ok: false, reason: 'captcha', message: 'Security verification token is missing. Please try again.' };
|
|
110
|
-
}
|
|
111
|
-
if (!secretKey) {
|
|
112
|
-
console.error('[Bot Protection] reCAPTCHA secret key is not configured.');
|
|
113
|
-
return { ok: false, reason: 'captcha', message: 'Bot protection is misconfigured. Please contact support.' };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const res = await fetch('https://www.google.com/recaptcha/api/siteverify', {
|
|
117
|
-
method: 'POST',
|
|
118
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
119
|
-
body: `secret=${encodeURIComponent(secretKey)}&response=${encodeURIComponent(token)}`,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const outcome = await res.json();
|
|
123
|
-
if (!outcome.success || outcome.score < 0.5) {
|
|
124
|
-
console.warn('[Bot Protection] reCAPTCHA verification failed:', outcome);
|
|
125
|
-
return { ok: false, reason: 'captcha', message: 'Security verification failed. Please try again.' };
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
} catch (error) {
|
|
129
|
-
console.error('[Bot Protection] Error during validation:', error);
|
|
130
|
-
return { ok: false, reason: 'captcha', message: 'Sorry, security verification could not be completed at this time.' };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return { ok: true };
|
|
134
|
-
}
|
|
1
|
+
// lib/botProtection/verify.ts
|
|
2
|
+
//
|
|
3
|
+
// Shared server-side bot-protection verification: the honeypot check plus
|
|
4
|
+
// Cloudflare Turnstile / Google reCAPTCHA token verification. This is the single
|
|
5
|
+
// source of truth used by BOTH the page contact-form handler
|
|
6
|
+
// (app/actions/formActions.ts) and the account-signup action (app/actions.ts).
|
|
7
|
+
//
|
|
8
|
+
// Server-only: it reads the RLS-bypassing service-role client, which throws if
|
|
9
|
+
// imported into a Client Component.
|
|
10
|
+
|
|
11
|
+
import { getServiceRoleSupabaseClient } from '@nextblock-cms/db/server';
|
|
12
|
+
|
|
13
|
+
export type BotProtectionProvider = 'none' | 'turnstile' | 'recaptcha';
|
|
14
|
+
|
|
15
|
+
// Shared field names — the client widgets emit these, the verifier reads them.
|
|
16
|
+
export const HONEYPOT_FIELD = 'verification_secondary_email';
|
|
17
|
+
export const TURNSTILE_TOKEN_FIELD = 'cf-turnstile-response';
|
|
18
|
+
export const RECAPTCHA_TOKEN_FIELD = 'g-recaptcha-response';
|
|
19
|
+
|
|
20
|
+
export type BotProtectionResult =
|
|
21
|
+
// Passed (or nothing configured beyond the honeypot).
|
|
22
|
+
| { ok: true }
|
|
23
|
+
// The honeypot was filled — almost certainly a bot. Callers should silently
|
|
24
|
+
// discard the submission and fake a success so the bot learns nothing.
|
|
25
|
+
| { ok: false; reason: 'honeypot' }
|
|
26
|
+
// The captcha was missing, failed, or could not be checked. `message` is a
|
|
27
|
+
// human-readable explanation safe to surface to the user.
|
|
28
|
+
| { ok: false; reason: 'captcha'; message: string };
|
|
29
|
+
|
|
30
|
+
type VerifyOptions = {
|
|
31
|
+
// A block/form may pin a specific provider; otherwise the site-wide setting
|
|
32
|
+
// (site_settings.bot_protection_public.provider) is used.
|
|
33
|
+
botProtectionProvider?: BotProtectionProvider;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Verify a submitted FormData against the configured bot protection.
|
|
38
|
+
*
|
|
39
|
+
* Phase 1 (honeypot) needs no network/DB and always runs — it is the always-on
|
|
40
|
+
* baseline. Phase 2 (captcha) reads the global provider + secret from
|
|
41
|
+
* `site_settings` and calls the provider's siteverify endpoint.
|
|
42
|
+
*/
|
|
43
|
+
export async function verifyBotProtection(
|
|
44
|
+
formData: FormData,
|
|
45
|
+
options?: VerifyOptions
|
|
46
|
+
): Promise<BotProtectionResult> {
|
|
47
|
+
// Phase 1: Honeypot validation
|
|
48
|
+
const honeypot = formData.get(HONEYPOT_FIELD);
|
|
49
|
+
if (honeypot && typeof honeypot === 'string' && honeypot.length > 0) {
|
|
50
|
+
console.warn('[Bot Protection] Honeypot triggered. Discarding submission from bot.');
|
|
51
|
+
return { ok: false, reason: 'honeypot' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Phase 2: Advanced captcha verification
|
|
55
|
+
try {
|
|
56
|
+
const supabase = getServiceRoleSupabaseClient();
|
|
57
|
+
|
|
58
|
+
const { data: publicSetting } = await supabase
|
|
59
|
+
.from('site_settings')
|
|
60
|
+
.select('value')
|
|
61
|
+
.eq('key', 'bot_protection_public')
|
|
62
|
+
.maybeSingle();
|
|
63
|
+
|
|
64
|
+
const { data: secretSetting } = await supabase
|
|
65
|
+
.from('site_settings')
|
|
66
|
+
.select('value')
|
|
67
|
+
.eq('key', 'bot_protection_secret')
|
|
68
|
+
.maybeSingle();
|
|
69
|
+
|
|
70
|
+
const publicVal = (publicSetting?.value || {}) as Record<string, any>;
|
|
71
|
+
const secretVal = (secretSetting?.value || {}) as Record<string, any>;
|
|
72
|
+
|
|
73
|
+
const pinnedProvider =
|
|
74
|
+
options?.botProtectionProvider === 'turnstile' || options?.botProtectionProvider === 'recaptcha'
|
|
75
|
+
? options.botProtectionProvider
|
|
76
|
+
: undefined;
|
|
77
|
+
const provider: BotProtectionProvider = pinnedProvider || publicVal.provider || 'none';
|
|
78
|
+
const secretKey =
|
|
79
|
+
secretVal.secretKey ||
|
|
80
|
+
(provider === 'turnstile'
|
|
81
|
+
? process.env.TURNSTILE_SECRET_KEY
|
|
82
|
+
: process.env.RECAPTCHA_SECRET_KEY) ||
|
|
83
|
+
'';
|
|
84
|
+
|
|
85
|
+
if (provider === 'turnstile') {
|
|
86
|
+
const token = formData.get(TURNSTILE_TOKEN_FIELD) as string;
|
|
87
|
+
if (!token) {
|
|
88
|
+
return { ok: false, reason: 'captcha', message: 'Security verification token is missing. Please try again.' };
|
|
89
|
+
}
|
|
90
|
+
if (!secretKey) {
|
|
91
|
+
console.error('[Bot Protection] Turnstile secret key is not configured.');
|
|
92
|
+
return { ok: false, reason: 'captcha', message: 'Bot protection is misconfigured. Please contact support.' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
98
|
+
body: `secret=${encodeURIComponent(secretKey)}&response=${encodeURIComponent(token)}`,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const outcome = await res.json();
|
|
102
|
+
if (!outcome.success) {
|
|
103
|
+
console.warn('[Bot Protection] Turnstile verification failed:', outcome);
|
|
104
|
+
return { ok: false, reason: 'captcha', message: 'Security verification failed. Please try again.' };
|
|
105
|
+
}
|
|
106
|
+
} else if (provider === 'recaptcha') {
|
|
107
|
+
const token = formData.get(RECAPTCHA_TOKEN_FIELD) as string;
|
|
108
|
+
if (!token) {
|
|
109
|
+
return { ok: false, reason: 'captcha', message: 'Security verification token is missing. Please try again.' };
|
|
110
|
+
}
|
|
111
|
+
if (!secretKey) {
|
|
112
|
+
console.error('[Bot Protection] reCAPTCHA secret key is not configured.');
|
|
113
|
+
return { ok: false, reason: 'captcha', message: 'Bot protection is misconfigured. Please contact support.' };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const res = await fetch('https://www.google.com/recaptcha/api/siteverify', {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
119
|
+
body: `secret=${encodeURIComponent(secretKey)}&response=${encodeURIComponent(token)}`,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const outcome = await res.json();
|
|
123
|
+
if (!outcome.success || outcome.score < 0.5) {
|
|
124
|
+
console.warn('[Bot Protection] reCAPTCHA verification failed:', outcome);
|
|
125
|
+
return { ok: false, reason: 'captcha', message: 'Security verification failed. Please try again.' };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('[Bot Protection] Error during validation:', error);
|
|
130
|
+
return { ok: false, reason: 'captcha', message: 'Sorry, security verification could not be completed at this time.' };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { ok: true };
|
|
134
|
+
}
|