email verification flow updated
This commit is contained in:
@@ -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", {
|
||||
|
||||
Reference in New Issue
Block a user