"use strict";
/**
 * Layer 2: Authentication Anti-Pattern Detection
 * Identifies weak or missing authentication/authorization patterns
 *
 * Key improvements:
 * - Respects global middleware protection (caps severity at info)
 * - Detects throwing auth helpers and suppresses redundant null checks
 * - Properly classifies public endpoints
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectAuthAntipatterns = detectAuthAntipatterns;
const middleware_detector_1 = require("../utils/middleware-detector");
const auth_helper_detector_1 = require("../utils/auth-helper-detector");
const context_helpers_1 = require("../utils/context-helpers");
const route_hierarchy_1 = require("../utils/route-hierarchy");
const schema_semantics_1 = require("../utils/schema-semantics");
const intent_detector_1 = require("../utils/intent-detector");
const AUTH_ANTIPATTERNS = [
    // Missing auth checks
    {
        name: 'Unprotected API route',
        pattern: /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)\s*\(/gi,
        severity: 'medium',
        description: 'API route handler may lack authentication - verify auth is checked',
        suggestedFix: 'Add authentication middleware or check session at the start of the handler',
    },
    {
        name: 'Express route without auth middleware',
        pattern: /\.(get|post|put|delete|patch)\s*\(\s*['"][^'"]+['"]\s*,\s*(async\s*)?\(\s*(req|request)/gi,
        severity: 'medium',
        description: 'Express route may lack authentication middleware',
        suggestedFix: 'Add authentication middleware before the route handler',
    },
    // Weak authentication patterns
    {
        name: 'Hardcoded credentials check',
        pattern: /if\s*\(\s*(username|user|email)\s*===?\s*['"][^'"]+['"]\s*&&\s*(password|pass|pwd)\s*===?\s*['"][^'"]+['"]/gi,
        severity: 'critical',
        description: 'Hardcoded credentials in authentication logic',
        suggestedFix: 'Use a proper user database with hashed passwords',
    },
    {
        name: 'Password in plain text comparison',
        pattern: /password\s*===?\s*user\.password|user\.password\s*===?\s*password/gi,
        severity: 'high',
        description: 'Plain text password comparison detected',
        suggestedFix: 'Use bcrypt.compare() or similar for password verification',
    },
    {
        name: 'JWT without verification',
        pattern: /jwt\.decode\s*\([^)]+\)(?!.*verify)/gi,
        severity: 'high',
        description: 'JWT decoded without signature verification',
        suggestedFix: 'Use jwt.verify() instead of jwt.decode() for authentication',
    },
    {
        name: 'Weak JWT secret',
        pattern: /jwt\.sign\s*\([^)]+,\s*['"][^'"]{1,20}['"]/gi,
        severity: 'high',
        description: 'JWT signed with a short/weak secret',
        suggestedFix: 'Use a strong, random secret of at least 256 bits from environment variables',
    },
    // Session issues
    {
        name: 'Session without secure flag',
        pattern: /session\s*\(\s*\{[^}]*(?!secure\s*:\s*true)[^}]*\}/gi,
        severity: 'medium',
        description: 'Session configuration may lack secure flag',
        suggestedFix: 'Set secure: true for cookies in production',
    },
    // NOTE: Cookie httpOnly detection removed - causes false positives
    // Client-side code (document.cookie) cannot set httpOnly - it's a server-only flag
    // Server-side cookie libraries have proper defaults
    // The pattern was triggering on client-side cookie access which is conceptually wrong
    // Authorization issues
    // NOTE: We intentionally do NOT flag "if (!user)" or "if (!userId)" patterns as issues
    // when throwing auth helpers are in use. Those helpers guarantee the user exists.
    // This pattern is now much more targeted.
    {
        name: 'Missing role check for admin operation',
        pattern: /\b(admin|superuser|moderator|owner)\b.*(?:delete|remove|update|modify|grant|revoke)/gi,
        severity: 'low',
        description: 'Potentially privileged operation - verify role/permission checks are in place',
        suggestedFix: 'Add role-based access control for sensitive operations',
    },
    {
        name: 'Client-side only auth check',
        pattern: /if\s*\(\s*!?\s*(isAuthenticated|isLoggedIn|user)\s*\)\s*\{?\s*(router\.push|navigate|redirect|window\.location)/gi,
        severity: 'medium',
        description: 'Client-side only authentication redirect detected',
        suggestedFix: 'Implement server-side authentication checks as well',
    },
    // Insecure token handling
    {
        name: 'Token in URL',
        pattern: /\?.*token=|&token=|\?.*api_key=|&api_key=/gi,
        severity: 'high',
        description: 'Sensitive token passed in URL query parameter',
        suggestedFix: 'Pass tokens in Authorization header or request body',
    },
    {
        name: 'Token in localStorage',
        pattern: /localStorage\.(setItem|getItem)\s*\(\s*['"](token|jwt|auth|session|apiKey)/gi,
        severity: 'medium',
        description: 'Sensitive token stored in localStorage (XSS vulnerable)',
        suggestedFix: 'Use httpOnly cookies for token storage',
    },
    // OAuth/Social auth issues
    // NOTE: OAuth detection narrowed significantly to reduce false positives.
    // Previously matched any line containing "oauth" which flagged variable names, imports, etc.
    // Now only matches actual OAuth authorization URL construction.
    {
        name: 'OAuth state parameter missing',
        // Only match actual OAuth authorization URL construction without state parameter
        // Must have: authorize endpoint + client_id/redirect_uri but missing state
        pattern: /['"`]https?:\/\/[^'"]*\/(?:oauth|authorize|auth)[^'"]*client_id=[^'"]*(?!.*state=)/gi,
        severity: 'medium',
        description: 'OAuth authorization URL may lack state parameter for CSRF protection',
        suggestedFix: 'Include a random state parameter in OAuth authorization requests',
    },
    // Password handling issues
    {
        name: 'Password logged',
        pattern: /console\.(log|info|debug|warn|error)\s*\([^)]*password/gi,
        severity: 'critical',
        description: 'Password may be logged to console',
        suggestedFix: 'Never log passwords or sensitive credentials',
    },
    // NOTE: 'Password in error message' pattern now uses smart intent detection
    // Error CODES like 'SAME_PASSWORD' are not flagged (they're codes, not values)
    // Only actual password values concatenated into errors are flagged
    // This is handled specially in detectAuthAntipatterns() below
    // Rate limiting
    {
        name: 'Login without rate limiting',
        pattern: /\/(login|signin|auth|authenticate)\s*['"],\s*(async\s*)?\(/gi,
        severity: 'medium',
        description: 'Login endpoint may lack rate limiting',
        suggestedFix: 'Add rate limiting to prevent brute force attacks',
    },
    // 2FA bypass
    {
        name: 'Potential 2FA bypass',
        pattern: /skip2fa|bypass2fa|disable2fa|twoFactor\s*[=:]\s*false/gi,
        severity: 'high',
        description: 'Two-factor authentication bypass detected',
        suggestedFix: 'Remove 2FA bypass options in production',
    },
];
// Check if line is a comment
function isComment(line) {
    const trimmed = line.trim();
    return (trimmed.startsWith('//') ||
        trimmed.startsWith('#') ||
        trimmed.startsWith('*') ||
        trimmed.startsWith('/*'));
}
// Check if file is likely an auth-related file
function isAuthRelatedFile(filePath) {
    const authKeywords = ['auth', 'login', 'session', 'user', 'account', 'credential', 'password', 'token', 'jwt', 'oauth'];
    const lowerPath = filePath.toLowerCase();
    return authKeywords.some(keyword => lowerPath.includes(keyword));
}
/**
 * Check if file is an auth implementation/library file
 * These files ARE the auth system - flagging them for "missing auth" is wrong
 *
 * Examples:
 * - packages/auth/src/handlers.ts
 * - lib/auth/middleware.ts
 * - services/authentication/index.ts
 */
