@plyaz/auth 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/pull_request_template.md +71 -0
- package/.github/workflows/deploy.yml +9 -0
- package/.github/workflows/publish.yml +14 -0
- package/.github/workflows/security.yml +20 -0
- package/README.md +89 -0
- package/commits.txt +5 -0
- package/dist/common/index.cjs +48 -0
- package/dist/common/index.cjs.map +1 -0
- package/dist/common/index.mjs +43 -0
- package/dist/common/index.mjs.map +1 -0
- package/dist/index.cjs +20411 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +5139 -0
- package/dist/index.mjs.map +1 -0
- package/eslint.config.mjs +13 -0
- package/index.html +13 -0
- package/package.json +141 -0
- package/src/adapters/auth-adapter-factory.ts +26 -0
- package/src/adapters/auth-adapter.mapper.ts +53 -0
- package/src/adapters/base-auth.adapter.ts +119 -0
- package/src/adapters/clerk/clerk.adapter.ts +204 -0
- package/src/adapters/custom/custom.adapter.ts +119 -0
- package/src/adapters/index.ts +4 -0
- package/src/adapters/next-auth/authOptions.ts +81 -0
- package/src/adapters/next-auth/next-auth.adapter.ts +211 -0
- package/src/api/client.ts +37 -0
- package/src/audit/audit.logger.ts +52 -0
- package/src/client/components/ProtectedRoute.tsx +37 -0
- package/src/client/hooks/useAuth.ts +128 -0
- package/src/client/hooks/useConnectedAccounts.ts +108 -0
- package/src/client/hooks/usePermissions.ts +36 -0
- package/src/client/hooks/useRBAC.ts +36 -0
- package/src/client/hooks/useSession.ts +18 -0
- package/src/client/providers/AuthProvider.tsx +104 -0
- package/src/client/store/auth.store.ts +306 -0
- package/src/client/utils/storage.ts +70 -0
- package/src/common/constants/oauth-providers.ts +49 -0
- package/src/common/errors/auth.errors.ts +64 -0
- package/src/common/errors/specific-auth-errors.ts +201 -0
- package/src/common/index.ts +19 -0
- package/src/common/regex/index.ts +27 -0
- package/src/common/types/auth.types.ts +641 -0
- package/src/common/types/index.ts +297 -0
- package/src/common/utils/index.ts +84 -0
- package/src/core/blacklist/token.blacklist.ts +60 -0
- package/src/core/index.ts +2 -0
- package/src/core/jwt/jwt.manager.ts +131 -0
- package/src/core/session/session.manager.ts +56 -0
- package/src/db/repositories/connected-account.repository.ts +415 -0
- package/src/db/repositories/role.repository.ts +519 -0
- package/src/db/repositories/session.repository.ts +308 -0
- package/src/db/repositories/user.repository.ts +320 -0
- package/src/flows/index.ts +2 -0
- package/src/flows/sign-in.flow.ts +106 -0
- package/src/flows/sign-up.flow.ts +121 -0
- package/src/index.ts +54 -0
- package/src/libs/clerk.helper.ts +36 -0
- package/src/libs/supabase.helper.ts +255 -0
- package/src/libs/supabaseClient.ts +6 -0
- package/src/providers/base/auth-provider.interface.ts +42 -0
- package/src/providers/base/index.ts +1 -0
- package/src/providers/index.ts +2 -0
- package/src/providers/oauth/facebook.provider.ts +97 -0
- package/src/providers/oauth/github.provider.ts +148 -0
- package/src/providers/oauth/google.provider.ts +126 -0
- package/src/providers/oauth/index.ts +3 -0
- package/src/rbac/dynamic-roles.ts +552 -0
- package/src/rbac/index.ts +4 -0
- package/src/rbac/permission-checker.ts +464 -0
- package/src/rbac/role-hierarchy.ts +545 -0
- package/src/rbac/role.manager.ts +75 -0
- package/src/security/csrf/csrf.protection.ts +37 -0
- package/src/security/index.ts +3 -0
- package/src/security/rate-limiting/auth/auth.controller.ts +12 -0
- package/src/security/rate-limiting/auth/rate-limiting.interface.ts +67 -0
- package/src/security/rate-limiting/auth.module.ts +32 -0
- package/src/server/auth.module.ts +158 -0
- package/src/server/decorators/auth.decorator.ts +43 -0
- package/src/server/decorators/auth.decorators.ts +31 -0
- package/src/server/decorators/current-user.decorator.ts +49 -0
- package/src/server/decorators/permission.decorator.ts +49 -0
- package/src/server/guards/auth.guard.ts +56 -0
- package/src/server/guards/custom-throttler.guard.ts +46 -0
- package/src/server/guards/permissions.guard.ts +115 -0
- package/src/server/guards/roles.guard.ts +31 -0
- package/src/server/middleware/auth.middleware.ts +46 -0
- package/src/server/middleware/index.ts +2 -0
- package/src/server/middleware/middleware.ts +11 -0
- package/src/server/middleware/session.middleware.ts +255 -0
- package/src/server/services/account.service.ts +269 -0
- package/src/server/services/auth.service.ts +79 -0
- package/src/server/services/brute-force.service.ts +98 -0
- package/src/server/services/index.ts +15 -0
- package/src/server/services/rate-limiter.service.ts +60 -0
- package/src/server/services/session.service.ts +287 -0
- package/src/server/services/token.service.ts +262 -0
- package/src/session/cookie-store.ts +255 -0
- package/src/session/enhanced-session-manager.ts +406 -0
- package/src/session/index.ts +14 -0
- package/src/session/memory-store.ts +320 -0
- package/src/session/redis-store.ts +443 -0
- package/src/strategies/oauth.strategy.ts +128 -0
- package/src/strategies/traditional-auth.strategy.ts +116 -0
- package/src/tokens/index.ts +4 -0
- package/src/tokens/refresh-token-manager.ts +448 -0
- package/src/tokens/token-validator.ts +311 -0
- package/tsconfig.build.json +28 -0
- package/tsconfig.json +38 -0
- package/tsup.config.mjs +28 -0
- package/vitest.config.mjs +16 -0
- package/vitest.setup.d.ts +2 -0
- package/vitest.setup.d.ts.map +1 -0
- package/vitest.setup.ts +1 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Dynamic roles manager for @plyaz/auth
|
|
3
|
+
* @module @plyaz/auth/rbac/dynamic-roles
|
|
4
|
+
*
|
|
5
|
+
* @description
|
|
6
|
+
* Manages runtime role assignment and modification for role-based access control.
|
|
7
|
+
* Handles temporary roles, conditional role assignment, and role expiration.
|
|
8
|
+
* Enables flexible role management without requiring database schema changes.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { DynamicRoles } from '@plyaz/auth';
|
|
13
|
+
*
|
|
14
|
+
* const dynamicRoles = new DynamicRoles();
|
|
15
|
+
*
|
|
16
|
+
* // Assign temporary role
|
|
17
|
+
* await dynamicRoles.assignTemporaryRole(
|
|
18
|
+
* userId,
|
|
19
|
+
* 'campaign_moderator',
|
|
20
|
+
* { campaignId: '123' },
|
|
21
|
+
* new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours
|
|
22
|
+
* );
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { NUMERIX } from '@plyaz/config';
|
|
27
|
+
import { AUTH_EVENTS } from '@plyaz/types';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Dynamic role assignment
|
|
31
|
+
*/
|
|
32
|
+
export interface DynamicRoleAssignment {
|
|
33
|
+
/** Assignment ID */
|
|
34
|
+
id: string;
|
|
35
|
+
/** User ID */
|
|
36
|
+
userId: string;
|
|
37
|
+
/** Role ID or name */
|
|
38
|
+
roleId: string;
|
|
39
|
+
/** Assignment conditions */
|
|
40
|
+
conditions?: Record<string, string>;
|
|
41
|
+
/** Assignment expiration */
|
|
42
|
+
expiresAt?: Date;
|
|
43
|
+
/** Assignment reason */
|
|
44
|
+
reason?: string;
|
|
45
|
+
/** Assigned by user ID */
|
|
46
|
+
assignedBy?: string;
|
|
47
|
+
/** Assignment metadata */
|
|
48
|
+
metadata?: Record<string, string>;
|
|
49
|
+
/** Created at */
|
|
50
|
+
createdAt: Date;
|
|
51
|
+
/** Is active */
|
|
52
|
+
isActive: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Role condition evaluator function
|
|
57
|
+
*/
|
|
58
|
+
export type RoleConditionEvaluator = (
|
|
59
|
+
userId: string,
|
|
60
|
+
conditions: Record<string, string>,
|
|
61
|
+
context?: Record<string, string>
|
|
62
|
+
) => Promise<boolean>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Dynamic roles configuration
|
|
66
|
+
*/
|
|
67
|
+
export interface DynamicRolesConfig {
|
|
68
|
+
/** Enable role expiration */
|
|
69
|
+
enableExpiration: boolean;
|
|
70
|
+
/** Default role TTL in seconds */
|
|
71
|
+
defaultTTL: number;
|
|
72
|
+
/** Enable condition evaluation */
|
|
73
|
+
enableConditions: boolean;
|
|
74
|
+
/** Maximum assignments per user */
|
|
75
|
+
maxAssignmentsPerUser: number;
|
|
76
|
+
/** Enable audit logging */
|
|
77
|
+
enableAuditLog: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Dynamic roles manager implementation
|
|
82
|
+
* Manages runtime role assignments with conditions and expiration
|
|
83
|
+
*/
|
|
84
|
+
export class DynamicRoles {
|
|
85
|
+
private readonly config: DynamicRolesConfig;
|
|
86
|
+
private readonly assignments = new Map<string, DynamicRoleAssignment>();
|
|
87
|
+
private readonly userAssignments = new Map<string, Set<string>>();
|
|
88
|
+
private readonly conditionEvaluators = new Map<string, RoleConditionEvaluator>();
|
|
89
|
+
private cleanupTimer?: globalThis.NodeJS.Timeout;
|
|
90
|
+
|
|
91
|
+
constructor(config: Partial<DynamicRolesConfig> = {}) {
|
|
92
|
+
this.config = {
|
|
93
|
+
enableExpiration: true,
|
|
94
|
+
defaultTTL: 86400, // 24 hours
|
|
95
|
+
enableConditions: true,
|
|
96
|
+
maxAssignmentsPerUser: 50,
|
|
97
|
+
enableAuditLog: true,
|
|
98
|
+
...config
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Start cleanup timer if expiration is enabled
|
|
102
|
+
if (this.config.enableExpiration) {
|
|
103
|
+
this.startCleanupTimer();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Assign role to user
|
|
109
|
+
* @param userId - User identifier
|
|
110
|
+
* @param roleId - Role identifier
|
|
111
|
+
* @param conditions - Assignment conditions
|
|
112
|
+
* @param expiresAt - Assignment expiration
|
|
113
|
+
* @param assignedBy - Assigning user ID
|
|
114
|
+
* @param reason - Assignment reason
|
|
115
|
+
* @returns Assignment ID
|
|
116
|
+
*/
|
|
117
|
+
// eslint-disable-next-line max-params
|
|
118
|
+
async assignRole(
|
|
119
|
+
userId: string,
|
|
120
|
+
roleId: string,
|
|
121
|
+
conditions?: Record<string, string>,
|
|
122
|
+
expiresAt?: Date,
|
|
123
|
+
assignedBy?: string,
|
|
124
|
+
reason?: string
|
|
125
|
+
): Promise<string> {
|
|
126
|
+
// Check assignment limits
|
|
127
|
+
await this.enforceAssignmentLimits(userId);
|
|
128
|
+
|
|
129
|
+
// Generate assignment ID
|
|
130
|
+
const assignmentId = this.generateAssignmentId();
|
|
131
|
+
|
|
132
|
+
// Create assignment
|
|
133
|
+
const assignment: DynamicRoleAssignment = {
|
|
134
|
+
id: assignmentId,
|
|
135
|
+
userId,
|
|
136
|
+
roleId,
|
|
137
|
+
conditions,
|
|
138
|
+
expiresAt: expiresAt ?? (this.config.enableExpiration ?
|
|
139
|
+
new Date(Date.now() + this.config.defaultTTL * NUMERIX.THOUSAND) : undefined),
|
|
140
|
+
reason,
|
|
141
|
+
assignedBy,
|
|
142
|
+
createdAt: new Date(),
|
|
143
|
+
isActive: true
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Store assignment
|
|
147
|
+
this.assignments.set(assignmentId, assignment);
|
|
148
|
+
|
|
149
|
+
// Track user assignments
|
|
150
|
+
if (!this.userAssignments.has(userId)) {
|
|
151
|
+
this.userAssignments.set(userId, new Set());
|
|
152
|
+
}
|
|
153
|
+
this.userAssignments.get(userId)!.add(assignmentId);
|
|
154
|
+
|
|
155
|
+
// Emit event
|
|
156
|
+
if (this.config.enableAuditLog) {
|
|
157
|
+
this.emitRoleAssignedEvent(assignment);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return assignmentId;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Assign temporary role to user
|
|
165
|
+
* @param userId - User identifier
|
|
166
|
+
* @param roleId - Role identifier
|
|
167
|
+
* @param conditions - Assignment conditions
|
|
168
|
+
* @param duration - Duration in seconds
|
|
169
|
+
* @param assignedBy - Assigning user ID
|
|
170
|
+
* @param reason - Assignment reason
|
|
171
|
+
* @returns Assignment ID
|
|
172
|
+
*/
|
|
173
|
+
// eslint-disable-next-line max-params
|
|
174
|
+
async assignTemporaryRole(
|
|
175
|
+
userId: string,
|
|
176
|
+
roleId: string,
|
|
177
|
+
conditions?: Record<string, string>,
|
|
178
|
+
duration: number = this.config.defaultTTL,
|
|
179
|
+
assignedBy?: string,
|
|
180
|
+
reason?: string
|
|
181
|
+
): Promise<string> {
|
|
182
|
+
const expiresAt = new Date(Date.now() + duration * NUMERIX.THOUSAND);
|
|
183
|
+
|
|
184
|
+
return await this.assignRole(
|
|
185
|
+
userId,
|
|
186
|
+
roleId,
|
|
187
|
+
conditions,
|
|
188
|
+
expiresAt,
|
|
189
|
+
assignedBy,
|
|
190
|
+
reason ?? 'Temporary assignment'
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Revoke role assignment
|
|
196
|
+
* @param assignmentId - Assignment identifier
|
|
197
|
+
* @param revokedBy - Revoking user ID
|
|
198
|
+
* @param reason - Revocation reason
|
|
199
|
+
*/
|
|
200
|
+
async revokeAssignment(
|
|
201
|
+
assignmentId: string,
|
|
202
|
+
revokedBy?: string,
|
|
203
|
+
reason?: string
|
|
204
|
+
): Promise<void> {
|
|
205
|
+
const assignment = this.assignments.get(assignmentId);
|
|
206
|
+
|
|
207
|
+
if (!assignment?.isActive) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Deactivate assignment
|
|
212
|
+
assignment.isActive = false;
|
|
213
|
+
assignment.metadata = {
|
|
214
|
+
...assignment.metadata,
|
|
215
|
+
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Remove from user assignments tracking
|
|
219
|
+
const userAssignmentSet = this.userAssignments.get(assignment.userId);
|
|
220
|
+
if (userAssignmentSet) {
|
|
221
|
+
userAssignmentSet.delete(assignmentId);
|
|
222
|
+
if (userAssignmentSet.size === 0) {
|
|
223
|
+
this.userAssignments.delete(assignment.userId);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Emit event
|
|
228
|
+
if (this.config.enableAuditLog) {
|
|
229
|
+
this.emitRoleRevokedEvent(assignment, revokedBy, reason);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Revoke all role assignments for user
|
|
235
|
+
* @param userId - User identifier
|
|
236
|
+
* @param revokedBy - Revoking user ID
|
|
237
|
+
* @param reason - Revocation reason
|
|
238
|
+
*/
|
|
239
|
+
async revokeAllUserAssignments(
|
|
240
|
+
userId: string,
|
|
241
|
+
revokedBy?: string,
|
|
242
|
+
reason?: string
|
|
243
|
+
): Promise<void> {
|
|
244
|
+
const userAssignmentSet = this.userAssignments.get(userId);
|
|
245
|
+
|
|
246
|
+
if (!userAssignmentSet) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const assignmentIds = Array.from(userAssignmentSet);
|
|
251
|
+
|
|
252
|
+
for (const assignmentId of assignmentIds) {
|
|
253
|
+
await this.revokeAssignment(assignmentId, revokedBy, reason);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get active roles for user
|
|
259
|
+
* @param userId - User identifier
|
|
260
|
+
* @param context - Evaluation context for conditions
|
|
261
|
+
* @returns Array of active role IDs
|
|
262
|
+
*/
|
|
263
|
+
async getUserRoles(userId: string, context?: Record<string, string>): Promise<string[]> {
|
|
264
|
+
const userAssignmentSet = this.userAssignments.get(userId);
|
|
265
|
+
|
|
266
|
+
if (!userAssignmentSet) {
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const activeRoles: string[] = [];
|
|
271
|
+
const now = new Date();
|
|
272
|
+
|
|
273
|
+
for (const assignmentId of userAssignmentSet) {
|
|
274
|
+
const assignment = this.assignments.get(assignmentId);
|
|
275
|
+
|
|
276
|
+
if (!assignment?.isActive) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Check expiration
|
|
281
|
+
if (assignment.expiresAt && assignment.expiresAt < now) {
|
|
282
|
+
await this.revokeAssignment(assignmentId, undefined, 'Expired');
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Check conditions
|
|
287
|
+
if (this.config.enableConditions && assignment.conditions) {
|
|
288
|
+
const conditionsMet = await this.evaluateConditions(
|
|
289
|
+
userId,
|
|
290
|
+
assignment.conditions,
|
|
291
|
+
context
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
if (!conditionsMet) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
activeRoles.push(assignment.roleId);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return activeRoles;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Check if user has specific role
|
|
307
|
+
* @param userId - User identifier
|
|
308
|
+
* @param roleId - Role identifier
|
|
309
|
+
* @param context - Evaluation context for conditions
|
|
310
|
+
* @returns True if user has role
|
|
311
|
+
*/
|
|
312
|
+
async hasRole(userId: string, roleId: string, context?: Record<string, string>): Promise<boolean> {
|
|
313
|
+
const userRoles = await this.getUserRoles(userId, context);
|
|
314
|
+
return userRoles.includes(roleId);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get assignment details
|
|
319
|
+
* @param assignmentId - Assignment identifier
|
|
320
|
+
* @returns Assignment details or null
|
|
321
|
+
*/
|
|
322
|
+
getAssignment(assignmentId: string): DynamicRoleAssignment | null {
|
|
323
|
+
return this.assignments.get(assignmentId) ?? null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get all assignments for user
|
|
328
|
+
* @param userId - User identifier
|
|
329
|
+
* @param includeInactive - Include inactive assignments
|
|
330
|
+
* @returns Array of assignments
|
|
331
|
+
*/
|
|
332
|
+
getUserAssignments(userId: string, includeInactive = false): DynamicRoleAssignment[] {
|
|
333
|
+
const userAssignmentSet = this.userAssignments.get(userId);
|
|
334
|
+
|
|
335
|
+
if (!userAssignmentSet) {
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const assignments: DynamicRoleAssignment[] = [];
|
|
340
|
+
|
|
341
|
+
for (const assignmentId of userAssignmentSet) {
|
|
342
|
+
const assignment = this.assignments.get(assignmentId);
|
|
343
|
+
|
|
344
|
+
if (assignment && (includeInactive || assignment.isActive)) {
|
|
345
|
+
assignments.push(assignment);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return assignments;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Register condition evaluator
|
|
354
|
+
* @param conditionType - Condition type name
|
|
355
|
+
* @param evaluator - Evaluator function
|
|
356
|
+
*/
|
|
357
|
+
registerConditionEvaluator(conditionType: string, evaluator: RoleConditionEvaluator): void {
|
|
358
|
+
this.conditionEvaluators.set(conditionType, evaluator);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Extend assignment expiration
|
|
363
|
+
* @param assignmentId - Assignment identifier
|
|
364
|
+
* @param newExpiresAt - New expiration date
|
|
365
|
+
*/
|
|
366
|
+
async extendAssignment(assignmentId: string, newExpiresAt: Date): Promise<void> {
|
|
367
|
+
const assignment = this.assignments.get(assignmentId);
|
|
368
|
+
|
|
369
|
+
if (!assignment?.isActive) {
|
|
370
|
+
throw new Error('Assignment not found or inactive');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
assignment.expiresAt = newExpiresAt;
|
|
374
|
+
assignment.metadata = {
|
|
375
|
+
...assignment.metadata,
|
|
376
|
+
extendedAt: new Date().toString()
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Clean up expired assignments
|
|
382
|
+
* @returns Number of cleaned assignments
|
|
383
|
+
*/
|
|
384
|
+
async cleanupExpiredAssignments(): Promise<number> {
|
|
385
|
+
let cleanedCount = 0;
|
|
386
|
+
const now = new Date();
|
|
387
|
+
const expiredAssignments: string[] = [];
|
|
388
|
+
|
|
389
|
+
for (const [assignmentId, assignment] of this.assignments.entries()) {
|
|
390
|
+
if (assignment.isActive && assignment.expiresAt && assignment.expiresAt < now) {
|
|
391
|
+
expiredAssignments.push(assignmentId);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
for (const assignmentId of expiredAssignments) {
|
|
396
|
+
await this.revokeAssignment(assignmentId, undefined, 'Expired');
|
|
397
|
+
cleanedCount++;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return cleanedCount;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Get assignment statistics
|
|
405
|
+
* @returns Assignment statistics
|
|
406
|
+
*/
|
|
407
|
+
getStats(): {
|
|
408
|
+
totalAssignments: number;
|
|
409
|
+
activeAssignments: number;
|
|
410
|
+
expiredAssignments: number;
|
|
411
|
+
usersWithAssignments: number;
|
|
412
|
+
} {
|
|
413
|
+
let activeCount = 0;
|
|
414
|
+
let expiredCount = 0;
|
|
415
|
+
const now = new Date();
|
|
416
|
+
|
|
417
|
+
for (const assignment of this.assignments.values()) {
|
|
418
|
+
if (assignment.isActive) {
|
|
419
|
+
if (assignment.expiresAt && assignment.expiresAt < now) {
|
|
420
|
+
expiredCount++;
|
|
421
|
+
} else {
|
|
422
|
+
activeCount++;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
totalAssignments: this.assignments.size,
|
|
429
|
+
activeAssignments: activeCount,
|
|
430
|
+
expiredAssignments: expiredCount,
|
|
431
|
+
usersWithAssignments: this.userAssignments.size
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Destroy dynamic roles manager
|
|
437
|
+
*/
|
|
438
|
+
destroy(): void {
|
|
439
|
+
if (this.cleanupTimer) {
|
|
440
|
+
globalThis.clearInterval(this.cleanupTimer);
|
|
441
|
+
this.cleanupTimer = undefined;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
this.assignments.clear();
|
|
445
|
+
this.userAssignments.clear();
|
|
446
|
+
this.conditionEvaluators.clear();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Evaluate assignment conditions
|
|
451
|
+
* @param userId - User identifier
|
|
452
|
+
* @param conditions - Assignment conditions
|
|
453
|
+
* @param context - Evaluation context
|
|
454
|
+
* @returns True if conditions are met
|
|
455
|
+
* @private
|
|
456
|
+
*/
|
|
457
|
+
private async evaluateConditions(
|
|
458
|
+
userId: string,
|
|
459
|
+
conditions: Record<string, string>,
|
|
460
|
+
context?: Record<string, string>
|
|
461
|
+
): Promise<boolean> {
|
|
462
|
+
for (const [conditionType, conditionValue] of Object.entries(conditions)) {
|
|
463
|
+
const evaluator = this.conditionEvaluators.get(conditionType);
|
|
464
|
+
|
|
465
|
+
if (evaluator) {
|
|
466
|
+
const result = await evaluator(userId, { [conditionType]: conditionValue }, context);
|
|
467
|
+
if (!result) {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
// Default evaluation: simple equality check with context
|
|
472
|
+
if (context && context[conditionType] !== conditionValue) {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Enforce assignment limits per user
|
|
483
|
+
* @param userId - User identifier
|
|
484
|
+
* @private
|
|
485
|
+
*/
|
|
486
|
+
private async enforceAssignmentLimits(userId: string): Promise<void> {
|
|
487
|
+
const userAssignments = this.getUserAssignments(userId, false);
|
|
488
|
+
|
|
489
|
+
if (userAssignments.length >= this.config.maxAssignmentsPerUser) {
|
|
490
|
+
throw new Error(`Maximum assignments per user exceeded: ${this.config.maxAssignmentsPerUser}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Generate unique assignment ID
|
|
496
|
+
* @returns Assignment identifier
|
|
497
|
+
* @private
|
|
498
|
+
*/
|
|
499
|
+
private generateAssignmentId(): string {
|
|
500
|
+
return `dyn_${Date.now()}_${Math.random().toString(NUMERIX.THIRTY_SIX).substr(NUMERIX.TWO, NUMERIX.NINE)}`;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Start cleanup timer for expired assignments
|
|
505
|
+
* @private
|
|
506
|
+
*/
|
|
507
|
+
private startCleanupTimer(): void {
|
|
508
|
+
// Run cleanup every 5 minutes
|
|
509
|
+
this.cleanupTimer = globalThis.setInterval(async () => {
|
|
510
|
+
await this.cleanupExpiredAssignments();
|
|
511
|
+
}, NUMERIX.FIVE * NUMERIX.SIXTY * NUMERIX.THOUSAND);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Emit role assigned event
|
|
516
|
+
* @param assignment - Role assignment
|
|
517
|
+
* @private
|
|
518
|
+
*/
|
|
519
|
+
private emitRoleAssignedEvent(assignment: DynamicRoleAssignment): void {
|
|
520
|
+
// Mock event emission - in real implementation would use event system
|
|
521
|
+
globalThis.console.log(`Event: ${AUTH_EVENTS.ROLE_ASSIGNED}`, {
|
|
522
|
+
userId: assignment.userId,
|
|
523
|
+
roleId: assignment.roleId,
|
|
524
|
+
assignmentId: assignment.id,
|
|
525
|
+
assignedBy: assignment.assignedBy,
|
|
526
|
+
timestamp: assignment.createdAt
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Emit role revoked event
|
|
532
|
+
* @param assignment - Role assignment
|
|
533
|
+
* @param revokedBy - Revoking user ID
|
|
534
|
+
* @param reason - Revocation reason
|
|
535
|
+
* @private
|
|
536
|
+
*/
|
|
537
|
+
private emitRoleRevokedEvent(
|
|
538
|
+
assignment: DynamicRoleAssignment,
|
|
539
|
+
revokedBy?: string,
|
|
540
|
+
reason?: string
|
|
541
|
+
): void {
|
|
542
|
+
// Mock event emission - in real implementation would use event system
|
|
543
|
+
globalThis.console.log(`Event: ${AUTH_EVENTS.ROLE_REVOKED}`, {
|
|
544
|
+
userId: assignment.userId,
|
|
545
|
+
roleId: assignment.roleId,
|
|
546
|
+
assignmentId: assignment.id,
|
|
547
|
+
revokedBy,
|
|
548
|
+
reason,
|
|
549
|
+
timestamp: new Date()
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
}
|