Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 124 additions & 22 deletions backend/controllers/authController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { sendEmail } from '../utils/sendEmail.js';
import bcrypt from 'bcryptjs';
import Joi from 'joi';
import jwt from 'jsonwebtoken';
import crypto from 'crypto';

// Validation schema for registration
const registerSchema = Joi.object({
Expand Down Expand Up @@ -47,6 +48,11 @@ export const register = async (req, res) => {
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(password, saltRounds);

// Generate email verification token
const emailVerificationToken = crypto.randomBytes(32).toString('hex');
const emailVerificationTokenExpiry = new Date();
emailVerificationTokenExpiry.setHours(emailVerificationTokenExpiry.getHours() + 24); // Token expires in 24 hours

// Create user
const user = await User.create({
firstName,
Expand All @@ -56,35 +62,78 @@ export const register = async (req, res) => {
studentStaffId,
role,
isConfirmed: false,
status: 'pending_role'
status: 'pending_role',
isEmailVerified: false,
emailVerificationToken,
emailVerificationTokenExpiry
});

// Send confirmation email
try {
await sendEmail(
email,
'Registration Successful - Bit by Bit',
`
<h2>Welcome to Bit by Bit!</h2>
<p>Dear ${firstName} ${lastName},</p>
<p>Your registration was successful. Your account is currently pending role verification.</p>
<p>Role: ${role}</p>
<p>Student/Staff ID: ${studentStaffId}</p>
<p>You will receive another email once your role has been verified by an administrator.</p>
<br>
<p>Best regards,<br>Bit by Bit Team</p>
`
);
} catch (emailError) {
console.error('Email sending failed:', emailError);
// Don't fail registration if email fails
// Send verification email (only for students)
if (role === 'student') {
try {
// Verification link should point to backend API endpoint
const backendUrl = process.env.BACKEND_URL || 'http://localhost:3000';
const verificationUrl = `${backendUrl}/api/users/verify-email?token=${emailVerificationToken}`;
await sendEmail(
email,
'Verify Your Email - Bit by Bit',
`
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #001F3F;">Welcome to Bit by Bit!</h2>
<p>Dear ${firstName} ${lastName},</p>
<p>Thank you for registering! Please verify your email address to complete your registration.</p>
<p>Click the button below to verify your email:</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${verificationUrl}"
style="background-color: #00C2CB; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px; display: inline-block;">
Verify Email Address
</a>
</div>
<p>Or copy and paste this link into your browser:</p>
<p style="word-break: break-all; color: #666;">${verificationUrl}</p>
<p style="color: #999; font-size: 12px;">This link will expire in 24 hours.</p>
<p style="color: #999; font-size: 12px;">If you didn't create an account, please ignore this email.</p>
<br>
<p>Best regards,<br>Bit by Bit Team</p>
</div>
`
);
} catch (emailError) {
console.error('Email sending failed:', emailError);
// Don't fail registration if email fails
}
} else {
// For non-students, send regular confirmation email
try {
await sendEmail(
email,
'Registration Successful - Bit by Bit',
`
<h2>Welcome to Bit by Bit!</h2>
<p>Dear ${firstName} ${lastName},</p>
<p>Your registration was successful. Your account is currently pending role verification.</p>
<p>Role: ${role}</p>
<p>Student/Staff ID: ${studentStaffId}</p>
<p>You will receive another email once your role has been verified by an administrator.</p>
<br>
<p>Best regards,<br>Bit by Bit Team</p>
`
);
} catch (emailError) {
console.error('Email sending failed:', emailError);
// Don't fail registration if email fails
}
}

// Return user data (without password)
const { password: _, ...userWithoutPassword } = user.toObject();
const { password: _, emailVerificationToken: __, ...userWithoutPassword } = user.toObject();

const message = role === 'student'
? 'Registration successful. Please check your email to verify your account.'
: 'Registration successful. Please wait for role verification.';

return res.status(201).json({
message: 'Registration successful. Please wait for role verification.',
message,
user: userWithoutPassword
});

Expand All @@ -109,6 +158,13 @@ export const login = async (req, res) => {
return res.status(401).json({ message: 'Invalid credentials' });
}

// Check email verification for students
if (user.role === 'student' && !user.isEmailVerified) {
return res.status(401).json({
message: 'Please verify your email address before logging in. Check your inbox for the verification link.'
});
}

// Check if user is confirmed and active
if (!user.isConfirmed || user.status !== 'active') {
return res.status(401).json({
Expand Down Expand Up @@ -150,6 +206,52 @@ export const login = async (req, res) => {
}
};

// Email verification
export const verifyEmail = async (req, res) => {
try {
const { token } = req.query;

if (!token) {
// Redirect to login page with error message
const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:5173';
return res.redirect(`${frontendUrl}/login?error=Invalid verification token`);
}

// Find user with this token
const user = await User.findOne({
emailVerificationToken: token,
emailVerificationTokenExpiry: { $gt: new Date() } // Token not expired
});

if (!user) {
// Token is invalid or expired
const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:5173';
return res.redirect(`${frontendUrl}/login?error=Verification token is invalid or has expired`);
}

// Check if already verified
if (user.isEmailVerified) {
const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:5173';
return res.redirect(`${frontendUrl}/login?message=Email already verified`);
}

// Verify the email
user.isEmailVerified = true;
user.emailVerificationToken = undefined;
user.emailVerificationTokenExpiry = undefined;
await user.save();

// Redirect to login page with success message
const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:5173';
return res.redirect(`${frontendUrl}/login?message=Email verified successfully. You can now login.`);

} catch (error) {
console.error('Email verification error:', error);
const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:5173';
return res.redirect(`${frontendUrl}/login?error=An error occurred during verification`);
}
};

// User logout
export const logout = async (req, res) => {
try {
Expand Down
Loading