@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,464 @@
1
+ /**
2
+ * @fileoverview Permission checker for @plyaz/auth
3
+ * @module @plyaz/auth/rbac/permission-checker
4
+ *
5
+ * @description
6
+ * Provides permission validation logic for role-based access control.
7
+ * Handles permission checking with conditions, resource-based permissions,
8
+ * and hierarchical role inheritance. Used by guards and services to
9
+ * enforce authorization policies.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { PermissionChecker } from '@plyaz/auth';
14
+ *
15
+ * const checker = new PermissionChecker();
16
+ * const hasPermission = await checker.checkPermission(
17
+ * userId,
18
+ * 'campaigns',
19
+ * 'create',
20
+ * { ownerId: userId }
21
+ * );
22
+ * ```
23
+ */
24
+
25
+ import { NUMERIX } from "@plyaz/config";
26
+ import type { Permission, PermissionCheckerConfig, PermissionCheckResult, PermissionContext, Role } from "@plyaz/types";
27
+
28
+ /**
29
+ * Permission checker implementation
30
+ * Validates user permissions with context and conditions
31
+ */
32
+ // RoleRepository interface based on PermissionChecker usage
33
+ export interface RoleRepository {
34
+ /**
35
+ * Get all roles assigned to a user
36
+ * @param userId - User identifier
37
+ * @returns Array of Role objects
38
+ */
39
+ getUserRoles(userId: string): Promise<Role[]>;
40
+
41
+ /**
42
+ * Get all permissions assigned to a role
43
+ * @param roleId - Role identifier
44
+ * @returns Array of Permission objects
45
+ */
46
+ getRolePermissions(roleId: string): Promise<Permission[]>;
47
+ }
48
+
49
+
50
+ // UserRepository interface based on PermissionChecker usage
51
+ export interface UserRepository {
52
+ /**
53
+ * Get all roles assigned to a user
54
+ * @param userId - User identifier
55
+ * @returns Array of Role objects
56
+ */
57
+ getUserRoles(userId: string): Promise<Role[]>;
58
+ }
59
+
60
+
61
+ export class PermissionChecker {
62
+ private readonly config: PermissionCheckerConfig;
63
+ private readonly permissionCache = new Map<string, { result: PermissionCheckResult; expires: number }>();
64
+
65
+ constructor(
66
+ config: Partial<PermissionCheckerConfig> = {},
67
+ private userRepository?: UserRepository,
68
+ private roleRepository?:RoleRepository
69
+ ) {
70
+ this.config = {
71
+ enableCaching: true,
72
+ cacheTTL: 300, // 5 minutes
73
+ enableAuditLog: true,
74
+ ...config
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Check if user has permission for resource and action
80
+ * @param userId - User identifier
81
+ * @param resource - Resource name (e.g., 'campaigns', 'users')
82
+ * @param action - Action name (e.g., 'create', 'read', 'update', 'delete')
83
+ * @param context - Permission context for conditional checks
84
+ * @returns Permission check result
85
+ */
86
+ async checkPermission(
87
+ userId: string,
88
+ resource: string,
89
+ action: string,
90
+ context: PermissionContext = {}
91
+ ): Promise<PermissionCheckResult> {
92
+ // Check cache first
93
+ if (this.config.enableCaching) {
94
+ const cacheKey = this.getCacheKey(userId, resource, action, context);
95
+ const cached = this.permissionCache.get(cacheKey);
96
+
97
+ if (cached && cached.expires > Date.now()) {
98
+ return cached.result;
99
+ }
100
+ }
101
+
102
+ // Get user permissions
103
+ const userPermissions = await this.getUserPermissions(userId);
104
+
105
+ // Check direct permissions
106
+ const directResult = this.checkDirectPermission(userPermissions, resource, action, context);
107
+
108
+ if (directResult.granted) {
109
+ return this.cacheAndReturn(userId, resource, action, context, directResult);
110
+ }
111
+
112
+ // Check role-based permissions
113
+ const roleResult = await this.checkRoleBasedPermission(userId, resource, action, context);
114
+
115
+ if (roleResult.granted) {
116
+ return this.cacheAndReturn(userId, resource, action, context, roleResult);
117
+ }
118
+
119
+ // Check wildcard permissions
120
+ const wildcardResult = this.checkWildcardPermission(userPermissions, resource, action, context);
121
+
122
+ if (wildcardResult.granted) {
123
+ return this.cacheAndReturn(userId, resource, action, context, wildcardResult);
124
+ }
125
+
126
+ // Permission denied
127
+ const deniedResult: PermissionCheckResult = {
128
+ granted: false,
129
+ reason: `No permission found for ${resource}:${action}`
130
+ };
131
+
132
+ return this.cacheAndReturn(userId, resource, action, context, deniedResult);
133
+ }
134
+
135
+ /**
136
+ * Check multiple permissions at once
137
+ * @param userId - User identifier
138
+ * @param permissions - Array of permission checks
139
+ * @returns Array of permission check results
140
+ */
141
+ async checkMultiplePermissions(
142
+ userId: string,
143
+ permissions: Array<{ resource: string; action: string; context?: PermissionContext }>
144
+ ): Promise<PermissionCheckResult[]> {
145
+ const results: PermissionCheckResult[] = [];
146
+
147
+ for (const permission of permissions) {
148
+ const result = await this.checkPermission(
149
+ userId,
150
+ permission.resource,
151
+ permission.action,
152
+ permission.context ?? {}
153
+ );
154
+ results.push(result);
155
+ }
156
+
157
+ return results;
158
+ }
159
+
160
+ /**
161
+ * Check if user has any of the specified permissions
162
+ * @param userId - User identifier
163
+ * @param permissions - Array of permission checks
164
+ * @returns True if user has at least one permission
165
+ */
166
+ async hasAnyPermission(
167
+ userId: string,
168
+ permissions: Array<{ resource: string; action: string; context?: PermissionContext }>
169
+ ): Promise<boolean> {
170
+ const results = await this.checkMultiplePermissions(userId, permissions);
171
+ return results.some(result => result.granted);
172
+ }
173
+
174
+ /**
175
+ * Check if user has all specified permissions
176
+ * @param userId - User identifier
177
+ * @param permissions - Array of permission checks
178
+ * @returns True if user has all permissions
179
+ */
180
+ async hasAllPermissions(
181
+ userId: string,
182
+ permissions: Array<{ resource: string; action: string; context?: PermissionContext }>
183
+ ): Promise<boolean> {
184
+ const results = await this.checkMultiplePermissions(userId, permissions);
185
+ return results.every(result => result.granted);
186
+ }
187
+
188
+ /**
189
+ * Get all permissions for a user
190
+ * @param userId - User identifier
191
+ * @returns Array of user permissions
192
+ */
193
+ async getUserPermissions(userId: string): Promise<Permission[]> {
194
+ if (!this.userRepository) {
195
+ throw new Error('UserRepository not configured');
196
+ }
197
+
198
+
199
+ const userRoles = await this.getUserRoles(userId);
200
+ const permissions: Permission[] = [];
201
+
202
+ for (const role of userRoles) {
203
+ const rolePermissions = await this.getRolePermissions(role.id);
204
+ permissions.push(...rolePermissions);
205
+ }
206
+
207
+ return permissions;
208
+ }
209
+
210
+ /**
211
+ * Clear permission cache for user
212
+ * @param userId - User identifier
213
+ */
214
+ clearUserCache(userId: string): void {
215
+ for (const [key] of this.permissionCache.entries()) {
216
+ if (key.startsWith(`${userId}:`)) {
217
+ this.permissionCache.delete(key);
218
+ }
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Clear all permission cache
224
+ */
225
+ clearAllCache(): void {
226
+ this.permissionCache.clear();
227
+ }
228
+
229
+ /**
230
+ * Get cache statistics
231
+ * @returns Cache statistics
232
+ */
233
+ getCacheStats(): { size: number; hitRate: number } {
234
+ return {
235
+ size: this.permissionCache.size,
236
+ hitRate: 0
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Check direct user permissions
242
+ * @param permissions - User permissions
243
+ * @param resource - Resource name
244
+ * @param action - Action name
245
+ * @param context - Permission context
246
+ * @returns Permission check result
247
+ * @private
248
+ */
249
+ private checkDirectPermission(
250
+ permissions: Permission[],
251
+ resource: string,
252
+ action: string,
253
+ context: PermissionContext
254
+ ): PermissionCheckResult {
255
+ for (const permission of permissions) {
256
+ if (permission.resource === resource && permission.action === action) {
257
+ // Check conditions if present
258
+ if (permission.conditions && Object.keys(permission.conditions).length > 0) {
259
+ const conditionsMet = this.evaluateConditions(permission.conditions, context);
260
+
261
+ // eslint-disable-next-line max-depth
262
+ if (conditionsMet) {
263
+ return {
264
+ granted: true,
265
+ reason: 'Direct permission with conditions met',
266
+ matchedPermission: permission,
267
+ conditions: permission.conditions
268
+ };
269
+ }
270
+ } else {
271
+ return {
272
+ granted: true,
273
+ reason: 'Direct permission without conditions',
274
+ matchedPermission: permission
275
+ };
276
+ }
277
+ }
278
+ }
279
+
280
+ return {
281
+ granted: false,
282
+ reason: 'No direct permission found'
283
+ };
284
+ }
285
+
286
+ /**
287
+ * Check role-based permissions
288
+ * @param userId - User identifier
289
+ * @param resource - Resource name
290
+ * @param action - Action name
291
+ * @param context - Permission context
292
+ * @returns Permission check result
293
+ * @private
294
+ */
295
+ private async checkRoleBasedPermission(
296
+ userId: string,
297
+ resource: string,
298
+ action: string,
299
+ context: PermissionContext
300
+ ): Promise<PermissionCheckResult> {
301
+ if (!this.roleRepository) {
302
+ return { granted: false, reason: 'RoleRepository not configured' };
303
+ }
304
+
305
+ const userRoles = await this.getUserRoles(userId);
306
+
307
+ for (const role of userRoles) {
308
+ const rolePermissions = await this.getRolePermissions(role.id);
309
+ const result = this.checkDirectPermission(rolePermissions, resource, action, context);
310
+
311
+ if (result.granted) {
312
+ return {
313
+ ...result,
314
+ reason: `Role-based permission from role: ${role.name}`
315
+ };
316
+ }
317
+ }
318
+
319
+ return {
320
+ granted: false,
321
+ reason: 'No role-based permission found'
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Check wildcard permissions
327
+ * @param permissions - User permissions
328
+ * @param resource - Resource name
329
+ * @param action - Action name
330
+ * @param context - Permission context
331
+ * @returns Permission check result
332
+ * @private
333
+ */
334
+ private checkWildcardPermission(
335
+ permissions: Permission[],
336
+ resource: string,
337
+ action: string,
338
+ context: PermissionContext
339
+ ): PermissionCheckResult {
340
+ // Check for wildcard permissions like "campaigns:*" or "*:read"
341
+ for (const permission of permissions) {
342
+ const resourceMatch = permission.resource === '*' || permission.resource === resource;
343
+ const actionMatch = permission.action === '*' || permission.action === action;
344
+
345
+ if (resourceMatch && actionMatch) {
346
+ // Check conditions if present
347
+ if (permission.conditions && Object.keys(permission.conditions).length > 0) {
348
+ const conditionsMet = this.evaluateConditions(permission.conditions, context);
349
+
350
+ // eslint-disable-next-line max-depth
351
+ if (conditionsMet) {
352
+ return {
353
+ granted: true,
354
+ reason: 'Wildcard permission with conditions met',
355
+ matchedPermission: permission,
356
+ conditions: permission.conditions
357
+ };
358
+ }
359
+ } else {
360
+ return {
361
+ granted: true,
362
+ reason: 'Wildcard permission without conditions',
363
+ matchedPermission: permission
364
+ };
365
+ }
366
+ }
367
+ }
368
+
369
+ return {
370
+ granted: false,
371
+ reason: 'No wildcard permission found'
372
+ };
373
+ }
374
+
375
+ /**
376
+ * Evaluate permission conditions
377
+ * @param conditions - Permission conditions
378
+ * @param context - Permission context
379
+ * @returns True if conditions are met
380
+ * @private
381
+ */
382
+ private evaluateConditions(conditions: Record<string, unknown>, context: PermissionContext): boolean {
383
+ for (const [key, expectedValue] of Object.entries(conditions)) {
384
+ const contextValue = context[key];
385
+
386
+ if (contextValue !== expectedValue) {
387
+ return false;
388
+ }
389
+ }
390
+
391
+ return true;
392
+ }
393
+
394
+ /**
395
+ * Get user roles
396
+ * @param userId - User identifier
397
+ * @returns Array of user roles
398
+ * @private
399
+ */
400
+ private async getUserRoles(userId: string): Promise<Role[]> {
401
+ if (!this.userRepository) {
402
+ return [];
403
+ }
404
+
405
+ return await this.userRepository.getUserRoles(userId);
406
+ }
407
+
408
+ /**
409
+ * Get role permissions
410
+ * @param roleId - Role identifier
411
+ * @returns Array of role permissions
412
+ * @private
413
+ */
414
+ private async getRolePermissions(roleId: string): Promise<Permission[]> {
415
+ if (!this.roleRepository) {
416
+ return [];
417
+ }
418
+
419
+ return await this.roleRepository.getRolePermissions(roleId);
420
+ }
421
+
422
+ /**
423
+ * Generate cache key
424
+ * @param userId - User identifier
425
+ * @param resource - Resource name
426
+ * @param action - Action name
427
+ * @param context - Permission context
428
+ * @returns Cache key
429
+ * @private
430
+ */
431
+ private getCacheKey(userId: string, resource: string, action: string, context: PermissionContext): string {
432
+ const contextHash = JSON.stringify(context);
433
+ return `${userId}:${resource}:${action}:${contextHash}`;
434
+ }
435
+
436
+ /**
437
+ * Cache result and return
438
+ * @param userId - User identifier
439
+ * @param resource - Resource name
440
+ * @param action - Action name
441
+ * @param context - Permission context
442
+ * @param result - Permission check result
443
+ * @returns Permission check result
444
+ * @private
445
+ */
446
+ // eslint-disable-next-line max-params
447
+ private cacheAndReturn(
448
+ userId: string,
449
+ resource: string,
450
+ action: string,
451
+ context: PermissionContext,
452
+ result: PermissionCheckResult
453
+ ): PermissionCheckResult {
454
+ if (this.config.enableCaching) {
455
+ const cacheKey = this.getCacheKey(userId, resource, action, context);
456
+ this.permissionCache.set(cacheKey, {
457
+ result,
458
+ expires: Date.now() + this.config.cacheTTL * NUMERIX.THOUSAND
459
+ });
460
+ }
461
+
462
+ return result;
463
+ }
464
+ }