Skip to content

Commit

Permalink
user login feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Ndevu12 committed May 3, 2024
1 parent b775d65 commit 972ed14
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 5 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@types/express": "^4.17.21",
"@types/jest": "^29.5.12",
"@types/jsend": "^1.0.32",
"@types/jsonwebtoken": "^9.0.6",
"@types/morgan": "^1.9.9",
"@types/node": "^20.12.7",
"@types/reflect-metadata": "^0.1.0",
Expand All @@ -69,10 +70,11 @@
"eslint-plugin-prettier": "^5.1.3",
"jest": "^29.7.0",
"jest-mock-extended": "^3.0.6",
"jsonwebtoken": "^9.0.2",
"prettier": "^3.2.5",
"supertest": "^7.0.0",
"ts-jest": "^29.1.2",
"typescript": "^5.4.5",
"typescript-eslint": "^7.7.1"
}
}
}
195 changes: 195 additions & 0 deletions src/__test__/signin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import request from 'supertest';
import { app, server } from '../index'; // update this with the path to your app file
import { Any, createConnection, getConnection, getConnectionOptions, getRepository } from 'typeorm';

Check warning on line 3 in src/__test__/signin.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'Any' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 3 in src/__test__/signin.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'Any' is defined but never used

Check warning on line 3 in src/__test__/signin.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'Any' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 3 in src/__test__/signin.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'Any' is defined but never used
import { User } from '../entities/User';

beforeAll(async () => {
// Connect to the test database
const connectionOptions = await getConnectionOptions();
await createConnection({ ...connectionOptions, name: 'testConnection' });
});

afterAll(async () => {
await getConnection('testConnection').close();
server.close();
});

describe('POST /user/login', () => {
it('should log in a user with email and password', async () => {
// sign up a user
const registerUser = {
firstName: 'Ndevu',
lastName: 'Elisa',
email: 'ndevukumurindi@gmail.com',
gender: 'male',
phoneNumber: '078907987443',
photoUrl: 'https://example.com/images/photo.jpg',
userType: 'vender',
verified: true,
status: 'active',
password: 'ndevurefu',
};
await request(app).post('/user/register').send(registerUser);

// Arrange
const loginUser = {
email: 'ndevukumurindi@gmail.com',
password: 'ndevurefu',
};
const res = await request(app).post('/user/login').send(loginUser);
expect(res.status).toBe(200);
expect(res.body).toEqual({
status: 'success',
data: {
code: 200,
message: 'logged in successful',
data: expect.any(String),
},
});

// Clean up: delete the test user
const userRepository = getRepository(User);
const user = await userRepository.findOne({ where: { email: registerUser.email } });
if (user) {
await userRepository.remove(user);
}
});

it('should not log in a user with empty credentials', async () => {
// Arrange
const loginUser = {
email: '',
password: '',
};
const res = await request(app).post('/user/login').send(loginUser);
expect(res.status).toBe(400);
expect(res.body).toEqual({ Message: 'Email and password are required' });
});

it('should not log in a user with wrong email', async () => {
// sign up a user
const registerUser = {
firstName: 'Ndevu',
lastName: 'Elisa',
email: 'ndevukkkk@gmail.com',
gender: 'male',
phoneNumber: '0789044308',
photoUrl: 'https://example.com/images/photo.jpg',
userType: 'vender',
verified: true,
status: 'active',
password: 'ndevu1',
};
await request(app).post('/user/register').send(registerUser);
// Arrange
const loginUser = {
email: 'ndevuk@gmail.com',
password: 'ndevu1',
};
const res = await request(app).post('/user/login').send(loginUser);
expect(res.status).toBe(400);
expect(res.body).toEqual({ Message: 'Invalid email' });

// Clean up: delete the test user
const userRepository = getRepository(User);
const user = await userRepository.findOne({ where: { email: registerUser.email } });
if (user) {
await userRepository.remove(user);
}
});

it('should not log in a user with unverified email', async () => {
// sign up a user
const registerUser = {
firstName: 'Ndevu',
lastName: 'Elisa',
email: 'ndevumnu@gmail.com',
gender: 'male',
phoneNumber: '0789044399',
photoUrl: 'https://example.com/images/photo.jpg',
userType: 'vender',
verified: 'false',
status: 'active',
password: 'ndevu2',
};
await request(app).post('/user/register').send(registerUser);
// Arrange
const loginUser = {
email: 'ndevumnu@gmail.com',
password: 'ndevu2',
};
const res = await request(app).post('/user/login').send(loginUser);
expect(res.status).toBe(400);
expect(res.body).toEqual({ Message: 'Email not verified. verified it first' });
// Clean up: delete the test user
const userRepository = getRepository(User);
const user = await userRepository.findOne({ where: { email: registerUser.email } });
if (user) {
await userRepository.remove(user);
}
});

it('should not log in a user with suspended account', async () => {
// sign up a user
const registerUser = {
firstName: 'Ndevu',
lastName: 'Elisa',
email: 'ndevutest1@gmail.com',
gender: 'male',
phoneNumber: '0789044391',
photoUrl: 'https://example.com/images/photo.jpg',
userType: 'vender',
verified: true,
status: 'suspended',
password: 'ndevu3',
};
await request(app).post('/user/register').send(registerUser);
// Arrange
const loginUser = {
email: 'ndevutest1@gmail.com',
password: 'ndevu3',
};
const res = await request(app).post('/user/login').send(loginUser);
expect(res.status).toBe(400);
expect(res.body).toEqual({ Message: 'You have been suspended, reach customer service for more details' });

// Clean up: delete the test user
const userRepository = getRepository(User);
const user = await userRepository.findOne({ where: { email: registerUser.email } });
if (user) {
await userRepository.remove(user);
}
});

it('should not log in a user with wrong password', async () => {
// sign up a user
const registerUser = {
firstName: 'Ndevu',
lastName: 'Elisa',
email: 'ndevumunene@gmail.com',
gender: 'male',
phoneNumber: '0709044398',
photoUrl: 'https://example.com/images/photo.jpg',
userType: 'vender',
verified: true,
status: 'active',
password: 'ndevu4',
};
await request(app).post('/user/register').send(registerUser);
// Arrange
const loginUser = {
email: 'ndevumunene@gmail.com',
password: 'ndevu123',
};
const res = await request(app).post('/user/login').send(loginUser);
expect(res.status).toBe(400);
expect(res.body).toEqual({ Message: 'Invalid password' });

// Clean up: delete the test user
const userRepository = getRepository(User);
const user = await userRepository.findOne({ where: { email: registerUser.email } });
if (user) {
await userRepository.remove(user);
}
});
});
3 changes: 2 additions & 1 deletion src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { UserController } from './authController';
import { login } from './loginAndOutController';

