/**
 * Authorization Flow Analyzer
 *
 * Analyzes authorization patterns in API endpoints to detect
 * potential BOLA, IDOR, and other authorization vulnerabilities.
 *
 * @module scanners/logic/auth-flow-analyzer
 */
import { readFile } from "fs/promises";
import { join } from "path";
import { logger } from "../../logger.js";
import { LOGIC_VULN_CWE_MAP as CWE_MAP, LOGIC_VULN_OWASP_MAP as OWASP_MAP, LOGIC_VULN_SEVERITY_MAP as SEVERITY_MAP, } from "./types.js";
import { inferResourceType } from "./endpoint-analyzer.js";
/**
 * Authentication method detection patterns
 */
const AUTH_METHOD_PATTERNS = [
    { pattern: /jwt|jsonwebtoken|jose|jws/i, method: "jwt" },
    { pattern: /session\.(user|userId|passport)/i, method: "session" },
    { pattern: /passport\.authenticate/i, method: "session" },
    { pattern: /apiKey|api[_-]key|x-api-key/i, method: "api-key" },
    { pattern: /oauth|openid|oidc/i, method: "oauth" },
    { pattern: /basicAuth|basic-auth|authorization:\s*basic/i, method: "basic" },
];
/**
 * Patterns indicating sensitive operations
 */
const SENSITIVE_OPERATION_PATTERNS = [
    { pattern: /delete|remove|destroy/i, operation: "delete" },
    { pattern: /update|modify|edit|patch/i, operation: "update" },
    { pattern: /create|add|insert|new/i, operation: "create" },
    { pattern: /get|find|fetch|read|list/i, operation: "read" },
];
/**
 * Patterns indicating resource ID access
 */
const ID_SOURCE_PATTERNS = [
    { pattern: /params\.(\w+)(?:Id)?/i, source: "path" },
    { pattern: /req\.params\.(\w+)/i, source: "path" },
    { pattern: /query\.(\w+)(?:Id)?/i, source: "query" },
    { pattern: /req\.query\.(\w+)/i, source: "query" },
    { pattern: /body\.(\w+)(?:Id)?/i, source: "body" },
    { pattern: /req\.body\.(\w+)/i, source: "body" },
    { pattern: /headers\['x-(\w+)-id'\]/i, source: "header" },
    { pattern: /session\.(\w+)(?:Id)?/i, source: "session" },
    { pattern: /req\.user\.(\w+)/i, source: "session" },
];
/**
 * Analyze authorization flow for an endpoint
 */
export async function analyzeAuthorizationFlow(endpoint, projectPath) {
    const filePath = join(projectPath, endpoint.file);
    let content;
    try {
        content = await readFile(filePath, "utf-8");
    }
    catch {
        return {
            endpoint,
            requiresAuth: endpoint.hasAuth,
            authzChecks: endpoint.authzChecks,
            resourceAccesses: [],
            vulnerabilities: [],
            confidence: 0,
        };
    }
    // Detect auth method
    let authMethod;
    for (const { pattern, method } of AUTH_METHOD_PATTERNS) {
        if (pattern.test(content)) {
            authMethod = method;
            break;
        }
    }
    // Find resource accesses
    const resourceAccesses = findResourceAccesses(content, endpoint);
    // Detect vulnerabilities
    const vulnerabilities = detectVulnerabilities(endpoint, resourceAccesses, content);
    // Calculate confidence
    const confidence = calculateConfidence(endpoint, resourceAccesses, vulnerabilities);
    return {
        endpoint,
        requiresAuth: endpoint.hasAuth,
        authMethod,
        authzChecks: endpoint.authzChecks,
        resourceAccesses,
        vulnerabilities,
        confidence,
    };
}
/**
 * Find resource accesses in endpoint code
 */
