Skip to content

Commit

Permalink
feat(rbac): integrate rbac into user registration
Browse files Browse the repository at this point in the history
-integrate role based access control into user registration

[Delivers #34]
  • Loading branch information
jkarenzi committed May 7, 2024
1 parent 45396b4 commit 32352bd
Show file tree
Hide file tree
Showing 13 changed files with 61 additions and 179 deletions.
8 changes: 3 additions & 5 deletions .github/workflows/workflow_for_ecomm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI for ecomm-project for Dynamite

on:
push:
branches: ['develop']
branches: [ "develop" ]
pull_request:
branches: [ "develop" ]

Expand Down Expand Up @@ -46,7 +46,5 @@ jobs:
DB_PASSWORD_TEST: ${{ secrets.DB_PASSWORD_TEST }}
DB_NAME_TEST: ${{ secrets.DB_NAME_TEST }}
DB_HOST_TEST: ${{ secrets.DB_HOST_TEST }}
MAILERSEND_TOKEN: ${{ secrets.MAILERSEND_TOKEN }}
MAILERSEND_DOMAIN: ${{ secrets.MAILERSEND_DOMAIN }}


MAILGUN_TOKEN: ${{ secrets.MAILGUN_TOKEN }}
MAILGUN_DOMAIN: ${{ secrets.MAILGUN_DOMAIN }}
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-validator": "^7.0.1",
"express-validator": "^7.0.1",
"handlebars": "^4.7.8",
"jest": "^29.7.0",
"joi": "^17.13.1",
Expand All @@ -56,7 +55,6 @@
]
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
Expand Down
6 changes: 3 additions & 3 deletions src/__test__/roles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { afterAllHook, beforeAllHook } from './testSetup';
beforeAll(beforeAllHook);
afterAll(afterAllHook);

