import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Shield, Plus, Pencil, Trash2, ExternalLink, FileCode2, Sparkles } from 'lucide-react' import { Button } from '../components/ui/Button' import { Input } from '../components/ui/Input' import { useRuleSets, useUpsertRuleSet, useDeleteRuleSet } from '../hooks/useSecurity' import type { SecurityRuleSet, UpsertRuleSetPayload } from '../api/security' import { ConfigReloadOverlay } from '../components/LoadingStates' /** * WAF Rule Presets for common security configurations */ const WAF_PRESETS = [ { name: 'OWASP Core Rule Set', url: 'https://github.com/coreruleset/coreruleset/archive/refs/tags/v3.3.5.tar.gz', content: '', description: 'Industry standard protection against OWASP Top 10 vulnerabilities.', }, { name: 'Basic SQL Injection Protection', url: '', content: `SecRule ARGS "@detectSQLi" "id:1001,phase:1,deny,status:403,msg:'SQLi Detected'" SecRule REQUEST_BODY "@detectSQLi" "id:1002,phase:2,deny,status:403,msg:'SQLi in Body'" SecRule REQUEST_COOKIES "@detectSQLi" "id:1003,phase:1,deny,status:403,msg:'SQLi in Cookies'"`, description: 'Simple rules to block common SQL injection patterns.', }, { name: 'Basic XSS Protection', url: '', content: `SecRule ARGS "@detectXSS" "id:2001,phase:1,deny,status:403,msg:'XSS Detected'" SecRule REQUEST_BODY "@detectXSS" "id:2002,phase:2,deny,status:403,msg:'XSS in Body'"`, description: 'Rules to block common Cross-Site Scripting (XSS) attacks.', }, { name: 'Common Bad Bots', url: '', content: `SecRule REQUEST_HEADERS:User-Agent "@rx (?i)(curl|curl|python|scrapy|httpclient|libwww|nikto|sqlmap)" "id:3001,phase:1,deny,status:403,msg:'Bad Bot Detected'" SecRule REQUEST_HEADERS:User-Agent "@streq -" "id:3002,phase:1,deny,status:403,msg:'Empty User-Agent'"`, description: 'Block known malicious bots and scanners.', }, ] as const /** * Confirmation dialog for destructive actions */ function ConfirmDialog({ isOpen, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, isLoading, deletingLabel, }: { isOpen: boolean title: string message: string confirmLabel: string cancelLabel: string onConfirm: () => void onCancel: () => void isLoading?: boolean deletingLabel: string }) { if (!isOpen) return null return (
e.stopPropagation()} >

{title}

{message}

) } /** * Form for creating/editing a WAF rule set */ function RuleSetForm({ initialData, onSubmit, onCancel, isLoading, t, }: { initialData?: SecurityRuleSet onSubmit: (data: UpsertRuleSetPayload) => void onCancel: () => void isLoading?: boolean t: (key: string) => string }) { const [name, setName] = useState(initialData?.name || '') const [sourceUrl, setSourceUrl] = useState(initialData?.source_url || '') const [content, setContent] = useState(initialData?.content || '') const [mode, setMode] = useState<'blocking' | 'detection'>( initialData?.mode === 'detection' ? 'detection' : 'blocking' ) const [selectedPreset, setSelectedPreset] = useState('') const handlePresetChange = (presetName: string) => { setSelectedPreset(presetName) if (presetName === '') return const preset = WAF_PRESETS.find((p) => p.name === presetName) if (preset) { setName(preset.name) setSourceUrl(preset.url) setContent(preset.content) } } const handleSubmit = (e: React.FormEvent) => { e.preventDefault() onSubmit({ id: initialData?.id, name: name.trim(), source_url: sourceUrl.trim() || undefined, content: content.trim() || undefined, mode, }) } const isValid = name.trim().length > 0 && (content.trim().length > 0 || sourceUrl.trim().length > 0) return (
{/* Presets Dropdown - only show when creating new */} {!initialData && (
{selectedPreset && (

{WAF_PRESETS.find((p) => p.name === selectedPreset)?.description}

)}
)} setName(e.target.value)} placeholder={t('wafConfig.ruleSetNamePlaceholder')} required data-testid="ruleset-name-input" />

{mode === 'blocking' ? t('wafConfig.blockingDescription') : t('wafConfig.detectionDescription')}

setSourceUrl(e.target.value)} placeholder="https://example.com/rules.conf" helperText={t('wafConfig.sourceUrlHelper')} data-testid="ruleset-url-input" />