permissions-contractx 2.2.0 → 2.4.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.
- package/README.md +93 -0
- package/dist/constants/permission-names.constants.d.ts +40 -4
- package/dist/constants/permission-names.constants.d.ts.map +1 -1
- package/dist/constants/permission-names.constants.js +53 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/inter-service-auth/index.d.ts +8 -0
- package/dist/inter-service-auth/index.d.ts.map +1 -0
- package/dist/inter-service-auth/index.js +31 -0
- package/dist/inter-service-auth/inter-service-auth.config.interface.d.ts +10 -0
- package/dist/inter-service-auth/inter-service-auth.config.interface.d.ts.map +1 -0
- package/dist/inter-service-auth/inter-service-auth.config.interface.js +2 -0
- package/dist/inter-service-auth/inter-service-auth.middleware.d.ts +11 -0
- package/dist/inter-service-auth/inter-service-auth.middleware.d.ts.map +1 -0
- package/dist/inter-service-auth/inter-service-auth.middleware.js +75 -0
- package/dist/inter-service-auth/inter-service-auth.module.d.ts +11 -0
- package/dist/inter-service-auth/inter-service-auth.module.d.ts.map +1 -0
- package/dist/inter-service-auth/inter-service-auth.module.js +44 -0
- package/dist/inter-service-auth/inter-service-auth.service.d.ts +23 -0
- package/dist/inter-service-auth/inter-service-auth.service.d.ts.map +1 -0
- package/dist/inter-service-auth/inter-service-auth.service.js +202 -0
- package/dist/inter-service-auth/inter-service-auth.types.d.ts +57 -0
- package/dist/inter-service-auth/inter-service-auth.types.d.ts.map +1 -0
- package/dist/inter-service-auth/inter-service-auth.types.js +15 -0
- package/dist/inter-service-auth/service-auth.decorators.d.ts +12 -0
- package/dist/inter-service-auth/service-auth.decorators.d.ts.map +1 -0
- package/dist/inter-service-auth/service-auth.decorators.js +22 -0
- package/dist/inter-service-auth/service-auth.guard.d.ts +17 -0
- package/dist/inter-service-auth/service-auth.guard.d.ts.map +1 -0
- package/dist/inter-service-auth/service-auth.guard.js +121 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,6 +84,99 @@ import { PermissionWrites } from 'permissions-contractx';
|
|
|
84
84
|
search() { /* ... */ }
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
## Inter-Service Authentication (HMAC)
|
|
88
|
+
|
|
89
|
+
Además del modelo de autorización basado en JWT de usuario, el paquete incluye el sistema de autenticación **machine-to-machine (M2M)** que usan los microservicios de ContractX para llamarse entre sí de forma segura.
|
|
90
|
+
|
|
91
|
+
Cada llamada saliente lleva headers `X-Service-*` con una firma HMAC-SHA256 del par `apiKey + secretKey` del servicio emisor. El receptor verifica la firma, el timestamp (ventana de expiración + drift de reloj) y el nonce (anti-replay). Ningún JWT de usuario interviene en estas llamadas.
|
|
92
|
+
|
|
93
|
+
Para detalles de implementación, arquitectura y deuda conocida, ver [`src/inter-service-auth/README.md`](./src/inter-service-auth/README.md).
|
|
94
|
+
|
|
95
|
+
### Configurar el módulo
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { InterServiceAuthModule, SERVICE_NAMES } from 'permissions-contractx';
|
|
99
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
100
|
+
|
|
101
|
+
@Module({
|
|
102
|
+
imports: [
|
|
103
|
+
InterServiceAuthModule.forRootAsync({
|
|
104
|
+
imports: [ConfigModule],
|
|
105
|
+
useFactory: (cfg: ConfigService) => ({
|
|
106
|
+
services: {
|
|
107
|
+
[SERVICE_NAMES.AUTH]: {
|
|
108
|
+
serviceId: SERVICE_NAMES.AUTH,
|
|
109
|
+
apiKey: cfg.get('AUTH_API_KEY'),
|
|
110
|
+
secretKey: cfg.get('AUTH_SECRET_KEY'),
|
|
111
|
+
},
|
|
112
|
+
[SERVICE_NAMES.CONTRACTS]: {
|
|
113
|
+
serviceId: SERVICE_NAMES.CONTRACTS,
|
|
114
|
+
apiKey: cfg.get('CONTRACTS_API_KEY'),
|
|
115
|
+
secretKey: cfg.get('CONTRACTS_SECRET_KEY'),
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
signatureExpireMinutes: 5,
|
|
119
|
+
allowedTimeDriftSeconds: 30,
|
|
120
|
+
enableLogging: cfg.get('SERVICE_AUTH_LOGGING') === 'true',
|
|
121
|
+
}),
|
|
122
|
+
inject: [ConfigService],
|
|
123
|
+
}),
|
|
124
|
+
],
|
|
125
|
+
})
|
|
126
|
+
export class AppModule {}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
El middleware (`InterServiceAuthMiddleware`) debe registrarse en el módulo consumidor:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { InterServiceAuthMiddleware } from 'permissions-contractx';
|
|
133
|
+
|
|
134
|
+
export class AppModule implements NestModule {
|
|
135
|
+
configure(consumer: MiddlewareConsumer) {
|
|
136
|
+
consumer.apply(InterServiceAuthMiddleware).forRoutes('*');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Proteger un endpoint (solo M2M)
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import {
|
|
145
|
+
ServiceAuthGuard,
|
|
146
|
+
RequireServiceAuth,
|
|
147
|
+
RequireServicePermissions,
|
|
148
|
+
} from 'permissions-contractx';
|
|
149
|
+
|
|
150
|
+
@Controller('internal')
|
|
151
|
+
@RequireServiceAuth() // rechaza cualquier request no-M2M
|
|
152
|
+
@UseGuards(ServiceAuthGuard)
|
|
153
|
+
export class InternalController {
|
|
154
|
+
@Get('contracts')
|
|
155
|
+
@RequireServicePermissions('contracts.read')
|
|
156
|
+
listContracts() { /* ... */ }
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Emitir una llamada M2M saliente
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
constructor(private readonly authSvc: InterServiceAuthService) {}
|
|
164
|
+
|
|
165
|
+
async fetchFromContracts(schema: string) {
|
|
166
|
+
const headers = this.authSvc.generateAuthHeaders(SERVICE_NAMES.CONTRACTS, schema);
|
|
167
|
+
return this.httpService.get('/internal/contracts', { headers }).toPromise();
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Decoradores disponibles
|
|
172
|
+
|
|
173
|
+
| Decorador | Propósito |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `@RequireServiceAuth()` | Endpoint exclusivo M2M; rechaza JWT de usuario |
|
|
176
|
+
| `@AllowServiceAuth(false)` | Bloquea M2M en un endpoint específico (gana sobre clase) |
|
|
177
|
+
| `@RequireServicePermissions(...p)` | El servicio llamante debe tener todos los permisos listados |
|
|
178
|
+
| `@RequireServiceRoles(...r)` | El servicio llamante debe tener al menos uno de los roles listados |
|
|
179
|
+
|
|
87
180
|
## Modelo de cuatro estados (migración)
|
|
88
181
|
|
|
89
182
|
El `AuthorizationGuard` permite migrar cada endpoint de forma gradual mediante la variable de entorno de enforcement:
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* permission-names.constants.ts
|
|
3
3
|
* ------------------------------------------------------------------------
|
|
4
|
-
* Constantes de NOMBRES (codes) de los
|
|
4
|
+
* Constantes de NOMBRES (codes) de los 264 permisos de dominio de ContractX.
|
|
5
5
|
* Derivadas del catálogo de Auth (domain-permissions.catalog.ts, ADR-004 Fase 1/2),
|
|
6
6
|
* tomando SOLO el campo `code` — SIN la metadata de seeding (rolesGranted,
|
|
7
7
|
* rolesReadOnly, approvalCategory, side, requiresHuman), que vive solo en Auth.
|
|
8
|
+
* Los comentarios `// rolesGranted: ...` sobre cada constante nueva desde
|
|
9
|
+
* v2.4.0 son referencia informal para la siembra en Auth, no metadata real
|
|
10
|
+
* de este paquete.
|
|
8
11
|
*
|
|
9
12
|
* Fuente única de NOMBRES: este archivo (en el paquete permissions-contractx).
|
|
10
13
|
* La metadata se cuelga encima, en Auth. Un test en Auth valida que todo `code`
|
|
11
14
|
* del catálogo existe como constante aquí (sincronización — Fase 6/8 del porte).
|
|
12
15
|
*
|
|
13
|
-
* Agrupación por microservicio: Auth(13) Contratos(
|
|
14
|
-
* Facturación(
|
|
16
|
+
* Agrupación por microservicio: Auth(13) Contratos(73) Entregables(52)
|
|
17
|
+
* Facturación(29) SLAs(45) Reuniones(2) UserContractRole(3) Proveedores(46)
|
|
18
|
+
* Workflows(1) = 264.
|
|
15
19
|
*
|
|
16
20
|
* Convención de claves: reflejan el recurso completo del code, en
|
|
17
21
|
* SCREAMING_SNAKE_CASE (recurso + acción). Esto evita colisiones entre dominios
|
|
@@ -98,6 +102,17 @@ export declare const CONTRATOS_PERMISSIONS: {
|
|
|
98
102
|
readonly WORK_ORDERS_DELETE: "work_orders.delete";
|
|
99
103
|
readonly RAID_DECISIONS_DELETE: "raid_decisions.delete";
|
|
100
104
|
readonly RAID_ISSUES_DELETE: "raid_issues.delete";
|
|
105
|
+
readonly CONTRACT_TYPES_VIEW: "contract_types.view";
|
|
106
|
+
readonly CONTRACT_TYPES_VIEW_DETAIL: "contract_types.view_detail";
|
|
107
|
+
readonly CONTRACT_CLAUSES_VIEW_WALK_AWAY_NOTES: "contract_clauses.view_walk_away_notes";
|
|
108
|
+
readonly CONTRACT_CLAUSES_VIEW_NEGOTIATION_NOTES: "contract_clauses.view_negotiation_notes";
|
|
109
|
+
readonly CONTRACT_CLAUSE_DEVIATIONS_VIEW: "contract_clause_deviations.view";
|
|
110
|
+
readonly CONTRACT_CLAUSE_DEVIATIONS_CREATE: "contract_clause_deviations.create";
|
|
111
|
+
readonly CONTRACT_CLAUSE_DEVIATIONS_APPROVE_WALK_AWAY_BREACH: "contract_clause_deviations.approve_walk_away_breach";
|
|
112
|
+
readonly CONTRACT_OBLIGATIONS_VIEW_LIST: "contract_obligations.view_list";
|
|
113
|
+
readonly CONTRACT_OBLIGATIONS_VIEW_DETAIL: "contract_obligations.view_detail";
|
|
114
|
+
readonly CONTRACT_OBLIGATIONS_WAIVE: "contract_obligations.waive";
|
|
115
|
+
readonly CONTRACTS_VIEW_RISK_DETAILS: "contracts.view_risk_details";
|
|
101
116
|
};
|
|
102
117
|
export declare const ENTREGABLES_PERMISSIONS: {
|
|
103
118
|
readonly DELIVERABLES_CREATE: "deliverables.create";
|
|
@@ -150,6 +165,8 @@ export declare const ENTREGABLES_PERMISSIONS: {
|
|
|
150
165
|
readonly REJECTION_REASONS_DELETE: "rejection_reasons.delete";
|
|
151
166
|
readonly DELIVERABLES_PLANNING_PLAN: "deliverables.planning.plan";
|
|
152
167
|
readonly DELIVERABLES_PLANNING_REPLAN: "deliverables.planning.replan";
|
|
168
|
+
readonly DELIVERABLE_REJECTION_CATALOG_MANAGE: "deliverable_rejection_catalog.manage";
|
|
169
|
+
readonly DELIVERABLES_RESOLVE_DISPUTE: "deliverables.resolve_dispute";
|
|
153
170
|
};
|
|
154
171
|
export declare const FACTURACION_PERMISSIONS: {
|
|
155
172
|
readonly INVOICES_REGISTER: "invoices.register";
|
|
@@ -337,6 +354,9 @@ export declare const PROVEEDORES_PERMISSIONS: {
|
|
|
337
354
|
readonly PROVIDERS_RISKS_DELETE: "providers.risks.delete";
|
|
338
355
|
readonly PROVIDERS_SURVEYS_DELETE: "providers.surveys.delete";
|
|
339
356
|
};
|
|
357
|
+
export declare const WORKFLOWS_PERMISSIONS: {
|
|
358
|
+
readonly WORKFLOWS_ADMIN: "workflows.admin";
|
|
359
|
+
};
|
|
340
360
|
/** Todas las constantes agrupadas por dominio. */
|
|
341
361
|
export declare const PERMISSIONS_BY_DOMAIN: {
|
|
342
362
|
readonly Auth: {
|
|
@@ -417,6 +437,17 @@ export declare const PERMISSIONS_BY_DOMAIN: {
|
|
|
417
437
|
readonly WORK_ORDERS_DELETE: "work_orders.delete";
|
|
418
438
|
readonly RAID_DECISIONS_DELETE: "raid_decisions.delete";
|
|
419
439
|
readonly RAID_ISSUES_DELETE: "raid_issues.delete";
|
|
440
|
+
readonly CONTRACT_TYPES_VIEW: "contract_types.view";
|
|
441
|
+
readonly CONTRACT_TYPES_VIEW_DETAIL: "contract_types.view_detail";
|
|
442
|
+
readonly CONTRACT_CLAUSES_VIEW_WALK_AWAY_NOTES: "contract_clauses.view_walk_away_notes";
|
|
443
|
+
readonly CONTRACT_CLAUSES_VIEW_NEGOTIATION_NOTES: "contract_clauses.view_negotiation_notes";
|
|
444
|
+
readonly CONTRACT_CLAUSE_DEVIATIONS_VIEW: "contract_clause_deviations.view";
|
|
445
|
+
readonly CONTRACT_CLAUSE_DEVIATIONS_CREATE: "contract_clause_deviations.create";
|
|
446
|
+
readonly CONTRACT_CLAUSE_DEVIATIONS_APPROVE_WALK_AWAY_BREACH: "contract_clause_deviations.approve_walk_away_breach";
|
|
447
|
+
readonly CONTRACT_OBLIGATIONS_VIEW_LIST: "contract_obligations.view_list";
|
|
448
|
+
readonly CONTRACT_OBLIGATIONS_VIEW_DETAIL: "contract_obligations.view_detail";
|
|
449
|
+
readonly CONTRACT_OBLIGATIONS_WAIVE: "contract_obligations.waive";
|
|
450
|
+
readonly CONTRACTS_VIEW_RISK_DETAILS: "contracts.view_risk_details";
|
|
420
451
|
};
|
|
421
452
|
readonly Entregables: {
|
|
422
453
|
readonly DELIVERABLES_CREATE: "deliverables.create";
|
|
@@ -469,6 +500,8 @@ export declare const PERMISSIONS_BY_DOMAIN: {
|
|
|
469
500
|
readonly REJECTION_REASONS_DELETE: "rejection_reasons.delete";
|
|
470
501
|
readonly DELIVERABLES_PLANNING_PLAN: "deliverables.planning.plan";
|
|
471
502
|
readonly DELIVERABLES_PLANNING_REPLAN: "deliverables.planning.replan";
|
|
503
|
+
readonly DELIVERABLE_REJECTION_CATALOG_MANAGE: "deliverable_rejection_catalog.manage";
|
|
504
|
+
readonly DELIVERABLES_RESOLVE_DISPUTE: "deliverables.resolve_dispute";
|
|
472
505
|
};
|
|
473
506
|
readonly Facturacion: {
|
|
474
507
|
readonly INVOICES_REGISTER: "invoices.register";
|
|
@@ -609,9 +642,12 @@ export declare const PERMISSIONS_BY_DOMAIN: {
|
|
|
609
642
|
readonly PROVIDERS_RISKS_DELETE: "providers.risks.delete";
|
|
610
643
|
readonly PROVIDERS_SURVEYS_DELETE: "providers.surveys.delete";
|
|
611
644
|
};
|
|
645
|
+
readonly Workflows: {
|
|
646
|
+
readonly WORKFLOWS_ADMIN: "workflows.admin";
|
|
647
|
+
};
|
|
612
648
|
};
|
|
613
649
|
/** Lista plana de los permission codes (para validación de sincronización con Auth). */
|
|
614
650
|
export declare const ALL_PERMISSION_CODES: readonly string[];
|
|
615
651
|
/** Tipo unión de todos los codes de permiso válidos. */
|
|
616
|
-
export type PermissionCode = (typeof AUTH_PERMISSIONS)[keyof typeof AUTH_PERMISSIONS] | (typeof CONTRATOS_PERMISSIONS)[keyof typeof CONTRATOS_PERMISSIONS] | (typeof ENTREGABLES_PERMISSIONS)[keyof typeof ENTREGABLES_PERMISSIONS] | (typeof FACTURACION_PERMISSIONS)[keyof typeof FACTURACION_PERMISSIONS] | (typeof SLAS_PERMISSIONS)[keyof typeof SLAS_PERMISSIONS] | (typeof REUNIONES_PERMISSIONS)[keyof typeof REUNIONES_PERMISSIONS] | (typeof USER_CONTRACT_ROLE_PERMISSIONS)[keyof typeof USER_CONTRACT_ROLE_PERMISSIONS] | (typeof PROVEEDORES_PERMISSIONS)[keyof typeof PROVEEDORES_PERMISSIONS];
|
|
652
|
+
export type PermissionCode = (typeof AUTH_PERMISSIONS)[keyof typeof AUTH_PERMISSIONS] | (typeof CONTRATOS_PERMISSIONS)[keyof typeof CONTRATOS_PERMISSIONS] | (typeof ENTREGABLES_PERMISSIONS)[keyof typeof ENTREGABLES_PERMISSIONS] | (typeof FACTURACION_PERMISSIONS)[keyof typeof FACTURACION_PERMISSIONS] | (typeof SLAS_PERMISSIONS)[keyof typeof SLAS_PERMISSIONS] | (typeof REUNIONES_PERMISSIONS)[keyof typeof REUNIONES_PERMISSIONS] | (typeof USER_CONTRACT_ROLE_PERMISSIONS)[keyof typeof USER_CONTRACT_ROLE_PERMISSIONS] | (typeof PROVEEDORES_PERMISSIONS)[keyof typeof PROVEEDORES_PERMISSIONS] | (typeof WORKFLOWS_PERMISSIONS)[keyof typeof WORKFLOWS_PERMISSIONS];
|
|
617
653
|
//# sourceMappingURL=permission-names.constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-names.constants.d.ts","sourceRoot":"","sources":["../../src/constants/permission-names.constants.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"permission-names.constants.d.ts","sourceRoot":"","sources":["../../src/constants/permission-names.constants.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* permission-names.constants.ts
|
|
4
4
|
* ------------------------------------------------------------------------
|
|
5
|
-
* Constantes de NOMBRES (codes) de los
|
|
5
|
+
* Constantes de NOMBRES (codes) de los 264 permisos de dominio de ContractX.
|
|
6
6
|
* Derivadas del catálogo de Auth (domain-permissions.catalog.ts, ADR-004 Fase 1/2),
|
|
7
7
|
* tomando SOLO el campo `code` — SIN la metadata de seeding (rolesGranted,
|
|
8
8
|
* rolesReadOnly, approvalCategory, side, requiresHuman), que vive solo en Auth.
|
|
9
|
+
* Los comentarios `// rolesGranted: ...` sobre cada constante nueva desde
|
|
10
|
+
* v2.4.0 son referencia informal para la siembra en Auth, no metadata real
|
|
11
|
+
* de este paquete.
|
|
9
12
|
*
|
|
10
13
|
* Fuente única de NOMBRES: este archivo (en el paquete permissions-contractx).
|
|
11
14
|
* La metadata se cuelga encima, en Auth. Un test en Auth valida que todo `code`
|
|
12
15
|
* del catálogo existe como constante aquí (sincronización — Fase 6/8 del porte).
|
|
13
16
|
*
|
|
14
|
-
* Agrupación por microservicio: Auth(13) Contratos(
|
|
15
|
-
* Facturación(
|
|
17
|
+
* Agrupación por microservicio: Auth(13) Contratos(73) Entregables(52)
|
|
18
|
+
* Facturación(29) SLAs(45) Reuniones(2) UserContractRole(3) Proveedores(46)
|
|
19
|
+
* Workflows(1) = 264.
|
|
16
20
|
*
|
|
17
21
|
* Convención de claves: reflejan el recurso completo del code, en
|
|
18
22
|
* SCREAMING_SNAKE_CASE (recurso + acción). Esto evita colisiones entre dominios
|
|
@@ -23,7 +27,7 @@
|
|
|
23
27
|
*/
|
|
24
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
29
|
exports.PROVIDERS_EVALS_DELETE = exports.PROVIDERS_DEPS_DELETE = exports.PROVIDERS_CONTACTS_DELETE = exports.PROVIDERS_PROFILE_DELETE = exports.PROVIDERS_REPORTS_VIEW = exports.PROVIDERS_REPORTS_INCIDENTS = exports.PROVIDERS_REPORTS_SURVEYS = exports.PROVIDERS_REPORTS_RISKS = exports.PROVIDERS_REPORTS_EVALUATIONS = exports.PROVIDERS_REPORTS_GENERATE = exports.PROVIDERS_INCIDENTS_VIEW = exports.PROVIDERS_INCIDENTS_REOPEN = exports.PROVIDERS_INCIDENTS_CLOSE = exports.PROVIDERS_INCIDENTS_MANAGE = exports.PROVIDERS_INCIDENTS_ASSIGN = exports.PROVIDERS_INCIDENTS_REGISTER = exports.PROVIDERS_SURVEYS_VIEW = exports.PROVIDERS_SURVEYS_RESPOND = exports.PROVIDERS_SURVEYS_MANAGE_LIFECYCLE = exports.PROVIDERS_SURVEYS_EDIT_QUESTIONS = exports.PROVIDERS_SURVEYS_CREATE = exports.PROVIDERS_RISKS_VIEW_PLANS = exports.PROVIDERS_RISKS_EXECUTE_ACTION = exports.PROVIDERS_RISKS_CREATE_PLAN = exports.PROVIDERS_RISKS_VIEW_MATRIX = exports.PROVIDERS_RISKS_EDIT = exports.PROVIDERS_RISKS_REGISTER = exports.PROVIDERS_EVALS_VIEW = exports.PROVIDERS_EVALS_CANCEL = exports.PROVIDERS_EVALS_EXECUTE = exports.PROVIDERS_EVALS_SCHEDULE = exports.PROVIDERS_DEPS_VIEW = exports.PROVIDERS_DEPS_MANAGE = exports.PROVIDERS_CONTACTS_SET_PRIMARY = exports.PROVIDERS_CONTACTS_VIEW = exports.PROVIDERS_CONTACTS_MANAGE = exports.PROVIDERS_PROFILE_CHANGE_RISK = exports.PROVIDERS_PROFILE_VIEW_BANK_INFO = exports.PROVIDERS_PROFILE_VIEW = exports.PROVIDERS_PROFILE_BLACKLIST = exports.PROVIDERS_PROFILE_CHANGE_STATUS = exports.PROVIDERS_PROFILE_EDIT = exports.PROVIDERS_PROFILE_CREATE = exports.USER_CONTRACT_ROLE_PERMISSIONS = exports.REUNIONES_PERMISSIONS = exports.SLAS_PERMISSIONS = exports.FACTURACION_PERMISSIONS = exports.ENTREGABLES_PERMISSIONS = exports.CONTRATOS_PERMISSIONS = exports.AUTH_PERMISSIONS = void 0;
|
|
26
|
-
exports.ALL_PERMISSION_CODES = exports.PERMISSIONS_BY_DOMAIN = exports.PROVEEDORES_PERMISSIONS = exports.PROVIDERS_SURVEYS_DELETE = exports.PROVIDERS_RISKS_DELETE = exports.PROVIDERS_INCIDENTS_DELETE = void 0;
|
|
30
|
+
exports.ALL_PERMISSION_CODES = exports.PERMISSIONS_BY_DOMAIN = exports.WORKFLOWS_PERMISSIONS = exports.PROVEEDORES_PERMISSIONS = exports.PROVIDERS_SURVEYS_DELETE = exports.PROVIDERS_RISKS_DELETE = exports.PROVIDERS_INCIDENTS_DELETE = void 0;
|
|
27
31
|
// ======================= AUTH (13) =======================
|
|
28
32
|
exports.AUTH_PERMISSIONS = {
|
|
29
33
|
ADMIN_USERS_CREATE_ANY_TENANT: 'admin.users.create_any_tenant',
|
|
@@ -40,7 +44,7 @@ exports.AUTH_PERMISSIONS = {
|
|
|
40
44
|
ADMIN_SESSIONS_VIEW_ME: 'admin.sessions.view_me',
|
|
41
45
|
ADMIN_DELEGATION_MANAGE_DELEGATE: 'admin.delegation.manage_delegate',
|
|
42
46
|
};
|
|
43
|
-
// ======================= CONTRATOS (
|
|
47
|
+
// ======================= CONTRATOS (73) =======================
|
|
44
48
|
exports.CONTRATOS_PERMISSIONS = {
|
|
45
49
|
// contracts.*
|
|
46
50
|
CONTRACTS_CREATE: 'contracts.create',
|
|
@@ -112,8 +116,35 @@ exports.CONTRATOS_PERMISSIONS = {
|
|
|
112
116
|
WORK_ORDERS_DELETE: 'work_orders.delete',
|
|
113
117
|
RAID_DECISIONS_DELETE: 'raid_decisions.delete',
|
|
114
118
|
RAID_ISSUES_DELETE: 'raid_issues.delete',
|
|
119
|
+
// contract_types.* (PR 0.b MVPV0 — scaffold contract_type + playbook, v2.4.0)
|
|
120
|
+
// rolesGranted: super_admin, client_superadmin, client_contract_admin, client_finance_resp
|
|
121
|
+
CONTRACT_TYPES_VIEW: 'contract_types.view',
|
|
122
|
+
// rolesGranted: super_admin, client_superadmin, client_contract_admin, client_finance_resp
|
|
123
|
+
CONTRACT_TYPES_VIEW_DETAIL: 'contract_types.view_detail',
|
|
124
|
+
// contract_clauses.* — notas internas (PR 0.c MVPV0, v2.4.0)
|
|
125
|
+
// rolesGranted: super_admin, client_superadmin
|
|
126
|
+
CONTRACT_CLAUSES_VIEW_WALK_AWAY_NOTES: 'contract_clauses.view_walk_away_notes',
|
|
127
|
+
// rolesGranted: super_admin, client_superadmin, client_contract_admin
|
|
128
|
+
CONTRACT_CLAUSES_VIEW_NEGOTIATION_NOTES: 'contract_clauses.view_negotiation_notes',
|
|
129
|
+
// contract_clause_deviations.* (PR 0.c MVPV0, v2.4.0)
|
|
130
|
+
// rolesGranted: super_admin, client_superadmin, client_contract_admin, client_finance_resp
|
|
131
|
+
CONTRACT_CLAUSE_DEVIATIONS_VIEW: 'contract_clause_deviations.view',
|
|
132
|
+
// rolesGranted: super_admin, client_superadmin, client_contract_admin
|
|
133
|
+
CONTRACT_CLAUSE_DEVIATIONS_CREATE: 'contract_clause_deviations.create',
|
|
134
|
+
// rolesGranted: super_admin, client_superadmin
|
|
135
|
+
CONTRACT_CLAUSE_DEVIATIONS_APPROVE_WALK_AWAY_BREACH: 'contract_clause_deviations.approve_walk_away_breach',
|
|
136
|
+
// contract_obligations.* (PR 0.a MVPV0, v2.4.0)
|
|
137
|
+
// rolesGranted: super_admin, client_superadmin, client_contract_admin, client_finance_resp, client_performance_resp
|
|
138
|
+
CONTRACT_OBLIGATIONS_VIEW_LIST: 'contract_obligations.view_list',
|
|
139
|
+
// rolesGranted: super_admin, client_superadmin, client_contract_admin, client_finance_resp, client_performance_resp
|
|
140
|
+
CONTRACT_OBLIGATIONS_VIEW_DETAIL: 'contract_obligations.view_detail',
|
|
141
|
+
// rolesGranted: super_admin, client_superadmin, client_contract_admin
|
|
142
|
+
CONTRACT_OBLIGATIONS_WAIVE: 'contract_obligations.waive',
|
|
143
|
+
// contracts.view_risk_details (PR 0.d MVPV0, v2.4.0)
|
|
144
|
+
// rolesGranted: super_admin, client_superadmin, client_contract_admin, client_finance_resp
|
|
145
|
+
CONTRACTS_VIEW_RISK_DETAILS: 'contracts.view_risk_details',
|
|
115
146
|
};
|
|
116
|
-
// ======================= ENTREGABLES (
|
|
147
|
+
// ======================= ENTREGABLES (52) =======================
|
|
117
148
|
exports.ENTREGABLES_PERMISSIONS = {
|
|
118
149
|
// deliverables.*
|
|
119
150
|
DELIVERABLES_CREATE: 'deliverables.create',
|
|
@@ -178,6 +209,11 @@ exports.ENTREGABLES_PERMISSIONS = {
|
|
|
178
209
|
// deliverables.planning.*
|
|
179
210
|
DELIVERABLES_PLANNING_PLAN: 'deliverables.planning.plan',
|
|
180
211
|
DELIVERABLES_PLANNING_REPLAN: 'deliverables.planning.replan',
|
|
212
|
+
// Sesión Entregables 2026-07-01, Matriz V7.3 publicada 2026-07-02 (v2.4.0)
|
|
213
|
+
// rolesGranted: super_admin, client_superadmin, client_contract_admin, client_performance_resp
|
|
214
|
+
DELIVERABLE_REJECTION_CATALOG_MANAGE: 'deliverable_rejection_catalog.manage',
|
|
215
|
+
// rolesGranted: super_admin, client_superadmin, client_contract_admin (RR-C excluido por conflicto de interés)
|
|
216
|
+
DELIVERABLES_RESOLVE_DISPUTE: 'deliverables.resolve_dispute',
|
|
181
217
|
};
|
|
182
218
|
// ======================= FACTURACIÓN (29) =======================
|
|
183
219
|
exports.FACTURACION_PERMISSIONS = {
|
|
@@ -399,6 +435,15 @@ exports.PROVEEDORES_PERMISSIONS = {
|
|
|
399
435
|
PROVIDERS_RISKS_DELETE: exports.PROVIDERS_RISKS_DELETE,
|
|
400
436
|
PROVIDERS_SURVEYS_DELETE: exports.PROVIDERS_SURVEYS_DELETE,
|
|
401
437
|
};
|
|
438
|
+
// ======================= WORKFLOWS (1) =======================
|
|
439
|
+
// Módulo: Flujos (contractx-workflows, puerto 3005).
|
|
440
|
+
// Fase 1: workflows.admin (semilla del dominio, v2.4.0).
|
|
441
|
+
// Permisos adicionales llegarán en v2.5.0+ tras cerrar la Fase 10 de la
|
|
442
|
+
// auditoría cruzada de Notificaciones y Permisos (ver BITACORA).
|
|
443
|
+
exports.WORKFLOWS_PERMISSIONS = {
|
|
444
|
+
// rolesGranted: super_admin (exclusivo, por diseño)
|
|
445
|
+
WORKFLOWS_ADMIN: 'workflows.admin',
|
|
446
|
+
};
|
|
402
447
|
// ======================= AGREGADOS Y TIPOS =======================
|
|
403
448
|
/** Todas las constantes agrupadas por dominio. */
|
|
404
449
|
exports.PERMISSIONS_BY_DOMAIN = {
|
|
@@ -410,6 +455,7 @@ exports.PERMISSIONS_BY_DOMAIN = {
|
|
|
410
455
|
Reuniones: exports.REUNIONES_PERMISSIONS,
|
|
411
456
|
UserContractRole: exports.USER_CONTRACT_ROLE_PERMISSIONS,
|
|
412
457
|
Proveedores: exports.PROVEEDORES_PERMISSIONS,
|
|
458
|
+
Workflows: exports.WORKFLOWS_PERMISSIONS,
|
|
413
459
|
};
|
|
414
460
|
/** Lista plana de los permission codes (para validación de sincronización con Auth). */
|
|
415
461
|
exports.ALL_PERMISSION_CODES = [
|
|
@@ -421,4 +467,5 @@ exports.ALL_PERMISSION_CODES = [
|
|
|
421
467
|
...Object.values(exports.REUNIONES_PERMISSIONS),
|
|
422
468
|
...Object.values(exports.USER_CONTRACT_ROLE_PERMISSIONS),
|
|
423
469
|
...Object.values(exports.PROVEEDORES_PERMISSIONS),
|
|
470
|
+
...Object.values(exports.WORKFLOWS_PERMISSIONS),
|
|
424
471
|
];
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,6 @@ export * from './decorators';
|
|
|
4
4
|
export * from './services';
|
|
5
5
|
export * from './interfaces';
|
|
6
6
|
export * from './constants';
|
|
7
|
+
export * from './inter-service-auth';
|
|
7
8
|
export type { JwtPayload, AuthenticatedRequest, PermissionsModuleOptions, JwtAuthConfig } from './interfaces';
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,YAAY,EAAE,UAAU,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,YAAY,EAAE,UAAU,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './inter-service-auth.types';
|
|
2
|
+
export * from './inter-service-auth.config.interface';
|
|
3
|
+
export { InterServiceAuthService, INTER_SERVICE_AUTH_CONFIG } from './inter-service-auth.service';
|
|
4
|
+
export * from './inter-service-auth.module';
|
|
5
|
+
export * from './inter-service-auth.middleware';
|
|
6
|
+
export { ServiceAuthGuard, SERVICE_PERMISSIONS_KEY, SERVICE_ROLES_KEY, ALLOW_SERVICE_AUTH_KEY, REQUIRE_SERVICE_AUTH_KEY, } from './service-auth.guard';
|
|
7
|
+
export * from './service-auth.decorators';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/inter-service-auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uCAAuC,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAClG,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.REQUIRE_SERVICE_AUTH_KEY = exports.ALLOW_SERVICE_AUTH_KEY = exports.SERVICE_ROLES_KEY = exports.SERVICE_PERMISSIONS_KEY = exports.ServiceAuthGuard = exports.INTER_SERVICE_AUTH_CONFIG = exports.InterServiceAuthService = void 0;
|
|
18
|
+
__exportStar(require("./inter-service-auth.types"), exports);
|
|
19
|
+
__exportStar(require("./inter-service-auth.config.interface"), exports);
|
|
20
|
+
var inter_service_auth_service_1 = require("./inter-service-auth.service");
|
|
21
|
+
Object.defineProperty(exports, "InterServiceAuthService", { enumerable: true, get: function () { return inter_service_auth_service_1.InterServiceAuthService; } });
|
|
22
|
+
Object.defineProperty(exports, "INTER_SERVICE_AUTH_CONFIG", { enumerable: true, get: function () { return inter_service_auth_service_1.INTER_SERVICE_AUTH_CONFIG; } });
|
|
23
|
+
__exportStar(require("./inter-service-auth.module"), exports);
|
|
24
|
+
__exportStar(require("./inter-service-auth.middleware"), exports);
|
|
25
|
+
var service_auth_guard_1 = require("./service-auth.guard");
|
|
26
|
+
Object.defineProperty(exports, "ServiceAuthGuard", { enumerable: true, get: function () { return service_auth_guard_1.ServiceAuthGuard; } });
|
|
27
|
+
Object.defineProperty(exports, "SERVICE_PERMISSIONS_KEY", { enumerable: true, get: function () { return service_auth_guard_1.SERVICE_PERMISSIONS_KEY; } });
|
|
28
|
+
Object.defineProperty(exports, "SERVICE_ROLES_KEY", { enumerable: true, get: function () { return service_auth_guard_1.SERVICE_ROLES_KEY; } });
|
|
29
|
+
Object.defineProperty(exports, "ALLOW_SERVICE_AUTH_KEY", { enumerable: true, get: function () { return service_auth_guard_1.ALLOW_SERVICE_AUTH_KEY; } });
|
|
30
|
+
Object.defineProperty(exports, "REQUIRE_SERVICE_AUTH_KEY", { enumerable: true, get: function () { return service_auth_guard_1.REQUIRE_SERVICE_AUTH_KEY; } });
|
|
31
|
+
__exportStar(require("./service-auth.decorators"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { IServiceCredentials } from './inter-service-auth.types';
|
|
2
|
+
export interface IServiceAuthConfig {
|
|
3
|
+
services: Record<string, IServiceCredentials>;
|
|
4
|
+
/** Minutes before a signed request expires (looking backward). Default: 5 */
|
|
5
|
+
signatureExpireMinutes?: number;
|
|
6
|
+
/** Seconds of future clock skew tolerated (looking forward). Default: 30 */
|
|
7
|
+
allowedTimeDriftSeconds?: number;
|
|
8
|
+
enableLogging?: boolean;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=inter-service-auth.config.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inter-service-auth.config.interface.d.ts","sourceRoot":"","sources":["../../src/inter-service-auth/inter-service-auth.config.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC9C,6EAA6E;IAC7E,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,4EAA4E;IAC5E,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NestMiddleware } from '@nestjs/common';
|
|
2
|
+
import { Request, Response, NextFunction } from 'express';
|
|
3
|
+
import { InterServiceAuthService } from './inter-service-auth.service';
|
|
4
|
+
export declare class InterServiceAuthMiddleware implements NestMiddleware {
|
|
5
|
+
private readonly interServiceAuthService;
|
|
6
|
+
private readonly logger;
|
|
7
|
+
constructor(interServiceAuthService: InterServiceAuthService);
|
|
8
|
+
use(req: Request, res: Response, next: NextFunction): void | Response<any, Record<string, any>>;
|
|
9
|
+
private normalizeHeaders;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=inter-service-auth.middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inter-service-auth.middleware.d.ts","sourceRoot":"","sources":["../../src/inter-service-auth/inter-service-auth.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,cAAc,EAAiC,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,qBACa,0BAA2B,YAAW,cAAc;IAGnD,OAAO,CAAC,QAAQ,CAAC,uBAAuB;IAFpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4C;gBAEtC,uBAAuB,EAAE,uBAAuB;IAE7E,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY;IA4CnD,OAAO,CAAC,gBAAgB;CAczB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.InterServiceAuthMiddleware = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const inter_service_auth_service_1 = require("./inter-service-auth.service");
|
|
15
|
+
let InterServiceAuthMiddleware = class InterServiceAuthMiddleware {
|
|
16
|
+
constructor(interServiceAuthService) {
|
|
17
|
+
this.interServiceAuthService = interServiceAuthService;
|
|
18
|
+
this.logger = new common_1.Logger('InterServiceAuthMiddleware');
|
|
19
|
+
}
|
|
20
|
+
use(req, res, next) {
|
|
21
|
+
try {
|
|
22
|
+
const isServiceAuth = req.headers['x-service-auth'] === 'true';
|
|
23
|
+
if (!isServiceAuth) {
|
|
24
|
+
// Not a service-to-service request — pass through for normal auth guards
|
|
25
|
+
req.isServiceRequest = false;
|
|
26
|
+
return next();
|
|
27
|
+
}
|
|
28
|
+
const headers = this.normalizeHeaders(req.headers);
|
|
29
|
+
const servicePayload = this.interServiceAuthService.validateServiceAuth(headers);
|
|
30
|
+
req.serviceAuth = servicePayload;
|
|
31
|
+
req.isServiceRequest = true;
|
|
32
|
+
this.logger.debug(`Service-to-service call authenticated: ${servicePayload.serviceId} -> ${req.path}`);
|
|
33
|
+
next();
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
37
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
38
|
+
this.logger.error(`Service authentication failed: ${message}`, stack);
|
|
39
|
+
if (error instanceof common_1.UnauthorizedException) {
|
|
40
|
+
return res.status(401).json({
|
|
41
|
+
statusCode: 401,
|
|
42
|
+
message: 'Service authentication failed',
|
|
43
|
+
error: 'Unauthorized',
|
|
44
|
+
timestamp: new Date().toISOString(),
|
|
45
|
+
path: req.url,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return res.status(500).json({
|
|
49
|
+
statusCode: 500,
|
|
50
|
+
message: 'Internal server error during service authentication',
|
|
51
|
+
error: 'Internal Server Error',
|
|
52
|
+
timestamp: new Date().toISOString(),
|
|
53
|
+
path: req.url,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
normalizeHeaders(headers) {
|
|
58
|
+
const normalized = {};
|
|
59
|
+
Object.keys(headers).forEach((key) => {
|
|
60
|
+
const value = headers[key];
|
|
61
|
+
if (typeof value === 'string') {
|
|
62
|
+
normalized[key.toLowerCase()] = value;
|
|
63
|
+
}
|
|
64
|
+
else if (Array.isArray(value) && value.length > 0) {
|
|
65
|
+
normalized[key.toLowerCase()] = value[0];
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
exports.InterServiceAuthMiddleware = InterServiceAuthMiddleware;
|
|
72
|
+
exports.InterServiceAuthMiddleware = InterServiceAuthMiddleware = __decorate([
|
|
73
|
+
(0, common_1.Injectable)(),
|
|
74
|
+
__metadata("design:paramtypes", [inter_service_auth_service_1.InterServiceAuthService])
|
|
75
|
+
], InterServiceAuthMiddleware);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { IServiceAuthConfig } from './inter-service-auth.config.interface';
|
|
3
|
+
export declare class InterServiceAuthModule {
|
|
4
|
+
static forRoot(config: IServiceAuthConfig): DynamicModule;
|
|
5
|
+
static forRootAsync(options: {
|
|
6
|
+
imports?: any[];
|
|
7
|
+
useFactory: (...args: any[]) => IServiceAuthConfig | Promise<IServiceAuthConfig>;
|
|
8
|
+
inject?: any[];
|
|
9
|
+
}): DynamicModule;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=inter-service-auth.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inter-service-auth.module.d.ts","sourceRoot":"","sources":["../../src/inter-service-auth/inter-service-auth.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAkB,MAAM,gBAAgB,CAAC;AAE/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAE3E,qBAEa,sBAAsB;IACjC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,kBAAkB,GAAG,aAAa;IAWzD,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE;QAC3B,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;QAChB,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;QACjF,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;KAChB,GAAG,aAAa;CAelB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var InterServiceAuthModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.InterServiceAuthModule = void 0;
|
|
11
|
+
const common_1 = require("@nestjs/common");
|
|
12
|
+
const inter_service_auth_service_1 = require("./inter-service-auth.service");
|
|
13
|
+
let InterServiceAuthModule = InterServiceAuthModule_1 = class InterServiceAuthModule {
|
|
14
|
+
static forRoot(config) {
|
|
15
|
+
return {
|
|
16
|
+
module: InterServiceAuthModule_1,
|
|
17
|
+
providers: [
|
|
18
|
+
{ provide: inter_service_auth_service_1.INTER_SERVICE_AUTH_CONFIG, useValue: config },
|
|
19
|
+
inter_service_auth_service_1.InterServiceAuthService,
|
|
20
|
+
],
|
|
21
|
+
exports: [inter_service_auth_service_1.InterServiceAuthService],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
static forRootAsync(options) {
|
|
25
|
+
return {
|
|
26
|
+
module: InterServiceAuthModule_1,
|
|
27
|
+
imports: options.imports || [],
|
|
28
|
+
providers: [
|
|
29
|
+
{
|
|
30
|
+
provide: inter_service_auth_service_1.INTER_SERVICE_AUTH_CONFIG,
|
|
31
|
+
useFactory: options.useFactory,
|
|
32
|
+
inject: options.inject || [],
|
|
33
|
+
},
|
|
34
|
+
inter_service_auth_service_1.InterServiceAuthService,
|
|
35
|
+
],
|
|
36
|
+
exports: [inter_service_auth_service_1.InterServiceAuthService],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
exports.InterServiceAuthModule = InterServiceAuthModule;
|
|
41
|
+
exports.InterServiceAuthModule = InterServiceAuthModule = InterServiceAuthModule_1 = __decorate([
|
|
42
|
+
(0, common_1.Global)(),
|
|
43
|
+
(0, common_1.Module)({})
|
|
44
|
+
], InterServiceAuthModule);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import { IServiceAuthConfig } from './inter-service-auth.config.interface';
|
|
3
|
+
import { IServiceAuthPayload, IServiceCredentials } from './inter-service-auth.types';
|
|
4
|
+
export declare const INTER_SERVICE_AUTH_CONFIG = "INTER_SERVICE_AUTH_CONFIG";
|
|
5
|
+
export declare class InterServiceAuthService implements OnModuleDestroy {
|
|
6
|
+
private readonly config;
|
|
7
|
+
private readonly logger;
|
|
8
|
+
private readonly seenNonces;
|
|
9
|
+
private readonly intervalId;
|
|
10
|
+
constructor(config: IServiceAuthConfig);
|
|
11
|
+
onModuleDestroy(): void;
|
|
12
|
+
generateAuthHeaders(serviceId: string, targetSchema?: string): Record<string, string>;
|
|
13
|
+
validateServiceAuth(headers: Record<string, string>): IServiceAuthPayload;
|
|
14
|
+
getServiceConfig(serviceId: string): IServiceCredentials | undefined;
|
|
15
|
+
getConfiguredServices(): string[];
|
|
16
|
+
hasPermission(serviceId: string, permission: string): boolean;
|
|
17
|
+
hasRole(serviceId: string, role: string): boolean;
|
|
18
|
+
private purgeExpiredNonces;
|
|
19
|
+
private generateSignature;
|
|
20
|
+
private verifySignature;
|
|
21
|
+
private generateNonce;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=inter-service-auth.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inter-service-auth.service.d.ts","sourceRoot":"","sources":["../../src/inter-service-auth/inter-service-auth.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqD,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEpG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtF,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AAErE,qBACa,uBAAwB,YAAW,eAAe;IASd,OAAO,CAAC,QAAQ,CAAC,MAAM;IARtE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4C;IAKnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiC;gBAEI,MAAM,EAAE,kBAAkB;IAK1F,eAAe,IAAI,IAAI;IAIvB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,GAAE,MAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IA6BzF,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,mBAAmB;IA2EzE,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAIpE,qBAAqB,IAAI,MAAM,EAAE;IAIjC,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAU7D,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAUjD,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,aAAa;CAGtB"}
|
|
@@ -0,0 +1,202 @@
|
|
|
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 __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
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;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
44
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
45
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
46
|
+
};
|
|
47
|
+
var InterServiceAuthService_1;
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.InterServiceAuthService = exports.INTER_SERVICE_AUTH_CONFIG = void 0;
|
|
50
|
+
const common_1 = require("@nestjs/common");
|
|
51
|
+
const crypto = __importStar(require("crypto"));
|
|
52
|
+
exports.INTER_SERVICE_AUTH_CONFIG = 'INTER_SERVICE_AUTH_CONFIG';
|
|
53
|
+
let InterServiceAuthService = InterServiceAuthService_1 = class InterServiceAuthService {
|
|
54
|
+
constructor(config) {
|
|
55
|
+
this.config = config;
|
|
56
|
+
this.logger = new common_1.Logger(InterServiceAuthService_1.name);
|
|
57
|
+
// Replay protection: tracks nonces seen within their validity window.
|
|
58
|
+
// Key: `${serviceId}:${nonce}`, value: expiresAt timestamp (ms).
|
|
59
|
+
// DEBT: in-memory only — assumes single-node deployment. For horizontal scaling, migrate to Redis.
|
|
60
|
+
this.seenNonces = new Map();
|
|
61
|
+
// Purge expired nonces every 60 s to prevent unbounded memory growth
|
|
62
|
+
this.intervalId = setInterval(() => this.purgeExpiredNonces(), 60_000);
|
|
63
|
+
}
|
|
64
|
+
onModuleDestroy() {
|
|
65
|
+
clearInterval(this.intervalId);
|
|
66
|
+
}
|
|
67
|
+
generateAuthHeaders(serviceId, targetSchema = '') {
|
|
68
|
+
const service = this.config.services[serviceId];
|
|
69
|
+
if (!service) {
|
|
70
|
+
throw new common_1.UnauthorizedException(`Service ${serviceId} not configured`);
|
|
71
|
+
}
|
|
72
|
+
const timestamp = Date.now();
|
|
73
|
+
const nonce = this.generateNonce();
|
|
74
|
+
const payload = {
|
|
75
|
+
serviceId: service.serviceId,
|
|
76
|
+
permissions: service.permissions ?? ['inter-service.communicate'],
|
|
77
|
+
roles: service.roles ?? ['service'],
|
|
78
|
+
timestamp,
|
|
79
|
+
nonce,
|
|
80
|
+
};
|
|
81
|
+
const signature = this.generateSignature(payload, service.secretKey, targetSchema);
|
|
82
|
+
return {
|
|
83
|
+
'X-Service-ID': service.serviceId,
|
|
84
|
+
'X-Service-API-Key': service.apiKey,
|
|
85
|
+
'X-Service-Timestamp': timestamp.toString(),
|
|
86
|
+
'X-Service-Nonce': nonce,
|
|
87
|
+
'X-Service-Signature': signature,
|
|
88
|
+
'X-Service-Auth': 'true',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
validateServiceAuth(headers) {
|
|
92
|
+
const serviceId = headers['x-service-id'];
|
|
93
|
+
const apiKey = headers['x-service-api-key'];
|
|
94
|
+
const timestamp = parseInt(headers['x-service-timestamp'] || '0');
|
|
95
|
+
const nonce = headers['x-service-nonce'];
|
|
96
|
+
const signature = headers['x-service-signature'];
|
|
97
|
+
const isServiceAuth = headers['x-service-auth'] === 'true';
|
|
98
|
+
if (!isServiceAuth) {
|
|
99
|
+
throw new common_1.UnauthorizedException('Not a service-to-service request');
|
|
100
|
+
}
|
|
101
|
+
if (!serviceId || !apiKey || !timestamp || !nonce || !signature) {
|
|
102
|
+
throw new common_1.UnauthorizedException('Missing service authentication headers');
|
|
103
|
+
}
|
|
104
|
+
const service = Object.values(this.config.services).find((s) => s.serviceId === serviceId && s.apiKey === apiKey);
|
|
105
|
+
if (!service) {
|
|
106
|
+
this.logger.warn(`Unknown service attempted authentication: ${serviceId}`);
|
|
107
|
+
throw new common_1.UnauthorizedException('Invalid service credentials');
|
|
108
|
+
}
|
|
109
|
+
// signatureExpireMinutes: window looking backward — how long a signature remains valid.
|
|
110
|
+
// allowedTimeDriftSeconds: window looking forward — tolerance for sender clock skew.
|
|
111
|
+
// These are distinct concepts. Using Math.abs() with OR collapses both into min(expiry, drift),
|
|
112
|
+
// making the effective window only 30 s in both directions instead of 5 min back / 30 s forward.
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
const maxAge = (this.config.signatureExpireMinutes ?? 5) * 60 * 1000;
|
|
115
|
+
const timeDrift = (this.config.allowedTimeDriftSeconds ?? 30) * 1000;
|
|
116
|
+
const ageMs = now - timestamp; // positive = timestamp is in the past; negative = timestamp is in the future
|
|
117
|
+
if (ageMs > maxAge) {
|
|
118
|
+
this.logger.warn(`Service auth signature expired: ${serviceId}`);
|
|
119
|
+
throw new common_1.UnauthorizedException('Service authentication signature expired');
|
|
120
|
+
}
|
|
121
|
+
if (ageMs < -timeDrift) {
|
|
122
|
+
this.logger.warn(`Service auth timestamp too far in the future: ${serviceId}`);
|
|
123
|
+
throw new common_1.UnauthorizedException('Service authentication timestamp invalid');
|
|
124
|
+
}
|
|
125
|
+
const payload = {
|
|
126
|
+
serviceId,
|
|
127
|
+
permissions: service.permissions ?? ['inter-service.communicate'],
|
|
128
|
+
roles: service.roles ?? ['service'],
|
|
129
|
+
timestamp,
|
|
130
|
+
nonce,
|
|
131
|
+
};
|
|
132
|
+
const targetSchema = headers['x-target-schema'] || '';
|
|
133
|
+
const expectedSignature = this.generateSignature(payload, service.secretKey, targetSchema);
|
|
134
|
+
if (!this.verifySignature(signature, expectedSignature)) {
|
|
135
|
+
this.logger.warn(`Invalid signature for service: ${serviceId}`);
|
|
136
|
+
throw new common_1.UnauthorizedException('Invalid service signature');
|
|
137
|
+
}
|
|
138
|
+
// Replay protection: reject any nonce already seen within its validity window.
|
|
139
|
+
// DEBT: in-memory only — assumes single-node deployment. For horizontal scaling, migrate to Redis.
|
|
140
|
+
const nonceKey = `${serviceId}:${nonce}`;
|
|
141
|
+
if (this.seenNonces.has(nonceKey)) {
|
|
142
|
+
this.logger.warn(`Replayed nonce detected for service: ${serviceId}`);
|
|
143
|
+
throw new common_1.UnauthorizedException('Nonce already used');
|
|
144
|
+
}
|
|
145
|
+
this.seenNonces.set(nonceKey, timestamp + maxAge);
|
|
146
|
+
if (this.config.enableLogging) {
|
|
147
|
+
this.logger.log(`Service authenticated successfully: ${serviceId}`);
|
|
148
|
+
}
|
|
149
|
+
return payload;
|
|
150
|
+
}
|
|
151
|
+
getServiceConfig(serviceId) {
|
|
152
|
+
return this.config.services[serviceId];
|
|
153
|
+
}
|
|
154
|
+
getConfiguredServices() {
|
|
155
|
+
return Object.keys(this.config.services);
|
|
156
|
+
}
|
|
157
|
+
hasPermission(serviceId, permission) {
|
|
158
|
+
const service = this.config.services[serviceId];
|
|
159
|
+
if (!service)
|
|
160
|
+
return false;
|
|
161
|
+
return ((service.permissions ?? []).includes(permission) ||
|
|
162
|
+
(service.permissions ?? []).includes('*') ||
|
|
163
|
+
(service.permissions ?? []).includes('inter-service.*'));
|
|
164
|
+
}
|
|
165
|
+
hasRole(serviceId, role) {
|
|
166
|
+
const service = this.config.services[serviceId];
|
|
167
|
+
if (!service)
|
|
168
|
+
return false;
|
|
169
|
+
const roles = service.roles ?? ['service'];
|
|
170
|
+
// Every configured service implicitly carries the base 'service' role,
|
|
171
|
+
// regardless of explicit configuration. This enables coarse-grained checks
|
|
172
|
+
// like @RequireServiceRoles('service') to pass for any valid credential.
|
|
173
|
+
return roles.includes(role) || role === 'service';
|
|
174
|
+
}
|
|
175
|
+
purgeExpiredNonces() {
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
for (const [key, expiresAt] of this.seenNonces) {
|
|
178
|
+
if (expiresAt < now) {
|
|
179
|
+
this.seenNonces.delete(key);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
generateSignature(payload, secretKey, targetSchema = '') {
|
|
184
|
+
const data = `${payload.serviceId}:${payload.timestamp}:${payload.nonce}:${targetSchema}`;
|
|
185
|
+
return crypto.createHmac('sha256', secretKey).update(data).digest('hex');
|
|
186
|
+
}
|
|
187
|
+
verifySignature(provided, expected) {
|
|
188
|
+
if (provided.length !== expected.length) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
return crypto.timingSafeEqual(Buffer.from(provided, 'hex'), Buffer.from(expected, 'hex'));
|
|
192
|
+
}
|
|
193
|
+
generateNonce() {
|
|
194
|
+
return crypto.randomBytes(16).toString('hex');
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
exports.InterServiceAuthService = InterServiceAuthService;
|
|
198
|
+
exports.InterServiceAuthService = InterServiceAuthService = InterServiceAuthService_1 = __decorate([
|
|
199
|
+
(0, common_1.Injectable)(),
|
|
200
|
+
__param(0, (0, common_1.Inject)(exports.INTER_SERVICE_AUTH_CONFIG)),
|
|
201
|
+
__metadata("design:paramtypes", [Object])
|
|
202
|
+
], InterServiceAuthService);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Request } from 'express';
|
|
2
|
+
export interface IServiceCredentials {
|
|
3
|
+
serviceId: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
secretKey: string;
|
|
6
|
+
/** Default: ['inter-service.communicate'] */
|
|
7
|
+
permissions?: string[];
|
|
8
|
+
/** Default: ['service'] */
|
|
9
|
+
roles?: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface IServiceAuthPayload {
|
|
12
|
+
serviceId: string;
|
|
13
|
+
permissions: string[];
|
|
14
|
+
roles: string[];
|
|
15
|
+
timestamp: number;
|
|
16
|
+
nonce: string;
|
|
17
|
+
}
|
|
18
|
+
export interface IServiceAuthRequest {
|
|
19
|
+
serviceId: string;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
nonce: string;
|
|
22
|
+
signature: string;
|
|
23
|
+
}
|
|
24
|
+
export declare const SERVICE_NAMES: {
|
|
25
|
+
readonly AUTH: "contractx-authorizations";
|
|
26
|
+
readonly CONTRACTS: "contractx-contracts";
|
|
27
|
+
readonly PROVIDERS: "contractx-providers";
|
|
28
|
+
readonly DELIVERIES: "contractx-deliveries";
|
|
29
|
+
readonly INVOICES: "contractx-invoices";
|
|
30
|
+
readonly WORKFLOWS: "contractx-workflows";
|
|
31
|
+
readonly SLAS: "contractx-SLAs";
|
|
32
|
+
readonly MEETINGS: "contractx-meetings";
|
|
33
|
+
readonly NOTIFICATIONS: "contractx-notifications";
|
|
34
|
+
readonly AUDIT: "contractx-audit";
|
|
35
|
+
};
|
|
36
|
+
export type ServiceName = (typeof SERVICE_NAMES)[keyof typeof SERVICE_NAMES];
|
|
37
|
+
declare module 'express-serve-static-core' {
|
|
38
|
+
interface Request {
|
|
39
|
+
serviceAuth?: IServiceAuthPayload;
|
|
40
|
+
isServiceRequest?: boolean;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Narrowed Request type for handlers that sit behind ServiceAuthGuard.
|
|
45
|
+
* Cast to this type when you need non-optional access to serviceAuth:
|
|
46
|
+
*
|
|
47
|
+
* @Get()
|
|
48
|
+
* @UseGuards(ServiceAuthGuard)
|
|
49
|
+
* myEndpoint(@Req() req: AuthenticatedServiceRequest) {
|
|
50
|
+
* const { serviceId } = req.serviceAuth; // guaranteed non-null
|
|
51
|
+
* }
|
|
52
|
+
*/
|
|
53
|
+
export type AuthenticatedServiceRequest = Request & {
|
|
54
|
+
serviceAuth: IServiceAuthPayload;
|
|
55
|
+
isServiceRequest: true;
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=inter-service-auth.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inter-service-auth.types.d.ts","sourceRoot":"","sources":["../../src/inter-service-auth/inter-service-auth.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,aAAa;;;;;;;;;;;CAWhB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAI7E,OAAO,QAAQ,2BAA2B,CAAC;IACzC,UAAU,OAAO;QACf,WAAW,CAAC,EAAE,mBAAmB,CAAC;QAClC,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,2BAA2B,GAAG,OAAO,GAAG;IAClD,WAAW,EAAE,mBAAmB,CAAC;IACjC,gBAAgB,EAAE,IAAI,CAAC;CACxB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SERVICE_NAMES = void 0;
|
|
4
|
+
exports.SERVICE_NAMES = {
|
|
5
|
+
AUTH: 'contractx-authorizations',
|
|
6
|
+
CONTRACTS: 'contractx-contracts',
|
|
7
|
+
PROVIDERS: 'contractx-providers',
|
|
8
|
+
DELIVERIES: 'contractx-deliveries',
|
|
9
|
+
INVOICES: 'contractx-invoices',
|
|
10
|
+
WORKFLOWS: 'contractx-workflows',
|
|
11
|
+
SLAS: 'contractx-SLAs',
|
|
12
|
+
MEETINGS: 'contractx-meetings',
|
|
13
|
+
NOTIFICATIONS: 'contractx-notifications',
|
|
14
|
+
AUDIT: 'contractx-audit',
|
|
15
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Requires the caller to have all listed permissions in their service credentials. */
|
|
2
|
+
export declare const RequireServicePermissions: (...permissions: string[]) => import("@nestjs/common").CustomDecorator<string>;
|
|
3
|
+
/** Requires the caller to have at least one of the listed roles. */
|
|
4
|
+
export declare const RequireServiceRoles: (...roles: string[]) => import("@nestjs/common").CustomDecorator<string>;
|
|
5
|
+
/**
|
|
6
|
+
* Controls whether M2M requests are accepted on this endpoint.
|
|
7
|
+
* @AllowServiceAuth(false) blocks M2M even when @RequireServiceAuth() is set at class level.
|
|
8
|
+
*/
|
|
9
|
+
export declare const AllowServiceAuth: (allow?: boolean) => import("@nestjs/common").CustomDecorator<string>;
|
|
10
|
+
/** Marks this endpoint as M2M-only. Rejects any request without valid service credentials. */
|
|
11
|
+
export declare const RequireServiceAuth: () => import("@nestjs/common").CustomDecorator<string>;
|
|
12
|
+
//# sourceMappingURL=service-auth.decorators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-auth.decorators.d.ts","sourceRoot":"","sources":["../../src/inter-service-auth/service-auth.decorators.ts"],"names":[],"mappings":"AAQA,uFAAuF;AACvF,eAAO,MAAM,yBAAyB,GAAI,GAAG,aAAa,MAAM,EAAE,qDACf,CAAC;AAEpD,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,GAAI,GAAG,OAAO,MAAM,EAAE,qDAA0C,CAAC;AAEjG;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAO,OAAc,qDAA+C,CAAC;AAEtG,8FAA8F;AAC9F,eAAO,MAAM,kBAAkB,wDAAoD,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RequireServiceAuth = exports.AllowServiceAuth = exports.RequireServiceRoles = exports.RequireServicePermissions = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
const service_auth_guard_1 = require("./service-auth.guard");
|
|
6
|
+
/** Requires the caller to have all listed permissions in their service credentials. */
|
|
7
|
+
const RequireServicePermissions = (...permissions) => (0, common_1.SetMetadata)(service_auth_guard_1.SERVICE_PERMISSIONS_KEY, permissions);
|
|
8
|
+
exports.RequireServicePermissions = RequireServicePermissions;
|
|
9
|
+
/** Requires the caller to have at least one of the listed roles. */
|
|
10
|
+
const RequireServiceRoles = (...roles) => (0, common_1.SetMetadata)(service_auth_guard_1.SERVICE_ROLES_KEY, roles);
|
|
11
|
+
exports.RequireServiceRoles = RequireServiceRoles;
|
|
12
|
+
/**
|
|
13
|
+
* Controls whether M2M requests are accepted on this endpoint.
|
|
14
|
+
* @AllowServiceAuth(false) blocks M2M even when @RequireServiceAuth() is set at class level.
|
|
15
|
+
*/
|
|
16
|
+
const AllowServiceAuth = (allow = true) => (0, common_1.SetMetadata)(service_auth_guard_1.ALLOW_SERVICE_AUTH_KEY, allow);
|
|
17
|
+
exports.AllowServiceAuth = AllowServiceAuth;
|
|
18
|
+
/** Marks this endpoint as M2M-only. Rejects any request without valid service credentials. */
|
|
19
|
+
const RequireServiceAuth = () => (0, common_1.SetMetadata)(service_auth_guard_1.REQUIRE_SERVICE_AUTH_KEY, true);
|
|
20
|
+
exports.RequireServiceAuth = RequireServiceAuth;
|
|
21
|
+
// TODO: @CurrentService() param decorator postponed — implement cleanly with
|
|
22
|
+
// createParamDecorator if a handler needs the calling serviceId.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import { Reflector } from '@nestjs/core';
|
|
3
|
+
import { InterServiceAuthService } from './inter-service-auth.service';
|
|
4
|
+
export declare const SERVICE_PERMISSIONS_KEY = "service_permissions";
|
|
5
|
+
export declare const SERVICE_ROLES_KEY = "service_roles";
|
|
6
|
+
export declare const ALLOW_SERVICE_AUTH_KEY = "allow_service_auth";
|
|
7
|
+
export declare const REQUIRE_SERVICE_AUTH_KEY = "require_service_auth";
|
|
8
|
+
export declare class ServiceAuthGuard implements CanActivate {
|
|
9
|
+
private readonly reflector;
|
|
10
|
+
private readonly interServiceAuthService;
|
|
11
|
+
private readonly logger;
|
|
12
|
+
constructor(reflector: Reflector, interServiceAuthService: InterServiceAuthService);
|
|
13
|
+
canActivate(context: ExecutionContext): boolean;
|
|
14
|
+
private checkPermission;
|
|
15
|
+
private checkRole;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=service-auth.guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-auth.guard.d.ts","sourceRoot":"","sources":["../../src/inter-service-auth/service-auth.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EACX,gBAAgB,EAIjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAC7D,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AACjD,eAAO,MAAM,sBAAsB,uBAAuB,CAAC;AAC3D,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAK/D,qBACa,gBAAiB,YAAW,WAAW;IAIhD,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,uBAAuB;IAJ1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;gBAGtC,SAAS,EAAE,SAAS,EACpB,uBAAuB,EAAE,uBAAuB;IAGnE,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO;IA6E/C,OAAO,CAAC,eAAe;IAyBvB,OAAO,CAAC,SAAS;CAYlB"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ServiceAuthGuard = exports.REQUIRE_SERVICE_AUTH_KEY = exports.ALLOW_SERVICE_AUTH_KEY = exports.SERVICE_ROLES_KEY = exports.SERVICE_PERMISSIONS_KEY = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const core_1 = require("@nestjs/core");
|
|
15
|
+
const inter_service_auth_service_1 = require("./inter-service-auth.service");
|
|
16
|
+
// Exported so decorators and consumers can reference keys without magic strings
|
|
17
|
+
exports.SERVICE_PERMISSIONS_KEY = 'service_permissions';
|
|
18
|
+
exports.SERVICE_ROLES_KEY = 'service_roles';
|
|
19
|
+
exports.ALLOW_SERVICE_AUTH_KEY = 'allow_service_auth';
|
|
20
|
+
exports.REQUIRE_SERVICE_AUTH_KEY = 'require_service_auth';
|
|
21
|
+
// TODO: @CurrentService() param decorator postponed — implement cleanly with
|
|
22
|
+
// createParamDecorator if a handler needs the calling serviceId.
|
|
23
|
+
let ServiceAuthGuard = class ServiceAuthGuard {
|
|
24
|
+
constructor(reflector, interServiceAuthService) {
|
|
25
|
+
this.reflector = reflector;
|
|
26
|
+
this.interServiceAuthService = interServiceAuthService;
|
|
27
|
+
this.logger = new common_1.Logger('ServiceAuthGuard');
|
|
28
|
+
}
|
|
29
|
+
canActivate(context) {
|
|
30
|
+
const request = context.switchToHttp().getRequest();
|
|
31
|
+
// Step 1: if @RequireServiceAuth() is set, reject non-M2M requests immediately
|
|
32
|
+
const requireServiceAuth = this.reflector.getAllAndOverride(exports.REQUIRE_SERVICE_AUTH_KEY, [
|
|
33
|
+
context.getHandler(),
|
|
34
|
+
context.getClass(),
|
|
35
|
+
]);
|
|
36
|
+
if (requireServiceAuth === true) {
|
|
37
|
+
if (!request.isServiceRequest || !request.serviceAuth) {
|
|
38
|
+
this.logger.warn(`Service authentication required but not provided for ${request.method} ${request.path}`);
|
|
39
|
+
throw new common_1.UnauthorizedException('Service-to-service authentication required');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// No service auth requirement — let other guards handle non-M2M requests
|
|
44
|
+
if (!request.isServiceRequest || !request.serviceAuth) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Step 2: @AllowServiceAuth(false) explicitly blocks M2M on this endpoint.
|
|
49
|
+
// This wins even when @RequireServiceAuth() is set at class level.
|
|
50
|
+
const allowServiceAuth = this.reflector.getAllAndOverride(exports.ALLOW_SERVICE_AUTH_KEY, [
|
|
51
|
+
context.getHandler(),
|
|
52
|
+
context.getClass(),
|
|
53
|
+
]);
|
|
54
|
+
if (allowServiceAuth === false) {
|
|
55
|
+
throw new common_1.ForbiddenException('Service authentication not allowed for this endpoint');
|
|
56
|
+
}
|
|
57
|
+
const serviceAuth = request.serviceAuth;
|
|
58
|
+
// Step 3: permission check (handler metadata takes precedence over class via getAllAndOverride)
|
|
59
|
+
const requiredPermissions = this.reflector.getAllAndOverride(exports.SERVICE_PERMISSIONS_KEY, [
|
|
60
|
+
context.getHandler(),
|
|
61
|
+
context.getClass(),
|
|
62
|
+
]) || [];
|
|
63
|
+
if (requiredPermissions.length > 0) {
|
|
64
|
+
const hasPermission = requiredPermissions.some((permission) => this.checkPermission(serviceAuth.serviceId, serviceAuth.permissions, permission));
|
|
65
|
+
if (!hasPermission) {
|
|
66
|
+
this.logger.warn(`Service ${serviceAuth.serviceId} lacks required permissions: ${requiredPermissions.join(', ')}`);
|
|
67
|
+
throw new common_1.ForbiddenException('Insufficient permissions for service operation');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Step 4: role check
|
|
71
|
+
const requiredRoles = this.reflector.getAllAndOverride(exports.SERVICE_ROLES_KEY, [
|
|
72
|
+
context.getHandler(),
|
|
73
|
+
context.getClass(),
|
|
74
|
+
]) || [];
|
|
75
|
+
if (requiredRoles.length > 0) {
|
|
76
|
+
const hasRole = requiredRoles.some((role) => this.checkRole(serviceAuth.serviceId, serviceAuth.roles, role));
|
|
77
|
+
if (!hasRole) {
|
|
78
|
+
this.logger.warn(`Service ${serviceAuth.serviceId} lacks required roles: ${requiredRoles.join(', ')}`);
|
|
79
|
+
throw new common_1.ForbiddenException('Insufficient role for service operation');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
this.logger.debug(`Service ${serviceAuth.serviceId} authorized for ${request.method} ${request.path}`);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
checkPermission(serviceId, servicePermissions, requiredPermission) {
|
|
86
|
+
if (servicePermissions.includes('*') || servicePermissions.includes('inter-service.*')) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (servicePermissions.includes(requiredPermission)) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
// Prefix wildcard: 'contracts.*' covers 'contracts.read', 'contracts.write', etc.
|
|
93
|
+
const hasWildcardPermission = servicePermissions.some((permission) => {
|
|
94
|
+
if (permission.endsWith('.*')) {
|
|
95
|
+
const basePermission = permission.slice(0, -2);
|
|
96
|
+
return requiredPermission.startsWith(basePermission);
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
});
|
|
100
|
+
if (hasWildcardPermission) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return this.interServiceAuthService.hasPermission(serviceId, requiredPermission);
|
|
104
|
+
}
|
|
105
|
+
checkRole(serviceId, serviceRoles, requiredRole) {
|
|
106
|
+
if (serviceRoles.includes(requiredRole)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
// Any service with the base 'service' role passes coarse-grained role checks
|
|
110
|
+
if (serviceRoles.includes('service')) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return this.interServiceAuthService.hasRole(serviceId, requiredRole);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
exports.ServiceAuthGuard = ServiceAuthGuard;
|
|
117
|
+
exports.ServiceAuthGuard = ServiceAuthGuard = __decorate([
|
|
118
|
+
(0, common_1.Injectable)(),
|
|
119
|
+
__metadata("design:paramtypes", [core_1.Reflector,
|
|
120
|
+
inter_service_auth_service_1.InterServiceAuthService])
|
|
121
|
+
], ServiceAuthGuard);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "permissions-contractx",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Enterprise-grade authentication and authorization package for NestJS microservices with role-based and permission-based access control",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|