"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NestAuthAuthGuard = void 0;
const common_1 = require("@nestjs/common");
const auth_constants_1 = require("../../auth.constants");
const core_1 = require("@nestjs/core");
const jwt_service_1 = require("../../core/services/jwt.service");
const session_manager_service_1 = require("../../session/services/session-manager.service");
const access_key_service_1 = require("../../user/services/access-key.service");
const skip_mfa_decorator_1 = require("../../core/decorators/skip-mfa.decorator");
const permissions_decorator_1 = require("../../core/decorators/permissions.decorator");
const role_decorator_1 = require("../../core/decorators/role.decorator");
const auth_config_service_1 = require("../../core/services/auth-config.service");
const cookie_helper_1 = require("../../utils/cookie.helper");
const lodash_1 = require("lodash");
const debug_logger_service_1 = require("../../core/services/debug-logger.service");
let NestAuthAuthGuard = class NestAuthAuthGuard {
    reflector;
    jwtService;
    sessionManager;
    accessKeyService;
    authConfigService;
    debugLogger;
    constructor(reflector, jwtService, sessionManager, accessKeyService, authConfigService, debugLogger) {
        this.reflector = reflector;
        this.jwtService = jwtService;
        this.sessionManager = sessionManager;
        this.accessKeyService = accessKeyService;
        this.authConfigService = authConfigService;
        this.debugLogger = debugLogger;
    }
    async canActivate(context) {
        const request = context.switchToHttp().getRequest();
        const response = context.switchToHttp().getResponse();
        this.debugLogger.logFunctionEntry('canActivate', 'AuthGuard', {
            method: request.method,
            url: request.url,
            path: request.path
        });
        const isOptional = this.reflector.getAllAndOverride(auth_constants_1.OPTIONAL_AUTH_KEY, [
            context.getHandler(),
            context.getClass(),
        ]);
        this.debugLogger.debug(`Auth check - isOptional: ${isOptional}`, 'AuthGuard', { isOptional, path: request.path });
        request.user = null;
        request.session = null;
        request.accessKey = null;
        request.authType = null;
        const { token, authType } = this.extractToken(request);
        if (!token) {
            this.debugLogger.debug('No token found in request', 'AuthGuard', { isOptional });
            if (isOptional) {
                this.debugLogger.debug('Optional auth - proceeding without user data', 'AuthGuard');
                return true;
            }
            else {
                this.debugLogger.warn('Required auth - no token provided', 'AuthGuard');
                throw new common_1.UnauthorizedException({
                    message: 'No authentication provided',
                    code: auth_constants_1.ERROR_CODES.NO_AUTH_PROVIDED
                });
            }
        }
        this.debugLogger.debug(`Token found - authType: ${authType}`, 'AuthGuard', { authType, tokenLength: token.length });
        let isAuthenticated = false;
        try {
            switch (authType) {
                case 'bearer':
                    this.debugLogger.debug('Handling JWT authentication', 'AuthGuard');
                    isAuthenticated = await this.handleJwtAuth(context, request, response, token, isOptional);
                    break;
                case 'apikey':
                    this.debugLogger.debug('Handling API key authentication', 'AuthGuard');
                    isAuthenticated = await this.handleApiKeyAuth(request, token, isOptional);
                    break;
                default:
                    this.debugLogger.warn(`Invalid auth type: ${authType}`, 'AuthGuard');
                    if (isOptional) {
                        return true;
                    }
                    else {
                        throw new common_1.UnauthorizedException({
                            message: 'Invalid authentication type',
                            code: auth_constants_1.ERROR_CODES.INVALID_AUTH_TYPE
                        });
                    }
            }
        }
        catch (error) {
            this.debugLogger.logError(error, 'AuthGuard', { isOptional, authType });
            if (isOptional) {
                this.debugLogger.debug('Optional auth failed - proceeding without user data', 'AuthGuard');
                return true;
            }
            else {
                throw error;
            }
        }
        this.debugLogger.debug(`Authentication result: ${isAuthenticated}`, 'AuthGuard', { isAuthenticated, userId: request.user?.sub || request.user?.id });
        if (!isAuthenticated && !isOptional) {
            this.debugLogger.warn('Authentication failed (required)', 'AuthGuard');
            return false;
        }
        if (isAuthenticated && request.user) {
            this.debugLogger.debug('Checking authorization (roles/permissions)', 'AuthGuard');
            await this.checkAuthorization(context, request);
        }
        this.debugLogger.logFunctionExit('canActivate', 'AuthGuard', { allowed: true });
        return true;
    }
    extractToken(request) {
        const config = this.authConfigService.getConfig();
        const accessTokenType = config.accessTokenType;
        const checkHeader = accessTokenType !== 'cookie';
        const checkCookie = accessTokenType !== 'header';
        this.debugLogger.verbose('Extracting token from request', 'AuthGuard', { accessTokenType, checkHeader, checkCookie });
        if (checkHeader) {
            const authHeader = request.headers.authorization;
            if (authHeader) {
                const [type, token] = authHeader.split(' ');
                if (type && token) {
                    const authType = type.toLowerCase();
                    if (authType === 'bearer' || authType === 'apikey') {
                        this.debugLogger.debug(`Token extracted from Authorization header`, 'AuthGuard', { authType, source: 'header' });
                        return { token, authType };
                    }
                }
            }
        }
        if (checkCookie) {
            const cookieToken = cookie_helper_1.CookieHelper.get(request, 'accessToken');
            if (cookieToken) {
                this.debugLogger.debug('Token extracted from cookie', 'AuthGuard', { authType: 'bearer', source: 'cookie' });
                return { token: cookieToken, authType: 'bearer' };
            }
        }
        this.debugLogger.verbose('No token found in request', 'AuthGuard');
        return { token: null, authType: null };
    }
    async handleJwtAuth(context, request, response, token, isOptional = false) {
        this.debugLogger.logFunctionEntry('handleJwtAuth', 'AuthGuard', { isOptional });
        try {
            this.debugLogger.verbose('Verifying JWT token', 'AuthGuard');
            const payload = await this.jwtService.verifyToken(token);
            this.debugLogger.debug('JWT token verified successfully', 'AuthGuard', { userId: payload.sub, sessionId: payload.sessionId });
            const config = this.authConfigService.getConfig();
            if (config.guards?.beforeAuth) {
                this.debugLogger.verbose('Executing beforeAuth hook', 'AuthGuard');
                const result = await config.guards.beforeAuth(request, payload);
                if (result && result.reject) {
                    this.debugLogger.warn('beforeAuth hook rejected authentication', 'AuthGuard', { reason: result.reason });
                    throw new common_1.UnauthorizedException({
                        message: result.reason || 'Authentication rejected by custom guard',
                        code: auth_constants_1.ERROR_CODES.ACCESS_DENIED
                    });
                }
            }
            request.user = payload;
            request.authType = 'jwt';
            this.debugLogger.verbose('Looking up session', 'AuthGuard', { sessionId: payload.sessionId });
            const session = await this.sessionManager.getSession(payload.sessionId);
            if (!session) {
                this.debugLogger.warn('Session not found', 'AuthGuard', { sessionId: payload.sessionId });
                if (isOptional) {
                    request.user = null;
                    request.authType = null;
                    return false;
                }
                else {
                    throw new common_1.UnauthorizedException({
                        message: 'Session not found',
                        code: auth_constants_1.ERROR_CODES.SESSION_NOT_FOUND
                    });
                }
            }
            this.debugLogger.debug('Session found', 'AuthGuard', { sessionId: session.id });
            if (config.jwt?.validateToken) {
                this.debugLogger.verbose('Executing validateToken hook', 'AuthGuard');
                const isValid = await config.jwt.validateToken(payload, session);
                if (!isValid) {
                    this.debugLogger.warn('validateToken hook returned invalid', 'AuthGuard');
                    throw new common_1.UnauthorizedException({
                        message: 'Token validation failed',
                        code: auth_constants_1.ERROR_CODES.INVALID_TOKEN
                    });
                }
            }
            request.session = session;
            if (session.user && session.user.isActive === false) {
                this.debugLogger.warn('User is not active', 'AuthGuard', { userId: session.user.id });
                if (isOptional) {
                    request.user = null;
                    request.authType = null;
                    return false;
                }
                else {
                    throw new common_1.UnauthorizedException({
                        message: 'User is not active',
                        code: auth_constants_1.ERROR_CODES.ACCOUNT_INACTIVE
                    });
                }
            }
            await this.checkMfa(context, payload, isOptional);
            if (config.guards?.afterAuth) {
                this.debugLogger.verbose('Executing afterAuth hook', 'AuthGuard');
                if (session.data?.user) {
                    await config.guards.afterAuth(request, session.data.user, session);
                }
            }
            this.debugLogger.logFunctionExit('handleJwtAuth', 'AuthGuard', { success: true });
            return true;
        }
        catch (error) {
            this.debugLogger.logError(error, 'AuthGuard.handleJwtAuth');
            if (isOptional) {
                this.debugLogger.debug('JWT auth failed but optional - continuing', 'AuthGuard');
                return true;
            }
            else {
                if (error instanceof common_1.UnauthorizedException || error.status) {
                    throw error;
                }
                throw new common_1.UnauthorizedException({
                    message: 'Invalid or expired token',
                    code: auth_constants_1.ERROR_CODES.INVALID_TOKEN
                });
            }
        }
    }
    async handleApiKeyAuth(request, token, isOptional = false) {
        this.debugLogger.logFunctionEntry('handleApiKeyAuth', 'AuthGuard', { isOptional });
        const [publicKey, privateKey] = token.split('.');
        if (!publicKey || !privateKey) {
            this.debugLogger.warn('Invalid API key format', 'AuthGuard');
            if (isOptional) {
                return false;
            }
            else {
                throw new common_1.UnauthorizedException({
                    message: 'Invalid API key format',
                    code: auth_constants_1.ERROR_CODES.INVALID_API_KEY_FORMAT
                });
            }
        }
        try {
            this.debugLogger.verbose('Validating API key', 'AuthGuard', { publicKey: publicKey.substring(0, 8) + '...' });
            const isValid = await this.accessKeyService.validateAccessKey(publicKey, privateKey);
            if (!isValid) {
                this.debugLogger.warn('Invalid API key', 'AuthGuard');
                if (isOptional) {
                    return false;
                }
                else {
                    throw new common_1.UnauthorizedException({
                        message: 'Invalid API key',
                        code: auth_constants_1.ERROR_CODES.INVALID_API_KEY
                    });
                }
            }
            this.debugLogger.verbose('Getting access key details', 'AuthGuard');
            const accessKey = await this.accessKeyService.getAccessKey(publicKey);
            await this.accessKeyService.updateAccessKeyLastUsed(publicKey);
            request.user = accessKey.user;
            request.accessKey = accessKey;
            request.authType = 'api-key';
            this.debugLogger.debug('API key authentication successful', 'AuthGuard', { userId: accessKey.user?.id, keyName: accessKey.name });
            this.debugLogger.logFunctionExit('handleApiKeyAuth', 'AuthGuard', { success: true });
            return true;
        }
        catch (error) {
            this.debugLogger.logError(error, 'AuthGuard.handleApiKeyAuth');
            if (isOptional) {
                return false;
            }
            else {
                throw error;
            }
        }
    }
    async checkMfa(context, payload, isOptional = false) {
        const skipMfa = this.reflector.getAllAndOverride(skip_mfa_decorator_1.SKIP_MFA_KEY, [
            context.getHandler(),
            context.getClass(),
        ]);
        const isMfaEnabled = payload.isMfaEnabled;
        const isMfaVerified = payload.isMfaVerified;
        this.debugLogger.verbose('Checking MFA requirements', 'AuthGuard', { isMfaEnabled, isMfaVerified, skipMfa });
        if (isMfaEnabled && !isMfaVerified && !skipMfa) {
            this.debugLogger.warn('MFA verification required but not verified', 'AuthGuard');
            if (isOptional) {
                throw new Error('MFA verification required - cannot proceed with optional auth');
            }
            else {
                throw new common_1.UnauthorizedException({
                    message: 'Multi-factor authentication is required',
                    code: auth_constants_1.ERROR_CODES.MFA_REQUIRED
                });
            }
        }
        this.debugLogger.debug('MFA check passed', 'AuthGuard');
    }
    async checkAuthorization(context, request) {
        const requiredPermissions = this.getRequiredPermissions(context);
        const requiredRoles = this.getRequiredRoles(context);
        const requiredGuard = this.getRequiredGuard(context);
        this.debugLogger.verbose('Checking authorization requirements', 'AuthGuard', { requiredPermissions, requiredRoles, requiredGuard });
        if (!requiredPermissions.length && !requiredRoles.length) {
            this.debugLogger.debug('No authorization requirements - allowing access', 'AuthGuard');
            return;
        }
        const user = request.user;
        if (!user) {
            this.debugLogger.warn('Authorization check failed - no user', 'AuthGuard');
            throw new common_1.ForbiddenException({
                message: 'Access denied: User not authenticated',
                code: auth_constants_1.ERROR_CODES.UNAUTHORIZED,
            });
        }
        if (requiredRoles.length > 0) {
            this.debugLogger.debug('Checking required roles', 'AuthGuard', { requiredRoles, requiredGuard });
            await this.checkRoles(user, requiredRoles, requiredGuard);
        }
        if (requiredPermissions.length > 0) {
            this.debugLogger.debug('Checking required permissions', 'AuthGuard', { requiredPermissions });
            await this.checkPermissions(user, requiredPermissions);
        }
        this.debugLogger.debug('Authorization check passed', 'AuthGuard');
    }
    getRequiredPermissions(context) {
        let permissions = this.reflector.getAllAndOverride(permissions_decorator_1.PERMISSIONS_KEY, [context.getHandler(), context.getClass()]);
        if (!permissions) {
            return [];
        }
        return typeof permissions === 'string' ? [permissions] : permissions;
    }
    getRequiredRoles(context) {
        let roles = this.reflector.getAllAndOverride(role_decorator_1.ROLES_KEY, [context.getHandler(), context.getClass()]);
        if (!roles) {
            return [];
        }
        return typeof roles === 'string' ? [roles] : roles;
    }
    getRequiredGuard(context) {
        return this.reflector.getAllAndOverride(role_decorator_1.GUARD_KEY, [context.getHandler(), context.getClass()]);
    }
    async resolveUserRoles(user) {
        const config = this.authConfigService.getConfig();
        if (config.authorization?.resolveRoles) {
            return await config.authorization.resolveRoles(user);
        }
        if (!user.roles || !Array.isArray(user.roles)) {
            return [];
        }
        return user.roles
            .filter((role) => role.isActive)
            .map((role) => role.name);
    }
    async checkRoles(user, requiredRoles, requiredGuard) {
        if (requiredGuard) {
            const userGuards = (0, lodash_1.uniq)(user.roles?.map((role) => role.guard));
            if (!userGuards.includes(requiredGuard)) {
                throw new common_1.ForbiddenException({
                    message: `Access denied: Guard mismatch. Required: ${requiredGuard}, Found: ${userGuards.join(', ')}`,
                    code: auth_constants_1.ERROR_CODES.GUARD_MISMATCH,
                });
            }
        }
        const userRoleNames = await this.resolveUserRoles(user);
        if (userRoleNames.length === 0 && (!user.roles || !Array.isArray(user.roles))) {
            throw new common_1.ForbiddenException({
                message: 'Access denied: No roles assigned',
                code: auth_constants_1.ERROR_CODES.NO_ROLES_ASSIGNED,
            });
        }
        const hasAllRoles = requiredRoles.every(role => userRoleNames.includes(role));
        if (!hasAllRoles) {
            const missingRoles = requiredRoles.filter(role => !userRoleNames.includes(role));
            throw new common_1.ForbiddenException({
                message: `Access denied: Missing required roles: ${missingRoles.join(', ')}`,
                code: auth_constants_1.ERROR_CODES.MISSING_REQUIRED_ROLES,
            });
        }
    }
    async checkPermissions(user, requiredPermissions) {
        const config = this.authConfigService.getConfig();
        let userPermissions = [];
        if (config.authorization?.resolvePermissions) {
            const roles = await this.resolveUserRoles(user);
            userPermissions = await config.authorization.resolvePermissions(user, roles);
        }
        else {
            if (!user.roles || !Array.isArray(user.roles)) {
                throw new common_1.ForbiddenException({
                    message: 'Access denied: No roles assigned for permission check',
                    code: auth_constants_1.ERROR_CODES.NO_ROLES_ASSIGNED,
                });
            }
            userPermissions = this.getUserPermissions(user.roles);
        }
        const hasAllPermissions = requiredPermissions.every(permission => userPermissions.includes(permission));
        if (!hasAllPermissions) {
            const missingPermissions = requiredPermissions.filter(permission => !userPermissions.includes(permission));
            throw new common_1.ForbiddenException({
                message: `Access denied: Missing required permissions: ${missingPermissions.join(', ')}`,
                code: auth_constants_1.ERROR_CODES.MISSING_REQUIRED_PERMISSIONS,
            });
        }
    }
    getUserPermissions(roles) {
        const permissions = new Set();
        roles.forEach(role => {
            if (!role.isActive) {
                return;
            }
            if (role.permissions && Array.isArray(role.permissions)) {
                role.permissions.forEach(permission => permissions.add(permission));
            }
        });
        return Array.from(permissions);
    }
};
exports.NestAuthAuthGuard = NestAuthAuthGuard;
exports.NestAuthAuthGuard = NestAuthAuthGuard = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [core_1.Reflector,
        jwt_service_1.JwtService,
        session_manager_service_1.SessionManagerService,
        access_key_service_1.AccessKeyService,
        auth_config_service_1.AuthConfigService,
        debug_logger_service_1.DebugLoggerService])
], NestAuthAuthGuard);
//# sourceMappingURL=auth.guard.js.map