@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,297 @@
1
+ /**
2
+ * @fileoverview Common types and utility types for @plyaz/auth
3
+ * @module @plyaz/auth/common/types
4
+ */
5
+
6
+ /**
7
+ * Utility type to make specific properties optional
8
+ * @template T - The base type
9
+ * @template K - Keys to make optional
10
+ */
11
+ export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
12
+
13
+ /**
14
+ * Utility type to make specific properties required
15
+ * @template T - The base type
16
+ * @template K - Keys to make required
17
+ */
18
+ export type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;
19
+
20
+ /**
21
+ * Utility type for deep partial objects
22
+ * @template T - The type to make deeply partial
23
+ */
24
+ export type DeepPartial<T> = {
25
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
26
+ };
27
+
28
+ /**
29
+ * User account status enumeration
30
+ */
31
+ export type UserStatus = 'active' | 'inactive' | 'suspended' | 'pending';
32
+
33
+ /**
34
+ * Available permission actions for RBAC
35
+ */
36
+ export type PermissionAction = 'create' | 'read' | 'update' | 'delete' | 'manage' | '*';
37
+
38
+ /**
39
+ * Resource type identifier (e.g., 'users', 'posts', 'campaigns')
40
+ */
41
+ export type ResourceType = string;
42
+
43
+ /**
44
+ * Permission string format: resource:action
45
+ * @example 'users:read', 'posts:create', 'campaigns:manage'
46
+ */
47
+ export type PermissionString = `${ResourceType}:${PermissionAction}`;
48
+
49
+ /**
50
+ * Base configuration interface for all auth components
51
+ */
52
+ export interface BaseConfig {
53
+ /** Whether the component is enabled */
54
+ enabled: boolean;
55
+ /** Enable debug logging */
56
+ debug?: boolean;
57
+ }
58
+
59
+ /**
60
+ * Authentication event structure for audit logging
61
+ */
62
+ export interface AuthEvent {
63
+ /** Event type identifier */
64
+ type: string;
65
+ /** User ID associated with the event */
66
+ userId?: string;
67
+ /** Session ID associated with the event */
68
+ sessionId?: string;
69
+ /** Event timestamp */
70
+ timestamp: Date;
71
+ /** Additional event metadata */
72
+ metadata?: Record<string, string>;
73
+ }
74
+
75
+ /**
76
+ * Generic callback function for authentication events
77
+ * @template T - The data type passed to the callback
78
+ */
79
+ export type AuthCallback<T > = (data: T) => void | Promise<void>;
80
+
81
+ /**
82
+ * Error callback function type
83
+ */
84
+ export type ErrorCallback = (error: Error) => void | Promise<void>;
85
+
86
+ /**
87
+ * Standard API response structure
88
+ * @template T - The data type returned in the response
89
+ */
90
+ export interface ApiResponse<T> {
91
+ /** Whether the request was successful */
92
+ success: boolean;
93
+ /** Response data (if successful) */
94
+ data?: T;
95
+ /** Error information (if failed) */
96
+ error?: {
97
+ /** Error code for programmatic handling */
98
+ code: string;
99
+ /** Human-readable error message */
100
+ message: string;
101
+ /** Additional error details */
102
+ details?: unknown;
103
+ };
104
+ /** Response metadata */
105
+ meta?: {
106
+ /** Response timestamp */
107
+ timestamp: string;
108
+ /** Unique request identifier */
109
+ requestId?: string;
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Pagination parameters for list queries
115
+ */
116
+ export interface PaginationParams {
117
+ /** Page number (1-based) */
118
+ page?: number;
119
+ /** Number of items per page */
120
+ limit?: number;
121
+ /** Number of items to skip */
122
+ offset?: number;
123
+ }
124
+
125
+ /**
126
+ * Paginated response structure
127
+ * @template T - The type of items in the response
128
+ */
129
+ export interface PaginatedResponse<T> {
130
+ /** Array of items for the current page */
131
+ items: T[];
132
+ /** Total number of items across all pages */
133
+ total: number;
134
+ /** Current page number */
135
+ page: number;
136
+ /** Number of items per page */
137
+ limit: number;
138
+ /** Whether there is a next page */
139
+ hasNext: boolean;
140
+ /** Whether there is a previous page */
141
+ hasPrev: boolean;
142
+ }
143
+
144
+ /**
145
+ * Generic filter parameters for queries
146
+ */
147
+ export interface FilterParams {
148
+ [key: string]: unknown;
149
+ }
150
+
151
+ /**
152
+ * Sort parameters for ordered queries
153
+ */
154
+ export interface SortParams {
155
+ /** Field to sort by */
156
+ field: string;
157
+ /** Sort order */
158
+ order: 'asc' | 'desc';
159
+ }
160
+
161
+ /**
162
+ * Audit log entry for tracking system changes
163
+ */
164
+ export interface AuditLog {
165
+ /** Unique audit log identifier */
166
+ id: string;
167
+ /** Action performed (e.g., 'create', 'update', 'delete') */
168
+ action: string;
169
+ /** Resource type affected */
170
+ resource: string;
171
+ /** Specific resource identifier */
172
+ resourceId?: string;
173
+ /** User who performed the action */
174
+ userId?: string;
175
+ /** Session identifier */
176
+ sessionId?: string;
177
+ /** Client IP address */
178
+ ipAddress?: string;
179
+ /** Client user agent */
180
+ userAgent?: string;
181
+ /** When the action occurred */
182
+ timestamp: Date;
183
+ /** Field-level changes made */
184
+ changes?: Record<string, { from: string; to: string }>;
185
+ /** Additional audit metadata */
186
+ metadata?: Record<string, string>;
187
+ }
188
+
189
+ /**
190
+ * Cache entry structure
191
+ * @template T - The type of cached value
192
+ */
193
+ export interface CacheEntry<T> {
194
+ /** Cached value */
195
+ value: T;
196
+ /** Expiration timestamp (Unix timestamp) */
197
+ expiresAt: number;
198
+ /** Creation timestamp (Unix timestamp) */
199
+ createdAt: number;
200
+ }
201
+
202
+ /**
203
+ * Cache performance statistics
204
+ */
205
+ export interface CacheStats {
206
+ /** Number of cache hits */
207
+ hits: number;
208
+ /** Number of cache misses */
209
+ misses: number;
210
+ /** Current cache size */
211
+ size: number;
212
+ /** Hit rate percentage (0-1) */
213
+ hitRate: number;
214
+ }
215
+
216
+ /**
217
+ * Validation rule definition for form fields
218
+ */
219
+ export interface ValidationRule {
220
+ /** Field name to validate */
221
+ field: string;
222
+ /** Whether the field is required */
223
+ required?: boolean;
224
+ /** Expected data type */
225
+ type?: 'string' | 'number' | 'boolean' | 'email' | 'url';
226
+ /** Minimum length for strings */
227
+ minLength?: number;
228
+ /** Maximum length for strings */
229
+ maxLength?: number;
230
+ /** Regular expression pattern to match */
231
+ pattern?: RegExp;
232
+ /** Custom validation function */
233
+ custom?: (value: unknown) => boolean | string;
234
+ }
235
+
236
+ /**
237
+ * Result of field validation
238
+ */
239
+ export interface ValidationResult {
240
+ /** Whether validation passed */
241
+ valid: boolean;
242
+ /** Array of validation errors */
243
+ errors: Array<{
244
+ /** Field that failed validation */
245
+ field: string;
246
+ /** Error message */
247
+ message: string;
248
+ /** Error code for programmatic handling */
249
+ code: string;
250
+ }>;
251
+ }
252
+
253
+ /**
254
+ * Database connection configuration
255
+ */
256
+ export interface DatabaseConfig {
257
+ /** Database host */
258
+ host: string;
259
+ /** Database port */
260
+ port: number;
261
+ /** Database name */
262
+ database: string;
263
+ /** Database username */
264
+ username: string;
265
+ /** Database password */
266
+ password: string;
267
+ /** Enable SSL connection */
268
+ ssl?: boolean;
269
+ /** Connection pool settings */
270
+ pool?: {
271
+ /** Minimum pool connections */
272
+ min: number;
273
+ /** Maximum pool connections */
274
+ max: number;
275
+ };
276
+ }
277
+
278
+ /**
279
+ * Generic metrics collection
280
+ */
281
+ export interface Metrics {
282
+ [key: string]: number | string | boolean;
283
+ }
284
+
285
+ /**
286
+ * Performance metrics for monitoring
287
+ */
288
+ export interface PerformanceMetrics {
289
+ /** Operation duration in milliseconds */
290
+ duration: number;
291
+ /** Memory usage in bytes */
292
+ memory: number;
293
+ /** CPU usage percentage */
294
+ cpu: number;
295
+ /** Measurement timestamp */
296
+ timestamp: Date;
297
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @fileoverview Common utility functions for @plyaz/auth
3
+ * @module @plyaz/auth/common/utils
4
+ */
5
+
6
+ import { NUMERIX } from "@plyaz/config";
7
+
8
+ /**
9
+ * Generate a random string of specified length using alphanumeric characters
10
+ * @param length - Length of the random string (default: 32)
11
+ * @returns Random alphanumeric string
12
+ * @example
13
+ * ```typescript
14
+ * const randomId = generateRandomString(16); // "a1B2c3D4e5F6g7H8"
15
+ * ```
16
+ */
17
+ export function generateRandomString(length: number = 32): string {
18
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
19
+ let result = '';
20
+ for (let i = 0; i < length; i++) {
21
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
22
+ }
23
+ return result;
24
+ }
25
+
26
+ /**
27
+ * Generate a cryptographically secure random ID with timestamp prefix
28
+ * @returns Unique identifier with timestamp and random suffix
29
+ * @example
30
+ * ```typescript
31
+ * const id = generateSecureId(); // "1703123456789_a1B2c3D4e5F6g7H8"
32
+ * ```
33
+ */
34
+ export function generateSecureId(): string {
35
+ return `${Date.now()}_${generateRandomString(NUMERIX.SIXTEEN)}`;
36
+ }
37
+
38
+ /**
39
+ * Sleep for specified milliseconds (async delay)
40
+ * @param ms - Milliseconds to sleep
41
+ * @returns Promise that resolves after the delay
42
+ * @example
43
+ * ```typescript
44
+ * await sleep(1000); // Wait 1 second
45
+ * ```
46
+ */
47
+ export function sleep(ms: number): Promise<void> {
48
+ return new Promise(resolve => globalThis.setTimeout(resolve, ms));
49
+ }
50
+
51
+ /**
52
+ * Mask sensitive data fields for safe logging
53
+ * @param data - Object containing potentially sensitive data
54
+ * @param sensitiveFields - Array of field names to mask (default: ['password', 'token', 'secret'])
55
+ * @returns Object with sensitive fields masked
56
+ * @example
57
+ * ```typescript
58
+ * const masked = maskSensitiveData({ email: 'user@example.com', password: 'secret123' });
59
+ * // Result: { email: 'user@example.com', password: 'secr*****' }
60
+ * ```
61
+ */
62
+ export function maskSensitiveData(
63
+ data: unknown,
64
+ sensitiveFields: string[] = ['password', 'token', 'secret']
65
+ ): unknown {
66
+ if (typeof data !== 'object' || data === null) {
67
+ return data;
68
+ }
69
+
70
+ // Type assertion to allow indexing by string
71
+ const masked: Record<string, string> = { ...(data as Record<string, string>) };
72
+ const four = 4;
73
+ for (const field of sensitiveFields) {
74
+ if (field in masked) {
75
+ const value = masked[field];
76
+ if (typeof value === 'string' && value.length > 0) {
77
+ masked[field] = value.substring(0, four) + '*'.repeat(Math.max(0, value.length - four));
78
+ }
79
+ }
80
+ }
81
+
82
+ return masked;
83
+ }
84
+
@@ -0,0 +1,60 @@
1
+ export interface TokenBlacklistConfig {
2
+ redisUrl?: string;
3
+ keyPrefix: string;
4
+ defaultTTL: number;
5
+ }
6
+
7
+ export class TokenBlacklist {
8
+ private blacklistedTokens = new Map<string, number>();
9
+
10
+ constructor(private config: TokenBlacklistConfig) {}
11
+
12
+ async add(tokenId: string, expiresAt: number): Promise<void> {
13
+ const ttl = Math.max(0, expiresAt - Date.now());
14
+ this.blacklistedTokens.set(tokenId, Date.now() + ttl);
15
+
16
+ // Auto-cleanup expired tokens
17
+ globalThis.setTimeout(() => {
18
+ this.blacklistedTokens.delete(tokenId);
19
+ }, ttl);
20
+ }
21
+
22
+ async blacklistToken(tokenId: string, expiresAt: number): Promise<void> {
23
+ return this.add(tokenId, expiresAt);
24
+ }
25
+
26
+ async isBlacklisted(tokenId: string): Promise<boolean> {
27
+ const expiry = this.blacklistedTokens.get(tokenId);
28
+ if (!expiry) return false;
29
+
30
+ if (expiry < Date.now()) {
31
+ this.blacklistedTokens.delete(tokenId);
32
+ return false;
33
+ }
34
+
35
+ return true;
36
+ }
37
+
38
+ async blacklistAllUserTokens(userId: string, issuedBefore: number): Promise<void> {
39
+ // In production, this would use Redis with a user-specific key
40
+ // For now, we'll use a simple approach
41
+ const userKey = `user:${userId}:blacklist_before`;
42
+ this.blacklistedTokens.set(userKey, issuedBefore);
43
+ }
44
+
45
+ async isUserTokenBlacklisted(userId: string, tokenIssuedAt: number): Promise<boolean> {
46
+ const userKey = `user:${userId}:blacklist_before`;
47
+ const blacklistBefore = this.blacklistedTokens.get(userKey);
48
+
49
+ return blacklistBefore ? tokenIssuedAt < blacklistBefore : false;
50
+ }
51
+
52
+ cleanup(): void {
53
+ const now = Date.now();
54
+ this.blacklistedTokens.forEach((expiry, key) => {
55
+ if (expiry < now) {
56
+ this.blacklistedTokens.delete(key);
57
+ }
58
+ });
59
+ }
60
+ }
@@ -0,0 +1,2 @@
1
+ export * from './jwt/jwt.manager';
2
+ export * from './session/session.manager';
@@ -0,0 +1,131 @@
1
+ import { sign, verify, type JwtPayload, type SignOptions } from "jsonwebtoken";
2
+ import type { AuthTokens } from '@plyaz/types';
3
+ import { NUMERIX } from "@plyaz/config";
4
+
5
+ // Define the token payload interface
6
+ export interface TokenPayload extends JwtPayload {
7
+ sub: string;
8
+ type: "access" | "refresh" | "verification";
9
+ }
10
+
11
+ // Define the StringValue type to match what jsonwebtoken expects
12
+ // Define the StringValue type to match what jsonwebtoken expects
13
+ type StringValue = `${number}s` | `${number}m` | `${number}h` | `${number}d`;
14
+
15
+
16
+ export class JwtManager {
17
+ private config: {
18
+ privateKey: string;
19
+ publicKey: string;
20
+ issuer: string;
21
+ audience: string;
22
+ };
23
+ private algorithm: "RS256" | "HS256" | "ES256" | "HS512";
24
+
25
+ constructor(
26
+ config: typeof JwtManager.prototype.config,
27
+ algorithm: "RS256" | "HS256" | "ES256" | "HS512"
28
+ ) {
29
+ this.config = config;
30
+ this.algorithm = algorithm;
31
+ }
32
+
33
+ // -------------------
34
+ // Generic Token Generator
35
+ // -------------------
36
+ private generateToken(
37
+ payload: Omit<TokenPayload, "iss" | "aud" | "iat" | "exp">,
38
+ expiresIn: string | number = "1h"
39
+ ): string {
40
+ const signOptions: SignOptions = {
41
+ algorithm: this.algorithm,
42
+ expiresIn: expiresIn as StringValue | number,
43
+ issuer: this.config.issuer,
44
+ audience: this.config.audience,
45
+ };
46
+
47
+ return sign(payload, this.config.privateKey, signOptions);
48
+ }
49
+
50
+ generateTokens(
51
+ user: { id: string },
52
+ accessTokenExpiry: string | number = "1h",
53
+ refreshTokenExpiry: string | number = "7d"
54
+ ): AuthTokens {
55
+ const accessToken = this.generateToken({ sub: user.id, type: "access" }, accessTokenExpiry);
56
+ const refreshToken = this.generateToken({ sub: user.id, type: "refresh" }, refreshTokenExpiry);
57
+
58
+ // Calculate the actual expiration time in seconds
59
+ let expiresIn: number;
60
+ if (typeof accessTokenExpiry === 'string') {
61
+ // Parse time strings like "1h", "30m", etc.
62
+ const match = accessTokenExpiry.match(/^(\d+)([smhd])$/);
63
+ if (match) {
64
+ const value = globalThis.parseInt(match[1], NUMERIX.TEN);
65
+ const unit = match[2];
66
+ const multipliers: Record<string, number> = { s: 1, m: 60, h: 3600, d: 86400 };
67
+ expiresIn = value * multipliers[unit];
68
+ } else {
69
+ expiresIn = NUMERIX.THIRTY_SIX_HUNDERD; // Default to 1 hour
70
+ }
71
+ } else {
72
+ expiresIn = accessTokenExpiry;
73
+ }
74
+
75
+ return {
76
+ accessToken,
77
+ refreshToken,
78
+ expiresIn,
79
+ tokenType: "Bearer"
80
+ };
81
+ }
82
+
83
+ // Generate access token
84
+ generateAccessToken(userId: string, expiresIn: string | number = "1h"): string {
85
+ return this.generateToken({ sub: userId, type: "access" }, expiresIn);
86
+ }
87
+
88
+ // Generate refresh token
89
+ generateRefreshToken(userId: string, expiresIn: string | number = "7d"): string {
90
+ return this.generateToken({ sub: userId, type: "refresh" }, expiresIn);
91
+ }
92
+
93
+ // Generate verification token (email confirmation)
94
+ generateVerificationToken(userId: string, expiresIn: string | number = "24h"): string {
95
+ return this.generateToken({ sub: userId, type: "verification" }, expiresIn);
96
+ }
97
+
98
+ // -------------------
99
+ // Token Verification
100
+ // -------------------
101
+ private verifyToken(token: string, tokenType: "access" | "refresh" | "verification"): TokenPayload {
102
+ const decoded = verify(token, this.config.publicKey, {
103
+ algorithms: [this.algorithm],
104
+ issuer: this.config.issuer,
105
+ audience: this.config.audience,
106
+ });
107
+
108
+ if (typeof decoded === "string") throw new Error(`Invalid ${tokenType} token payload`);
109
+
110
+ const payload = decoded as JwtPayload;
111
+
112
+ // Verify the token type
113
+ if (payload.type !== tokenType) {
114
+ throw new Error(`Invalid token type. Expected ${tokenType}, got ${payload.type}`);
115
+ }
116
+
117
+ return payload as TokenPayload;
118
+ }
119
+
120
+ verifyAccessToken(token: string): TokenPayload {
121
+ return this.verifyToken(token, "access");
122
+ }
123
+
124
+ verifyRefreshToken(token: string): TokenPayload {
125
+ return this.verifyToken(token, "refresh");
126
+ }
127
+
128
+ verifyVerificationToken(token: string): TokenPayload {
129
+ return this.verifyToken(token, "verification");
130
+ }
131
+ }
@@ -0,0 +1,56 @@
1
+
2
+ import type { Session, SessionRepository, SessionConfig, CreateSessionData } from '@plyaz/types';
3
+
4
+
5
+
6
+ export class SessionManager {
7
+ constructor(
8
+ private sessionRepo: SessionRepository,
9
+ private config: SessionConfig
10
+ ) {}
11
+
12
+ async createSession(userId: string, deviceInfo: Record<string,unknown>): Promise<Session> {
13
+ await this.enforceSessionLimits(userId);
14
+
15
+ const sessionData: CreateSessionData = {
16
+ userId,
17
+ provider: 'default',
18
+ expiresAt: new Date(Date.now() + this.config.sessionTTL),
19
+ metadata:deviceInfo,
20
+ };
21
+
22
+ return this.sessionRepo.create(sessionData);
23
+ }
24
+
25
+ async getSession(sessionId: string): Promise<Session | null> {
26
+ const session = await this.sessionRepo.findById(sessionId);
27
+ if (!session || session.expiresAt < new Date()) {
28
+ return null;
29
+ }
30
+ return session;
31
+ }
32
+
33
+ async updateLastActive(sessionId: string): Promise<void> {
34
+ await this.sessionRepo.updateLastActive(sessionId);
35
+ }
36
+
37
+ async invalidateSession(sessionId: string): Promise<void> {
38
+ await this.sessionRepo.delete(sessionId);
39
+ }
40
+
41
+ async invalidateAllUserSessions(userId: string): Promise<void> {
42
+ await this.sessionRepo.deleteByUserId(userId);
43
+ }
44
+
45
+ private async enforceSessionLimits(userId: string): Promise<void> {
46
+ const sessions = await this.sessionRepo.findByUserId(userId);
47
+ if (sessions.length >= this.config.maxConcurrentSessions) {
48
+ const oldestSession = sessions.sort((a, b) =>
49
+ a.lastActivityAt.getTime() - b.lastActivityAt.getTime()
50
+ )[0];
51
+ await this.sessionRepo.delete(oldestSession.id);
52
+ }
53
+ }
54
+
55
+
56
+ }