@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,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
|
+
}
|