"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.authRouter = void 0; const express_1 = require("express"); const zod_1 = require("zod"); const bcryptjs_1 = __importDefault(require("bcryptjs")); const client_1 = require("@prisma/client"); const auth_service_1 = require("./auth.service"); const auth_schemas_1 = require("./auth.schemas"); const validate_1 = require("../../middleware/validate"); const auth_middleware_1 = require("../../middleware/auth.middleware"); const rate_limit_1 = require("../../middleware/rate-limit"); const database_1 = require("../../config/database"); const verification_token_service_1 = require("../../services/verification-token.service"); const password_reset_token_service_1 = require("../../services/password-reset-token.service"); const email_service_1 = require("../../services/email.service"); const settings_service_1 = require("../settings/settings.service"); const env_1 = require("../../config/env"); const logger_1 = require("../../utils/logger"); const auth_rate_limits_1 = require("./auth.rate-limits"); const profile_service_1 = require("../people/profile.service"); const router = (0, express_1.Router)(); exports.authRouter = router; // POST /api/auth/login router.post('/login', rate_limit_1.authRateLimit, (0, validate_1.validate)(auth_schemas_1.loginSchema), async (req, res, next) => { try { const result = await auth_service_1.authService.login(req.body.email, req.body.password); res.json(result); } catch (err) { next(err); } }); // POST /api/auth/register router.post('/register', rate_limit_1.authRateLimit, (0, validate_1.validate)(auth_schemas_1.registerSchema), async (req, res, next) => { try { const result = await auth_service_1.authService.register(req.body); res.status(201).json(result); } catch (err) { next(err); } }); // POST /api/auth/verify-email const verifyEmailSchema = zod_1.z.object({ token: zod_1.z.string().min(1) }); router.post('/verify-email', rate_limit_1.authRateLimit, (0, validate_1.validate)(verifyEmailSchema), async (req, res, next) => { try { const { token } = req.body; const result = await verification_token_service_1.verificationTokenService.verifyToken(token); if (!result.valid || !result.userId) { res.status(400).json({ error: { message: result.error || 'Invalid token', code: 'INVALID_TOKEN' }, }); return; } const settings = await settings_service_1.siteSettingsService.get(); const autoApprove = settings.autoApproveVerifiedUsers; const newStatus = autoApprove ? client_1.UserStatus.ACTIVE : client_1.UserStatus.PENDING_APPROVAL; await database_1.prisma.user.update({ where: { id: result.userId }, data: { emailVerified: true, status: newStatus }, }); // If not auto-approved, notify admins if (!autoApprove) { const user = await database_1.prisma.user.findUnique({ where: { id: result.userId } }); if (user) { const admins = await database_1.prisma.user.findMany({ where: { role: client_1.UserRole.SUPER_ADMIN, status: client_1.UserStatus.ACTIVE }, select: { email: true }, }); if (admins.length > 0) { await email_service_1.emailService.sendPendingApprovalNotification({ adminEmails: admins.map(a => a.email), newUserEmail: user.email, newUserName: user.name || '', }).catch(err => logger_1.logger.error('Failed to send approval notification:', err)); } } } res.json({ verified: true, approved: autoApprove, message: autoApprove ? 'Email verified. You can now log in.' : 'Email verified. Your account is pending admin approval.', }); } catch (err) { next(err); } }); // POST /api/auth/resend-verification const resendVerificationSchema = zod_1.z.object({ email: zod_1.z.string().email() }); router.post('/resend-verification', (0, auth_rate_limits_1.createVerificationRateLimit)(), (0, validate_1.validate)(resendVerificationSchema), async (req, res, next) => { try { // Always return success to prevent user enumeration res.json({ message: 'If your email is registered and pending verification, a new verification link has been sent.' }); // Send asynchronously (don't block response) const user = await database_1.prisma.user.findUnique({ where: { email: req.body.email } }); if (user && user.status === client_1.UserStatus.PENDING_VERIFICATION) { const token = await verification_token_service_1.verificationTokenService.createToken(user.id); const adminUrl = env_1.env.ADMIN_URL || 'http://localhost:3000'; const verificationUrl = `${adminUrl}/verify-email?token=${token}`; await email_service_1.emailService.sendVerificationEmail({ recipientEmail: user.email, recipientName: user.name || 'there', verificationUrl, }).catch(err => logger_1.logger.error('Failed to resend verification email:', err)); } } catch (err) { next(err); } }); // POST /api/auth/forgot-password const forgotPasswordSchema = zod_1.z.object({ email: zod_1.z.string().email() }); router.post('/forgot-password', (0, auth_rate_limits_1.createResetRateLimit)(), (0, validate_1.validate)(forgotPasswordSchema), async (req, res, next) => { try { // Always return success to prevent user enumeration res.json({ message: 'If your email is registered, a password reset link has been sent.' }); // Send asynchronously const user = await database_1.prisma.user.findUnique({ where: { email: req.body.email } }); if (user && user.status === client_1.UserStatus.ACTIVE) { const token = await password_reset_token_service_1.passwordResetTokenService.createToken(user.id); const adminUrl = env_1.env.ADMIN_URL || 'http://localhost:3000'; const resetUrl = `${adminUrl}/reset-password?token=${token}`; await email_service_1.emailService.sendPasswordResetEmail({ recipientEmail: user.email, recipientName: user.name || 'there', resetUrl, }).catch(err => logger_1.logger.error('Failed to send password reset email:', err)); } } catch (err) { next(err); } }); // POST /api/auth/reset-password const resetPasswordSchema = zod_1.z.object({ token: zod_1.z.string().min(1), password: zod_1.z.string() .min(12, 'Password must be at least 12 characters') .regex(/[A-Z]/, 'Password must contain at least one uppercase letter') .regex(/[a-z]/, 'Password must contain at least one lowercase letter') .regex(/[0-9]/, 'Password must contain at least one digit'), }); router.post('/reset-password', rate_limit_1.authRateLimit, (0, validate_1.validate)(resetPasswordSchema), async (req, res, next) => { try { const { token, password } = req.body; const result = await password_reset_token_service_1.passwordResetTokenService.validateToken(token); if (!result.valid || !result.userId) { res.status(400).json({ error: { message: result.error || 'Invalid token', code: 'INVALID_TOKEN' }, }); return; } const hashedPassword = await bcryptjs_1.default.hash(password, 12); // Update password, mark token used, invalidate all refresh tokens — all in one transaction await database_1.prisma.$transaction(async (tx) => { await tx.user.update({ where: { id: result.userId }, data: { password: hashedPassword }, }); await tx.refreshToken.deleteMany({ where: { userId: result.userId } }); await tx.passwordResetToken.update({ where: { token }, data: { usedAt: new Date() }, }); }); res.json({ message: 'Password has been reset. You can now log in with your new password.' }); } catch (err) { next(err); } }); // POST /api/auth/refresh router.post('/refresh', rate_limit_1.authRateLimit, (0, validate_1.validate)(auth_schemas_1.refreshSchema), async (req, res, next) => { try { const result = await auth_service_1.authService.refreshTokens(req.body.refreshToken); res.json(result); } catch (err) { next(err); } }); // POST /api/auth/logout router.post('/logout', rate_limit_1.authRateLimit, (0, validate_1.validate)(auth_schemas_1.refreshSchema), async (req, res, next) => { try { await auth_service_1.authService.logout(req.body.refreshToken); res.json({ message: 'Logged out' }); } catch (err) { next(err); } }); // GET /api/auth/me/profile-token router.get('/me/profile-token', auth_middleware_1.authenticate, async (req, res, next) => { try { const userId = req.user.id; // Look up existing Contact linked to this user let contact = await database_1.prisma.contact.findUnique({ where: { userId } }); // Auto-create Contact if none exists if (!contact) { const user = await database_1.prisma.user.findUnique({ where: { id: userId }, select: { name: true, email: true }, }); if (!user) { res.status(401).json({ error: { message: 'Invalid token', code: 'INVALID_TOKEN' } }); return; } try { contact = await database_1.prisma.contact.create({ data: { displayName: user.name || user.email, firstName: user.name?.split(' ')[0] || null, lastName: user.name?.split(' ').slice(1).join(' ') || null, email: user.email, userId, primarySource: 'USER', }, }); } catch (err) { // Race condition: another request created the Contact — retry lookup if (err.code === 'P2002') { contact = await database_1.prisma.contact.findUnique({ where: { userId } }); } if (!contact) throw err; } } // Generate profile token if Contact doesn't have one if (!contact.profileToken) { const result = await profile_service_1.profileService.generateProfileToken(contact.id); res.json({ token: result.token }); return; } res.json({ token: contact.profileToken }); } catch (err) { next(err); } }); // GET /api/auth/me router.get('/me', auth_middleware_1.authenticate, async (req, res, next) => { try { const user = await database_1.prisma.user.findUnique({ where: { id: req.user.id }, select: { id: true, email: true, name: true, phone: true, role: true, roles: true, status: true, permissions: true, createdVia: true, emailVerified: true, lastLoginAt: true, createdAt: true, updatedAt: true, }, }); if (!user) { res.status(401).json({ error: { message: 'Invalid token', code: 'INVALID_TOKEN' } }); return; } res.json(user); } catch (err) { next(err); } }); //# sourceMappingURL=auth.routes.js.map