function findResourceAccesses(content, endpoint) {
    const accesses = [];
    const resourceType = inferResourceType(endpoint.path);
    const lines = content.split("\n");
    // Find ID source in the code
    let idSource = "path";
    for (const { pattern, source } of ID_SOURCE_PATTERNS) {
        if (pattern.test(content)) {
            idSource = source;
            break;
        }
    }
    // Determine operation from HTTP method and code
    let operation = "read";
    const methodStr = Array.isArray(endpoint.method) ? endpoint.method.join(",") : endpoint.method;
    if (/DELETE/i.test(methodStr)) {
        operation = "delete";
    }
    else if (/POST/i.test(methodStr)) {
        operation = "create";
    }
    else if (/PUT|PATCH/i.test(methodStr)) {
        operation = "update";
    }
    // Look for database queries that access resources
    for (const query of endpoint.dbQueries) {
        const hasOwnership = query.hasOwnershipFilter ||
            endpoint.authzChecks.some((c) => c.type === "ownership");
        accesses.push({
            resource: query.table || resourceType || "unknown",
            operation,
            ownershipCheck: hasOwnership,
            idSource,
            file: endpoint.file,
            line: query.location.line,
        });
    }
    // If no queries found but endpoint has path params, create access entry
    if (accesses.length === 0 && endpoint.pathParams.length > 0) {
        const hasOwnership = endpoint.authzChecks.some((c) => c.type === "ownership");
        accesses.push({
            resource: resourceType || endpoint.pathParams[0]?.replace(/Id$/i, "") || "unknown",
            operation,
            ownershipCheck: hasOwnership,
            idSource,
            file: endpoint.file,
            line: endpoint.line,
        });
    }
    return accesses;
}
/**
 * Detect authorization vulnerabilities
 */
function detectVulnerabilities(endpoint, resourceAccesses, content) {
    const vulnerabilities = [];
    // Check for missing authentication
    if (!endpoint.hasAuth && hasSecureOperation(endpoint)) {
        vulnerabilities.push(createVulnerability("missing-auth", endpoint, {
            description: `Endpoint ${endpoint.path} performs sensitive operations without authentication`,
        }));
    }
    // Check for BOLA/IDOR
    for (const access of resourceAccesses) {
        // BOLA: Object-level access without ownership check
        if (!access.ownershipCheck && access.idSource === "path" && endpoint.hasAuth) {
            vulnerabilities.push(createVulnerability("bola", endpoint, {
                description: `Endpoint ${endpoint.path} accesses ${access.resource} by ID without ownership verification`,
                resourceAccess: access,
            }));
        }
        // IDOR: Direct object reference from user input
        if (!access.ownershipCheck && ["path", "query", "body"].includes(access.idSource)) {
            // Check if BOLA was already added
            const hasBola = vulnerabilities.some((v) => v.vulnType === "bola" && v.line === endpoint.line);
            if (!hasBola) {
                vulnerabilities.push(createVulnerability("idor", endpoint, {
                    description: `Endpoint ${endpoint.path} uses user-controlled ${access.idSource} parameter to access ${access.resource}`,
                    resourceAccess: access,
                }));
            }
        }
    }
    // Check for BFLA (function-level authorization)
    if (isAdminFunction(endpoint.path, content) && !hasAdminCheck(endpoint.authzChecks)) {
        vulnerabilities.push(createVulnerability("bfla", endpoint, {
            description: `Admin endpoint ${endpoint.path} missing role/permission check`,
        }));
    }
    // Check for mass assignment
    if (hasMassAssignmentRisk(endpoint, content)) {
        vulnerabilities.push(createVulnerability("mass-assignment", endpoint, {
            description: `Endpoint ${endpoint.path} may allow mass assignment of sensitive fields`,
        }));
    }
    // Check for direct database access without authorization
    if (endpoint.dbQueries.length > 0 && !endpoint.hasAuth && endpoint.authzChecks.length === 0) {
        vulnerabilities.push(createVulnerability("direct-db-access", endpoint, {
            description: `Endpoint ${endpoint.path} accesses database without authorization checks`,
        }));
    }
    // Check for privilege escalation patterns
    const privEscPatterns = detectPrivilegeEscalation(endpoint, content);
    vulnerabilities.push(...privEscPatterns);
    return vulnerabilities;
}
/**
 * Check if endpoint performs secure/sensitive operations
 */
