Skip to content

Commit

Permalink
feat(api/auth): improve methods
Browse files Browse the repository at this point in the history
  • Loading branch information
NedcloarBR committed Nov 10, 2024
1 parent eafdf1e commit 93cf4b9
Show file tree
Hide file tree
Showing 21 changed files with 133 additions and 232 deletions.
29 changes: 15 additions & 14 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
{
"cSpell.words": [
"Adapter",
"biomejs",
"cnpj",
"fastify",
"Fastify",
"interceptors",
"Interceptors",
"lcov",
"ndix",
"NDIX",
"Nest",
"nestjs",
"ultrasecretpassword"
],
"Adapter",
"biomejs",
"cnpj",
"DTOs",
"fastify",
"Fastify",
"interceptors",
"Interceptors",
"lcov",
"ndix",
"NDIX",
"Nest",
"nestjs",
"ultrasecretpassword"
],
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true,
Expand Down
Binary file modified apps/api/.yarn/install-state.gz
Binary file not shown.
4 changes: 2 additions & 2 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"test:e2e": "vitest --config vitest.config-e2e.ts"
},
"dependencies": {
"@fastify/helmet": "^12.0.1",
"@fastify/helmet": "^11.1.1",
"@fastify/jwt": "^9.0.1",
"@fastify/static": "^7.0.4",
"@nestjs/common": "^10.3.10",
Expand All @@ -44,7 +44,7 @@
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"fastify": "^5.1.0",
"fastify": "^4.28.1",
"nestjs-prisma": "^0.23.0",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { ServeStaticModule } from "@nestjs/serve-static";
import { CustomPrismaModule } from "nestjs-prisma";
import { TransactionModule, UserModule } from "./modules";
import { AuthModule, TransactionModule, UserModule } from "./modules";
import { extendedPrismaClient } from "./modules/database/ExtendedPrismaClient";
import { Services } from "./types/constants";

Expand All @@ -24,6 +24,7 @@ import { Services } from "./types/constants";
},
isGlobal: true,
}),
AuthModule,
UserModule,
TransactionModule,
],
Expand Down
1 change: 0 additions & 1 deletion apps/api/src/common/errors/Unauthorized.error.ts

This file was deleted.

7 changes: 7 additions & 0 deletions apps/api/src/common/errors/UserNotFound.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NotFoundException } from "@nestjs/common";

export class UserNotFoundError extends NotFoundException {
public constructor(message?: string) {
super(message || "No User found with this credentials");
}
}
2 changes: 1 addition & 1 deletion apps/api/src/common/errors/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from "./Unauthorized.error";
export * from "./UserNotFound.error";
27 changes: 27 additions & 0 deletions apps/api/src/common/filters/UserNotFoundError.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
type ArgumentsHost,
Catch,
type ExceptionFilter,
} from "@nestjs/common";
import type { FastifyReply } from "fastify";
import { UserNotFoundError } from "../errors";

@Catch(UserNotFoundError)
export class UserNotFoundErrorFilter<T extends UserNotFoundError>
implements ExceptionFilter
{
public catch(exception: T, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<FastifyReply>();
const status = exception.getStatus();
const exceptionResponse = exception.getResponse();
const error =
typeof response === "string"
? { message: exceptionResponse }
: (exceptionResponse as object);

response
.status(status)
.send({ ...error, timestamp: new Date().toISOString() });
}
}
1 change: 1 addition & 0 deletions apps/api/src/common/filters/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./HttpException.filter";
export * from "./UserNotFoundError.filter";
3 changes: 1 addition & 2 deletions apps/api/src/common/interceptors/Unauthorized.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
UnauthorizedException,
} from "@nestjs/common";
import { type Observable, catchError } from "rxjs";
import { UnauthorizedError } from "../errors";

