diff --git a/package-lock.json b/package-lock.json index 3f4d42f..50adad5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@nestjs/common": "^11.0.12", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.12", + "@nestjs/cqrs": "^11.0.3", "@nestjs/jwt": "^11.0.0", "@nestjs/mapped-types": "^1.0.0", "@nestjs/microservices": "^11.0.12", @@ -1905,6 +1906,7 @@ "version": "11.0.12", "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.0.12.tgz", "integrity": "sha512-6PXxmDe2iYmb57xacnxzpW1NAxRZ7Gf+acMT7/hmRB/4KpZiFU/cNvLWwgbM2BL5QSzQulOwY6ny5bbKnPpB+A==", + "license": "MIT", "dependencies": { "iterare": "1.2.1", "tslib": "2.8.1", @@ -1984,6 +1986,18 @@ } } }, + "node_modules/@nestjs/cqrs": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/cqrs/-/cqrs-11.0.3.tgz", + "integrity": "sha512-2ezBftiXqVfNTzjCrmhazohYhIQzgm8rvM0aKndv73IOOBcVlNuNiQ3HHiHdd4c2w/3MOQDtsGbQHgZUuW6DPw==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0", + "rxjs": "^7.2.0" + } + }, "node_modules/@nestjs/jwt": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.0.tgz", diff --git a/package.json b/package.json index c8c2d3d..73ac369 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@nestjs/common": "^11.0.12", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.12", + "@nestjs/cqrs": "^11.0.3", "@nestjs/jwt": "^11.0.0", "@nestjs/mapped-types": "^1.0.0", "@nestjs/microservices": "^11.0.12", diff --git a/src/auth/auth/auth.controller.ts b/src/auth/auth/auth.controller.ts index d874ba8..3f252fb 100644 --- a/src/auth/auth/auth.controller.ts +++ b/src/auth/auth/auth.controller.ts @@ -5,28 +5,26 @@ import { HttpStatus, Post, } from '@nestjs/common'; -import { AuthService } from './auth.service'; -import { UsersService } from '../users/users.service'; -import { LoginDto } from '../auth/dto/login.dto'; -import { ResultModel } from 'src/core/models/result.model'; -import { ApiBody, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger'; -import { ApiTags } from '@nestjs/swagger'; -import { ApiResponse } from '@nestjs/swagger'; -import { ApiOperation } from '@nestjs/swagger'; -import { ApiBearerAuth } from '@nestjs/swagger'; -import { ApiCreatedResponse } from '@nestjs/swagger'; -import { ApiBadRequestResponse } from '@nestjs/swagger'; -import { ApiNotFoundResponse } from '@nestjs/swagger'; -import { ApiInternalServerErrorResponse } from '@nestjs/swagger'; -import { ApiForbiddenResponse } from '@nestjs/swagger'; +import { CommandBus } from '@nestjs/cqrs'; +import { CqrsModule } from '@nestjs/cqrs'; +import { AuthenticateUserCommand } from './authenticate-user.command'; import { LoginResponseDto } from './dto/LoginResponseDto'; - +import { LoginDto } from './dto/login.dto'; +import { ResultModel } from 'src/core/models/result.model'; +import { AuthService } from './auth.service'; +import { + ApiTags, + ApiOperation, + ApiBody, + ApiOkResponse, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; @ApiTags('Auth') @Controller('api/v1/auth') export class AuthController { constructor( - private readonly usersService: UsersService, + private readonly commandBus: CommandBus, private readonly authService: AuthService, ) {} @@ -38,11 +36,9 @@ export class AuthController { type: LoginResponseDto, }) @ApiUnauthorizedResponse({ description: 'Usuário ou senha inválidos' }) - async login(@Body() model: LoginDto): Promise { - const result = await this.usersService.authenticate({ - userName: model.username, - password: model.password, - }); + async login(@Body() dto: LoginDto): Promise { + const command = new AuthenticateUserCommand(dto.username, dto.password); + const result = await this.commandBus.execute(command); if (!result.success) { throw new HttpException( @@ -52,7 +48,6 @@ export class AuthController { } const user = result.data; - const token = await this.authService.createToken( user.id, user.sellerId, @@ -71,4 +66,4 @@ export class AuthController { token: token, }; } -} \ No newline at end of file +} diff --git a/src/auth/auth/auth.module.ts b/src/auth/auth/auth.module.ts index 7a98a9f..46cdc9a 100644 --- a/src/auth/auth/auth.module.ts +++ b/src/auth/auth/auth.module.ts @@ -6,20 +6,30 @@ import { JwtStrategy } from '../strategies/jwt-strategy'; import { RedisModule } from 'src/core/configs/cache/redis.module'; import { UsersModule } from '../users/users.module'; import { AuthController } from './auth.controller'; +import { CqrsModule } from '@nestjs/cqrs'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { AuthenticateUserHandler } from './authenticate-user.service'; @Module({ imports: [ - JwtModule.register({ - secret: '4557C0D7-DFB0-40DA-BF83-91A75103F7A9', - signOptions: { expiresIn: '8h' }, + CqrsModule, + ConfigModule, + JwtModule.registerAsync({ + imports: [ConfigModule], + useFactory: async (configService: ConfigService) => ({ + secret: configService.get('JWT_SECRET'), + signOptions: { + expiresIn: configService.get('JWT_EXPIRES_IN'), + }, + }), + inject: [ConfigService], }), PassportModule.register({ defaultStrategy: 'jwt' }), RedisModule, UsersModule, - ], controllers: [AuthController], - providers: [AuthService, JwtStrategy], + providers: [AuthService, JwtStrategy, AuthenticateUserHandler], exports: [AuthService], }) export class AuthModule {} diff --git a/src/auth/auth/auth.service.ts b/src/auth/auth/auth.service.ts index 8fd5f2c..b444074 100644 --- a/src/auth/auth/auth.service.ts +++ b/src/auth/auth/auth.service.ts @@ -1,12 +1,8 @@ -/* eslint-disable prettier/prettier */ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { Injectable } from '@nestjs/common'; import { JwtService, JwtSignOptions } from '@nestjs/jwt'; import { UsersService } from '../users/users.service'; import { JwtPayload } from '../models/jwt-payload.model'; -import Redis from 'ioredis'; - - +import { UserRepository } from '../users/UserRepository'; @Injectable() @@ -14,6 +10,7 @@ export class AuthService { constructor( private readonly usersService: UsersService, private readonly jwtService: JwtService, + private readonly userRepository: UserRepository, ) {} async createToken(id: number, sellerId: number, username: string, email: string, storeId: string) { @@ -29,6 +26,15 @@ export class AuthService { } async validateUser(payload: JwtPayload): Promise { - return payload; + const user = await this.userRepository.findById(payload.id); + if (!user || !user.active) return null; + + return { + id: user.id, + sellerId: user.sellerId, + storeId: user.storeId, + username: user.username, + email: user.email, + }; } } \ No newline at end of file diff --git a/src/auth/auth/authenticate-user.command.ts b/src/auth/auth/authenticate-user.command.ts index 5cb949a..8ceedb3 100644 --- a/src/auth/auth/authenticate-user.command.ts +++ b/src/auth/auth/authenticate-user.command.ts @@ -1,4 +1,4 @@ - export class AuthenticateUserCommand { +export class AuthenticateUserCommand { constructor( public readonly username: string, public readonly password: string, diff --git a/src/auth/auth/authenticate-user.service.ts b/src/auth/auth/authenticate-user.service.ts index 6f9f2fc..d1fa0fe 100644 --- a/src/auth/auth/authenticate-user.service.ts +++ b/src/auth/auth/authenticate-user.service.ts @@ -1,13 +1,16 @@ -import { Injectable, ForbiddenException } from '@nestjs/common'; -import { UserRepository } from '../users/UserRepository'; +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { AuthenticateUserCommand } from './authenticate-user.command'; +import { UserRepository } from '../users/UserRepository'; import { Result } from '../models/result'; +import { Injectable } from '@nestjs/common'; +import { UserModel } from 'src/core/models/user.model'; +@CommandHandler(AuthenticateUserCommand) @Injectable() -export class AuthenticateUserService { +export class AuthenticateUserHandler implements ICommandHandler { constructor(private readonly userRepository: UserRepository) {} - async execute(command: AuthenticateUserCommand): Promise> { + async execute(command: AuthenticateUserCommand): Promise> { const { username, password } = command; const user = await this.userRepository.findByUsernameAndPassword(username, password); @@ -17,13 +20,18 @@ export class AuthenticateUserService { } if (user.dataDesligamento !== null) { - return Result.fail('Usuário desligado da empresa, login não permitido, Procure o setor de RH!'); + return Result.fail('Usuário desligado da empresa, login não permitido!'); } if (user.situacao === 'I') { return Result.fail('Usuário inativo, login não permitido!'); } - return Result.ok(user); + if (user.situacao === 'B') { + return Result.fail('Usuário bloqueado, login não permitido!'); + } + + + return Result.ok(user); } } diff --git a/src/auth/doc/doc.txt b/src/auth/doc/doc.txt index 45c1f01..6d8c874 100644 --- a/src/auth/doc/doc.txt +++ b/src/auth/doc/doc.txt @@ -1,46 +1,53 @@ -Controller - ↓ recebe LoginDto -UsersService - ↓ monta o AuthenticateUserCommand -AuthenticateUserService +// Documentação atualizada da funcionalidade de autenticação usando Command + Microservice + +/* +Fluxo atualizado: + +Client (HTTP POST /api/v1/auth/login) + ↓ envia LoginRequestDto +AuthController + ↓ envia AuthenticateUserCommand para CommandBus +AuthenticateUserHandler (CommandHandler) ↓ executa regras de negócio UserRepository ↓ executa query no banco Result ← resposta encapsulada +AuthController + ↓ gera JWT com AuthService +Resposta final: LoginResponseDto com token +*/ +// 1. AuthController +// Entrada da requisição HTTP (POST /api/v1/auth/login) +// - Recebe LoginRequestDto com username e password +// - Cria AuthenticateUserCommand +// - Envia para CommandBus +// - Se sucesso, gera JWT via AuthService e retorna LoginResponseDto -1. LoginController -Entrada da requisição HTTP (POST /auth/login) +// 2. AuthenticateUserCommand +// Representa a intenção de autenticação +// - Dados: username, password -Recebe LoginDto com email e password +// 3. AuthenticateUserHandler +// Executa o caso de uso de autenticação +// - Busca usuário no UserRepository +// - Verifica se o usuário está desligado ou inativo +// - Retorna Result -Encaminha para o UsersService +// 4. UserRepository +// Camada de persistência (interface com o banco de dados) +// - Executa query findByUsernameAndPassword(username, password) -2. UsersService -Cria e valida o comando AuthenticateUserCommand +// 5. Result +// Objeto genérico de retorno que encapsula: +// - Sucesso (success = true, data: User) +// - Falha (success = false, error: string) -Chama o serviço de autenticação de domínio (AuthenticateUserService) +// 6. AuthService +// Gera token JWT com base nos dados do usuário +// - Pode gerar access token e refresh token (se necessário) -3. AuthenticateUserService -Lógica de negócio da autenticação: - -Busca usuário pelo e-mail - -Verifica se o usuário está ativo - -Valida a senha com hash - -Retorna Result - -4. UserRepository -Camada de persistência (interface com o banco de dados) - -Executa a query findByEmail(email: string) - -5. Result -Objeto genérico de retorno que encapsula: - -Sucesso (success = true, com data: User) - -Falha (success = false, com error: string) \ No newline at end of file +// Estrutura atual suporta futura extração do AuthModule como microserviço +// - Usando @MessagePattern('auth.login') no AuthController do microserviço +// - Consumo via ClientProxy na aplicação principal com ClientModule diff --git a/src/auth/doc/login.fluxo.html b/src/auth/doc/login.fluxo.html index 7f5f841..4c7c123 100644 --- a/src/auth/doc/login.fluxo.html +++ b/src/auth/doc/login.fluxo.html @@ -3,7 +3,7 @@ - Documentação - Fluxo de Login + Documentação - Fluxo de Login com Microserviço -

🔐 Fluxo de Login - Portal Juru API

+

🔐 Fluxo de Login com Microserviço - Portal Juru API

📌 Rota de Login

URL: POST /api/v1/auth/login

-

Descrição: Autentica o usuário, valida regras de negócio e retorna um token JWT.

-

Acesso: Público

+

Descrição: Autentica o usuário via microserviço e retorna um token JWT.

📤 Body (JSON)

{
@@ -79,40 +78,40 @@
   "error": "Usuário ou senha inválidos."
 }
-

🧱 Camadas e Responsabilidades

+

🧱 Camadas e Responsabilidades (CQRS + Microservice)

- - - - - - - - + + + + + + + + +
CamadaResponsabilidade
AuthControllerRecebe requisição e coordena autenticação
UsersServiceOrquestra os casos de uso (login, reset, troca senha)
AuthenticateUserServiceExecuta lógica de autenticação e validações
UserRepositoryExecuta SQL diretamente no Oracle
AuthServiceGera o token JWT com os dados do usuário
JwtStrategyValida o token em rotas protegidas, usando Redis como cache
RedisClientAdapterWrapper de acesso ao Redis com interface genérica e TTL
ComponenteResponsabilidade
AuthControllerRecebe requisição HTTP e envia AuthenticateUserCommand ao CommandBus
AuthenticateUserCommandRepresenta a intenção de autenticar um usuário
AuthenticateUserHandlerExecuta regras de autenticação com base no comando
UserRepositoryConsulta o banco para obter dados do usuário
Result<T>Encapsula sucesso ou falha com mensagens claras
AuthServiceGera token JWT e/ou Refresh Token
Auth MicroserviceEscuta o evento 'auth.login' e executa a autenticação
ClientProxyFaz chamada ao microserviço de autenticação
-

🧊 Redis na Autenticação

+

🔗 Comunicação via Microserviço

    -
  • Chave: session:{userId}
  • -
  • Valor: JSON serializado do usuário autenticado
  • -
  • TTL: 8 horas
  • -
  • Fallback: Se o cache não existir, consulta ao banco
  • -
  • Auditoria: espaço reservado para log de acesso
  • +
  • Protocolo: TCP
  • +
  • Evento: auth.login
  • +
  • Porta: 3001
  • +
  • Formato: { username, password }
  • +
  • Resposta: Result<User>
-

🔐 Proteção de Rotas

-

Rotas protegidas utilizam @UseGuards(JwtAuthGuard) e @ApiBearerAuth().

-

Header necessário:

+

🛡️ Proteção com JWT

+

Após o login, o token JWT é retornado e deve ser enviado em todas as requisições protegidas.

Authorization: Bearer <token>

🚀 Melhorias Futuras

    -
  • [ ] Blacklist de tokens para logout
  • -
  • [ ] Log de auditoria
  • +
  • [ ] Blacklist de tokens
  • [ ] Refresh Token
  • -
  • [ ] Controle de permissões/roles
  • +
  • [ ] Log de auditoria
  • +
  • [ ] Controle de permissões e roles
-

Última atualização: 28/03/2025

+

Última atualização: 31/03/2025

diff --git a/src/auth/users/users.module.ts b/src/auth/users/users.module.ts index c7724c9..fc95d55 100644 --- a/src/auth/users/users.module.ts +++ b/src/auth/users/users.module.ts @@ -3,7 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; // <-- importe aqui import { UsersService } from './users.service'; import { UserRepository } from './UserRepository'; -import { AuthenticateUserService } from '../auth/authenticate-user.service'; +import { AuthenticateUserHandler } from '../auth/authenticate-user.service'; import { ResetPasswordService } from './reset-password.service'; import { ChangePasswordService } from './change-password.service'; import { EmailService } from './email.service'; @@ -16,7 +16,7 @@ import { EmailService } from './email.service'; providers: [ UsersService, UserRepository, - AuthenticateUserService, + AuthenticateUserHandler, ResetPasswordService, ChangePasswordService, EmailService, diff --git a/src/auth/users/users.service.ts b/src/auth/users/users.service.ts index 8c33c74..0d66221 100644 --- a/src/auth/users/users.service.ts +++ b/src/auth/users/users.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { AuthenticateUserService } from '../auth/authenticate-user.service'; +import { AuthenticateUserHandler } from '../auth/authenticate-user.service'; import { ResetPasswordService } from './reset-password.service'; import { ChangePasswordService } from './change-password.service'; import { AuthenticateUserCommand } from '../auth/authenticate-user.command'; @@ -9,7 +9,7 @@ import { AuthenticateUserCommand } from '../auth/authenticate-user.command'; @Injectable() export class UsersService { constructor( - private readonly authenticateUserService: AuthenticateUserService, + private readonly authenticateUserService: AuthenticateUserHandler, private readonly resetPasswordService: ResetPasswordService, private readonly changePasswordService: ChangePasswordService, ) {}