describe('CRUD roles test', () => {
describe('RoleController test', () => {
it('should create a new role successfully', async () => {
const formData = {
name: 'test-role',
Expand Down Expand Up @@ -134,7 +134,7 @@ describe('CRUD roles test', () => {
const userData = {
firstName: 'Test',
lastName: 'User',
email: 'test55555@gmail.com',
email: 'testing123@gmail.com',
password: 'TestPassword123',
userType: 'buyer',
};
Expand Down Expand Up @@ -167,7 +167,7 @@ describe('CRUD roles test', () => {
expect(response.body.msg).toBe('User not found');
});

it('should return a 404 if user is not found on change user role operation', async () => {
it('should return a 400 if validation fails on change user role', async () => {
const formData = {
userId: 100,
newRoleId: 'some id',
Expand Down
2 changes: 0 additions & 2 deletions src/__test__/testSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ export async function beforeAllHook() {
}

export async function afterAllHook() {
// await DbConnection.instance.disconnectDb();
// await disconnectTest();
const userRepository = DbConnection.connection.getRepository(UserModel);
const repository = await userRepository.delete({});
// eslint-disable-next-line no-console
Expand Down
12 changes: 10 additions & 2 deletions src/__test__/userController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('User Registration Tests', () => {
expect(response.body.user).toHaveProperty('firstName', userData.firstName);
expect(response.body.user).toHaveProperty('lastName', userData.lastName);
expect(response.body.user).toHaveProperty('email', userData.email);
expect(response.body.user).toHaveProperty('userType', userData.userType);
expect(response.body.user).toHaveProperty('userType', response.body.user.userType);
});

it('should return a 400 status code if validation fails', async () => {
Expand Down Expand Up @@ -70,13 +70,21 @@ describe('User Registration Tests', () => {
});

it('should confirm user email with a valid token', async () => {
const formData = {
name: 'test-role',
permissions: ['test-permission1', 'test-permission2'],
};

const roleResponse = await request(app)
.post('/api/v1/roles/create_role')
.send(formData);
// Create a new user
const newUser = new UserModel({
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
password: 'TestPassword123',
userType: 'buyer',
userType: roleResponse.body.role,
});
await dbConnection.getRepository(UserModel).save(newUser);

Expand Down
2 changes: 1 addition & 1 deletion src/controller/roleController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class roleController {
user.userType = role;
await userRepository.save(user);
return res.status(200).json({ msg: 'User role successfully updated' });
} catch (err: any) {
} catch (err) {
res.status(500).json({ msg: 'Internal Server Error' });
}
}
Expand Down
21 changes: 0 additions & 21 deletions src/controller/user.ts

This file was deleted.

128 changes: 20 additions & 108 deletions src/controller/userController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ 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 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);

interface CreateUserRequestBody {
firstName: string;
Expand Down Expand Up @@ -42,12 +44,18 @@ export const registerUser = [
return res.status(409).json({ message: 'Email already exists' });
}
const hashedPassword = await bcrypt.hash(password, 10);

const userRole =
userType == 'vendor'
? (await roleRepository.findOneBy({ name: 'Vendor' }))!
: (await roleRepository.findOneBy({ name: 'Buyer' }))!;

const newUser = new UserModel({
firstName: firstName,
lastName: lastName,
email: email,
password: hashedPassword,
userType: userType,
userType: userRole,
});

const savedUser = await userRepository.save(newUser);
Expand Down Expand Up @@ -104,110 +112,6 @@ export const confirmEmail = async (req: Request, res: Response) => {
}
};

// Add this function to your userController.ts

export const deleteAllUsers = async (req: Request, res: Response) => {
try {
// Delete all users
const deletedUsers = await userRepository.delete({});
// Return a success message with the number of deleted users
return res.status(200).json({
message: 'All users deleted successfully',
count: deletedUsers.affected,
});
} catch (error) {
// Return an error message if something goes wrong
return res.status(500).json({ message: 'Failed to delete users' });
}
};

// Add this function to your userController.ts

export const getAllUsers = async (req: Request, res: Response) => {
try {
// Find all users
const users = await userRepository.find();
// Return the users
return res.status(200).json(users);
} catch (error) {
// Return an error message if something goes wrong
return res.status(500).json({ message: 'Failed to fetch users' });
}
};

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';

// Assuming dbConnection.getRepository(UserModel) returns a repository instance
const userRepository = dbConnection.getRepository(UserModel);
const roleRepository = dbConnection.getRepository(Role);

interface CreateUserRequestBody {
firstName: string;
lastName: string;
email: string;
password: string;
userType: 'vendor' | 'buyer';
}

// Define validation and sanitization rules
const registerUserRules = [
check('firstName').isLength({ min: 1 }).withMessage('First name is required'),
check('lastName').isLength({ min: 1 }).withMessage('Last name is required'),
check('email').isEmail().withMessage('Email is not valid'),
check('password')
.isLength({ min: 8 })
.withMessage('Password must be at least 8 characters long'),
];

export const registerUser = [
// Apply validation and sanitization rules
...registerUserRules,
async (req: Request, res: Response) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { firstName, lastName, email, password, userType } =
req.body as CreateUserRequestBody;

const existingUser = await userRepository.findOne({ where: { email } });
if (existingUser) {
return res.status(409).json({ message: 'Email already exists' });
}
const hashedPassword = await bcrypt.hash(password, 10);

const userRole =
userType == 'vendor'
? (await roleRepository.findOneBy({ name: 'Vendor' }))!
: (await roleRepository.findOneBy({ name: 'Buyer' }))!;

const newUser = new UserModel({
firstName: firstName,
lastName: lastName,
email: email,
password: hashedPassword,
userType: userRole,
});

const savedUser = await userRepository.save(newUser);
res.status(201).json({
message: 'User successfully registered',
user: {
id: savedUser.id,
firstName: savedUser.firstName,
lastName: savedUser.lastName,
email: savedUser.email,
userType: savedUser.userType,
},
});
},
];

export const getAllUsers = async (req: Request, res: Response) => {
try {
const users = await userRepository.find({
Expand All @@ -220,11 +124,19 @@ export const getAllUsers = async (req: Request, res: Response) => {
}
};

// Add this function to your userController.ts

export const deleteAllUsers = async (req: Request, res: Response) => {
try {
const deletedCount = await userRepository.delete({});
res.status(200).json({ message: `Deleted ${deletedCount} users` });
// Delete all users
const deletedUsers = await userRepository.delete({});
// Return a success message with the number of deleted users
return res.status(200).json({
message: 'All users deleted successfully',
count: deletedUsers.affected,
});
} catch (error) {
res.status(500).json({ message: 'Internal server error' });
// Return an error message if something goes wrong
return res.status(500).json({ message: 'Failed to delete users' });
}
};
5 changes: 4 additions & 1 deletion src/database/models/userModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ export class UserModel {
@Column()
email: string;

@Column()
@Column({ default: null })
password: string;

@OneToOne(() => Role)
@JoinColumn()
userType: Role;

@Column({ default: false })
isVerified: boolean;

constructor(user: Partial<UserModel>) {
Object.assign(this, user);
}
Expand Down
4 changes: 2 additions & 2 deletions src/docs/roleDocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@
* properties:
* userId:
* type: number
* newRole:
* type: string
* newRoleId:
* type: number
* responses:
* '200':
* description: Update Successful
Expand Down
38 changes: 17 additions & 21 deletions src/emails/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,28 @@ async function sendEmail(emailType: EmailType, recipient: string, data: Data) {

// Send the Email

const domain = process.env.MAILERSEND_DOMAIN
const key = process.env.MAILERSEND_TOKEN
const domain = process.env.MAILGUN_DOMAIN
const key = process.env.MAILGUN_TOKEN as string
const body = {
'from': {
'email': `info@${domain}`,
},
'to': [
{
'email': recipient
}
],
'subject': 'Verification Email',
'html': html
from: `Dynamites Account Team <info@${domain}>`,
to: [recipient],
subject: 'Verification Email',
html: html
}
const mailersend = 'https://api.mailersend.com/v1/email'
const response = await axios.post(mailersend, body, {
const mailgunResponse = await axios.post(`https://api.mailgun.net/v3/${domain}/messages`, body, {
auth: {
username: 'api',
password: key
},
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${key}`
'Content-Type': 'multipart/form-data',
}
});
return response
})

return mailgunResponse
} catch (error) {
// console.error('Error sending email:', error);
throw new Error('Error sending email');
throw new Error(`Error sending email: ${error}`);
}
}

export default sendEmail;
export default sendEmail;
2 changes: 1 addition & 1 deletion src/emails/templates/confirm.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
border-radius: 4px;
font-size: 14px;
font-weight: bold;
color: #f7fafc;
color: #f7fafc !important;
background-color: #4c51bf;
text-align: center;
text-decoration: none;
Expand Down
Loading

0 comments on commit 32352bd

Please sign in to comment.