@plyaz/auth 1.0.0 → 1.0.2
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/commits.txt +3 -3
- package/dist/common/index.cjs +3 -1
- package/dist/common/index.cjs.map +1 -1
- package/dist/common/index.mjs +3 -1
- package/dist/common/index.mjs.map +1 -1
- package/dist/index.cjs +424 -154
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +421 -152
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/release_message.txt +28 -0
- package/src/adapters/auth-adapter-factory.ts +4 -3
- package/src/adapters/auth-adapter.mapper.ts +2 -2
- package/src/adapters/base-auth.adapter.ts +17 -9
- package/src/adapters/clerk/clerk.adapter.ts +9 -12
- package/src/adapters/custom/custom.adapter.ts +19 -10
- package/src/adapters/index.ts +0 -1
- package/src/adapters/next-auth/authOptions.ts +20 -16
- package/src/adapters/next-auth/next-auth.adapter.ts +13 -15
- package/src/api/client.ts +4 -6
- package/src/audit/audit.logger.ts +19 -10
- package/src/client/components/ProtectedRoute.tsx +15 -11
- package/src/client/hooks/useAuth.ts +23 -21
- package/src/client/hooks/useConnectedAccounts.ts +57 -45
- package/src/client/hooks/usePermissions.ts +1 -1
- package/src/client/hooks/useRBAC.ts +6 -6
- package/src/client/hooks/useSession.ts +5 -5
- package/src/client/providers/AuthProvider.tsx +23 -17
- package/src/client/store/auth.store.ts +71 -62
- package/src/client/utils/storage.ts +45 -18
- package/src/common/constants/oauth-providers.ts +10 -7
- package/src/common/errors/auth.errors.ts +4 -4
- package/src/common/errors/specific-auth-errors.ts +5 -9
- package/src/common/regex/index.ts +6 -4
- package/src/common/types/auth.types.ts +47 -38
- package/src/common/types/index.ts +12 -6
- package/src/common/utils/index.ts +15 -11
- package/src/core/blacklist/token.blacklist.ts +13 -7
- package/src/core/index.ts +2 -2
- package/src/core/jwt/jwt.manager.ts +47 -22
- package/src/core/session/session.manager.ts +17 -14
- package/src/db/repositories/connected-account.repository.ts +120 -78
- package/src/db/repositories/role.repository.ts +41 -26
- package/src/db/repositories/session.repository.ts +9 -10
- package/src/db/repositories/user.repository.ts +105 -91
- package/src/flows/index.ts +2 -2
- package/src/flows/sign-in.flow.ts +28 -14
- package/src/flows/sign-up.flow.ts +31 -20
- package/src/index.ts +36 -37
- package/src/libs/clerk.helper.ts +6 -7
- package/src/libs/supabase.helper.ts +79 -61
- package/src/libs/supabaseClient.ts +3 -3
- package/src/providers/base/auth-provider.interface.ts +13 -11
- package/src/providers/base/index.ts +1 -1
- package/src/providers/index.ts +1 -1
- package/src/providers/oauth/facebook.provider.ts +63 -39
- package/src/providers/oauth/github.provider.ts +14 -10
- package/src/providers/oauth/google.provider.ts +39 -28
- package/src/providers/oauth/index.ts +1 -1
- package/src/rbac/dynamic-roles.ts +88 -54
- package/src/rbac/index.ts +4 -4
- package/src/rbac/permission-checker.ts +147 -75
- package/src/rbac/role-hierarchy.ts +8 -8
- package/src/rbac/role.manager.ts +11 -8
- package/src/security/csrf/csrf.protection.ts +9 -7
- package/src/security/index.ts +2 -2
- package/src/security/rate-limiting/auth/auth.controller.ts +2 -4
- package/src/security/rate-limiting/auth/rate-limiting.interface.ts +26 -6
- package/src/security/rate-limiting/auth.module.ts +1 -2
- package/src/server/auth.module.ts +55 -52
- package/src/server/decorators/auth.decorator.ts +9 -11
- package/src/server/decorators/auth.decorators.ts +8 -9
- package/src/server/decorators/current-user.decorator.ts +6 -6
- package/src/server/decorators/permission.decorator.ts +17 -9
- package/src/server/guards/auth.guard.ts +21 -16
- package/src/server/guards/custom-throttler.guard.ts +4 -9
- package/src/server/guards/permissions.guard.ts +32 -23
- package/src/server/guards/roles.guard.ts +14 -12
- package/src/server/middleware/auth.middleware.ts +4 -4
- package/src/server/middleware/session.middleware.ts +4 -4
- package/src/server/services/account.service.ts +96 -48
- package/src/server/services/auth.service.ts +57 -28
- package/src/server/services/brute-force.service.ts +24 -19
- package/src/server/services/index.ts +1 -1
- package/src/server/services/rate-limiter.service.ts +9 -4
- package/src/server/services/session.service.ts +84 -48
- package/src/server/services/token.service.ts +71 -51
- package/src/session/cookie-store.ts +47 -34
- package/src/session/enhanced-session-manager.ts +69 -48
- package/src/session/index.ts +5 -5
- package/src/session/memory-store.ts +37 -30
- package/src/session/redis-store.ts +105 -72
- package/src/strategies/oauth.strategy.ts +10 -9
- package/src/strategies/traditional-auth.strategy.ts +41 -29
- package/src/tokens/index.ts +4 -4
- package/src/tokens/refresh-token-manager.ts +70 -55
- package/src/tokens/token-validator.ts +109 -53
- package/vitest.setup.d.ts +2 -2
- package/vitest.setup.ts +1 -1
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import { randomBytes, pbkdf2Sync } from
|
|
2
|
-
import type { UserRepository } from
|
|
3
|
-
import type { SessionManager } from
|
|
4
|
-
import type { AuthTokens, AuthUser, Session } from
|
|
5
|
-
import { AuthenticationError } from
|
|
6
|
-
import { NUMERIX } from
|
|
7
|
-
import type { JwtManager } from
|
|
1
|
+
import { randomBytes, pbkdf2Sync } from "crypto";
|
|
2
|
+
import type { UserRepository } from "../db/repositories/user.repository";
|
|
3
|
+
import type { SessionManager } from "../core/session/session.manager";
|
|
4
|
+
import type { AuthTokens, AuthUser, Session } from "@plyaz/types";
|
|
5
|
+
import { AuthenticationError } from "@plyaz/errors";
|
|
6
|
+
import { NUMERIX } from "@plyaz/config";
|
|
7
|
+
import type { JwtManager } from "@/core/jwt/jwt.manager";
|
|
8
8
|
|
|
9
9
|
export class TraditionalAuthStrategy {
|
|
10
10
|
constructor(
|
|
11
11
|
private userRepo: UserRepository,
|
|
12
12
|
private jwtManager: JwtManager,
|
|
13
|
-
private sessionManager: SessionManager
|
|
13
|
+
private sessionManager: SessionManager,
|
|
14
14
|
) {}
|
|
15
15
|
|
|
16
16
|
async authenticate(email: string, password: string): Promise<AuthUser> {
|
|
17
17
|
const user = await this.userRepo.findByEmail(email);
|
|
18
18
|
if (!user?.passwordHash) {
|
|
19
|
-
throw new AuthenticationError(
|
|
19
|
+
throw new AuthenticationError("AUTH_INVALID_CREDENTIALS");
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const isValid = await this.verifyPassword(password, user.passwordHash);
|
|
23
23
|
if (!isValid) {
|
|
24
|
-
throw new AuthenticationError(
|
|
24
|
+
throw new AuthenticationError("AUTH_INVALID_CREDENTIALS");
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if (!user.isActive) {
|
|
28
|
-
throw new AuthenticationError(
|
|
28
|
+
throw new AuthenticationError("AUTH_ACCOUNT_LOCKED");
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
if (user.isSuspended) {
|
|
32
|
-
throw new AuthenticationError(
|
|
32
|
+
throw new AuthenticationError("AUTH_ACCOUNT_SUSPENDED");
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
return user;
|
|
@@ -38,16 +38,28 @@ export class TraditionalAuthStrategy {
|
|
|
38
38
|
async hashPassword(password: string): Promise<string> {
|
|
39
39
|
const SALT_LENGTH = 32;
|
|
40
40
|
const HASH_LENGTH = 64;
|
|
41
|
-
const salt = randomBytes(SALT_LENGTH).toString(
|
|
42
|
-
const hash = pbkdf2Sync(
|
|
41
|
+
const salt = randomBytes(SALT_LENGTH).toString("hex");
|
|
42
|
+
const hash = pbkdf2Sync(
|
|
43
|
+
password,
|
|
44
|
+
salt,
|
|
45
|
+
NUMERIX.THOUSAND,
|
|
46
|
+
HASH_LENGTH,
|
|
47
|
+
"sha512",
|
|
48
|
+
).toString("hex");
|
|
43
49
|
return `pbkdf2$${salt}$${hash}`;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
async verifyPassword(password: string, hash: string): Promise<boolean> {
|
|
47
53
|
const HASH_LENGTH = 64;
|
|
48
|
-
if (hash.startsWith(
|
|
49
|
-
const [, salt, storedHash] = hash.split(
|
|
50
|
-
const computedHash = pbkdf2Sync(
|
|
54
|
+
if (hash.startsWith("pbkdf2$")) {
|
|
55
|
+
const [, salt, storedHash] = hash.split("$");
|
|
56
|
+
const computedHash = pbkdf2Sync(
|
|
57
|
+
password,
|
|
58
|
+
salt,
|
|
59
|
+
NUMERIX.THOUSAND,
|
|
60
|
+
HASH_LENGTH,
|
|
61
|
+
"sha512",
|
|
62
|
+
).toString("hex");
|
|
51
63
|
return computedHash === storedHash;
|
|
52
64
|
}
|
|
53
65
|
return false;
|
|
@@ -63,7 +75,7 @@ export class TraditionalAuthStrategy {
|
|
|
63
75
|
// Check if user already exists
|
|
64
76
|
const existingUser = await this.userRepo.findByEmail(data.email);
|
|
65
77
|
if (existingUser) {
|
|
66
|
-
throw new AuthenticationError(
|
|
78
|
+
throw new AuthenticationError("AUTH_INVALID_CREDENTIALS");
|
|
67
79
|
}
|
|
68
80
|
|
|
69
81
|
// Hash password
|
|
@@ -76,21 +88,21 @@ export class TraditionalAuthStrategy {
|
|
|
76
88
|
firstName: data.firstName,
|
|
77
89
|
lastName: data.lastName,
|
|
78
90
|
passwordHash,
|
|
79
|
-
authProvider:
|
|
91
|
+
authProvider: "EMAIL",
|
|
80
92
|
isActive: true,
|
|
81
|
-
isVerified: false
|
|
82
|
-
}
|
|
93
|
+
isVerified: false,
|
|
94
|
+
});
|
|
83
95
|
|
|
84
96
|
// Create session
|
|
85
97
|
const session = await this.sessionManager.createSession(user.id, {
|
|
86
|
-
provider:
|
|
87
|
-
userAgent:
|
|
88
|
-
ipAddress:
|
|
98
|
+
provider: "EMAIL",
|
|
99
|
+
userAgent: "test",
|
|
100
|
+
ipAddress: "127.0.0.1",
|
|
89
101
|
});
|
|
90
102
|
|
|
91
103
|
// Generate tokens
|
|
92
104
|
const tokens = this.jwtManager.generateTokens(user);
|
|
93
|
-
|
|
105
|
+
|
|
94
106
|
return { user, session, tokens };
|
|
95
107
|
}
|
|
96
108
|
|
|
@@ -103,9 +115,9 @@ export class TraditionalAuthStrategy {
|
|
|
103
115
|
|
|
104
116
|
// Create session
|
|
105
117
|
const session = await this.sessionManager.createSession(user.id, {
|
|
106
|
-
provider:
|
|
107
|
-
userAgent:
|
|
108
|
-
ipAddress:
|
|
118
|
+
provider: "EMAIL",
|
|
119
|
+
userAgent: "test",
|
|
120
|
+
ipAddress: "127.0.0.1",
|
|
109
121
|
});
|
|
110
122
|
|
|
111
123
|
// Generate tokens
|
|
@@ -113,4 +125,4 @@ export class TraditionalAuthStrategy {
|
|
|
113
125
|
|
|
114
126
|
return { user, session, tokens };
|
|
115
127
|
}
|
|
116
|
-
}
|
|
128
|
+
}
|
package/src/tokens/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
1
|
+
export * from "./token-validator";
|
|
2
|
+
export * from "./refresh-token-manager";
|
|
3
|
+
export * from "../core/jwt/jwt.manager";
|
|
4
|
+
export * from "../core/blacklist/token.blacklist";
|
|
@@ -3,31 +3,30 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @fileoverview Refresh token manager for @plyaz/auth
|
|
5
5
|
* @module @plyaz/auth/tokens/refresh-token-manager
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* @description
|
|
8
8
|
* Manages refresh token lifecycle including generation, validation, rotation,
|
|
9
9
|
* and revocation. Implements security best practices like token rotation
|
|
10
10
|
* and family tracking to prevent token theft and replay attacks.
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
12
|
* @example
|
|
13
13
|
* ```typescript
|
|
14
14
|
* import { RefreshTokenManager } from '@plyaz/auth';
|
|
15
|
-
*
|
|
15
|
+
*
|
|
16
16
|
* const manager = new RefreshTokenManager({
|
|
17
17
|
* secretKey: 'your-secret-key',
|
|
18
18
|
* tokenTTL: 604800, // 7 days
|
|
19
19
|
* enableRotation: true
|
|
20
20
|
* });
|
|
21
|
-
*
|
|
21
|
+
*
|
|
22
22
|
* const tokens = await manager.generateTokenPair(userId, sessionId);
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { sign, verify } from
|
|
27
|
-
import { randomBytes } from
|
|
28
|
-
import { TokenBlacklist } from
|
|
29
|
-
import { AuthenticationError } from
|
|
30
|
-
|
|
26
|
+
import { sign, verify } from "jsonwebtoken";
|
|
27
|
+
import { randomBytes } from "crypto";
|
|
28
|
+
import { TokenBlacklist } from "../core/blacklist/token.blacklist";
|
|
29
|
+
import { AuthenticationError } from "@plyaz/errors";
|
|
31
30
|
|
|
32
31
|
/**
|
|
33
32
|
* Refresh token manager configuration
|
|
@@ -78,7 +77,7 @@ export interface RefreshTokenPayload {
|
|
|
78
77
|
/** Token generation number */
|
|
79
78
|
generation?: number;
|
|
80
79
|
/** Token type */
|
|
81
|
-
type:
|
|
80
|
+
type: "refresh";
|
|
82
81
|
/** Issued at */
|
|
83
82
|
iat: number;
|
|
84
83
|
/** Expires at */
|
|
@@ -102,7 +101,7 @@ export interface AccessTokenPayload {
|
|
|
102
101
|
/** User permissions */
|
|
103
102
|
permissions?: string[];
|
|
104
103
|
/** Token type */
|
|
105
|
-
type:
|
|
104
|
+
type: "access";
|
|
106
105
|
/** Issued at */
|
|
107
106
|
iat: number;
|
|
108
107
|
/** Expires at */
|
|
@@ -142,7 +141,10 @@ export class RefreshTokenManager {
|
|
|
142
141
|
|
|
143
142
|
constructor(config: RefreshTokenManagerConfig) {
|
|
144
143
|
this.config = config;
|
|
145
|
-
this.blacklist = new TokenBlacklist({
|
|
144
|
+
this.blacklist = new TokenBlacklist({
|
|
145
|
+
keyPrefix: "token:",
|
|
146
|
+
defaultTTL: 3600,
|
|
147
|
+
});
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
/**
|
|
@@ -157,7 +159,7 @@ export class RefreshTokenManager {
|
|
|
157
159
|
userId: string,
|
|
158
160
|
sessionId: string,
|
|
159
161
|
userRoles?: string[],
|
|
160
|
-
userPermissions?: string[]
|
|
162
|
+
userPermissions?: string[],
|
|
161
163
|
): Promise<TokenPair> {
|
|
162
164
|
const now = Math.floor(Date.now() / 1000);
|
|
163
165
|
const accessTokenExp = now + this.config.accessTokenTTL;
|
|
@@ -175,7 +177,7 @@ export class RefreshTokenManager {
|
|
|
175
177
|
sessionId,
|
|
176
178
|
generation,
|
|
177
179
|
createdAt: new Date(),
|
|
178
|
-
lastUsedAt: new Date()
|
|
180
|
+
lastUsedAt: new Date(),
|
|
179
181
|
});
|
|
180
182
|
}
|
|
181
183
|
|
|
@@ -185,11 +187,11 @@ export class RefreshTokenManager {
|
|
|
185
187
|
sessionId,
|
|
186
188
|
roles: userRoles,
|
|
187
189
|
permissions: userPermissions,
|
|
188
|
-
type:
|
|
190
|
+
type: "access",
|
|
189
191
|
iat: now,
|
|
190
192
|
exp: accessTokenExp,
|
|
191
193
|
iss: this.config.issuer,
|
|
192
|
-
aud: this.config.audience
|
|
194
|
+
aud: this.config.audience,
|
|
193
195
|
};
|
|
194
196
|
|
|
195
197
|
// Create refresh token payload
|
|
@@ -198,22 +200,26 @@ export class RefreshTokenManager {
|
|
|
198
200
|
sessionId,
|
|
199
201
|
family,
|
|
200
202
|
generation,
|
|
201
|
-
type:
|
|
203
|
+
type: "refresh",
|
|
202
204
|
iat: now,
|
|
203
205
|
exp: refreshTokenExp,
|
|
204
206
|
iss: this.config.issuer,
|
|
205
|
-
aud: this.config.audience
|
|
207
|
+
aud: this.config.audience,
|
|
206
208
|
};
|
|
207
209
|
|
|
208
210
|
// Sign tokens
|
|
209
|
-
const accessToken = sign(accessPayload, this.config.secretKey, {
|
|
210
|
-
|
|
211
|
+
const accessToken = sign(accessPayload, this.config.secretKey, {
|
|
212
|
+
algorithm: "HS256",
|
|
213
|
+
});
|
|
214
|
+
const refreshToken = sign(refreshPayload, this.config.secretKey, {
|
|
215
|
+
algorithm: "HS256",
|
|
216
|
+
});
|
|
211
217
|
|
|
212
218
|
return {
|
|
213
219
|
accessToken,
|
|
214
220
|
refreshToken,
|
|
215
221
|
accessTokenExpiresAt: new Date(accessTokenExp * 1000),
|
|
216
|
-
refreshTokenExpiresAt: new Date(refreshTokenExp * 1000)
|
|
222
|
+
refreshTokenExpiresAt: new Date(refreshTokenExp * 1000),
|
|
217
223
|
};
|
|
218
224
|
}
|
|
219
225
|
|
|
@@ -227,7 +233,7 @@ export class RefreshTokenManager {
|
|
|
227
233
|
async refreshTokenPair(
|
|
228
234
|
refreshToken: string,
|
|
229
235
|
userRoles?: string[],
|
|
230
|
-
userPermissions?: string[]
|
|
236
|
+
userPermissions?: string[],
|
|
231
237
|
): Promise<TokenPair> {
|
|
232
238
|
// Validate refresh token
|
|
233
239
|
const payload = await this.validateRefreshToken(refreshToken);
|
|
@@ -247,7 +253,7 @@ export class RefreshTokenManager {
|
|
|
247
253
|
payload.sub,
|
|
248
254
|
payload.sessionId,
|
|
249
255
|
userRoles,
|
|
250
|
-
userPermissions
|
|
256
|
+
userPermissions,
|
|
251
257
|
);
|
|
252
258
|
|
|
253
259
|
// Update token family
|
|
@@ -256,11 +262,11 @@ export class RefreshTokenManager {
|
|
|
256
262
|
if (family) {
|
|
257
263
|
family.generation++;
|
|
258
264
|
family.lastUsedAt = new Date();
|
|
259
|
-
|
|
265
|
+
|
|
260
266
|
// Enforce family size limit
|
|
261
267
|
if (family.generation > this.config.maxFamilySize) {
|
|
262
268
|
await this.revokeTokenFamily(payload.family);
|
|
263
|
-
throw new AuthenticationError(
|
|
269
|
+
throw new AuthenticationError("AUTH_TOKEN_INVALID");
|
|
264
270
|
}
|
|
265
271
|
}
|
|
266
272
|
}
|
|
@@ -273,41 +279,41 @@ export class RefreshTokenManager {
|
|
|
273
279
|
* @param refreshToken - Refresh token to validate
|
|
274
280
|
* @returns Decoded payload
|
|
275
281
|
*/
|
|
276
|
-
async validateRefreshToken(
|
|
282
|
+
async validateRefreshToken(
|
|
283
|
+
refreshToken: string,
|
|
284
|
+
): Promise<RefreshTokenPayload> {
|
|
277
285
|
try {
|
|
278
286
|
// Check if token is blacklisted
|
|
279
287
|
if (await this.blacklist.isBlacklisted(refreshToken)) {
|
|
280
|
-
throw new AuthenticationError(
|
|
288
|
+
throw new AuthenticationError("AUTH_TOKEN_REVOKED");
|
|
281
289
|
}
|
|
282
290
|
|
|
283
291
|
// Verify token
|
|
284
292
|
const payload = verify(refreshToken, this.config.secretKey, {
|
|
285
293
|
issuer: this.config.issuer,
|
|
286
294
|
audience: this.config.audience,
|
|
287
|
-
algorithms: [
|
|
295
|
+
algorithms: ["HS256"],
|
|
288
296
|
}) as RefreshTokenPayload;
|
|
289
297
|
|
|
290
298
|
// Validate token type
|
|
291
|
-
if (payload.type !==
|
|
292
|
-
throw new AuthenticationError(
|
|
299
|
+
if (payload.type !== "refresh") {
|
|
300
|
+
throw new AuthenticationError("AUTH_TOKEN_INVALID");
|
|
293
301
|
}
|
|
294
302
|
|
|
295
303
|
return payload;
|
|
296
|
-
|
|
297
304
|
} catch (error) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (error.name === 'JsonWebTokenError') {
|
|
304
|
-
throw new AuthenticationError('AUTH_TOKEN_INVALID');
|
|
305
|
-
}
|
|
306
|
-
}
|
|
305
|
+
if (error instanceof Error) {
|
|
306
|
+
if (error.name === "TokenExpiredError") {
|
|
307
|
+
throw new AuthenticationError("AUTH_TOKEN_EXPIRED");
|
|
308
|
+
}
|
|
307
309
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
+
if (error.name === "JsonWebTokenError") {
|
|
311
|
+
throw new AuthenticationError("AUTH_TOKEN_INVALID");
|
|
312
|
+
}
|
|
313
|
+
}
|
|
310
314
|
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
311
317
|
}
|
|
312
318
|
|
|
313
319
|
/**
|
|
@@ -317,7 +323,7 @@ export class RefreshTokenManager {
|
|
|
317
323
|
async revokeRefreshToken(refreshToken: string): Promise<void> {
|
|
318
324
|
try {
|
|
319
325
|
const payload = await this.validateRefreshToken(refreshToken);
|
|
320
|
-
|
|
326
|
+
|
|
321
327
|
// Add to blacklist
|
|
322
328
|
await this.blacklist.add(refreshToken, payload.exp);
|
|
323
329
|
|
|
@@ -325,10 +331,12 @@ export class RefreshTokenManager {
|
|
|
325
331
|
if (this.config.enableFamilyTracking && payload.family) {
|
|
326
332
|
await this.revokeTokenFamily(payload.family);
|
|
327
333
|
}
|
|
328
|
-
|
|
329
334
|
} catch (error) {
|
|
330
335
|
// Token might already be invalid, but still try to blacklist
|
|
331
|
-
await this.blacklist.add(
|
|
336
|
+
await this.blacklist.add(
|
|
337
|
+
refreshToken,
|
|
338
|
+
Math.floor(Date.now() / 1000) + 3600,
|
|
339
|
+
);
|
|
332
340
|
}
|
|
333
341
|
}
|
|
334
342
|
|
|
@@ -378,7 +386,9 @@ export class RefreshTokenManager {
|
|
|
378
386
|
|
|
379
387
|
for (const [familyId, family] of this.tokenFamilies.entries()) {
|
|
380
388
|
// Consider family expired if not used for longer than token TTL
|
|
381
|
-
const expiredTime = new Date(
|
|
389
|
+
const expiredTime = new Date(
|
|
390
|
+
family.lastUsedAt.getTime() + this.config.tokenTTL * 1000,
|
|
391
|
+
);
|
|
382
392
|
if (expiredTime < now) {
|
|
383
393
|
expiredFamilies.push(familyId);
|
|
384
394
|
}
|
|
@@ -397,28 +407,33 @@ export class RefreshTokenManager {
|
|
|
397
407
|
* @param payload - Refresh token payload
|
|
398
408
|
* @private
|
|
399
409
|
*/
|
|
400
|
-
private async validateTokenFamily(
|
|
410
|
+
private async validateTokenFamily(
|
|
411
|
+
payload: RefreshTokenPayload,
|
|
412
|
+
): Promise<void> {
|
|
401
413
|
if (!payload.family) {
|
|
402
414
|
return;
|
|
403
415
|
}
|
|
404
416
|
|
|
405
417
|
const family = this.tokenFamilies.get(payload.family);
|
|
406
|
-
|
|
418
|
+
|
|
407
419
|
if (!family) {
|
|
408
|
-
throw new AuthenticationError(
|
|
420
|
+
throw new AuthenticationError("AUTH_TOKEN_INVALID");
|
|
409
421
|
}
|
|
410
422
|
|
|
411
423
|
// Check if token generation is valid
|
|
412
424
|
if (payload.generation !== family.generation) {
|
|
413
425
|
// Potential token theft - revoke entire family
|
|
414
426
|
await this.revokeTokenFamily(payload.family);
|
|
415
|
-
throw new AuthenticationError(
|
|
427
|
+
throw new AuthenticationError("AUTH_TOKEN_REVOKED");
|
|
416
428
|
}
|
|
417
429
|
|
|
418
430
|
// Check if user and session match
|
|
419
|
-
if (
|
|
431
|
+
if (
|
|
432
|
+
family.userId !== payload.sub ||
|
|
433
|
+
family.sessionId !== payload.sessionId
|
|
434
|
+
) {
|
|
420
435
|
await this.revokeTokenFamily(payload.family);
|
|
421
|
-
throw new AuthenticationError(
|
|
436
|
+
throw new AuthenticationError("AUTH_TOKEN_REVOKED");
|
|
422
437
|
}
|
|
423
438
|
}
|
|
424
439
|
|
|
@@ -429,7 +444,7 @@ export class RefreshTokenManager {
|
|
|
429
444
|
*/
|
|
430
445
|
private async revokeTokenFamily(familyId: string): Promise<void> {
|
|
431
446
|
const family = this.tokenFamilies.get(familyId);
|
|
432
|
-
|
|
447
|
+
|
|
433
448
|
if (family) {
|
|
434
449
|
// In a real implementation, this would blacklist all tokens in the family
|
|
435
450
|
// For now, we just remove the family tracking
|
|
@@ -443,6 +458,6 @@ export class RefreshTokenManager {
|
|
|
443
458
|
* @private
|
|
444
459
|
*/
|
|
445
460
|
private generateFamilyId(): string {
|
|
446
|
-
return randomBytes(16).toString(
|
|
461
|
+
return randomBytes(16).toString("hex");
|
|
447
462
|
}
|
|
448
|
-
}
|
|
463
|
+
}
|