Skip to content

Commit

Permalink
feat(auth): fixing i18n translations at strategies and guards
Browse files Browse the repository at this point in the history
  • Loading branch information
leonardodimarchi committed Oct 29, 2023
1 parent 35bd004 commit 2f74295
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 81 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ This is where i'm going to place my ideas and things that i want to do or use in
- [x] Add base user roles such as Admin, Student and Instructor
- [x] Environment service
- [x] Add JWT Secret
- [ ] Translate Auth error messages
- [ ] Translate role guard error messages
- [x] Translate Auth error messages
- [x] Translate role guard error messages
- [x] SWC
101 changes: 51 additions & 50 deletions src/generated/i18n.generated.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,59 @@
/* DO NOT EDIT, file generated by nestjs-i18n */

import { Path } from "nestjs-i18n";
import { Path } from 'nestjs-i18n';
export type I18nTranslations = {
"auth": {
"errors": {
"user-not-found": string;
"incorrect-password": string;
};
"validations": {
"EMAIL_IS_DEFINED": string;
"EMAIL_IS_EMAIL": string;
"PASSWORD_IS_DEFINED": string;
"PASSWORD_IS_STRING": string;
};
auth: {
validations: {
EMAIL_IS_DEFINED: string;
EMAIL_IS_EMAIL: string;
PASSWORD_IS_DEFINED: string;
PASSWORD_IS_STRING: string;
};
"course": {
"validations": {
"TITLE_IS_DEFINED": string;
"TITLE_IS_STRING": string;
"TITLE_IS_NOT_EMPTY": string;
"DESCRIPTION_IS_DEFINED": string;
"DESCRIPTION_IS_STRING": string;
"DESCRIPTION_IS_NOT_EMPTY": string;
"PRICE_IS_DEFINED": string;
"PRICE_IS_NUMBER": string;
"PRICE_MIN": string;
"INSTRUCTOR_IS_DEFINED": string;
"INSTRUCTOR_IS_UUID": string;
"STUDENT_IS_DEFINED": string;
"STUDENT_IS_UUID": string;
};
"errors": {
"invalid-money": string;
"instructor-not-found": string;
"course-not-found": string;
"student-not-found": string;
"student-already-enrolled": string;
};
errors: {
'user-not-found': string;
'incorrect-password': string;
'no-authorization': string;
};
"user": {
"validations": {
"EMAIL_IS_EMAIL": string;
"NAME_IS_DEFINED": string;
"NAME_IS_STRING": string;
"NAME_IS_NOT_EMPTY": string;
"PASSWORD_IS_DEFINED": string;
"PASSWORD_IS_STRING": string;
"PASSWORD_IS_NOT_EMPTY": string;
};
"errors": {
"duplicated-email": string;
"invalid-email": string;
"invalid-name": string;
};
};
course: {
validations: {
TITLE_IS_DEFINED: string;
TITLE_IS_STRING: string;
TITLE_IS_NOT_EMPTY: string;
DESCRIPTION_IS_DEFINED: string;
DESCRIPTION_IS_STRING: string;
DESCRIPTION_IS_NOT_EMPTY: string;
PRICE_IS_DEFINED: string;
PRICE_IS_NUMBER: string;
PRICE_MIN: string;
INSTRUCTOR_IS_DEFINED: string;
INSTRUCTOR_IS_UUID: string;
STUDENT_IS_DEFINED: string;
STUDENT_IS_UUID: string;
};
errors: {
'invalid-money': string;
'instructor-not-found': string;
'course-not-found': string;
'student-not-found': string;
'student-already-enrolled': string;
};
};
user: {
validations: {
EMAIL_IS_EMAIL: string;
NAME_IS_DEFINED: string;
NAME_IS_STRING: string;
NAME_IS_NOT_EMPTY: string;
PASSWORD_IS_DEFINED: string;
PASSWORD_IS_STRING: string;
PASSWORD_IS_NOT_EMPTY: string;
};
errors: {
'duplicated-email': string;
'invalid-email': string;
'invalid-name': string;
};
};
};
export type I18nPath = Path<I18nTranslations>;
9 changes: 5 additions & 4 deletions src/i18n/en/auth.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"errors": {
"user-not-found": "Could not find a user with the provided e-mail.",
"incorrect-password": "The provided password is incorrect."
},
"validations": {
"EMAIL_IS_DEFINED": "You need to send the e-mail to authenticate.",
"EMAIL_IS_EMAIL": "You need to send a valid e-mail to authenticate.",
"PASSWORD_IS_DEFINED": "You need to send your password to authenticate.",
"PASSWORD_IS_STRING": "You need to send a valid password to authenticate."
},
"errors": {
"user-not-found": "Could not find a user with the provided e-mail.",
"incorrect-password": "The provided password is incorrect.",
"no-authorization": "You are not authorized to access this resource."
}
}
9 changes: 5 additions & 4 deletions src/i18n/pt-br/auth.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"errors": {
"user-not-found": "Não foi possível encontrar um usuário com o e-mail enviado.",
"incorrect-password": "Senha incorreta."
},
"validations": {
"EMAIL_IS_DEFINED": "É necessário enviar o e-mail para realizar a autenticação.",
"EMAIL_IS_EMAIL": "É necessário enviar um e-mail válido.",
"PASSWORD_IS_DEFINED": "É necessário enviar a senha para realizar a autenticação.",
"PASSWORD_IS_STRING": "É necessário enviar uma senha válida."
},
"errors": {
"user-not-found": "Não foi possível encontrar um usuário com o e-mail enviado.",
"incorrect-password": "Senha incorreta.",
"no-authorization": "Você não é autorizado para acessar este recurso."
}
}
2 changes: 1 addition & 1 deletion src/i18n/pt-br/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
"invalid-email": "O e-mail enviado está inválido.",
"invalid-name": "O nome enviado está inválido."
}
}
}
20 changes: 18 additions & 2 deletions src/modules/auth/infra/guards/auth-jwt.guard.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import { Injectable } from '@nestjs/common';
import { RequestUserEntity } from '@modules/auth/domain/entities/request-user.entity';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { I18nContext } from 'nestjs-i18n';
import { I18nTranslations } from 'src/generated/i18n.generated';