function hasSecureOperation(endpoint) {
    const method = Array.isArray(endpoint.method) ? endpoint.method : [endpoint.method];
    const sensitiveMethods = ["POST", "PUT", "PATCH", "DELETE"];
    if (method.some((m) => sensitiveMethods.includes(m))) {
        return true;
    }
    // Check path for sensitive resources
    const sensitivePaths = [
        /\/admin/i,
        /\/user/i,
        /\/account/i,
        /\/profile/i,
        /\/settings/i,
        /\/config/i,
        /\/payment/i,
        /\/billing/i,
        /\/order/i,
        /\/secret/i,
    ];
    return sensitivePaths.some((p) => p.test(endpoint.path));
}
/**
 * Check if path appears to be an admin function
 */
function isAdminFunction(path, content) {
    const adminPatterns = [
        /\/admin\//i,
        /\/management\//i,
        /\/internal\//i,
        /\/superuser/i,
        /\/system\//i,
        /bulk|batch|mass/i,
        /delete-all|deleteAll/i,
    ];
    return adminPatterns.some((p) => p.test(path) || p.test(content));
}
/**
 * Check if endpoint has admin/role check
 */
function hasAdminCheck(checks) {
    return checks.some((c) => c.type === "admin" || c.type === "role");
}
/**
 * Check for mass assignment vulnerability
 */
