Skip to content

Commit

Permalink
Merge pull request #115 from akshayw1/emailtemp
Browse files Browse the repository at this point in the history
Add Email Service for Registration and Verification
  • Loading branch information
rajutkarsh07 authored Dec 18, 2024
2 parents 4f360bc + 2c3d77f commit 3637830
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 77 deletions.
7 changes: 4 additions & 3 deletions webiu-server/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
PORT=5100
PORT=5000
MONGODB_URI=mongodb://localhost:27017/webiu
JWT_SECRET=your_jwt_secret
GITHUB_ACCESS_TOKEN=your_github_access_token_add_here

FRONTEND_BASE_URL=""
GMAIL_USER=""
GMAIL_PASSWORD=""
86 changes: 37 additions & 49 deletions webiu-server/__tests__/authController.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
const request = require('supertest');
const express = require('express');
const User = require('../models/User');
const { sendVerificationEmail } = require('../services/emailServices');
const { signToken } = require('../utils/jwt');
const crypto = require('crypto');
const authController = require('../controllers/authController');

jest.mock('../services/emailServices'); // Mock email service
jest.mock('../utils/jwt', () => ({
signToken: jest.fn().mockReturnValue('mockedToken'),
}));
Expand All @@ -12,43 +15,14 @@ const app = express();
app.use(express.json());
app.post('/register', authController.register);
app.post('/login', authController.login);
app.get('/verify-email', authController.verifyEmail); // GET method to verify email