@Injectable()
export class AuthJwtGuard extends AuthGuard('jwt') {}
export class AuthJwtGuard extends AuthGuard('jwt') {
handleRequest<T = RequestUserEntity>(err: Error, user: T | false): T {
const i18n = I18nContext.current<I18nTranslations>();

if (err || !user) {
throw (
err ||
new UnauthorizedException(i18n?.t('auth.errors.no-authorization'))
);
}

return user;
}
}
13 changes: 6 additions & 7 deletions src/modules/auth/infra/strategies/auth-local.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,14 @@ import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { LoginUseCase } from '@modules/auth/domain/usecases/login.usecase';
import { UserNotFoundError } from '@modules/auth/domain/errors/user-not-found.error';
import { I18nService } from 'nestjs-i18n';
import { I18nContext } from 'nestjs-i18n';
import { I18nTranslations } from 'src/generated/i18n.generated';
import { IncorrectPasswordError } from '@modules/auth/domain/errors/incorrect-password.error';
import { UserEntity } from '@modules/user/domain/entities/user/user.entity';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly loginUseCase: LoginUseCase,
private readonly i18n: I18nService<I18nTranslations>,
) {
constructor(private readonly loginUseCase: LoginUseCase) {
super({
usernameField: 'email',
});
Expand All @@ -34,13 +31,15 @@ export class LocalStrategy extends PassportStrategy(Strategy) {
return result.value.user;
}

const i18n = I18nContext.current<I18nTranslations>();

if (result.value instanceof UserNotFoundError) {
throw new NotFoundException(this.i18n.t('auth.errors.user-not-found'));
throw new NotFoundException(i18n?.t('auth.errors.user-not-found'));
}

if (result.value instanceof IncorrectPasswordError) {
throw new UnauthorizedException(
this.i18n.t('auth.errors.incorrect-password'),
i18n?.t('auth.errors.incorrect-password'),
);
}

Expand Down
5 changes: 2 additions & 3 deletions src/modules/course/presenter/controllers/course.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class CourseController {
) {}

@Get()
@ProtectedTo(UserRole.ADMIN, UserRole.INSTRUCTOR, UserRole.STUDENT)
@ProtectedTo(UserRole.INSTRUCTOR)
@ApiOperation({ summary: 'Get all courses (paginated)' })
@ApiHeader({ name: 'Accept-Language', example: 'en', required: false })
@ApiQuery({
Expand Down Expand Up @@ -84,8 +84,7 @@ export class CourseController {
}

@Post()
@UseGuards(AuthJwtGuard)
@ApiBearerAuth()
@ProtectedTo(UserRole.ADMIN, UserRole.INSTRUCTOR, UserRole.STUDENT)
@ApiOperation({ summary: 'Creates a new course' })
@ApiHeader({ name: 'Accept-Language', example: 'en', required: false })
@ApiBody({
Expand Down
2 changes: 1 addition & 1 deletion src/shared/presenter/decorators/protected-to.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ export const ProtectedTo = (...roles: UserRole[]) =>
ApiForbiddenResponse({
type: ForbiddenException,
status: 403,
description: 'Você não possui autorização para acessar esse recurso.',
description: 'You are not authorized to access this resource.',
}),
);
14 changes: 7 additions & 7 deletions src/shared/presenter/guards/role.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import {
Injectable,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { I18nContext } from 'nestjs-i18n';
import { I18nTranslations } from 'src/generated/i18n.generated';

@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
constructor(private readonly reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<UserRole[]>('roles', context.getHandler());
Expand All @@ -22,16 +24,14 @@ export class RolesGuard implements CanActivate {
const request = context.switchToHttp().getRequest();
const user = request.user as RequestUserEntity;

const i18n = I18nContext.current<I18nTranslations>();

if (!user || !user.roles) {
throw new ForbiddenException(
'Você não possui autorização para acessar esse recurso.',
);
throw new ForbiddenException(i18n?.t('auth.errors.no-authorization'));
}

if (!roles.some((role) => user.roles.includes(role))) {
throw new ForbiddenException(
'Você não possui autorização para acessar esse recurso.',
);
throw new ForbiddenException(i18n?.t('auth.errors.no-authorization'));
}

return true;
Expand Down

0 comments on commit 2f74295

Please sign in to comment.