-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(rbac): implement role-based access control
- assign role on user registration - implement middleware to check role on protected routes - write tests for roleCheck middleware [Finishes #45]
- Loading branch information
Showing
11 changed files
with
184 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { Response, NextFunction, Request } from 'express'; | ||
import { User } from '../entities/User'; | ||
import { hasRole } from '../middlewares'; | ||
import { responseError } from '../utils/response.utils'; | ||
import { dbConnection } from '../startups/dbConnection'; | ||
import { v4 as uuid } from 'uuid'; | ||
|
||
let reqMock: Partial<Request>; | ||
let resMock: Partial<Response>; | ||
let nextMock: NextFunction; | ||
|
||
const userId = uuid(); | ||
|
||
beforeAll(async () => { | ||
// Connect to the test database | ||
const connection = await dbConnection(); | ||
|
||
const userRepository = connection?.getRepository(User); | ||
|
||
const user = new User(); | ||
|
||
user.id = userId; | ||
user.firstName = 'John2'; | ||
user.lastName = 'Doe'; | ||
user.email = '[email protected]'; | ||
user.password = 'password'; | ||
user.gender = 'Male'; | ||
user.phoneNumber = '1234'; | ||
user.userType = 'Buyer'; | ||
user.photoUrl = 'https://example.com/photo.jpg'; | ||
|
||
await userRepository?.save(user); | ||
}); | ||
|
||
afterAll(async () => { | ||
|
||
}); | ||
|
||
describe('hasRole MiddleWare Test', () => { | ||
|
||
beforeEach(() => { | ||
reqMock = {}; | ||
resMock = { | ||
status: jest.fn().mockReturnThis(), | ||
json: jest.fn() | ||
}; | ||
nextMock = jest.fn(); | ||
}); | ||
|
||
it('should return 401, if user is not authentication', async () => { | ||
await hasRole('ADMIN')(reqMock as Request, resMock as Response, nextMock); | ||
expect(responseError).toHaveBeenCalled; | ||
expect(resMock.status).toHaveBeenCalledWith(401); | ||
}); | ||
|
||
it('should return 401 if user is not found', async () => { | ||
reqMock = { user: { id: uuid() } }; | ||
|
||
await hasRole('ADMIN')(reqMock as Request, resMock as Response, nextMock); | ||
|
||
expect(responseError).toHaveBeenCalled; | ||
expect(resMock.status).toHaveBeenCalledWith(401); | ||
}); | ||
|
||
it('should return 403 if user does not have required role', async () => { | ||
reqMock = { user: { id: userId } }; | ||
|
||
await hasRole('ADMIN')(reqMock as Request, resMock as Response, nextMock); | ||
|
||
expect(responseError).toHaveBeenCalled; | ||
expect(resMock.status).toHaveBeenCalledWith(403); | ||
}); | ||
|
||
it('should call next() if user has required role', async () => { | ||
reqMock = { user: { id: userId } }; | ||
|
||
await hasRole('BUYER')(reqMock as Request, resMock as Response, nextMock); | ||
|
||
expect(nextMock).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should return 400 if user id is of invalid format', async () => { | ||
reqMock = { user: { id: 'sample userId' } }; | ||
|
||
await hasRole('BUYER')(reqMock as Request, resMock as Response, nextMock); | ||
|
||
expect(responseError).toHaveBeenCalled; | ||
expect(resMock.status).toHaveBeenCalledWith(400); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,10 @@ | ||
import { Request, Response } from 'express'; | ||
import { User } from '../entities/User'; | ||
import bcrypt from 'bcrypt'; | ||
import { getRepository } from 'typeorm'; | ||
import { responseError, responseServerError, responseSuccess } from '../utils/response.utils'; | ||
import { validate } from 'class-validator'; | ||
import { userVerificationService, userRegistrationService } from '../services'; | ||
|
||
export const userRegistration = async (req: Request, res: Response) => { | ||
Check warning on line 4 in src/controllers/authController.ts
|
||
await userRegistrationService(req, res); | ||
} | ||
}; | ||
export const userVerification = async (req: Request, res: Response) => { | ||
Check warning on line 7 in src/controllers/authController.ts
|
||
await userVerificationService(req, res); | ||
} | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
// export all middlewares | ||
|
||
export * from './roleCheck'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { NextFunction, Request, Response } from "express"; | ||
import { User, UserInterface } from "../entities/User"; | ||
import { getRepository } from "typeorm"; | ||
import { responseError } from "../utils/response.utils"; | ||
|
||
|
||
/** | ||
* Middleware to check user role before granting access to protectered routes. | ||
* @param {("ADMIN" | "VENDOR" | "BUYER")} role - The role required to access the route. | ||
* @returns {function} Helper function for making responses. | ||
*/ | ||
|
||
declare module 'express' { | ||
interface Request { | ||
user?: Partial<UserInterface>; | ||
} | ||
} | ||
|
||
export const hasRole = (role: "ADMIN" | "VENDOR" | "BUYER") => async (req: Request, res: Response, next: NextFunction) => { | ||
try { | ||
if (!req.user) { | ||
return responseError(res, 401, 'Authentication required'); | ||
} | ||
|
||
const userId = req.user.id; | ||
|
||
const userRepository = getRepository(User); | ||
|
||
const user = await userRepository.findOne({ where: { id: userId } }); | ||
if (!user) { | ||
return responseError(res, 401, 'User not found'); | ||
} | ||
if (user.role !== role) { | ||
return responseError(res, 403, 'Unauthorized action'); | ||
} | ||
|
||
next(); | ||
} catch (error) { | ||
responseError(res, 400, (error as Error).message); | ||
} | ||
}; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export const roles = { | ||
admin: "ADMIN", | ||
vendor: "VENDOR", | ||
buyer: "BUYER" | ||
}; |