permissions-contractx 1.0.2 → 1.1.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/LICENSE +1 -1
- package/README.md +53 -1346
- package/dist/constants/contractx-permissions.constants.d.ts +84 -92
- package/dist/constants/contractx-permissions.constants.d.ts.map +1 -1
- package/dist/constants/contractx-permissions.constants.js +2 -2
- package/dist/constants/contractx-roles.constants.d.ts +150 -254
- package/dist/constants/contractx-roles.constants.d.ts.map +1 -1
- package/dist/constants/contractx-roles.constants.js +2 -2
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +1 -0
- package/dist/constants/permission-names.constants.d.ts +310 -0
- package/dist/constants/permission-names.constants.d.ts.map +1 -0
- package/dist/constants/permission-names.constants.js +209 -0
- package/dist/constants/security.constants.d.ts +49 -49
- package/dist/constants/security.constants.d.ts.map +1 -1
- package/dist/constants/security.constants.js +2 -2
- package/dist/decorators/current-user.decorator.d.ts +5 -53
- package/dist/decorators/current-user.decorator.d.ts.map +1 -1
- package/dist/decorators/current-user.decorator.js +4 -51
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +1 -0
- package/dist/decorators/permission-writes.decorator.d.ts +14 -0
- package/dist/decorators/permission-writes.decorator.d.ts.map +1 -0
- package/dist/decorators/permission-writes.decorator.js +18 -0
- package/dist/decorators/permissions.decorator.d.ts +0 -58
- package/dist/decorators/permissions.decorator.d.ts.map +1 -1
- package/dist/decorators/permissions.decorator.js +0 -58
- package/dist/decorators/public.decorator.d.ts +0 -0
- package/dist/decorators/public.decorator.d.ts.map +0 -0
- package/dist/decorators/public.decorator.js +0 -0
- package/dist/decorators/roles.decorator.d.ts +4 -57
- package/dist/decorators/roles.decorator.d.ts.map +1 -1
- package/dist/decorators/roles.decorator.js +6 -57
- package/dist/guards/authorization.guard.d.ts +37 -0
- package/dist/guards/authorization.guard.d.ts.map +1 -0
- package/dist/guards/authorization.guard.js +150 -0
- package/dist/guards/index.d.ts +1 -0
- package/dist/guards/index.d.ts.map +1 -1
- package/dist/guards/index.js +1 -0
- package/dist/guards/jwt-auth.guard.d.ts +0 -0
- package/dist/guards/jwt-auth.guard.d.ts.map +1 -1
- package/dist/guards/jwt-auth.guard.js +0 -0
- package/dist/guards/permissions.guard.d.ts +0 -0
- package/dist/guards/permissions.guard.d.ts.map +1 -1
- package/dist/guards/permissions.guard.js +8 -2
- package/dist/guards/roles.guard.d.ts +0 -0
- package/dist/guards/roles.guard.d.ts.map +1 -1
- package/dist/guards/roles.guard.js +1 -1
- package/dist/index.d.ts +0 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -6
- package/dist/interfaces/index.d.ts +1 -0
- package/dist/interfaces/index.d.ts.map +1 -1
- package/dist/interfaces/index.js +1 -0
- package/dist/interfaces/jwt-payload.interface.d.ts +46 -9
- package/dist/interfaces/jwt-payload.interface.d.ts.map +1 -1
- package/dist/interfaces/jwt-payload.interface.js +19 -0
- package/dist/interfaces/permission-mode.enum.d.ts +22 -0
- package/dist/interfaces/permission-mode.enum.d.ts.map +1 -0
- package/dist/interfaces/permission-mode.enum.js +25 -0
- package/dist/modules/index.d.ts +0 -0
- package/dist/modules/index.d.ts.map +0 -0
- package/dist/modules/index.js +0 -0
- package/dist/modules/permissions-contractx.module.d.ts +0 -0
- package/dist/modules/permissions-contractx.module.d.ts.map +1 -1
- package/dist/modules/permissions-contractx.module.js +4 -2
- package/dist/services/contractx-authorization.service.d.ts +198 -27
- package/dist/services/contractx-authorization.service.d.ts.map +1 -1
- package/dist/services/contractx-authorization.service.js +2 -0
- package/dist/services/contractx-validation.service.d.ts +93 -12
- package/dist/services/contractx-validation.service.d.ts.map +1 -1
- package/dist/services/contractx-validation.service.js +1 -0
- package/dist/services/index.d.ts +0 -2
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/user-context.service.d.ts +29 -34
- package/dist/services/user-context.service.d.ts.map +1 -1
- package/dist/services/user-context.service.js +65 -44
- package/package.json +5 -24
- package/dist/services/contractx-document-compliance.service.d.ts +0 -85
- package/dist/services/contractx-document-compliance.service.d.ts.map +0 -1
- package/dist/services/contractx-document-compliance.service.js +0 -536
- package/dist/test-document-compliance.d.ts +0 -7
- package/dist/test-document-compliance.d.ts.map +0 -1
- package/dist/test-document-compliance.js +0 -118
package/README.md
CHANGED
|
@@ -1,1397 +1,104 @@
|
|
|
1
|
-
#
|
|
1
|
+
# permissions-contractx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Librería compartida de autorización para la plataforma **ContractX**. Provee los guards, decoradores, interfaces y constantes que los microservicios usan para verificar los JWT que emite el servicio de Auth y aplicar el modelo de permisos de dominio (ADR-004).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
[](https://nestjs.com/)
|
|
7
|
-
[](https://jwt.io/)
|
|
8
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
Auth **emite y firma** los tokens; cada microservicio **verifica** los suyos usando este paquete. Ningún servicio mantiene su propio catálogo de permisos: todos referencian el vocabulario central definido en Auth.
|
|
9
6
|
|
|
10
|
-
##
|
|
11
|
-
|
|
12
|
-
- 🔑 **JWT Authentication** - Secure token-based authentication
|
|
13
|
-
- 👥 **Role-Based Access Control** - Hierarchical role system
|
|
14
|
-
- 🔐 **Permission-Based Authorization** - Granular permission control
|
|
15
|
-
- 🏢 **Multi-Tenant Support** - Client/Provider organizational structure
|
|
16
|
-
- 🚀 **Easy Integration** - Simple decorator-based API
|
|
17
|
-
- 🛡️ **Security First** - Built-in security best practices
|
|
18
|
-
- 📊 **Request Context** - User context service for easy access
|
|
19
|
-
- 🔧 **TypeScript** - Full TypeScript support with strict typing
|
|
20
|
-
- 📖 **Comprehensive Documentation** - Detailed examples and guides
|
|
21
|
-
|
|
22
|
-
## 📦 Installation
|
|
7
|
+
## Instalación
|
|
23
8
|
|
|
24
9
|
```bash
|
|
25
10
|
npm install permissions-contractx
|
|
26
11
|
```
|
|
27
12
|
|
|
28
|
-
### Peer
|
|
13
|
+
### Peer dependencies
|
|
29
14
|
|
|
30
|
-
|
|
15
|
+
El paquete espera que el servicio consumidor ya tenga instaladas:
|
|
31
16
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
17
|
+
- `@nestjs/common`, `@nestjs/core`
|
|
18
|
+
- `reflect-metadata`, `rxjs`
|
|
19
|
+
- `jsonwebtoken`
|
|
20
|
+
- `express`
|
|
35
21
|
|
|
36
|
-
##
|
|
22
|
+
## Qué incluye
|
|
37
23
|
|
|
38
|
-
###
|
|
24
|
+
### Guards
|
|
39
25
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
import { Module } from '@nestjs/common';
|
|
43
|
-
import { PermissionsContractXModule } from 'permissions-contractx';
|
|
26
|
+
- **`AuthorizationGuard`** — guard de autorización con cuatro estados de migración por endpoint (legacy / observación / estricto / solo-permisos), eje lectura/escritura derivado del método HTTP, y soporte de modo-ver (un permiso de solo lectura bloquea escrituras). Resuelve conflictos con la regla *deny-overrides* (deny siempre gana).
|
|
27
|
+
- `JwtAuthGuard` — verificación y decodificación del JWT.
|
|
44
28
|
|
|
45
|
-
|
|
46
|
-
imports: [
|
|
47
|
-
PermissionsContractXModule.register({
|
|
48
|
-
jwt: {
|
|
49
|
-
secret: 'your-secret-key',
|
|
50
|
-
issuer: 'your-api',
|
|
51
|
-
audience: 'your-users',
|
|
52
|
-
expiresIn: '15m',
|
|
53
|
-
},
|
|
54
|
-
guards: {
|
|
55
|
-
enableGlobalAuth: true, // Apply authentication globally
|
|
56
|
-
},
|
|
57
|
-
}),
|
|
58
|
-
],
|
|
59
|
-
})
|
|
60
|
-
export class AppModule {}
|
|
61
|
-
```
|
|
29
|
+
### Decoradores
|
|
62
30
|
|
|
63
|
-
|
|
31
|
+
- `@RequirePermissions(...)` — declara los permisos que un endpoint exige.
|
|
32
|
+
- `@PermissionWrites(...)` — override explícito del eje lectura/escritura (por ejemplo, un `GET` con efectos o un `POST` de solo lectura).
|
|
33
|
+
- `@CurrentUser()` — inyecta el usuario resuelto del token.
|
|
34
|
+
- `@Public()` — marca un endpoint como abierto.
|
|
64
35
|
|
|
65
|
-
|
|
66
|
-
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
|
|
67
|
-
import {
|
|
68
|
-
CurrentUser,
|
|
69
|
-
Roles,
|
|
70
|
-
RequirePermissions,
|
|
71
|
-
Public,
|
|
72
|
-
JwtAuthGuard,
|
|
73
|
-
RolesGuard,
|
|
74
|
-
PermissionsGuard,
|
|
75
|
-
JwtPayload,
|
|
76
|
-
} from 'permissions-contractx';
|
|
36
|
+
### Interfaces
|
|
77
37
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
export class UsersController {
|
|
81
|
-
|
|
82
|
-
// Public endpoint (no authentication)
|
|
83
|
-
@Public()
|
|
84
|
-
@Get('public')
|
|
85
|
-
getPublicInfo() {
|
|
86
|
-
return { message: 'This is public' };
|
|
87
|
-
}
|
|
38
|
+
- **`JwtPayload`** — contrato del token que emite Auth: `sub`, `role[]`, `permissions[]`, `permissionsView[]`, `clientId[]`, `providerId`, `tenantContext` (`'client' | 'provider' | 'system'`), `key_client`, entre otros.
|
|
39
|
+
- `PermissionMode` — enum `WRITE` / `READ`, espejo del modo del par rol↔permiso en Auth.
|
|
88
40
|
|
|
89
|
-
|
|
90
|
-
@Get('profile')
|
|
91
|
-
getProfile(@CurrentUser() user: JwtPayload) {
|
|
92
|
-
return {
|
|
93
|
-
id: user.sub,
|
|
94
|
-
name: user.fullName,
|
|
95
|
-
roles: user.role,
|
|
96
|
-
permissions: user.permissions,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
41
|
+
### Constantes
|
|
99
42
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@Get('admin-data')
|
|
103
|
-
getAdminData() {
|
|
104
|
-
return { message: 'Admin only data' };
|
|
105
|
-
}
|
|
43
|
+
- **`permission-names.constants`** — los 130 códigos de permiso de dominio, agrupados por microservicio, más `PERMISSIONS_BY_DOMAIN`, `ALL_PERMISSION_CODES` y el tipo `PermissionCode`.
|
|
44
|
+
- `security.constants` — constantes de roles del sistema.
|
|
106
45
|
|
|
107
|
-
|
|
108
|
-
@RequirePermissions('users.create')
|
|
109
|
-
@Post()
|
|
110
|
-
createUser(@CurrentUser() user: JwtPayload) {
|
|
111
|
-
return { message: 'User created', createdBy: user.fullName };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Combined role and permission
|
|
115
|
-
@Roles('client_contract_admin')
|
|
116
|
-
@RequirePermissions('users.delete')
|
|
117
|
-
@Delete(':id')
|
|
118
|
-
deleteUser(@Param('id') id: string) {
|
|
119
|
-
return { message: `User ${id} deleted` };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### 3. Environment Variables Setup
|
|
125
|
-
|
|
126
|
-
Create a `.env` file:
|
|
127
|
-
|
|
128
|
-
```env
|
|
129
|
-
JWT_SECRET=your-super-secret-jwt-key-change-in-production
|
|
130
|
-
JWT_ISSUER=contractx-api
|
|
131
|
-
JWT_AUDIENCE=contractx-users
|
|
132
|
-
JWT_EXPIRES_IN=15m
|
|
133
|
-
|
|
134
|
-
# Optional: Enable global guards
|
|
135
|
-
ENABLE_GLOBAL_AUTH=true
|
|
136
|
-
ENABLE_AUTH_LOGGING=true
|
|
137
|
-
|
|
138
|
-
# Development only
|
|
139
|
-
DISABLE_AUTH=false # Set to true to disable auth in development
|
|
140
|
-
```
|
|
46
|
+
## Uso básico
|
|
141
47
|
|
|
142
|
-
|
|
48
|
+
Registrar el guard de forma global en un microservicio (NestJS):
|
|
143
49
|
|
|
144
50
|
```typescript
|
|
145
|
-
|
|
146
|
-
@
|
|
147
|
-
imports: [
|
|
148
|
-
PermissionsContractXModule.forRoot(), // Auto-configures from environment variables
|
|
149
|
-
],
|
|
150
|
-
})
|
|
151
|
-
export class AppModule {}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## 🏗️ Advanced Configuration
|
|
155
|
-
|
|
156
|
-
### Async Configuration
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
51
|
+
import { AuthorizationGuard } from 'permissions-contractx';
|
|
52
|
+
import { APP_GUARD } from '@nestjs/core';
|
|
160
53
|
|
|
161
54
|
@Module({
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
imports: [ConfigModule],
|
|
165
|
-
useFactory: async (configService: ConfigService) => ({
|
|
166
|
-
jwt: {
|
|
167
|
-
secret: configService.get('JWT_SECRET'),
|
|
168
|
-
issuer: configService.get('JWT_ISSUER'),
|
|
169
|
-
audience: configService.get('JWT_AUDIENCE'),
|
|
170
|
-
expiresIn: configService.get('JWT_EXPIRES_IN', '15m'),
|
|
171
|
-
clockTolerance: 30, // seconds
|
|
172
|
-
},
|
|
173
|
-
guards: {
|
|
174
|
-
enableGlobalAuth: true,
|
|
175
|
-
enableGlobalRoles: false,
|
|
176
|
-
enableGlobalPermissions: false,
|
|
177
|
-
},
|
|
178
|
-
security: {
|
|
179
|
-
enableLogging: process.env.NODE_ENV === 'production',
|
|
180
|
-
},
|
|
181
|
-
development: {
|
|
182
|
-
disableAuth: process.env.NODE_ENV === 'development',
|
|
183
|
-
mockUser: {
|
|
184
|
-
sub: 'dev-user-123',
|
|
185
|
-
role: ['superadmin'],
|
|
186
|
-
permissions: ['*'],
|
|
187
|
-
fullName: 'Development User',
|
|
188
|
-
email: 'dev@example.com',
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
}),
|
|
192
|
-
inject: [ConfigService],
|
|
193
|
-
}),
|
|
55
|
+
providers: [
|
|
56
|
+
{ provide: APP_GUARD, useClass: AuthorizationGuard },
|
|
194
57
|
],
|
|
195
58
|
})
|
|
196
59
|
export class AppModule {}
|
|
197
60
|
```
|
|
198
61
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
The JWT token must include the following payload structure:
|
|
202
|
-
|
|
203
|
-
```json
|
|
204
|
-
{
|
|
205
|
-
"sub": "user_123456789", // User ID (required)
|
|
206
|
-
"fullName": "John Doe", // User's full name (required)
|
|
207
|
-
"email": "john@example.com", // User's email
|
|
208
|
-
"clientId": "client_001", // Organization/Client ID
|
|
209
|
-
"sessionId": "session_abc", // Session tracking
|
|
210
|
-
|
|
211
|
-
"role": [ // User roles array (required)
|
|
212
|
-
"admin",
|
|
213
|
-
"manager",
|
|
214
|
-
"user"
|
|
215
|
-
],
|
|
216
|
-
|
|
217
|
-
"permissions": [ // User permissions array (required)
|
|
218
|
-
"users:read",
|
|
219
|
-
"users:create",
|
|
220
|
-
"users:update",
|
|
221
|
-
"users:delete",
|
|
222
|
-
"settings:manage"
|
|
223
|
-
],
|
|
224
|
-
|
|
225
|
-
// Standard JWT fields
|
|
226
|
-
"iat": 1609459200, // Issued at
|
|
227
|
-
"exp": 1609545600, // Expires at
|
|
228
|
-
"iss": "contractx-api", // Issuer
|
|
229
|
-
"aud": "contractx-users" // Audience
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
## 🛡️ Guards
|
|
234
|
-
|
|
235
|
-
### 1. JwtAuthGuard
|
|
236
|
-
|
|
237
|
-
Validates JWT tokens and extracts user information.
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
import { Controller, UseGuards } from '@nestjs/common';
|
|
241
|
-
import { JwtAuthGuard } from 'permissions-contractx';
|
|
242
|
-
|
|
243
|
-
@Controller('protected')
|
|
244
|
-
@UseGuards(JwtAuthGuard)
|
|
245
|
-
export class ProtectedController {
|
|
246
|
-
// All routes require valid JWT token
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### 2. RolesGuard
|
|
251
|
-
|
|
252
|
-
Checks if user has required roles.
|
|
62
|
+
Proteger un endpoint exigiendo un permiso:
|
|
253
63
|
|
|
254
64
|
```typescript
|
|
255
|
-
import {
|
|
256
|
-
import { JwtAuthGuard, RolesGuard, RequireRoles } from 'permissions-contractx';
|
|
257
|
-
|
|
258
|
-
@Controller('admin')
|
|
259
|
-
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
260
|
-
export class AdminController {
|
|
261
|
-
|
|
262
|
-
@Get('dashboard')
|
|
263
|
-
@RequireRoles('admin', 'manager') // User must have admin OR manager role
|
|
264
|
-
getDashboard() {
|
|
265
|
-
return { message: 'Admin dashboard' };
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### 3. PermissionsGuard
|
|
271
|
-
|
|
272
|
-
Checks if user has required permissions.
|
|
273
|
-
|
|
274
|
-
```typescript
|
|
275
|
-
import { Controller, UseGuards } from '@nestjs/common';
|
|
276
|
-
import { JwtAuthGuard, PermissionsGuard, RequirePermissions } from 'permissions-contractx';
|
|
277
|
-
|
|
278
|
-
@Controller('users')
|
|
279
|
-
@UseGuards(JwtAuthGuard, PermissionsGuard)
|
|
280
|
-
export class UsersController {
|
|
281
|
-
|
|
282
|
-
@Post()
|
|
283
|
-
@RequirePermissions('users:create') // User must have this exact permission
|
|
284
|
-
createUser() {
|
|
285
|
-
return { message: 'User created' };
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
@Delete(':id')
|
|
289
|
-
@RequirePermissions('users:delete', 'admin:all') // User must have one of these
|
|
290
|
-
deleteUser() {
|
|
291
|
-
return { message: 'User deleted' };
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
## 🎯 Decorators
|
|
297
|
-
|
|
298
|
-
The permissions-contractx package provides a comprehensive set of decorators for authentication and authorization in your NestJS applications.
|
|
299
|
-
|
|
300
|
-
### 🔐 Authentication Decorators
|
|
301
|
-
|
|
302
|
-
#### `@Public()`
|
|
303
|
-
Marks routes as public (no authentication required). Essential for login endpoints, health checks, and public APIs.
|
|
304
|
-
|
|
305
|
-
```typescript
|
|
306
|
-
@Controller('auth')
|
|
307
|
-
export class AuthController {
|
|
308
|
-
@Public()
|
|
309
|
-
@Post('login')
|
|
310
|
-
login(@Body() credentials: LoginDto) {
|
|
311
|
-
return this.authService.login(credentials);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
@Public()
|
|
315
|
-
@Get('health')
|
|
316
|
-
getHealth() {
|
|
317
|
-
return { status: 'OK', timestamp: new Date() };
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
#### `@CurrentUser(property?)`
|
|
323
|
-
Injects the current authenticated user or specific user properties into route handlers.
|
|
324
|
-
|
|
325
|
-
```typescript
|
|
326
|
-
@Controller('users')
|
|
327
|
-
export class UsersController {
|
|
328
|
-
// Inject full user object
|
|
329
|
-
@Get('profile')
|
|
330
|
-
getProfile(@CurrentUser() user: JwtPayload) {
|
|
331
|
-
return {
|
|
332
|
-
id: user.sub,
|
|
333
|
-
name: user.fullName,
|
|
334
|
-
email: user.email,
|
|
335
|
-
roles: user.role,
|
|
336
|
-
permissions: user.permissions
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Inject specific user property
|
|
341
|
-
@Post('actions')
|
|
342
|
-
performAction(
|
|
343
|
-
@CurrentUser('sub') userId: string,
|
|
344
|
-
@CurrentUser('fullName') userName: string,
|
|
345
|
-
@Body() actionData: any
|
|
346
|
-
) {
|
|
347
|
-
return {
|
|
348
|
-
action: 'completed',
|
|
349
|
-
performedBy: userName,
|
|
350
|
-
userId: userId,
|
|
351
|
-
data: actionData
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
#### `@UserId()`
|
|
358
|
-
Convenience decorator to get the current user's ID directly.
|
|
359
|
-
|
|
360
|
-
```typescript
|
|
361
|
-
@Controller('resources')
|
|
362
|
-
export class ResourcesController {
|
|
363
|
-
@Post()
|
|
364
|
-
createResource(
|
|
365
|
-
@UserId() userId: string,
|
|
366
|
-
@Body() resourceData: CreateResourceDto
|
|
367
|
-
) {
|
|
368
|
-
return this.resourcesService.create({
|
|
369
|
-
...resourceData,
|
|
370
|
-
createdBy: userId,
|
|
371
|
-
createdAt: new Date()
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
@Get('my-resources')
|
|
376
|
-
getMyResources(@UserId() userId: string) {
|
|
377
|
-
return this.resourcesService.findByUser(userId);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
#### `@UserRoles()`
|
|
383
|
-
Injects the current user's roles array into the route handler.
|
|
384
|
-
|
|
385
|
-
```typescript
|
|
386
|
-
@Controller('dashboard')
|
|
387
|
-
export class DashboardController {
|
|
388
|
-
@Get('capabilities')
|
|
389
|
-
getUserCapabilities(@UserRoles() roles: string[]) {
|
|
390
|
-
const capabilities = {
|
|
391
|
-
canManageUsers: roles.includes('superadmin') || roles.includes('client_contract_admin'),
|
|
392
|
-
canViewReports: roles.some(role => role.includes('reports')),
|
|
393
|
-
canManageContracts: roles.some(role => role.includes('contract_admin')),
|
|
394
|
-
isClientSide: roles.some(role => role.startsWith('client_')),
|
|
395
|
-
isProviderSide: roles.some(role => role.startsWith('provider_'))
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
return { roles, capabilities };
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
```
|
|
65
|
+
import { RequirePermissions } from 'permissions-contractx';
|
|
402
66
|
|
|
403
|
-
#### `@UserPermissions()`
|
|
404
|
-
Injects the current user's permissions array into the route handler.
|
|
405
|
-
|
|
406
|
-
```typescript
|
|
407
|
-
@Controller('admin')
|
|
408
|
-
export class AdminController {
|
|
409
|
-
@Get('available-actions')
|
|
410
|
-
getAvailableActions(@UserPermissions() permissions: string[]) {
|
|
411
|
-
const modulePermissions = permissions.reduce((acc, permission) => {
|
|
412
|
-
const [module, action] = permission.split(':');
|
|
413
|
-
if (!acc[module]) acc[module] = [];
|
|
414
|
-
acc[module].push(action);
|
|
415
|
-
return acc;
|
|
416
|
-
}, {});
|
|
417
|
-
|
|
418
|
-
return {
|
|
419
|
-
totalPermissions: permissions.length,
|
|
420
|
-
moduleBreakdown: modulePermissions,
|
|
421
|
-
rawPermissions: permissions
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
#### `@UserClientId()`
|
|
428
|
-
Injects the current user's client ID for multi-tenant applications.
|
|
429
|
-
|
|
430
|
-
```typescript
|
|
431
|
-
@Controller('tenant')
|
|
432
|
-
export class TenantController {
|
|
433
|
-
@Get('data')
|
|
434
|
-
getTenantData(@UserClientId() clientId: string) {
|
|
435
|
-
return this.dataService.findByClient(clientId);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
@Post('settings')
|
|
439
|
-
updateTenantSettings(
|
|
440
|
-
@UserClientId() clientId: string,
|
|
441
|
-
@Body() settings: TenantSettingsDto
|
|
442
|
-
) {
|
|
443
|
-
return this.settingsService.update(clientId, settings);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
### 👥 Role-Based Authorization Decorators
|
|
449
|
-
|
|
450
|
-
#### `@Roles(...roles)`
|
|
451
|
-
Requires user to have **at least one** of the specified roles (OR logic).
|
|
452
|
-
|
|
453
|
-
```typescript
|
|
454
|
-
@Controller('admin')
|
|
455
|
-
export class AdminController {
|
|
456
|
-
// User needs superadmin OR any contract admin role
|
|
457
|
-
@Roles('superadmin', 'client_contract_admin', 'provider_contract_admin')
|
|
458
|
-
@Get('dashboard')
|
|
459
|
-
getAdminDashboard() {
|
|
460
|
-
return { message: 'Admin dashboard data' };
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Multiple specific roles
|
|
464
|
-
@Roles('client_finance_manager', 'provider_finance_manager')
|
|
465
|
-
@Get('financial-reports')
|
|
466
|
-
getFinancialReports() {
|
|
467
|
-
return this.reportsService.getFinancialData();
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
#### `@AdminOnly()`
|
|
473
|
-
Shortcut for admin-level access (superadmin, client_contract_admin, provider_contract_admin).
|
|
474
|
-
|
|
475
|
-
```typescript
|
|
476
|
-
@Controller('system')
|
|
477
|
-
export class SystemController {
|
|
478
|
-
@AdminOnly()
|
|
479
|
-
@Delete('cache')
|
|
480
|
-
clearCache() {
|
|
481
|
-
return this.cacheService.clear();
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
@AdminOnly()
|
|
485
|
-
@Post('maintenance-mode')
|
|
486
|
-
enableMaintenanceMode(@Body() config: MaintenanceConfigDto) {
|
|
487
|
-
return this.systemService.enableMaintenance(config);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
#### `@ClientOnly()`
|
|
493
|
-
Restricts access to client-side roles only.
|
|
494
|
-
|
|
495
|
-
```typescript
|
|
496
|
-
@Controller('client')
|
|
497
|
-
export class ClientController {
|
|
498
|
-
@ClientOnly()
|
|
499
|
-
@Get('performance-metrics')
|
|
500
|
-
getClientPerformanceMetrics(@UserClientId() clientId: string) {
|
|
501
|
-
return this.metricsService.getClientMetrics(clientId);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
@ClientOnly()
|
|
505
|
-
@Post('escalation')
|
|
506
|
-
createEscalation(@Body() escalationData: CreateEscalationDto) {
|
|
507
|
-
return this.escalationService.create(escalationData);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
#### `@ProviderOnly()`
|
|
513
|
-
Restricts access to provider-side roles only.
|
|
514
|
-
|
|
515
|
-
```typescript
|
|
516
|
-
@Controller('provider')
|
|
517
|
-
export class ProviderController {
|
|
518
|
-
@ProviderOnly()
|
|
519
|
-
@Get('delivery-schedule')
|
|
520
|
-
getDeliverySchedule() {
|
|
521
|
-
return this.deliveryService.getSchedule();
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
@ProviderOnly()
|
|
525
|
-
@Put('deliverable/:id/status')
|
|
526
|
-
updateDeliverableStatus(
|
|
527
|
-
@Param('id') id: string,
|
|
528
|
-
@Body() statusUpdate: DeliverableStatusDto
|
|
529
|
-
) {
|
|
530
|
-
return this.deliverableService.updateStatus(id, statusUpdate);
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
#### `@SuperAdminOnly()`
|
|
536
|
-
Restricts access to superadmin role only.
|
|
537
|
-
|
|
538
|
-
```typescript
|
|
539
|
-
@Controller('system-admin')
|
|
540
|
-
export class SystemAdminController {
|
|
541
|
-
@SuperAdminOnly()
|
|
542
|
-
@Post('create-tenant')
|
|
543
|
-
createTenant(@Body() tenantData: CreateTenantDto) {
|
|
544
|
-
return this.tenantService.create(tenantData);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
@SuperAdminOnly()
|
|
548
|
-
@Delete('user/:id')
|
|
549
|
-
deleteUser(@Param('id') userId: string) {
|
|
550
|
-
return this.userService.delete(userId);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
### 🔐 Permission-Based Authorization Decorators
|
|
556
|
-
|
|
557
|
-
#### `@RequirePermissions(...permissions)`
|
|
558
|
-
Requires user to have **all** specified permissions (AND logic).
|
|
559
|
-
|
|
560
|
-
```typescript
|
|
561
67
|
@Controller('contracts')
|
|
562
68
|
export class ContractsController {
|
|
563
|
-
// User must have ALL specified permissions
|
|
564
|
-
@RequirePermissions('contracts.create', 'contracts.validate')
|
|
565
69
|
@Post()
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Single permission requirement
|
|
571
|
-
@RequirePermissions('contracts.delete')
|
|
572
|
-
@Delete(':id')
|
|
573
|
-
deleteContract(@Param('id') id: string) {
|
|
574
|
-
return this.contractService.delete(id);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
#### `@RequireAnyPermission(...permissions)`
|
|
580
|
-
Requires user to have **at least one** of the specified permissions (OR logic).
|
|
581
|
-
|
|
582
|
-
```typescript
|
|
583
|
-
@Controller('documents')
|
|
584
|
-
export class DocumentsController {
|
|
585
|
-
// User needs ANY of these permissions
|
|
586
|
-
@RequireAnyPermission('documents.read', 'documents.show', 'documents.filter')
|
|
587
|
-
@Get()
|
|
588
|
-
getDocuments(@Query() filters: DocumentFiltersDto) {
|
|
589
|
-
return this.documentService.findAll(filters);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// Administrative permissions
|
|
593
|
-
@RequireAnyPermission('documents.admin', 'superadmin.all')
|
|
594
|
-
@Post('batch-process')
|
|
595
|
-
batchProcessDocuments(@Body() batchData: BatchProcessDto) {
|
|
596
|
-
return this.documentService.batchProcess(batchData);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
### 🎯 Module-Level Permission Decorators
|
|
602
|
-
|
|
603
|
-
These decorators provide convenient access patterns based on common CRUD operations:
|
|
604
|
-
|
|
605
|
-
#### `@ReadAccess(module)`
|
|
606
|
-
Grants read access to a module (read, show, or filter permissions).
|
|
607
|
-
|
|
608
|
-
```typescript
|
|
609
|
-
@Controller('reports')
|
|
610
|
-
export class ReportsController {
|
|
611
|
-
@ReadAccess('reports')
|
|
612
|
-
@Get('financial')
|
|
613
|
-
getFinancialReports() {
|
|
614
|
-
// User needs reports.read, reports.show, OR reports.filter
|
|
615
|
-
return this.reportsService.getFinancialReports();
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
@ReadAccess('contracts')
|
|
619
|
-
@Get('contract-summary')
|
|
620
|
-
getContractSummary() {
|
|
621
|
-
// User needs contracts.read, contracts.show, OR contracts.filter
|
|
622
|
-
return this.contractService.getSummary();
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
#### `@WriteAccess(module)`
|
|
628
|
-
Grants write access to a module (create or update permissions).
|
|
629
|
-
|
|
630
|
-
```typescript
|
|
631
|
-
@Controller('users')
|
|
632
|
-
export class UsersController {
|
|
633
|
-
@WriteAccess('users')
|
|
634
|
-
@Post()
|
|
635
|
-
createUser(@Body() userData: CreateUserDto) {
|
|
636
|
-
// User needs users.create OR users.update
|
|
637
|
-
return this.userService.create(userData);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
@WriteAccess('users')
|
|
641
|
-
@Put(':id')
|
|
642
|
-
updateUser(
|
|
643
|
-
@Param('id') id: string,
|
|
644
|
-
@Body() userData: UpdateUserDto
|
|
645
|
-
) {
|
|
646
|
-
// User needs users.create OR users.update
|
|
647
|
-
return this.userService.update(id, userData);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
```
|
|
651
|
-
|
|
652
|
-
#### `@DeleteAccess(module)`
|
|
653
|
-
Grants delete access to a module.
|
|
654
|
-
|
|
655
|
-
```typescript
|
|
656
|
-
@Controller('deliverables')
|
|
657
|
-
export class DeliverablesController {
|
|
658
|
-
@DeleteAccess('deliverables')
|
|
659
|
-
@Delete(':id')
|
|
660
|
-
deleteDeliverable(@Param('id') id: string) {
|
|
661
|
-
// User needs deliverables.delete
|
|
662
|
-
return this.deliverableService.delete(id);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
```
|
|
666
|
-
|
|
667
|
-
#### `@FullAccess(module)`
|
|
668
|
-
Grants full CRUD access to a module (any module permission).
|
|
669
|
-
|
|
670
|
-
```typescript
|
|
671
|
-
@Controller('admin/modules')
|
|
672
|
-
export class ModuleAdminController {
|
|
673
|
-
@FullAccess('users')
|
|
674
|
-
@Post('users/bulk-operation')
|
|
675
|
-
bulkUserOperation(@Body() operation: BulkOperationDto) {
|
|
676
|
-
// User needs ANY users module permission
|
|
677
|
-
return this.userService.bulkOperation(operation);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
```
|
|
681
|
-
|
|
682
|
-
### 🔄 Combining Decorators
|
|
683
|
-
|
|
684
|
-
Decorators can be combined for complex authorization logic:
|
|
685
|
-
|
|
686
|
-
```typescript
|
|
687
|
-
@Controller('advanced')
|
|
688
|
-
export class AdvancedController {
|
|
689
|
-
// Combine role and permission requirements
|
|
690
|
-
@Roles('client_contract_admin', 'provider_contract_admin')
|
|
691
|
-
@RequirePermissions('contracts.approve')
|
|
692
|
-
@Put('contracts/:id/approve')
|
|
693
|
-
approveContract(
|
|
694
|
-
@Param('id') contractId: string,
|
|
695
|
-
@CurrentUser() user: JwtPayload
|
|
696
|
-
) {
|
|
697
|
-
return this.contractService.approve(contractId, user.sub);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// Complex permission logic
|
|
701
|
-
@ClientOnly()
|
|
702
|
-
@RequireAnyPermission('reports.generate', 'reports.admin')
|
|
703
|
-
@Post('custom-report')
|
|
704
|
-
generateCustomReport(
|
|
705
|
-
@Body() reportSpec: CustomReportDto,
|
|
706
|
-
@UserClientId() clientId: string
|
|
707
|
-
) {
|
|
708
|
-
return this.reportsService.generateCustom(reportSpec, clientId);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// Multiple permission sets
|
|
712
|
-
@RequirePermissions('documents.create')
|
|
713
|
-
@RequireAnyPermission('contracts.update', 'deliverables.update')
|
|
714
|
-
@Post('document-with-linking')
|
|
715
|
-
createLinkedDocument(
|
|
716
|
-
@Body() documentData: LinkedDocumentDto,
|
|
717
|
-
@UserId() userId: string
|
|
718
|
-
) {
|
|
719
|
-
// User must have documents.create AND (contracts.update OR deliverables.update)
|
|
720
|
-
return this.documentService.createWithLinking(documentData, userId);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
### 🏷️ Method vs Class Level Decorators
|
|
726
|
-
|
|
727
|
-
Decorators can be applied at both class and method levels:
|
|
728
|
-
|
|
729
|
-
```typescript
|
|
730
|
-
// Class-level decorators apply to all methods
|
|
731
|
-
@Controller('secure')
|
|
732
|
-
@UseGuards(JwtAuthGuard, RolesGuard, PermissionsGuard)
|
|
733
|
-
@ClientOnly() // All methods require client roles
|
|
734
|
-
export class SecureController {
|
|
735
|
-
@Get('data')
|
|
736
|
-
getData() {
|
|
737
|
-
// Inherits @ClientOnly from class
|
|
738
|
-
return this.dataService.getClientData();
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
@RequirePermissions('admin.override')
|
|
742
|
-
@Get('admin-data')
|
|
743
|
-
getAdminData() {
|
|
744
|
-
// Combines @ClientOnly + @RequirePermissions
|
|
745
|
-
return this.dataService.getAdminData();
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
@Public() // Overrides class-level @ClientOnly
|
|
749
|
-
@Get('public-info')
|
|
750
|
-
getPublicInfo() {
|
|
751
|
-
return { message: 'Public information' };
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
```
|
|
755
|
-
|
|
756
|
-
## 🛠️ Services
|
|
757
|
-
|
|
758
|
-
### UserContextService
|
|
759
|
-
|
|
760
|
-
Access user information and check permissions programmatically:
|
|
761
|
-
|
|
762
|
-
```typescript
|
|
763
|
-
import { Injectable } from '@nestjs/common';
|
|
764
|
-
import { UserContextService } from 'permissions-contractx';
|
|
765
|
-
|
|
766
|
-
@Injectable()
|
|
767
|
-
export class MyService {
|
|
768
|
-
constructor(private userContext: UserContextService) {}
|
|
769
|
-
|
|
770
|
-
async doSomething() {
|
|
771
|
-
// Get user information
|
|
772
|
-
const user = this.userContext.getUser();
|
|
773
|
-
const userId = this.userContext.getUserId();
|
|
774
|
-
const roles = this.userContext.getUserRoles();
|
|
775
|
-
|
|
776
|
-
// Check permissions
|
|
777
|
-
if (this.userContext.hasRole('admin')) {
|
|
778
|
-
// Do admin stuff
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
if (this.userContext.hasPermission('users.create')) {
|
|
782
|
-
// Can create users
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
if (this.userContext.isClientUser()) {
|
|
786
|
-
// Client-side user logic
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// Get user summary for logging
|
|
790
|
-
const summary = this.userContext.getUserSummary();
|
|
791
|
-
console.log('User accessing service:', summary);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
```
|
|
795
|
-
|
|
796
|
-
## 🚫 Error Handling
|
|
797
|
-
|
|
798
|
-
The package provides specific error messages for different authentication and authorization failures:
|
|
799
|
-
|
|
800
|
-
### Common Error Responses
|
|
801
|
-
|
|
802
|
-
```json
|
|
803
|
-
// 401 - Missing token
|
|
804
|
-
{
|
|
805
|
-
"statusCode": 401,
|
|
806
|
-
"message": "Access token is required",
|
|
807
|
-
"error": "Unauthorized"
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
// 401 - Invalid token
|
|
811
|
-
{
|
|
812
|
-
"statusCode": 401,
|
|
813
|
-
"message": "Invalid access token",
|
|
814
|
-
"error": "Unauthorized"
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// 401 - Expired token
|
|
818
|
-
{
|
|
819
|
-
"statusCode": 401,
|
|
820
|
-
"message": "Access token has expired",
|
|
821
|
-
"error": "Unauthorized"
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// 403 - Insufficient permissions
|
|
825
|
-
{
|
|
826
|
-
"statusCode": 403,
|
|
827
|
-
"message": "Insufficient permissions. Required: users:create",
|
|
828
|
-
"error": "Forbidden"
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// 403 - Missing roles
|
|
832
|
-
{
|
|
833
|
-
"statusCode": 403,
|
|
834
|
-
"message": "Insufficient roles. Required: admin",
|
|
835
|
-
"error": "Forbidden"
|
|
836
|
-
}
|
|
837
|
-
```
|
|
838
|
-
|
|
839
|
-
### Custom Error Handling
|
|
840
|
-
|
|
841
|
-
```typescript
|
|
842
|
-
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
|
|
843
|
-
import { UnauthorizedException, ForbiddenException } from '@nestjs/common';
|
|
844
|
-
|
|
845
|
-
@Catch(UnauthorizedException, ForbiddenException)
|
|
846
|
-
export class AuthExceptionFilter implements ExceptionFilter {
|
|
847
|
-
catch(exception: UnauthorizedException | ForbiddenException, host: ArgumentsHost) {
|
|
848
|
-
const ctx = host.switchToHttp();
|
|
849
|
-
const response = ctx.getResponse();
|
|
850
|
-
|
|
851
|
-
// Custom error handling logic
|
|
852
|
-
response.status(exception.getStatus()).json({
|
|
853
|
-
statusCode: exception.getStatus(),
|
|
854
|
-
message: exception.message,
|
|
855
|
-
timestamp: new Date().toISOString(),
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
```
|
|
860
|
-
|
|
861
|
-
## 💾 Database Integration & ODS Seeder
|
|
862
|
-
|
|
863
|
-
### ODS Roles and Permissions Seeder
|
|
864
|
-
|
|
865
|
-
The package includes a complete seeder for the ODS (Operational Data Store) roles and permissions matrix with **16 predefined roles** and **23 modules**:
|
|
866
|
-
|
|
867
|
-
```typescript
|
|
868
|
-
import { ContractXRolePermissionSeeder } from '@your-org/permissions-contractx';
|
|
869
|
-
|
|
870
|
-
@Injectable()
|
|
871
|
-
export class DatabaseSeederService {
|
|
872
|
-
constructor(
|
|
873
|
-
private readonly seeder: ContractXRolePermissionSeeder,
|
|
874
|
-
// Your repository dependencies
|
|
875
|
-
@InjectRepository(Role) private roleRepo: Repository<Role>,
|
|
876
|
-
@InjectRepository(Permission) private permissionRepo: Repository<Permission>,
|
|
877
|
-
@InjectRepository(RolePermission) private rolePermissionRepo: Repository<RolePermission>
|
|
878
|
-
) {}
|
|
879
|
-
|
|
880
|
-
async seedRolesAndPermissions() {
|
|
881
|
-
const { roles, permissions, rolePermissions } = await this.seeder.seed();
|
|
882
|
-
|
|
883
|
-
// Save to database
|
|
884
|
-
const savedRoles = await this.roleRepo.save(roles);
|
|
885
|
-
const savedPermissions = await this.permissionRepo.save(permissions);
|
|
886
|
-
|
|
887
|
-
// Update IDs for role-permission mappings
|
|
888
|
-
const mappingsWithIds = rolePermissions.map(rp => ({
|
|
889
|
-
...rp,
|
|
890
|
-
roleId: savedRoles.find(r => r.name === roles.find(role => role.id === rp.roleId)?.name)?.id,
|
|
891
|
-
permissionId: savedPermissions.find(p => p.name === permissions.find(perm => perm.id === rp.permissionId)?.name)?.id
|
|
892
|
-
}));
|
|
893
|
-
|
|
894
|
-
await this.rolePermissionRepo.save(mappingsWithIds);
|
|
895
|
-
|
|
896
|
-
// Validate seeded data
|
|
897
|
-
const isValid = await this.seeder.validateSeed(savedRoles, savedPermissions, mappingsWithIds);
|
|
898
|
-
if (!isValid) {
|
|
899
|
-
throw new Error('Seeder validation failed');
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
// Get statistics
|
|
903
|
-
const stats = this.seeder.getSeederStats();
|
|
904
|
-
console.log('Seeding completed:', stats);
|
|
905
|
-
/*
|
|
906
|
-
Output:
|
|
907
|
-
{
|
|
908
|
-
totalRoles: 16,
|
|
909
|
-
totalPermissions: 161, // 23 modules × 7 actions each
|
|
910
|
-
totalModules: 23,
|
|
911
|
-
rolesByType: {
|
|
912
|
-
system: 4,
|
|
913
|
-
client: 6,
|
|
914
|
-
provider: 6
|
|
915
|
-
},
|
|
916
|
-
permissionsByCategory: {
|
|
917
|
-
contract_management: 98,
|
|
918
|
-
financial: 21,
|
|
919
|
-
system_admin: 21,
|
|
920
|
-
user_management: 7,
|
|
921
|
-
client_management: 7,
|
|
922
|
-
provider_management: 7
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
*/
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
```
|
|
929
|
-
|
|
930
|
-
### ODS Roles Matrix (16 Roles)
|
|
931
|
-
|
|
932
|
-
The seeder creates the following roles based on the complete ODS specification:
|
|
933
|
-
|
|
934
|
-
#### 🔧 System Roles
|
|
935
|
-
- **superadmin**: Full system access to all modules
|
|
936
|
-
- **support**: Broad access excluding client management
|
|
937
|
-
- **auditor**: Read-only audit and compliance access
|
|
938
|
-
- **guest**: Limited read-only access
|
|
939
|
-
|
|
940
|
-
#### 👥 Client-Side Roles
|
|
941
|
-
- **client_contract_admin**: Full contract management for client
|
|
942
|
-
- **client_performance_manager**: SLA and performance management
|
|
943
|
-
- **client_finance_manager**: Financial operations and invoicing
|
|
944
|
-
- **client_reports_manager**: Reporting and analytics
|
|
945
|
-
- **client_relationship_manager**: Relationship and meeting management
|
|
946
|
-
- **client_risk_manager**: Risk and security management
|
|
947
|
-
|
|
948
|
-
#### 🏢 Provider-Side Roles
|
|
949
|
-
- **provider_contract_admin**: Provider contract administration
|
|
950
|
-
- **provider_performance_manager**: Provider performance management
|
|
951
|
-
- **provider_finance_manager**: Provider financial operations
|
|
952
|
-
- **provider_relationship_manager**: Provider relationship management
|
|
953
|
-
- **provider_risk_manager**: Provider risk management
|
|
954
|
-
- **provider_operator**: Operational support and deliverable management
|
|
955
|
-
|
|
956
|
-
### 📊 ODS Modules (23 Modules)
|
|
957
|
-
|
|
958
|
-
The seeder covers all ContractX modules with comprehensive CRUD permissions:
|
|
959
|
-
|
|
960
|
-
- **Core Management**: clients, contracts, users, providers, documents, clauses
|
|
961
|
-
- **Deliverables**: deliverables, subdeliverables, deliverable_history
|
|
962
|
-
- **SLA Management**: sla_services, measurement_windows, credit_service_levels
|
|
963
|
-
- **Collaboration**: meetings, meeting_participants, action_items
|
|
964
|
-
- **Notifications**: notification_escalations
|
|
965
|
-
- **Financial**: invoice_services, invoice_lines
|
|
966
|
-
- **System**: security_control, configuration, workflows
|
|
967
|
-
|
|
968
|
-
### Database Entity Examples
|
|
969
|
-
|
|
970
|
-
For database integration with TypeORM, implement the seeder interfaces:
|
|
971
|
-
|
|
972
|
-
```typescript
|
|
973
|
-
// Role Entity
|
|
974
|
-
@Entity('roles')
|
|
975
|
-
export class Role implements RoleEntity {
|
|
976
|
-
@PrimaryGeneratedColumn('uuid')
|
|
977
|
-
id: string;
|
|
978
|
-
|
|
979
|
-
@Column({ unique: true })
|
|
980
|
-
name: string;
|
|
981
|
-
|
|
982
|
-
@Column()
|
|
983
|
-
displayName: string;
|
|
984
|
-
|
|
985
|
-
@Column()
|
|
986
|
-
description: string;
|
|
987
|
-
|
|
988
|
-
@Column()
|
|
989
|
-
type: string;
|
|
990
|
-
|
|
991
|
-
@Column()
|
|
992
|
-
scope: string;
|
|
993
|
-
|
|
994
|
-
@Column()
|
|
995
|
-
level: number;
|
|
996
|
-
|
|
997
|
-
@Column({ default: true })
|
|
998
|
-
isActive: boolean;
|
|
999
|
-
|
|
1000
|
-
@Column()
|
|
1001
|
-
tenantAssociation: string;
|
|
1002
|
-
|
|
1003
|
-
@Column('jsonb')
|
|
1004
|
-
metadata: Record<string, any>;
|
|
1005
|
-
|
|
1006
|
-
@CreateDateColumn()
|
|
1007
|
-
createdAt: Date;
|
|
1008
|
-
|
|
1009
|
-
@UpdateDateColumn()
|
|
1010
|
-
updatedAt: Date;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
// Permission Entity
|
|
1014
|
-
@Entity('permissions')
|
|
1015
|
-
export class Permission implements PermissionEntity {
|
|
1016
|
-
@PrimaryGeneratedColumn('uuid')
|
|
1017
|
-
id: string;
|
|
1018
|
-
|
|
1019
|
-
@Column({ unique: true })
|
|
1020
|
-
name: string;
|
|
1021
|
-
|
|
1022
|
-
@Column()
|
|
1023
|
-
displayName: string;
|
|
1024
|
-
|
|
1025
|
-
@Column()
|
|
1026
|
-
description: string;
|
|
1027
|
-
|
|
1028
|
-
@Column()
|
|
1029
|
-
module: string;
|
|
1030
|
-
|
|
1031
|
-
@Column()
|
|
1032
|
-
action: string;
|
|
1033
|
-
|
|
1034
|
-
@Column()
|
|
1035
|
-
category: string;
|
|
1036
|
-
|
|
1037
|
-
@Column()
|
|
1038
|
-
scope: string;
|
|
1039
|
-
|
|
1040
|
-
@Column({ default: true })
|
|
1041
|
-
isActive: boolean;
|
|
1042
|
-
|
|
1043
|
-
@Column('jsonb')
|
|
1044
|
-
metadata: Record<string, any>;
|
|
1045
|
-
|
|
1046
|
-
@CreateDateColumn()
|
|
1047
|
-
createdAt: Date;
|
|
1048
|
-
|
|
1049
|
-
@UpdateDateColumn()
|
|
1050
|
-
updatedAt: Date;
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
// Role-Permission Junction
|
|
1054
|
-
@Entity('role_permissions')
|
|
1055
|
-
export class RolePermission implements RolePermissionEntity {
|
|
1056
|
-
@PrimaryGeneratedColumn('uuid')
|
|
1057
|
-
id: string;
|
|
1058
|
-
|
|
1059
|
-
@Column()
|
|
1060
|
-
roleId: string;
|
|
1061
|
-
|
|
1062
|
-
@Column()
|
|
1063
|
-
permissionId: string;
|
|
1064
|
-
|
|
1065
|
-
@Column({ default: true })
|
|
1066
|
-
isActive: boolean;
|
|
1067
|
-
|
|
1068
|
-
@Column('jsonb', { nullable: true })
|
|
1069
|
-
metadata: Record<string, any>;
|
|
1070
|
-
|
|
1071
|
-
@CreateDateColumn()
|
|
1072
|
-
createdAt: Date;
|
|
1073
|
-
|
|
1074
|
-
@UpdateDateColumn()
|
|
1075
|
-
updatedAt: Date;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
// User entity example with JWT payload
|
|
1079
|
-
@Entity('users')
|
|
1080
|
-
export class User implements JwtPayload {
|
|
1081
|
-
@PrimaryGeneratedColumn('uuid')
|
|
1082
|
-
id: string;
|
|
1083
|
-
|
|
1084
|
-
@Column()
|
|
1085
|
-
email: string;
|
|
1086
|
-
|
|
1087
|
-
@Column('simple-array')
|
|
1088
|
-
roles: string[];
|
|
1089
|
-
|
|
1090
|
-
@Column('simple-array')
|
|
1091
|
-
permissions: string[];
|
|
1092
|
-
|
|
1093
|
-
@Column({ nullable: true })
|
|
1094
|
-
clientId?: string;
|
|
1095
|
-
|
|
1096
|
-
@Column({ nullable: true })
|
|
1097
|
-
providerId?: string;
|
|
1098
|
-
|
|
1099
|
-
@Column()
|
|
1100
|
-
firstName: string;
|
|
1101
|
-
|
|
1102
|
-
@Column()
|
|
1103
|
-
lastName: string;
|
|
1104
|
-
|
|
1105
|
-
@Column({ default: true })
|
|
1106
|
-
isActive: boolean;
|
|
1107
|
-
|
|
1108
|
-
@CreateDateColumn()
|
|
1109
|
-
createdAt: Date;
|
|
1110
|
-
|
|
1111
|
-
@UpdateDateColumn()
|
|
1112
|
-
updatedAt: Date;
|
|
1113
|
-
}
|
|
1114
|
-
```
|
|
1115
|
-
|
|
1116
|
-
### Integration with Auth Service
|
|
1117
|
-
|
|
1118
|
-
```typescript
|
|
1119
|
-
// In your auth-service-contract
|
|
1120
|
-
@Injectable()
|
|
1121
|
-
export class AuthService {
|
|
1122
|
-
constructor(
|
|
1123
|
-
private readonly seeder: ContractXRolePermissionSeeder,
|
|
1124
|
-
private readonly authorizationService: ContractXAuthorizationService,
|
|
1125
|
-
private readonly validationService: ContractXValidationService,
|
|
1126
|
-
) {}
|
|
1127
|
-
|
|
1128
|
-
async initializeSystem() {
|
|
1129
|
-
// Seed ODS roles and permissions
|
|
1130
|
-
await this.seeder.seed();
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
async validateUserAccess(user: JwtPayload, requiredPermission: string) {
|
|
1134
|
-
return this.authorizationService.checkPermission(user, requiredPermission);
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
async getUserAccessMatrix(user: JwtPayload) {
|
|
1138
|
-
return this.authorizationService.generateAccessMatrix(user);
|
|
1139
|
-
}
|
|
70
|
+
@RequirePermissions('contracts.create')
|
|
71
|
+
create() { /* ... */ }
|
|
1140
72
|
}
|
|
1141
73
|
```
|
|
1142
74
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
| Variable | Description | Default | Required |
|
|
1146
|
-
|----------|-------------|---------|----------|
|
|
1147
|
-
| `JWT_SECRET` | JWT signing secret | - | ✅ Yes |
|
|
1148
|
-
| `JWT_ISSUER` | Token issuer | `contractx-api` | No |
|
|
1149
|
-
| `JWT_AUDIENCE` | Token audience | `contractx-users` | No |
|
|
1150
|
-
| `JWT_EXPIRES_IN` | Token expiration | `15m` | No |
|
|
1151
|
-
| `JWT_CLOCK_TOLERANCE` | Clock tolerance (seconds) | `0` | No |
|
|
1152
|
-
| `ENABLE_GLOBAL_AUTH` | Apply auth guard globally | `false` | No |
|
|
1153
|
-
| `ENABLE_GLOBAL_ROLES` | Apply roles guard globally | `false` | No |
|
|
1154
|
-
| `ENABLE_GLOBAL_PERMISSIONS` | Apply permissions guard globally | `false` | No |
|
|
1155
|
-
| `ENABLE_AUTH_LOGGING` | Enable security logging | `false` | No |
|
|
1156
|
-
| `DISABLE_AUTH` | Disable authentication | `false` | No |
|
|
1157
|
-
|
|
1158
|
-
## 🔒 Security Best Practices
|
|
1159
|
-
|
|
1160
|
-
### 1. JWT Token Structure
|
|
1161
|
-
|
|
1162
|
-
The package expects JWT tokens with this payload structure:
|
|
75
|
+
Override del eje lectura/escritura cuando la convención HTTP no aplica:
|
|
1163
76
|
|
|
1164
77
|
```typescript
|
|
1165
|
-
|
|
1166
|
-
sub: string; // User ID
|
|
1167
|
-
role: string[]; // User roles
|
|
1168
|
-
permissions: string[]; // User permissions
|
|
1169
|
-
fullName: string; // User's full name
|
|
1170
|
-
email?: string; // User's email
|
|
1171
|
-
clientId?: string; // Organization/client ID
|
|
1172
|
-
sessionId?: string; // Session tracking
|
|
1173
|
-
iat?: number; // Issued at
|
|
1174
|
-
exp?: number; // Expires at
|
|
1175
|
-
}
|
|
1176
|
-
```
|
|
1177
|
-
|
|
1178
|
-
### 2. Environment Variables
|
|
1179
|
-
|
|
1180
|
-
```env
|
|
1181
|
-
# Required
|
|
1182
|
-
JWT_SECRET=your-256-bit-secret-key-here
|
|
1183
|
-
JWT_ISSUER=your-api-name
|
|
1184
|
-
JWT_AUDIENCE=your-api-users
|
|
1185
|
-
|
|
1186
|
-
# Optional but recommended
|
|
1187
|
-
JWT_EXPIRES_IN=15m
|
|
1188
|
-
JWT_CLOCK_TOLERANCE=30
|
|
1189
|
-
|
|
1190
|
-
# Security
|
|
1191
|
-
ENABLE_AUTH_LOGGING=true
|
|
1192
|
-
```
|
|
1193
|
-
|
|
1194
|
-
### 3. Global Guards
|
|
78
|
+
import { PermissionWrites } from 'permissions-contractx';
|
|
1195
79
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
enableGlobalRoles: false, // Only specific routes check roles
|
|
1202
|
-
enableGlobalPermissions: false, // Only specific routes check permissions
|
|
1203
|
-
},
|
|
1204
|
-
})
|
|
80
|
+
// Un POST que en realidad solo consulta:
|
|
81
|
+
@Post('search')
|
|
82
|
+
@RequirePermissions('contracts.view_list')
|
|
83
|
+
@PermissionWrites(false)
|
|
84
|
+
search() { /* ... */ }
|
|
1205
85
|
```
|
|
1206
86
|
|
|
1207
|
-
##
|
|
1208
|
-
|
|
1209
|
-
### Unit Testing
|
|
1210
|
-
|
|
1211
|
-
```typescript
|
|
1212
|
-
import { Test } from '@nestjs/testing';
|
|
1213
|
-
import { JwtService } from '@nestjs/jwt';
|
|
1214
|
-
import { JwtAuthGuard } from 'permissions-contractx';
|
|
1215
|
-
|
|
1216
|
-
describe('Authentication', () => {
|
|
1217
|
-
let guard: JwtAuthGuard;
|
|
1218
|
-
|
|
1219
|
-
beforeEach(async () => {
|
|
1220
|
-
const module = await Test.createTestingModule({
|
|
1221
|
-
providers: [
|
|
1222
|
-
JwtAuthGuard,
|
|
1223
|
-
{
|
|
1224
|
-
provide: JwtService,
|
|
1225
|
-
useValue: { verifyAsync: jest.fn() },
|
|
1226
|
-
},
|
|
1227
|
-
// ... other providers
|
|
1228
|
-
],
|
|
1229
|
-
}).compile();
|
|
1230
|
-
|
|
1231
|
-
guard = module.get(JwtAuthGuard);
|
|
1232
|
-
});
|
|
1233
|
-
|
|
1234
|
-
it('should authenticate valid token', async () => {
|
|
1235
|
-
// Test implementation
|
|
1236
|
-
});
|
|
1237
|
-
});
|
|
1238
|
-
```
|
|
1239
|
-
|
|
1240
|
-
### Integration Testing
|
|
1241
|
-
|
|
1242
|
-
```typescript
|
|
1243
|
-
import * as request from 'supertest';
|
|
1244
|
-
|
|
1245
|
-
describe('Protected Routes', () => {
|
|
1246
|
-
it('should reject unauthenticated requests', () => {
|
|
1247
|
-
return request(app.getHttpServer())
|
|
1248
|
-
.get('/protected-route')
|
|
1249
|
-
.expect(401);
|
|
1250
|
-
});
|
|
1251
|
-
|
|
1252
|
-
it('should allow authenticated requests', () => {
|
|
1253
|
-
return request(app.getHttpServer())
|
|
1254
|
-
.get('/protected-route')
|
|
1255
|
-
.set('Authorization', `Bearer ${validToken}`)
|
|
1256
|
-
.expect(200);
|
|
1257
|
-
});
|
|
1258
|
-
});
|
|
1259
|
-
```
|
|
1260
|
-
|
|
1261
|
-
## 🚀 Production Deployment
|
|
1262
|
-
|
|
1263
|
-
### Docker Support
|
|
1264
|
-
|
|
1265
|
-
```dockerfile
|
|
1266
|
-
FROM node:18-alpine
|
|
1267
|
-
WORKDIR /app
|
|
1268
|
-
COPY package*.json ./
|
|
1269
|
-
RUN npm ci --only=production
|
|
1270
|
-
COPY . .
|
|
1271
|
-
RUN npm run build
|
|
1272
|
-
|
|
1273
|
-
ENV NODE_ENV=production
|
|
1274
|
-
ENV JWT_SECRET=${JWT_SECRET}
|
|
1275
|
-
ENV JWT_ISSUER=contractx-production
|
|
1276
|
-
|
|
1277
|
-
EXPOSE 3000
|
|
1278
|
-
CMD ["npm", "run", "start:prod"]
|
|
1279
|
-
```
|
|
1280
|
-
|
|
1281
|
-
### Environment Configuration
|
|
1282
|
-
|
|
1283
|
-
```yaml
|
|
1284
|
-
# docker-compose.yml
|
|
1285
|
-
version: '3.8'
|
|
1286
|
-
services:
|
|
1287
|
-
app:
|
|
1288
|
-
build: .
|
|
1289
|
-
environment:
|
|
1290
|
-
- NODE_ENV=production
|
|
1291
|
-
- JWT_SECRET=${JWT_SECRET}
|
|
1292
|
-
- JWT_ISSUER=contractx-api
|
|
1293
|
-
- JWT_AUDIENCE=contractx-users
|
|
1294
|
-
- ENABLE_AUTH_LOGGING=true
|
|
1295
|
-
```
|
|
1296
|
-
|
|
1297
|
-
## 📖 Examples
|
|
1298
|
-
|
|
1299
|
-
### Complete Controller Example
|
|
1300
|
-
|
|
1301
|
-
```typescript
|
|
1302
|
-
import {
|
|
1303
|
-
Controller,
|
|
1304
|
-
Get,
|
|
1305
|
-
Post,
|
|
1306
|
-
Put,
|
|
1307
|
-
Delete,
|
|
1308
|
-
Body,
|
|
1309
|
-
Param,
|
|
1310
|
-
UseGuards,
|
|
1311
|
-
} from '@nestjs/common';
|
|
1312
|
-
import {
|
|
1313
|
-
JwtAuthGuard,
|
|
1314
|
-
RolesGuard,
|
|
1315
|
-
PermissionsGuard,
|
|
1316
|
-
CurrentUser,
|
|
1317
|
-
Roles,
|
|
1318
|
-
RequirePermissions,
|
|
1319
|
-
Public,
|
|
1320
|
-
ClientOnly,
|
|
1321
|
-
AdminOnly,
|
|
1322
|
-
JwtPayload,
|
|
1323
|
-
} from 'permissions-contractx';
|
|
1324
|
-
|
|
1325
|
-
@Controller('contracts')
|
|
1326
|
-
@UseGuards(JwtAuthGuard, RolesGuard, PermissionsGuard)
|
|
1327
|
-
export class ContractsController {
|
|
1328
|
-
|
|
1329
|
-
@Public()
|
|
1330
|
-
@Get('health')
|
|
1331
|
-
getHealth() {
|
|
1332
|
-
return { status: 'OK' };
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
@Get()
|
|
1336
|
-
@RequirePermissions('contracts.read', 'contracts.filter')
|
|
1337
|
-
async getContracts(@CurrentUser() user: JwtPayload) {
|
|
1338
|
-
// User must have both read AND filter permissions
|
|
1339
|
-
return { contracts: [], requestedBy: user.fullName };
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
@Post()
|
|
1343
|
-
@ClientOnly()
|
|
1344
|
-
@RequirePermissions('contracts.create')
|
|
1345
|
-
async createContract(
|
|
1346
|
-
@Body() contractData: any,
|
|
1347
|
-
@CurrentUser() user: JwtPayload,
|
|
1348
|
-
) {
|
|
1349
|
-
// Only client-side users with create permission
|
|
1350
|
-
return { id: '123', createdBy: user.sub };
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
@Put(':id/approve')
|
|
1354
|
-
@AdminOnly()
|
|
1355
|
-
@RequirePermissions('contracts.approve')
|
|
1356
|
-
async approveContract(
|
|
1357
|
-
@Param('id') id: string,
|
|
1358
|
-
@CurrentUser() user: JwtPayload,
|
|
1359
|
-
) {
|
|
1360
|
-
// Only admins with approval permission
|
|
1361
|
-
return { id, approvedBy: user.fullName, approvedAt: new Date() };
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
@Delete(':id')
|
|
1365
|
-
@Roles('superadmin')
|
|
1366
|
-
async deleteContract(@Param('id') id: string) {
|
|
1367
|
-
// Only superadmin can delete
|
|
1368
|
-
return { message: `Contract ${id} deleted` };
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
```
|
|
1372
|
-
|
|
1373
|
-
## 🤝 Contributing
|
|
1374
|
-
|
|
1375
|
-
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
1376
|
-
|
|
1377
|
-
## 📄 License
|
|
87
|
+
## Modelo de cuatro estados (migración)
|
|
1378
88
|
|
|
1379
|
-
|
|
89
|
+
El `AuthorizationGuard` permite migrar cada endpoint de forma gradual mediante la variable de entorno de enforcement:
|
|
1380
90
|
|
|
1381
|
-
|
|
91
|
+
1. **legacy / solo-roles** — sin gateo por permisos (estado de partida).
|
|
92
|
+
2. **observación** — el gateo por permisos evalúa y registra discrepancias, pero solo el rol-gate bloquea.
|
|
93
|
+
3. **estricto** — ambos gates bloquean.
|
|
94
|
+
4. **solo-permisos** — endpoint graduado, gateado únicamente por permisos.
|
|
1382
95
|
|
|
1383
|
-
-
|
|
1384
|
-
- 🐛 Issues: [GitHub Issues](https://github.com/your-org/permissions-contractx/issues)
|
|
1385
|
-
- 📚 Documentation: [Full Documentation](https://permissions-contractx.dev)
|
|
96
|
+
Esto permite introducir el enforcement servicio por servicio sin romper el acceso existente. El principio rector es *fallar-seguro*: ante cualquier conflicto, gana lo más restrictivo.
|
|
1386
97
|
|
|
1387
|
-
##
|
|
98
|
+
## Versionado
|
|
1388
99
|
|
|
1389
|
-
-
|
|
1390
|
-
- 🚀 **TypeScript First** - Built with TypeScript for TypeScript
|
|
1391
|
-
- 📦 **Tree Shakeable** - Only import what you need
|
|
1392
|
-
- 🔒 **Security Focused** - Built with security best practices
|
|
1393
|
-
- 🧪 **Well Tested** - Comprehensive test coverage
|
|
100
|
+
Sigue [SemVer](https://semver.org/). La serie `1.x` es aditiva sobre el modelo ADR-004; los cambios que retiren la API antigua de autorización se reservan para una mayor (`2.0.0`).
|
|
1394
101
|
|
|
1395
|
-
|
|
102
|
+
## Licencia
|
|
1396
103
|
|
|
1397
|
-
|
|
104
|
+
MIT
|