/**
 * @fileoverview Provides utility functions for authorization, specifically for
 * checking token scopes against required permissions for a given operation.
 * @module src/mcp-server/transports/auth/core/authUtils
 */
import { config } from '../../../../config/index.js';
import { authContext } from '../../../../mcp-server/transports/auth/lib/authContext.js';
import { forbidden, unauthorized } from '../../../../types-global/errors.js';
import { logger } from '../../../../utils/internal/logger.js';
import { requestContextService } from '../../../../utils/internal/requestContext.js';
/**
 * Checks if the current authentication context contains all the specified scopes.
 * When auth is disabled (`MCP_AUTH_MODE=none`), scope checks are skipped.
 * When auth is enabled and the auth context is missing, fails closed with Unauthorized.
 * When `MCP_AUTH_DISABLE_SCOPE_CHECKS=true`, scope enforcement is bypassed after the
 * auth-context presence check; signature, audience, issuer, and expiry validation
 * remain intact.
 *
 * @param requiredScopes - An array of scope strings that are mandatory for the operation.
 * @param parentContext - Optional parent request context for trace correlation.
 * @throws {McpError} Throws `Unauthorized` if auth is enabled but no auth context exists.
 * @throws {McpError} Throws `Forbidden` if auth is active and required scopes are missing.
 */
export function withRequiredScopes(requiredScopes, parentContext) {
    const initialContext = parentContext
        ? {
            ...parentContext,
            operation: 'withRequiredScopesCheck',
            requiredScopes,
        }
        : requestContextService.createRequestContext({
            operation: 'withRequiredScopesCheck',
            additionalContext: { requiredScopes },
        });
    // Explicitly check if auth is disabled — only skip scope checks when intentionally off.
    if (config.mcpAuthMode === 'none') {
        logger.debug('Auth disabled (MCP_AUTH_MODE=none), skipping scope check.', initialContext);
        return;
    }
    const store = authContext.getStore();
    // Auth is enabled but no context exists — fail closed.
    if (!store?.authInfo) {
        logger.warning('Auth enabled but no authentication context found. Denying request.', initialContext);
        throw unauthorized('Authentication required but no auth context was established.', initialContext);
    }
    if (config.mcpAuthDisableScopeChecks) {
        logger.debug('Scope enforcement bypassed (MCP_AUTH_DISABLE_SCOPE_CHECKS=true).', initialContext);
        return;
    }
    logger.debug('Performing scope authorization check.', initialContext);
    const { scopes: grantedScopes, clientId, subject } = store.authInfo;
    const grantedScopeSet = new Set(grantedScopes);
    const missingScopes = requiredScopes.filter((scope) => !grantedScopeSet.has(scope));
    const finalContext = {
        ...initialContext,
        grantedScopes,
        clientId,
        subject,
    };
    if (missingScopes.length > 0) {
        // Log full details server-side (grantedScopes, clientId, subject stay in logs)
        logger.warning('Authorization failed: Missing required scopes.', {
            ...finalContext,
            missingScopes,
        });
        // Do not include scope names in the client-facing error data — prevents scope enumeration.
        // Full details (grantedScopes, missingScopes, clientId, subject) are in the server-side log above.
        throw forbidden('Insufficient permissions.');
    }
    logger.debug('Scope authorization successful.', finalContext);
}
//# sourceMappingURL=authUtils.js.map