function hasMassAssignmentRisk(endpoint, content) {
    const method = Array.isArray(endpoint.method) ? endpoint.method : [endpoint.method];
    if (!method.some((m) => ["POST", "PUT", "PATCH"].includes(m))) {
        return false;
    }
    // Look for direct body spread or assignment
    const massAssignPatterns = [
        /\.create\s*\(\s*req\.body\s*\)/i,
        /\.update\s*\(\s*req\.body\s*\)/i,
        /\{\s*\.\.\.req\.body\s*\}/i,
        /Object\.assign\s*\([^,]+,\s*req\.body/i,
        /\$set:\s*req\.body/i,
    ];
    // Check for allow/block list
    const hasAllowList = /allowedFields|whitelist|pick\s*\(/i.test(content);
    const hasBlockList = /blockedFields|blacklist|omit\s*\(/i.test(content);
    return massAssignPatterns.some((p) => p.test(content)) && !hasAllowList && !hasBlockList;
}
/**
 * Detect privilege escalation patterns
 */
function detectPrivilegeEscalation(endpoint, content) {
    const vulnerabilities = [];
    // Horizontal privilege escalation: User can access other users' data
    if (endpoint.pathParams.some((p) => /user|account|profile/i.test(p))) {
        const hasUserIdFromSession = /session\.user\.id|req\.user\.id|currentUser/i.test(content);
        const hasOwnershipCheck = endpoint.authzChecks.some((c) => c.type === "ownership");
        if (!hasOwnershipCheck && !hasUserIdFromSession) {
            vulnerabilities.push(createVulnerability("horizontal-priv-esc", endpoint, {
                description: `Endpoint ${endpoint.path} allows accessing other users' data without ownership verification`,
            }));
        }
    }
    // Vertical privilege escalation: User can elevate their own role
    const roleEscalationPatterns = [
        /role\s*:\s*req\.body\.role/i,
        /isAdmin\s*:\s*req\.body/i,
        /permissions\s*:\s*req\.body/i,
        /\.role\s*=\s*[^;]+body/i,
    ];
    if (roleEscalationPatterns.some((p) => p.test(content))) {
        vulnerabilities.push(createVulnerability("vertical-priv-esc", endpoint, {
            description: `Endpoint ${endpoint.path} allows users to modify their own role/permissions`,
        }));
    }
    return vulnerabilities;
}
/**
 * Create a logic vulnerability object
 */
function createVulnerability(vulnType, endpoint, details) {
    const vulnNames = {
        "bola": "Broken Object Level Authorization",
        "idor": "Insecure Direct Object Reference",
        "bfla": "Broken Function Level Authorization",
        "mass-assignment": "Mass Assignment Vulnerability",
        "race-condition-auth": "Race Condition in Authorization",
        "privilege-escalation": "Privilege Escalation",
        "missing-auth": "Missing Authentication",
        "missing-authz": "Missing Authorization",
        "direct-db-access": "Direct Database Access Without Authorization",
        "horizontal-priv-esc": "Horizontal Privilege Escalation",
        "vertical-priv-esc": "Vertical Privilege Escalation",
    };
    const remediations = {
        "bola": "Verify that the authenticated user owns or has permission to access the requested resource before performing any operation.",
        "idor": "Implement object-level authorization checks. Validate that the user has permission to access the specific object referenced by the ID.",
        "bfla": "Implement function-level access control. Verify that the user has the required role or permission before executing admin functions.",
        "mass-assignment": "Use allowlists to specify which fields can be updated. Never directly pass user input to database operations.",
        "race-condition-auth": "Use database transactions or locking mechanisms to prevent race conditions in authorization decisions.",
        "privilege-escalation": "Never allow users to modify their own role or permissions. Implement server-side authorization for all privilege changes.",
        "missing-auth": "Add authentication middleware to protect sensitive endpoints. Require valid credentials before processing requests.",
        "missing-authz": "Add authorization checks after authentication. Verify the user has permission to perform the requested action.",
        "direct-db-access": "Implement authorization middleware that validates permissions before any database operation.",
        "horizontal-priv-esc": "Compare the resource's owner ID with the authenticated user's ID before allowing access.",
        "vertical-priv-esc": "Implement strict server-side role validation. Never trust client-provided role or permission data.",
    };
    return {
        vulnType,
        name: vulnNames[vulnType],
        description: details.description,
        severity: SEVERITY_MAP[vulnType],
        confidence: 70,
        endpoint: endpoint.path,
        method: endpoint.method,
        file: endpoint.file,
        line: endpoint.line,
        authCheck: {
            present: endpoint.hasAuth,
            bypassable: endpoint.authzChecks.some((c) => c.potentiallyBypassable),
            bypassReason: endpoint.authzChecks.find((c) => c.potentiallyBypassable)?.bypassReason,
        },
        resourceAccess: details.resourceAccess,
        cweIds: CWE_MAP[vulnType],
        owaspRefs: OWASP_MAP[vulnType],
        remediation: remediations[vulnType],
    };
}
/**
 * Calculate confidence score for analysis
 */
function calculateConfidence(endpoint, resourceAccesses, vulnerabilities) {
    let confidence = 50;
    // Higher confidence if we found clear patterns
    if (endpoint.dbQueries.length > 0)
        confidence += 15;
    if (endpoint.authzChecks.length > 0)
        confidence += 10;
    if (resourceAccesses.length > 0)
        confidence += 10;
    if (vulnerabilities.length > 0)
        confidence += 5;
    // Lower confidence for complex endpoints
    if (endpoint.pathParams.length > 3)
        confidence -= 10;
    return Math.max(0, Math.min(100, confidence));
}
/**
 * Batch analyze multiple endpoints
 */
export async function analyzeEndpoints(endpoints, projectPath) {
    const analyses = [];
    for (const endpoint of endpoints) {
        try {
            const analysis = await analyzeAuthorizationFlow(endpoint, projectPath);
            analyses.push(analysis);
        }
        catch (error) {
            logger.debug("auth_flow_analyzer.endpoint_error", {
                endpoint: endpoint.path,
                error: String(error),
            });
        }
    }
    return analyses;
}
//# sourceMappingURL=auth-flow-analyzer.js.map