MFA
This commit is contained in:
627
backend/routes/twoFactor.js
Normal file
627
backend/routes/twoFactor.js
Normal file
@@ -0,0 +1,627 @@
|
||||
const express = require("express");
|
||||
const { User } = require("../models");
|
||||
const TwoFactorService = require("../services/TwoFactorService");
|
||||
const emailServices = require("../services/email");
|
||||
const logger = require("../utils/logger");
|
||||
const { authenticateToken } = require("../middleware/auth");
|
||||
const { requireStepUpAuth } = require("../middleware/stepUpAuth");
|
||||
const { csrfProtection } = require("../middleware/csrf");
|
||||
const {
|
||||
sanitizeInput,
|
||||
validateTotpCode,
|
||||
validateEmailOtp,
|
||||
validateRecoveryCode,
|
||||
} = require("../middleware/validation");
|
||||
const {
|
||||
twoFactorVerificationLimiter,
|
||||
twoFactorSetupLimiter,
|
||||
recoveryCodeLimiter,
|
||||
emailOtpSendLimiter,
|
||||
} = require("../middleware/rateLimiter");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Helper for structured security audit logging
|
||||
const auditLog = (req, action, userId, details = {}) => {
|
||||
logger.info({
|
||||
type: 'security_audit',
|
||||
action,
|
||||
userId,
|
||||
ip: req.ip,
|
||||
userAgent: req.get('User-Agent'),
|
||||
...details,
|
||||
});
|
||||
};
|
||||
|
||||
// All routes require authentication
|
||||
router.use(authenticateToken);
|
||||
|
||||
// ============================================
|
||||
// SETUP ENDPOINTS
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* POST /api/2fa/setup/totp/init
|
||||
* Initialize TOTP setup - generate secret and QR code
|
||||
*/
|
||||
router.post(
|
||||
"/setup/totp/init",
|
||||
twoFactorSetupLimiter,
|
||||
csrfProtection,
|
||||
async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
if (user.twoFactorEnabled) {
|
||||
return res.status(400).json({
|
||||
error: "Multi-factor authentication is already enabled",
|
||||
});
|
||||
}
|
||||
|
||||
// Generate TOTP secret and QR code
|
||||
const { qrCodeDataUrl, encryptedSecret, encryptedSecretIv } =
|
||||
await TwoFactorService.generateTotpSecret(user.email);
|
||||
|
||||
// Store pending secret for verification
|
||||
await user.storePendingTotpSecret(encryptedSecret, encryptedSecretIv);
|
||||
|
||||
auditLog(req, '2fa.setup.initiated', user.id, { method: 'totp' });
|
||||
|
||||
res.json({
|
||||
qrCodeDataUrl,
|
||||
message: "Scan the QR code with your authenticator app",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("TOTP setup init error:", error);
|
||||
res.status(500).json({ error: "Failed to initialize TOTP setup" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* POST /api/2fa/setup/totp/verify
|
||||
* Verify TOTP code and enable 2FA
|
||||
*/
|
||||
router.post(
|
||||
"/setup/totp/verify",
|
||||
twoFactorSetupLimiter,
|
||||
csrfProtection,
|
||||
sanitizeInput,
|
||||
validateTotpCode,
|
||||
async (req, res) => {
|
||||
try {
|
||||
const { code } = req.body;
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
if (user.twoFactorEnabled) {
|
||||
return res.status(400).json({
|
||||
error: "Multi-factor authentication is already enabled",
|
||||
});
|
||||
}
|
||||
|
||||
if (!user.twoFactorSetupPendingSecret) {
|
||||
return res.status(400).json({
|
||||
error: "No pending TOTP setup. Please start the setup process again.",
|
||||
});
|
||||
}
|
||||
|
||||
// Verify the code against the pending secret
|
||||
const isValid = user.verifyPendingTotpCode(code);
|
||||
|
||||
if (!isValid) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid verification code. Please try again.",
|
||||
});
|
||||
}
|
||||
|
||||
// Generate recovery codes
|
||||
const { codes: recoveryCodes } =
|
||||
await TwoFactorService.generateRecoveryCodes();
|
||||
|
||||
// Enable TOTP
|
||||
await user.enableTotp(recoveryCodes);
|
||||
|
||||
// Send confirmation email
|
||||
try {
|
||||
await emailServices.auth.sendTwoFactorEnabledEmail(user);
|
||||
} catch (emailError) {
|
||||
logger.error("Failed to send 2FA enabled email:", emailError);
|
||||
// Don't fail the request if email fails
|
||||
}
|
||||
|
||||
auditLog(req, '2fa.setup.completed', user.id, { method: 'totp' });
|
||||
|
||||
res.json({
|
||||
message: "Multi-factor authentication enabled successfully",
|
||||
recoveryCodes,
|
||||
warning:
|
||||
"Save these recovery codes in a secure location. You will not be able to see them again.",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("TOTP setup verify error:", error);
|
||||
res.status(500).json({ error: "Failed to enable multi-factor authentication" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* POST /api/2fa/setup/email/init
|
||||
* Initialize email 2FA setup - send verification code
|
||||
*/
|
||||
router.post(
|
||||
"/setup/email/init",
|
||||
twoFactorSetupLimiter,
|
||||
emailOtpSendLimiter,
|
||||
csrfProtection,
|
||||
async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
if (user.twoFactorEnabled) {
|
||||
return res.status(400).json({
|
||||
error: "Multi-factor authentication is already enabled",
|
||||
});
|
||||
}
|
||||
|
||||
// Generate and send email OTP
|
||||
const otpCode = await user.generateEmailOtp();
|
||||
|
||||
try {
|
||||
await emailServices.auth.sendTwoFactorOtpEmail(user, otpCode);
|
||||
} catch (emailError) {
|
||||
logger.error("Failed to send 2FA setup OTP email:", emailError);
|
||||
return res.status(500).json({ error: "Failed to send verification email" });
|
||||
}
|
||||
|
||||
auditLog(req, '2fa.setup.initiated', user.id, { method: 'email' });
|
||||
|
||||
res.json({
|
||||
message: "Verification code sent to your email",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Email 2FA setup init error:", error);
|
||||
res.status(500).json({ error: "Failed to initialize email 2FA setup" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* POST /api/2fa/setup/email/verify
|
||||
* Verify email OTP and enable email 2FA
|
||||
*/
|
||||
router.post(
|
||||
"/setup/email/verify",
|
||||
twoFactorSetupLimiter,
|
||||
csrfProtection,
|
||||
sanitizeInput,
|
||||
validateEmailOtp,
|
||||
async (req, res) => {
|
||||
try {
|
||||
const { code } = req.body;
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
if (user.twoFactorEnabled) {
|
||||
return res.status(400).json({
|
||||
error: "Multi-factor authentication is already enabled",
|
||||
});
|
||||
}
|
||||
|
||||
if (user.isEmailOtpLocked()) {
|
||||
return res.status(429).json({
|
||||
error: "Too many failed attempts. Please request a new code.",
|
||||
});
|
||||
}
|
||||
|
||||
// Verify the OTP
|
||||
const isValid = user.verifyEmailOtp(code);
|
||||
|
||||
if (!isValid) {
|
||||
await user.incrementEmailOtpAttempts();
|
||||
return res.status(400).json({
|
||||
error: "Invalid or expired verification code",
|
||||
});
|
||||
}
|
||||
|
||||
// Generate recovery codes
|
||||
const { codes: recoveryCodes } =
|
||||
await TwoFactorService.generateRecoveryCodes();
|
||||
|
||||
// Enable email 2FA
|
||||
await user.enableEmailTwoFactor(recoveryCodes);
|
||||
await user.clearEmailOtp();
|
||||
|
||||
// Send confirmation email
|
||||
try {
|
||||
await emailServices.auth.sendTwoFactorEnabledEmail(user);
|
||||
} catch (emailError) {
|
||||
logger.error("Failed to send 2FA enabled email:", emailError);
|
||||
}
|
||||
|
||||
auditLog(req, '2fa.setup.completed', user.id, { method: 'email' });
|
||||
|
||||
res.json({
|
||||
message: "Multi-factor authentication enabled successfully",
|
||||
recoveryCodes,
|
||||
warning:
|
||||
"Save these recovery codes in a secure location. You will not be able to see them again.",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Email 2FA setup verify error:", error);
|
||||
res.status(500).json({ error: "Failed to enable multi-factor authentication" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// VERIFICATION ENDPOINTS (Step-up auth)
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* POST /api/2fa/verify/totp
|
||||
* Verify TOTP code for step-up authentication
|
||||
*/
|
||||
router.post(
|
||||
"/verify/totp",
|
||||
twoFactorVerificationLimiter,
|
||||
csrfProtection,
|
||||
sanitizeInput,
|
||||
validateTotpCode,
|
||||
async (req, res) => {
|
||||
try {
|
||||
const { code } = req.body;
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
if (!user.twoFactorEnabled || user.twoFactorMethod !== "totp") {
|
||||
logger.warn(`2FA verify failed for user ${user.id}: TOTP not enabled or wrong method`);
|
||||
return res.status(400).json({
|
||||
error: "Verification failed",
|
||||
});
|
||||
}
|
||||
|
||||
const isValid = user.verifyTotpCode(code);
|
||||
|
||||
if (!isValid) {
|
||||
auditLog(req, '2fa.verify.failed', user.id, { method: 'totp' });
|
||||
return res.status(400).json({
|
||||
error: "Invalid verification code",
|
||||
});
|
||||
}
|
||||
|
||||
// Mark code as used for replay protection
|
||||
await user.markTotpCodeUsed(code);
|
||||
|
||||
// Update step-up session
|
||||
await user.updateStepUpSession();
|
||||
|
||||
auditLog(req, '2fa.verify.success', user.id, { method: 'totp' });
|
||||
|
||||
res.json({
|
||||
message: "Verification successful",
|
||||
verified: true,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("TOTP verification error:", error);
|
||||
res.status(500).json({ error: "Verification failed" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* POST /api/2fa/verify/email/send
|
||||
* Send email OTP for step-up authentication
|
||||
*/
|
||||
router.post(
|
||||
"/verify/email/send",
|
||||
emailOtpSendLimiter,
|
||||
csrfProtection,
|
||||
async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
if (!user.twoFactorEnabled) {
|
||||
logger.warn(`2FA verify failed for user ${user.id}: 2FA not enabled`);
|
||||
return res.status(400).json({
|
||||
error: "Verification failed",
|
||||
});
|
||||
}
|
||||
|
||||
// Generate and send email OTP
|
||||
const otpCode = await user.generateEmailOtp();
|
||||
|
||||
try {
|
||||
await emailServices.auth.sendTwoFactorOtpEmail(user, otpCode);
|
||||
} catch (emailError) {
|
||||
logger.error("Failed to send 2FA OTP email:", emailError);
|
||||
return res.status(500).json({ error: "Failed to send verification email" });
|
||||
}
|
||||
|
||||
auditLog(req, '2fa.otp.sent', user.id, { method: 'email' });
|
||||
|
||||
res.json({
|
||||
message: "Verification code sent to your email",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Email OTP send error:", error);
|
||||
res.status(500).json({ error: "Failed to send verification code" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* POST /api/2fa/verify/email
|
||||
* Verify email OTP for step-up authentication
|
||||
*/
|
||||
router.post(
|
||||
"/verify/email",
|
||||
twoFactorVerificationLimiter,
|
||||
csrfProtection,
|
||||
sanitizeInput,
|
||||
validateEmailOtp,
|
||||
async (req, res) => {
|
||||
try {
|
||||
const { code } = req.body;
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
if (!user.twoFactorEnabled) {
|
||||
logger.warn(`2FA verify failed for user ${user.id}: 2FA not enabled`);
|
||||
return res.status(400).json({
|
||||
error: "Verification failed",
|
||||
});
|
||||
}
|
||||
|
||||
if (user.isEmailOtpLocked()) {
|
||||
return res.status(429).json({
|
||||
error: "Too many failed attempts. Please request a new code.",
|
||||
});
|
||||
}
|
||||
|
||||
const isValid = user.verifyEmailOtp(code);
|
||||
|
||||
if (!isValid) {
|
||||
await user.incrementEmailOtpAttempts();
|
||||
auditLog(req, '2fa.verify.failed', user.id, { method: 'email' });
|
||||
return res.status(400).json({
|
||||
error: "Invalid or expired verification code",
|
||||
});
|
||||
}
|
||||
|
||||
// Update step-up session and clear OTP
|
||||
await user.updateStepUpSession();
|
||||
await user.clearEmailOtp();
|
||||
|
||||
auditLog(req, '2fa.verify.success', user.id, { method: 'email' });
|
||||
|
||||
res.json({
|
||||
message: "Verification successful",
|
||||
verified: true,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Email OTP verification error:", error);
|
||||
res.status(500).json({ error: "Verification failed" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* POST /api/2fa/verify/recovery
|
||||
* Use recovery code for step-up authentication
|
||||
*/
|
||||
router.post(
|
||||
"/verify/recovery",
|
||||
recoveryCodeLimiter,
|
||||
csrfProtection,
|
||||
sanitizeInput,
|
||||
validateRecoveryCode,
|
||||
async (req, res) => {
|
||||
try {
|
||||
const { code } = req.body;
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
if (!user.twoFactorEnabled) {
|
||||
logger.warn(`2FA verify failed for user ${user.id}: 2FA not enabled`);
|
||||
return res.status(400).json({
|
||||
error: "Verification failed",
|
||||
});
|
||||
}
|
||||
|
||||
const { valid, remainingCodes } = await user.useRecoveryCode(code);
|
||||
|
||||
if (!valid) {
|
||||
auditLog(req, '2fa.verify.failed', user.id, { method: 'recovery' });
|
||||
return res.status(400).json({
|
||||
error: "Invalid recovery code",
|
||||
});
|
||||
}
|
||||
|
||||
// Send alert email about recovery code usage
|
||||
try {
|
||||
await emailServices.auth.sendRecoveryCodeUsedEmail(user, remainingCodes);
|
||||
} catch (emailError) {
|
||||
logger.error("Failed to send recovery code used email:", emailError);
|
||||
}
|
||||
|
||||
auditLog(req, '2fa.recovery.used', user.id, { lowCodes: remainingCodes <= 2 });
|
||||
|
||||
res.json({
|
||||
message: "Verification successful",
|
||||
verified: true,
|
||||
remainingCodes,
|
||||
warning:
|
||||
remainingCodes <= 2
|
||||
? "You are running low on recovery codes. Please generate new ones."
|
||||
: null,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Recovery code verification error:", error);
|
||||
res.status(500).json({ error: "Verification failed" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// MANAGEMENT ENDPOINTS
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* GET /api/2fa/status
|
||||
* Get current 2FA status for the user
|
||||
*/
|
||||
router.get("/status", async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
res.json({
|
||||
enabled: user.twoFactorEnabled,
|
||||
method: user.twoFactorMethod,
|
||||
hasRecoveryCodes: user.getRemainingRecoveryCodes() > 0,
|
||||
lowRecoveryCodes: user.getRemainingRecoveryCodes() <= 2,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("2FA status error:", error);
|
||||
res.status(500).json({ error: "Failed to get 2FA status" });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/2fa/disable
|
||||
* Disable 2FA (requires step-up authentication)
|
||||
*/
|
||||
router.post(
|
||||
"/disable",
|
||||
csrfProtection,
|
||||
requireStepUpAuth("2fa_disable"),
|
||||
async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
if (!user.twoFactorEnabled) {
|
||||
logger.warn(`2FA disable failed for user ${user.id}: 2FA not enabled`);
|
||||
return res.status(400).json({
|
||||
error: "Operation failed",
|
||||
});
|
||||
}
|
||||
|
||||
await user.disableTwoFactor();
|
||||
|
||||
// Send notification email
|
||||
try {
|
||||
await emailServices.auth.sendTwoFactorDisabledEmail(user);
|
||||
} catch (emailError) {
|
||||
logger.error("Failed to send 2FA disabled email:", emailError);
|
||||
}
|
||||
|
||||
auditLog(req, '2fa.disabled', user.id);
|
||||
|
||||
res.json({
|
||||
message: "Multi-factor authentication has been disabled",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("2FA disable error:", error);
|
||||
res.status(500).json({ error: "Failed to disable multi-factor authentication" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* POST /api/2fa/recovery/regenerate
|
||||
* Generate new recovery codes (requires step-up authentication)
|
||||
*/
|
||||
router.post(
|
||||
"/recovery/regenerate",
|
||||
csrfProtection,
|
||||
requireStepUpAuth("recovery_regenerate"),
|
||||
async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
if (!user.twoFactorEnabled) {
|
||||
logger.warn(`Recovery regenerate failed for user ${user.id}: 2FA not enabled`);
|
||||
return res.status(400).json({
|
||||
error: "Operation failed",
|
||||
});
|
||||
}
|
||||
|
||||
const recoveryCodes = await user.regenerateRecoveryCodes();
|
||||
|
||||
auditLog(req, '2fa.recovery.regenerated', user.id);
|
||||
|
||||
res.json({
|
||||
recoveryCodes,
|
||||
warning:
|
||||
"Save these recovery codes in a secure location. Your previous codes are now invalid.",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Recovery code regeneration error:", error);
|
||||
res.status(500).json({ error: "Failed to regenerate recovery codes" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* GET /api/2fa/recovery/remaining
|
||||
* Get recovery codes status (not exact count for security)
|
||||
*/
|
||||
router.get("/recovery/remaining", async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
const remaining = user.getRemainingRecoveryCodes();
|
||||
res.json({
|
||||
hasRecoveryCodes: remaining > 0,
|
||||
lowRecoveryCodes: remaining <= 2,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Recovery codes remaining error:", error);
|
||||
res.status(500).json({ error: "Failed to get recovery codes status" });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,9 +1,12 @@
|
||||
const express = require('express');
|
||||
const { User, UserAddress } = require('../models'); // Import from models/index.js to get models with associations
|
||||
const { authenticateToken, optionalAuth, requireAdmin } = require('../middleware/auth');
|
||||
const { validateCoordinatesBody, handleValidationErrors } = require('../middleware/validation');
|
||||
const { validateCoordinatesBody, validatePasswordChange, handleValidationErrors, sanitizeInput } = require('../middleware/validation');
|
||||
const { requireStepUpAuth } = require('../middleware/stepUpAuth');
|
||||
const { csrfProtection } = require('../middleware/csrf');
|
||||
const logger = require('../utils/logger');
|
||||
const userService = require('../services/UserService');
|
||||
const emailServices = require('../services/email');
|
||||
const { validateS3Keys } = require('../utils/s3KeyValidator');
|
||||
const { IMAGE_LIMITS } = require('../config/imageLimits');
|
||||
const router = express.Router();
|
||||
@@ -362,6 +365,58 @@ router.post('/admin/:id/ban', authenticateToken, requireAdmin, async (req, res,
|
||||
}
|
||||
});
|
||||
|
||||
// Change password (requires step-up auth if 2FA is enabled)
|
||||
router.put('/password', authenticateToken, csrfProtection, requireStepUpAuth('password_change'), sanitizeInput, validatePasswordChange, async (req, res, next) => {
|
||||
try {
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
// Google OAuth users can't change password
|
||||
if (user.authProvider === 'google' && !user.password) {
|
||||
return res.status(400).json({
|
||||
error: 'Cannot change password for accounts linked with Google'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify current password
|
||||
const isValid = await user.comparePassword(currentPassword);
|
||||
if (!isValid) {
|
||||
return res.status(400).json({ error: 'Current password is incorrect' });
|
||||
}
|
||||
|
||||
// Update password (this increments jwtVersion to invalidate other sessions)
|
||||
await user.resetPassword(newPassword);
|
||||
|
||||
// Send password changed notification
|
||||
try {
|
||||
await emailServices.auth.sendPasswordChangedEmail(user);
|
||||
} catch (emailError) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error('Failed to send password changed email', {
|
||||
error: emailError.message,
|
||||
userId: req.user.id
|
||||
});
|
||||
}
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info('Password changed successfully', { userId: req.user.id });
|
||||
|
||||
res.json({ message: 'Password changed successfully' });
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error('Password change failed', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id
|
||||
});
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Admin: Unban a user
|
||||
router.post('/admin/:id/unban', authenticateToken, requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user