279 lines
12 KiB
JavaScript
279 lines
12 KiB
JavaScript
"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
|