@globaltracking/auth-middleware 2.0.0

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.
Files changed (118) hide show
  1. package/README.md +372 -0
  2. package/dist/config.d.ts +9 -0
  3. package/dist/config.d.ts.map +1 -0
  4. package/dist/config.js +110 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/errors.d.ts +26 -0
  7. package/dist/errors.d.ts.map +1 -0
  8. package/dist/errors.js +42 -0
  9. package/dist/errors.js.map +1 -0
  10. package/dist/extract-user.d.ts +10 -0
  11. package/dist/extract-user.d.ts.map +1 -0
  12. package/dist/extract-user.js +30 -0
  13. package/dist/extract-user.js.map +1 -0
  14. package/dist/index.d.ts +16 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +42 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/middlewares/authenticate.d.ts +10 -0
  19. package/dist/middlewares/authenticate.d.ts.map +1 -0
  20. package/dist/middlewares/authenticate.js +26 -0
  21. package/dist/middlewares/authenticate.js.map +1 -0
  22. package/dist/middlewares/error-handler.d.ts +12 -0
  23. package/dist/middlewares/error-handler.d.ts.map +1 -0
  24. package/dist/middlewares/error-handler.js +22 -0
  25. package/dist/middlewares/error-handler.js.map +1 -0
  26. package/dist/middlewares/require-permission.d.ts +12 -0
  27. package/dist/middlewares/require-permission.d.ts.map +1 -0
  28. package/dist/middlewares/require-permission.js +63 -0
  29. package/dist/middlewares/require-permission.js.map +1 -0
  30. package/dist/middlewares/require-role.d.ts +12 -0
  31. package/dist/middlewares/require-role.d.ts.map +1 -0
  32. package/dist/middlewares/require-role.js +30 -0
  33. package/dist/middlewares/require-role.js.map +1 -0
  34. package/dist/middlewares/require-self.d.ts +10 -0
  35. package/dist/middlewares/require-self.d.ts.map +1 -0
  36. package/dist/middlewares/require-self.js +40 -0
  37. package/dist/middlewares/require-self.js.map +1 -0
  38. package/dist/middlewares/require-tenant.d.ts +11 -0
  39. package/dist/middlewares/require-tenant.d.ts.map +1 -0
  40. package/dist/middlewares/require-tenant.js +25 -0
  41. package/dist/middlewares/require-tenant.js.map +1 -0
  42. package/dist/nestjs/constants.d.ts +5 -0
  43. package/dist/nestjs/constants.d.ts.map +1 -0
  44. package/dist/nestjs/constants.js +8 -0
  45. package/dist/nestjs/constants.js.map +1 -0
  46. package/dist/nestjs/decorators/index.d.ts +44 -0
  47. package/dist/nestjs/decorators/index.d.ts.map +1 -0
  48. package/dist/nestjs/decorators/index.js +61 -0
  49. package/dist/nestjs/decorators/index.js.map +1 -0
  50. package/dist/nestjs/filters/auth-exception.filter.d.ts +16 -0
  51. package/dist/nestjs/filters/auth-exception.filter.d.ts.map +1 -0
  52. package/dist/nestjs/filters/auth-exception.filter.js +37 -0
  53. package/dist/nestjs/filters/auth-exception.filter.js.map +1 -0
  54. package/dist/nestjs/gt-auth.module.d.ts +31 -0
  55. package/dist/nestjs/gt-auth.module.d.ts.map +1 -0
  56. package/dist/nestjs/gt-auth.module.js +102 -0
  57. package/dist/nestjs/gt-auth.module.js.map +1 -0
  58. package/dist/nestjs/guards/auth.guard.d.ts +13 -0
  59. package/dist/nestjs/guards/auth.guard.d.ts.map +1 -0
  60. package/dist/nestjs/guards/auth.guard.js +53 -0
  61. package/dist/nestjs/guards/auth.guard.js.map +1 -0
  62. package/dist/nestjs/guards/internal-only.guard.d.ts +15 -0
  63. package/dist/nestjs/guards/internal-only.guard.d.ts.map +1 -0
  64. package/dist/nestjs/guards/internal-only.guard.js +51 -0
  65. package/dist/nestjs/guards/internal-only.guard.js.map +1 -0
  66. package/dist/nestjs/guards/permissions.guard.d.ts +23 -0
  67. package/dist/nestjs/guards/permissions.guard.d.ts.map +1 -0
  68. package/dist/nestjs/guards/permissions.guard.js +134 -0
  69. package/dist/nestjs/guards/permissions.guard.js.map +1 -0
  70. package/dist/nestjs/index.d.ts +12 -0
  71. package/dist/nestjs/index.d.ts.map +1 -0
  72. package/dist/nestjs/index.js +40 -0
  73. package/dist/nestjs/index.js.map +1 -0
  74. package/dist/nestjs/interceptors/org-context.interceptor.d.ts +21 -0
  75. package/dist/nestjs/interceptors/org-context.interceptor.d.ts.map +1 -0
  76. package/dist/nestjs/interceptors/org-context.interceptor.js +63 -0
  77. package/dist/nestjs/interceptors/org-context.interceptor.js.map +1 -0
  78. package/dist/nestjs/middleware/trusted-headers.middleware.d.ts +15 -0
  79. package/dist/nestjs/middleware/trusted-headers.middleware.d.ts.map +1 -0
  80. package/dist/nestjs/middleware/trusted-headers.middleware.js +42 -0
  81. package/dist/nestjs/middleware/trusted-headers.middleware.js.map +1 -0
  82. package/dist/nestjs.d.ts +2 -0
  83. package/dist/nestjs.d.ts.map +1 -0
  84. package/dist/nestjs.js +18 -0
  85. package/dist/nestjs.js.map +1 -0
  86. package/dist/strategies/gateway-header.strategy.d.ts +13 -0
  87. package/dist/strategies/gateway-header.strategy.d.ts.map +1 -0
  88. package/dist/strategies/gateway-header.strategy.js +51 -0
  89. package/dist/strategies/gateway-header.strategy.js.map +1 -0
  90. package/dist/strategies/index.d.ts +5 -0
  91. package/dist/strategies/index.d.ts.map +1 -0
  92. package/dist/strategies/index.js +10 -0
  93. package/dist/strategies/index.js.map +1 -0
  94. package/dist/strategies/jwt.strategy.d.ts +13 -0
  95. package/dist/strategies/jwt.strategy.d.ts.map +1 -0
  96. package/dist/strategies/jwt.strategy.js +94 -0
  97. package/dist/strategies/jwt.strategy.js.map +1 -0
  98. package/dist/strategies/strategy.interface.d.ts +15 -0
  99. package/dist/strategies/strategy.interface.d.ts.map +1 -0
  100. package/dist/strategies/strategy.interface.js +3 -0
  101. package/dist/strategies/strategy.interface.js.map +1 -0
  102. package/dist/strategies/trusted-headers.strategy.d.ts +16 -0
  103. package/dist/strategies/trusted-headers.strategy.d.ts.map +1 -0
  104. package/dist/strategies/trusted-headers.strategy.js +50 -0
  105. package/dist/strategies/trusted-headers.strategy.js.map +1 -0
  106. package/dist/types.d.ts +78 -0
  107. package/dist/types.d.ts.map +1 -0
  108. package/dist/types.js +3 -0
  109. package/dist/types.js.map +1 -0
  110. package/dist/utils/helpers.d.ts +14 -0
  111. package/dist/utils/helpers.d.ts.map +1 -0
  112. package/dist/utils/helpers.js +26 -0
  113. package/dist/utils/helpers.js.map +1 -0
  114. package/dist/utils/jwt.d.ts +12 -0
  115. package/dist/utils/jwt.d.ts.map +1 -0
  116. package/dist/utils/jwt.js +104 -0
  117. package/dist/utils/jwt.js.map +1 -0
  118. package/package.json +82 -0
