@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.
Files changed (113) hide show
  1. package/.github/pull_request_template.md +71 -0
  2. package/.github/workflows/deploy.yml +9 -0
  3. package/.github/workflows/publish.yml +14 -0
  4. package/.github/workflows/security.yml +20 -0
  5. package/README.md +89 -0
  6. package/commits.txt +5 -0
  7. package/dist/common/index.cjs +48 -0
  8. package/dist/common/index.cjs.map +1 -0
  9. package/dist/common/index.mjs +43 -0
  10. package/dist/common/index.mjs.map +1 -0
  11. package/dist/index.cjs +20411 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.mjs +5139 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/eslint.config.mjs +13 -0
  16. package/index.html +13 -0
  17. package/package.json +141 -0
  18. package/src/adapters/auth-adapter-factory.ts +26 -0
  19. package/src/adapters/auth-adapter.mapper.ts +53 -0
  20. package/src/adapters/base-auth.adapter.ts +119 -0
  21. package/src/adapters/clerk/clerk.adapter.ts +204 -0
  22. package/src/adapters/custom/custom.adapter.ts +119 -0
  23. package/src/adapters/index.ts +4 -0
  24. package/src/adapters/next-auth/authOptions.ts +81 -0
  25. package/src/adapters/next-auth/next-auth.adapter.ts +211 -0
  26. package/src/api/client.ts +37 -0
  27. package/src/audit/audit.logger.ts +52 -0
  28. package/src/client/components/ProtectedRoute.tsx +37 -0
  29. package/src/client/hooks/useAuth.ts +128 -0
  30. package/src/client/hooks/useConnectedAccounts.ts +108 -0
  31. package/src/client/hooks/usePermissions.ts +36 -0
  32. package/src/client/hooks/useRBAC.ts +36 -0
  33. package/src/client/hooks/useSession.ts +18 -0
  34. package/src/client/providers/AuthProvider.tsx +104 -0
  35. package/src/client/store/auth.store.ts +306 -0
  36. package/src/client/utils/storage.ts +70 -0
  37. package/src/common/constants/oauth-providers.ts +49 -0
  38. package/src/common/errors/auth.errors.ts +64 -0
  39. package/src/common/errors/specific-auth-errors.ts +201 -0
  40. package/src/common/index.ts +19 -0
  41. package/src/common/regex/index.ts +27 -0
  42. package/src/common/types/auth.types.ts +641 -0
  43. package/src/common/types/index.ts +297 -0
  44. package/src/common/utils/index.ts +84 -0
  45. package/src/core/blacklist/token.blacklist.ts +60 -0
  46. package/src/core/index.ts +2 -0
  47. package/src/core/jwt/jwt.manager.ts +131 -0
  48. package/src/core/session/session.manager.ts +56 -0
  49. package/src/db/repositories/connected-account.repository.ts +415 -0
  50. package/src/db/repositories/role.repository.ts +519 -0
  51. package/src/db/repositories/session.repository.ts +308 -0
  52. package/src/db/repositories/user.repository.ts +320 -0
  53. package/src/flows/index.ts +2 -0
  54. package/src/flows/sign-in.flow.ts +106 -0
  55. package/src/flows/sign-up.flow.ts +121 -0
  56. package/src/index.ts +54 -0
  57. package/src/libs/clerk.helper.ts +36 -0
  58. package/src/libs/supabase.helper.ts +255 -0
  59. package/src/libs/supabaseClient.ts +6 -0
  60. package/src/providers/base/auth-provider.interface.ts +42 -0
  61. package/src/providers/base/index.ts +1 -0
  62. package/src/providers/index.ts +2 -0
  63. package/src/providers/oauth/facebook.provider.ts +97 -0
  64. package/src/providers/oauth/github.provider.ts +148 -0
  65. package/src/providers/oauth/google.provider.ts +126 -0
  66. package/src/providers/oauth/index.ts +3 -0
  67. package/src/rbac/dynamic-roles.ts +552 -0
  68. package/src/rbac/index.ts +4 -0
  69. package/src/rbac/permission-checker.ts +464 -0
  70. package/src/rbac/role-hierarchy.ts +545 -0
  71. package/src/rbac/role.manager.ts +75 -0
  72. package/src/security/csrf/csrf.protection.ts +37 -0
  73. package/src/security/index.ts +3 -0
  74. package/src/security/rate-limiting/auth/auth.controller.ts +12 -0
  75. package/src/security/rate-limiting/auth/rate-limiting.interface.ts +67 -0
  76. package/src/security/rate-limiting/auth.module.ts +32 -0
  77. package/src/server/auth.module.ts +158 -0
  78. package/src/server/decorators/auth.decorator.ts +43 -0
  79. package/src/server/decorators/auth.decorators.ts +31 -0
  80. package/src/server/decorators/current-user.decorator.ts +49 -0
  81. package/src/server/decorators/permission.decorator.ts +49 -0
  82. package/src/server/guards/auth.guard.ts +56 -0
  83. package/src/server/guards/custom-throttler.guard.ts +46 -0
  84. package/src/server/guards/permissions.guard.ts +115 -0
  85. package/src/server/guards/roles.guard.ts +31 -0
  86. package/src/server/middleware/auth.middleware.ts +46 -0
  87. package/src/server/middleware/index.ts +2 -0
  88. package/src/server/middleware/middleware.ts +11 -0
  89. package/src/server/middleware/session.middleware.ts +255 -0
  90. package/src/server/services/account.service.ts +269 -0
  91. package/src/server/services/auth.service.ts +79 -0
  92. package/src/server/services/brute-force.service.ts +98 -0
  93. package/src/server/services/index.ts +15 -0
  94. package/src/server/services/rate-limiter.service.ts +60 -0
  95. package/src/server/services/session.service.ts +287 -0
  96. package/src/server/services/token.service.ts +262 -0
  97. package/src/session/cookie-store.ts +255 -0
  98. package/src/session/enhanced-session-manager.ts +406 -0
  99. package/src/session/index.ts +14 -0
  100. package/src/session/memory-store.ts +320 -0
  101. package/src/session/redis-store.ts +443 -0
  102. package/src/strategies/oauth.strategy.ts +128 -0
  103. package/src/strategies/traditional-auth.strategy.ts +116 -0
  104. package/src/tokens/index.ts +4 -0
  105. package/src/tokens/refresh-token-manager.ts +448 -0
  106. package/src/tokens/token-validator.ts +311 -0
  107. package/tsconfig.build.json +28 -0
  108. package/tsconfig.json +38 -0
  109. package/tsup.config.mjs +28 -0
  110. package/vitest.config.mjs +16 -0
  111. package/vitest.setup.d.ts +2 -0
  112. package/vitest.setup.d.ts.map +1 -0
  113. package/vitest.setup.ts +1 -0
