"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 = exports.OPTIONAL_AUTH_KEY = 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");
exports.OPTIONAL_AUTH_KEY = 'optional_auth';
let NestAuthAuthGuard = class NestAuthAuthGuard {
    reflector;
    jwtService;
    sessionManager;
    accessKeyService;
    authConfigService;
    constructor(reflector, jwtService, sessionManager, accessKeyService, authConfigService) {
        this.reflector = reflector;
        this.jwtService = jwtService;
        this.sessionManager = sessionManager;
        this.accessKeyService = accessKeyService;
        this.authConfigService = authConfigService;
    }
    async canActivate(context) {
        const request = context.switchToHttp().getRequest();
        const response = context.switchToHttp().getResponse();
        const isOptional = this.reflector.getAllAndOverride(exports.OPTIONAL_AUTH_KEY, [
            context.getHandler(),
            context.getClass(),
        ]);
        request.user = null;
        request.session = null;
        request.accessKey = null;
        request.authType = null;
        const authHeader = request.headers.authorization;
        if (!authHeader) {
            if (isOptional) {
                return true;
            }
            else {
                throw new common_1.UnauthorizedException({
                    message: 'No authentication provided',
                    code: auth_constants_1.ERROR_CODES.NO_AUTH_PROVIDED
                });
            }
        }
        const [type, token] = authHeader.split(' ');
        if (!type || !token) {
            if (isOptional) {
                return true;
            }
            else {
                throw new common_1.UnauthorizedException({
                    message: 'Invalid authentication format',
                    code: auth_constants_1.ERROR_CODES.INVALID_AUTH_FORMAT
                });
            }
        }
        let isAuthenticated = false;
        try {
            switch (type.toLowerCase()) {
                case 'bearer':
                    isAuthenticated = await this.handleJwtAuth(context, request, response, token, isOptional);
                    break;
                case 'apikey':
                    isAuthenticated = await this.handleApiKeyAuth(request, token, isOptional);
                    break;
                default:
                    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) {
            if (isOptional) {
                return true;
            }
            else {
                throw error;
            }
        }
        if (!isAuthenticated && !isOptional) {
            return false;
        }
        if (isAuthenticated && request.user) {
            await this.checkAuthorization(context, request);
        }
        return true;
    }
    async handleJwtAuth(context, request, response, token, isOptional = false) {
        try {
            const payload = await this.jwtService.verifyToken(token);
            const config = this.authConfigService.getConfig();
            if (config.guards?.beforeAuth) {
                const result = await config.guards.beforeAuth(request, payload);
                if (result && result.reject) {
                    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';
            const session = await this.sessionManager.getSession(payload.sessionId);
            if (!session) {
                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
                    });
                }
            }
            if (config.jwt?.validateToken) {
                const isValid = await config.jwt.validateToken(payload, session);
                if (!isValid) {
                    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) {
                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) {
                if (session.data?.user) {
                    await config.guards.afterAuth(request, session.data.user, session);
                }
            }
            return true;
        }
        catch (error) {
            if (isOptional) {
                return false;
            }
            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) {
        const [publicKey, privateKey] = token.split('.');
        if (!publicKey || !privateKey) {
            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 {
            const isValid = await this.accessKeyService.validateAccessKey(publicKey, privateKey);
            if (!isValid) {
                if (isOptional) {
                    return false;
                }
                else {
                    throw new common_1.UnauthorizedException({
                        message: 'Invalid API key',
                        code: auth_constants_1.ERROR_CODES.INVALID_API_KEY
                    });
                }
            }
            const accessKey = await this.accessKeyService.getAccessKey(publicKey);
            await this.accessKeyService.updateAccessKeyLastUsed(publicKey);
            request.user = accessKey.user;
            request.accessKey = accessKey;
            request.authType = 'api-key';
            return true;
        }
        catch (error) {
            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;
        if (isMfaEnabled && !isMfaVerified && !skipMfa) {
            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
                });
            }
        }
    }
    async checkAuthorization(context, request) {
        const requiredPermissions = this.getRequiredPermissions(context);
        const requiredRoles = this.getRequiredRoles(context);
        if (!requiredPermissions.length && !requiredRoles.length) {
            return;
        }
        const user = request.user;
        if (!user) {
            throw new common_1.ForbiddenException({
                message: 'Access denied: User not authenticated',
                code: auth_constants_1.ERROR_CODES.UNAUTHORIZED,
            });
        }
        if (requiredRoles.length > 0) {
            await this.checkRoles(user, requiredRoles);
        }
        if (requiredPermissions.length > 0) {
            await this.checkPermissions(user, requiredPermissions);
        }
    }
    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;
    }
    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) {
        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])
], NestAuthAuthGuard);
//# sourceMappingURL=auth.guard.js.map