package/README.md ADDED
@@ -0,0 +1,372 @@
1
+ # @globaltracking/auth-middleware
2
+
3
+ Unified authentication and authorization middleware for the **Global Tracking** platform. Shared by all backend microservices — supports both **Express** and **NestJS**.
4
+
5
+ ## Features
6
+
7
+ - **Strategy pattern**: Gateway header, trusted headers, JWT — configurable per service
8
+ - **NestJS adapter**: `GtAuthModule`, guards, decorators, interceptors, exception filter
9
+ - **Express middleware**: `authenticate`, `requirePermission`, `requireRole`, `requireSelf`, `requireTenant`
10
+ - **Hybrid permission resolution**: JWT claims (fast) → custom resolver → RBAC HTTP call → deny (fail-closed)
11
+ - **Multi-tenant RLS**: `OrgContextInterceptor` sets `SET LOCAL app.current_org_id` for PostgreSQL Row-Level Security
12
+ - **Admin bypass**: Configurable `adminRoles[]` bypass all permission checks
13
+ - **TypeScript-first**: Full type definitions with Express `Request` augmentation
14
+ - **98%+ test coverage**: 89 tests across strategies, middlewares, and config
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @globaltracking/auth-middleware
20
+ ```
21
+
22
+ **Peer dependencies:**
23
+
24
+ | Package | Required for | Optional? |
25
+ |---------|-------------|-----------|
26
+ | `express` ^4.18 | Express middleware | Required |
27
+ | `@nestjs/common` ^11.0 | NestJS adapter | Optional |
28
+ | `@nestjs/core` ^11.0 | NestJS adapter | Optional |
29
+ | `typeorm` ^0.3.0 | OrgContextInterceptor (RLS) | Optional |
30
+ | `rxjs` ^7.0 | OrgContextInterceptor | Optional |
31
+
32
+ ---
33
+
34
+ ## NestJS Integration (Recommended)
35
+
36
+ This is the primary integration path for Global Tracking microservices.
37
+
38
+ ### 1. Import GtAuthModule in your AppModule
39
+
40
+ ```typescript
41
+ import { Module } from '@nestjs/common';
42
+ import { ConfigService } from '@nestjs/config';
43
+ import { GtAuthModule } from '@globaltracking/auth-middleware/nestjs';
44
+
45
+ @Module({
46
+ imports: [
47
+ // ... ConfigModule, TypeOrmModule, ThrottlerModule ...
48
+
49
+ GtAuthModule.forRootAsync({
50
+ inject: [ConfigService],
51
+ useFactory: (config: ConfigService) => ({
52
+ strategies: ['trusted-headers'],
53
+ internalGatewayToken: config.get('INTERNAL_GATEWAY_TOKEN'),
54
+ adminRoles: ['system_admin', 'org_admin'],
55
+ rbacServiceUrl: config.get('RBAC_SERVICE_URL'),
56
+ }),
57
+ }),
58
+
59
+ // ... domain modules ...
60
+ ],
61
+ })
62
+ export class AppModule {}
63
+ ```
64
+
65
+ **What `GtAuthModule` provides (globally):**
66
+
67
+ | Component | What it does |
68
+ |-----------|-------------|
69
+ | `GtTrustedHeadersMiddleware` | Extracts `req.user` from configured strategy chain (auto-applied to all routes) |
70
+ | `InternalOnlyGuard` | Validates `X-Gateway-Token` header — register as `APP_GUARD` |
71
+ | `GtPermissionsGuard` | Checks `@RequirePermissions()` — register as `APP_GUARD` |
72
+ | `OrgContextInterceptor` | Sets PostgreSQL RLS context — register as `APP_INTERCEPTOR` |
73
+ | `AuthExceptionFilter` | Catches `AuthError` and returns standard error envelope |
74
+
75
+ ### 2. Use decorators in controllers
76
+
77
+ ```typescript
78
+ import {
79
+ RequirePermissions,
80
+ CurrentUser,
81
+ CurrentOrg,
82
+ Public,
83
+ RequireRoles,
84
+ } from '@globaltracking/auth-middleware/nestjs';
85
+
86
+ @Controller('vehicles')
87
+ export class VehiclesController {
88
+ @Post()
89
+ @RequirePermissions('vehicles:create')
90
+ create(
91
+ @CurrentOrg() orgId: string,
92
+ @CurrentUser('userId') userId: string,
93
+ @Body() dto: CreateVehicleDto,
94
+ ) {
95
+ // orgId and userId extracted from trusted headers
96
+ }
97
+
98
+ @Get(':id')
99
+ @RequirePermissions('vehicles:read')
100
+ findOne(
101
+ @CurrentOrg() orgId: string,
102
+ @Param('id', ParseUUIDPipe) id: string,
103
+ ) { ... }
104
+
105
+ @Delete(':id')
106
+ @RequireRoles('system_admin')
107
+ remove(@Param('id', ParseUUIDPipe) id: string) { ... }
108
+ }
109
+ ```
110
+
111
+ ```typescript
112
+ // Health endpoints skip auth
113
+ @Controller('health')
114
+ export class HealthController {
115
+ @Get()
116
+ @Public()
117
+ liveness() {
118
+ return { status: 'ok' };
119
+ }
120
+ }
121
+ ```
122
+
123
+ ### 3. Register guards and interceptors
124
+
125
+ ```typescript
126
+ // app.module.ts providers
127
+ import {
128
+ InternalOnlyGuard,
129
+ GtPermissionsGuard,
130
+ OrgContextInterceptor,
131
+ } from '@globaltracking/auth-middleware/nestjs';
132
+
133
+ providers: [
134
+ { provide: APP_GUARD, useClass: InternalOnlyGuard },
135
+ { provide: APP_GUARD, useClass: GtPermissionsGuard },
136
+ { provide: APP_GUARD, useClass: ThrottlerGuard },
137
+ { provide: APP_INTERCEPTOR, useClass: OrgContextInterceptor },
138
+ // ... your other interceptors
139
+ ],
140
+ ```
141
+
142
+ ### 4. Add env vars
143
+
144
+ ```env
145
+ INTERNAL_GATEWAY_TOKEN=your-32-char-min-secret
146
+ RBAC_SERVICE_URL=http://gt-rbac-service:3000 # optional, for permission resolution
147
+ ```
148
+
149
+ ### Special case: gt-rbac-service
150
+
151
+ The RBAC service **cannot call itself** for permission resolution. Instead, inject its own `ResolveService` via `permissionResolver`:
152
+
153
+ ```typescript
154
+ GtAuthModule.forRootAsync({
155
+ imports: [ResolveModule],
156
+ inject: [ConfigService, ResolveService],
157
+ useFactory: (config: ConfigService, resolveService: ResolveService) => ({
158
+ strategies: ['trusted-headers'],
159
+ internalGatewayToken: config.get('INTERNAL_GATEWAY_TOKEN'),
160
+ adminRoles: ['system_admin', 'org_admin'],
161
+ permissionResolver: (orgId, userId, resource, action) =>
162
+ resolveService.checkPermission(orgId, userId, resource, action),
163
+ }),
164
+ }),
165
+ ```
166
+
167
+ ---
168
+
169
+ ## Express Integration
170
+
171
+ For services that use plain Express (no NestJS):
172
+
173
+ ```typescript
174
+ import express from 'express';
175
+ import {
176
+ initAuth,
177
+ authenticate,
178
+ requirePermission,
179
+ requireTenant,
180
+ authErrorHandler,
181
+ } from '@globaltracking/auth-middleware';
182
+
183
+ const app = express();
184
+
185
+ // Initialize once at startup
186
+ initAuth({
187
+ strategies: ['gateway-header', 'jwt'],
188
+ adminRoles: ['system_admin', 'org_admin'],
189
+ publicKeyPath: './keys/public.pem',
190
+ });
191
+
192
+ // Protected route
193
+ app.get('/v1/vehicles',
194
+ authenticate,
195
+ requireTenant,
196
+ requirePermission('vehicles:read'),
197
+ async (req, res) => {
198
+ // req.user.userId, req.user.orgId, etc.
199
+ // req.tenantId set by requireTenant
200
+ }
201
+ );
202
+
203
+ // Error handler (must be last)
204
+ app.use(authErrorHandler);
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Configuration
210
+
211
+ ```typescript
212
+ initAuth({
213
+ // Strategy chain — tried in order until one matches
214
+ strategies: ['gateway-header', 'jwt'], // default
215
+
216
+ // Gateway header name (GCP API Gateway)
217
+ gatewayHeaderName: 'x-apigateway-api-userinfo', // default
218
+
219
+ // JWT verification (for local dev / non-gateway)
220
+ jwtIssuer: 'globaltracking-auth', // default
221
+ publicKey: '-----BEGIN PUBLIC KEY-----\n...', // PEM string
222
+ publicKeyPath: './keys/public.pem', // or file path
223
+
224
+ // Roles that bypass all permission checks
225
+ adminRoles: ['system_admin', 'org_admin'], // default
226
+
227
+ // Trusted headers strategy config
228
+ internalGatewayToken: 'secret', // also reads env INTERNAL_GATEWAY_TOKEN
229
+ trustedHeaderNames: {
230
+ userId: 'x-user-id', // default
231
+ orgId: 'x-org-id', // default
232
+ userRole: 'x-user-role', // default
233
+ requestId: 'x-request-id', // default
234
+ gatewayToken: 'x-gateway-token', // default
235
+ },
236
+
237
+ // Permission resolution (NestJS GtPermissionsGuard)
238
+ rbacServiceUrl: 'http://gt-rbac-service:3000', // HTTP fallback
239
+ permissionResolver: async (orgId, userId, resource, action) => {
240
+ // Custom resolver (e.g., direct DB call)
241
+ return true;
242
+ },
243
+ });
244
+ ```
245
+
246
+ Public key resolution order: `publicKey` → `publicKeyPath` → `AUTH_PUBLIC_KEY` env → `AUTH_PUBLIC_KEY_PATH` env.
247
+
248
+ ---
249
+
250
+ ## Auth Strategies
251
+
252
+ | Strategy | When used | What it reads |
253
+ |----------|-----------|---------------|
254
+ | `gateway-header` | Production (GCP API Gateway) | Base64-encoded JSON in `X-Apigateway-Api-Userinfo` |
255
+ | `trusted-headers` | NestJS services behind API Gateway | `X-User-Id`, `X-Org-Id`, `X-User-Role`, `X-Request-Id`, `X-Gateway-Token` |
256
+ | `jwt` | Local dev, non-gateway environments | `Authorization: Bearer <token>` (RS256 verification) |
257
+
258
+ The strategy chain iterates in the configured order. The first strategy whose `canHandle(req)` returns true is used.
259
+
260
+ ---
261
+
262
+ ## AuthUser Shape
263
+
264
+ Every authenticated request gets `req.user` populated with:
265
+
266
+ ```typescript
267
+ interface AuthUser {
268
+ userId: string; // from JWT sub or X-User-Id
269
+ email: string; // from JWT email (empty in trusted-headers mode)
270
+ role: UserRole; // from JWT role or X-User-Role
271
+ orgId: string; // from JWT org_id or X-Org-Id
272
+ tenantId: string; // = orgId in trusted-headers mode
273
+ permissions: string[]; // from JWT claims (empty in trusted-headers mode)
274
+ requestId: string; // from X-Request-Id
275
+ authSource: AuthStrategy; // 'gateway-header' | 'trusted-headers' | 'jwt'
276
+ }
277
+ ```
278
+
279
+ ---
280
+
281
+ ## Permission Resolution (GtPermissionsGuard)
282
+
283
+ The NestJS `GtPermissionsGuard` resolves permissions in 3 tiers:
284
+
285
+ 1. **JWT claims** — If `user.permissions` is non-empty, check in-memory (fast path)
286
+ 2. **permissionResolver** — If configured, call the function directly (for RBAC service)
287
+ 3. **RBAC HTTP call** — If `rbacServiceUrl` is configured, POST to `/v1/resolve/check`
288
+ 4. **Deny** — Fail-closed if no resolution mechanism is available
289
+
290
+ Admin roles (`config.adminRoles`) always bypass all checks.
291
+
292
+ ---
293
+
294
+ ## Express Middlewares
295
+
296
+ | Middleware | Purpose |
297
+ |-----------|---------|
298
+ | `authenticate` | Extracts `req.user` from strategy chain |
299
+ | `requireRole(...roles)` | Checks `req.user.role` against allowed roles |
300
+ | `requirePermission(...perms)` | Requires ALL listed permissions (admin bypass) |
301
+ | `requireAnyPermission(...perms)` | Requires AT LEAST ONE permission (admin bypass) |
302
+ | `requireTenant` | Ensures `req.user.tenantId` exists, sets `req.tenantId` |
303
+ | `requireSelf(paramName?)` | Compares `req.user.userId` with route param (admin bypass) |
304
+ | `authErrorHandler` | Error handler for `AuthError` subclasses |
305
+
306
+ ---
307
+
308
+ ## Error Handling
309
+
310
+ All auth errors extend `AuthError` and produce the standard Global Tracking error envelope:
311
+
312
+ ```typescript
313
+ import { UnauthorizedError, ForbiddenError } from '@globaltracking/auth-middleware';
314
+
315
+ throw new UnauthorizedError('Token expired');
316
+ // → 401 { success: false, error: { code: 'UNAUTHORIZED', message: 'Token expired', statusCode: 401 } }
317
+
318
+ throw new ForbiddenError('Insufficient permissions', { required: ['vehicles:write'] });
319
+ // → 403 { success: false, error: { code: 'FORBIDDEN', message: 'Insufficient permissions', statusCode: 403 } }
320
+ ```
321
+
322
+ ---
323
+
324
+ ## Utility Functions
325
+
326
+ ```typescript
327
+ import { extractUser, hasRole, hasPermission, hasAnyPermission } from '@globaltracking/auth-middleware';
328
+
329
+ const user = extractUser(req); // throws UnauthorizedError if missing
330
+ hasRole(user, 'system_admin'); // boolean
331
+ hasPermission(user, 'vehicles:read'); // all must match
332
+ hasAnyPermission(user, 'a:read', 'b:read'); // at least one
333
+ ```
334
+
335
+ ---
336
+
337
+ ## Files Replaced Per Service
338
+
339
+ When a NestJS service adopts this package, these ~10 files can be deleted:
340
+
341
+ ```
342
+ src/common/decorators/current-org.decorator.ts → @CurrentOrg from package
343
+ src/common/decorators/current-user.decorator.ts → @CurrentUser from package
344
+ src/common/decorators/permissions.decorator.ts → @RequirePermissions from package
345
+ src/common/decorators/public.decorator.ts → @Public from package
346
+ src/common/guards/internal-only.guard.ts → InternalOnlyGuard from package
347
+ src/common/guards/permissions.guard.ts → GtPermissionsGuard from package
348
+ src/common/interceptors/org-context.interceptor.ts → OrgContextInterceptor from package
349
+ src/common/middleware/trusted-headers.middleware.ts → GtTrustedHeadersMiddleware from package
350
+ src/common/interfaces/jwt-payload.interface.ts → AuthUser from package
351
+ src/common/interfaces/request-context.interface.ts → (Express augmentation from package)
352
+ ```
353
+
354
+ ---
355
+
356
+ ## Development
357
+
358
+ ```bash
359
+ npm test # run tests with coverage (89 tests, 98%+ coverage)
360
+ npm run test:watch # watch mode
361
+ npm run build # compile to dist/
362
+ npm run clean # remove dist/
363
+ ```
364
+
365
+ ---
366
+
367
+ ## Migration from v1.x
368
+
369
+ - `superAdminRole` → use `adminRoles: ['system_admin', 'org_admin']` (legacy `superAdminRole` still works)
370
+ - `RequestUser` type alias still exported for backward compatibility
371
+ - Error handler now returns `{ success: false, error: { code, message, statusCode } }` envelope instead of `{ error: message }`
372
+ - `extractUser()` now sets `authSource` and `requestId` on returned user
@@ -0,0 +1,9 @@
1
+ import { AuthConfig, ResolvedAuthConfig } from './types';
2
+ /**
3
+ * Initialize auth middleware configuration. Call once at app startup.
4
+ */
5
+ export declare function initAuth(config?: AuthConfig): void;
6
+ export declare function getConfig(): ResolvedAuthConfig;
7
+ /** Reset config to defaults (used in tests) */
8
+ export declare function resetConfig(): void;
9
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAyBzD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,GAAE,UAAe,GAAG,IAAI,CAqCtD;AAED,wBAAgB,SAAS,IAAI,kBAAkB,CAE9C;AAED,+CAA+C;AAC/C,wBAAgB,WAAW,IAAI,IAAI,CAElC"}
package/dist/config.js ADDED
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.initAuth = initAuth;
37
+ exports.getConfig = getConfig;
38
+ exports.resetConfig = resetConfig;
39
+ const fs = __importStar(require("fs"));
40
+ const DEFAULT_TRUSTED_HEADERS = {
41
+ userId: 'x-user-id',
42
+ orgId: 'x-org-id',
43
+ userRole: 'x-user-role',
44
+ requestId: 'x-request-id',
45
+ gatewayToken: 'x-gateway-token',
46
+ };
47
+ const DEFAULT_CONFIG = {
48
+ gatewayHeaderName: 'x-apigateway-api-userinfo',
49
+ jwtIssuer: 'globaltracking-auth',
50
+ publicKey: '',
51
+ publicKeyPath: '',
52
+ strategies: ['gateway-header', 'jwt'],
53
+ adminRoles: ['system_admin', 'org_admin'],
54
+ internalGatewayToken: '',
55
+ trustedHeaderNames: { ...DEFAULT_TRUSTED_HEADERS },
56
+ rbacServiceUrl: '',
57
+ permissionResolver: undefined,
58
+ };
59
+ let currentConfig = { ...DEFAULT_CONFIG, trustedHeaderNames: { ...DEFAULT_TRUSTED_HEADERS } };
60
+ /**
61
+ * Initialize auth middleware configuration. Call once at app startup.
62
+ */
63
+ function initAuth(config = {}) {
64
+ // Resolve adminRoles: explicit adminRoles > legacy superAdminRole > default
65
+ let adminRoles = DEFAULT_CONFIG.adminRoles;
66
+ if (config.adminRoles) {
67
+ adminRoles = config.adminRoles;
68
+ }
69
+ else if (config.superAdminRole) {
70
+ adminRoles = [config.superAdminRole];
71
+ }
72
+ currentConfig = {
73
+ gatewayHeaderName: config.gatewayHeaderName ?? DEFAULT_CONFIG.gatewayHeaderName,
74
+ jwtIssuer: config.jwtIssuer ?? DEFAULT_CONFIG.jwtIssuer,
75
+ publicKey: '',
76
+ publicKeyPath: '',
77
+ strategies: config.strategies ?? DEFAULT_CONFIG.strategies,
78
+ adminRoles,
79
+ internalGatewayToken: config.internalGatewayToken || process.env.INTERNAL_GATEWAY_TOKEN || '',
80
+ trustedHeaderNames: {
81
+ ...DEFAULT_TRUSTED_HEADERS,
82
+ ...config.trustedHeaderNames,
83
+ },
84
+ rbacServiceUrl: config.rbacServiceUrl ?? '',
85
+ permissionResolver: config.permissionResolver,
86
+ };
87
+ // Resolve public key: explicit config → env vars → empty (gateway-only mode)
88
+ if (config.publicKey) {
89
+ currentConfig.publicKey = config.publicKey;
90
+ }
91
+ else if (config.publicKeyPath) {
92
+ currentConfig.publicKey = fs.readFileSync(config.publicKeyPath, 'utf-8');
93
+ currentConfig.publicKeyPath = config.publicKeyPath;
94
+ }
95
+ else if (process.env.AUTH_PUBLIC_KEY) {
96
+ currentConfig.publicKey = process.env.AUTH_PUBLIC_KEY;
97
+ }
98
+ else if (process.env.AUTH_PUBLIC_KEY_PATH) {
99
+ currentConfig.publicKey = fs.readFileSync(process.env.AUTH_PUBLIC_KEY_PATH, 'utf-8');
100
+ currentConfig.publicKeyPath = process.env.AUTH_PUBLIC_KEY_PATH;
101
+ }
102
+ }
103
+ function getConfig() {
104
+ return currentConfig;
105
+ }
106
+ /** Reset config to defaults (used in tests) */
107
+ function resetConfig() {
108
+ currentConfig = { ...DEFAULT_CONFIG, trustedHeaderNames: { ...DEFAULT_TRUSTED_HEADERS } };
109
+ }
110
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Base auth error class. Works in both Express and NestJS contexts.
3
+ * Contains a `toResponse()` method that produces the standard error envelope.
4
+ */
5
+ export declare class AuthError extends Error {
6
+ readonly statusCode: number;
7
+ readonly code: string;
8
+ constructor(message: string, statusCode: number, code: string);
9
+ /** Returns the standard Global Tracking error envelope */
10
+ toResponse(): {
11
+ success: false;
12
+ error: {
13
+ code: string;
14
+ message: string;
15
+ statusCode: number;
16
+ };
17
+ };
18
+ }
19
+ export declare class UnauthorizedError extends AuthError {
20
+ constructor(message?: string);
21
+ }
22
+ export declare class ForbiddenError extends AuthError {
23
+ readonly details?: Record<string, unknown>;
24
+ constructor(message?: string, details?: Record<string, unknown>);
25
+ }
26
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,SAAgB,UAAU,EAAE,MAAM,CAAC;IACnC,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAQ7D,0DAA0D;IAC1D,UAAU,IAAI;QAAE,OAAO,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;CAU/F;AAED,qBAAa,iBAAkB,SAAQ,SAAS;gBAClC,OAAO,SAA4B;CAGhD;AAED,qBAAa,cAAe,SAAQ,SAAS;IAC3C,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAEtC,OAAO,SAAkB,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAIzE"}
package/dist/errors.js ADDED
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ForbiddenError = exports.UnauthorizedError = exports.AuthError = void 0;
4
+ /**
5
+ * Base auth error class. Works in both Express and NestJS contexts.
6
+ * Contains a `toResponse()` method that produces the standard error envelope.
7
+ */
8
+ class AuthError extends Error {
9
+ constructor(message, statusCode, code) {
10
+ super(message);
11
+ this.name = this.constructor.name;
12
+ this.statusCode = statusCode;
13
+ this.code = code;
14
+ Object.setPrototypeOf(this, new.target.prototype);
15
+ }
16
+ /** Returns the standard Global Tracking error envelope */
17
+ toResponse() {
18
+ return {
19
+ success: false,
20
+ error: {
21
+ code: this.code,
22
+ message: this.message,
23
+ statusCode: this.statusCode,
24
+ },
25
+ };
26
+ }
27
+ }
28
+ exports.AuthError = AuthError;
29
+ class UnauthorizedError extends AuthError {
30
+ constructor(message = 'Authentication required') {
31
+ super(message, 401, 'UNAUTHORIZED');
32
+ }
33
+ }
34
+ exports.UnauthorizedError = UnauthorizedError;
35
+ class ForbiddenError extends AuthError {
36
+ constructor(message = 'Access denied', details) {
37
+ super(message, 403, 'FORBIDDEN');
38
+ this.details = details;
39
+ }
40
+ }
41
+ exports.ForbiddenError = ForbiddenError;
42
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,MAAa,SAAU,SAAQ,KAAK;IAIlC,YAAY,OAAe,EAAE,UAAkB,EAAE,IAAY;QAC3D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QAClC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;IAED,0DAA0D;IAC1D,UAAU;QACR,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,UAAU,EAAE,IAAI,CAAC,UAAU;aAC5B;SACF,CAAC;IACJ,CAAC;CACF;AAvBD,8BAuBC;AAED,MAAa,iBAAkB,SAAQ,SAAS;IAC9C,YAAY,OAAO,GAAG,yBAAyB;QAC7C,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;IACtC,CAAC;CACF;AAJD,8CAIC;AAED,MAAa,cAAe,SAAQ,SAAS;IAG3C,YAAY,OAAO,GAAG,eAAe,EAAE,OAAiC;QACtE,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAPD,wCAOC"}
@@ -0,0 +1,10 @@
1
+ import { Request } from 'express';
2
+ import { AuthUser } from './types';
3
+ /**
4
+ * Extract AuthUser from a request using the configured strategy chain.
5
+ * Iterates through `config.strategies` in order, trying each until one succeeds.
6
+ *
7
+ * Throws UnauthorizedError if no strategy can handle the request.
8
+ */
9
+ export declare function extractUser(req: Request): AuthUser;
10
+ //# sourceMappingURL=extract-user.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-user.d.ts","sourceRoot":"","sources":["../src/extract-user.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC,OAAO,EAAE,QAAQ,EAAgB,MAAM,SAAS,CAAC;AAYjD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,CAWlD"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractUser = extractUser;
4
+ const config_1 = require("./config");
5
+ const errors_1 = require("./errors");
6
+ const gateway_header_strategy_1 = require("./strategies/gateway-header.strategy");
7
+ const trusted_headers_strategy_1 = require("./strategies/trusted-headers.strategy");
8
+ const jwt_strategy_1 = require("./strategies/jwt.strategy");
9
+ const strategyMap = {
10
+ 'gateway-header': new gateway_header_strategy_1.GatewayHeaderStrategy(),
11
+ 'trusted-headers': new trusted_headers_strategy_1.TrustedHeadersStrategy(),
12
+ 'jwt': new jwt_strategy_1.JwtStrategy(),
13
+ };
14
+ /**
15
+ * Extract AuthUser from a request using the configured strategy chain.
16
+ * Iterates through `config.strategies` in order, trying each until one succeeds.
17
+ *
18
+ * Throws UnauthorizedError if no strategy can handle the request.
19
+ */
20
+ function extractUser(req) {
21
+ const config = (0, config_1.getConfig)();
22
+ for (const strategyName of config.strategies) {
23
+ const strategy = strategyMap[strategyName];
24
+ if (strategy && strategy.canHandle(req)) {
25
+ return strategy.extract(req);
26
+ }
27
+ }
28
+ throw new errors_1.UnauthorizedError('No authentication credentials provided');
29
+ }
30
+ //# sourceMappingURL=extract-user.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-user.js","sourceRoot":"","sources":["../src/extract-user.ts"],"names":[],"mappings":";;AAqBA,kCAWC;AA/BD,qCAAqC;AACrC,qCAA6C;AAG7C,kFAA6E;AAC7E,oFAA+E;AAC/E,4DAAwD;AAExD,MAAM,WAAW,GAAiD;IAChE,gBAAgB,EAAE,IAAI,+CAAqB,EAAE;IAC7C,iBAAiB,EAAE,IAAI,iDAAsB,EAAE;IAC/C,KAAK,EAAE,IAAI,0BAAW,EAAE;CACzB,CAAC;AAEF;;;;;GAKG;AACH,SAAgB,WAAW,CAAC,GAAY;IACtC,MAAM,MAAM,GAAG,IAAA,kBAAS,GAAE,CAAC;IAE3B,KAAK,MAAM,YAAY,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAC7C,MAAM,QAAQ,GAAG,WAAW,CAAC,YAAY,CAAC,CAAC;QAC3C,IAAI,QAAQ,IAAI,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;YACxC,OAAO,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,MAAM,IAAI,0BAAiB,CAAC,wCAAwC,CAAC,CAAC;AACxE,CAAC"}
@@ -0,0 +1,16 @@
1
+ export { initAuth, getConfig, resetConfig } from './config';
2
+ export { AuthUser, RequestUser, AuthConfig, AuthStrategy, JwtPayload, UserRole, TrustedHeaderNames, ResolvedAuthConfig } from './types';
3
+ export { AuthError, UnauthorizedError, ForbiddenError } from './errors';
4
+ export { extractUser } from './extract-user';
5
+ export { AuthExtractionStrategy } from './strategies/strategy.interface';
6
+ export { GatewayHeaderStrategy } from './strategies/gateway-header.strategy';
7
+ export { TrustedHeadersStrategy } from './strategies/trusted-headers.strategy';
8
+ export { JwtStrategy } from './strategies/jwt.strategy';
9
+ export { authenticate } from './middlewares/authenticate';
10
+ export { requireRole } from './middlewares/require-role';
11
+ export { requirePermission, requireAnyPermission } from './middlewares/require-permission';
12
+ export { requireTenant } from './middlewares/require-tenant';
13
+ export { requireSelf } from './middlewares/require-self';
14
+ export { authErrorHandler } from './middlewares/error-handler';
15
+ export { hasRole, hasPermission, hasAnyPermission } from './utils/helpers';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAG5D,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGxI,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAGxE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAGxD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAC3F,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAG/D,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}