diff --git a/frontends/nextjs/src/components/organisms/security/ActionButtons.tsx b/frontends/nextjs/src/components/organisms/security/ActionButtons.tsx new file mode 100644 index 000000000..e0a5f441a --- /dev/null +++ b/frontends/nextjs/src/components/organisms/security/ActionButtons.tsx @@ -0,0 +1,42 @@ +import { Button } from '@/components/ui' +import { DialogFooter } from '@/components/ui' +import type { SecurityScanResult } from '@/lib/security/scanner/security-scanner' + +interface ActionButtonsProps { + scanResult: SecurityScanResult + onCancel: () => void + onProceed?: () => void + showProceedButton?: boolean +} + +export function ActionButtons({ + scanResult, + onCancel, + onProceed, + showProceedButton = false +}: ActionButtonsProps) { + const disableProceed = scanResult.severity === 'critical' + + return ( + + + + {!scanResult.safe && showProceedButton && ( + + )} + + ) +} diff --git a/frontends/nextjs/src/components/organisms/security/SecurityMessage.tsx b/frontends/nextjs/src/components/organisms/security/SecurityMessage.tsx new file mode 100644 index 000000000..3091dc661 --- /dev/null +++ b/frontends/nextjs/src/components/organisms/security/SecurityMessage.tsx @@ -0,0 +1,171 @@ +import { Alert, AlertDescription } from '@/components/ui' +import { Badge } from '@/components/ui' +import { DialogDescription, DialogHeader, DialogTitle } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Separator } from '@/components/ui' +import { ShieldWarning, Warning, Info, CheckCircle } from '@phosphor-icons/react' +import type { SecurityIssue, SecurityScanResult } from '@/lib/security/scanner/security-scanner' +import { getSeverityColor, getSeverityIcon } from '@/lib/security/scanner/security-scanner' + +interface SecurityMessageProps { + scanResult: SecurityScanResult + codeType?: string +} + +const severityOrder = ['critical', 'high', 'medium', 'low'] as const + +const getSeverityBadgeVariant = (severity: string) => { + switch (severity) { + case 'critical': + case 'high': + return 'destructive' + case 'medium': + return 'default' + case 'low': + return 'secondary' + default: + return 'outline' + } +} + +const getIcon = (severity: string) => { + switch (severity) { + case 'critical': + case 'high': + return + case 'medium': + return + case 'low': + return + default: + return + } +} + +const getTitle = (severity: string, safe: boolean) => { + if (safe) return 'Code Security Check Passed' + + switch (severity) { + case 'critical': + return 'CRITICAL SECURITY THREAT DETECTED' + case 'high': + return 'High-Risk Security Issues Detected' + case 'medium': + return 'Security Warnings Detected' + case 'low': + return 'Minor Security Concerns' + default: + return 'Security Scan Complete' + } +} + +const getDescription = (scanResult: SecurityScanResult, codeType: string) => { + if (scanResult.safe) { + return `Your ${codeType} has been scanned and appears to be safe.` + } + + const { issues } = scanResult + return `Your ${codeType} contains ${issues.length} security ${issues.length === 1 ? 'issue' : 'issues'} that require attention.` +} + +export function SecurityMessage({ scanResult, codeType = 'code' }: SecurityMessageProps) { + const groupedIssues = scanResult.issues.reduce((acc, issue) => { + if (!acc[issue.severity]) { + acc[issue.severity] = [] + } + acc[issue.severity].push(issue) + return acc + }, {} as Record) + + return ( + <> + +
+ {getIcon(scanResult.severity)} +
+ {getTitle(scanResult.severity, scanResult.safe)} + + {getDescription(scanResult, codeType)} + +
+
+
+ + + {scanResult.safe ? ( + + + + No security issues detected. Your code follows security best practices. + + + ) : ( +
+ {severityOrder.map(severity => { + const issues = groupedIssues[severity] + if (!issues || issues.length === 0) return null + + return ( +
+
+ + {getSeverityIcon(severity)} {severity} + + + {issues.length} {issues.length === 1 ? 'issue' : 'issues'} + +
+ +
+ {issues.map((issue, idx) => ( + +
+
+
+

{issue.message}

+ {issue.line && ( +

+ Line {issue.line} +

+ )} +
+ + {issue.pattern} + +
+ {issue.recommendation && ( + <> + +
+

Recommendation:

+

{issue.recommendation}

+
+ + )} +
+
+ ))} +
+
+ ) + })} + + {(scanResult.severity === 'critical' || scanResult.severity === 'high') && ( + + + +

Security Alert

+

+ {scanResult.severity === 'critical' + ? 'This code contains CRITICAL security vulnerabilities that could compromise system security, steal data, or execute malicious actions. It is strongly recommended NOT to proceed.' + : 'This code contains HIGH-RISK security issues that could lead to vulnerabilities or unexpected behavior. Carefully review and fix these issues before proceeding.'} +

+
+
+ )} +
+ )} +
+ + ) +} diff --git a/frontends/nextjs/src/components/organisms/security/SecurityWarningDialog.tsx b/frontends/nextjs/src/components/organisms/security/SecurityWarningDialog.tsx index d84d4015e..2e1129fe4 100644 --- a/frontends/nextjs/src/components/organisms/security/SecurityWarningDialog.tsx +++ b/frontends/nextjs/src/components/organisms/security/SecurityWarningDialog.tsx @@ -1,32 +1,19 @@ /** * SecurityWarningDialog - Organism Component - * + * * This component is categorized as an organism (not a molecule) because: * 1. It contains complex data processing (groups security issues by severity) * 2. It implements security-specific business rules (severity ordering, badge variants) * 3. It's a feature-specific component for security scanning results * 4. It exceeds the recommended 150 LOC guideline for molecules (235 LOC) - * + * * See: docs/analysis/molecule-organism-audit.md for full categorization analysis */ -import { useState } from 'react' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui' -import { Button } from '@/components/ui' -import { Alert, AlertDescription } from '@/components/ui' -import { ScrollArea } from '@/components/ui' -import { Badge } from '@/components/ui' -import { Separator } from '@/components/ui' -import { ShieldWarning, Warning, Info, CheckCircle } from '@phosphor-icons/react' -import type { SecurityScanResult, SecurityIssue } from '@/lib/security/scanner/security-scanner' -import { getSeverityColor, getSeverityIcon } from '@/lib/security/scanner/security-scanner' +import { Dialog, DialogContent } from '@/components/ui' +import type { SecurityScanResult } from '@/lib/security/scanner/security-scanner' +import { ActionButtons } from './ActionButtons' +import { SecurityMessage } from './SecurityMessage' interface SecurityWarningDialogProps { open: boolean @@ -47,200 +34,34 @@ export function SecurityWarningDialog({ codeType = 'code', showProceedButton = false }: SecurityWarningDialogProps) { - const [acknowledged, setAcknowledged] = useState(false) + const closeDialog = () => { + onOpenChange(false) + } const handleProceed = () => { if (onProceed) { onProceed() } - setAcknowledged(false) - onOpenChange(false) + closeDialog() } const handleCancel = () => { if (onCancel) { onCancel() } - setAcknowledged(false) - onOpenChange(false) + closeDialog() } - const getSeverityBadgeVariant = (severity: string) => { - switch (severity) { - case 'critical': - return 'destructive' - case 'high': - return 'destructive' - case 'medium': - return 'default' - case 'low': - return 'secondary' - default: - return 'outline' - } - } - - const getIcon = () => { - switch (scanResult.severity) { - case 'critical': - case 'high': - return - case 'medium': - return - case 'low': - return - default: - return - } - } - - const getTitle = () => { - if (scanResult.safe) { - return 'Code Security Check Passed' - } - switch (scanResult.severity) { - case 'critical': - return 'CRITICAL SECURITY THREAT DETECTED' - case 'high': - return 'High-Risk Security Issues Detected' - case 'medium': - return 'Security Warnings Detected' - case 'low': - return 'Minor Security Concerns' - default: - return 'Security Scan Complete' - } - } - - const getDescription = () => { - if (scanResult.safe) { - return `Your ${codeType} has been scanned and appears to be safe.` - } - return `Your ${codeType} contains ${scanResult.issues.length} security ${scanResult.issues.length === 1 ? 'issue' : 'issues'} that require attention.` - } - - const groupedIssues = scanResult.issues.reduce((acc, issue) => { - if (!acc[issue.severity]) { - acc[issue.severity] = [] - } - acc[issue.severity].push(issue) - return acc - }, {} as Record) - - const severityOrder = ['critical', 'high', 'medium', 'low'] - return ( - -
- {getIcon()} -
- {getTitle()} - - {getDescription()} - -
-
-
- - - {scanResult.safe ? ( - - - - No security issues detected. Your code follows security best practices. - - - ) : ( -
- {severityOrder.map(severity => { - const issues = groupedIssues[severity] - if (!issues || issues.length === 0) return null - - return ( -
-
- - {getSeverityIcon(severity)} {severity} - - - {issues.length} {issues.length === 1 ? 'issue' : 'issues'} - -
- -
- {issues.map((issue, idx) => ( - -
-
-
-

{issue.message}

- {issue.line && ( -

- Line {issue.line} -

- )} -
- - {issue.pattern} - -
- {issue.recommendation && ( - <> - -
-

Recommendation:

-

{issue.recommendation}

-
- - )} -
-
- ))} -
-
- ) - })} - - {(scanResult.severity === 'critical' || scanResult.severity === 'high') && ( - - - -

Security Alert

-

- {scanResult.severity === 'critical' - ? 'This code contains CRITICAL security vulnerabilities that could compromise system security, steal data, or execute malicious actions. It is strongly recommended NOT to proceed.' - : 'This code contains HIGH-RISK security issues that could lead to vulnerabilities or unexpected behavior. Carefully review and fix these issues before proceeding.' - } -

-
-
- )} -
- )} -
- - - - - {!scanResult.safe && showProceedButton && ( - - )} - + +
)