@Injectable()
export class UnauthorizedInterceptor implements NestInterceptor {
Expand All @@ -16,7 +15,7 @@ export class UnauthorizedInterceptor implements NestInterceptor {
): Observable<unknown> | Promise<Observable<unknown>> {
return next.handle().pipe(
catchError((error) => {
if (error instanceof UnauthorizedError) {
if (error instanceof UnauthorizedException) {
throw new UnauthorizedException(error.message);
}
throw error;
Expand Down
9 changes: 2 additions & 7 deletions apps/api/src/lib/fastify.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import fastifyHelmet from "@fastify/helmet";
import { ConfigService } from "@nestjs/config";
import { NestFastifyApplication } from "@nestjs/platform-fastify";

export function configureFastify(
app: NestFastifyApplication,
configService: ConfigService,
): void {
export function configureFastify(app: NestFastifyApplication): void {
const fastifyInstance = app.getHttpAdapter().getInstance();
fastifyInstance
.addHook("onRequest", async (req, res) => {
Expand All @@ -19,5 +14,5 @@ export function configureFastify(
this.send("");
});

app.use(fastifyHelmet);
app.register(require("@fastify/helmet"));
}
2 changes: 1 addition & 1 deletion apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async function bootstrap(): Promise<void> {
const PORT = configService.getOrThrow<number>("PORT");

Configure.App(app);
Configure.Fastify(app, configService);
Configure.Fastify(app);
Configure.Globals(app);
Configure.Swagger(app);

Expand Down
42 changes: 9 additions & 33 deletions apps/api/src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,21 @@
import {
Controller,
Get,
HttpCode,
HttpStatus,
Req,
Res,
UseGuards,
} from "@nestjs/common";
// biome-ignore lint/style/useImportType: <Cannot useImportType in classes used in Injection>
import { ConfigService } from "@nestjs/config";
import type { FastifyReply, FastifyRequest } from "fastify";
import { Body, Controller, Get, Post, UseGuards } from "@nestjs/common";
import { AuthUser } from "src/common/decorators/AuthUser.decorator";
import { JwtAuthGuard } from "src/common/guards/jwt.guard";
import type { UserEntity } from "../user";
// biome-ignore lint/style/useImportType: <Cannot useImportType on DTOs>
import { UserLoginDTO } from "../user/user.dto";
// biome-ignore lint/style/useImportType: <Cannot useImportType in classes used in Injection>
import { AuthService } from "./auth.service";

@Controller("auth")
export class AuthController {
public constructor(
private readonly authService: AuthService,
private readonly configService: ConfigService,
) {}

@Get("login")
@UseGuards(JwtAuthGuard)
public login(): void {}
public constructor(private readonly authService: AuthService) {}

@Get("redirect")
@HttpCode(HttpStatus.CREATED)
@UseGuards(JwtAuthGuard)
// @Redirect(
// isInProduction
// ? "http://localhost:4401/@me"
// : "http://localhost:4400/auth/status",
// )
public async redirect(
@Req() req: FastifyRequest,
@Res() res: FastifyReply,
): Promise<void> {
const jwt = await this.authService.login({ id: req.user.publicId });
@Post("login")
public async login(@Body() userLoginDTO: UserLoginDTO): Promise<string> {
const user = await this.authService.validateUser(userLoginDTO);
const jwt = await this.authService.login({ publicId: user.publicId });
return jwt;
}

@Get("status")
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Module } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
import { UserModule } from "../user/user.module";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { JwtStrategy } from "./jwt.strategy";
Expand All @@ -20,6 +21,7 @@ import { JwtStrategy } from "./jwt.strategy";
PassportModule.register({
session: true,
}),
UserModule,
],
controllers: [AuthController],
providers: [JwtStrategy, AuthService],
Expand Down
20 changes: 14 additions & 6 deletions apps/api/src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import { Injectable } from "@nestjs/common";
import { Inject, Injectable } from "@nestjs/common";
// biome-ignore lint/style/useImportType: <Cannot useImportType in classes used in Injection>
import { JwtService } from "@nestjs/jwt";
import { UserNotFoundError } from "src/common/errors";
import type { JwtPayload } from "src/types";
import { Services } from "src/types/constants";
import { PasswordUtils } from "src/utils/password";
// biome-ignore lint/style/useImportType: <Cannot useImportType in classes used in Injection>
import { type UserEntity, UserService } from "../user";
import type { UserDTO } from "../user/user.dto";
// biome-ignore lint/style/useImportType: <Cannot useImportType in DTOs>
import { UserLoginDTO } from "../user/user.dto";

@Injectable()
export class AuthService {
public constructor(
private readonly userService: UserService,
@Inject(Services.User) private readonly userService: UserService,
private readonly jwtService: JwtService,
) {}

public async validateUser(details: UserDTO): Promise<UserEntity> {
public async validateUser(details: UserLoginDTO): Promise<UserEntity> {
const user = await this.userService.findByDocument(details.document);
return user || (await this.userService.create(details));
if (await PasswordUtils.compare(details.password, user.password)) {
return user;
}

throw new UserNotFoundError();
}

public async find(payload: JwtPayload) {
const user = await this.userService.findByPublicId(payload.id);
const user = await this.userService.findByPublicId(payload.publicId);
return user;
}

Expand Down
9 changes: 4 additions & 5 deletions apps/api/src/modules/transaction/transaction.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Inject, Injectable } from "@nestjs/common";
import { UnauthorizedError } from "src/common/errors";
import { Inject, Injectable, UnauthorizedException } from "@nestjs/common";
import { Repositories, Services } from "src/types/constants";
import type {
ITransactionRepository,
Expand All @@ -24,15 +23,15 @@ export class TransactionService implements ITransactionService {
);

if (sender.money < data.value) {
throw new UnauthorizedError("Sender não tem dinheiro o suficiente");
throw new UnauthorizedException("Sender não tem dinheiro o suficiente");
}

if (sender.userType === "CNPJ") {
throw new UnauthorizedError("CNPJ não pode enviar");
throw new UnauthorizedException("CNPJ não pode enviar");
}

if (receiver.document === sender.document) {
throw new UnauthorizedError("Não é possível enviar para si mesmo");
throw new UnauthorizedException("Não é possível enviar para si mesmo");
}

const transaction = await this.transactionRepository.create(data);
Expand Down
7 changes: 7 additions & 0 deletions apps/api/src/modules/user/user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import {
IsEmail,
IsEnum,
IsNotEmpty,
IsOptional,
IsString,
Length,
ValidateIf,
} from "class-validator";
import { IsCPFOrCNPJ } from "src/common/decorators/IsCPFOrCNPJ.decorator";

Expand Down Expand Up @@ -78,3 +80,8 @@ export class UserPartialDTO extends PartialType(UserDTO) {}
export class UserDocumentDTO extends PickType(UserDTO, ["document"] as const) {}

export class UserEmailDTO extends PickType(UserDTO, ["email"] as const) {}

export class UserLoginDTO extends PickType(UserDTO, [
"password",
"document",
] as const) {}
9 changes: 7 additions & 2 deletions apps/api/src/modules/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Inject, Injectable } from "@nestjs/common";
import { genSalt, hash } from "bcrypt";
import { Repositories } from "src/types/constants";
import { PasswordUtils } from "src/utils/password";
import { type IUserRepository, type IUserService, type UserEntity } from ".";
import { UserDTO } from "./user.dto";

Expand All @@ -11,8 +12,12 @@ export class UserService implements IUserService {
) {}

public async create(data: UserDTO): Promise<UserEntity> {
const hashedData = await this.hashData(data);
return await this.userRepository.create(hashedData);
const { password, ...rest } = data;
const hashedPassword = await PasswordUtils.hash(password);
return await this.userRepository.create({
...rest,
password: hashedPassword,
});
}

public async findByPublicId(publicId: string): Promise<UserEntity> {
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ declare module "fastify" {
}

export interface JwtPayload {
id: UserEntity["publicId"];
publicId: UserEntity["publicId"];
}
15 changes: 15 additions & 0 deletions apps/api/src/utils/password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { compare, genSalt, hash } from "bcrypt";

// biome-ignore lint/complexity/noStaticOnlyClass: <Utils class>
export class PasswordUtils {
public static async hash(password: string): Promise<string> {
return await hash(password, await genSalt());
}

public static async compare(
password: string,
hash: string,
): Promise<boolean> {
return await compare(password, hash);
}
}
Loading

0 comments on commit 93cf4b9

Please sign in to comment.