function isAuthImplementationFile(filePath) {
    const authImplPatterns = [
        /\/packages\/auth\//i,
        /\/lib\/auth\//i,
        /\/utils\/auth\//i,
        /\/services\/auth/i,
        /\/authentication\//i,
        /\/auth-provider/i,
        /\/auth-helpers/i,
        /\/auth-utils/i,
        /\/passport-/i, // Passport.js strategy files
        /\/next-auth\//i, // NextAuth config
        /\/lucia\//i, // Lucia auth
        /\/better-auth\//i, // Better Auth
        /\/supabase\/auth/i, // Supabase auth
        /\/clerk\//i, // Clerk auth
        /\/auth0\//i, // Auth0
        /\/keycloak\//i, // Keycloak
    ];
    return authImplPatterns.some(p => p.test(filePath));
}
// Check if endpoint is a known public endpoint (health checks, webhooks, cron)
function isKnownPublicEndpoint(lineContent, filePath) {
    const PUBLIC_ENDPOINTS = [
        // Health checks
        /\/health\b/i,
        /\/healthz\b/i,
        /\/ready\b/i,
        /\/live\b/i,
        /\/ping\b/i,
        /\/status\b/i,
        /\/_health/i,
        // Webhooks (receive external calls)
        /\/webhook\b/i,
        /\/webhooks\//i,
        /\/callback\b/i,
        /\/stripe\/webhook/i,
        /\/clerk\/webhook/i,
        /\/svix\//i,
        // Cron/scheduled tasks
        /\/cron\//i,
        /\/scheduled\//i,
        /\/tasks\//i,
        /\/jobs?\//i,
        // Public APIs - intentionally unauthenticated
        /\/public\//i,
        /\/openpage/i, // OpenPage API pattern (intentionally public)
        /\/open-api\//i,
        /\/api\/public\//i,
        /\/api\/v\d+\/public\//i,
        /\bGET\b.*\/api\/\w+\/\[id\]/i, // Public resource reads with ID param
        // Auth endpoints (must be public for users to authenticate)
        /\/api\/auth\//i,
        /\/auth\//i,
        /\/login\b/i,
        /\/signup\b/i,
        /\/register\b/i,
        /\/forgot-password/i,
        /\/reset-password/i,
        /\/verify-email/i,
        /\/magic-link/i,
        /\/oauth\//i,
        // RSS/Atom feeds (typically public)
        /\/feed\b/i,
        /\/rss\b/i,
        /\/atom\b/i,
        // Sitemap/robots (always public)
        /\/sitemap/i,
        /\/robots/i,
        // OpenGraph/meta endpoints
        /\/og\//i,
        /\/opengraph/i,
        /\/meta\//i,
        // Share/embed endpoints
        /\/share\//i,
        /\/embed\//i,
        /\/widget\//i,
    ];
    return PUBLIC_ENDPOINTS.some(pattern => pattern.test(lineContent) || pattern.test(filePath));
}
// Check if there are auth indicators in nearby lines (within 15 lines)
function hasAuthCheckNearby(lines, lineIndex) {
    const startLine = Math.max(0, lineIndex);
    const endLine = Math.min(lines.length, lineIndex + 15);
    const searchWindow = lines.slice(startLine, endLine);
    const authIndicators = [
        /authorization/i,
        /bearer\s+token/i,
        /req\.user/,
        /request\.user/,
        /isAuthenticated/,
        /requireAuth/,
        /verifyToken/,
        /checkPermission/,
        /getServerSession/,
        /auth\(\)/,
        /middleware.*auth/i,
        /session\s*\.\s*user/,
        // Internal secret checks (network-level auth)
        /internal.?secret/i,
        /INTERNAL_SECRET/,
        /x-internal-secret/i,
        /admin.?secret/i,
        /service.?token/i,
        // BYOK patterns - user provides their own API key (implicit auth)
        /userApiKey|user_api_key|clientApiKey/i,
        /req\.body\.(?:apiKey|api_key|openaiKey|anthropicKey)/i,
        /headers\[['"`]x-(?:openai|api|anthropic)-key['"`]\]/i,
        // Next.js / React auth patterns (expanded)
        /const\s+session\s*=\s*await\s+auth\s*\(\)/, // const session = await auth()
        /const\s+\{\s*session\s*\}\s*=\s*await\s+auth\s*\(\)/, // const { session } = await auth()
        /if\s*\(\s*!session\?\.user/, // if (!session?.user)
        /if\s*\(\s*!session\s*\)/, // if (!session)
        /session\s*\?\.\s*user/, // session?.user
        /getServerSession\s*\(\s*authOptions/, // getServerSession(authOptions)
        /getServerSession\s*\(\s*req\s*,\s*res/, // getServerSession(req, res, ...)
        /useSession\s*\(\)/, // useSession()
        /signIn\s*\(/, // signIn()
        /signOut\s*\(/, // signOut()
        // Clerk auth patterns
        /currentUser\s*\(\)/, // currentUser()
        /auth\s*\(\)\s*\.\s*protect/, // auth().protect
        /auth\s*\(\)\s*\.\s*userId/, // auth().userId
        /clerkClient/i,
        /getAuth\s*\(/,
        /ClerkProvider/,
        // Supabase auth patterns
        /supabase\s*\.\s*auth\s*\.\s*getUser/, // supabase.auth.getUser()
        /supabase\s*\.\s*auth\s*\.\s*getSession/, // supabase.auth.getSession()
        /createServerClient/, // Supabase server client
        /createRouteHandlerClient/,
        // Lucia auth patterns
        /lucia\s*\.\s*validateSession/,
        /validateRequest/,
        // Better Auth patterns
        /betterAuth/,
        /auth\.api\./,
        // Throwing auth helpers (if these are called, route is authenticated)
        /throw\s+new\s+Error\s*\(\s*['"]unauthorized/i,
        /throw\s+new\s+Error\s*\(\s*['"]unauthenticated/i,
        /ChatSDKError\s*\(\s*['"]unauthorized/i,
        /return\s+new\s+Response\s*\(\s*.*401/,
        /return\s+NextResponse\s*\.\s*json\s*\(\s*.*401/,
    ];
    return searchWindow.some(line => authIndicators.some(pattern => pattern.test(line)));
}
function detectAuthAntipatterns(content, filePath, options = {}) {
    const { middlewareConfig, authHelpers, fileAuthImports, parsed } = options;
    const vulnerabilities = [];
    // Skip scanner/fixture files to avoid self-detection
    if ((0, context_helpers_1.isScannerOrFixtureFile)(filePath))
        return vulnerabilities;
    const lines = parsed?.lines ?? content.split('\n');
    const isAuthFile = isAuthRelatedFile(filePath);
    const isAuthImpl = isAuthImplementationFile(filePath);
    // Check framework route hierarchy protection (Remix, Next.js route groups)
    const routeHierarchy = (0, route_hierarchy_1.getRouteProtectionContext)(filePath);
    // Check if this route is protected by global middleware
    const routePath = (0, middleware_detector_1.getRoutePathFromFile)(filePath);
    const middlewareProtection = routePath && middlewareConfig
        ? (0, middleware_detector_1.isRouteProtectedByMiddleware)(routePath, middlewareConfig)
        : { isProtected: false, reason: '' };
    // Check if file uses imported auth middleware/helpers
    const importedAuthProtection = fileAuthImports?.get(filePath);
    const usesImportedAuth = importedAuthProtection?.usesImportedAuth ?? false;
    // Check if file uses throwing auth helpers
    const helpersList = authHelpers?.helpers || [];
    // Check if this is a component only used in authenticated contexts
    const isAuthOnlyComponent = (0, route_hierarchy_1.isAuthenticatedOnlyComponent)(filePath);
    lines.forEach((line, index) => {
        // Skip comment lines
        if (isComment(line))
            return;
        for (const pattern of AUTH_ANTIPATTERNS) {
            const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags);
            if (regex.test(line)) {
                // Special handling for unprotected route patterns
                if (pattern.name === 'Unprotected API route' ||
                    pattern.name === 'Express route without auth middleware') {
                    // PRIORITY -1: Skip auth implementation files entirely
                    // These files ARE the auth system - flagging them for "missing auth" is conceptually wrong
                    if (isAuthImpl) {
                        break; // Skip this pattern
                    }
                    // PRIORITY 0: Check if this is actually a route file
                    // In Next.js, routes must be in `route.ts/js` files. Files like `handlers.ts`,
                    // `safe-handlers.ts`, `utils.ts` etc. are NOT actual API routes even if they
                    // export GET/POST functions.
                    const isActualRouteFile = /\/(route|page)\.(ts|js|tsx|jsx)$/i.test(filePath) ||
                        /\/(api|routes?)\/.*\/(index|route)\.(ts|js)$/i.test(filePath) ||
                        // Express/Koa routes typically have 'routes' or 'router' in path
                        /\/(routes?|router|controllers?)\.[tj]s$/i.test(filePath);
                    // Files explicitly named as handlers, helpers, utils are not routes
                    const isUtilityFile = /(handler|helper|util|mock|test|fixture|safe|example)/i.test(filePath);
                    if (!isActualRouteFile || isUtilityFile) {
                        // Not an actual route file - skip this finding
                        break;
                    }
                    // PRIORITY 0.5: Check if route is in a protected route hierarchy
                    // Framework route conventions (Remix _authenticated+, Next.js route groups)
                    if (routeHierarchy.isInProtectedHierarchy) {
                        // Route is in a protected hierarchy - cap severity at info
                        vulnerabilities.push({
                            id: `auth-antipattern-${filePath}-${index + 1}-${pattern.name}`,
                            filePath,
                            lineNumber: index + 1,
                            lineContent: line.trim(),
                            severity: 'info',
                            category: 'missing_auth',
                            title: pattern.name + ' (in protected route hierarchy)',
                            description: `This route is within a protected route hierarchy (${routeHierarchy.protectionSource.join(', ')}). Authentication is likely handled by parent layout/middleware.`,
                            suggestedFix: 'Verify parent layout enforces authentication. If not, add auth check here.',
                            confidence: 'low',
                            layer: 2,
                        });
                        break; // Only report once per line
                    }
                    // PRIORITY 0.75: Check if this is a component only used in authenticated contexts
                    if (isAuthOnlyComponent) {
                        // Component is in admin/dashboard/etc - skip entirely
                        break;
                    }
                    // PRIORITY 1: Check if route is protected by global middleware
                    // This is the STRONGEST signal - if middleware protects the route, suppress entirely
                    if (middlewareProtection.isProtected) {
                        // Route is authenticated by middleware - no finding needed
                        break; // Skip this pattern, route is protected
                    }
                    // PRIORITY 1.5: Check if file imports and uses auth middleware
                    // e.g., import { authMiddleware } from '@/lib/auth' + wraps handlers
                    if (usesImportedAuth) {
                        // File imports auth middleware and uses it - no finding needed
                        break; // Skip this pattern, route is protected via imported auth
                    }
                    // PRIORITY 2: Check if file uses throwing auth helpers
                    // If getCurrentUserId() or similar is called, the route is authenticated
                    const authHelperCall = (0, auth_helper_detector_1.hasAuthHelperCallBefore)(content, index, helpersList);
                    if (authHelperCall.hasCall && authHelperCall.helper) {
                        // Route uses a throwing auth helper - no finding needed
                        // The auth helper guarantees authenticated context
                        break; // Skip this pattern, route is protected
                    }
                    // PRIORITY 3: Check if this is a known public endpoint
                    if (isKnownPublicEndpoint(line, filePath)) {
                        vulnerabilities.push({
                            id: `auth-antipattern-${filePath}-${index + 1}-${pattern.name}`,
                            filePath,
                            lineNumber: index + 1,
                            lineContent: line.trim(),
                            severity: 'info',
                            category: 'missing_auth',
                            title: pattern.name + ' (public endpoint)',
                            description: 'This appears to be a public endpoint (health check, webhook, cron, etc.). Verify this is intentionally public and consider rate limiting if needed.',
                            suggestedFix: 'If this is a webhook or cron endpoint, ensure it has appropriate authentication (API keys, signatures, etc.). Health checks typically do not need auth.',
                            confidence: 'low',
                            layer: 2,
                        });
                        break; // Only report once per line
                    }
                    // PRIORITY 4: Check if auth check exists nearby (inline check)
                    if (hasAuthCheckNearby(lines, index)) {
                        vulnerabilities.push({
                            id: `auth-antipattern-${filePath}-${index + 1}-${pattern.name}`,
                            filePath,
                            lineNumber: index + 1,
                            lineContent: line.trim(),
                            severity: 'low',
                            category: 'missing_auth',
                            title: pattern.name,
                            description: pattern.description + ' (auth check detected in nearby lines)',
                            suggestedFix: pattern.suggestedFix,
                            confidence: 'low',
                            layer: 2,
                        });
                        break; // Only report once per line
                    }
                }
                // Standard handling for all patterns (or fallback for route patterns)
                // Boost confidence for auth-related files
                const confidence = isAuthFile ? 'high' : 'medium';
                vulnerabilities.push({
                    id: `auth-antipattern-${filePath}-${index + 1}-${pattern.name}`,
                    filePath,
                    lineNumber: index + 1,
                    lineContent: line.trim(),
                    severity: pattern.severity,
                    category: 'missing_auth',
                    title: pattern.name,
                    description: pattern.description,
                    suggestedFix: pattern.suggestedFix,
                    confidence,
                    layer: 2,
                });
                break; // Only report once per line
            }
        }
    });
    // Special handling: Password in error message with smart intent detection
    // Only flag actual password VALUES, not error CODES like 'SAME_PASSWORD'
    const passwordErrorPattern = /throw\s+new\s+Error\s*\([^)]*password|Error\s*\([^)]*password/gi;
    lines.forEach((line, index) => {
        if (isComment(line))
            return;
        if (passwordErrorPattern.test(line)) {
            // Reset regex state
            passwordErrorPattern.lastIndex = 0;
            // Skip if this is an error CODE, not a VALUE
            if ((0, intent_detector_1.isPasswordErrorCode)(line)) {
                return; // This is fine - 'SAME_PASSWORD' is a code, not a value
            }
            // Only flag if actual password value is in the error
            if ((0, intent_detector_1.hasPasswordValueInError)(line)) {
                vulnerabilities.push({
                    id: `auth-antipattern-${filePath}-${index + 1}-password-in-error`,
                    filePath,
                    lineNumber: index + 1,
                    lineContent: line.trim(),
                    severity: 'high',
                    category: 'missing_auth',
                    title: 'Password value in error message',
                    description: 'Actual password value may be included in error message, exposing sensitive data.',
                    suggestedFix: 'Never include actual password values in error messages. Use error codes instead.',
                    confidence: 'high',
                    layer: 2,
                });
            }
        }
    });
    // Special handling: 2FA optional fields with OR validation
    // Don't flag .optional() on 2FA fields if there's .refine() enforcing OR logic
    const twoFAOptionalPattern = /\.(totp|otp|backupCode|recoveryCode|twoFactor|2fa|mfa).*\.optional\s*\(\)/gi;
    lines.forEach((line, index) => {
        if (isComment(line))
            return;
        if (twoFAOptionalPattern.test(line)) {
            // Reset regex state
            twoFAOptionalPattern.lastIndex = 0;
            // Check if this is legitimate OR validation (either TOTP or backup code required)
            if ((0, schema_semantics_1.is2FAOrValidation)(content, index)) {
                // This is legitimate OR validation - skip or report as info
                return;
            }
            // Only flag if this truly allows bypassing 2FA
            // Most cases with .refine() are fine - this is handled by schema-semantics.ts
        }
    });
    return vulnerabilities;
}
//# sourceMappingURL=auth-antipatterns.js.map