@@ -0,0 +1,519 @@
1
+ /**
2
+ * @fileoverview Role hierarchy manager for @plyaz/auth
3
+ * @module @plyaz/auth/rbac/role-hierarchy
4
+ *
5
+ * @description
6
+ * Manages role hierarchy and inheritance for role-based access control.
7
+ * Handles role precedence, permission inheritance, and hierarchical
8
+ * role validation. Enables complex organizational structures with
9
+ * inherited permissions and role-based delegation.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { RoleHierarchy } from '@plyaz/auth';
14
+ *
15
+ * const hierarchy = new RoleHierarchy();
16
+ * hierarchy.addRole({ id: 'admin', name: 'Admin', permissions: ['*'] });
17
+ * hierarchy.addRole({ id: 'moderator', name: 'Moderator', permissions: ['read', 'write'] });
18
+ * hierarchy.addRoleRelationship('moderator', 'admin');
19
+ *
20
+ * const hasAdminAccess = hierarchy.hasPermission('moderator', 'delete');
21
+ * console.log(hierarchy.getHierarchyTree());
22
+ * ```
23
+ */
24
+
25
+ import type { Role, RoleHierarchyConfig, RoleNode } from "@plyaz/types";
26
+
27
+ /**
28
+ * Role hierarchy manager implementation
29
+ * Manages role relationships and permission inheritance
30
+ */
31
+
32
+ interface AddRole extends Role {
33
+ permissions?:[]
34
+ }
35
+
36
+ export class RoleHierarchy {
37
+ private readonly config: RoleHierarchyConfig;
38
+ private readonly roles = new Map<string, RoleNode>();
39
+ private readonly hierarchyCache = new Map<string, Set<string>>();
40
+
41
+ constructor(config: Partial<RoleHierarchyConfig> = {}) {
42
+ this.config = {
43
+ enableInheritance: true,
44
+ maxDepth: 10,
45
+ detectCircular: true,
46
+ cacheInheritance: true,
47
+ ...config
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Add role to hierarchy
53
+ */
54
+
55
+ addRole(role: AddRole): void {
56
+ const roleNode: RoleNode = {
57
+ role,
58
+ parents: new Set(),
59
+ children: new Set(),
60
+ permissions: new Set(role.permissions ?? [])
61
+ };
62
+
63
+ this.roles.set(role.id, roleNode);
64
+
65
+ // Clear cache when hierarchy changes
66
+ if (this.config.cacheInheritance) {
67
+ this.hierarchyCache.clear();
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Remove role from hierarchy
73
+ */
74
+ removeRole(roleId: string): void {
75
+ const roleNode = this.roles.get(roleId);
76
+
77
+ if (!roleNode) {
78
+ return;
79
+ }
80
+
81
+ // Remove from parent-child relationships
82
+ for (const parentId of roleNode.parents) {
83
+ const parent = this.roles.get(parentId);
84
+ if (parent) {
85
+ parent.children.delete(roleId);
86
+ }
87
+ }
88
+
89
+ for (const childId of roleNode.children) {
90
+ const child = this.roles.get(childId);
91
+ if (child) {
92
+ child.parents.delete(roleId);
93
+ }
94
+ }
95
+
96
+ this.roles.delete(roleId);
97
+
98
+ // Clear cache when hierarchy changes
99
+ if (this.config.cacheInheritance) {
100
+ this.hierarchyCache.clear();
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Add parent-child relationship between roles
106
+ */
107
+ addRoleRelationship(childRoleId: string, parentRoleId: string): void {
108
+ const childRole = this.roles.get(childRoleId);
109
+ const parentRole = this.roles.get(parentRoleId);
110
+
111
+ if (!childRole || !parentRole) {
112
+ throw new Error('Role not found');
113
+ }
114
+
115
+ // Check for circular dependencies
116
+ if (this.config.detectCircular && this.inheritsFrom(parentRoleId, childRoleId)) {
117
+ throw new Error('Adding relationship would create circular dependency');
118
+ }
119
+
120
+ // Check hierarchy depth
121
+ const depth = this.calculateDepth(parentRoleId);
122
+ if (depth >= this.config.maxDepth) {
123
+ throw new Error(`Maximum hierarchy depth exceeded: ${depth}/${this.config.maxDepth}`);
124
+ }
125
+
126
+ // Add relationship
127
+ childRole.parents.add(parentRoleId);
128
+ parentRole.children.add(childRoleId);
129
+
130
+ // Clear cache when hierarchy changes
131
+ if (this.config.cacheInheritance) {
132
+ this.hierarchyCache.clear();
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Remove parent-child relationship between roles
138
+ */
139
+ removeRoleRelationship(childRoleId: string, parentRoleId: string): void {
140
+ const childRole = this.roles.get(childRoleId);
141
+ const parentRole = this.roles.get(parentRoleId);
142
+
143
+ if (childRole) {
144
+ childRole.parents.delete(parentRoleId);
145
+ }
146
+
147
+ if (parentRole) {
148
+ parentRole.children.delete(childRoleId);
149
+ }
150
+
151
+ // Clear cache when hierarchy changes
152
+ if (this.config.cacheInheritance) {
153
+ this.hierarchyCache.clear();
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Add permission to role
159
+ */
160
+ addPermission(roleId: string, permission: string): void {
161
+ const roleNode = this.roles.get(roleId);
162
+ if (roleNode) {
163
+ roleNode.permissions.add(permission);
164
+ if (this.config.cacheInheritance) {
165
+ this.hierarchyCache.clear();
166
+ }
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Remove permission from role
172
+ */
173
+ removePermission(roleId: string, permission: string): void {
174
+ const roleNode = this.roles.get(roleId);
175
+ if (roleNode) {
176
+ roleNode.permissions.delete(permission);
177
+ if (this.config.cacheInheritance) {
178
+ this.hierarchyCache.clear();
179
+ }
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Check if role has specific permission (including inherited)
185
+ */
186
+ hasPermission(roleId: string, permission: string): boolean {
187
+ if (!this.config.enableInheritance) {
188
+ const roleNode = this.roles.get(roleId);
189
+ return roleNode ? roleNode.permissions.has(permission) : false;
190
+ }
191
+
192
+ return this.getEffectivePermissions(roleId).has(permission);
193
+ }
194
+
195
+ /**
196
+ * Check if role inherits from another role
197
+ */
198
+ inheritsFrom(roleId: string, ancestorRoleId: string): boolean {
199
+ if (roleId === ancestorRoleId) {
200
+ return true;
201
+ }
202
+
203
+ const ancestors = this.getAncestors(roleId);
204
+ return ancestors.has(ancestorRoleId);
205
+ }
206
+
207
+ /**
208
+ * Get all ancestor roles (roles this role inherits from)
209
+ */
210
+ getAncestors(roleId: string): Set<string> {
211
+ if (this.config.cacheInheritance) {
212
+ const cached = this.hierarchyCache.get(`ancestors:${roleId}`);
213
+ if (cached) {
214
+ return cached;
215
+ }
216
+ }
217
+
218
+ const ancestors = this.collectAncestorsSync(roleId);
219
+
220
+ if (this.config.cacheInheritance) {
221
+ this.hierarchyCache.set(`ancestors:${roleId}`, ancestors);
222
+ }
223
+
224
+ return ancestors;
225
+ }
226
+
227
+ /**
228
+ * Get all descendant roles (roles that inherit from this role)
229
+ */
230
+ getDescendants(roleId: string): Set<string> {
231
+ if (this.config.cacheInheritance) {
232
+ const cached = this.hierarchyCache.get(`descendants:${roleId}`);
233
+ if (cached) {
234
+ return cached;
235
+ }
236
+ }
237
+
238
+ const descendants = this.collectDescendantsSync(roleId);
239
+
240
+ if (this.config.cacheInheritance) {
241
+ this.hierarchyCache.set(`descendants:${roleId}`, descendants);
242
+ }
243
+
244
+ return descendants;
245
+ }
246
+
247
+ /**
248
+ * Get effective permissions for role (including inherited)
249
+ */
250
+ getEffectivePermissions(roleId: string): Set<string> {
251
+ if (!this.config.enableInheritance) {
252
+ const roleNode = this.roles.get(roleId);
253
+ return roleNode ? roleNode.permissions : new Set();
254
+ }
255
+
256
+ if (this.config.cacheInheritance) {
257
+ const cached = this.hierarchyCache.get(`permissions:${roleId}`);
258
+ if (cached) {
259
+ return cached;
260
+ }
261
+ }
262
+
263
+ const effectivePermissions = new Set<string>();
264
+ const roleNode = this.roles.get(roleId);
265
+
266
+ if (!roleNode) {
267
+ return effectivePermissions;
268
+ }
269
+
270
+ // Add direct permissions
271
+ for (const permission of roleNode.permissions) {
272
+ effectivePermissions.add(permission);
273
+ }
274
+
275
+ // Add inherited permissions
276
+ const ancestors = this.getAncestors(roleId);
277
+ for (const ancestorId of ancestors) {
278
+ const ancestorNode = this.roles.get(ancestorId);
279
+ if (ancestorNode) {
280
+ for (const permission of ancestorNode.permissions) {
281
+ effectivePermissions.add(permission);
282
+ }
283
+ }
284
+ }
285
+
286
+ if (this.config.cacheInheritance) {
287
+ this.hierarchyCache.set(`permissions:${roleId}`, effectivePermissions);
288
+ }
289
+
290
+ return effectivePermissions;
291
+ }
292
+
293
+
294
+ /**
295
+ * Validate hierarchy integrity
296
+ */
297
+ validateHierarchy(): {
298
+ valid: boolean;
299
+ issues: string[];
300
+ } {
301
+ const issues: string[] = [];
302
+
303
+ // Check for circular dependencies
304
+ if (this.config.detectCircular) {
305
+ for (const roleId of this.roles.keys()) {
306
+ if (this.hasCircularDependency(roleId)) {
307
+ issues.push(`Circular dependency detected involving role: ${roleId}`);
308
+ }
309
+ }
310
+ }
311
+
312
+ // Check hierarchy depth
313
+ for (const roleId of this.roles.keys()) {
314
+ const depth = this.calculateDepth(roleId);
315
+ if (depth > this.config.maxDepth) {
316
+ issues.push(`Role ${roleId} exceeds maximum hierarchy depth: ${depth}/${this.config.maxDepth}`);
317
+ }
318
+ }
319
+
320
+ // Check for orphaned relationships
321
+ for (const [roleId, roleNode] of this.roles.entries()) {
322
+ for (const parentId of roleNode.parents) {
323
+ if (!this.roles.has(parentId)) {
324
+ issues.push(`Role ${roleId} references non-existent parent: ${parentId}`);
325
+ }
326
+ }
327
+
328
+ for (const childId of roleNode.children) {
329
+ if (!this.roles.has(childId)) {
330
+ issues.push(`Role ${roleId} references non-existent child: ${childId}`);
331
+ }
332
+ }
333
+ }
334
+
335
+ return {
336
+ valid: issues.length === 0,
337
+ issues
338
+ };
339
+ }
340
+
341
+ /**
342
+ * Clear hierarchy cache
343
+ */
344
+ clearCache(): void {
345
+ this.hierarchyCache.clear();
346
+ }
347
+
348
+ /**
349
+ * Get hierarchy statistics
350
+ */
351
+ getStats(): {
352
+ totalRoles: number;
353
+ maxDepth: number;
354
+ rootRoles: number;
355
+ leafRoles: number;
356
+ avgPermissions: number;
357
+ } {
358
+ let maxDepth = 0;
359
+ let rootRoles = 0;
360
+ let leafRoles = 0;
361
+ let totalPermissions = 0;
362
+
363
+ for (const [roleId, roleNode] of this.roles.entries()) {
364
+ if (roleNode.parents.size === 0) rootRoles++;
365
+ if (roleNode.children.size === 0) leafRoles++;
366
+
367
+ const depth = this.calculateDepth(roleId);
368
+ maxDepth = Math.max(maxDepth, depth);
369
+ totalPermissions += roleNode.permissions.size;
370
+ }
371
+
372
+ return {
373
+ totalRoles: this.roles.size,
374
+ maxDepth,
375
+ rootRoles,
376
+ leafRoles,
377
+ avgPermissions: this.roles.size > 0 ? Math.round(totalPermissions / this.roles.size) : 0
378
+ };
379
+ }
380
+
381
+ // ==================== PRIVATE METHODS ====================
382
+
383
+ /**
384
+ * Collect ancestors iteratively (synchronous)
385
+ */
386
+ private collectAncestorsSync(roleId: string): Set<string> {
387
+ const ancestors = new Set<string>();
388
+ const stack: string[] = [];
389
+ const visited = new Set<string>();
390
+
391
+ const roleNode = this.roles.get(roleId);
392
+ if (!roleNode) return ancestors;
393
+
394
+ for (const parentId of roleNode.parents) {
395
+ stack.push(parentId);
396
+ }
397
+
398
+ while (stack.length > 0) {
399
+ const parentId = stack.pop()!;
400
+ if (visited.has(parentId)) continue;
401
+
402
+ visited.add(parentId);
403
+ ancestors.add(parentId);
404
+
405
+ const parentNode = this.roles.get(parentId);
406
+ if (parentNode) {
407
+ for (const grandparentId of parentNode.parents) {
408
+ stack.push(grandparentId);
409
+ }
410
+ }
411
+ }
412
+
413
+ return ancestors;
414
+ }
415
+
416
+ /**
417
+ * Collect descendants iteratively (synchronous)
418
+ */
419
+ private collectDescendantsSync(roleId: string): Set<string> {
420
+ const descendants = new Set<string>();
421
+ const stack: string[] = [];
422
+ const visited = new Set<string>();
423
+
424
+ const roleNode = this.roles.get(roleId);
425
+ if (!roleNode) return descendants;
426
+
427
+ for (const childId of roleNode.children) {
428
+ stack.push(childId);
429
+ }
430
+
431
+ while (stack.length > 0) {
432
+ const childId = stack.pop()!;
433
+ if (visited.has(childId)) continue;
434
+
435
+ visited.add(childId);
436
+ descendants.add(childId);
437
+
438
+ const childNode = this.roles.get(childId);
439
+ if (childNode) {
440
+ for (const grandchildId of childNode.children) {
441
+ stack.push(grandchildId);
442
+ }
443
+ }
444
+ }
445
+
446
+ return descendants;
447
+ }
448
+
449
+ /**
450
+ * Check for circular dependency starting from role
451
+ */
452
+ private hasCircularDependency(roleId: string): boolean {
453
+ const visited = new Set<string>();
454
+ const recursionStack = new Set<string>();
455
+ return this.detectCircularRecursive(roleId, visited, recursionStack);
456
+ }
457
+
458
+ /**
459
+ * Detect circular dependency recursively
460
+ */
461
+ private detectCircularRecursive(
462
+ roleId: string,
463
+ visited: Set<string>,
464
+ recursionStack: Set<string>
465
+ ): boolean {
466
+ visited.add(roleId);
467
+ recursionStack.add(roleId);
468
+
469
+ const roleNode = this.roles.get(roleId);
470
+ if (!roleNode) {
471
+ recursionStack.delete(roleId);
472
+ return false;
473
+ }
474
+
475
+ for (const parentId of roleNode.parents) {
476
+ if (!visited.has(parentId)) {
477
+ if (this.detectCircularRecursive(parentId, visited, recursionStack)) {
478
+ return true;
479
+ }
480
+ } else if (recursionStack.has(parentId)) {
481
+ return true; // Circular dependency found
482
+ }
483
+ }
484
+
485
+ recursionStack.delete(roleId);
486
+ return false;
487
+ }
488
+
489
+ /**
490
+ * Calculate hierarchy depth for role (synchronous)
491
+ */
492
+ private calculateDepth(roleId: string): number {
493
+ return this.collectAncestorsSync(roleId).size;
494
+ }
495
+
496
+ /**
497
+ * Build role tree recursively
498
+ */
499
+ private buildRoleTree(roleId: string): { role: Role; children: Record<string, unknown> } | null {
500
+ const roleNode = this.roles.get(roleId);
501
+ if (!roleNode) {
502
+ return null;
503
+ }
504
+
505
+ const tree: { role: Role; children: Record<string, unknown> } = {
506
+ role: roleNode.role,
507
+ children: {}
508
+ };
509
+
510
+ for (const childId of roleNode.children) {
511
+ const childTree = this.buildRoleTree(childId);
512
+ if (childTree) {
513
+ tree.children[childId] = childTree;
514
+ }
515
+ }
516
+
517
+ return tree;
518
+ }
519
+ }