@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,262 @@
1
+ /**
2
+ * @fileoverview Token service for @plyaz/auth
3
+ * @module @plyaz/auth/server/services/token-service
4
+ *
5
+ * @description
6
+ * NestJS service for token management operations. Provides high-level
7
+ * token operations including validation, refresh, and revocation.
8
+ * Wraps TokenValidator and RefreshTokenManager with NestJS integration.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { TokenService } from '@plyaz/auth';
13
+ *
14
+ * @Injectable()
15
+ * export class AuthService {
16
+ * constructor(private tokenService: TokenService) {}
17
+ *
18
+ * async validateRequest(token: string) {
19
+ * return await this.tokenService.validateAccessToken(token);
20
+ * }
21
+ * }
22
+ * ```
23
+ */
24
+
25
+ import { Injectable, Logger } from '@nestjs/common';
26
+ import type { TokenValidator, ValidatedTokenPayload, TokenValidationResult } from '../../tokens/token-validator';
27
+ import type { RefreshTokenManager, TokenPair } from '../../tokens/refresh-token-manager';
28
+
29
+ /**
30
+ * Token service implementation
31
+ * Provides token management operations for NestJS applications
32
+ */
33
+ @Injectable()
34
+ export class TokenService {
35
+ private readonly logger = new Logger(TokenService.name);
36
+
37
+ constructor(
38
+ private tokenValidator: TokenValidator,
39
+ private refreshTokenManager: RefreshTokenManager
40
+ ) {}
41
+
42
+ /**
43
+ * Validate access token
44
+ * @param token - Access token to validate
45
+ * @returns Validation result with payload or error
46
+ */
47
+ async validateAccessToken(token: string): Promise<TokenValidationResult> {
48
+ this.logger.debug('Validating access token');
49
+
50
+ try {
51
+ const result = await this.tokenValidator.validateAccessToken(token);
52
+
53
+ if (!result.valid) {
54
+ this.logger.warn(`Access token validation failed: ${result.error?.message}`);
55
+ }
56
+
57
+ return result;
58
+ } catch (error) {
59
+ this.logger.error('Failed to validate access token', error);
60
+ return {
61
+ valid: false,
62
+ error: {
63
+ code: 'VALIDATION_ERROR',
64
+ message: 'Token validation failed'
65
+ }
66
+ };
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Validate refresh token
72
+ * @param token - Refresh token to validate
73
+ * @returns Validation result with payload or error
74
+ */
75
+ async validateRefreshToken(token: string): Promise<TokenValidationResult> {
76
+ this.logger.debug('Validating refresh token');
77
+
78
+ try {
79
+ const result = await this.tokenValidator.validateRefreshToken(token);
80
+
81
+ if (!result.valid) {
82
+ this.logger.warn(`Refresh token validation failed: ${result.error?.message}`);
83
+ }
84
+
85
+ return result;
86
+ } catch (error) {
87
+ this.logger.error('Failed to validate refresh token', error);
88
+ return {
89
+ valid: false,
90
+ error: {
91
+ code: 'VALIDATION_ERROR',
92
+ message: 'Token validation failed'
93
+ }
94
+ };
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Generate new token pair
100
+ * @param userId - User identifier
101
+ * @param sessionId - Session identifier
102
+ * @param userRoles - User roles for access token
103
+ * @param userPermissions - User permissions for access token
104
+ * @returns Token pair
105
+ */
106
+ async generateTokenPair(
107
+ userId: string,
108
+ sessionId: string,
109
+ userRoles?: string[],
110
+ userPermissions?: string[]
111
+ ): Promise<TokenPair> {
112
+ this.logger.debug(`Generating token pair for user: ${userId}`);
113
+
114
+ try {
115
+ const tokenPair = await this.refreshTokenManager.generateTokenPair(
116
+ userId,
117
+ sessionId,
118
+ userRoles,
119
+ userPermissions
120
+ );
121
+
122
+ this.logger.log(`Token pair generated for user: ${userId}`);
123
+ return tokenPair;
124
+ } catch (error) {
125
+ this.logger.error(`Failed to generate token pair for user: ${userId}`, error);
126
+ throw error;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Refresh token pair using refresh token
132
+ * @param refreshToken - Current refresh token
133
+ * @param userRoles - Updated user roles
134
+ * @param userPermissions - Updated user permissions
135
+ * @returns New token pair
136
+ */
137
+ async refreshTokenPair(
138
+ refreshToken: string,
139
+ userRoles?: string[],
140
+ userPermissions?: string[]
141
+ ): Promise<TokenPair> {
142
+ this.logger.debug('Refreshing token pair');
143
+
144
+ try {
145
+ const tokenPair = await this.refreshTokenManager.refreshTokenPair(
146
+ refreshToken,
147
+ userRoles,
148
+ userPermissions
149
+ );
150
+
151
+ this.logger.log('Token pair refreshed successfully');
152
+ return tokenPair;
153
+ } catch (error) {
154
+ this.logger.error('Failed to refresh token pair', error);
155
+ throw error;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Revoke refresh token
161
+ * @param refreshToken - Refresh token to revoke
162
+ */
163
+ async revokeRefreshToken(refreshToken: string): Promise<void> {
164
+ this.logger.debug('Revoking refresh token');
165
+
166
+ try {
167
+ await this.refreshTokenManager.revokeRefreshToken(refreshToken);
168
+ this.logger.log('Refresh token revoked successfully');
169
+ } catch (error) {
170
+ this.logger.error('Failed to revoke refresh token', error);
171
+ throw error;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Revoke all refresh tokens for a user
177
+ * @param userId - User identifier
178
+ */
179
+ async revokeAllUserTokens(userId: string): Promise<void> {
180
+ this.logger.debug(`Revoking all tokens for user: ${userId}`);
181
+
182
+ try {
183
+ await this.refreshTokenManager.revokeAllUserTokens(userId);
184
+ this.logger.log(`All tokens revoked for user: ${userId}`);
185
+ } catch (error) {
186
+ this.logger.error(`Failed to revoke all tokens for user: ${userId}`, error);
187
+ throw error;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Revoke all refresh tokens for a session
193
+ * @param sessionId - Session identifier
194
+ */
195
+ async revokeSessionTokens(sessionId: string): Promise<void> {
196
+ this.logger.debug(`Revoking tokens for session: ${sessionId}`);
197
+
198
+ try {
199
+ await this.refreshTokenManager.revokeSessionTokens(sessionId);
200
+ this.logger.log(`Tokens revoked for session: ${sessionId}`);
201
+ } catch (error) {
202
+ this.logger.error(`Failed to revoke tokens for session: ${sessionId}`, error);
203
+ throw error;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Extract token payload without validation
209
+ * @param token - JWT token
210
+ * @returns Decoded payload or null
211
+ */
212
+ extractTokenPayload(token: string): ValidatedTokenPayload | null {
213
+ try {
214
+ return this.tokenValidator.extractPayload(token);
215
+ } catch (error) {
216
+ this.logger.error('Failed to extract token payload', error);
217
+ return null;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Check if token is expired
223
+ * @param token - JWT token
224
+ * @returns True if token is expired
225
+ */
226
+ isTokenExpired(token: string): boolean {
227
+ try {
228
+ return this.tokenValidator.isTokenExpired(token);
229
+ } catch (error) {
230
+ this.logger.error('Failed to check token expiration', error);
231
+ return true;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Get token expiration time
237
+ * @param token - JWT token
238
+ * @returns Expiration date or null
239
+ */
240
+ getTokenExpiration(token: string): Date | null {
241
+ try {
242
+ return this.tokenValidator.getTokenExpiration(token);
243
+ } catch (error) {
244
+ this.logger.error('Failed to get token expiration', error);
245
+ return null;
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Get time until token expires
251
+ * @param token - JWT token
252
+ * @returns Seconds until expiration or null
253
+ */
254
+ getTimeUntilExpiration(token: string): number | null {
255
+ try {
256
+ return this.tokenValidator.getTimeUntilExpiration(token);
257
+ } catch (error) {
258
+ this.logger.error('Failed to get time until expiration', error);
259
+ return null;
260
+ }
261
+ }
262
+ }
@@ -0,0 +1,255 @@
1
+ /**
2
+ * @fileoverview Cookie-based session store for @plyaz/auth
3
+ * @module @plyaz/auth/session/cookie-store
4
+ *
5
+ * @description
6
+ * Implements session storage using HTTP cookies. Provides stateless session
7
+ * management by storing session data directly in encrypted cookies.
8
+ * Suitable for applications that don't require server-side session storage
9
+ * or need to minimize server memory usage.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { CookieStore } from '@plyaz/auth';
14
+ *
15
+ * const store = new CookieStore({
16
+ * secretKey: 'your-secret-key',
17
+ * cookieName: 'session',
18
+ * secure: true
19
+ * });
20
+ *
21
+ * await store.set('session_123', sessionData, 3600);
22
+ * ```
23
+ */
24
+
25
+ import { NUMERIX } from '@plyaz/config';
26
+ import type { SessionData, SessionStore, SessionStoreConfig } from '@plyaz/types';
27
+ import { createCipher, createDecipher } from 'crypto';
28
+
29
+ /**
30
+ * Cookie store configuration
31
+ */
32
+ export interface CookieStoreConfig extends Partial<SessionStoreConfig> {
33
+ /** Secret key for encryption */
34
+ secretKey: string;
35
+ /** Cookie name */
36
+ cookieName: string;
37
+ /** Cookie domain */
38
+ domain?: string;
39
+ /** Cookie path */
40
+ path?: string;
41
+ /** Secure flag (HTTPS only) */
42
+ secure?: boolean;
43
+ /** HttpOnly flag */
44
+ httpOnly?: boolean;
45
+ /** SameSite policy */
46
+ sameSite?: 'strict' | 'lax' | 'none';
47
+ }
48
+
49
+ /**
50
+ * Cookie-based session store implementation
51
+ * Stores session data in encrypted HTTP cookies
52
+ */
53
+ export class CookieStore implements SessionStore {
54
+ private readonly config: Required<CookieStoreConfig>;
55
+ private readonly sessions = new Map<string, SessionData>();
56
+
57
+ constructor(config: CookieStoreConfig) {
58
+ this.config = {
59
+ defaultTTL: 3600,
60
+ maxSessionsPerUser: 5,
61
+ cleanupInterval: 300,
62
+ keyPrefix: 'session:',
63
+ domain: '',
64
+ path: '/',
65
+ secure: true,
66
+ httpOnly: true,
67
+ sameSite: 'lax',
68
+ ...config
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Store session data in cookie
74
+ * @param sessionId - Session identifier
75
+ * @param data - Session data to store
76
+ * @param ttlSeconds - Time to live in seconds
77
+ */
78
+ async set(sessionId: string, data: SessionData, ttlSeconds: number): Promise<void> {
79
+ // Store in memory for this implementation
80
+ // In real implementation, this would set HTTP cookie
81
+ this.sessions.set(sessionId, {
82
+ ...data,
83
+ expiresAt: new Date(Date.now() + ttlSeconds * NUMERIX.THOUSAND)
84
+ });
85
+
86
+ // Simulate cookie setting
87
+ this.setCookie(sessionId, data, ttlSeconds);
88
+ }
89
+
90
+ /**
91
+ * Retrieve session data from cookie
92
+ * @param sessionId - Session identifier
93
+ * @returns Session data or null if not found/expired
94
+ */
95
+ async get(sessionId: string): Promise<SessionData | null> {
96
+ const data = this.sessions.get(sessionId);
97
+
98
+ if (!data) {
99
+ return null;
100
+ }
101
+
102
+ // Check expiration
103
+ if (data.expiresAt < new Date()) {
104
+ await this.delete(sessionId);
105
+ return null;
106
+ }
107
+
108
+ return data;
109
+ }
110
+
111
+ /**
112
+ * Delete session cookie
113
+ * @param sessionId - Session identifier
114
+ */
115
+ async delete(sessionId: string): Promise<void> {
116
+ this.sessions.delete(sessionId);
117
+ // In real implementation, this would clear the cookie
118
+ this.clearCookie(sessionId);
119
+ }
120
+
121
+ /**
122
+ * Delete all sessions for a user
123
+ * @param userId - User identifier
124
+ * @returns Number of deleted sessions
125
+ */
126
+ async deleteByUserId(userId: string): Promise<number> {
127
+ let deletedCount = 0;
128
+
129
+ for (const [sessionId, data] of this.sessions.entries()) {
130
+ if (data.userId === userId) {
131
+ await this.delete(sessionId);
132
+ deletedCount++;
133
+ }
134
+ }
135
+
136
+ return deletedCount;
137
+ }
138
+
139
+ /**
140
+ * Update session activity timestamp
141
+ * @param sessionId - Session identifier
142
+ */
143
+ async updateActivity(sessionId: string): Promise<void> {
144
+ const data = this.sessions.get(sessionId);
145
+ if (data) {
146
+ data.lastActivityAt = new Date();
147
+ this.sessions.set(sessionId, data);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Check if session exists and is valid
153
+ * @param sessionId - Session identifier
154
+ * @returns True if session exists and is valid
155
+ */
156
+ async exists(sessionId: string): Promise<boolean> {
157
+ const data = await this.get(sessionId);
158
+ return data !== null;
159
+ }
160
+
161
+ /**
162
+ * Get all active sessions for a user
163
+ * @param userId - User identifier
164
+ * @returns Array of session data
165
+ */
166
+ async getByUserId(userId: string): Promise<SessionData[]> {
167
+ const userSessions: SessionData[] = [];
168
+
169
+ for (const data of this.sessions.values()) {
170
+ if (data.userId === userId && data.expiresAt > new Date()) {
171
+ userSessions.push(data);
172
+ }
173
+ }
174
+
175
+ return userSessions;
176
+ }
177
+
178
+ /**
179
+ * Clean up expired sessions
180
+ * @returns Number of cleaned sessions
181
+ */
182
+ async cleanup(): Promise<number> {
183
+ let cleanedCount = 0;
184
+ const now = new Date();
185
+
186
+ for (const [sessionId, data] of this.sessions.entries()) {
187
+ if (data.expiresAt < now) {
188
+ await this.delete(sessionId);
189
+ cleanedCount++;
190
+ }
191
+ }
192
+
193
+ return cleanedCount;
194
+ }
195
+
196
+ /**
197
+ * Get session count for a user
198
+ * @param userId - User identifier
199
+ * @returns Session count
200
+ */
201
+ async getSessionCount(userId: string): Promise<number> {
202
+ const sessions = await this.getByUserId(userId);
203
+ return sessions.length;
204
+ }
205
+
206
+ /**
207
+ * Encrypt session data for cookie storage
208
+ * @param data - Session data to encrypt
209
+ * @returns Encrypted string
210
+ * @private
211
+ */
212
+ private encrypt(data: string): string {
213
+ const cipher = createCipher('aes-256-cbc', this.config.secretKey);
214
+ let encrypted = cipher.update(data, 'utf8', 'hex');
215
+ encrypted += cipher.final('hex');
216
+ return encrypted;
217
+ }
218
+
219
+ /**
220
+ * Decrypt session data from cookie
221
+ * @param encryptedData - Encrypted session data
222
+ * @returns Decrypted string
223
+ * @private
224
+ */
225
+ private decrypt(encryptedData: string): string {
226
+ const decipher = createDecipher('aes-256-cbc', this.config.secretKey);
227
+ let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
228
+ decrypted += decipher.final('utf8');
229
+ return decrypted;
230
+ }
231
+
232
+ /**
233
+ * Set HTTP cookie (mock implementation)
234
+ * @param sessionId - Session identifier
235
+ * @param data - Session data
236
+ * @param ttlSeconds - Time to live in seconds
237
+ * @private
238
+ */
239
+ private setCookie(sessionId: string, data: SessionData, ttlSeconds: number): void {
240
+ // Mock implementation - in real app this would use response.cookie()
241
+ const encryptedData = this.encrypt(JSON.stringify(data));
242
+ globalThis.console.log(`Setting cookie: ${this.config.cookieName}=${encryptedData}; Max-Age=${ttlSeconds}`);
243
+ }
244
+
245
+ /**
246
+ * Clear HTTP cookie (mock implementation)
247
+ * @param sessionId - Session identifier
248
+ * @private
249
+ */
250
+ private clearCookie(sessionId:string): void {
251
+ globalThis.console.log('session_id',sessionId)
252
+ // Mock implementation - in real app this would use response.clearCookie()
253
+
254
+ }
255
+ }