From faed3e405416a1874e92e176922dcd23dcae875c Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Tue, 24 Oct 2023 14:30:54 -0300 Subject: [PATCH 1/3] (#136) adiciona autenticacao --- .env.development | 4 ++ .env.test | 4 ++ docker-compose.yml | 4 ++ src/app.controler.ts | 2 + src/app.module.ts | 27 +++++++++++- src/autenticacao.guard.ts | 43 +++++++++++++++++++ .../decorators/public-route.decorator.ts | 4 ++ 7 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/autenticacao.guard.ts create mode 100644 src/shared/decorators/public-route.decorator.ts diff --git a/.env.development b/.env.development index fdbde7a..67f6ce5 100644 --- a/.env.development +++ b/.env.development @@ -5,3 +5,7 @@ DB_USERNAME=postgres DB_PASS=postgres DB_DATABASE=gerocuidado-saude-db DB_PORT=5003 + +## TCP +AUTH_HOST=gerocuidado-usuario-api +AUTH_PORT=4001 diff --git a/.env.test b/.env.test index ec73639..6b73efd 100644 --- a/.env.test +++ b/.env.test @@ -5,3 +5,7 @@ DB_USERNAME=postgres DB_PASS=postgres DB_DATABASE=gerocuidado-saude-db-test DB_PORT=5003 + +## TCP +AUTH_HOST=gerocuidado-usuario-api +AUTH_PORT=4001 diff --git a/docker-compose.yml b/docker-compose.yml index 3554eaa..1ac9e8f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: - gerocuidado-saude-db networks: - gerocuidado-saude-net + - gerocuidado-apis-net gerocuidado-saude-db: build: @@ -36,3 +37,6 @@ services: networks: gerocuidado-saude-net: driver: bridge + gerocuidado-apis-net: + name: gerocuidado-apis-net + external: true diff --git a/src/app.controler.ts b/src/app.controler.ts index a41a124..cf15115 100644 --- a/src/app.controler.ts +++ b/src/app.controler.ts @@ -1,11 +1,13 @@ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; +import { PublicRoute } from './shared/decorators/public-route.decorator'; @Controller() export class AppController { constructor(private readonly service: AppService) {} @Get('health-check') + @PublicRoute() heathCheck() { return this.service.heathCheck(); } diff --git a/src/app.module.ts b/src/app.module.ts index ce0a9b0..60bfe91 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,8 +1,11 @@ import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { APP_GUARD } from '@nestjs/core'; +import { ClientsModule, Transport } from '@nestjs/microservices'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controler'; import { AppService } from './app.service'; +import { AutenticacaoGuard } from './autenticacao.guard'; import { DbModule } from './config/db/db.module'; import { DbService } from './config/db/db.service'; @@ -18,9 +21,29 @@ const ENV = process.env.NODE_ENV; imports: [ConfigModule, DbModule], useClass: DbService, }), + ClientsModule.registerAsync([ + { + name: 'AUTH_CLIENT', + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + transport: Transport.TCP, + options: { + host: configService.get('AUTH_HOST'), + port: configService.get('AUTH_PORT'), + }, + }), + inject: [ConfigService], + }, + ]), DbModule, ], controllers: [AppController], - providers: [AppService], + providers: [ + AppService, + { + provide: APP_GUARD, + useClass: AutenticacaoGuard, + }, + ], }) export class AppModule {} diff --git a/src/autenticacao.guard.ts b/src/autenticacao.guard.ts new file mode 100644 index 0000000..201f21a --- /dev/null +++ b/src/autenticacao.guard.ts @@ -0,0 +1,43 @@ +import { + CanActivate, + ExecutionContext, + Inject, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { ClientProxy } from '@nestjs/microservices'; +import { lastValueFrom, timeout } from 'rxjs'; +import { IS_PUBLIC_KEY } from './shared/decorators/public-route.decorator'; + +@Injectable() +export class AutenticacaoGuard implements CanActivate { + constructor( + @Inject('AUTH_CLIENT') + private readonly _client: ClientProxy, + private readonly _reflector: Reflector, + ) {} + + async canActivate(context: ExecutionContext) { + const req = context.switchToHttp().getRequest(); + const jwt = req.headers['authorization']?.split(' ')[1]; + + const isPublic = this._reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); + + if (isPublic) return true; + + const request = this._client + .send({ role: 'auth', cmd: 'check' }, { jwt }) + .pipe(timeout(5000)); + const response = await lastValueFrom(request); + + if (!response) { + throw new UnauthorizedException('Usuário não autenticado!'); + } + + return true; + } +} diff --git a/src/shared/decorators/public-route.decorator.ts b/src/shared/decorators/public-route.decorator.ts new file mode 100644 index 0000000..c27f73c --- /dev/null +++ b/src/shared/decorators/public-route.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common'; + +export const IS_PUBLIC_KEY = 'isPublic'; +export const PublicRoute = () => SetMetadata(IS_PUBLIC_KEY, true); From cfbc9eef5458b92ab305d848b4b38dc798d176d2 Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Tue, 24 Oct 2023 16:52:01 -0300 Subject: [PATCH 2/3] (#136) adiciona configuracoes e testes --- docker-compose.prod.yml | 6 ++ package-lock.json | 6 +- package.json | 4 +- src/autenticacao.guard.spec.ts | 97 ++++++++++++++++++++++ src/shared/decorators/public-route.spec.ts | 8 ++ 5 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 src/autenticacao.guard.spec.ts create mode 100644 src/shared/decorators/public-route.spec.ts diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e970400..06117bd 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -9,12 +9,15 @@ services: - DB_PASS=postgres - DB_DATABASE=gerocuidado-saude-db - DB_PORT=5003 + - AUTH_HOST=gerocuidado-usuario-api-prod + - AUTH_PORT=4001 ports: - '3003:3003' depends_on: - gerocuidado-saude-db networks: - gerocuidado-saude-net + - gerocuidado-apis-net gerocuidado-saude-db: build: @@ -36,3 +39,6 @@ services: networks: gerocuidado-saude-net: driver: bridge + gerocuidado-apis-net: + name: gerocuidado-apis-net + external: true diff --git a/package-lock.json b/package-lock.json index 1a856dd..506f20d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5412,9 +5412,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.565", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.565.tgz", - "integrity": "sha512-XbMoT6yIvg2xzcbs5hCADi0dXBh4//En3oFXmtPX+jiyyiCTiM9DGFT2SLottjpEs9Z8Mh8SqahbR96MaHfuSg==", + "version": "1.4.566", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.566.tgz", + "integrity": "sha512-mv+fAy27uOmTVlUULy15U3DVJ+jg+8iyKH1bpwboCRhtDC69GKf1PPTZvEIhCyDr81RFqfxZJYrbgp933a1vtg==", "dev": true }, "node_modules/emittery": { diff --git a/package.json b/package.json index a31240f..8eedd72 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,10 @@ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest --no-cache --colors --detectOpenHandles", "test:watch": "jest --watchAll", - "test:cov": "jest --coverage --colors", + "test:cov": "jest --forceExit --detectOpenHandles --runInBand --coverage --colors", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --forceExit --detectOpenHandles --colors --config ./e2e/jest-e2e.json", - "test:e2e:cov": "jest --forceExit --detectOpenHandles --colors --coverage --config ./e2e/jest-e2e.json", + "test:e2e:cov": "jest --forceExit --detectOpenHandles --runInBand --colors --coverage --config ./e2e/jest-e2e.json", "test:e2e:watch": "jest --detectOpenHandles --config ./e2e/jest-e2e.json --watchAll", "typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js", "typeorm:create": "npm run typeorm migration:create", diff --git a/src/autenticacao.guard.spec.ts b/src/autenticacao.guard.spec.ts new file mode 100644 index 0000000..289291e --- /dev/null +++ b/src/autenticacao.guard.spec.ts @@ -0,0 +1,97 @@ +import { UnauthorizedException } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Test, TestingModule } from '@nestjs/testing'; +import { of } from 'rxjs'; +import { AutenticacaoGuard } from './autenticacao.guard'; + +describe('AutenticacaoGuard', () => { + let guard: AutenticacaoGuard; + let reflector: Reflector; + + const mockClientProxy = { + send: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + AutenticacaoGuard, + { + provide: 'AUTH_CLIENT', + useValue: mockClientProxy, + }, + Reflector, + ], + }).compile(); + + guard = module.get(AutenticacaoGuard); + reflector = module.get(Reflector); + }); + + it('should be defined', () => { + expect(guard).toBeDefined(); + }); + + it('should pass if route is public', async () => { + const context = { + switchToHttp: jest.fn().mockReturnValue({ + getRequest: jest.fn().mockReturnValue({ + headers: { + authorization: 'Bearer valid_token', + }, + }), + }), + getHandler: jest.fn(), + getClass: jest.fn(), + }; + jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(true); + + const result = await guard.canActivate(context as any); + + expect(result).toBe(true); + }); + + it('should pass if authentication is successful', async () => { + const context = { + switchToHttp: jest.fn().mockReturnValue({ + getRequest: jest.fn().mockReturnValue({ + headers: { + authorization: 'Bearer valid_token', + }, + }), + }), + getHandler: jest.fn(), + getClass: jest.fn(), + }; + jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false); + mockClientProxy.send.mockReturnValue(of(true)); + + const result = await guard.canActivate(context as any); + + expect(result).toBe(true); + }); + + it('should not pass if authentication is unsuccessful', async () => { + const context = { + switchToHttp: jest.fn().mockReturnValue({ + getRequest: jest.fn().mockReturnValue({ + headers: { + authorization: 'Bearer invalid_token', + }, + }), + }), + getHandler: jest.fn(), + getClass: jest.fn(), + }; + jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false); + mockClientProxy.send.mockReturnValue(of(false)); + + guard + .canActivate(context as any) + .catch((err) => + expect(err).toEqual( + new UnauthorizedException('Usuário não autenticado!'), + ), + ); + }); +}); diff --git a/src/shared/decorators/public-route.spec.ts b/src/shared/decorators/public-route.spec.ts new file mode 100644 index 0000000..4303b04 --- /dev/null +++ b/src/shared/decorators/public-route.spec.ts @@ -0,0 +1,8 @@ +import { PublicRoute } from './public-route.decorator'; + +describe('Pagination', () => { + it('should be defined', () => { + PublicRoute(); + expect(PublicRoute).toBeDefined(); + }); +}); From 8984bd60d4560fdbc6c6cfc190855e6b4e3ff647 Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Tue, 24 Oct 2023 16:59:17 -0300 Subject: [PATCH 3/3] (#136) corrige linhas duplicadas --- src/autenticacao.guard.spec.ts | 51 ++++++++++------------------------ 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/src/autenticacao.guard.spec.ts b/src/autenticacao.guard.spec.ts index 289291e..54570e1 100644 --- a/src/autenticacao.guard.spec.ts +++ b/src/autenticacao.guard.spec.ts @@ -12,6 +12,18 @@ describe('AutenticacaoGuard', () => { send: jest.fn(), }; + const mockContext = { + switchToHttp: jest.fn().mockReturnValue({ + getRequest: jest.fn().mockReturnValue({ + headers: { + authorization: 'Bearer token', + }, + }), + }), + getHandler: jest.fn(), + getClass: jest.fn(), + }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -33,61 +45,28 @@ describe('AutenticacaoGuard', () => { }); it('should pass if route is public', async () => { - const context = { - switchToHttp: jest.fn().mockReturnValue({ - getRequest: jest.fn().mockReturnValue({ - headers: { - authorization: 'Bearer valid_token', - }, - }), - }), - getHandler: jest.fn(), - getClass: jest.fn(), - }; jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(true); - const result = await guard.canActivate(context as any); + const result = await guard.canActivate(mockContext as any); expect(result).toBe(true); }); it('should pass if authentication is successful', async () => { - const context = { - switchToHttp: jest.fn().mockReturnValue({ - getRequest: jest.fn().mockReturnValue({ - headers: { - authorization: 'Bearer valid_token', - }, - }), - }), - getHandler: jest.fn(), - getClass: jest.fn(), - }; jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false); mockClientProxy.send.mockReturnValue(of(true)); - const result = await guard.canActivate(context as any); + const result = await guard.canActivate(mockContext as any); expect(result).toBe(true); }); it('should not pass if authentication is unsuccessful', async () => { - const context = { - switchToHttp: jest.fn().mockReturnValue({ - getRequest: jest.fn().mockReturnValue({ - headers: { - authorization: 'Bearer invalid_token', - }, - }), - }), - getHandler: jest.fn(), - getClass: jest.fn(), - }; jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false); mockClientProxy.send.mockReturnValue(of(false)); guard - .canActivate(context as any) + .canActivate(mockContext as any) .catch((err) => expect(err).toEqual( new UnauthorizedException('Usuário não autenticado!'),