Skip to content

Commit

Permalink
feat(shared && courses): fixing protected to and using it at the cour…
Browse files Browse the repository at this point in the history
…ses controller
  • Loading branch information
leonardodimarchi committed Oct 17, 2023
1 parent 2070486 commit 1508b6e
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 26 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ This is where i'm going to place my ideas and things that i want to do or use in
- [ ] Environment service
- [ ] Add JWT Secret
- [ ] Translate Auth error messages
- [ ] Translate role guard error messages
- [x] SWC
4 changes: 4 additions & 0 deletions src/modules/auth/domain/entities/request-user.entity.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import { faker } from '@faker-js/faker';
import { UserRole } from '@modules/user/domain/entities/user/user-role.enum';
import { Right } from '@shared/helpers/either';
import { RequestUserEntity } from './request-user.entity';

describe('RequestUserEntity', () => {
it('should be able to instantiate', () => {
const id = faker.string.uuid();
const email = faker.internet.email();
const roles = [UserRole.ADMIN];

const entity = RequestUserEntity.create({
id,
email,
roles,
});

expect(entity).toBeInstanceOf(Right);

expect((entity.value as RequestUserEntity).id).toBe(id);
expect((entity.value as RequestUserEntity).email).toBe(email);
expect((entity.value as RequestUserEntity).roles).toEqual(roles);
});
});
9 changes: 8 additions & 1 deletion src/modules/auth/domain/entities/request-user.entity.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { UserRole } from '@modules/user/domain/entities/user/user-role.enum';
import { Either, Right } from '@shared/helpers/either';
import { UUID } from 'crypto';

export interface RequestUserEntityProps {
id: UUID;
email: string;
roles: UserRole[];
}

export type RequestUserEntityCreateProps = RequestUserEntityProps;
Expand All @@ -17,8 +19,9 @@ export class RequestUserEntity {
static create({
id,
email,
roles,
}: RequestUserEntityCreateProps): Either<Error, RequestUserEntity> {
return new Right(new RequestUserEntity({ id, email }));
return new Right(new RequestUserEntity({ id, email, roles }));
}

private props: RequestUserEntityProps;
Expand All @@ -30,4 +33,8 @@ export class RequestUserEntity {
get email(): string {
return this.props.email;
}

get roles(): UserRole[] {
return this.props.roles;
}
}
8 changes: 7 additions & 1 deletion src/modules/auth/infra/strategies/auth-jwt.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RequestUserEntity } from '@modules/auth/domain/entities/request-user.entity';
import { UserRole } from '@modules/user/domain/entities/user/user-role.enum';
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { UUID } from 'crypto';
Expand All @@ -14,10 +15,15 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
});
}

