@tsdevstack/nest-common 0.1.4
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 +21 -0
- package/README.md +111 -0
- package/dist/auth/auth-user.interface.d.ts +62 -0
- package/dist/auth/auth.guard.d.ts +181 -0
- package/dist/auth/auth.guard.test.d.ts +1 -0
- package/dist/auth/auth.module.d.ts +45 -0
- package/dist/auth/index.d.ts +17 -0
- package/dist/auth/partner-api.decorator.d.ts +42 -0
- package/dist/auth/partner.decorator.d.ts +60 -0
- package/dist/auth/partner.decorator.test.d.ts +1 -0
- package/dist/auth/public.decorator.d.ts +42 -0
- package/dist/auth/public.decorator.test.d.ts +1 -0
- package/dist/auth/utils/extract-user-from-headers.d.ts +45 -0
- package/dist/auth/utils/extract-user-from-headers.test.d.ts +1 -0
- package/dist/auth/utils/index.d.ts +8 -0
- package/dist/auth/utils/parse-header-value.d.ts +40 -0
- package/dist/auth/utils/parse-header-value.test.d.ts +1 -0
- package/dist/auth/utils/to-camel-case.d.ts +18 -0
- package/dist/auth/utils/to-camel-case.test.d.ts +1 -0
- package/dist/bootstrap/create-app.d.ts +31 -0
- package/dist/bootstrap/create-app.test.d.ts +1 -0
- package/dist/bootstrap/start-worker.d.ts +24 -0
- package/dist/bootstrap/start-worker.test.d.ts +1 -0
- package/dist/bull/bull-config.module.d.ts +22 -0
- package/dist/bull/bull-config.module.test.d.ts +1 -0
- package/dist/bull/index.d.ts +1 -0
- package/dist/config/load-framework-config.d.ts +32 -0
- package/dist/config/load-framework-config.test.d.ts +1 -0
- package/dist/database/prisma-connection.d.ts +48 -0
- package/dist/database/prisma-connection.test.d.ts +1 -0
- package/dist/email-rate-limit/email-rate-limit.decorator.d.ts +8 -0
- package/dist/email-rate-limit/email-rate-limit.decorator.test.d.ts +1 -0
- package/dist/email-rate-limit/email-rate-limit.guard.d.ts +11 -0
- package/dist/email-rate-limit/email-rate-limit.guard.test.d.ts +1 -0
- package/dist/email-rate-limit/email-rate-limit.module.d.ts +2 -0
- package/dist/health/health.controller.d.ts +11 -0
- package/dist/health/health.controller.test.d.ts +1 -0
- package/dist/health/health.interface.d.ts +31 -0
- package/dist/health/health.module.d.ts +5 -0
- package/dist/health/health.service.d.ts +12 -0
- package/dist/health/health.service.test.d.ts +1 -0
- package/dist/health/index.d.ts +6 -0
- package/dist/health/indicators/memory.indicator.d.ts +7 -0
- package/dist/health/indicators/memory.indicator.test.d.ts +1 -0
- package/dist/health/indicators/redis.indicator.d.ts +7 -0
- package/dist/health/indicators/redis.indicator.test.d.ts +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +9 -0
- package/dist/index.mjs +9 -0
- package/dist/logging/index.d.ts +6 -0
- package/dist/logging/logger.interface.d.ts +29 -0
- package/dist/logging/logger.module.d.ts +14 -0
- package/dist/logging/logger.service.d.ts +31 -0
- package/dist/logging/logger.service.test.d.ts +1 -0
- package/dist/logging/logging.interceptor.d.ts +8 -0
- package/dist/logging/logging.interceptor.test.d.ts +1 -0
- package/dist/metrics/index.d.ts +5 -0
- package/dist/metrics/metrics.controller.d.ts +7 -0
- package/dist/metrics/metrics.controller.test.d.ts +1 -0
- package/dist/metrics/metrics.interceptor.d.ts +9 -0
- package/dist/metrics/metrics.interceptor.test.d.ts +1 -0
- package/dist/metrics/metrics.interface.d.ts +17 -0
- package/dist/metrics/metrics.module.d.ts +5 -0
- package/dist/metrics/metrics.service.d.ts +79 -0
- package/dist/metrics/metrics.service.test.d.ts +1 -0
- package/dist/notifications/index.d.ts +15 -0
- package/dist/notifications/interfaces/email-options.interface.d.ts +23 -0
- package/dist/notifications/interfaces/index.d.ts +6 -0
- package/dist/notifications/interfaces/push-options.interface.d.ts +16 -0
- package/dist/notifications/interfaces/sms-options.interface.d.ts +12 -0
- package/dist/notifications/notification.module.d.ts +2 -0
- package/dist/notifications/notification.module.test.d.ts +1 -0
- package/dist/notifications/notification.service.d.ts +28 -0
- package/dist/notifications/notification.service.test.d.ts +1 -0
- package/dist/notifications/providers/email/console.provider.d.ts +9 -0
- package/dist/notifications/providers/email/console.provider.test.d.ts +1 -0
- package/dist/notifications/providers/email/resend.provider.d.ts +24 -0
- package/dist/notifications/providers/email/resend.provider.test.d.ts +1 -0
- package/dist/notifications/providers/email-provider.interface.d.ts +17 -0
- package/dist/observability/index.d.ts +2 -0
- package/dist/observability/observability.interface.d.ts +32 -0
- package/dist/observability/observability.module.d.ts +24 -0
- package/dist/observability/observability.module.test.d.ts +1 -0
- package/dist/open-api-docs/create-swagger-document.d.ts +10 -0
- package/dist/open-api-docs/create-swagger-document.test.d.ts +1 -0
- package/dist/open-api-docs/generate-swagger-docs.d.ts +12 -0
- package/dist/open-api-docs/generate-swagger-docs.test.d.ts +1 -0
- package/dist/rate-limit/rate-limit-headers.interceptor.d.ts +5 -0
- package/dist/rate-limit/rate-limit-headers.interceptor.test.d.ts +1 -0
- package/dist/rate-limit/rate-limit.decorator.d.ts +11 -0
- package/dist/rate-limit/rate-limit.decorator.test.d.ts +1 -0
- package/dist/rate-limit/rate-limit.guard.d.ts +13 -0
- package/dist/rate-limit/rate-limit.guard.test.d.ts +1 -0
- package/dist/rate-limit/rate-limit.module.d.ts +2 -0
- package/dist/redis/redis.module.d.ts +2 -0
- package/dist/redis/redis.service.d.ts +17 -0
- package/dist/redis/redis.service.test.d.ts +1 -0
- package/dist/scheduler/index.d.ts +1 -0
- package/dist/scheduler/scheduler.guard.d.ts +73 -0
- package/dist/scheduler/scheduler.guard.test.d.ts +1 -0
- package/dist/secrets/index.d.ts +10 -0
- package/dist/secrets/providers/aws.provider.d.ts +56 -0
- package/dist/secrets/providers/aws.provider.test.d.ts +1 -0
- package/dist/secrets/providers/azure.provider.d.ts +70 -0
- package/dist/secrets/providers/azure.provider.test.d.ts +1 -0
- package/dist/secrets/providers/cloud-provider-adapter.d.ts +50 -0
- package/dist/secrets/providers/cloud-provider-adapter.test.d.ts +1 -0
- package/dist/secrets/providers/cloud-provider.interface.d.ts +86 -0
- package/dist/secrets/providers/gcp.provider.d.ts +64 -0
- package/dist/secrets/providers/gcp.provider.test.d.ts +1 -0
- package/dist/secrets/providers/local.provider.d.ts +82 -0
- package/dist/secrets/providers/local.provider.test.d.ts +1 -0
- package/dist/secrets/providers/provider-factory.d.ts +39 -0
- package/dist/secrets/providers/provider-factory.test.d.ts +1 -0
- package/dist/secrets/secrets.interface.d.ts +93 -0
- package/dist/secrets/secrets.module.d.ts +24 -0
- package/dist/secrets/secrets.service.d.ts +70 -0
- package/dist/secrets/secrets.service.test.d.ts +1 -0
- package/dist/service-client/base-service-client.d.ts +113 -0
- package/dist/service-client/base-service-client.test.d.ts +1 -0
- package/dist/service-client/filter-forward-headers.d.ts +11 -0
- package/dist/service-client/filter-forward-headers.test.d.ts +1 -0
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/telemetry.interface.d.ts +33 -0
- package/dist/telemetry/telemetry.module.d.ts +5 -0
- package/dist/telemetry/telemetry.service.d.ts +39 -0
- package/dist/telemetry/telemetry.service.test.d.ts +1 -0
- package/dist/telemetry/tracing.interceptor.d.ts +11 -0
- package/dist/telemetry/tracing.interceptor.test.d.ts +1 -0
- package/dist/utils/package-json.d.ts +25 -0
- package/package.json +102 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 tsdevstack
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# @tsdevstack/nest-common
|
|
2
|
+
|
|
3
|
+
Shared NestJS modules for tsdevstack microservices. Provides auth, rate limiting, secrets management, observability, notifications, database connectivity, and application bootstrap — so every service starts with production-ready infrastructure.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Authentication** — Kong JWT validation guard with public/partner route decorators
|
|
8
|
+
- **Rate Limiting** — Redis-backed rate limiting with per-route configuration
|
|
9
|
+
- **Email Rate Limiting** — Dedicated rate limiter for email-sending endpoints
|
|
10
|
+
- **Secrets Management** — Multi-provider secrets (GCP Secret Manager, AWS Secrets Manager, Azure Key Vault)
|
|
11
|
+
- **Observability** — Logging (Pino), metrics (Prometheus/OpenTelemetry), tracing (OTLP), health checks
|
|
12
|
+
- **Database** — Prisma connection pooling with `pg` adapter and SSL support
|
|
13
|
+
- **Notifications** — Email (Resend), SMS, and push notification service
|
|
14
|
+
- **Background Jobs** — BullMQ configuration and scheduler guard
|
|
15
|
+
- **Service Client** — Type-safe HTTP client for inter-service communication
|
|
16
|
+
- **Bootstrap** — `startApp()` and `startWorker()` for consistent app initialization
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @tsdevstack/nest-common
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Bootstrap a service
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { startApp } from '@tsdevstack/nest-common';
|
|
30
|
+
import { AppModule } from './app.module';
|
|
31
|
+
|
|
32
|
+
startApp(AppModule, {
|
|
33
|
+
serviceName: 'my-service',
|
|
34
|
+
port: 3000,
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Import modules
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import {
|
|
42
|
+
AuthModule,
|
|
43
|
+
RedisModule,
|
|
44
|
+
RateLimitModule,
|
|
45
|
+
ObservabilityModule,
|
|
46
|
+
SecretsModule,
|
|
47
|
+
} from '@tsdevstack/nest-common';
|
|
48
|
+
|
|
49
|
+
@Module({
|
|
50
|
+
imports: [
|
|
51
|
+
ObservabilityModule.register({ serviceName: 'my-service' }),
|
|
52
|
+
SecretsModule,
|
|
53
|
+
AuthModule,
|
|
54
|
+
RedisModule,
|
|
55
|
+
RateLimitModule,
|
|
56
|
+
],
|
|
57
|
+
})
|
|
58
|
+
export class AppModule {}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Protect routes
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { AuthGuard, Public, RateLimit } from '@tsdevstack/nest-common';
|
|
65
|
+
|
|
66
|
+
@Controller('users')
|
|
67
|
+
@UseGuards(AuthGuard)
|
|
68
|
+
export class UsersController {
|
|
69
|
+
@Public()
|
|
70
|
+
@Get('health')
|
|
71
|
+
health() {
|
|
72
|
+
return { status: 'ok' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@RateLimit({ points: 10, duration: 60 })
|
|
76
|
+
@Get('profile')
|
|
77
|
+
getProfile(@Req() req: AuthenticatedRequest) {
|
|
78
|
+
return req.user;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Modules
|
|
84
|
+
|
|
85
|
+
| Module | Description |
|
|
86
|
+
|--------|-------------|
|
|
87
|
+
| `AuthModule` | Kong JWT validation, `@Public()` and `@PartnerApi()` decorators |
|
|
88
|
+
| `RedisModule` | Redis connection with retry and health checks |
|
|
89
|
+
| `RateLimitModule` | Redis-backed rate limiting with `@RateLimit()` decorator |
|
|
90
|
+
| `EmailRateLimitModule` | Dedicated email rate limiting |
|
|
91
|
+
| `SecretsModule` | Multi-cloud secrets loading (GCP, AWS, Azure) |
|
|
92
|
+
| `ObservabilityModule` | Logging, metrics, tracing, and health endpoints |
|
|
93
|
+
| `NotificationModule` | Email (Resend), SMS, and push notifications |
|
|
94
|
+
| `BullConfigModule` | BullMQ queue configuration |
|
|
95
|
+
|
|
96
|
+
## Utilities
|
|
97
|
+
|
|
98
|
+
| Export | Description |
|
|
99
|
+
|--------|-------------|
|
|
100
|
+
| `startApp()` | Bootstrap a NestJS application with standard middleware |
|
|
101
|
+
| `startWorker()` | Bootstrap a background worker process |
|
|
102
|
+
| `createPrismaConnection()` | Prisma client with `pg` adapter and SSL |
|
|
103
|
+
| `BaseServiceClient` | HTTP client for service-to-service calls |
|
|
104
|
+
| `filterForwardHeaders()` | Header filtering for inter-service requests |
|
|
105
|
+
| `generateSwaggerDocs()` | OpenAPI documentation setup |
|
|
106
|
+
| `LoggerService` | Pino-based structured logging |
|
|
107
|
+
| `MetricsService` | Custom Prometheus metrics |
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Request } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* User object extracted from Kong headers.
|
|
4
|
+
*
|
|
5
|
+
* Kong forwards JWT claims as `X-JWT-Claim-*` headers, which are
|
|
6
|
+
* dynamically extracted into this object. The `id` field is always
|
|
7
|
+
* present (from `X-Consumer-ID`), but all other fields are dynamic
|
|
8
|
+
* based on JWT claims.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // Kong headers:
|
|
13
|
+
* // X-Consumer-ID: user-123
|
|
14
|
+
* // X-JWT-Claim-Email: user@example.com
|
|
15
|
+
* // X-JWT-Claim-Roles: USER,ADMIN
|
|
16
|
+
* // X-JWT-Claim-TenantId: tenant-456
|
|
17
|
+
*
|
|
18
|
+
* // Resulting KongUser:
|
|
19
|
+
* {
|
|
20
|
+
* id: "user-123",
|
|
21
|
+
* email: "user@example.com",
|
|
22
|
+
* roles: ["USER", "ADMIN"],
|
|
23
|
+
* tenantId: "tenant-456"
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export interface KongUser {
|
|
28
|
+
/** User ID from X-Consumer-ID (JWT sub claim) */
|
|
29
|
+
id: string;
|
|
30
|
+
/** Dynamic claims extracted from X-JWT-Claim-* headers */
|
|
31
|
+
[key: string]: string | string[] | number | boolean | undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Express Request with Kong authentication populated.
|
|
35
|
+
*
|
|
36
|
+
* After KongAuthGuard processes the request, either `user` (JWT auth)
|
|
37
|
+
* or `service` (API key auth) will be populated.
|
|
38
|
+
*/
|
|
39
|
+
export interface AuthenticatedRequest extends Request {
|
|
40
|
+
/** User object (JWT authentication) */
|
|
41
|
+
user?: KongUser;
|
|
42
|
+
/** Service name (API key authentication) */
|
|
43
|
+
service?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Kong header names for type safety.
|
|
47
|
+
*
|
|
48
|
+
* These are the standard headers that Kong sets after validating
|
|
49
|
+
* JWT tokens or API keys.
|
|
50
|
+
*/
|
|
51
|
+
export declare enum KongHeaders {
|
|
52
|
+
/** Consumer ID (JWT sub claim) */
|
|
53
|
+
CONSUMER_ID = "x-consumer-id",
|
|
54
|
+
/** Consumer username (API key service name) */
|
|
55
|
+
CONSUMER_USERNAME = "x-consumer-username",
|
|
56
|
+
/** Credential identifier (JWT sub claim from kong-oidc-v3) */
|
|
57
|
+
CREDENTIAL_IDENTIFIER = "x-credential-identifier",
|
|
58
|
+
/** JWT claims as JSON (kong-oidc-v3 plugin) */
|
|
59
|
+
USERINFO = "x-userinfo",
|
|
60
|
+
/** Prefix for JWT claim headers (legacy) */
|
|
61
|
+
JWT_CLAIM_PREFIX = "x-jwt-claim-"
|
|
62
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import { Reflector } from '@nestjs/core';
|
|
3
|
+
import { SecretsService } from '../secrets/secrets.service';
|
|
4
|
+
/**
|
|
5
|
+
* Guard that validates requests came through Kong gateway and extracts user data.
|
|
6
|
+
*
|
|
7
|
+
* This guard implements the tsdevstack authentication architecture where:
|
|
8
|
+
* 1. Kong validates JWT signatures using JWKS endpoint
|
|
9
|
+
* 2. Kong forwards ALL JWT claims as JSON in `X-Userinfo` header (kong-oidc-v3)
|
|
10
|
+
* 3. Kong adds `X-Kong-Trust` header to prove requests came through gateway
|
|
11
|
+
* 4. Services validate Kong trust header for defense-in-depth security
|
|
12
|
+
* 5. Services trust Kong headers (network isolation + trust header prevents spoofing)
|
|
13
|
+
* 6. Services never validate JWT tokens themselves
|
|
14
|
+
*
|
|
15
|
+
* ## How It Works
|
|
16
|
+
*
|
|
17
|
+
* ### Kong Trust Header Verification
|
|
18
|
+
* - Kong adds `X-Kong-Trust` header with KONG_TRUST_TOKEN value to ALL requests
|
|
19
|
+
* - Guard verifies this header before processing authentication
|
|
20
|
+
* - Direct service-to-service calls with `x-api-key` bypass this check
|
|
21
|
+
* - Prevents direct access to backend services bypassing Kong
|
|
22
|
+
*
|
|
23
|
+
* ### JWT Authentication (User Requests)
|
|
24
|
+
* - Kong validates JWT and sets `X-Consumer-ID` (from JWT `sub` claim)
|
|
25
|
+
* - Kong forwards all JWT claims as JSON in `X-Userinfo` header
|
|
26
|
+
* - Guard parses the JSON and extracts ALL claims into `req.user` object
|
|
27
|
+
* - Claims preserve their original types (arrays, numbers, booleans, strings)
|
|
28
|
+
* - Falls back to legacy `X-JWT-Claim-*` headers for backward compatibility
|
|
29
|
+
*
|
|
30
|
+
* ### API Key Authentication (Service-to-Service)
|
|
31
|
+
* - Kong validates API key and sets `X-Consumer-Username` (service name)
|
|
32
|
+
* - Guard sets `req.service` to the service name
|
|
33
|
+
* - No user object is created
|
|
34
|
+
*
|
|
35
|
+
* ### Public Endpoints
|
|
36
|
+
* - Routes marked with `@Public()` decorator skip user authentication
|
|
37
|
+
* - But still require Kong trust header (unless direct service-to-service)
|
|
38
|
+
*
|
|
39
|
+
* @example Basic usage with JWT
|
|
40
|
+
* ```typescript
|
|
41
|
+
* @Controller('offers')
|
|
42
|
+
* export class OffersController {
|
|
43
|
+
* @Post()
|
|
44
|
+
* @UseGuards(AuthGuard)
|
|
45
|
+
* create(@Request() req: AuthenticatedRequest) {
|
|
46
|
+
* const { id, email, roles } = req.user;
|
|
47
|
+
* // Access any custom claims dynamically
|
|
48
|
+
* const tenantId = req.user.tenantId;
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example Public endpoint
|
|
54
|
+
* ```typescript
|
|
55
|
+
* @Get()
|
|
56
|
+
* @Public()
|
|
57
|
+
* list() {
|
|
58
|
+
* // No user authentication required, but must come through Kong
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @example Service-to-service with API key
|
|
63
|
+
* ```typescript
|
|
64
|
+
* @Get('internal')
|
|
65
|
+
* @UseGuards(AuthGuard)
|
|
66
|
+
* internal(@Request() req: AuthenticatedRequest) {
|
|
67
|
+
* const serviceName = req.service; // e.g., "bff-service"
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare class AuthGuard implements CanActivate {
|
|
72
|
+
private reflector;
|
|
73
|
+
private secrets;
|
|
74
|
+
private readonly logger;
|
|
75
|
+
constructor(reflector: Reflector, secrets: SecretsService);
|
|
76
|
+
/**
|
|
77
|
+
* Validates the request came through Kong and extracts authentication data.
|
|
78
|
+
*
|
|
79
|
+
* @param context - Execution context
|
|
80
|
+
* @returns true if authentication is valid or endpoint is public
|
|
81
|
+
* @throws UnauthorizedException if Kong headers are missing
|
|
82
|
+
*/
|
|
83
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
84
|
+
/**
|
|
85
|
+
* Extracts user object from Kong headers.
|
|
86
|
+
*
|
|
87
|
+
* kong-oidc-v3 forwards JWT claims as base64-encoded JSON in the `X-Userinfo` header.
|
|
88
|
+
* Falls back to legacy `X-JWT-Claim-*` headers for backward compatibility.
|
|
89
|
+
*
|
|
90
|
+
* @param headers - HTTP headers from the request
|
|
91
|
+
* @returns User object with id and all dynamic claims
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* // Input headers (kong-oidc-v3):
|
|
96
|
+
* {
|
|
97
|
+
* 'x-credential-identifier': 'user-123',
|
|
98
|
+
* 'x-userinfo': 'eyJzdWIiOiJ1c2VyLTEyMyIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsInJvbGVzIjpbIlVTRVIiLCJBRE1JTiJdLCJ0ZW5hbnRJZCI6InRlbmFudC00NTYifQ=='
|
|
99
|
+
* }
|
|
100
|
+
*
|
|
101
|
+
* // Output user object:
|
|
102
|
+
* {
|
|
103
|
+
* id: 'user-123',
|
|
104
|
+
* email: 'user@example.com',
|
|
105
|
+
* roles: ['USER', 'ADMIN'],
|
|
106
|
+
* tenantId: 'tenant-456'
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
private extractUserFromHeaders;
|
|
111
|
+
/**
|
|
112
|
+
* Converts kebab-case to camelCase.
|
|
113
|
+
*
|
|
114
|
+
* @param str - Kebab-case string (e.g., "tenant-id")
|
|
115
|
+
* @returns CamelCase string (e.g., "tenantId")
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* toCamelCase('tenant-id') // 'tenantId'
|
|
120
|
+
* toCamelCase('is-verified') // 'isVerified'
|
|
121
|
+
* toCamelCase('email') // 'email'
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
private toCamelCase;
|
|
125
|
+
/**
|
|
126
|
+
* Parses header value to appropriate JavaScript type.
|
|
127
|
+
*
|
|
128
|
+
* Kong forwards all JWT claims as strings. This method intelligently
|
|
129
|
+
* parses them back to their original types:
|
|
130
|
+
* - Arrays: "USER,ADMIN" → ["USER", "ADMIN"]
|
|
131
|
+
* - Numbers: "123" → 123
|
|
132
|
+
* - Booleans: "true" → true, "false" → false
|
|
133
|
+
* - Strings: everything else
|
|
134
|
+
*
|
|
135
|
+
* @param value - String value from header
|
|
136
|
+
* @returns Parsed value in appropriate type
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* parseValue('USER,ADMIN') // ['USER', 'ADMIN']
|
|
141
|
+
* parseValue('123') // 123
|
|
142
|
+
* parseValue('true') // true
|
|
143
|
+
* parseValue('false') // false
|
|
144
|
+
* parseValue('john@example.com') // 'john@example.com'
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
private parseValue;
|
|
148
|
+
/**
|
|
149
|
+
* Verifies that the request came through Kong gateway by validating the trust header.
|
|
150
|
+
* This is a defense-in-depth measure to prevent direct access to backend services.
|
|
151
|
+
*
|
|
152
|
+
* @param request - HTTP request object
|
|
153
|
+
* @throws UnauthorizedException if Kong trust header is missing or invalid
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* // Kong adds this header to all requests:
|
|
158
|
+
* // Request headers: { 'x-kong-trust': 'KONG_TRUST_TOKEN_VALUE' }
|
|
159
|
+
* // Service validates it matches the KONG_TRUST_TOKEN from secrets
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
private verifyKongTrustHeader;
|
|
163
|
+
/**
|
|
164
|
+
* Validates direct service-to-service API key authentication.
|
|
165
|
+
* Used when services call each other directly (bypassing Kong).
|
|
166
|
+
*
|
|
167
|
+
* @param request - HTTP request object
|
|
168
|
+
* @param apiKey - API key from x-api-key header
|
|
169
|
+
* @returns true if API key is valid
|
|
170
|
+
* @throws UnauthorizedException if API key configuration is missing
|
|
171
|
+
* @throws ForbiddenException if API key is invalid
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* // Service A calling Service B:
|
|
176
|
+
* // Request headers: { 'x-api-key': 'AUTH_SERVICE_API_KEY_VALUE' }
|
|
177
|
+
* // Service B validates against its own API_KEY from secrets
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
private validateServiceApiKey;
|
|
181
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kong Authentication Module.
|
|
3
|
+
*
|
|
4
|
+
* Provides the AuthGuard globally to all modules in the application.
|
|
5
|
+
* Import this module once in your root AppModule to make AuthGuard
|
|
6
|
+
* available throughout your application.
|
|
7
|
+
*
|
|
8
|
+
* ## Features
|
|
9
|
+
* - Dynamic JWT claim extraction from Kong headers
|
|
10
|
+
* - Service-to-service API key authentication
|
|
11
|
+
* - @Public() decorator for public endpoints
|
|
12
|
+
* - Network isolation security (trusts Kong headers only)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // app.module.ts
|
|
17
|
+
* @Module({
|
|
18
|
+
* imports: [
|
|
19
|
+
* AuthModule,
|
|
20
|
+
* // ... other modules
|
|
21
|
+
* ],
|
|
22
|
+
* })
|
|
23
|
+
* export class AppModule {}
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example Usage in controllers
|
|
27
|
+
* ```typescript
|
|
28
|
+
* @Controller('offers')
|
|
29
|
+
* export class OffersController {
|
|
30
|
+
* @Get()
|
|
31
|
+
* @Public()
|
|
32
|
+
* list() {
|
|
33
|
+
* // Public endpoint
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* @Post()
|
|
37
|
+
* @UseGuards(AuthGuard)
|
|
38
|
+
* create(@Request() req: AuthenticatedRequest) {
|
|
39
|
+
* const { id, email, roles } = req.user;
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare class AuthModule {
|
|
45
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Module
|
|
3
|
+
*
|
|
4
|
+
* Provides authentication for tsdevstack applications including:
|
|
5
|
+
* - Kong gateway JWT authentication
|
|
6
|
+
* - Kong API key authentication
|
|
7
|
+
* - Direct service-to-service API key authentication
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
export { AuthModule } from './auth.module';
|
|
12
|
+
export { AuthGuard } from './auth.guard';
|
|
13
|
+
export { Public, IS_PUBLIC_KEY } from './public.decorator';
|
|
14
|
+
export { PartnerApi, IS_PARTNER_API_KEY } from './partner-api.decorator';
|
|
15
|
+
export { Partner } from './partner.decorator';
|
|
16
|
+
export type { KongUser, AuthenticatedRequest } from './auth-user.interface';
|
|
17
|
+
export { KongHeaders } from './auth-user.interface';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata key for the @PartnerApi() decorator.
|
|
3
|
+
*/
|
|
4
|
+
export declare const IS_PARTNER_API_KEY = "isPartnerApi";
|
|
5
|
+
/**
|
|
6
|
+
* Decorator to mark routes as accessible via Partner API (requires API key).
|
|
7
|
+
*
|
|
8
|
+
* Routes marked with this decorator will be exposed under the /api prefix
|
|
9
|
+
* in Kong Gateway and require a valid API key for access.
|
|
10
|
+
*
|
|
11
|
+
* This decorator is ADDITIVE, not exclusive:
|
|
12
|
+
* - Can be used alone for partner-only access
|
|
13
|
+
* - Can be combined with @ApiBearerAuth() for dual access (JWT + Partner API)
|
|
14
|
+
*
|
|
15
|
+
* @example Partner-only endpoint
|
|
16
|
+
* ```typescript
|
|
17
|
+
* @Controller('offers')
|
|
18
|
+
* export class OffersController {
|
|
19
|
+
* @Get('current-plan')
|
|
20
|
+
* @PartnerApi()
|
|
21
|
+
* getCurrentPlan() {
|
|
22
|
+
* // Accessible only via /api/offers/current-plan with API key
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example Dual-access endpoint (JWT + Partner API)
|
|
28
|
+
* ```typescript
|
|
29
|
+
* @Controller('data')
|
|
30
|
+
* export class DataController {
|
|
31
|
+
* @Get('export')
|
|
32
|
+
* @ApiBearerAuth()
|
|
33
|
+
* @PartnerApi()
|
|
34
|
+
* exportData() {
|
|
35
|
+
* // Accessible via:
|
|
36
|
+
* // - /data/export with JWT token (for users)
|
|
37
|
+
* // - /api/data/export with API key (for partners)
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare const PartnerApi: () => <TFunction extends Function, Y>(target: TFunction | object, propertyKey?: string | symbol, descriptor?: TypedPropertyDescriptor<Y>) => void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts partner identifier from Kong's X-Consumer-Username header.
|
|
3
|
+
*
|
|
4
|
+
* Kong automatically adds this header when a valid API key is used.
|
|
5
|
+
* The value is the partner's username as defined in .secrets.user.json.
|
|
6
|
+
*
|
|
7
|
+
* @returns Partner username string, or undefined if not present
|
|
8
|
+
*
|
|
9
|
+
* @example Basic usage
|
|
10
|
+
* ```typescript
|
|
11
|
+
* @Controller('webhooks')
|
|
12
|
+
* export class WebhooksController {
|
|
13
|
+
* @PartnerApi()
|
|
14
|
+
* @Post('data')
|
|
15
|
+
* async receiveData(@Partner() partner: string) {
|
|
16
|
+
* // partner = "acme-corp" (from X-Consumer-Username header)
|
|
17
|
+
* this.logger.info('Partner API call', { partner });
|
|
18
|
+
* return this.processData();
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example With optional typing (dual JWT + Partner access)
|
|
24
|
+
* ```typescript
|
|
25
|
+
* @Controller('exports')
|
|
26
|
+
* export class ExportsController {
|
|
27
|
+
* @ApiBearerAuth()
|
|
28
|
+
* @PartnerApi()
|
|
29
|
+
* @Get('data')
|
|
30
|
+
* async exportData(
|
|
31
|
+
* @User() user?: AuthUser,
|
|
32
|
+
* @Partner() partner?: string,
|
|
33
|
+
* ) {
|
|
34
|
+
* if (partner) {
|
|
35
|
+
* // Called via /api/exports/data with API key
|
|
36
|
+
* this.logger.info('Partner export', { partner });
|
|
37
|
+
* } else if (user) {
|
|
38
|
+
* // Called via /exports/data with JWT
|
|
39
|
+
* this.logger.info('User export', { userId: user.id });
|
|
40
|
+
* }
|
|
41
|
+
* return this.getExportData();
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example With logging and tracking
|
|
47
|
+
* ```typescript
|
|
48
|
+
* @PartnerApi()
|
|
49
|
+
* @Post('webhook')
|
|
50
|
+
* async handleWebhook(
|
|
51
|
+
* @Partner() partner: string,
|
|
52
|
+
* @Body() data: WebhookDto,
|
|
53
|
+
* ) {
|
|
54
|
+
* await this.analytics.trackPartnerUsage(partner, '/webhook');
|
|
55
|
+
* await this.webhookService.process(partner, data);
|
|
56
|
+
* return { success: true };
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare const Partner: (...dataOrPipes: unknown[]) => ParameterDecorator;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata key for the @Public() decorator.
|
|
3
|
+
*/
|
|
4
|
+
export declare const IS_PUBLIC_KEY = "isPublic";
|
|
5
|
+
/**
|
|
6
|
+
* Decorator to mark routes as public (no authentication required).
|
|
7
|
+
*
|
|
8
|
+
* When applied to a controller method or class, KongAuthGuard will
|
|
9
|
+
* skip authentication checks for those routes.
|
|
10
|
+
*
|
|
11
|
+
* @example Method-level (specific endpoint is public)
|
|
12
|
+
* ```typescript
|
|
13
|
+
* @Controller('auth')
|
|
14
|
+
* export class AuthController {
|
|
15
|
+
* @Post('login')
|
|
16
|
+
* @Public()
|
|
17
|
+
* async login(@Body() dto: LoginDto) {
|
|
18
|
+
* // No authentication required for login
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* @Get('profile')
|
|
22
|
+
* @UseGuards(KongAuthGuard)
|
|
23
|
+
* async getProfile(@Request() req: AuthenticatedRequest) {
|
|
24
|
+
* // Authentication required
|
|
25
|
+
* const user = req.user;
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example Class-level (all endpoints are public)
|
|
31
|
+
* ```typescript
|
|
32
|
+
* @Controller('health')
|
|
33
|
+
* @Public()
|
|
34
|
+
* export class HealthController {
|
|
35
|
+
* @Get()
|
|
36
|
+
* check() {
|
|
37
|
+
* return { status: 'ok' };
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare const Public: () => import("@nestjs/common").CustomDecorator<string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { KongUser } from '../auth-user.interface';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts user object from Kong headers.
|
|
4
|
+
*
|
|
5
|
+
* Dynamically extracts ALL JWT claims from headers that start with
|
|
6
|
+
* `X-JWT-Claim-*`. This makes the authentication future-proof - any
|
|
7
|
+
* new claims added to the JWT will automatically flow through without
|
|
8
|
+
* code changes.
|
|
9
|
+
*
|
|
10
|
+
* @param headers - HTTP headers from the request
|
|
11
|
+
* @returns User object with id and all dynamic claims
|
|
12
|
+
*
|
|
13
|
+
* @example Basic extraction
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const headers = {
|
|
16
|
+
* 'x-consumer-id': 'user-123',
|
|
17
|
+
* 'x-jwt-claim-email': 'user@example.com',
|
|
18
|
+
* 'x-jwt-claim-roles': 'USER,ADMIN',
|
|
19
|
+
* };
|
|
20
|
+
*
|
|
21
|
+
* extractUserFromHeaders(headers);
|
|
22
|
+
* // {
|
|
23
|
+
* // id: 'user-123',
|
|
24
|
+
* // email: 'user@example.com',
|
|
25
|
+
* // roles: ['USER', 'ADMIN']
|
|
26
|
+
* // }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example With camelCase conversion
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const headers = {
|
|
32
|
+
* 'x-consumer-id': 'user-123',
|
|
33
|
+
* 'x-jwt-claim-tenant-id': 'tenant-456',
|
|
34
|
+
* 'x-jwt-claim-is-verified': 'true',
|
|
35
|
+
* };
|
|
36
|
+
*
|
|
37
|
+
* extractUserFromHeaders(headers);
|
|
38
|
+
* // {
|
|
39
|
+
* // id: 'user-123',
|
|
40
|
+
* // tenantId: 'tenant-456',
|
|
41
|
+
* // isVerified: true
|
|
42
|
+
* // }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function extractUserFromHeaders(headers: Record<string, string>): KongUser;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|