Implement two-layer CAPTCHA bypass

This commit is contained in:
gpt-engineer-app[bot]
2025-10-11 00:41:13 +00:00
parent c986a54fbf
commit 21acbb948c
7 changed files with 112 additions and 5 deletions

3
.env
View File

@@ -16,3 +16,6 @@ VITE_CLOUDFLARE_ACCOUNT_HASH=X-2-mmiWukWxvAQQ2_o-7Q
VITE_NOVU_APPLICATION_IDENTIFIER="" VITE_NOVU_APPLICATION_IDENTIFIER=""
VITE_NOVU_SOCKET_URL="wss://ws.novu.co" VITE_NOVU_SOCKET_URL="wss://ws.novu.co"
VITE_NOVU_API_URL="https://api.novu.co" VITE_NOVU_API_URL="https://api.novu.co"
# CAPTCHA Bypass (Development/Preview Only)
VITE_ALLOW_CAPTCHA_BYPASS=true

View File

@@ -14,6 +14,12 @@ VITE_TURNSTILE_SITE_KEY=your-turnstile-site-key
# Cloudflare Images Configuration # Cloudflare Images Configuration
VITE_CLOUDFLARE_ACCOUNT_HASH=your-cloudflare-account-hash VITE_CLOUDFLARE_ACCOUNT_HASH=your-cloudflare-account-hash
# CAPTCHA Bypass Control (Development/Preview Only)
# This acts as a safety gate - even if admins enable bypass in settings,
# it will only work if this is set to 'true'
# MUST be 'false' or unset in production
VITE_ALLOW_CAPTCHA_BYPASS=false
# Novu Configuration # Novu Configuration
# For Novu Cloud, use these defaults: # For Novu Cloud, use these defaults:
# - Socket URL: wss://ws.novu.co # - Socket URL: wss://ws.novu.co

View File

