feat(Roles): role guards et setup ownship(not implemented, yet)
This commit is contained in:
parent
e53f646659
commit
cc567b2b26
11
src/common/decorators/ownership.decorator.ts
Normal file
11
src/common/decorators/ownership.decorator.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { SetMetadata } from "@nestjs/common";
|
||||
|
||||
export const OWNER_KEY = 'ownership';
|
||||
export interface OwnershipMeta {
|
||||
serviceToken: string;
|
||||
idParam?: string;
|
||||
ownerField?: string;
|
||||
}
|
||||
|
||||
export const CheckOwnership = (meta: OwnershipMeta) =>
|
||||
SetMetadata(OWNER_KEY, meta);
|
||||
|
|
@ -2,5 +2,7 @@ import { SetMetadata } from '@nestjs/common';
|
|||
import { Roles } from '@prisma/client';
|
||||
|
||||
export const ROLES_KEY = 'roles';
|
||||
export const ROlesAllowed = (...roles: Roles[]) =>
|
||||
export const RolesAllowed = (...roles: Roles[]) =>
|
||||
SetMetadata(ROLES_KEY, roles);
|
||||
|
||||
|
||||
|
|
|
|||
51
src/common/guards/ownership.guard.ts
Normal file
51
src/common/guards/ownership.guard.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import {
|
||||
CanActivate,
|
||||
Injectable,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
} from "@nestjs/common";
|
||||
import { Reflector, ModuleRef } from "@nestjs/core";
|
||||
import { OWNER_KEY, OwnershipMeta } from "../decorators/ownership.decorator";
|
||||
import { Request } from 'express';
|
||||
|
||||
interface RequestWithUser extends Request {
|
||||
user: { id: string, role: string };
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class OwnershipGuard implements CanActivate {
|
||||
constructor(
|
||||
private reflector: Reflector,
|
||||
private moduleRef: ModuleRef,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean>{
|
||||
const meta = this.reflector.get<OwnershipMeta>(
|
||||
OWNER_KEY, context.getHandler(),
|
||||
);
|
||||
if (!meta)
|
||||
return true;
|
||||
|
||||
const request = context.switchToHttp().getRequest<RequestWithUser>();
|
||||
const user = request.user;
|
||||
const resourceId = request.params[meta.idParam || 'id'];
|
||||
|
||||
const service = this.moduleRef.get<any>(
|
||||
meta.serviceToken,
|
||||
{ strict: false },
|
||||
);
|
||||
const resource = await service.findOne(resourceId);
|
||||
const ownerField = meta.ownerField || 'ownerId';
|
||||
|
||||
if (user.role === 'ADMIN') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!resource || resource[ownerField] !== user.id) {
|
||||
throw new ForbiddenException(
|
||||
`You do not own the rights to this resource.`
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,8 +22,9 @@ export class RolesGuard implements CanActivate {
|
|||
ROLES_KEY,
|
||||
ctx.getHandler(),
|
||||
);
|
||||
if (!requiredRoles) {
|
||||
return true;
|
||||
//for "deny-by-default" when role is wrong or unavailable
|
||||
if (!requiredRoles || requiredRoles.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const request = ctx.switchToHttp().getRequest<RequestWithUser>();
|
||||
const user = request.user;
|
||||
|
|
|
|||
17
src/main.ts
17
src/main.ts
|
|
@ -1,9 +1,24 @@
|
|||
import 'reflect-metadata';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ModuleRef, NestFactory, Reflector } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { JwtAuthGuard } from './modules/authentication/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from './common/guards/roles.guard';
|
||||
import { OwnershipGuard } from './common/guards/ownership.guard';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
//setup Reflector for Roles()
|
||||
const reflector = app.get(Reflector);
|
||||
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({ whitelist: true, transform: true}));
|
||||
app.useGlobalGuards(
|
||||
new JwtAuthGuard(reflector), //Authentification JWT
|
||||
new RolesGuard(reflector), //deny-by-default and Role-based Access Control
|
||||
new OwnershipGuard(reflector, app.get(ModuleRef)), //Global use of OwnershipGuard, not implemented yet
|
||||
);
|
||||
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
bootstrap();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,18 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
|
||||
canActivate(context: ExecutionContext) {
|
||||
return super.canActivate(context);
|
||||
}
|
||||
|
||||
handleRequest(err, user, info) {
|
||||
if(err || !user) {
|
||||
throw err || new UnauthorizedException();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,33 @@ import { CustomersService } from '../services/customers.service';
|
|||
import { Customers, Employees } from '@prisma/client';
|
||||
import { CreateCustomerDto } from '../dtos/create-customer';
|
||||
import { UpdateCustomerDto } from '../dtos/update-customer';
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
|
||||
@Controller('customers')
|
||||
export class CustomersController {
|
||||
constructor(private readonly customersService: CustomersService) {}
|
||||
|
||||
@Post()
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.SUPERVISOR)
|
||||
create(@Body() dto: CreateCustomerDto): Promise<Customers> {
|
||||
return this.customersService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
findAll(): Promise<Customers[]> {
|
||||
return this.customersService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
findOne(@Param('id', ParseIntPipe) id: number): Promise<Customers> {
|
||||
return this.customersService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE,RoleEnum.SUPERVISOR)
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() dto: UpdateCustomerDto,
|
||||
|
|
@ -32,6 +38,7 @@ export class CustomersController {
|
|||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.SUPERVISOR)
|
||||
remove(@Param('id', ParseIntPipe) id: number): Promise<Customers>{
|
||||
return this.customersService.remove(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,26 +13,34 @@ import { EmployeesService } from '../services/employees.service';
|
|||
import { CreateEmployeeDto } from '../dtos/create-employee.dto';
|
||||
import { UpdateEmployeeDto } from '../dtos/update-employee.dto';
|
||||
|
||||
//decorators and roles imports
|
||||
import { RolesAllowed } from '../../../common/decorators/roles.decorators';
|
||||
import { Roles as RoleEnum } from 'prisma/prisma-client';
|
||||
|
||||
@Controller('employees')
|
||||
export class EmployeesController {
|
||||
constructor(private readonly employeesService: EmployeesService) {}
|
||||
|
||||
@Post()
|
||||
@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
create(@Body() dto: CreateEmployeeDto): Promise<Employees> {
|
||||
return this.employeesService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ACCOUNTING)
|
||||
findAll(): Promise<Employees[]> {
|
||||
return this.employeesService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR,RoleEnum.ACCOUNTING )
|
||||
findOne(@Param('id', ParseIntPipe) id: number): Promise<Employees> {
|
||||
return this.employeesService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR,RoleEnum.ACCOUNTING )
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() dto: UpdateEmployeeDto,
|
||||
|
|
@ -41,6 +49,7 @@ export class EmployeesController {
|
|||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR )
|
||||
remove(@Param('id', ParseIntPipe) id: number): Promise<Employees> {
|
||||
return this.employeesService.remove(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,33 @@ import { LeaveRequestsService } from "../services/leave-request.service";
|
|||
import { CreateLeaveRequestsDto } from "../dtos/create-leave-requests.dto";
|
||||
import { LeaveRequests } from "@prisma/client";
|
||||
import { UpdateLeaveRequestsDto } from "../dtos/update-leave-requests.dto";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
|
||||
@Controller('leave-requests')
|
||||
export class LeaveRequestController {
|
||||
constructor(private readonly leaveRequetsService: LeaveRequestsService){}
|
||||
|
||||
@Post()
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
create(@Body() dto: CreateLeaveRequestsDto): Promise<LeaveRequests> {
|
||||
return this. leaveRequetsService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
findAll(): Promise<LeaveRequests[]> {
|
||||
return this.leaveRequetsService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
findOne(@Param('id', ParseIntPipe) id: number): Promise<LeaveRequests> {
|
||||
return this.leaveRequetsService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() dto: UpdateLeaveRequestsDto,
|
||||
|
|
@ -32,6 +38,7 @@ export class LeaveRequestController {
|
|||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
remove(@Param('id', ParseIntPipe) id: number): Promise<LeaveRequests> {
|
||||
return this.leaveRequetsService.remove(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,32 +3,39 @@ import { OauthAccessTokensService } from '../services/oauth-access-tokens.servic
|
|||
import { CreateOauthAccessTokenDto } from '../dtos/create-oauth-access-token.dto';
|
||||
import { OAuthAccessTokens } from '@prisma/client';
|
||||
import { UpdateOauthAccessTokenDto } from '../dtos/update-oauth-access-token.dto';
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
|
||||
@Controller('oauth-access-tokens')
|
||||
export class OauthAccessTokensController {
|
||||
constructor(private readonly oauthAccessTokensService: OauthAccessTokensService){}
|
||||
|
||||
@Post()
|
||||
@RolesAllowed(RoleEnum.ADMIN)
|
||||
create(@Body()dto: CreateOauthAccessTokenDto): Promise<OAuthAccessTokens> {
|
||||
return this.oauthAccessTokensService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@RolesAllowed(RoleEnum.ADMIN)
|
||||
findAll(): Promise<OAuthAccessTokens[]> {
|
||||
return this.oauthAccessTokensService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@RolesAllowed(RoleEnum.ADMIN)
|
||||
findOne(@Param('id') id: string): Promise<OAuthAccessTokens> {
|
||||
return this.oauthAccessTokensService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@RolesAllowed(RoleEnum.ADMIN)
|
||||
update(@Param('id') id: string, @Body() dto: UpdateOauthAccessTokenDto): Promise<OAuthAccessTokens> {
|
||||
return this.oauthAccessTokensService.update(id,dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RolesAllowed(RoleEnum.ADMIN)
|
||||
remove(@Param('id') id: string): Promise<OAuthAccessTokens> {
|
||||
return this.oauthAccessTokensService.remove(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,33 +3,40 @@ import { ShiftCodesService } from "../services/shift-codes.service";
|
|||
import { CreateShiftCodesDto } from "../dtos/create-shift-codes.dto";
|
||||
import { ShiftCodes } from "@prisma/client";
|
||||
import { UpdateShiftCodesDto } from "../dtos/update-shift-codes.dto";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
|
||||
@Controller()
|
||||
export class ShiftCodesController {
|
||||
constructor(private readonly shiftCodesService: ShiftCodesService) {}
|
||||
|
||||
@Post()
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR)
|
||||
create(@Body()dto: CreateShiftCodesDto): Promise<ShiftCodes> {
|
||||
return this.shiftCodesService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR)
|
||||
findAll(): Promise<ShiftCodes[]> {
|
||||
return this.shiftCodesService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR)
|
||||
findOne(@Param('id', ParseIntPipe) id: number): Promise<ShiftCodes> {
|
||||
return this.shiftCodesService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR)
|
||||
update(@Param('id', ParseIntPipe) id: number,
|
||||
@Body() dto: UpdateShiftCodesDto): Promise<ShiftCodes> {
|
||||
return this.shiftCodesService.update(id,dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR)
|
||||
remove(@Param('id', ParseIntPipe)id: number): Promise<ShiftCodes> {
|
||||
return this.shiftCodesService.remove(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { ShiftsService } from "../services/shifts.service";
|
|||
import { Shifts } from "@prisma/client";
|
||||
import { CreateShiftDto } from "../dtos/create-shifts.dto";
|
||||
import { UpdateShiftsDto } from "../dtos/update-shifts.dto";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
|
||||
@Controller('shifts')
|
||||
export class ShiftsController {
|
||||
|
|
@ -10,21 +12,25 @@ export class ShiftsController {
|
|||
|
||||
|
||||
@Post()
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
create(@Body() dto: CreateShiftDto): Promise<Shifts> {
|
||||
return this.shiftsService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
findAll(): Promise<Shifts[]> {
|
||||
return this.shiftsService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
findOne(@Param('id', ParseIntPipe) id: number): Promise<Shifts> {
|
||||
return this.shiftsService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() dto: UpdateShiftsDto,
|
||||
|
|
@ -33,6 +39,7 @@ export class ShiftsController {
|
|||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
remove(@Param('id', ParseIntPipe) id: number): Promise<Shifts> {
|
||||
return this.shiftsService.remove(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,33 @@ import { TimesheetsService } from '../services/timesheets.service';
|
|||
import { CreateTimesheetDto } from '../dtos/create-timesheet.dto';
|
||||
import { Timesheets } from '@prisma/client';
|
||||
import { UpdateTimesheetDto } from '../dtos/update-timesheet.dto';
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
|
||||
@Controller('timesheets')
|
||||
export class TimesheetsController {
|
||||
constructor(private readonly timesheetsService: TimesheetsService) {}
|
||||
|
||||
@Post()
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
create(@Body() dto: CreateTimesheetDto): Promise<Timesheets> {
|
||||
return this.timesheetsService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
findAll(): Promise<Timesheets[]> {
|
||||
return this.timesheetsService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
findOne(@Param('id', ParseIntPipe) id: number): Promise<Timesheets> {
|
||||
return this.timesheetsService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id:number,
|
||||
@Body() dto: UpdateTimesheetDto,
|
||||
|
|
@ -32,6 +38,7 @@ export class TimesheetsController {
|
|||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
remove(@Param('id', ParseIntPipe) id: number): Promise<Timesheets> {
|
||||
return this.timesheetsService.remove(id);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user