export{UserController};
export { UserController, login };
58 changes: 58 additions & 0 deletions src/controllers/loginAndOutController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Request, Response } from 'express';
import { User } from '../entities/User';
import { responseSuccess } from '../utils/response.utils';
import { getRepository } from 'typeorm';
import { tokenize, check } from '../helpers/TokenizeAndVerifyPass';

// Method to login admin
export const login = async (req: Request, res: Response): Promise<void> => {
try {
const { email, password } = req.body;

if (!email || !password) {
res.status(400).json({ Message: 'Email and password are required' });
return;
}

const getrepository = getRepository(User);
const user = await getrepository.findOneBy({ email: email });

if (!user) {
res.status(400).json({ Message: 'Invalid email' });
return;
}

if (user.verified !== true) {
res.status(400).json({ Message: 'Email not verified. verified it first' });
return;
}

if (user.status !== 'active') {
res.status(400).json({ Message: 'You have been suspended, reach customer service for more details' });
return;
}

const isPasswordValid = await check(user.password, password);
if (!isPasswordValid) {
res.status(400).json({ Message: 'Invalid password' });
return;
}

const accessToken = tokenize({
id: user.id,
email: user.email,
role: user.userType,
});

if (process.env.NODE_ENV === 'production') {
res.cookie('token', accessToken, { httpOnly: true, sameSite: false, secure: true });
} else {
res.cookie('token', accessToken, { httpOnly: true, sameSite: 'lax', secure: false });
}

responseSuccess(res, 200, 'logged in successful', accessToken);
} catch (error) {
console.error('Error logging in a user:', error);
res.status(500).json({ error: 'Sorry, Something went wrong' });
}
};
18 changes: 18 additions & 0 deletions src/helpers/TokenizeAndVerifyPass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
import bcrypt from 'bcrypt';

dotenv.config();

const jwtSecretKey = process.env.JWT_SECRETKEY;

if (!jwtSecretKey) {
throw new Error('JWT_SECRETKEY is not defined in the environment variables.');
}

export const tokenize = (payload: string | object | Buffer): string =>
jwt.sign(payload, jwtSecretKey, { expiresIn: '48h' });

export const check = (hashedPassword: any, password: string): boolean => {
return bcrypt.compareSync(password, hashedPassword);
};
7 changes: 4 additions & 3 deletions src/routes/UserRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Router } from 'express';
import { Router } from 'express';
import { UserController } from '../controllers/index';

import { login } from '../controllers/index';

const { registerUser } = UserController;

const router = Router();

router.post('/register', registerUser);
router.post('/login', login);

export default router;
export default router;

0 comments on commit 972ed14

Please sign in to comment.