@@ -10,6 +10,7 @@ import { supabase } from '@/integrations/supabase/client';
import { useToast } from '@/hooks/use-toast'; import { useToast } from '@/hooks/use-toast';
import { TurnstileCaptcha } from './TurnstileCaptcha'; import { TurnstileCaptcha } from './TurnstileCaptcha';
import { notificationService } from '@/lib/notificationService'; import { notificationService } from '@/lib/notificationService';
import { useCaptchaBypass } from '@/hooks/useCaptchaBypass';
interface AuthModalProps { interface AuthModalProps {
open: boolean; open: boolean;
@@ -34,9 +35,7 @@ export function AuthModal({ open, onOpenChange, defaultTab = 'signin' }: AuthMod
displayName: '' displayName: ''
}); });
// Detect iframe environment and make CAPTCHA optional for preview compatibility const { requireCaptcha } = useCaptchaBypass();
const isInIframe = window.self !== window.top;
const requireCaptcha = !isInIframe;
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData(prev => ({ setFormData(prev => ({

View File

@@ -78,6 +78,12 @@ export function useAdminSettings() {
return settings?.filter(s => s.category === category) || []; return settings?.filter(s => s.category === category) || [];
}; };
const getCaptchaBypassEnabled = (): boolean => {
const value = getSettingValue('auth.captcha_bypass_enabled', 'false');
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value;
return cleanValue === 'true' || cleanValue === true;
};
const updateSetting = async (key: string, value: any) => { const updateSetting = async (key: string, value: any) => {
return updateSettingMutation.mutateAsync({ key, value }); return updateSettingMutation.mutateAsync({ key, value });
}; };
@@ -179,5 +185,6 @@ export function useAdminSettings() {
getAutoRefreshStrategy, getAutoRefreshStrategy,
getPreserveInteractionState, getPreserveInteractionState,
getUseRealtimeQueue, getUseRealtimeQueue,
getCaptchaBypassEnabled,
}; };
} }

View File

@@ -0,0 +1,34 @@
import { useEffect } from 'react';
import { useAdminSettings } from './useAdminSettings';
export function useCaptchaBypass() {
const { getSettingValue } = useAdminSettings();
// Layer 1: Check if environment allows bypass
const environmentAllowsBypass = import.meta.env.VITE_ALLOW_CAPTCHA_BYPASS === 'true';
// Layer 2: Check if admin has enabled bypass
const adminEnabledBypass = getSettingValue('auth.captcha_bypass_enabled', false) === true ||
getSettingValue('auth.captcha_bypass_enabled', false) === 'true';
// Both layers must allow bypass
const bypassEnabled = environmentAllowsBypass && adminEnabledBypass;
// Log warning if bypass is active
useEffect(() => {
if (bypassEnabled && typeof window !== 'undefined') {
console.warn(
'⚠️ CAPTCHA BYPASS IS ACTIVE\n' +
'This should only be enabled in development/preview environments.\n' +
'Verify VITE_ALLOW_CAPTCHA_BYPASS=false in production!'
);
}
}, [bypassEnabled]);
return {
bypassEnabled,
requireCaptcha: !bypassEnabled,
environmentAllowsBypass,
adminEnabledBypass
};
}

View File

@@ -13,7 +13,7 @@ import { useUserRole } from '@/hooks/useUserRole';
import { useAdminSettings } from '@/hooks/useAdminSettings'; import { useAdminSettings } from '@/hooks/useAdminSettings';
import { NovuMigrationUtility } from '@/components/admin/NovuMigrationUtility'; import { NovuMigrationUtility } from '@/components/admin/NovuMigrationUtility';
import { TestDataGenerator } from '@/components/admin/TestDataGenerator'; import { TestDataGenerator } from '@/components/admin/TestDataGenerator';
import { Loader2, Save, Clock, Users, Bell, Shield, Settings, Trash2, Plug } from 'lucide-react'; import { Loader2, Save, Clock, Users, Bell, Shield, Settings, Trash2, Plug, AlertTriangle, Lock } from 'lucide-react';
export default function AdminSettings() { export default function AdminSettings() {
const { user } = useAuth(); const { user } = useAuth();
@@ -24,7 +24,8 @@ export default function AdminSettings() {
error, error,
updateSetting, updateSetting,
isUpdating, isUpdating,
getSettingsByCategory getSettingsByCategory,
getCaptchaBypassEnabled
} = useAdminSettings(); } = useAdminSettings();
if (roleLoading || isLoading) { if (roleLoading || isLoading) {
@@ -440,6 +441,10 @@ export default function AdminSettings() {
<Shield className="w-4 h-4" /> <Shield className="w-4 h-4" />
<span className="hidden sm:inline">Moderation</span> <span className="hidden sm:inline">Moderation</span>
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="auth" className="flex items-center gap-2">
<Lock className="w-4 h-4" />
<span className="hidden sm:inline">Auth</span>
</TabsTrigger>
<TabsTrigger value="user_management" className="flex items-center gap-2"> <TabsTrigger value="user_management" className="flex items-center gap-2">
<Users className="w-4 h-4" /> <Users className="w-4 h-4" />
<span className="hidden sm:inline">Users</span> <span className="hidden sm:inline">Users</span>
@@ -488,6 +493,50 @@ export default function AdminSettings() {
</Card> </Card>
</TabsContent> </TabsContent>
<TabsContent value="auth">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Lock className="w-5 h-5" />
Authentication Settings
</CardTitle>
<CardDescription>
Configure authentication security, CAPTCHA, and login settings
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{getCaptchaBypassEnabled() && (
<Card className="bg-yellow-50 dark:bg-yellow-900/20 border-yellow-300">
<CardContent className="pt-6">
<div className="flex items-start gap-3">
<AlertTriangle className="w-5 h-5 text-yellow-600 dark:text-yellow-400 mt-0.5" />
<div className="space-y-1">
<p className="font-medium text-yellow-800 dark:text-yellow-200">
CAPTCHA Bypass is Currently Enabled
</p>
<p className="text-sm text-yellow-700 dark:text-yellow-300">
Authentication requests will not require CAPTCHA verification.
This should ONLY be enabled in development environments.
</p>
</div>
</div>
</CardContent>
</Card>
)}
{getSettingsByCategory('auth').length > 0 ? (
getSettingsByCategory('auth').map((setting) => (
<SettingInput key={setting.id} setting={setting} />
))
) : (
<div className="text-center py-8 text-muted-foreground">
<Lock className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p>No authentication settings configured yet.</p>
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="user_management"> <TabsContent value="user_management">
<Card> <Card>
<CardHeader> <CardHeader>

View File

@@ -0,0 +1,9 @@
-- Add CAPTCHA bypass setting to admin_settings
INSERT INTO public.admin_settings (setting_key, setting_value, category, description)
VALUES (
'auth.captcha_bypass_enabled',
'false',
'auth',
'Allow CAPTCHA bypass for authentication (development only - requires VITE_ALLOW_CAPTCHA_BYPASS=true in environment)'
)
ON CONFLICT (setting_key) DO NOTHING;