"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.ChallengeService = 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 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");
let ChallengeService = class ChallengeService {
    constructor(prisma, security, setting, token, mail) {
        this.prisma = prisma;
        this.security = security;
        this.setting = setting;
        this.token = token;
        this.mail = mail;
    }
    async verifyMfaEmail(locale, userId, email) {
        var _a;
        const settings = await this.setting.getSettingValues([
            'mfa-email-code-length',
            'mfa-challenge-expiration-minutes'
        ]);
        const user = await this.prisma.user.findUnique({
            where: { id: userId },
            select: { name: true }
        });
        if (!user) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
        }
        const code = Array.from(crypto.getRandomValues(new Uint8Array(settings['mfa-email-code-length'] || 6)))
            .map(n => (n % 10).toString())
            .join('');
        const codeHash = this.security.hashWithPepper(code);
        const userMfa = await this.prisma.user_mfa.create({
            data: {
                name: 'Email',
                type: 'email',
                user_id: userId,
                user_mfa_email: {
                    create: {
                        email,
                    }
                },
                user_mfa_challenge: {
                    create: {
                        expires_at: new Date(Date.now() + (settings['mfa-challenge-expiration-minutes'] || 15) * 60000),
                        hash: codeHash,
                    }
                }
            },
            select: {
                id: true,
                user_mfa_challenge: {
                    select: { id: true }
                }
            }
        });
        await this.mail.sendTemplatedMail(locale, {
            email,
            slug: 'auth-email-verification-code',
            variables: {
                code,
                name: user.name,
            }
        });
        const challengeId = (_a = userMfa.user_mfa_challenge.find(c => c !== null)) === null || _a === void 0 ? void 0 : _a.id;
        return { challengeId };
    }
    async verifyEmail(locale, userId, email) {
        const identifier = await this.prisma.user_identifier.findFirst({
            where: {
                user_id: userId,
                type: 'email',
                value: email,
                verified_at: null,
            },
            select: { id: true }
        });
        if (!identifier) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('identifierNotFound', locale, 'Email identifier not found or already verified.'));
        }
        const settings = await this.setting.getSettingValues([
            'mfa-email-code-length',
            'mfa-challenge-expiration-minutes'
        ]);
        const user = await this.prisma.user.findUnique({
            where: { id: userId },
            select: { name: true }
        });
        if (!user) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
        }
        const code = Array.from(crypto.getRandomValues(new Uint8Array(settings['mfa-email-code-length'] || 6)))
            .map(n => (n % 10).toString())
            .join('');
        const codeHash = this.security.hashWithPepper(code);
        const challenge = await this.prisma.user_identifier_challenge.create({
            data: {
                hash: codeHash,
                expires_at: new Date(Date.now() + (settings['mfa-challenge-expiration-minutes'] || 15) * 60000),
                user_identifier_id: identifier.id,
            },
            select: {
                id: true,
            }
        });
        await this.mail.sendTemplatedMail(locale, {
            email,
            slug: 'auth-sign-up-confirm-email',
            variables: {
                code,
                name: user.name,
            }
        });
        return { challengeId: challenge.id };
    }
    async verifyMultipleEmails(locale, userId, emails) {
        const settings = await this.setting.getSettingValues([
            'mfa-email-code-length',
        ]);
        const user = await this.prisma.user.findUnique({
            where: { id: userId },
            select: { name: true }
        });
        if (!user) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
        }
        const code = Array.from(crypto.getRandomValues(new Uint8Array(settings['mfa-email-code-length'] || 6)))
            .map(n => (n % 10).toString())
            .join('');
        const codeHash = this.security.hashWithPepper(code);
        for (const email of emails) {
            this.mail.sendTemplatedMail(locale, {
                email,
                slug: 'auth-sign-up-confirm-email',
                variables: {
                    code,
                    name: user.name,
                }
            });
        }
        return { codeHash };
    }
    async forgotEmail(locale, email) {
        const openChallengesCount = await this.prisma.user_mfa_challenge.count({
            where: {
                user_mfa: {
                    user_mfa_email: {
                        some: { email }
                    }
                },
                expires_at: { gt: new Date() }
            }
        });
        if (openChallengesCount >= 3) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('forgot.rateLimit', locale, 'Too many password reset requests. Please try again later.'));
        }
        const userMfaEmail = await this.prisma.user_mfa_email.findFirst({
            where: { email },
            include: {
                user_mfa: { include: { user: true } },
            },
        });
        if (!userMfaEmail)
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('forgot.userNotFound', locale, 'User not found'));
        const settings = await this.setting.getSettingValues([
            'mfa-challenge-expiration-minutes',
            'url'
        ]);
        const token = await this.token.createOpaqueToken(16);
        const hash = this.security.hashWithPepper(token);
        const challenge = await this.prisma.user_mfa_challenge.create({
            data: {
                hash,
                expires_at: new Date(Date.now() + (settings['mfa-challenge-expiration-minutes'] || 15) * 60000),
                user_mfa_id: userMfaEmail.user_mfa_id,
            },
        });
        const tokenBase64 = Buffer.from(token).toString('base64');
        this.mail.sendTemplatedMail(locale, {
            email,
            slug: 'auth-forget-password',
            variables: {
                link: `${settings['url']}/reset-password?token=${tokenBase64}`,
                name: userMfaEmail.user_mfa.user.name,
            }
        });
        return challenge;
    }
    async verifyCodePassword(locale, code) {
        const hash = this.security.hashWithPepper(code);
        const challenge = await this.prisma.user_mfa_challenge.findFirst({
            where: {
                hash,
                expires_at: { gt: new Date() }
            },
            include: {
                user_mfa: { include: { user: true } },
            },
        });
        if (!challenge) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('challenge.invalidOrExpired', locale, 'The provided code is invalid or has expired.'));
        }
        return challenge;
    }
    async verifyCode(locale, userId, code, providedHash, name, email) {
        const hash = this.security.hashWithPepper(code);
        if (providedHash && hash !== providedHash) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('challenge.invalidOrExpired', locale, 'The provided code is invalid or has expired.'));
        }
        if (name && email) {
            const challenge = await this.prisma.user_mfa.create({
                data: {
                    name,
                    type: 'email',
                    user_id: userId,
                    verified_at: new Date(),
                    user_mfa_challenge: {
                        create: {
                            hash: providedHash,
                            expires_at: new Date(),
                            verified_at: new Date(),
                            attempts: 1,
                        }
                    },
                    user_mfa_email: {
                        create: {
                            email: email,
                        },
                    }
                }
            });
            if (challenge.user_id !== userId) {
                throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('accessDenied', locale, 'Access denied.'));
            }
            return challenge;
        }
        return { success: true };
    }
    async sendMfaCodeByEmail(locale, userId, email) {
        const user = await this.prisma.user.findUnique({
            where: { id: userId },
            select: { name: true }
        });
        if (!user) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
        }
        const settings = await this.setting.getSettingValues([
            'mfa-challenge-expiration-minutes',
            'mfa-email-code-length'
        ]);
        const code = Array.from(crypto.getRandomValues(new Uint8Array(settings['mfa-email-code-length'] || 6)))
            .map(n => (n % 10).toString())
            .join('');
        const hash = this.security.hashWithPepper(code);
        const expiresAt = new Date(Date.now() + (settings['mfa-challenge-expiration-minutes'] || 15) * 60000);
        const existingUserMfa = await this.prisma.user_mfa.findFirst({
            where: { user_id: userId, type: 'email' },
            select: { id: true }
        });
        await this.prisma.user_mfa_challenge.create({
            data: {
                hash,
                expires_at: expiresAt,
                user_mfa: existingUserMfa
                    ? { connect: { id: existingUserMfa.id } }
                    : { create: { name: user.name, type: 'email', user: { connect: { id: userId } } } }
            }
        });
        this.mail.sendTemplatedMail(locale, {
            email,
            slug: 'auth-mfa-code',
            variables: {
                code,
                name: user.name,
            }
        });
        return { success: true };
    }
    async verifyMfaEmailCode(locale, userId, code) {
        const hash = this.security.hashWithPepper(code);
        const challenge = await this.prisma.user_mfa_challenge.findFirst({
            where: {
                hash,
                expires_at: { gt: new Date() },
                user_mfa: {
                    user_id: userId,
                    type: 'email'
                }
            }
        });
        if (!challenge) {
            return false;
        }
        await this.prisma.user_mfa_challenge.delete({
            where: { id: challenge.id }
        });
        return true;
    }
    async sendMfaCodeToMultipleEmails(locale, userId, emails) {
        if (!emails || emails.length === 0) {
            return { success: true };
        }
        const user = await this.prisma.user.findUnique({
            where: { id: userId },
            select: { name: true }
        });
        if (!user) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
        }
        const settings = await this.setting.getSettingValues([
            'mfa-challenge-expiration-minutes',
            'mfa-email-code-length',
        ]);
        const code = Array.from(crypto.getRandomValues(new Uint8Array(settings['mfa-email-code-length'] || 6)))
            .map(n => (n % 10).toString())
            .join('');
        const hash = this.security.hashWithPepper(code);
        const expiresAt = new Date(Date.now() + (settings['mfa-challenge-expiration-minutes'] || 15) * 60000);
        const existingUserMfa = await this.prisma.user_mfa.findFirst({
            where: { user_id: userId, type: 'email' },
            select: { id: true }
        });
        await this.prisma.user_mfa_challenge.create({
            data: {
                hash,
                expires_at: expiresAt,
                user_mfa: existingUserMfa
                    ? { connect: { id: existingUserMfa.id } }
                    : { create: { name: user.name, type: 'email', user: { connect: { id: userId } } } }
            }
        });
        for (const email of emails) {
            this.mail.sendTemplatedMail(locale, {
                email,
                slug: 'auth-mfa-code',
                variables: {
                    code,
                    name: user.name,
                }
            });
        }
        return { success: true };
    }
};
exports.ChallengeService = ChallengeService;
exports.ChallengeService = ChallengeService = __decorate([
    (0, common_1.Injectable)(),
    __param(1, (0, common_1.Inject)((0, common_1.forwardRef)(() => security_service_1.SecurityService))),
    __param(2, (0, common_1.Inject)((0, common_1.forwardRef)(() => setting_service_1.SettingService))),
    __param(3, (0, common_1.Inject)((0, common_1.forwardRef)(() => token_service_1.TokenService))),
    __param(4, (0, common_1.Inject)((0, common_1.forwardRef)(() => mail_service_1.MailService))),
    __metadata("design:paramtypes", [api_prisma_1.PrismaService,
        security_service_1.SecurityService,
        setting_service_1.SettingService,
        token_service_1.TokenService,
        mail_service_1.MailService])
], ChallengeService);
//# sourceMappingURL=challenge.service.js.map