email verification flow updated

This commit is contained in:
jackiettran
2025-12-15 22:45:55 -05:00
parent 5e01bb8cff
commit 372ab093ef
19 changed files with 1214 additions and 246 deletions

View File

@@ -20,7 +20,9 @@ const {
loginLimiter,
registerLimiter,
passwordResetLimiter,
emailVerificationLimiter,
} = require("../middleware/rateLimiter");
const { authenticateToken } = require("../middleware/auth");
const router = express.Router();
const googleClient = new OAuth2Client(
@@ -43,8 +45,7 @@ router.post(
validateRegistration,
async (req, res) => {
try {
const { email, password, firstName, lastName, phone } =
req.body;
const { email, password, firstName, lastName, phone } = req.body;
const existingUser = await User.findOne({
where: { email },
@@ -64,7 +65,7 @@ router.post(
// Alpha access validation
let alphaInvitation = null;
if (process.env.ALPHA_TESTING_ENABLED === 'true') {
if (process.env.ALPHA_TESTING_ENABLED === "true") {
if (req.cookies && req.cookies.alphaAccessCode) {
const { code } = req.cookies.alphaAccessCode;
if (code) {
@@ -88,7 +89,8 @@ router.post(
if (!alphaInvitation) {
return res.status(403).json({
error: "Alpha access required. Please enter your invitation code first.",
error:
"Alpha access required. Please enter your invitation code first.",
});
}
}
@@ -116,7 +118,10 @@ router.post(
// Send verification email (don't block registration if email fails)
let verificationEmailSent = false;
try {
await emailServices.auth.sendVerificationEmail(user, user.verificationToken);
await emailServices.auth.sendVerificationEmail(
user,
user.verificationToken
);
verificationEmailSent = true;
} catch (emailError) {
const reqLogger = logger.withRequestId(req.id);
@@ -200,7 +205,10 @@ router.post(
const user = await User.findOne({ where: { email } });
if (!user) {
return res.status(401).json({ error: "Invalid credentials" });
return res.status(401).json({
error:
"Unable to log in. Please check your email and password, or create an account.",
});
}
// Check if account is locked
@@ -217,7 +225,10 @@ router.post(
if (!isPasswordValid) {
// Increment login attempts
await user.incLoginAttempts();
return res.status(401).json({ error: "Invalid credentials" });
return res.status(401).json({
error:
"Unable to log in. Please check your email and password, or create an account.",
});
}
// Reset login attempts on successful login
@@ -322,7 +333,8 @@ router.post(
if (!email) {
return res.status(400).json({
error: "Email permission is required to continue. Please grant email access when signing in with Google and try again."
error:
"Email permission is required to continue. Please grant email access when signing in with Google and try again.",
});
}
@@ -332,18 +344,22 @@ router.post(
let lastName = familyName;
if (!firstName || !lastName) {
const emailUsername = email.split('@')[0];
const emailUsername = email.split("@")[0];
// Try to split email username by common separators
const nameParts = emailUsername.split(/[._-]/);
if (!firstName) {
firstName = nameParts[0] ? nameParts[0].charAt(0).toUpperCase() + nameParts[0].slice(1) : 'Google';
firstName = nameParts[0]
? nameParts[0].charAt(0).toUpperCase() + nameParts[0].slice(1)
: "Google";
}
if (!lastName) {
lastName = nameParts.length > 1
? nameParts[nameParts.length - 1].charAt(0).toUpperCase() + nameParts[nameParts.length - 1].slice(1)
: 'User';
lastName =
nameParts.length > 1
? nameParts[nameParts.length - 1].charAt(0).toUpperCase() +
nameParts[nameParts.length - 1].slice(1)
: "User";
}
}
@@ -375,7 +391,7 @@ router.post(
});
// Check if there's an alpha invitation for this email
if (process.env.ALPHA_TESTING_ENABLED === 'true') {
if (process.env.ALPHA_TESTING_ENABLED === "true") {
const alphaInvitation = await AlphaInvitation.findOne({
where: { email: email.toLowerCase().trim() },
});
@@ -467,73 +483,121 @@ router.post(
);
// Email verification endpoint
router.post("/verify-email", sanitizeInput, async (req, res) => {
try {
const { token } = req.body;
router.post(
"/verify-email",
emailVerificationLimiter,
authenticateToken,
sanitizeInput,
async (req, res) => {
try {
const { code } = req.body;
if (!token) {
return res.status(400).json({
error: "Verification token required",
code: "TOKEN_REQUIRED",
});
}
if (!code) {
return res.status(400).json({
error: "Verification code required",
code: "CODE_REQUIRED",
});
}
// Find user with this verification token
const user = await User.findOne({
where: { verificationToken: token },
});
// Validate 6-digit format
if (!/^\d{6}$/.test(code)) {
return res.status(400).json({
error: "Verification code must be 6 digits",
code: "INVALID_CODE_FORMAT",
});
}
if (!user) {
return res.status(400).json({
error: "Invalid verification token",
code: "VERIFICATION_TOKEN_INVALID",
});
}
// Get the authenticated user
const user = await User.findByPk(req.user.id);
// Check if already verified
if (user.isVerified) {
return res.status(400).json({
error: "Email already verified",
code: "ALREADY_VERIFIED",
});
}
if (!user) {
return res.status(404).json({
error: "User not found",
code: "USER_NOT_FOUND",
});
}
// Check if token is valid (not expired)
if (!user.isVerificationTokenValid(token)) {
return res.status(400).json({
error: "Verification token has expired. Please request a new one.",
code: "VERIFICATION_TOKEN_EXPIRED",
});
}
// Check if already verified
if (user.isVerified) {
return res.status(400).json({
error: "Email already verified",
code: "ALREADY_VERIFIED",
});
}
// Verify the email
await user.verifyEmail();
// Check if too many failed attempts
if (user.isVerificationLocked()) {
return res.status(429).json({
error: "Too many verification attempts. Please request a new code.",
code: "TOO_MANY_ATTEMPTS",
});
}
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Email verified successfully", {
userId: user.id,
email: user.email,
});
// Check if user has a verification token
if (!user.verificationToken) {
return res.status(400).json({
error: "No verification code found. Please request a new one.",
code: "NO_CODE",
});
}
res.json({
message: "Email verified successfully",
user: {
id: user.id,
// Check if code is expired
if (
user.verificationTokenExpiry &&
new Date() > new Date(user.verificationTokenExpiry)
) {
return res.status(400).json({
error: "Verification code has expired. Please request a new one.",
code: "VERIFICATION_EXPIRED",
});
}
// Validate the code
if (!user.isVerificationTokenValid(input)) {
// Increment failed attempts
await user.incrementVerificationAttempts();
const reqLogger = logger.withRequestId(req.id);
reqLogger.warn("Invalid verification code attempt", {
userId: user.id,
attempts: user.verificationAttempts + 1,
});
return res.status(400).json({
error: "Invalid verification code",
code: "VERIFICATION_INVALID",
});
}
// Verify the email
await user.verifyEmail();
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Email verified successfully", {
userId: user.id,
email: user.email,
isVerified: true,
},
});
} catch (error) {
const reqLogger = logger.withRequestId(req.id);
reqLogger.error("Email verification error", {
error: error.message,
stack: error.stack,
});
res.status(500).json({
error: "Email verification failed. Please try again.",
});
});
res.json({
message: "Email verified successfully",
user: {
id: user.id,
email: user.email,
isVerified: true,
},
});
} catch (error) {
const reqLogger = logger.withRequestId(req.id);
reqLogger.error("Email verification error", {
error: error.message,
stack: error.stack,
});
res.status(500).json({
error: "Email verification failed. Please try again.",
});
}
}
});
);
// Resend verification email endpoint
router.post(
@@ -575,7 +639,10 @@ router.post(
// Send verification email
try {
await emailServices.auth.sendVerificationEmail(user, user.verificationToken);
await emailServices.auth.sendVerificationEmail(
user,
user.verificationToken
);
} catch (emailError) {
const reqLogger = logger.withRequestId(req.id);
reqLogger.error("Failed to resend verification email", {