@cargolift-cdi/lib-common 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.jwt-logger-context.md +44 -0
- package/README.md +232 -0
- package/dist/auth/api-client.decorator.d.ts +2 -0
- package/dist/auth/api-client.decorator.js +4 -0
- package/dist/auth/api-client.decorator.js.map +1 -0
- package/dist/auth/auth.guard.d.ts +13 -0
- package/dist/auth/auth.guard.js +95 -0
- package/dist/auth/auth.guard.js.map +1 -0
- package/dist/auth/auth.module.d.ts +2 -0
- package/dist/auth/auth.module.js +19 -0
- package/dist/auth/auth.module.js.map +1 -0
- package/dist/auth/jwt-verifier.service.d.ts +22 -0
- package/dist/auth/jwt-verifier.service.js +164 -0
- package/dist/auth/jwt-verifier.service.js.map +1 -0
- package/dist/auth/roles.decorator.d.ts +2 -0
- package/dist/auth/roles.decorator.js +4 -0
- package/dist/auth/roles.decorator.js.map +1 -0
- package/dist/auth/user.decorator.d.ts +1 -0
- package/dist/auth/user.decorator.js +6 -0
- package/dist/auth/user.decorator.js.map +1 -0
- package/dist/errors/application.error.d.ts +7 -0
- package/dist/errors/application.error.js +12 -0
- package/dist/errors/application.error.js.map +1 -0
- package/dist/errors/base.error.d.ts +22 -0
- package/dist/errors/base.error.js +37 -0
- package/dist/errors/base.error.js.map +1 -0
- package/dist/errors/business.error.d.ts +7 -0
- package/dist/errors/business.error.js +12 -0
- package/dist/errors/business.error.js.map +1 -0
- package/dist/errors/invalid-payload-business.error.d.ts +7 -0
- package/dist/errors/invalid-payload-business.error.js +10 -0
- package/dist/errors/invalid-payload-business.error.js.map +1 -0
- package/dist/errors/invalid-schema-validation.error.d.ts +9 -0
- package/dist/errors/invalid-schema-validation.error.js +8 -0
- package/dist/errors/invalid-schema-validation.error.js.map +1 -0
- package/dist/errors/not-found-business.error.d.ts +7 -0
- package/dist/errors/not-found-business.error.js +20 -0
- package/dist/errors/not-found-business.error.js.map +1 -0
- package/dist/filters/api-exceptions.filter.d.ts +8 -0
- package/dist/filters/api-exceptions.filter.js +113 -0
- package/dist/filters/api-exceptions.filter.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/app-logger.module.d.ts +13 -0
- package/dist/logger/app-logger.module.js +42 -0
- package/dist/logger/app-logger.module.js.map +1 -0
- package/dist/logger/app-logger.token.d.ts +1 -0
- package/dist/logger/app-logger.token.js +2 -0
- package/dist/logger/app-logger.token.js.map +1 -0
- package/dist/logger/logger.async-context.d.ts +10 -0
- package/dist/logger/logger.async-context.js +20 -0
- package/dist/logger/logger.async-context.js.map +1 -0
- package/dist/logger/logger.module.d.ts +2 -0
- package/dist/logger/logger.module.js +19 -0
- package/dist/logger/logger.module.js.map +1 -0
- package/dist/logger/logger.service.d.ts +27 -0
- package/dist/logger/logger.service.js +275 -0
- package/dist/logger/logger.service.js.map +1 -0
- package/dist/middleware/api-logger.middleware.d.ts +8 -0
- package/dist/middleware/api-logger.middleware.js +83 -0
- package/dist/middleware/api-logger.middleware.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/util/payload.util.d.ts +4 -0
- package/dist/util/payload.util.js +34 -0
- package/dist/util/payload.util.js.map +1 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Licença MIT
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Cargolift
|
|
4
|
+
|
|
5
|
+
A permissão é concedida, gratuitamente, a qualquer pessoa que obtenha uma cópia deste software e dos arquivos de documentação associados (o "Software"), para lidar com o Software sem restrições, incluindo, sem limitação, os direitos de usar, copiar, modificar, mesclar, publicar, distribuir, sublicenciar e/ou vender cópias do Software, e permitir que pessoas a quem o Software é fornecido o façam, sujeito às seguintes condições:
|
|
6
|
+
|
|
7
|
+
O aviso de copyright acima e este aviso de permissão devem ser incluídos em todas as cópias ou partes substanciais do Software.
|
|
8
|
+
|
|
9
|
+
O SOFTWARE É FORNECIDO "NO ESTADO EM QUE SE ENCONTRA", SEM GARANTIA DE QUALQUER TIPO, EXPRESSA OU IMPLÍCITA, INCLUINDO, MAS NÃO SE LIMITANDO ÀS GARANTIAS DE COMERCIALIZAÇÃO, ADEQUAÇÃO A UM DETERMINADO FIM E NÃO VIOLAÇÃO. EM NENHUMA HIPÓTESE OS AUTORES OU DETENTORES DOS DIREITOS AUTORAIS SERÃO RESPONSÁVEIS POR QUALQUER REIVINDICAÇÃO, DANO OU OUTRA RESPONSABILIDADE, SEJA EM UMA AÇÃO DE CONTRATO, DELITO OU DE OUTRA FORMA, DECORRENTE DE, FORA DE OU EM CONEXÃO COM O SOFTWARE OU O USO OU OUTRAS NEGOCIAÇÕES NO SOFTWARE.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Enriquecimento de LoggerContext com dados do JWT
|
|
2
|
+
|
|
3
|
+
## Objetivo
|
|
4
|
+
Propagar informações de usuário (id, name, email, ip, user-agent, aplicação de origem) extraídas do token JWT para:
|
|
5
|
+
- Contexto de logs (LoggerContextService)
|
|
6
|
+
- Envelope de mensagens publicadas (IntegrationEventPublisher / RabbitMQPublisherService)
|
|
7
|
+
|
|
8
|
+
## Fluxo Implementado
|
|
9
|
+
1. Middleware `APILoggerMiddleware` inicializa o contexto de log por requisição (correlation_id, trace básico, application.function via URL, etc).
|
|
10
|
+
2. Guard `AuthGuard` valida o token e agora enriquece o contexto via `logger.updateSource()`, mesclando:
|
|
11
|
+
- `user_name` => preferred_username | username | name
|
|
12
|
+
- `user_id` => sub
|
|
13
|
+
- `user_email`=> email
|
|
14
|
+
- `application` => azp | aud
|
|
15
|
+
- `ip` e `user_agent` (cabecalhos / request)
|
|
16
|
+
3. Controller dispara `IntegrationEventPublisher.publish()`, que:
|
|
17
|
+
- Obtém `ctx = logger.getContext()`
|
|
18
|
+
- Mescla `ctx.source` + `opts.envelope.source` + fallback (ip/user_agent) para construir `envelope.source`
|
|
19
|
+
- Publica headers padronizados: `x-correlation-id`, `x-trace`, `x-source`
|
|
20
|
+
|
|
21
|
+
## Ponto de Extensão
|
|
22
|
+
Se quiser acrescentar campos customizados (ex: filial, tenant_id):
|
|
23
|
+
```ts
|
|
24
|
+
this.logger.updateSource({ tenant_id: payload?.tenant_id });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Boas Práticas
|
|
28
|
+
- Evitar sobrescrever `source` inteiro; sempre usar `updateSource` para merge incremental.
|
|
29
|
+
- Caso haja Interceptor para métricas, pode ler `logger.getContext().source.user_id` sem recompor payload.
|
|
30
|
+
- Em chamadas internas (sem HTTP), pode-se chamar manualmente:
|
|
31
|
+
```ts
|
|
32
|
+
this.logger.updateSource({ application: 'internal-task-runner' });
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Erros/Edge Cases
|
|
36
|
+
- Se o middleware não rodar (ex: rota sem HTTP padrão), o guard ainda enriquece parcialmente o contexto.
|
|
37
|
+
- Campos ausentes no token não sobrescrevem os já registrados.
|
|
38
|
+
- `ip` extraído de `x-forwarded-for` (primeiro IP) se existir.
|
|
39
|
+
|
|
40
|
+
## Alternativas Consideradas
|
|
41
|
+
- Interceptor global pós-guard (maior latência e ordem de execução mais complexa).
|
|
42
|
+
- Decorator por controller (repetitivo e sujeito a esquecimento).
|
|
43
|
+
|
|
44
|
+
A abordagem atual centraliza a responsabilidade de *enriquecimento* no mesmo ponto da *autenticação*, garantindo consistência.
|
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# @cargolift-cdi/common
|
|
2
|
+
|
|
3
|
+
Utilidades comuns e padronização de erros para projetos Cargolift CDI (NestJS).
|
|
4
|
+
|
|
5
|
+
Este pacote provê:
|
|
6
|
+
- Classes de erro de domínio (BusinessError) com código, dados adicionais e causa encadeada
|
|
7
|
+
- Filtro global de exceções para APIs (APIExceptionsFilter) já integrado ao util-logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Instalação
|
|
11
|
+
|
|
12
|
+
Requisitos:
|
|
13
|
+
- Node.js LTS (18+) e npm
|
|
14
|
+
- Peer dependency: `@nestjs/common@^11`
|
|
15
|
+
|
|
16
|
+
Instale:
|
|
17
|
+
|
|
18
|
+
```cmd
|
|
19
|
+
npm i @cargolift-cdi/common
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
O pacote depende de `@cargolift-cdi/util-logger` (instalado automaticamente).
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Uso rápido
|
|
26
|
+
|
|
27
|
+
### 1) Aplicar o filtro global de exceções (NestJS)
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { NestFactory } from '@nestjs/core';
|
|
31
|
+
import { AppModule } from './app.module';
|
|
32
|
+
import { APIExceptionsFilter } from '@cargolift-cdi/common';
|
|
33
|
+
import { LoggerContextService } from '@cargolift-cdi/util-logger';
|
|
34
|
+
|
|
35
|
+
async function bootstrap() {
|
|
36
|
+
const app = await NestFactory.create(AppModule, { bufferLogs: true });
|
|
37
|
+
|
|
38
|
+
// Usa o LoggerContextService já registrado no container
|
|
39
|
+
const logger = app.get(LoggerContextService);
|
|
40
|
+
app.useGlobalFilters(new APIExceptionsFilter(logger));
|
|
41
|
+
|
|
42
|
+
await app.listen(3000);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
bootstrap();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
O filtro padroniza o payload de erro, enriquece logs com `correlation_id` e trata mensagens conhecidas (ex.: erros de rede).
|
|
49
|
+
|
|
50
|
+
Alternativa: registrar via provider global (APP_FILTER):
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { Module } from '@nestjs/common';
|
|
54
|
+
import { APP_FILTER } from '@nestjs/core';
|
|
55
|
+
import { APIExceptionsFilter } from '@cargolift-cdi/common';
|
|
56
|
+
// Certifique-se de que o LoggerContextService esteja disponível no container
|
|
57
|
+
import { LoggerContextService } from '@cargolift-cdi/util-logger';
|
|
58
|
+
|
|
59
|
+
@Module({
|
|
60
|
+
providers: [
|
|
61
|
+
LoggerContextService, // ou importe o módulo que o fornece, se existir
|
|
62
|
+
{ provide: APP_FILTER, useClass: APIExceptionsFilter },
|
|
63
|
+
],
|
|
64
|
+
})
|
|
65
|
+
export class AppModule {}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2) Lançar erros de negócio no seu código
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { NotFoundBusinessError, GenericBusinessError } from '@cargolift-cdi/common';
|
|
72
|
+
|
|
73
|
+
// Recurso não encontrado
|
|
74
|
+
throw new NotFoundBusinessError('Order', '12345');
|
|
75
|
+
|
|
76
|
+
// Erro de negócio genérico com código e dados extras
|
|
77
|
+
throw new GenericBusinessError('Falha no provedor', {
|
|
78
|
+
code: 'PAYMENT_PROVIDER_ERROR',
|
|
79
|
+
data: { context: 'Pagamento', errorCode: 'PX-999', provider: 'AcmePay' },
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
## API pública (exportada por `index`)
|
|
85
|
+
|
|
86
|
+
- `BusinessError` (abstrata)
|
|
87
|
+
- Base para erros de domínio. Suporta `code?: string`, `data?: Record<string, unknown>`, `cause?: unknown` e serialização segura via `toJSON()`.
|
|
88
|
+
- `GenericBusinessError`
|
|
89
|
+
- Erro de negócio genérico (mensagem padrão "Generic Business error" quando não informada). Útil para normalizar exceções desconhecidas.
|
|
90
|
+
- `NotFoundBusinessError`
|
|
91
|
+
- Especialização para recursos não encontrados: construtor `(resourceType: string, resourceId: string|number, message?, options?)`.
|
|
92
|
+
- `APIExceptionsFilter` (NestJS)
|
|
93
|
+
- Filtro global de exceções. Decide tipo do erro (business/application), aplica códigos padrão e registra logs estruturados via `LoggerContextService`.
|
|
94
|
+
|
|
95
|
+
Observação: existem classes internas adicionais (ex.: `ApplicationError`, `GenericApplicationError`) que podem evoluir; utilize apenas a API pública acima para evitar quebras.
|
|
96
|
+
|
|
97
|
+
## Contexto de Logs com AsyncLocalStorage (v2)
|
|
98
|
+
|
|
99
|
+
A partir da versão que introduz esta seção, o `LoggerContextService` passou a ser singleton (escopo DEFAULT) e o isolamento por requisição/mensagem é garantido via `AsyncLocalStorage`.
|
|
100
|
+
|
|
101
|
+
Benefícios:
|
|
102
|
+
- Menos alocações de instâncias (melhor para alto throughput)
|
|
103
|
+
- Propagação automática de contexto por callbacks/promises sem passar manualmente
|
|
104
|
+
- Publishers/clients apenas usam `logger.getContext()`
|
|
105
|
+
|
|
106
|
+
Helpers exportados:
|
|
107
|
+
```ts
|
|
108
|
+
import { runWithLoggerContext, getLoggerContext, updateLoggerContext } from '@cargolift-cdi/common';
|
|
109
|
+
|
|
110
|
+
runWithLoggerContext({ correlation_id: 'abc' }, () => {
|
|
111
|
+
// Tudo aqui dentro (e awaits) mantém o contexto
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Fluxo HTTP (middleware `APILoggerMiddleware`):
|
|
116
|
+
1. Abre `runWithLoggerContext({})`
|
|
117
|
+
2. Chama `logger.setContextRequest(req)` (gera/propaga `correlation_id` e `trace`)
|
|
118
|
+
3. Todos os logs subsequentes compartilham o mesmo contexto
|
|
119
|
+
|
|
120
|
+
Fluxo RabbitMQ (consumer):
|
|
121
|
+
1. Cria um child logger a partir dos headers da mensagem (`childFromRabbit`)
|
|
122
|
+
2. Semeia o ALS com `runWithLoggerContext(child.getContext(), processMessage)`
|
|
123
|
+
3. Dentro do processamento usa-se o `logger` base normalmente
|
|
124
|
+
|
|
125
|
+
Caso precise anexar dados adicionais no meio da execução:
|
|
126
|
+
```ts
|
|
127
|
+
updateLoggerContext({ caller_info: { type: 'user', id: 'u-1' } });
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Se um log for emitido fora de um escopo ALS (ex.: antes de bootstrap), o logger ainda funcionará, porém sem `correlation_id` fixo (um novo poderá ser gerado quando o contexto for definido).
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
## Detalhes das classes
|
|
135
|
+
|
|
136
|
+
### BusinessError
|
|
137
|
+
|
|
138
|
+
Contrato básico:
|
|
139
|
+
- `name: string` — Nome da classe (ex.: `NotFoundBusinessError`)
|
|
140
|
+
- `message?: string` — Mensagem humana
|
|
141
|
+
- `code?: string` — Ex.: `BUSINESS_RESOURCE_NOT_FOUND`, `INVALID_PAYLOAD`
|
|
142
|
+
- `data?: Record<string, unknown>` — Dados serializáveis extras
|
|
143
|
+
- `cause?: unknown` — Erro original para encadeamento
|
|
144
|
+
- `toJSON()` — Retorna objeto serializável, inclusive com `stack` enumerável e `cause` segura
|
|
145
|
+
|
|
146
|
+
`BusinessErrorOptions`:
|
|
147
|
+
```ts
|
|
148
|
+
type BusinessErrorOptions = {
|
|
149
|
+
code?: string;
|
|
150
|
+
cause?: unknown;
|
|
151
|
+
data?: Record<string, unknown>;
|
|
152
|
+
};
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### NotFoundBusinessError
|
|
156
|
+
|
|
157
|
+
- Código padrão: `BUSINESS_RESOURCE_NOT_FOUND`
|
|
158
|
+
- `data`: `{ resourceType: string, resourceId: string|number }`
|
|
159
|
+
|
|
160
|
+
### GenericBusinessError
|
|
161
|
+
|
|
162
|
+
- Código padrão: `BUSINESS_ERROR`
|
|
163
|
+
- Permite enviar `data` adicional e causa original (`cause`).
|
|
164
|
+
|
|
165
|
+
### APIExceptionsFilter
|
|
166
|
+
|
|
167
|
+
- Inspeciona exceções do NestJS (`HttpException`) e erros comuns de infraestrutura (ex.: `ECONNREFUSED`, `ETIMEDOUT`)
|
|
168
|
+
- Cria/propaga `correlation_id` no contexto de log
|
|
169
|
+
- Resposta JSON padrão:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"message": "<mensagem>",
|
|
174
|
+
"error": {
|
|
175
|
+
"statusCode": 500,
|
|
176
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
177
|
+
"path": "/rota",
|
|
178
|
+
"errorCode": "INTERNAL_ERROR"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# App Logger (Global)
|
|
185
|
+
Use AppLoggerModule para configurar um log global com contexto.
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
import { AppLoggerModule, APP_LOGGER, LoggerContextService } from '@cargolift-cdi/common';
|
|
189
|
+
|
|
190
|
+
@Module({
|
|
191
|
+
imports: [
|
|
192
|
+
AppLoggerModule.forRoot({
|
|
193
|
+
application: {
|
|
194
|
+
name: 'middleware-esb',
|
|
195
|
+
function: 'service',
|
|
196
|
+
},
|
|
197
|
+
}),
|
|
198
|
+
],
|
|
199
|
+
})
|
|
200
|
+
export class AppModule {}
|
|
201
|
+
|
|
202
|
+
@Injectable()
|
|
203
|
+
export class SomeService {
|
|
204
|
+
constructor(@Inject(APP_LOGGER) private readonly appLogger: LoggerContextService) {}
|
|
205
|
+
|
|
206
|
+
doStuff() {
|
|
207
|
+
this.appLogger.log('App level log');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Notes:
|
|
213
|
+
- Este log possui um contexto único; não chame `setContext`.
|
|
214
|
+
- Para logs por mensagem/requisição , use a versão de escopo TRANSIENT `LoggerContextService` e chame `setContextRabbitMQ`/`setContextRequest`.
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
## Scripts do projeto
|
|
219
|
+
|
|
220
|
+
- `npm run build` — Compila TypeScript para `dist/`
|
|
221
|
+
- `npm publish` — Publica (executa `build` no `prepublishOnly`)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
## Contribuição
|
|
225
|
+
|
|
226
|
+
- Abra issues e PRs no GitHub: https://github.com/cargolift-cdi/common
|
|
227
|
+
- Padrões: TypeScript, NestJS 11, commits claros, linters/formatters do seu editor
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
## Licença
|
|
231
|
+
|
|
232
|
+
MIT © Cargolift CDI
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.decorator.js","sourceRoot":"","sources":["../../src/auth/api-client.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC;AACjD,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,QAAgB,EAAE,EAAE,CAAC,WAAW,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext } from "@nestjs/common";
|
|
2
|
+
import { Reflector } from "@nestjs/core";
|
|
3
|
+
import { JwtVerifierService } from "./jwt-verifier.service.js";
|
|
4
|
+
import { LoggerContextService } from "../logger/logger.service.js";
|
|
5
|
+
export declare class AuthGuard implements CanActivate {
|
|
6
|
+
private readonly jwtVerifier;
|
|
7
|
+
private readonly reflector;
|
|
8
|
+
private readonly logger;
|
|
9
|
+
constructor(jwtVerifier: JwtVerifierService, reflector: Reflector, logger: LoggerContextService);
|
|
10
|
+
private baseClientId;
|
|
11
|
+
private resolveApiClientId;
|
|
12
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Injectable, UnauthorizedException, ForbiddenException, InternalServerErrorException } from "@nestjs/common";
|
|
11
|
+
import { Reflector } from "@nestjs/core";
|
|
12
|
+
import { JwtVerifierService } from "./jwt-verifier.service.js";
|
|
13
|
+
import { LoggerContextService } from "../logger/logger.service.js";
|
|
14
|
+
import { ROLES_KEY } from "./roles.decorator.js";
|
|
15
|
+
import { API_CLIENT_ID_KEY } from "./api-client.decorator.js";
|
|
16
|
+
let AuthGuard = class AuthGuard {
|
|
17
|
+
constructor(jwtVerifier, reflector, logger) {
|
|
18
|
+
this.jwtVerifier = jwtVerifier;
|
|
19
|
+
this.reflector = reflector;
|
|
20
|
+
this.logger = logger;
|
|
21
|
+
}
|
|
22
|
+
baseClientId() {
|
|
23
|
+
return process.env.KEYCLOAK_AUDIENCE || "api.util";
|
|
24
|
+
}
|
|
25
|
+
resolveApiClientId(context, payload) {
|
|
26
|
+
const decorated = this.reflector.getAllAndOverride(API_CLIENT_ID_KEY, [context.getHandler(), context.getClass()]);
|
|
27
|
+
if (decorated)
|
|
28
|
+
return decorated;
|
|
29
|
+
if (payload?.aud && typeof payload.aud === "string")
|
|
30
|
+
return payload.aud;
|
|
31
|
+
return this.baseClientId();
|
|
32
|
+
}
|
|
33
|
+
async canActivate(context) {
|
|
34
|
+
let request;
|
|
35
|
+
try {
|
|
36
|
+
request = context.switchToHttp().getRequest();
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
throw new InternalServerErrorException("Erro ao obter dados de autorização da requisição: " + e.message);
|
|
40
|
+
}
|
|
41
|
+
const auth = request.headers["authorization"] || request.headers["Authorization"];
|
|
42
|
+
if (!auth || typeof auth !== "string" || !auth.startsWith("Bearer ")) {
|
|
43
|
+
throw new UnauthorizedException("Autorização ausente ou inválida");
|
|
44
|
+
}
|
|
45
|
+
const token = auth.substring("Bearer ".length).trim();
|
|
46
|
+
let payload;
|
|
47
|
+
try {
|
|
48
|
+
payload = await this.jwtVerifier.verify(token);
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
throw new UnauthorizedException("Falha ao obter autorização: " + (e.message || "Token inválido"));
|
|
52
|
+
}
|
|
53
|
+
request.user = payload;
|
|
54
|
+
try {
|
|
55
|
+
const ip = payload?.clientAddress ||
|
|
56
|
+
request.headers["x-forwarded-for"]?.split(",")[0]?.trim() ||
|
|
57
|
+
request.ip ||
|
|
58
|
+
request.connection?.remoteAddress ||
|
|
59
|
+
undefined;
|
|
60
|
+
const userAgent = request.headers["user-agent"];
|
|
61
|
+
const username = payload?.preferred_username || payload?.username || payload?.name;
|
|
62
|
+
const email = payload?.email;
|
|
63
|
+
const userId = payload?.sub;
|
|
64
|
+
this.logger.updateSource({
|
|
65
|
+
ip: ip,
|
|
66
|
+
user_agent: userAgent,
|
|
67
|
+
user_email: email,
|
|
68
|
+
user_id: userId,
|
|
69
|
+
user_name: username,
|
|
70
|
+
application: payload?.azp || payload?.aud,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
}
|
|
75
|
+
const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [context.getHandler(), context.getClass()]);
|
|
76
|
+
if (!requiredRoles || requiredRoles.length === 0) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
const apiClientId = this.resolveApiClientId(context, payload);
|
|
80
|
+
const roles = payload?.resource_access?.[apiClientId]?.roles || [];
|
|
81
|
+
const missing = requiredRoles.filter((r) => !roles.includes(r));
|
|
82
|
+
if (missing.length > 0) {
|
|
83
|
+
throw new ForbiddenException(`Sem permissão para acessar este recurso. Cliente: ${apiClientId}, regra: ${missing.join(", ")}`);
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
AuthGuard = __decorate([
|
|
89
|
+
Injectable(),
|
|
90
|
+
__metadata("design:paramtypes", [JwtVerifierService,
|
|
91
|
+
Reflector,
|
|
92
|
+
LoggerContextService])
|
|
93
|
+
], AuthGuard);
|
|
94
|
+
export { AuthGuard };
|
|
95
|
+
//# sourceMappingURL=auth.guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.guard.js","sourceRoot":"","sources":["../../src/auth/auth.guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Module } from '@nestjs/common';
|
|
8
|
+
import { JwtVerifierService } from './jwt-verifier.service.js';
|
|
9
|
+
import { AuthGuard } from './auth.guard.js';
|
|
10
|
+
let AuthModule = class AuthModule {
|
|
11
|
+
};
|
|
12
|
+
AuthModule = __decorate([
|
|
13
|
+
Module({
|
|
14
|
+
providers: [JwtVerifierService, AuthGuard],
|
|
15
|
+
exports: [JwtVerifierService, AuthGuard],
|
|
16
|
+
})
|
|
17
|
+
], AuthModule);
|
|
18
|
+
export { AuthModule };
|
|
19
|
+
//# sourceMappingURL=auth.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":";;;;;;AACA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAMrC,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,UAAU;IAJtB,MAAM,CAAC;QACN,SAAS,EAAE,CAAC,kBAAkB,EAAE,SAAS,CAAC;QAC1C,OAAO,EAAE,CAAC,kBAAkB,EAAE,SAAS,CAAC;KACzC,CAAC;GACW,UAAU,CAAG"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { LoggerContextService } from '../logger/logger.service.js';
|
|
3
|
+
export declare class JwtVerifierService implements OnModuleInit {
|
|
4
|
+
private readonly logger;
|
|
5
|
+
private openIdConfig?;
|
|
6
|
+
private keyCache;
|
|
7
|
+
private configFetchedAt?;
|
|
8
|
+
constructor(logger: LoggerContextService);
|
|
9
|
+
private readonly CONFIG_TTL_MS;
|
|
10
|
+
private readonly KEY_TTL_MS;
|
|
11
|
+
private get issuer();
|
|
12
|
+
private get audience();
|
|
13
|
+
private get wellKnownUrl();
|
|
14
|
+
private fetchOpenIdConfig;
|
|
15
|
+
private fetchJwks;
|
|
16
|
+
private getKeyByKid;
|
|
17
|
+
private parseJwt;
|
|
18
|
+
private verifySignatureRS256;
|
|
19
|
+
private assertClaims;
|
|
20
|
+
verify(token: string): Promise<any>;
|
|
21
|
+
onModuleInit(): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Injectable } from '@nestjs/common';
|
|
11
|
+
import { createPublicKey, createVerify } from 'crypto';
|
|
12
|
+
import { LoggerContextService } from '../logger/logger.service.js';
|
|
13
|
+
function base64urlDecode(input) {
|
|
14
|
+
input = input.replace(/-/g, '+').replace(/_/g, '/');
|
|
15
|
+
const pad = input.length % 4;
|
|
16
|
+
if (pad)
|
|
17
|
+
input += '='.repeat(4 - pad);
|
|
18
|
+
return Buffer.from(input, 'base64');
|
|
19
|
+
}
|
|
20
|
+
let JwtVerifierService = class JwtVerifierService {
|
|
21
|
+
constructor(logger) {
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
this.keyCache = new Map();
|
|
24
|
+
this.CONFIG_TTL_MS = 60_000;
|
|
25
|
+
this.KEY_TTL_MS = 10 * 60_000;
|
|
26
|
+
}
|
|
27
|
+
get issuer() {
|
|
28
|
+
return process.env.KEYCLOAK_ISSUER;
|
|
29
|
+
}
|
|
30
|
+
get audience() {
|
|
31
|
+
return process.env.KEYCLOAK_AUDIENCE;
|
|
32
|
+
}
|
|
33
|
+
get wellKnownUrl() {
|
|
34
|
+
const baseUrl = process.env.KEYCLOAK_BASE_URL;
|
|
35
|
+
const realm = process.env.KEYCLOAK_REALM;
|
|
36
|
+
return `${baseUrl}/realms/${realm}/.well-known/openid-configuration`;
|
|
37
|
+
}
|
|
38
|
+
async fetchOpenIdConfig() {
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
if (this.openIdConfig && this.configFetchedAt && now - this.configFetchedAt < this.CONFIG_TTL_MS) {
|
|
41
|
+
return this.openIdConfig;
|
|
42
|
+
}
|
|
43
|
+
const res = await fetch(this.wellKnownUrl, { method: 'GET' }).catch(err => {
|
|
44
|
+
throw new Error(`Falha ao buscar configuração OpenID (${err.message})`);
|
|
45
|
+
});
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
throw new Error(`Falha ao carregar configuração OpenID: ${res.status}`);
|
|
48
|
+
}
|
|
49
|
+
const json = await res.json();
|
|
50
|
+
this.openIdConfig = { issuer: json.issuer, jwks_uri: json.jwks_uri };
|
|
51
|
+
this.configFetchedAt = now;
|
|
52
|
+
return this.openIdConfig;
|
|
53
|
+
}
|
|
54
|
+
async fetchJwks() {
|
|
55
|
+
const { jwks_uri } = await this.fetchOpenIdConfig();
|
|
56
|
+
const res = await fetch(jwks_uri, { method: 'GET' }).catch(err => {
|
|
57
|
+
throw new Error(`Falha ao buscar JWKS (${err.message})`);
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
throw new Error(`Falha ao carregar JWKS: ${res.status}`);
|
|
61
|
+
}
|
|
62
|
+
return res.json();
|
|
63
|
+
}
|
|
64
|
+
async getKeyByKid(kid) {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const cached = this.keyCache.get(kid);
|
|
67
|
+
if (cached && now - cached.fetchedAt < this.KEY_TTL_MS) {
|
|
68
|
+
return cached.key;
|
|
69
|
+
}
|
|
70
|
+
const jwks = await this.fetchJwks();
|
|
71
|
+
const jwk = jwks.keys.find(k => k.kid === kid);
|
|
72
|
+
if (!jwk) {
|
|
73
|
+
throw new Error(`Chave com kid ${kid} não encontrada no JWKS`);
|
|
74
|
+
}
|
|
75
|
+
const key = createPublicKey({ key: jwk, format: 'jwk' });
|
|
76
|
+
this.keyCache.set(kid, { key, fetchedAt: now });
|
|
77
|
+
return key;
|
|
78
|
+
}
|
|
79
|
+
parseJwt(token) {
|
|
80
|
+
const parts = token.split('.');
|
|
81
|
+
if (parts.length !== 3)
|
|
82
|
+
throw new Error('Invalid JWT format');
|
|
83
|
+
const [encodedHeader, encodedPayload, encodedSig] = parts;
|
|
84
|
+
const header = JSON.parse(base64urlDecode(encodedHeader).toString('utf8'));
|
|
85
|
+
const payload = JSON.parse(base64urlDecode(encodedPayload).toString('utf8'));
|
|
86
|
+
const signature = base64urlDecode(encodedSig);
|
|
87
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
88
|
+
return { header, payload, signature, signingInput };
|
|
89
|
+
}
|
|
90
|
+
verifySignatureRS256(signingInput, signature, publicKey) {
|
|
91
|
+
const verifier = createVerify('RSA-SHA256');
|
|
92
|
+
verifier.update(signingInput);
|
|
93
|
+
verifier.end();
|
|
94
|
+
return verifier.verify(publicKey, signature);
|
|
95
|
+
}
|
|
96
|
+
assertClaims(payload) {
|
|
97
|
+
const now = Math.floor(Date.now() / 1000);
|
|
98
|
+
if (typeof payload.exp === 'number' && now >= payload.exp) {
|
|
99
|
+
throw new Error('Token expirado');
|
|
100
|
+
}
|
|
101
|
+
if (typeof payload.nbf === 'number' && now < payload.nbf) {
|
|
102
|
+
throw new Error('Token ainda não válido (nbf)');
|
|
103
|
+
}
|
|
104
|
+
if (this.issuer && payload.iss !== this.issuer) {
|
|
105
|
+
throw new Error(`Invalid issuer: expected ${this.issuer}`);
|
|
106
|
+
}
|
|
107
|
+
if (this.audience) {
|
|
108
|
+
const aud = payload.aud;
|
|
109
|
+
const ok = aud === this.audience ||
|
|
110
|
+
(Array.isArray(aud) && aud.includes(this.audience));
|
|
111
|
+
if (!ok)
|
|
112
|
+
throw new Error(`Invalid audience, expected ${this.audience}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async verify(token) {
|
|
116
|
+
const { header, payload, signature, signingInput } = this.parseJwt(token);
|
|
117
|
+
if (header.alg !== 'RS256') {
|
|
118
|
+
throw new Error(`Unsupported alg: ${header.alg}`);
|
|
119
|
+
}
|
|
120
|
+
if (!header.kid) {
|
|
121
|
+
throw new Error('JWT missing kid');
|
|
122
|
+
}
|
|
123
|
+
const key = await this.getKeyByKid(header.kid);
|
|
124
|
+
const valid = this.verifySignatureRS256(signingInput, signature, key);
|
|
125
|
+
if (!valid)
|
|
126
|
+
throw new Error('Token com assinatura inválida');
|
|
127
|
+
this.assertClaims(payload);
|
|
128
|
+
return payload;
|
|
129
|
+
}
|
|
130
|
+
async onModuleInit() {
|
|
131
|
+
try {
|
|
132
|
+
if (!process.env.KEYCLOAK_BASE_URL || !process.env.KEYCLOAK_REALM) {
|
|
133
|
+
this.logger.warn('JWT verifier warmup skipped: KEYCLOAK environment variables not fully set');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
await this.fetchOpenIdConfig();
|
|
137
|
+
const jwks = await this.fetchJwks();
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
let loaded = 0;
|
|
140
|
+
for (const jwk of jwks.keys) {
|
|
141
|
+
if (!jwk.kid)
|
|
142
|
+
continue;
|
|
143
|
+
try {
|
|
144
|
+
const key = createPublicKey({ key: jwk, format: 'jwk' });
|
|
145
|
+
this.keyCache.set(jwk.kid, { key, fetchedAt: now });
|
|
146
|
+
loaded++;
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
this.logger.warn(`Falha ao preparar chave kid=${jwk.kid}: ${e.message}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
this.logger.log(`JWT verifier warmup concluído: ${loaded} chave(s) em cache`);
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
this.logger.warn(`JWT verifier warmup falhou: ${e.message}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
JwtVerifierService = __decorate([
|
|
160
|
+
Injectable(),
|
|
161
|
+
__metadata("design:paramtypes", [LoggerContextService])
|
|
162
|
+
], JwtVerifierService);
|
|
163
|
+
export { JwtVerifierService };
|
|
164
|
+
//# sourceMappingURL=jwt-verifier.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt-verifier.service.js","sourceRoot":"","sources":["../../src/auth/jwt-verifier.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roles.decorator.js","sourceRoot":"","sources":["../../src/auth/roles.decorator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,CAAC,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAC1C,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAG,KAAe,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC"}
|