Skip to content

Commit

Permalink
Merge pull request #47 from atlp-rwanda/ft-Authenticate-user-#29
Browse files Browse the repository at this point in the history
Ft authenticate user #29
  • Loading branch information
Habinezajanvier authored May 8, 2024
2 parents cce246a + 1b2c59f commit dabe813
Show file tree
Hide file tree
Showing 12 changed files with 2,484 additions and 897 deletions.
3,121 changes: 2,232 additions & 889 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
"dependencies": {
"@types/passport-google-oauth20": "^2.0.14",
"axios": "^1.6.8",
"bcrypt": "^5.1.1",
"bcryptjs": "^2.4.3",
"check-code-coverage": "^1.10.5",
"cookie-session": "^2.1.0",
"class-validator": "^0.14.1",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
Expand Down Expand Up @@ -79,6 +81,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"ts-jest": "^29.1.2",
"ts-node-dev": "^2.0.0",
Expand Down
4 changes: 2 additions & 2 deletions src/__test__/testSetup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DbConnection } from '../database/index';
import { UserModel } from '../database/models/userModel';
import UserModel from '../database/models/userModel';

import { Role } from '../database/models';

Expand All @@ -12,7 +12,7 @@ export async function beforeAllHook() {

export async function afterAllHook() {
const userRepository = DbConnection.connection.getRepository(UserModel);
const repository = await userRepository.delete({});
const repository = await userRepository.clear();
// eslint-disable-next-line no-console
console.log(repository);

Expand Down
95 changes: 94 additions & 1 deletion src/__test__/userController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import app from '../app';
import { afterAllHook, beforeAllHook } from './testSetup';
import jwt from 'jsonwebtoken';
import dbConnection from '../database';
import { UserModel } from '../database/models';
import UserModel from '../database/models/userModel';
const userRepository = dbConnection.getRepository(UserModel);

beforeAll(beforeAllHook);
afterAll(afterAllHook);

Expand Down Expand Up @@ -143,3 +145,94 @@ describe('User Registration Tests', () => {
expect(response.body.message).toBe('User not found');
});
});





describe('User Login Tests', () => {
it('should log in a user with valid credentials', async () => {
const userData = {
firstName: 'Test',
lastName: 'User',
email: 'test@gmail.com',
password: 'TestPassword123',
userType: 'buyer',
};
await request(app).post('/api/v1/register').send(userData);
const updatedUser = await userRepository.findOne({ where: { email: userData.email } });
if (updatedUser) {
updatedUser.isVerified = true;
await userRepository.save(updatedUser);

const loginResponse = await request(app).post('/api/v1/login').send({
email: userData.email,
password: userData.password,
});

expect(loginResponse.status).toBe(200);
expect(loginResponse.body.token).toBeDefined();
expect(loginResponse.body.message).toBe('Successfully Logged in');
} else {
throw new Error('User not found');
}
});

it('should return a 401 status code if the email is not verified', async () => {
const userData = {
firstName: 'Test',
lastName: 'User',
email: 'test@gmail.com',
password: 'TestPassword123',
userType: 'buyer',
};
await request(app).post('/api/v1/register').send(userData);
const updatedUser = await userRepository.findOne({
where: { email: userData.email },
});

if (updatedUser) {
updatedUser.isVerified = false;
await userRepository.save(updatedUser);
const loginResponse = await request(app).post('/api/v1/login').send({
email: userData.email,
password: userData.password,
});

expect(loginResponse.status).toBe(401);
expect(loginResponse.body.message).toBe('Please verify your email. Confirmation link has been sent.'); // Corrected message
} else {
throw new Error('User not found');
}
});

it('should return a 401 status code if the password does not match', async () => {
const userData = {
firstName: 'Test',
lastName: 'User',
email: 'test@gmail.com',
password: 'TestPassword123',
userType: 'buyer',
};
await request(app).post('/api/v1/register').send(userData);

const loginResponse = await request(app).post('/api/v1/login').send({
email: userData.email,
password: 'IncorrectPassword',
});
expect(loginResponse.status).toBe(401);
expect(loginResponse.body.message).toBe('Password does not match');
});

it('should return a 404 status code if the user is not found', async () => {

const nonExistentEmail = 'nonexistent@example.com';
const loginResponse = await request(app).post('/api/v1/login').send({
email: nonExistentEmail,
password: 'TestPassword123',
});

expect(loginResponse.status).toBe(404);
expect(loginResponse.body.message).toBe('User Not Found');
});
});
4 changes: 4 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ const logStream = fs.createWriteStream(path.join(__dirname, 'output.log'), {
flags: 'a',
});

//Data Sanitation Against SQL injection

//Data Sanitation Against SiteScripts

morgan.token('type', function (req: Request) {
return req.headers['content-type'];
});
Expand Down
2 changes: 1 addition & 1 deletion src/controller/roleController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
updateRoleSchema,
changeRoleSchema,
} from '../middlewares/roleSchema';
import { UserModel } from '../database/models/userModel';
import UserModel from '../database/models/userModel';