public validate(payload: { sub: UUID; email: string }): RequestUserEntity {
public validate(payload: {
sub: UUID;
email: string;
roles: UserRole[];
}): RequestUserEntity {
const entityResult = RequestUserEntity.create({
id: payload.sub,
email: payload.email,
roles: payload.roles,
});

return entityResult.value as RequestUserEntity;
Expand Down
1 change: 1 addition & 0 deletions src/modules/auth/presenter/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export class AuthController {
const accessToken = await this.jwtService.signAsync({
email: requestUser.email.value,
sub: requestUser.id,
roles: requestUser.roles,
});

return {
Expand Down
20 changes: 14 additions & 6 deletions src/modules/course/course.module.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { UserRepository } from '@modules/user/domain/repositories/user.repository';
import { UserDatabaseModule } from '@modules/user/infra/database/user-database.module';
import { Module } from '@nestjs/common';
import { CourseDatabaseModule } from './infra/database/course-database.module';
import { CourseController } from './presenter/controllers/course.controller';
import { CreateCourseUseCase } from './domain/usecases/create-course.usecase';
import { CourseRepository } from './domain/repositories/course.repository';
import { UserRepository } from '@modules/user/domain/repositories/user.repository';
import { EnrollStudentInCourseUseCase } from './domain/usecases/enroll-student-in-course.usecase';
import { EnrollmentRepository } from './domain/repositories/enrollment.repository';
import { UserDatabaseModule } from '@modules/user/infra/database/user-database.module';
import { CreateCourseUseCase } from './domain/usecases/create-course.usecase';
import { EnrollStudentInCourseUseCase } from './domain/usecases/enroll-student-in-course.usecase';
import { GetAllCoursesUseCase } from './domain/usecases/get-all-courses.usecase';
import { CourseDatabaseModule } from './infra/database/course-database.module';
import { CourseController } from './presenter/controllers/course.controller';

@Module({
imports: [UserDatabaseModule, CourseDatabaseModule],
controllers: [CourseController],
providers: [
{
provide: GetAllCoursesUseCase,
useFactory: (repository: CourseRepository) => {
return new GetAllCoursesUseCase(repository);
},
inject: [CourseRepository],
},
{
provide: CreateCourseUseCase,
useFactory: (
Expand Down
5 changes: 3 additions & 2 deletions src/modules/course/presenter/controllers/course.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { StudentNotFoundError } from '@modules/course/domain/errors/student-not-
import { CreateCourseUseCase } from '@modules/course/domain/usecases/create-course.usecase';
import { EnrollStudentInCourseUseCase } from '@modules/course/domain/usecases/enroll-student-in-course.usecase';
import { GetAllCoursesUseCase } from '@modules/course/domain/usecases/get-all-courses.usecase';
import { UserRole } from '@modules/user/domain/entities/user/user-role.enum';
import {
BadRequestException,
Body,
Expand All @@ -30,6 +31,7 @@ import {
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { ProtectedTo } from '@shared/presenter/decorators/protected-to.decorator';
import { PaginatedQueryParams } from '@shared/presenter/models/paginated-query-params';
import { PaginatedViewModel } from '@shared/presenter/models/paginated.view-model.';
import { I18n, I18nContext } from 'nestjs-i18n';
Expand All @@ -49,8 +51,7 @@ export class CourseController {
) {}

@Get()
@UseGuards(AuthJwtGuard)
@ApiBearerAuth()
@ProtectedTo(UserRole.ADMIN, UserRole.INSTRUCTOR, UserRole.STUDENT)
@ApiOperation({ summary: 'Get all courses (paginated)' })
@ApiHeader({ name: 'Accept-Language', example: 'en', required: false })
@ApiQuery({
Expand Down
16 changes: 8 additions & 8 deletions src/shared/presenter/decorators/protected-to.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
// protected-to.decorator.ts
import { AuthJwtGuard } from '@modules/auth/infra/guards/auth-jwt.guard';
import { UserRole } from '@modules/user/domain/entities/user/user-role.enum';
import {
applyDecorators,
ForbiddenException,
SetMetadata,
UseGuards,
} from '@nestjs/common';
import {
ApiBearerAuth,
ApiForbiddenResponse,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';
import { ApiBearerAuth, ApiForbiddenResponse } from '@nestjs/swagger';
import { RolesGuard } from '../guards/role.guard';

export const ProtectedTo = (...roles: UserRole[]) =>
applyDecorators(
SetMetadata('roles', roles),
UseGuards(RolesGuard),
UseGuards(AuthJwtGuard, RolesGuard),
ApiBearerAuth(),
ApiForbiddenResponse({ type: ForbiddenException }),
ApiForbiddenResponse({
type: ForbiddenException,
status: 403,
description: 'Você não possui autorização para acessar esse recurso.',
}),
);
16 changes: 8 additions & 8 deletions src/shared/presenter/guards/role.guard.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// roles.guard.ts
import { RequestUserEntity } from '@modules/auth/domain/entities/request-user.entity';
import { UserRole } from '@modules/user/domain/entities/user/user-role.enum';
import {
CanActivate,
Expand All @@ -16,24 +16,24 @@ export class RolesGuard implements CanActivate {
const roles = this.reflector.get<UserRole[]>('roles', context.getHandler());

if (!roles) {
return true; // No specific roles required, so access is granted.
return true;
}

const request = context.switchToHttp().getRequest();
const user = request.user; // Assuming you have user information in your request object.
const user = request.user as RequestUserEntity;

if (!user || !user.role) {
if (!user || !user.roles) {
throw new ForbiddenException(
'Você não possui autorização para acessar esse recurso.',
); // Access is denied if user or role is not defined.
);
}

if (!roles.includes(user.role)) {
if (!roles.some((role) => user.roles.includes(role))) {
throw new ForbiddenException(
'Você não possui autorização para acessar esse recurso.',
); // Access is denied if the user's role doesn't match the allowed roles.
);
}

return true; // Access is granted if the user's role matches the allowed roles.
return true;
}
}

0 comments on commit 1508b6e

Please sign in to comment.