describe('Auth Controller Tests', () => {
beforeEach(() => {
jest.clearAllMocks();
});

// Test successful user registration
it('should register a user successfully', async () => {
const mockUser = {
_id: '60d6f96a9b1f8f001c8f27c5',
name: 'John Doe',
email: '[email protected]',
password: 'password123',
};

User.findOne = jest.fn().mockResolvedValue(null);
User.prototype.save = jest.fn().mockResolvedValue(mockUser);

const response = await request(app).post('/register').send({
name: 'John Doe',
email: '[email protected]',
password: 'password123',
confirmPassword: 'password123',
});

response.body.data.user.id = mockUser._id;

expect(response.status).toBe(201);
expect(response.body.status).toBe('success');
expect(response.body.data.user).toEqual({
id: mockUser._id,
name: mockUser.name,
email: mockUser.email,
});
expect(response.body.data.token).toBe('mockedToken');
jest.clearAllMocks(); // Clear mocks before each test
});


// Test failed user registration - email already exists
it('should return an error when email already exists during registration', async () => {
const mockUser = {
Expand Down Expand Up @@ -86,6 +60,21 @@ describe('Auth Controller Tests', () => {
expect(response.body.message).toBe('Passwords do not match');
});

// Test failed user registration - invalid email format
it('should return an error for invalid email format during registration', async () => {
const response = await request(app).post('/register').send({
name: 'John Doe',
email: 'invalid-email',
password: 'password123',
confirmPassword: 'password123',
});

expect(response.status).toBe(400);
expect(response.body.status).toBe('error');
expect(response.body.message).toBe('Invalid email format');
});


// Test successful user login
it('should login a user successfully', async () => {
const mockUser = {
Expand All @@ -94,6 +83,7 @@ describe('Auth Controller Tests', () => {
password: 'password123',
matchPassword: jest.fn().mockResolvedValue(true),
githubId: 'john-github',
isVerified: true,
};

User.findOne = jest.fn().mockResolvedValue(mockUser);
Expand All @@ -105,6 +95,7 @@ describe('Auth Controller Tests', () => {

expect(response.status).toBe(200);
expect(response.body.status).toBe('success');
expect(response.body.message).toBe('Login successful');
expect(response.body.data.user).toEqual({
id: mockUser._id,
name: mockUser.name,
Expand All @@ -120,7 +111,7 @@ describe('Auth Controller Tests', () => {
_id: '60d6f96a9b1f8f001c8f27c5',
email: '[email protected]',
password: 'password123',
matchPassword: jest.fn().mockResolvedValue(false),
matchPassword: jest.fn().mockResolvedValue(false),
};

User.findOne = jest.fn().mockResolvedValue(mockUser);
Expand All @@ -137,7 +128,7 @@ describe('Auth Controller Tests', () => {

// Test failed user login - user not found
it('should return an error if user is not found during login', async () => {
User.findOne = jest.fn().mockResolvedValue(null);
User.findOne = jest.fn().mockResolvedValue(null);

const response = await request(app).post('/login').send({
email: '[email protected]',
Expand All @@ -149,28 +140,25 @@ describe('Auth Controller Tests', () => {
expect(response.body.message).toBe('User not found');
});

// Test failed user registration - invalid email format
it('should return an error for invalid email format during registration', async () => {
const response = await request(app).post('/register').send({
name: 'John Doe',
email: 'invalid-email',
// Test failed user login - email not verified
it('should return an error if email is not verified during login', async () => {
const mockUser = {
_id: '60d6f96a9b1f8f001c8f27c5',
email: '[email protected]',
password: 'password123',
confirmPassword: 'password123',
});
matchPassword: jest.fn().mockResolvedValue(true),
isVerified: false,
};

expect(response.status).toBe(400);
expect(response.body.status).toBe('error');
expect(response.body.message).toBe('Invalid email format');
});
User.findOne = jest.fn().mockResolvedValue(mockUser);

// Test failed user login - missing fields
it('should return an error if required fields are missing during login', async () => {
const response = await request(app).post('/login').send({
email: '[email protected]',
email: '[email protected]',
password: 'password123',
});

expect(response.status).toBe(401);
expect(response.body.status).toBe('error');
expect(response.body.message).toBe('User not found');
expect(response.body.message).toBe('Please verify your email to login');
});
});
74 changes: 58 additions & 16 deletions webiu-server/controllers/authController.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
// controllers/authController.js
const User = require('../models/User');
const { signToken } = require('../utils/jwt');

const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;

const { sendVerificationEmail } = require('../services/emailServices.js');
const crypto = require('crypto');
// const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;

const register = async (req, res) => {
const { name, email, password, confirmPassword, githubId } = req.body;

// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return res.status(400).json({
status: 'error',
message: 'Invalid email format',
});
}

// Validate password and confirmPassword
if (password !== confirmPassword) {
return res.status(400).json({
status: 'error',
Expand All @@ -23,6 +26,7 @@ const register = async (req, res) => {
}

try {
// Check if the user already exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({
Expand All @@ -31,32 +35,61 @@ const register = async (req, res) => {
});
}

const user = new User({ name, email, password, githubId });
// Generate a unique email verification token
const verificationToken = crypto.randomBytes(32).toString('hex');

// Create a new user (email is not verified yet)
const user = new User({
name,
email,
password,
githubId,
verificationToken,
isVerified: false,
});
await user.save();

const token = signToken(user);
// Send verification email
await sendVerificationEmail(email, verificationToken);

res.status(201).json({
status: 'success',
message: 'User registered successfully',
data: {
user: {
id: user._id,
name: user.name,
email: user.email,
},
token,
},
message: 'User registered successfully. Please verify your email to log in.',
});
} catch (error) {
console.error(error);
res.status(500).json({
status: 'error',
message: error.message,
message: 'Internal server error',
});
}
};


// Verify user's email
const verifyEmail = async (req, res) => {
const { token } = req.query;

try {
// Find the user with the given token
const user = await User.findOne({ verificationToken: token });
if (!user) {
return res.status(400).json({ message: 'Invalid or expired token' });
}

// Verify the user
user.isVerified = true;
user.verificationToken = undefined; // Clear the token
await user.save();

res.status(200).json({ message: 'Email verified successfully' });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal Server Error' });
}
};


const login = async (req, res) => {
const { email, password } = req.body;

Expand All @@ -76,6 +109,13 @@ const login = async (req, res) => {
});
}

if(!user.isVerified){
return res.status(401).json({
status: 'error',
message: 'Please verify your email to login',
});
}


const token = signToken(user);

Expand All @@ -100,4 +140,6 @@ const login = async (req, res) => {
}
};

module.exports = { register, login };


module.exports = { register, login,verifyEmail };
21 changes: 13 additions & 8 deletions webiu-server/models/User.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
githubId: { type: String },
}, { timestamps: true });
const userSchema = new mongoose.Schema(
{
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
githubId: { type: String },
isVerified: { type: Boolean, default: false }, // Email verification status
verificationToken: { type: String }, // Token for email verification
},
{ timestamps: true }
);

// Hash the password before saving
userSchema.pre('save', async function(next) {
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});

userSchema.methods.matchPassword = async function(password) {
userSchema.methods.matchPassword = async function (password) {
return await bcrypt.compare(password, this.password);
};

Expand Down
18 changes: 18 additions & 0 deletions webiu-server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion webiu-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@
"colors": "^1.4.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"dotenv": "^16.4.5",
"express": "^4.21.1",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.8.1",
"nodemailer": "^6.9.16",
"nodemon": "^3.1.4",
"path": "^0.12.7"
},
"devDependencies": {
"jest": "^29.7.0",
"supertest": "^7.0.0"
}
}
}
Loading

0 comments on commit 3637830

Please sign in to comment.