const roleRepository = dbConnection.getRepository(Role);
const userRepository = dbConnection.getRepository(UserModel);
Expand Down
49 changes: 47 additions & 2 deletions src/controller/userController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { Request, Response } from 'express';
import { check, validationResult } from 'express-validator';
import bcrypt from 'bcryptjs';
import dbConnection from '../database';
import { UserModel } from '../database/models/userModel';
import { Role } from '../database/models/roleEntity';
import UserModel from '../database/models/userModel';
import sendEmail from '../emails/index';
import jwt from 'jsonwebtoken';


// Assuming dbConnection.getRepository(UserModel) returns a repository instance
const userRepository = dbConnection.getRepository(UserModel);
const roleRepository = dbConnection.getRepository(Role);
Expand Down Expand Up @@ -161,5 +162,49 @@ export const deleteUser = async (req: Request, res: Response) => {
return res
.status(500)
.json({ error: 'An error occurred while deleting the record.' });

}}



export const Login = async (req: Request, res: Response) => {
try {
const user = await userRepository.findOne({ where: { email: req.body['email'] } });
if (!user) {
return res.status(404).send({ message: 'User Not Found' });
}
const passwordMatch = await bcrypt.compare(req.body.password, user.password); // Compare with hashed password from the database
if (!passwordMatch) {
return res.status(401).send({ message: 'Password does not match' });
}
if (!user.isVerified) {
// Send confirmation email if user is not verified
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET as jwt.Secret,
{ expiresIn: '1d' }
);
const confirmLink = `${process.env.APP_URL}/api/v1/confirm?token=${token}`;
await sendEmail('confirm', user.email, { name: user.firstName, link: confirmLink });
return res.status(401).send({ message: 'Please verify your email. Confirmation link has been sent.' });
}
const userToken = jwt.sign({
id: user.id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
userType: user.userType
}, process.env.JWT_SECRET as string, { expiresIn: '7d' });
const { id, firstName, lastName, email, userType } = user;
res.status(200).json({ token: userToken, message: 'Successfully Logged in', id, firstName, lastName, email, userType });
} catch (error) {
console.error('Error occurred while logging in:', error);

Check warning on line 201 in src/controller/userController.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected console statement

Check warning on line 201 in src/controller/userController.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected console statement
res.status(500).send(error);
}
};
}






3 changes: 2 additions & 1 deletion src/database/models/userModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import {
} from 'typeorm';
import { Role } from './roleEntity';


@Entity()
export class UserModel {
export default class UserModel {
@PrimaryGeneratedColumn()
id: number;

Expand Down
93 changes: 93 additions & 0 deletions src/docs/userAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* @swagger
* /api/v1/login:
* post:
* summary: Login user
* tags: [Login]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* email:
* type: string
* format: email
* description: The email address of the user
* password:
* type: string
* format: password
* description: The password of the user
* responses:
* '200':
* description: Successful login
* content:
* application/json:
* schema:
* type: object
* properties:
* token:
* type: string
* description: JWT token for authentication
* message:
* type: string
* description: A message indicating successful login
* id:
* type: integer
* description: The unique identifier of the user
* firstName:
* type: string
* description: The first name of the user
* lastName:
* type: string
* description: The last name of the user
* email:
* type: string
* format: email
* description: The email address of the user
* userType:
* type: string
* enum: ['vendor', 'buyer']
* description: The type of user
* '400':
* description: Bad request
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* description: An error message indicating invalid request
* '401':
* description: Unauthorized - Password does not match or email not verified
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* description: An error message indicating password does not match or email not verified
* '404':
* description: Not Found - User not found
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* description: An error message indicating user not found
* '500':
* description: Internal server error
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* description: An error message indicating internal server error
*/
2 changes: 1 addition & 1 deletion src/middlewares/passport-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import passport from 'passport';
import GooglePassport from 'passport-google-oauth20';
import FacebookPassport from 'passport-facebook';
import dbConnection from '../database';
import { UserModel } from '../database/models';
import UserModel from '../database/models/userModel';
import dotenv from 'dotenv';
dotenv.config();

Expand Down
4 changes: 4 additions & 0 deletions src/routes/userRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
deleteAllUsers,
getAllUsers,
deleteUser,
Login
} from '../controller/userController';

const route = Router();
Expand All @@ -13,5 +14,8 @@ route.get('/getAllUsers', getAllUsers);
route.get('/confirm', confirmEmail);
route.delete('/delete/:id', deleteUser);
route.delete('/deleteAllUsers', deleteAllUsers);
route.post('/login',Login)
route.get('/all-users', getAllUsers);

export default route;

1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"module": "commonjs",
"jsx": "preserve",
"outDir": "dist",
"types": ["jest"],
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
Expand Down

0 comments on commit dabe813

Please sign in to comment.