"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
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 __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
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.ProfileService = void 0;
const api_locale_1 = require("@hed-hog/api-locale");
const api_prisma_1 = require("@hed-hog/api-prisma");
const common_1 = require("@nestjs/common");
const jwt_1 = require("@nestjs/jwt");
const server_1 = require("@simplewebauthn/server");
const qrcode = __importStar(require("qrcode"));
const speakeasy = __importStar(require("speakeasy"));
const challenge_service_1 = require("../challenge/challenge.service");
const mail_service_1 = require("../mail/mail.service");
const security_service_1 = require("../security/security.service");
const setting_service_1 = require("../setting/setting.service");
const token_service_1 = require("../token/token.service");
const user_service_1 = require("../user/user.service");
let ProfileService = class ProfileService {
    constructor(prisma, user, jwt, setting, security, challenge, token, mail) {
        this.prisma = prisma;
        this.user = user;
        this.jwt = jwt;
        this.setting = setting;
        this.security = security;
        this.challenge = challenge;
        this.token = token;
        this.mail = mail;
    }
    async changeEmail(locale, userId, { email, password, pin }) {
        var _a;
        const checkAlreadyExists = await this.prisma.user_identifier.findFirst({
            where: { type: 'email', value: email, enabled: true },
        });
        if (checkAlreadyExists) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('emailAlreadyInUse', locale, 'The provided email is already in use.'));
        }
        const settings = await this.setting.getSettingValues([
            'require-email-verification'
        ]);
        if (settings['require-email-verification'] === true && !pin) {
            const checkIfExists = await this.prisma.user_identifier.findFirst({
                where: { type: 'email', value: email, user_id: userId },
            });
            if (!checkIfExists) {
                await this.prisma.user_identifier.create({
                    data: {
                        type: 'email',
                        value: email,
                        user_id: userId,
                    },
                });
            }
            return {
                requireEmailVerification: settings['require-email-verification'] === true
            };
        }
        const user = await this.prisma.user.findUnique({
            where: { id: userId },
            include: { user_credential: true }
        });
        if (!user) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
        }
        const credentials = ((_a = user.user_credential) === null || _a === void 0 ? void 0 : _a.filter((c) => c.type === 'password')) || [];
        if (!await this.security.validatePassword(locale, credentials, password)) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('accessDenied', locale, 'Access denied.'));
        }
        await this.prisma.$transaction(async (tx) => {
            const userIdentifier = await tx.user_identifier.findFirst({
                where: { user_id: userId, type: 'email', enabled: true },
            });
            if (!userIdentifier) {
                throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('emailIdentifierNotFound', locale, 'Email identifier not found.'));
            }
            await tx.user_identifier.updateMany({
                where: { user_id: userId, type: 'email' },
                data: { value: email, verified_at: null, enabled: true },
            });
            await tx.user_identifier_challenge.deleteMany({
                where: {
                    user_identifier_id: userIdentifier.id
                }
            });
        });
        return {
            requireEmailVerification: settings['require-email-verification'] === true
        };
    }
    async updatePreferences(locale, userId, { theme, language }) {
        if (theme) {
            await this.setting.setSettingUserValue(locale, userId, 'theme-mode', theme);
        }
        if (language) {
            await this.setting.setSettingUserValue(locale, userId, 'language', language);
        }
        return { success: true };
    }
    async confirmEmailVerificationMfa(locale, userId, { pin, challengeId, name }) {
        const user = await this.prisma.user.findUnique({
            where: { id: userId },
        });
        if (!user) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
        }
        const challenge = await this.prisma.user_mfa_challenge.findFirst({
            where: {
                id: challengeId,
                expires_at: {
                    gt: new Date()
                },
                verified_at: null
            },
            include: {
                user_mfa: {
                    include: {
                        user_mfa_email: true
                    }
                }
            }
        });
        if (!challenge) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('challengeNotFound', locale, 'Challenge not found.'));
        }
        await this.prisma.user_mfa_challenge.update({
            where: {
                id: challengeId
            },
            data: {
                attempts: { increment: 1 }
            }
        });
        if (challenge.hash !== this.security.hashWithPepper(pin)) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidPin', locale, 'Invalid PIN.'));
        }
        await this.prisma.$transaction(async (tx) => {
            await tx.user_mfa_challenge.update({
                where: { id: challengeId },
                data: { verified_at: new Date() },
            });
            await tx.user_mfa.update({
                where: { id: challenge.user_mfa.id },
                data: {
                    verified_at: new Date(),
                    name: name || challenge.user_mfa.name
                },
            });
            await tx.user_identifier.updateMany({
                where: {
                    user_id: userId,
                    verified_at: { not: null },
                    value: challenge.user_mfa.user_mfa_email[0].email,
                },
                data: {
                    enabled: true,
                    verified_at: new Date(),
                }
            });
        });
        const updatedUser = await this.prisma.user.findFirst({ where: { id: user.id } });
        const codes = await this.createMfaRecoveryCodes(user.id);
        const newToken = await this.getToken(updatedUser);
        return Object.assign(Object.assign({}, newToken), { codes });
    }
    async confirmEmailVerification(locale, userId, { pin, challengeId }) {
        const challenge = await this.prisma.user_identifier_challenge.findUnique({
            where: { id: challengeId },
        });
        if (!challenge) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('challengeNotFound', locale, 'Challenge not found.'));
        }
        if (challenge.hash !== this.security.hashWithPepper(pin)) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidPin', locale, 'Invalid PIN.'));
        }
        const verifiedAt = new Date();
        await this.prisma.$transaction(async (tx) => {
            const challenge = await tx.user_identifier_challenge.findUnique({
                where: { id: challengeId },
            });
            let userIdentifierId = challenge.user_identifier_id;
            if (!userIdentifierId) {
                const userIdentifier = await tx.user_identifier.findFirst({
                    where: { user_id: userId, type: 'email' },
                    select: { id: true }
                });
                userIdentifierId = userIdentifier.id;
                await tx.user_identifier_challenge.update({
                    where: { id: challengeId },
                    data: { user_identifier_id: userIdentifierId },
                });
            }
            await tx.user_identifier_challenge.update({
                where: { id: challengeId },
                data: { verified_at: verifiedAt },
            });
            await tx.user_identifier.update({
                where: { id: userIdentifierId },
                data: {
                    verified_at: verifiedAt,
                    enabled: true
                }
            });
            await tx.user_identifier.deleteMany({
                where: {
                    user_id: userId,
                    type: 'email',
                    id: { not: userIdentifierId }
                }
            });
        });
        return { success: true };
    }
    async sendEmailVerificationMfa(locale, userId, { email }) {
        return this.challenge.verifyMfaEmail(locale, userId, email);
    }
    async sendEmailVerification(locale, userId, { email }) {
        return this.challenge.verifyEmail(locale, userId, email);
    }
    async sendCodeToRemove(locale, userId, email) {
        return this.challenge.verifyEmail(locale, userId, email);
    }
    async getEmailIdentifier(userId) {
        return this.prisma.user_identifier.findFirst({
            where: { user_id: userId, type: 'email', enabled: true },
        });
    }
    async getProfile(userId) {
        return this.prisma.user.findUnique({
            where: { id: userId },
        });
    }
    async getMFAMethods(userId) {
        return this.prisma.user_mfa.findMany({
            where: {
                user_id: userId,
                verified_at: { not: null },
            },
            include: {
                user_mfa_email: true,
                user_mfa_totp: true,
                user_mfa_webauthn: true,
            },
        });
    }
    async updateMFA(userId, mfaId, { name }) {
        return this.prisma.user_mfa.update({
            where: { user_id: userId, id: mfaId },
            data: { name }
        });
    }
    async update(userid, { name }) {
        return this.prisma.user.update({
            where: { id: userid },
            data: {
                name,
            },
        });
    }
    async updateAvatar(locale, userId, file) {
        return this.user.changeAvatar(locale, userId, file);
    }
    async changePassword(locale, userId, { currentPassword, newPassword }, ipAddress, userAgent) {
        var _a, _b;
        const credential = await this.prisma.user_credential.findFirst({
            where: { user_id: userId, type: 'password' },
        });
        if (!credential) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
        }
        const isValidPassowrd = await this.security.validatePassword(locale, [credential], currentPassword);
        if (!isValidPassowrd) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('accessDenied', locale, 'Access denied.'));
        }
        const passwordHash = await this.security.hashArgon2(newPassword);
        await this.prisma.user_credential.update({
            where: { id: credential.id },
            data: { hash: passwordHash, requires_reset: false },
        });
        // Get user data and email for notification
        const user = await this.prisma.user.findUnique({
            where: { id: userId },
            include: {
                user_identifier: {
                    where: { type: 'email', enabled: true, verified_at: { not: null } },
                    select: { value: true },
                },
            },
        });
        if ((_b = (_a = user === null || user === void 0 ? void 0 : user.user_identifier) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value) {
            this.mail.sendTemplatedMail(locale, {
                email: user.user_identifier[0].value,
                slug: 'auth-change-password-notification',
                variables: {
                    name: user.name,
                    userAgent: userAgent || 'Unknown',
                    ipAddress: ipAddress || 'Unknown',
                    location: 'Unknown',
                },
            }).catch(error => {
                console.error('Failed to send password change notification:', error);
            });
        }
        return { success: true };
    }
    generateRecoveryCodes(count = 10) {
        const codes = Array.from({ length: count }, () => {
            const array = new Uint8Array(4);
            globalThis.crypto.getRandomValues(array);
            return Array.from(array)
                .map((b) => b.toString(16).padStart(2, '0'))
                .join('');
        });
        return codes;
    }
    async createMfaRecoveryCodes(userId) {
        const codes = this.generateRecoveryCodes();
        await this.prisma.user_recovery_code.deleteMany({
            where: {
                user_id: userId,
            },
        });
        const data = await Promise.all(codes.map(async (code) => ({
            user_id: userId,
            hash: await this.security.hashArgon2(code),
        })));
        await this.prisma.user_recovery_code.createMany({ data });
        return codes;
    }
    async getToken(user) {
        return { token: this.jwt.sign(user) };
    }
    async verifyMfaTotp(locale, userId, token, secret, name) {
        var _a, _b;
        const settings = await this.setting.getSettingValues([
            'mfa-issuer',
            'mfa-window',
            'mfa-setp',
            'system-name',
            'google_client_id',
            'google_client_secret',
            'google_scopes',
            'url',
            'mfa-challenge-expiration-minutes',
            'access-token-expiration-minutes'
        ]);
        const window = (_a = settings['mfa-window']) !== null && _a !== void 0 ? _a : 0;
        const step = (_b = settings['mfa-setp']) !== null && _b !== void 0 ? _b : 30;
        const user = await this.prisma.user.findFirst({
            where: { id: userId },
            include: { user_mfa: { include: { user_mfa_totp: true } } }
        });
        if (!user) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found'));
        }
        const decodedSecret = Buffer.from(secret, 'base64').toString('utf-8');
        const decryptedSecret = this.security.decrypt(decodedSecret);
        const isValid = speakeasy.totp.verify({
            secret: decryptedSecret,
            encoding: 'base32',
            token,
            window,
            step
        });
        if (!isValid) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidCode', locale, 'Invalid code'));
        }
        if (name) {
            await this.prisma.user_mfa.create({
                data: {
                    name,
                    user_id: userId,
                    type: 'totp',
                    verified_at: new Date(),
                    user_mfa_totp: {
                        create: {
                            secret: Buffer.from(decryptedSecret, 'utf-8'),
                        }
                    },
                    user_mfa_challenge: {
                        create: {
                            hash: await this.security.hashArgon2(decryptedSecret),
                            attempts: 1,
                            expires_at: new Date(),
                            verified_at: new Date()
                        }
                    }
                },
            });
        }
        const updatedUser = await this.prisma.user.findFirst({ where: { id: user.id } });
        const codes = await this.createMfaRecoveryCodes(updatedUser.id);
        const newToken = await this.getToken(updatedUser);
        return Object.assign(Object.assign({}, newToken), { codes });
    }
    async generateMfaTotp(locale, userId) {
        var _a, _b;
        const settings = await this.setting.getSettingValues([
            'mfa-issuer',
            'mfa-window',
            'mfa-setp',
            'system-name',
            'google_client_id',
            'google_client_secret',
            'google_scopes',
            'url',
            'mfa-challenge-expiration-minutes',
            'access-token-expiration-minutes'
        ]);
        const user = await this.prisma.user.findFirst({
            where: { id: userId },
            select: {
                user_mfa: true,
                user_identifier: true
            },
        });
        const issuer = (_a = settings['mfa-issuer']) !== null && _a !== void 0 ? _a : 'Hedhog';
        const userEmail = user.user_identifier.find(ui => ui.type === 'email').value;
        const appName = `${issuer} (${userEmail})`;
        const secret = speakeasy.generateSecret({
            length: 32,
            name: appName,
            otpauth: { label: appName, issuer },
            encoding: 'base32',
        });
        const step = (_b = settings['mfa-setp']) !== null && _b !== void 0 ? _b : 30;
        const otpauth = speakeasy.otpauthURL({
            secret: secret.base32,
            label: `${issuer}:${userEmail}`,
            issuer,
            period: step,
            encoding: 'base32',
        });
        const qrCode = await qrcode.toDataURL(otpauth);
        const encryptedSecret = this.security.encrypt(secret.base32);
        const encodedSecret = Buffer.from(encryptedSecret, 'utf-8').toString('base64');
        return { otpauthUrl: secret.otpauth_url, qrCode, secret: encodedSecret };
    }
    async removeMfaTotp(locale, userId, mfaId, token) {
        try {
            const user = await this.prisma.user.findFirst({
                where: { id: userId },
                include: { user_identifier: true, user_mfa: { include: { user_mfa_totp: true } } }
            });
            const totpMfa = user.user_mfa.find((m) => m.type === 'totp' && m.user_mfa_totp);
            if (!totpMfa || !totpMfa.user_mfa_totp) {
                throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('totpNotFound', locale, 'TOTP method not found'));
            }
            const secretBuffer = totpMfa.user_mfa_totp[0].secret;
            const secretBase32 = secretBuffer instanceof Uint8Array
                ? Buffer.from(secretBuffer).toString('utf-8')
                : String(secretBuffer);
            const encryptedSecret = this.security.encrypt(secretBase32);
            const encodedSecret = Buffer.from(encryptedSecret, 'utf-8').toString('base64');
            await this.verifyMfaTotp(locale, user.id, token, encodedSecret);
            await this.prisma.$transaction(async (tx) => {
                await tx.user_mfa.deleteMany({
                    where: {
                        id: mfaId,
                        user_id: userId,
                        type: 'totp',
                    },
                });
                await tx.user_recovery_code.deleteMany({
                    where: {
                        user_id: userId,
                    },
                });
            });
            return this.getUserToken(userId);
        }
        catch (error) {
            throw new common_1.BadRequestException(`Invalid code: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async removeMfaEmail(locale, userId, mfaId, token, providedHash) {
        try {
            await this.challenge.verifyCode(locale, userId, token, providedHash);
            await this.prisma.user_mfa.deleteMany({
                where: { id: mfaId, user_id: userId, type: 'email' },
            });
            return this.getUserToken(userId);
        }
        catch (error) {
            throw new common_1.BadRequestException(`Invalid code: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async removeMfaWithRecoveryCode(locale, userId, mfaId, recoveryCode) {
        try {
            const user = await this.prisma.user.findUnique({
                where: { id: userId },
                include: {
                    user_recovery_code: true,
                },
            });
            if (!user) {
                throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found'));
            }
            let isValid = false;
            for (const code of user.user_recovery_code) {
                if (await this.security.verifyArgon2(recoveryCode, code.hash)) {
                    isValid = true;
                    await this.prisma.user_recovery_code.delete({
                        where: { id: code.id },
                    });
                    break;
                }
            }
            if (!isValid) {
                throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidRecoveryCode', locale, 'Invalid recovery code.'));
            }
            await this.prisma.user_mfa.deleteMany({
                where: { id: mfaId, user_id: userId },
            });
            return this.getUserToken(userId);
        }
        catch (error) {
            throw new common_1.BadRequestException(`Invalid code: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async removeMfa(locale, userId, mfaId, data) {
        try {
            const user = await this.prisma.user.findUnique({
                where: { id: userId },
                include: {
                    user_mfa: {
                        where: { id: mfaId, verified_at: { not: null } },
                        include: {
                            user_mfa_totp: true,
                            user_mfa_email: true,
                            user_mfa_webauthn: true
                        }
                    },
                    user_recovery_code: true
                }
            });
            if (!user) {
                throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
            }
            const mfaToRemove = user.user_mfa.find(m => m.id === mfaId);
            if (!mfaToRemove) {
                throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('mfaMethodNotFound', locale, 'MFA method not found.'));
            }
            if (data.verificationType !== 'webauthn' && !data.token) {
                throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('verificationCodeRequired', locale, 'Verification code is required.'));
            }
            if (data.verificationType === 'webauthn') {
                if (!data.assertionResponse) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('assertionResponseRequired', locale, 'Assertion response is required for WebAuthn.'));
                }
                try {
                    await this.verifyWebAuthnAuthentication(userId, data.assertionResponse);
                }
                catch (error) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidWebAuthn', locale, 'WebAuthn verification failed.'));
                }
            }
            else if (data.verificationType === 'recovery') {
                if (!data.token) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('verificationCodeRequired', locale, 'Verification code is required.'));
                }
                let isValid = false;
                for (const recoveryCode of user.user_recovery_code) {
                    if (await this.security.verifyArgon2(data.token, recoveryCode.hash)) {
                        isValid = true;
                        await this.prisma.user_recovery_code.delete({
                            where: { id: recoveryCode.id }
                        });
                        break;
                    }
                }
                if (!isValid) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidRecoveryCode', locale, 'Invalid recovery code.'));
                }
            }
            else if (data.verificationType === 'email' || mfaToRemove.type === 'email') {
                if (!data.token) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('verificationCodeRequired', locale, 'Verification code is required.'));
                }
                try {
                    const isValid = await this.challenge.verifyMfaEmailCode(locale, userId, data.token);
                    if (!isValid) {
                        throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidVerificationCode', locale, 'Invalid verification code.'));
                    }
                }
                catch (error) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidVerificationCode', locale, 'Invalid verification code.'));
                }
            }
            else if (data.verificationType === 'totp' || mfaToRemove.type === 'totp') {
                const totpMfa = user.user_mfa.find(m => m.type === 'totp' && m.user_mfa_totp);
                if (!totpMfa || !totpMfa.user_mfa_totp || !totpMfa.user_mfa_totp[0]) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('totpMfaNotFound', locale, 'TOTP MFA not found.'));
                }
                const secretBuffer = totpMfa.user_mfa_totp[0].secret;
                const secretBase32 = secretBuffer instanceof Uint8Array
                    ? Buffer.from(secretBuffer).toString('utf-8')
                    : String(secretBuffer);
                const encryptedSecret = this.security.encrypt(secretBase32);
                const encodedSecret = Buffer.from(encryptedSecret, 'utf-8').toString('base64');
                try {
                    await this.verifyMfaTotp(locale, userId, data.token, encodedSecret);
                }
                catch (error) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidVerificationCode', locale, 'Invalid verification code.'));
                }
            }
            await this.prisma.user_mfa.deleteMany({
                where: { id: mfaId, user_id: userId },
            });
            return this.getUserToken(userId);
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to remove MFA: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async checkMfaBeforeAdd(locale, userId) {
        try {
            const mfaMethods = await this.prisma.user_mfa.findMany({
                where: {
                    user_id: userId,
                    verified_at: { not: null }
                },
                include: {
                    user_mfa_totp: true,
                    user_mfa_email: true,
                    user_mfa_webauthn: true
                }
            });
            if (mfaMethods.length === 0) {
                return { requiresVerification: false };
            }
            const totpMfa = mfaMethods.find(m => m.type === 'totp' && m.user_mfa_totp);
            const emailMfas = mfaMethods.filter(m => m.type === 'email' && m.user_mfa_email && m.user_mfa_email.length > 0);
            const webauthnMfa = mfaMethods.find(m => m.type === 'webauthn' && m.user_mfa_webauthn);
            const availableMethods = [];
            if (totpMfa) {
                availableMethods.push('totp');
            }
            if (emailMfas.length > 0) {
                const emails = emailMfas.map(mfa => mfa.user_mfa_email[0].email);
                await this.challenge.sendMfaCodeToMultipleEmails(locale, userId, emails);
                availableMethods.push('email');
            }
            if (availableMethods.length === 0 && !webauthnMfa) {
                return { requiresVerification: false };
            }
            if (availableMethods.length === 0 && webauthnMfa) {
                return {
                    requiresVerification: true,
                    availableMethods: [],
                    hasWebAuthn: true,
                    hasRecoveryCodes: true,
                };
            }
            if (availableMethods.length === 2) {
                return {
                    requiresVerification: true,
                    availableMethods,
                    hasWebAuthn: !!webauthnMfa,
                    hasRecoveryCodes: true,
                };
            }
            return {
                requiresVerification: true,
                verificationType: availableMethods[0],
                hasWebAuthn: !!webauthnMfa,
                hasRecoveryCodes: true,
            };
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to check MFA: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async checkMfaBeforeRemove(locale, userId) {
        try {
            const mfaMethods = await this.prisma.user_mfa.findMany({
                where: {
                    user_id: userId,
                    verified_at: { not: null }
                },
                include: {
                    user_mfa_totp: true,
                    user_mfa_email: true,
                    user_mfa_webauthn: true
                }
            });
            if (mfaMethods.length === 0) {
                throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('noMfaMethodsFound', locale, 'No MFA methods found.'));
            }
            const totpMfa = mfaMethods.find(m => m.type === 'totp' && m.user_mfa_totp);
            const emailMfas = mfaMethods.filter(m => m.type === 'email' && m.user_mfa_email && m.user_mfa_email.length > 0);
            const webauthnMfa = mfaMethods.find(m => m.type === 'webauthn' && m.user_mfa_webauthn);
            const availableMethods = [];
            if (totpMfa) {
                availableMethods.push('totp');
            }
            if (emailMfas.length > 0) {
                const emails = emailMfas.map(mfa => mfa.user_mfa_email[0].email);
                await this.challenge.sendMfaCodeToMultipleEmails(locale, userId, emails);
                availableMethods.push('email');
            }
            return {
                requiresVerification: true,
                availableMethods,
                hasWebAuthn: !!webauthnMfa,
                hasRecoveryCodes: mfaMethods.length > 0,
            };
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to check MFA before remove: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async verifyBeforeAddMfa(locale, userId, data) {
        try {
            const user = await this.prisma.user.findUnique({
                where: { id: userId },
                include: {
                    user_mfa: {
                        where: { verified_at: { not: null } },
                        include: {
                            user_mfa_totp: true,
                            user_mfa_email: true,
                            user_mfa_webauthn: true
                        }
                    },
                    user_recovery_code: true
                }
            });
            if (!user) {
                throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
            }
            if (user.user_mfa.length === 0) {
                return { success: true };
            }
            if (data.verificationType === 'webauthn') {
                if (!data.assertionResponse) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('assertionResponseRequired', locale, 'Assertion response is required for WebAuthn.'));
                }
                try {
                    await this.verifyWebAuthnAuthentication(userId, data.assertionResponse);
                }
                catch (error) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidWebAuthn', locale, 'WebAuthn verification failed.'));
                }
            }
            else if (data.verificationType === 'recovery') {
                if (!data.verificationCode) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('verificationCodeRequired', locale, 'Verification code is required.'));
                }
                let isValid = false;
                for (const recoveryCode of user.user_recovery_code) {
                    if (await this.security.verifyArgon2(data.verificationCode, recoveryCode.hash)) {
                        isValid = true;
                        await this.prisma.user_recovery_code.delete({
                            where: { id: recoveryCode.id }
                        });
                        break;
                    }
                }
                if (!isValid) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidRecoveryCode', locale, 'Invalid recovery code.'));
                }
            }
            else if (data.verificationType === 'email') {
                const emailMfa = user.user_mfa.find(m => m.type === 'email' && m.user_mfa_email);
                if (!emailMfa) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('emailMfaNotFound', locale, 'Email MFA not found.'));
                }
                if (!data.verificationCode) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('verificationCodeRequired', locale, 'Verification code is required.'));
                }
                try {
                    const isValid = await this.challenge.verifyMfaEmailCode(locale, userId, data.verificationCode);
                    if (!isValid) {
                        throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidVerificationCode', locale, 'Invalid verification code.'));
                    }
                }
                catch (error) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidVerificationCode', locale, 'Invalid verification code.'));
                }
            }
            else {
                const totpMfa = user.user_mfa.find(m => m.type === 'totp' && m.user_mfa_totp);
                if (!totpMfa || !totpMfa.user_mfa_totp[0]) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('totpMfaNotFound', locale, 'TOTP MFA not found.'));
                }
                const secretBuffer = totpMfa.user_mfa_totp[0].secret;
                const secretBase32 = secretBuffer instanceof Uint8Array
                    ? Buffer.from(secretBuffer).toString('utf-8')
                    : String(secretBuffer);
                const encryptedSecret = this.security.encrypt(secretBase32);
                const encodedSecret = Buffer.from(encryptedSecret, 'utf-8').toString('base64');
                try {
                    await this.verifyMfaTotp(locale, userId, data.verificationCode, encodedSecret);
                }
                catch (error) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidVerificationCode', locale, 'Invalid verification code.'));
                }
            }
            return { success: true };
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to verify MFA: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async sendRecoveryCodesVerification(locale, userId) {
        try {
            const mfaMethods = await this.prisma.user_mfa.findMany({
                where: {
                    user_id: userId,
                    verified_at: { not: null }
                },
                include: {
                    user_mfa_totp: true,
                    user_mfa_email: true,
                    user_mfa_webauthn: true
                }
            });
            if (mfaMethods.length === 0) {
                return { requiresVerification: false };
            }
            const totpMfa = mfaMethods.find(m => m.type === 'totp' && m.user_mfa_totp);
            const emailMfas = mfaMethods.filter(m => m.type === 'email' && m.user_mfa_email && m.user_mfa_email.length > 0);
            const webauthnMfa = mfaMethods.find(m => m.type === 'webauthn' && m.user_mfa_webauthn);
            const availableMethods = [];
            if (totpMfa) {
                availableMethods.push('totp');
            }
            if (emailMfas.length > 0) {
                const emails = emailMfas.map(mfa => mfa.user_mfa_email[0].email);
                await this.challenge.sendMfaCodeToMultipleEmails(locale, userId, emails);
                availableMethods.push('email');
            }
            if (availableMethods.length === 0 && !webauthnMfa) {
                return { requiresVerification: false };
            }
            if (availableMethods.length === 0 && webauthnMfa) {
                return {
                    requiresVerification: true,
                    availableMethods: [],
                    hasWebAuthn: true,
                    hasRecoveryCodes: true,
                };
            }
            if (availableMethods.length === 2) {
                return {
                    requiresVerification: true,
                    availableMethods,
                    hasWebAuthn: !!webauthnMfa,
                    hasRecoveryCodes: true,
                };
            }
            return {
                requiresVerification: true,
                verificationType: availableMethods[0],
                hasWebAuthn: !!webauthnMfa,
                hasRecoveryCodes: true,
            };
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to send verification: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async regenerateRecoveryCodes(locale, userId, data) {
        try {
            const user = await this.prisma.user.findUnique({
                where: { id: userId },
                include: {
                    user_mfa: {
                        where: { verified_at: { not: null } },
                        include: {
                            user_mfa_totp: true,
                            user_mfa_email: true,
                            user_mfa_webauthn: true
                        }
                    },
                    user_recovery_code: true
                }
            });
            if (!user) {
                throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
            }
            if (user.user_mfa.length > 0) {
                if (data.verificationType !== 'webauthn' && !data.verificationCode) {
                    throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('verificationCodeRequired', locale, 'Verification code is required.'));
                }
                if (data.verificationType === 'webauthn') {
                    if (!data.assertionResponse) {
                        throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('assertionResponseRequired', locale, 'Assertion response is required for WebAuthn.'));
                    }
                    try {
                        await this.verifyWebAuthnAuthentication(userId, data.assertionResponse);
                    }
                    catch (error) {
                        throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidWebAuthn', locale, 'WebAuthn verification failed.'));
                    }
                }
                else if (data.verificationType === 'recovery') {
                    if (!data.verificationCode) {
                        throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('verificationCodeRequired', locale, 'Verification code is required.'));
                    }
                    let isValid = false;
                    for (const recoveryCode of user.user_recovery_code) {
                        if (await this.security.verifyArgon2(data.verificationCode, recoveryCode.hash)) {
                            isValid = true;
                            await this.prisma.user_recovery_code.delete({
                                where: { id: recoveryCode.id }
                            });
                            break;
                        }
                    }
                    if (!isValid) {
                        throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidRecoveryCode', locale, 'Invalid recovery code.'));
                    }
                }
                else if (data.verificationType === 'email') {
                    const emailMfa = user.user_mfa.find(m => m.type === 'email' && m.user_mfa_email);
                    if (!emailMfa) {
                        throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('emailMfaNotFound', locale, 'Email MFA not found.'));
                    }
                    if (!data.verificationCode) {
                        throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('verificationCodeRequired', locale, 'Verification code is required.'));
                    }
                    try {
                        const isValid = await this.challenge.verifyMfaEmailCode(locale, userId, data.verificationCode);
                        if (!isValid) {
                            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidVerificationCode', locale, 'Invalid verification code.'));
                        }
                    }
                    catch (error) {
                        throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidVerificationCode', locale, 'Invalid verification code.'));
                    }
                }
                else {
                    const totpMfa = user.user_mfa.find(m => m.type === 'totp' && m.user_mfa_totp);
                    if (!totpMfa || !totpMfa.user_mfa_totp[0]) {
                        throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('totpMfaNotFound', locale, 'TOTP MFA not found.'));
                    }
                    const secretBuffer = totpMfa.user_mfa_totp[0].secret;
                    const secretBase32 = secretBuffer instanceof Uint8Array
                        ? Buffer.from(secretBuffer).toString('utf-8')
                        : String(secretBuffer);
                    const encryptedSecret = this.security.encrypt(secretBase32);
                    const encodedSecret = Buffer.from(encryptedSecret, 'utf-8').toString('base64');
                    try {
                        await this.verifyMfaTotp(locale, userId, data.verificationCode, encodedSecret);
                    }
                    catch (error) {
                        throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('invalidVerificationCode', locale, 'Invalid verification code.'));
                    }
                }
            }
            const codes = await this.createMfaRecoveryCodes(userId);
            return { codes };
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to regenerate recovery codes: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async generateWebAuthnRegistrationOptions(locale, userId, name) {
        var _a;
        try {
            const user = await this.prisma.user.findFirst({
                where: { id: userId },
                include: {
                    user_identifier: true,
                    user_mfa: {
                        where: { type: 'webauthn', verified_at: { not: null } },
                        include: { user_mfa_webauthn: true }
                    }
                }
            });
            if (!user) {
                throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
            }
            const settings = await this.setting.getSettingValues([
                'mfa-issuer',
                'mfa-window',
                'mfa-setp',
                'system-name',
                'google_client_id',
                'google_client_secret',
                'google_scopes',
                'url',
                'mfa-challenge-expiration-minutes',
                'access-token-expiration-minutes'
            ]);
            const rpName = settings['system-name'] || 'HedHog';
            const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
            const userEmail = ((_a = user.user_identifier.find(ui => ui.type === 'email')) === null || _a === void 0 ? void 0 : _a.value) || `user${userId}@example.com`;
            const existingAuthenticators = user.user_mfa
                .filter(mfa => mfa.user_mfa_webauthn && mfa.user_mfa_webauthn.length > 0)
                .map(mfa => ({
                credentialID: mfa.user_mfa_webauthn[0].credential_id,
                credentialPublicKey: mfa.user_mfa_webauthn[0].public_key,
                counter: mfa.user_mfa_webauthn[0].sign_count,
            }));
            const options = await (0, server_1.generateRegistrationOptions)({
                rpName,
                rpID,
                userName: userEmail,
                userDisplayName: user.name || userEmail,
                attestationType: 'none',
                excludeCredentials: existingAuthenticators.map(auth => ({
                    id: Buffer.from(auth.credentialID).toString('base64url'),
                })),
                authenticatorSelection: {
                    residentKey: 'preferred',
                    userVerification: 'preferred',
                },
            });
            await this.prisma.user_mfa_challenge.create({
                data: {
                    user_mfa: {
                        create: {
                            user_id: userId,
                            type: 'webauthn',
                            name: name || 'Security Key',
                        }
                    },
                    hash: options.challenge,
                    attempts: 0,
                    expires_at: new Date(Date.now() + 5 * 60 * 1000),
                }
            });
            return options;
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to generate registration options: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async verifyWebAuthnRegistration(locale, userId, name, attestationResponse) {
        try {
            const challengeRecord = await this.prisma.user_mfa_challenge.findFirst({
                where: {
                    user_mfa: {
                        user_id: userId,
                        type: 'webauthn',
                        verified_at: null,
                    },
                    expires_at: { gt: new Date() }
                },
                include: { user_mfa: true },
                orderBy: { created_at: 'desc' }
            });
            if (!challengeRecord) {
                throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('challengeNotFound', locale, 'Challenge not found or expired.'));
            }
            const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
            const expectedOrigin = process.env.FRONTEND_URL || 'http://localhost:3200';
            const verification = await (0, server_1.verifyRegistrationResponse)({
                response: attestationResponse,
                expectedChallenge: challengeRecord.hash,
                expectedOrigin,
                expectedRPID: rpID,
            });
            if (!verification.verified || !verification.registrationInfo) {
                throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('verificationFailed', locale, 'Verification failed.'));
            }
            const { credential } = verification.registrationInfo;
            await this.prisma.user_mfa.update({
                where: { id: challengeRecord.user_mfa_id },
                data: {
                    name,
                    verified_at: new Date(),
                    user_mfa_webauthn: {
                        create: {
                            credential_id: Buffer.from(credential.id, 'base64url'),
                            public_key: credential.publicKey,
                            sign_count: credential.counter,
                        }
                    }
                }
            });
            await this.prisma.user_mfa_challenge.update({
                where: { id: challengeRecord.id },
                data: { verified_at: new Date() }
            });
            const codes = await this.createMfaRecoveryCodes(userId);
            return { success: true, codes };
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to verify registration: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async generateWebAuthnAuthenticationOptions(locale, userId) {
        try {
            const user = await this.prisma.user.findFirst({
                where: { id: userId },
                include: {
                    user_mfa: {
                        where: { type: 'webauthn', verified_at: { not: null } },
                        include: { user_mfa_webauthn: true }
                    }
                }
            });
            if (!user || user.user_mfa.length === 0) {
                throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('webauthnNotConfigured', locale, 'WebAuthn not configured.'));
            }
            const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
            const allowCredentials = user.user_mfa
                .filter(mfa => mfa.user_mfa_webauthn && mfa.user_mfa_webauthn.length > 0)
                .map(mfa => ({
                id: Buffer.from(mfa.user_mfa_webauthn[0].credential_id).toString('base64url'),
            }));
            const options = await (0, server_1.generateAuthenticationOptions)({
                rpID,
                allowCredentials,
                userVerification: 'preferred',
            });
            await this.prisma.user_mfa_challenge.create({
                data: {
                    user_mfa_id: user.user_mfa[0].id,
                    hash: options.challenge,
                    attempts: 0,
                    expires_at: new Date(Date.now() + 5 * 60 * 1000),
                }
            });
            return options;
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to generate authentication options: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async verifyWebAuthnAuthentication(userId, assertionResponse) {
        try {
            const challengeRecord = await this.prisma.user_mfa_challenge.findFirst({
                where: {
                    user_mfa: {
                        user_id: userId,
                        type: 'webauthn',
                        verified_at: { not: null },
                    },
                    verified_at: null,
                    expires_at: { gt: new Date() }
                },
                include: {
                    user_mfa: {
                        include: { user_mfa_webauthn: true }
                    }
                },
                orderBy: { created_at: 'desc' }
            });
            if (!challengeRecord || !challengeRecord.user_mfa.user_mfa_webauthn[0]) {
                throw new common_1.BadRequestException('Challenge not found or expired.');
            }
            const authenticator = challengeRecord.user_mfa.user_mfa_webauthn[0];
            const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
            const expectedOrigin = process.env.FRONTEND_URL || 'http://localhost:3200';
            const verification = await (0, server_1.verifyAuthenticationResponse)({
                response: assertionResponse,
                expectedChallenge: challengeRecord.hash,
                expectedOrigin,
                expectedRPID: rpID,
                credential: {
                    id: Buffer.from(authenticator.credential_id).toString('base64url'),
                    publicKey: authenticator.public_key,
                    counter: authenticator.sign_count,
                },
            });
            if (!verification.verified) {
                throw new common_1.BadRequestException('Verification failed.');
            }
            await this.prisma.user_mfa_webauthn.update({
                where: { id: authenticator.id },
                data: {
                    sign_count: verification.authenticationInfo.newCounter,
                    last_used_at: new Date(),
                }
            });
            await this.prisma.user_mfa_challenge.update({
                where: { id: challengeRecord.id },
                data: { verified_at: new Date() }
            });
            return { success: true };
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to verify authentication: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async removeWebAuthnMfa(locale, userId, mfaId) {
        try {
            const mfa = await this.prisma.user_mfa.findFirst({
                where: {
                    id: mfaId,
                    user_id: userId,
                    type: 'webauthn',
                },
            });
            if (!mfa) {
                throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('mfaNotFound', locale, 'MFA method not found.'));
            }
            await this.prisma.user_mfa.delete({
                where: { id: mfaId },
            });
            return { success: true };
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to remove WebAuthn: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async generateWebAuthnAuthenticationOptionsForLogin(locale, mfaToken) {
        try {
            const decoded = this.jwt.decode(mfaToken);
            if (!decoded || !decoded.userId) {
                throw new common_1.BadRequestException('Invalid MFA token');
            }
            const user = await this.prisma.user.findFirst({
                where: { id: decoded.userId },
                include: {
                    user_mfa: {
                        where: { type: 'webauthn', verified_at: { not: null } },
                        include: { user_mfa_webauthn: true }
                    }
                }
            });
            if (!user || user.user_mfa.length === 0) {
                throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('webauthnNotConfigured', locale, 'WebAuthn not configured.'));
            }
            const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
            const allowCredentials = user.user_mfa
                .filter(mfa => mfa.user_mfa_webauthn && mfa.user_mfa_webauthn.length > 0)
                .map(mfa => ({
                id: Buffer.from(mfa.user_mfa_webauthn[0].credential_id).toString('base64url'),
            }));
            const options = await (0, server_1.generateAuthenticationOptions)({
                rpID,
                allowCredentials,
                userVerification: 'preferred',
            });
            await this.prisma.user_mfa_challenge.create({
                data: {
                    user_mfa_id: user.user_mfa[0].id,
                    hash: options.challenge,
                    attempts: 0,
                    expires_at: new Date(Date.now() + 5 * 60 * 1000),
                }
            });
            return options;
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to generate authentication options: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async verifyWebAuthnAuthenticationForLogin(locale, mfaToken, assertionResponse) {
        try {
            const decoded = this.jwt.decode(mfaToken);
            if (!decoded || !decoded.userId) {
                throw new common_1.BadRequestException('Invalid MFA token');
            }
            const userId = decoded.userId;
            const challengeRecord = await this.prisma.user_mfa_challenge.findFirst({
                where: {
                    user_mfa: {
                        user_id: userId,
                        type: 'webauthn',
                        verified_at: { not: null },
                    },
                    verified_at: null,
                    expires_at: { gt: new Date() }
                },
                include: {
                    user_mfa: {
                        include: { user_mfa_webauthn: true }
                    }
                },
                orderBy: { created_at: 'desc' }
            });
            if (!challengeRecord || !challengeRecord.user_mfa.user_mfa_webauthn[0]) {
                throw new common_1.BadRequestException('Challenge not found or expired.');
            }
            const authenticator = challengeRecord.user_mfa.user_mfa_webauthn[0];
            const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
            const expectedOrigin = process.env.FRONTEND_URL || 'http://localhost:3200';
            const verification = await (0, server_1.verifyAuthenticationResponse)({
                response: assertionResponse,
                expectedChallenge: challengeRecord.hash,
                expectedOrigin,
                expectedRPID: rpID,
                credential: {
                    id: Buffer.from(authenticator.credential_id).toString('base64url'),
                    publicKey: authenticator.public_key,
                    counter: authenticator.sign_count,
                },
            });
            if (!verification.verified) {
                throw new common_1.BadRequestException('Verification failed.');
            }
            await this.prisma.user_mfa_webauthn.update({
                where: { id: authenticator.id },
                data: {
                    sign_count: verification.authenticationInfo.newCounter,
                    last_used_at: new Date(),
                }
            });
            await this.prisma.user_mfa_challenge.update({
                where: { id: challengeRecord.id },
                data: { verified_at: new Date() }
            });
            const user = await this.prisma.user.findFirst({ where: { id: userId } });
            if (!user) {
                throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found'));
            }
            const payload = { sub: userId };
            const accessToken = await this.token.createAccessToken(payload);
            const refreshToken = this.jwt.sign({ userId, type: 'refresh' }, { expiresIn: '7d' });
            return {
                accessToken,
                refreshToken,
            };
        }
        catch (error) {
            throw new common_1.BadRequestException(`Failed to verify authentication: ${error === null || error === void 0 ? void 0 : error.message}`);
        }
    }
    async getUserToken(userId) {
        const updatedUser = await this.prisma.user.findFirst({ where: { id: userId } });
        return this.getToken(updatedUser);
    }
};
exports.ProfileService = ProfileService;
exports.ProfileService = ProfileService = __decorate([
    (0, common_1.Injectable)(),
    __param(2, (0, common_1.Inject)((0, common_1.forwardRef)(() => jwt_1.JwtService))),
    __param(3, (0, common_1.Inject)((0, common_1.forwardRef)(() => setting_service_1.SettingService))),
    __param(6, (0, common_1.Inject)((0, common_1.forwardRef)(() => token_service_1.TokenService))),
    __param(7, (0, common_1.Inject)((0, common_1.forwardRef)(() => mail_service_1.MailService))),
    __metadata("design:paramtypes", [api_prisma_1.PrismaService,
        user_service_1.UserService,
        jwt_1.JwtService,
        setting_service_1.SettingService,
        security_service_1.SecurityService,
        challenge_service_1.ChallengeService,
        token_service_1.TokenService,
        mail_service_1.MailService])
], ProfileService);
//# sourceMappingURL=profile.service.js.map