Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Signin a user with email and password #58

Closed
wants to merge 1 commit into from
Closed
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
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"
}
}
}
219 changes: 219 additions & 0 deletions src/__test__/signin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import request from 'supertest';
import { app, server } from '../index'; // update this with the path to your app file
import { createConnection, getConnection, getConnectionOptions, getRepository } from 'typeorm';
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: '[email protected]',
gender: 'male',
phoneNumber: '078907987443',
photoUrl: 'https://example.com/images/photo.jpg',
userType: 'vender',
verified: true,
status: 'active',
password: 'ndevurefu',
};

const mail = registerUser.email;
const pass = registerUser.password;

await request(app).post('/user/register').send(registerUser);

// Arrange
const loginUser = {
email: mail,
password: pass,
};
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: '[email protected]',
gender: 'male',
phoneNumber: '0789044308',
photoUrl: 'https://example.com/images/photo.jpg',
userType: 'vender',
verified: true,
status: 'active',
password: 'ndevu1',
};

const mail = '[email protected]';
const pass = registerUser.password;

await request(app).post('/user/register').send(registerUser);

// Arrange
const loginUser = {
email: mail,
password: pass,
};

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: '[email protected]',
gender: 'male',
phoneNumber: '0789044399',
photoUrl: 'https://example.com/images/photo.jpg',
userType: 'vender',
verified: 'false',
status: 'active',
password: 'ndevu2',
};
const mail = registerUser.email;
const pass = registerUser.password;

await request(app).post('/user/register').send(registerUser);

// Arrange
const loginUser = {
email: mail,
password: pass,
};

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: '[email protected]',
gender: 'male',
phoneNumber: '0789044391',
photoUrl: 'https://example.com/images/photo.jpg',
userType: 'vender',
verified: true,
status: 'suspended',
password: 'ndevu3',
};
const mail = registerUser.email;
const pass = registerUser.password;

await request(app).post('/user/register').send(registerUser);

// Arrange
const loginUser = {
email: mail,
password: pass,
};

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: '[email protected]',
gender: 'male',
phoneNumber: '0709044398',
photoUrl: 'https://example.com/images/photo.jpg',
userType: 'vender',
verified: true,
status: 'active',
password: 'ndevu4',
};
const mail = registerUser.email;
const pass = 'ndevu5';

await request(app).post('/user/register').send(registerUser);

// Arrange
const loginUser = {
email: mail,
password: pass,
};
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);
}
});
});
4 changes: 3 additions & 1 deletion src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { UserController } from './authController';
import { login } from './loginController';

export{UserController};
export { UserController };
export { login };
7 changes: 7 additions & 0 deletions src/controllers/loginController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Request, Response } from 'express';
import { loginServices } from '../services/loginServices';

// Method to login admin
export const login = async (req: Request, res: Response): Promise<void> => {
await loginServices(req, res);
};
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);
};
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import router from './routes';
import { addDocumentation } from './startups/docs';
import 'reflect-metadata';

import cookieParser from 'cookie-parser';

Check failure on line 7 in src/index.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Cannot find module 'cookie-parser' or its corresponding type declarations.

import { CustomError, errorHandler } from './middlewares/errorHandler';
import morgan from 'morgan';
Expand All @@ -14,7 +14,7 @@
export const app = express();
const port = process.env.PORT || 8000;
app.use(express.json());

app.use(cookieParser());
app.use(cors({ origin: '*' }));
app.use(router);
addDocumentation(app);
Expand All @@ -34,4 +34,4 @@

export const server = app.listen(port, () => {
console.log(`[server]: Server is running at http://localhost:${port}`);
});
});
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;
58 changes: 58 additions & 0 deletions src/services/loginServices.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 loginServices = 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) {
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' });
}
};
Loading