"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);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthService = void 0;
const common_1 = require("@nestjs/common");
const common_2 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const user_entity_1 = require("../../user/entities/user.entity");
const access_role_resolver_util_1 = require("../../role/utils/access-role-resolver.util");
const auth_constants_1 = require("../../auth.constants");
const mfa_service_1 = require("./mfa.service");
const jwt_service_1 = require("../../core/services/jwt.service");
const event_emitter_1 = require("@nestjs/event-emitter");
const session_manager_service_1 = require("../../session/services/session-manager.service");
const request_context_1 = require("../../request-context/request-context");
const nest_auth_contracts_1 = require("@ackplus/nest-auth-contracts");
const user_registered_event_1 = require("../events/user-registered.event");
const user_created_event_1 = require("../../user/events/user-created.event");
const has_token_1 = require("../../utils/has-token");
const user_logged_in_event_1 = require("../events/user-logged-in.event");
const login_failed_event_1 = require("../events/login-failed.event");
const user_2fa_verified_event_1 = require("../events/user-2fa-verified.event");
const user_refresh_token_event_1 = require("../events/user-refresh-token.event");
const auth_provider_registry_service_1 = require("../../core/services/auth-provider-registry.service");
const tenant_service_1 = require("../../tenant/services/tenant.service");
const debug_logger_service_1 = require("../../core/services/debug-logger.service");
const auth_config_service_1 = require("../../core/services/auth-config.service");
const cookie_helper_1 = require("../../utils/cookie.helper");
const user_service_1 = require("../../user/services/user.service");
const auth_constants_2 = require("../../auth.constants");
const role_mapper_util_1 = require("../../role/utils/role-mapper.util");
const utils_1 = require("../../utils");
const otp_flow_service_1 = require("./otp-flow.service");
const logout_service_1 = require("./logout.service");
const session_token_service_1 = require("./session-token.service");
const passwordless_code_requested_event_1 = require("../events/passwordless-code-requested.event");
const lodash_1 = require("lodash");
const platform_access_entity_1 = require("../../user/entities/platform-access.entity");
let AuthService = class AuthService {
    userRepository;
    authProviderRegistry;
    mfaService;
    sessionManager;
    jwtService;
    eventEmitter;
    tenantService;
    debugLogger;
    authConfigService;
    userService;
    otpFlow;
    logoutService;
    sessionTokenService;
    tenantContext;
    authConfig;
    constructor(userRepository, authProviderRegistry, mfaService, sessionManager, jwtService, eventEmitter, tenantService, debugLogger, authConfigService, userService, otpFlow, logoutService, sessionTokenService, tenantContext) {
        this.userRepository = userRepository;
        this.authProviderRegistry = authProviderRegistry;
        this.mfaService = mfaService;
        this.sessionManager = sessionManager;
        this.jwtService = jwtService;
        this.eventEmitter = eventEmitter;
        this.tenantService = tenantService;
        this.debugLogger = debugLogger;
        this.authConfigService = authConfigService;
        this.userService = userService;
        this.otpFlow = otpFlow;
        this.logoutService = logoutService;
        this.sessionTokenService = sessionTokenService;
        this.tenantContext = tenantContext;
        this.authConfig = this.authConfigService.getConfig();
    }
    getUserWithRoles(userId, relations = []) {
        return this.sessionTokenService.getUserWithRoles(userId, relations);
    }
    getUserWithAccess(userId, tenantId, isPlatformAccess = false) {
        return this.sessionTokenService.getUserWithAccess(userId, tenantId, isPlatformAccess);
    }
    async signup(input) {
        this.debugLogger.logFunctionEntry('signup', 'AuthService', { email: input.email, phone: input.phone, hasPassword: !!input.password });
        try {
            if (this.authConfig.registration?.enabled === false) {
                throw new common_1.ForbiddenException({
                    message: 'Registration is disabled',
                    code: auth_constants_1.ERROR_CODES.REGISTRATION_DISABLED,
                });
            }
            if (this.authConfig.registrationHooks?.beforeSignup) {
                const req = request_context_1.RequestContext.currentRequest();
                input = await this.authConfig.registrationHooks.beforeSignup(input, { request: req });
            }
            const { email, phone, password, tenantId } = input;
            this.assertTenantIdAllowed(tenantId);
            await this.tenantService.resolveTenantId(tenantId);
            this.debugLogger.logAuthOperation('signup', 'email|phone', undefined, { email, phone, resolvedTenantId: tenantId });
            if (!email && !phone) {
                this.debugLogger.error('Signup failed: Neither email nor phone provided', 'AuthService');
                throw new common_1.BadRequestException({
                    message: 'Either email or phone must be provided',
                    code: auth_constants_1.ERROR_CODES.EMAIL_OR_PHONE_REQUIRED,
                });
            }
            const providersToLink = [];
            if (email && this.authConfig.emailAuth?.enabled !== false) {
                const provider = this.authProviderRegistry.getProvider(auth_constants_1.EMAIL_AUTH_PROVIDER);
                if (provider) {
                    providersToLink.push({ provider, providerId: email, type: 'email' });
                }
            }
            if (phone && this.authConfig.phoneAuth?.enabled === true) {
                const provider = this.authProviderRegistry.getProvider(auth_constants_1.PHONE_AUTH_PROVIDER);
                if (provider) {
                    providersToLink.push({ provider, providerId: phone, type: 'phone' });
                }
            }
            if (providersToLink.length === 0) {
                this.debugLogger.error('Provider not found for signup', 'AuthService', { email: !!email, phone: !!phone });
                throw new common_1.InternalServerErrorException({
                    message: 'Phone or email authentication is not enabled',
                    code: auth_constants_1.ERROR_CODES.PROVIDER_NOT_FOUND,
                });
            }
            for (const item of providersToLink) {
                const requiredTenantId = this.tenantService.checkRequiredTenant(tenantId);
                const identity = await item.provider.findIdentity(item.providerId, requiredTenantId ? tenantId : undefined);
                if (identity) {
                    this.debugLogger.warn('Identity already exists', 'AuthService', { email: !!email, phone: !!phone, tenantId });
                    if (item.type === 'email') {
                        throw new common_1.BadRequestException({
                            message: 'Email already exists in this tenant',
                            code: auth_constants_1.ERROR_CODES.EMAIL_ALREADY_EXISTS,
                        });
                    }
                    if (item.type === 'phone') {
                        throw new common_1.BadRequestException({
                            message: 'Phone number already exists in this tenant',
                            code: auth_constants_1.ERROR_CODES.PHONE_ALREADY_EXISTS,
                        });
                    }
                }
            }
            this.debugLogger.debug('Creating new user via UserService', 'AuthService', { email: !!email, phone: !!phone, tenantId });
            const request = request_context_1.RequestContext.currentRequest();
            const user = await this.userService.runInTransaction(async (manager) => {
                const created = await this.userService.createUser({
                    email,
                    phone,
                    emailVerifiedAt: null,
                    phoneVerifiedAt: null,
                    password
                }, tenantId, input, manager);
                if (this.authConfig.registrationHooks?.onSignup) {
                    this.debugLogger.debug('Applying registrationHooks.onSignup hook', 'AuthService', { userId: created.id });
                    await this.authConfig.registrationHooks.onSignup(created, input, { request, manager });
                }
                return created;
            });
            this.debugLogger.info('User created successfully', 'AuthService', { userId: user.id, tenantId });
            await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.USER_CREATED, new user_created_event_1.UserCreatedEvent({ user, input, tenantId }));
            const { user: authUser, userAccess } = await this.getUserWithAccess(user.id, tenantId);
            this.debugLogger.debug('Creating session for new user', 'AuthService', { userId: authUser.id });
            const session = await this.sessionManager.createSessionFromUser(authUser, userAccess, { tenantId });
            const tokens = await this.generateTokensFromSession(session);
            const isRequiresMfa = await this.mfaService.isRequiresMfa(authUser.id);
            this.debugLogger.debug('Signup tokens generated', 'AuthService', { userId: authUser.id, isRequiresMfa });
            this.debugLogger.debug('Emitting user registration event', 'AuthService', { userId: authUser.id });
            const provider = providersToLink[0]?.provider;
            await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.REGISTERED, new user_registered_event_1.UserRegisteredEvent({
                user: authUser,
                userAccess,
                tenantId,
                input,
                provider,
                session,
                tokens,
                isRequiresMfa
            }));
            this.debugLogger.logFunctionExit('signup', 'AuthService', { userId: user.id, isRequiresMfa });
            const autoLoginAfterSignup = this.authConfig.registration?.autoLoginAfterSignup !== false;
            if (!autoLoginAfterSignup) {
                return {
                    message: 'Account created successfully. Please login.',
                    accessToken: '',
                    refreshToken: '',
                    isRequiresMfa: false,
                };
            }
            return this.generateAuthResponse(authUser, session, tokens, isRequiresMfa, undefined);
        }
        catch (error) {
            this.debugLogger.logError(error, 'signup', { email: input.email, phone: input.phone });
            this.handleError(error, 'signup');
            throw error;
        }
    }
    async login(input) {
        let { credentials, providerName, createUserIfNotExists = false, guard, tenantId } = input;
        const isPlatformAccess = await access_role_resolver_util_1.AccessRoleResolver.isPlatformAccess();
        this.debugLogger.logFunctionEntry('login', 'AuthService', { providerName, createUserIfNotExists, guard, tenantId });
        try {
            if (!isPlatformAccess) {
                this.assertTenantIdAllowed(tenantId);
            }
            let resolvedTenantId = null;
            if (isPlatformAccess) {
                resolvedTenantId = null;
            }
            else {
                await this.tenantService.resolveTenantId(tenantId);
                resolvedTenantId = tenantId;
            }
            this.debugLogger.logAuthOperation('login', providerName, undefined, { tenantId, resolvedTenantId, createUserIfNotExists, isPlatformAccess });
            const provider = this.authProviderRegistry.getProvider(providerName);
            if (!provider) {
                throw new common_1.UnauthorizedException({
                    message: 'Invalid authentication providerName or provider is not enabled',
                    code: auth_constants_1.ERROR_CODES.INVALID_PROVIDER,
                });
            }
            const requiredFields = provider.getRequiredFields();
            if (!requiredFields.every(field => credentials[field])) {
                throw new common_1.BadRequestException({
                    message: `Missing ${requiredFields.join(', ')} required fields`,
                    code: auth_constants_1.ERROR_CODES.MISSING_REQUIRED_FIELDS,
                });
            }
            const authProviderUser = await provider.validate(credentials, resolvedTenantId);
            const identity = await provider.findIdentityByUserId(authProviderUser.userId);
            let user = identity?.user || null;
            if (!user) {
                if (!createUserIfNotExists) {
                    throw new common_1.UnauthorizedException({
                        message: 'Invalid credentials',
                        code: auth_constants_1.ERROR_CODES.INVALID_CREDENTIALS,
                    });
                }
                user = await this.handleSocialLogin(provider, authProviderUser, resolvedTenantId);
            }
            else {
                user = await this.applyProviderVerification(user, authProviderUser);
            }
            if (user.isActive === false) {
                throw new common_1.UnauthorizedException({
                    message: 'Your account is suspended, please contact support',
                    code: auth_constants_1.ERROR_CODES.ACCOUNT_INACTIVE,
                });
            }
            const { user: authUser, userAccess, platformAccess } = await this.getUserWithAccess(user.id, resolvedTenantId, isPlatformAccess);
            if (this.authConfig.loginHooks?.onLogin) {
                this.debugLogger.debug('Applying loginHooks.onLogin hook', 'AuthService', { userId: authUser.id });
                const request = request_context_1.RequestContext.currentRequest();
                await this.authConfig.loginHooks.onLogin(authUser, input, { userAccess, platformAccess, request, provider });
            }
            if (isPlatformAccess) {
                if (authUser && !platformAccess) {
                    throw new common_1.ForbiddenException({
                        message: 'Only platform admins can login',
                        code: auth_constants_1.ERROR_CODES.ACCESS_DENIED,
                    });
                }
            }
            else {
                await this.ensureTenantAccess(authUser, resolvedTenantId, createUserIfNotExists);
            }
            let isRequiresMfa = false;
            let isTrusted = false;
            if (!provider.skipMfa) {
                isRequiresMfa = await this.mfaService.isRequiresMfa(authUser.id);
            }
            user.isMfaEnabled = isRequiresMfa;
            if (guard && (platformAccess || userAccess)) {
                let guardRoles = [];
                if (isPlatformAccess) {
                    guardRoles = platformAccess?.roles ?? [];
                }
                else {
                    guardRoles = userAccess?.roles ?? [];
                }
                const isExistsGuard = guardRoles.some(r => r.guard === guard);
                if (!isExistsGuard) {
                    throw new common_1.UnauthorizedException({
                        message: 'Invalid credentials',
                        code: auth_constants_1.ERROR_CODES.INVALID_CREDENTIALS,
                    });
                }
            }
            let session = await this.sessionManager.createSessionFromUser(authUser, userAccess, {
                tenantId: resolvedTenantId,
                platformAccess: platformAccess,
                isPlatformAccess: isPlatformAccess ?? false
            });
            if (isRequiresMfa) {
                isTrusted = await this.checkTrustedDevice(user);
                if (isTrusted) {
                    isRequiresMfa = false;
                }
                session = await this.sessionManager.updateSession(session.id, {
                    data: { ...session.data, isMfaEnabled: true, isMfaVerified: isTrusted }
                });
            }
            const tokens = await this.generateTokensFromSession(session);
            await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.LOGGED_IN, new user_logged_in_event_1.UserLoggedInEvent({
                user: authUser,
                userAccess,
                platformAccess,
                tenantId,
                input,
                provider,
                session,
                tokens,
                isRequiresMfa
            }));
            return this.generateAuthResponse(authUser, session, tokens, isRequiresMfa);
        }
        catch (error) {
            this.debugLogger.logError(error, 'login', { providerName, createUserIfNotExists });
            await this.emitLoginFailed(input, error);
            this.handleError(error, 'login');
            throw error;
        }
    }
    async emitLoginFailed(input, error) {
        try {
            const creds = input?.credentials ?? {};
            const identifier = creds.email ?? creds.phone ?? creds.identifier ?? undefined;
            const req = request_context_1.RequestContext.currentRequest?.();
            const resp = error?.getResponse?.();
            const status = error?.getStatus?.();
            const reasonCode = (typeof resp === 'object' && resp?.code) ||
                error?.code ||
                (status ? `HTTP_${status}` : 'LOGIN_FAILED');
            await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.LOGIN_FAILED, new login_failed_event_1.LoginFailedEvent({
                identifier,
                providerName: input?.providerName,
                reasonCode,
                reason: error?.message,
                ip: req?.ip ?? req?.headers?.['x-forwarded-for'],
                userAgent: req?.headers?.['user-agent'],
                tenantId: input?.tenantId ?? null,
                at: new Date(),
            }));
        }
        catch {
        }
    }
    async resolveOrCreateUserForSend(input) {
        const passwordlessConfig = this.authConfigService.getConfig().passwordless;
        const { channel, tenantId } = input;
        const raw = input.identifier?.trim();
        if (!raw) {
            throw new common_1.BadRequestException({
                message: 'Identifier is required',
                code: auth_constants_1.ERROR_CODES.MISSING_REQUIRED_FIELD,
            });
        }
        if (channel === 'email') {
            const emailNorm = (0, utils_1.normalizedEmail)(raw);
            if (!emailNorm) {
                throw new common_1.BadRequestException({
                    message: 'A valid email is required',
                    code: auth_constants_1.ERROR_CODES.MISSING_REQUIRED_FIELD,
                });
            }
            const provider = this.authProviderRegistry.getProvider(auth_constants_1.EMAIL_AUTH_PROVIDER);
            if (!provider) {
                throw new common_1.BadRequestException({
                    message: 'Email authentication is not enabled',
                    code: auth_constants_1.ERROR_CODES.PROVIDER_NOT_FOUND,
                });
            }
            const identity = await provider.findIdentity(emailNorm, tenantId);
            if (identity?.user) {
                return identity.user;
            }
            if (!passwordlessConfig.allowSignUp) {
                return null;
            }
            const reg = this.authConfigService.getConfig().registration;
            if (reg?.enabled === false) {
                throw new common_1.ForbiddenException({
                    message: 'Registration is disabled',
                    code: auth_constants_1.ERROR_CODES.REGISTRATION_DISABLED,
                });
            }
            return this.userService.createUser({ email: emailNorm }, tenantId ?? undefined, { source: 'passwordless', channel: 'email' });
        }
        else {
            const phoneNorm = (0, utils_1.normalizedPhone)(raw);
            if (!phoneNorm) {
                throw new common_1.BadRequestException({
                    message: 'Phone is required',
                    code: auth_constants_1.ERROR_CODES.MISSING_REQUIRED_FIELD,
                });
            }
            const provider = this.authProviderRegistry.getProvider(auth_constants_1.PHONE_AUTH_PROVIDER);
            if (!provider) {
                throw new common_1.BadRequestException({
                    message: 'Phone authentication is not enabled',
                    code: auth_constants_1.ERROR_CODES.PROVIDER_NOT_FOUND,
                });
            }
            const identity = await provider.findIdentity(phoneNorm, tenantId);
            if (identity?.user) {
                return identity.user;
            }
            if (!passwordlessConfig.allowSignUp) {
                return null;
            }
            const reg = this.authConfigService.getConfig().registration;
            if (reg?.enabled === false) {
                throw new common_1.ForbiddenException({
                    message: 'Registration is disabled',
                    code: auth_constants_1.ERROR_CODES.REGISTRATION_DISABLED,
                });
            }
            return this.userService.createUser({ phone: phoneNorm }, tenantId ?? undefined, { source: 'passwordless', channel: 'sms' });
        }
    }
    async passwordlessSend(input) {
        const passwordlessConfig = this.authConfigService.getConfig().passwordless;
        if (!passwordlessConfig.enabled) {
            throw new common_1.ForbiddenException({
                message: 'Passwordless login is disabled',
                code: auth_constants_1.ERROR_CODES.PASSWORDLESS_DISABLED,
            });
        }
        this.debugLogger.logFunctionEntry('sendCode', 'AuthService', { channel: input.channel });
        try {
            await this.tenantService.resolveTenantId(input.tenantId);
            const user = await this.resolveOrCreateUserForSend(input);
            if (!user) {
                return { message: 'If an account exists, a login code has been sent' };
            }
            const { entity: otpEntity, plainCode: code } = await this.otpFlow.createOtp({
                userId: user.id,
                type: nest_auth_contracts_1.NestAuthOTPTypeEnum.PASSWORDLESS_LOGIN,
                replaceExisting: true,
            });
            await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.PASSWORDLESS_CODE_REQUESTED, new passwordless_code_requested_event_1.PasswordlessCodeRequestedEvent({
                user,
                tenantId: input.tenantId,
                channel: input.channel,
                otp: otpEntity,
                code,
            }));
            this.debugLogger.logFunctionExit('sendCode', 'AuthService', { userId: user.id });
            return { message: 'If an account exists, a login code has been sent' };
        }
        catch (error) {
            this.debugLogger.logError(error, 'sendCode');
            throw error;
        }
    }
    async verify2fa(input) {
        this.debugLogger.logFunctionEntry('verify2fa', 'AuthService', { method: input.method });
        try {
            let user = await request_context_1.RequestContext.currentUser();
            const session = request_context_1.RequestContext.currentSession();
            if (!session) {
                throw new common_1.UnauthorizedException({
                    message: 'Session not found',
                    code: auth_constants_1.ERROR_CODES.SESSION_NOT_FOUND,
                });
            }
            this.debugLogger.debug('Verifying MFA code', 'AuthService', { userId: session.userId, method: input.method });
            const isValid = await this.mfaService.verifyMfa(session.userId, input.otp, input.method);
            if (!isValid) {
                throw new common_1.UnauthorizedException({
                    message: 'Invalid MFA code',
                    code: auth_constants_1.ERROR_CODES.MFA_CODE_INVALID,
                });
            }
            const payload = await this.sessionManager.updateSession(session.id, {
                data: {
                    ...session.data,
                    isMfaVerified: true,
                }
            });
            const tokens = await this.generateTokensFromSession(payload);
            let trustToken;
            if (input.trustDevice) {
                const req = request_context_1.RequestContext.currentRequest();
                if (req) {
                    const userAgent = (req.headers['user-agent'] || '');
                    const ip = (req.ip || req.socket.remoteAddress || '');
                    trustToken = await this.mfaService.createTrustedDevice(session.userId, userAgent, ip);
                }
            }
            if (!user) {
                return null;
            }
            this.debugLogger.debug('Emitting 2FA verified event', 'AuthService', { userId: user.id });
            await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.TWO_FACTOR_VERIFIED, new user_2fa_verified_event_1.User2faVerifiedEvent({
                user,
                tenantId: payload.data?.tenantId ?? user?.tenantId,
                input,
                session: payload,
                tokens
            }));
            this.debugLogger.logFunctionExit('verify2fa', 'AuthService', { userId: user.id });
            return this.generateAuthResponse(user, payload, tokens, false, trustToken);
        }
        catch (error) {
            this.debugLogger.logError(error, 'verify2fa', { method: input.method });
            this.handleError(error, 'mfa');
            throw error;
        }
    }
    async switchTenant(tenantId) {
        const session = request_context_1.RequestContext.currentSession();
        if (!session) {
            throw new common_1.UnauthorizedException({
                message: 'Session not found',
                code: auth_constants_1.ERROR_CODES.SESSION_NOT_FOUND,
            });
        }
        if (!this.authConfig.tenant?.enabled) {
            throw new common_1.BadRequestException({
                message: 'Multi-tenancy is disabled on this deployment.',
                code: auth_constants_1.ERROR_CODES.TENANT_SWITCHING_DISABLED,
            });
        }
        const tenantMode = this.authConfig.tenant?.mode ?? nest_auth_contracts_1.TenantModeEnum.ISOLATED;
        if (tenantMode === nest_auth_contracts_1.TenantModeEnum.ISOLATED) {
            throw new common_1.BadRequestException({
                message: 'Tenant switching is not supported in isolated mode. Sign in to the target tenant directly.',
                code: auth_constants_1.ERROR_CODES.TENANT_SWITCHING_NOT_SUPPORTED,
            });
        }
        const resolvedTenantId = await this.tenantService.resolveTenantId(tenantId || null);
        const { user, userAccess } = await this.getUserWithAccess(session.userId, resolvedTenantId);
        if (!user) {
            throw new common_1.UnauthorizedException({
                message: 'User not found',
                code: auth_constants_1.ERROR_CODES.USER_NOT_FOUND,
            });
        }
        if (resolvedTenantId && !userAccess) {
            const platformAccess = await platform_access_entity_1.NestAuthPlatformAccess.findOne({
                where: { userId: user.id, isActive: true },
            });
            if (!platformAccess) {
                throw new common_1.ForbiddenException({
                    message: 'You do not have access to that tenant.',
                    code: auth_constants_1.ERROR_CODES.NOT_A_MEMBER_OF_TENANT,
                });
            }
        }
        await this.ensureTenantAccess(user, resolvedTenantId, false);
        const rolesWithPermissions = userAccess?.roles ?? [];
        const permissions = (0, lodash_1.chain)(rolesWithPermissions)
            .map((role) => (0, role_mapper_util_1.getRolePermissionNames)(role))
            .flatten()
            .uniq()
            .value();
        const roles = rolesWithPermissions?.map((role) => (0, role_mapper_util_1.mapRoleToSessionSnapshot)(role));
        const updatedSession = await this.sessionManager.updateSession(session.id, {
            data: {
                ...(session.data || {}),
                user,
                roles,
                permissions,
                tenantId: resolvedTenantId || undefined,
            }
        });
        const tokens = await this.generateTokensFromSession(updatedSession);
        return this.generateAuthResponse(user, updatedSession, tokens, false);
    }
    async getSessionUserData() {
        const session = request_context_1.RequestContext.currentSession();
        const tenantId = request_context_1.RequestContext.currentTenantId();
        const isPlatformAccess = await access_role_resolver_util_1.AccessRoleResolver.isPlatformAccess();
        const { user, userAccess, platformAccess } = await this.getUserWithAccess(session.userId, tenantId, isPlatformAccess);
        let rolesWithPermissions = [];
        if (isPlatformAccess) {
            rolesWithPermissions = platformAccess?.roles ?? [];
        }
        else {
            rolesWithPermissions = userAccess?.roles ?? [];
        }
        const permissions = (0, lodash_1.chain)(rolesWithPermissions)
            .map((role) => (0, role_mapper_util_1.getRolePermissionNames)(role))
            .flatten()
            .uniq()
            .value();
        const userRoles = rolesWithPermissions.map((role) => (0, lodash_1.pick)(role, ['id', 'name', 'guard']));
        const config = this.authConfigService.getConfig();
        let serializedUser = {};
        if (config.user?.getSessionUserData) {
            serializedUser = await config.user.getSessionUserData(user);
        }
        return {
            ...(0, lodash_1.pick)(user, ['id', 'email', 'phone', 'emailVerifiedAt', 'phoneVerifiedAt', 'isMfaEnabled', 'metadata', 'mustChangePassword']),
            ...(serializedUser || {}),
            roles: userRoles,
            permissions,
        };
    }
    async send2faCode(userId, method) {
        const user = await this.userRepository.findOne({ where: { id: userId } });
        if (!user) {
            throw new common_1.UnauthorizedException({
                message: 'User not found',
                code: auth_constants_1.ERROR_CODES.USER_NOT_FOUND,
            });
        }
        await this.mfaService.sendMfaCode(user.id, method);
        return true;
    }
    async handleSocialLogin(provider, providerUser, tenantId) {
        let identity = await provider.findIdentity(providerUser.userId, tenantId);
        if (identity) {
            return identity.user;
        }
        const linkUserWith = provider.linkUserWith();
        const linkUserValue = providerUser?.[linkUserWith] || providerUser.userId;
        let user = await this.userRepository.findOne({ where: { [linkUserWith]: linkUserValue } });
        if (!user) {
            try {
                user = await this.userService.createUser({
                    [linkUserWith]: linkUserValue,
                    emailVerifiedAt: new Date(),
                    metadata: providerUser.metadata || {},
                }, tenantId, {
                    [linkUserWith]: linkUserValue,
                    firstName: (providerUser.metadata?.name ?? '').split(' ')[0],
                    lastName: (providerUser.metadata?.name ?? '').split(' ').slice(1).join(' '),
                    ...providerUser,
                    provider: provider.providerName,
                    description: 'Social login auto-creation'
                });
            }
            catch (error) {
                if (error instanceof common_1.ConflictException || error.status === 409) {
                    user = await this.userRepository.findOne({ where: { [linkUserWith]: linkUserValue } });
                    if (!user) {
                        throw error;
                    }
                }
                else {
                    throw error;
                }
            }
        }
        await provider.linkToUser(user.id, providerUser.userId, providerUser.metadata || {});
        return user;
    }
    async refreshToken(refreshToken) {
        this.debugLogger.logFunctionEntry('refreshToken', 'AuthService', { hasRefreshToken: !!refreshToken });
        try {
            if (!refreshToken) {
                this.debugLogger.error('No refresh token provided', 'AuthService');
                throw new common_1.UnauthorizedException({
                    message: 'No refresh token provided',
                    code: auth_constants_1.ERROR_CODES.REFRESH_TOKEN_INVALID,
                });
            }
            const isPlatformAccess = await access_role_resolver_util_1.AccessRoleResolver.isPlatformAccess();
            this.debugLogger.debug('Verifying refresh token', 'AuthService');
            let payload;
            try {
                payload = await this.jwtService.verifyToken(refreshToken);
            }
            catch (error) {
                this.debugLogger.warn('Invalid or expired refresh token', 'AuthService');
                throw new common_1.UnauthorizedException({
                    message: 'Invalid or expired refresh token',
                    code: auth_constants_1.ERROR_CODES.REFRESH_TOKEN_EXPIRED,
                });
            }
            if (!payload.sessionId) {
                throw new common_1.UnauthorizedException({
                    message: 'Invalid refresh token payload',
                    code: auth_constants_1.ERROR_CODES.REFRESH_TOKEN_INVALID,
                });
            }
            const session = await this.sessionManager.getSession(payload.sessionId);
            if (!session) {
                throw new common_1.UnauthorizedException({
                    message: 'Invalid refresh token',
                    code: auth_constants_1.ERROR_CODES.REFRESH_TOKEN_INVALID,
                });
            }
            const storedRefreshHash = session.refreshToken;
            if (storedRefreshHash) {
                const secret = this.authConfig.session?.jwt?.secret ?? '';
                if (!(0, has_token_1.timingSafeEqualHex)(storedRefreshHash, (0, has_token_1.hmacSha256Hex)(secret, refreshToken))) {
                    throw new common_1.UnauthorizedException({
                        message: 'Refresh token is no longer valid (rotated or replayed)',
                        code: auth_constants_1.ERROR_CODES.REFRESH_TOKEN_INVALID,
                    });
                }
            }
            const { user, userAccess, platformAccess } = await this.getUserWithAccess(session.userId, session.data?.tenantId ?? null, isPlatformAccess);
            if (!user) {
                await this.sessionManager.revokeSession(session.id, 'security');
                throw new common_1.UnauthorizedException({
                    message: 'User not found',
                    code: auth_constants_1.ERROR_CODES.USER_NOT_FOUND,
                });
            }
            if (user.isActive === false) {
                await this.sessionManager.revokeSession(session.id, 'security');
                throw new common_1.UnauthorizedException({
                    message: 'Your account is suspended, please contact support',
                    code: auth_constants_1.ERROR_CODES.ACCOUNT_INACTIVE,
                });
            }
            const tenantId = session.data?.tenantId ?? null;
            if (!isPlatformAccess && !userAccess) {
                try {
                    await this.ensureTenantAccess(user, tenantId, false);
                }
                catch (error) {
                    await this.sessionManager.revokeSession(session.id, 'security');
                    throw error;
                }
            }
            if (isPlatformAccess && !platformAccess) {
                await this.sessionManager.revokeSession(session.id, 'security');
                throw new common_1.UnauthorizedException({
                    message: 'You are not authorized to platform access',
                    code: auth_constants_1.ERROR_CODES.ACCESS_DENIED,
                });
            }
            const isMfaVerified = !!session.data?.isMfaVerified;
            let roles = [];
            if (isPlatformAccess) {
                roles = platformAccess?.roles ?? [];
            }
            else {
                roles = userAccess?.roles ?? [];
            }
            const permissions = (0, lodash_1.chain)(roles)
                .map((role) => (0, role_mapper_util_1.getRolePermissionNames)(role))
                .flatten()
                .uniq()
                .value();
            let freshSessionData = {
                user,
                isMfaVerified,
                roles: roles.map((role) => (0, role_mapper_util_1.mapRoleToSessionSnapshot)(role)),
                permissions,
                tenantId,
                isPlatformAccess: isPlatformAccess ?? false,
            };
            const customize = auth_config_service_1.AuthConfigService.getOptions().session?.customizeSessionData;
            if (customize) {
                freshSessionData = await customize(freshSessionData, user);
            }
            const refreshedSession = await this.sessionManager.refreshSession(session);
            const updatedSession = await this.sessionManager.updateSession(refreshedSession.id, {
                data: {
                    ...(refreshedSession.data ?? {}),
                    ...freshSessionData,
                },
            });
            this.debugLogger.debug('Generating new tokens from refreshed session', 'AuthService', { sessionId: updatedSession.id });
            const tokens = await this.generateTokensFromSession(updatedSession);
            this.debugLogger.debug('Emitting refresh token event', 'AuthService', { sessionId: updatedSession.id });
            await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.REFRESH_TOKEN, new user_refresh_token_event_1.UserRefreshTokenEvent({
                oldRefreshToken: refreshToken,
                session: updatedSession,
                tokens,
            }));
            this.debugLogger.logFunctionExit('refreshToken', 'AuthService', { sessionId: updatedSession.id });
            return this.generateAuthResponse(user, updatedSession, tokens, false);
        }
        catch (error) {
            this.debugLogger.logError(error, 'refreshToken', { hasRefreshToken: !!refreshToken });
            this.handleError(error, 'refresh');
            throw error;
        }
    }
    async logout(logoutType = 'user', reason) {
        return this.logoutService.logout(logoutType, reason);
    }
    async logoutAll(userId, logoutType = 'user', reason) {
        return this.logoutService.logoutAll(userId, logoutType, reason);
    }
    async ensureTenantAccess(user, tenantId, allowAutoJoin = false) {
        if (!tenantId || !this.tenantContext.isEnabled()) {
            return;
        }
        const isMember = await this.userService.isUserInTenant(user.id, tenantId);
        if (!isMember) {
            if (allowAutoJoin) {
                await this.userService.ensureUserAccess(user.id, tenantId);
                return;
            }
            throw new common_1.ForbiddenException({
                message: 'User does not belong to this tenant',
                code: auth_constants_1.ERROR_CODES.ACCESS_DENIED,
            });
        }
    }
    handleError(error, context) {
        const config = this.authConfigService.getConfig();
        if (config.errorHandler) {
            const result = config.errorHandler(error, context);
            if (result) {
                throw result;
            }
        }
    }
    async generateTokensFromSession(session) {
        return this.sessionTokenService.generateTokensFromSession(session);
    }
    async generateAuthResponse(user, session, tokens, isRequiresMfa, trustToken) {
        return this.sessionTokenService.generateAuthResponse(user, session, tokens, isRequiresMfa, trustToken);
    }
    async applyProviderVerification(user, providerUser) {
        const updates = {};
        if (providerUser.emailVerified === true &&
            !user.emailVerifiedAt &&
            user.email &&
            providerUser.email &&
            user.email.toLowerCase() === providerUser.email.toLowerCase()) {
            updates.emailVerifiedAt = new Date();
        }
        if (providerUser.phoneVerified === true &&
            !user.phoneVerifiedAt &&
            user.phone &&
            providerUser.phone &&
            user.phone === providerUser.phone) {
            updates.phoneVerifiedAt = new Date();
        }
        if (Object.keys(updates).length === 0) {
            return user;
        }
        await this.userRepository.update({ id: user.id }, updates);
        return (await this.userRepository.findOne({ where: { id: user.id } })) ?? user;
    }
    assertTenantIdAllowed(tenantId) {
        if (!this.authConfig.tenant?.enabled && tenantId) {
            throw new common_1.BadRequestException({
                message: 'tenantId provided but multi-tenancy is disabled on this deployment.',
                code: auth_constants_1.ERROR_CODES.TENANT_NOT_ENABLED,
            });
        }
    }
    async checkTrustedDevice(user) {
        const trustCookieName = auth_config_service_1.AuthConfigService.getOptions().mfa?.trustDeviceStorageName || auth_constants_1.NEST_AUTH_TRUST_DEVICE_KEY;
        const req = request_context_1.RequestContext.currentRequest();
        if (req) {
            let trustToken = cookie_helper_1.CookieHelper.get(req, trustCookieName);
            if (!trustToken) {
                trustToken = req.headers[trustCookieName];
            }
            if (trustToken) {
                return await this.mfaService.validateTrustedDevice(user.id, trustToken);
            }
        }
        return false;
    }
};
exports.AuthService = AuthService;
exports.AuthService = AuthService = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(user_entity_1.NestAuthUser)),
    __param(13, (0, common_2.Inject)(auth_constants_2.NEST_AUTH_TENANT_CONTEXT_SERVICE)),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        auth_provider_registry_service_1.AuthProviderRegistryService,
        mfa_service_1.MfaService,
        session_manager_service_1.SessionManagerService,
        jwt_service_1.JwtService,
        event_emitter_1.EventEmitter2,
        tenant_service_1.TenantService,
        debug_logger_service_1.DebugLoggerService,
        auth_config_service_1.AuthConfigService,
        user_service_1.UserService,
        otp_flow_service_1.OtpFlowService,
        logout_service_1.LogoutService,
        session_token_service_1.SessionTokenService, Object])
], AuthService);
//# sourceMappingURL=auth.service.js.map