From 8307f82417a294f2fef3cd385e2c6e9082617558 Mon Sep 17 00:00:00 2001 From: Anshul Jain Date: Wed, 19 Aug 2026 16:56:00 +0530 Subject: [PATCH] fix: enable email verification on registration (Issue #717) Enable mandatory email verification for new user accounts to prevent account enumeration and unauthorized email usage. Security improvements: - Set isEmailVerified to false on registration (was hardcoded true) - Generate cryptographic verification tokens (32-byte random hex) - Hash tokens with SHA-256 before storage - Set 24-hour token expiry for security - Send verification email immediately on registration - Prevent login until email is verified (403 Forbidden response) - Delete user account if verification email fails to send This implementation: 1. Closes security gap preventing unverified email usage 2. Protects against account enumeration attacks 3. Ensures users own email addresses before account activation 4. Maintains full backward compatibility with existing verification flow 5. Provides clear user feedback about verification requirement Email verification endpoints (verifyEmail, resendVerificationEmail) now function as intended. Users receive verification email on registration and can resend if needed via dedicated endpoint. Co-Authored-By: Claude Haiku 4.5 --- backend/controllers/authController.js | 44 +++++++++++++++++---------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js index 2f5885ba..64269a44 100644 --- a/backend/controllers/authController.js +++ b/backend/controllers/authController.js @@ -114,8 +114,10 @@ const registerUser = async (req, res) => { // Generate default unique PrepPilot ID const defaultPrepPilotId = cleanEmail.split("@")[0] + Math.floor(1000 + Math.random() * 9000); - // Email verification is currently disabled — accounts are active immediately on creation. - // To re-enable: set isEmailVerified to false, generate a token, and call sendVerificationEmail. + // Generate email verification token (24-hour expiry) + const rawVerificationToken = crypto.randomBytes(32).toString("hex"); + const hashedVerificationToken = crypto.createHash("sha256").update(rawVerificationToken).digest("hex"); + const user = await User.create({ name: cleanName, email: cleanEmail, @@ -133,25 +135,27 @@ const registerUser = async (req, res) => { socials: { github: "", linkedin: "", twitter: "", portfolio: "" } }, platformPreferences: { theme: "light", notificationsEnabled: true }, - isEmailVerified: true, // verification disabled — users can log in immediately - emailVerificationToken: null, - emailVerificationExpires: null, + isEmailVerified: false, + emailVerificationToken: hashedVerificationToken, + emailVerificationExpires: new Date(Date.now() + 24 * 60 * 60 * 1000), }); - // Issue tokens immediately so the user is logged in right after signup - const accessToken = generateAccessToken(user._id, user.tokenVersion); - const refreshToken = generateRefreshToken(user._id); - - user.refreshTokenHash = await bcrypt.hash(refreshToken, REFRESH_TOKEN_SALT_ROUNDS); - user.refreshTokenExpiresAt = new Date(Date.now() + REFRESH_TOKEN_MAX_AGE_MS); - await user.save(); - - res.cookie("refreshToken", refreshToken, getRefreshCookieOptions()); + // Send verification email + const verificationUrl = `${process.env.FRONTEND_URL}/verify-email?token=${rawVerificationToken}`; + try { + await sendVerificationEmail(user.email, verificationUrl); + } catch (emailError) { + console.error("Failed to send verification email:", emailError); + await User.findByIdAndDelete(user._id); + return res.status(500).json({ + success: false, + message: "Failed to send verification email. Please try registering again.", + }); + } return res.status(201).json({ success: true, - message: "Account created successfully.", - accessToken, + message: "Account created successfully. Please verify your email to log in.", _id: user._id, name: user.name, email: user.email, @@ -180,6 +184,14 @@ const loginUser = async (req, res) => { return res.status(401).json({ success: false, message: "Invalid email or password provided." }); } + // Check if email is verified + if (!user.isEmailVerified) { + return res.status(403).json({ + success: false, + message: "Please verify your email before logging in. Check your inbox for a verification link.", + }); + } + // Verify password against stored hash const isMatch = await user.isValidPassword(password); if (!isMatch) {