@veloxts/auth 0.6.68 → 0.6.70

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.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Enhanced Token Store for JWT Revocation
3
+ *
4
+ * Provides an enhanced in-memory token store with:
5
+ * - Token revocation with expiry
6
+ * - Refresh token reuse detection
7
+ * - Automatic cleanup of expired entries
8
+ *
9
+ * @module auth/token-store
10
+ */
11
+ /**
12
+ * Enhanced token store interface
13
+ *
14
+ * Extends basic token revocation with refresh token reuse detection
15
+ * and automatic cleanup capabilities.
16
+ */
17
+ export interface EnhancedTokenStore {
18
+ /** Revoke a token with optional expiry time */
19
+ revoke(jti: string, expiresInMs?: number): void;
20
+ /** Check if token is revoked */
21
+ isRevoked(jti: string): boolean;
22
+ /** Mark refresh token as used (for reuse detection) */
23
+ markRefreshTokenUsed(jti: string, userId: string): void;
24
+ /** Check if refresh token was already used, returns userId if reused */
25
+ isRefreshTokenUsed(jti: string): string | undefined;
26
+ /** Revoke all tokens for a user (placeholder for production implementation) */
27
+ revokeAllUserTokens(userId: string): void;
28
+ /** Clear all entries (useful for testing) */
29
+ clear(): void;
30
+ /** Stop cleanup interval and release resources */
31
+ destroy(): void;
32
+ }
33
+ /**
34
+ * Options for creating an enhanced token store
35
+ */
36
+ export interface EnhancedTokenStoreOptions {
37
+ /** Interval for cleanup of expired entries (default: 5 minutes) */
38
+ cleanupIntervalMs?: number;
39
+ /** Default expiry for revoked tokens (default: 7 days) */
40
+ defaultExpiryMs?: number;
41
+ }
42
+ /**
43
+ * Creates an enhanced in-memory token store
44
+ *
45
+ * Provides token revocation with expiry tracking and refresh token
46
+ * reuse detection for security.
47
+ *
48
+ * **WARNING: NOT suitable for production!**
49
+ * Use Redis or database-backed store for:
50
+ * - Persistence across server restarts
51
+ * - Horizontal scaling (multiple server instances)
52
+ * - Proper token revocation across deployments
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * import { createEnhancedTokenStore } from '@veloxts/auth';
57
+ *
58
+ * // Create store with defaults
59
+ * const tokenStore = createEnhancedTokenStore();
60
+ *
61
+ * // Revoke token on logout
62
+ * tokenStore.revoke(accessTokenJti);
63
+ *
64
+ * // Detect refresh token reuse (security measure)
65
+ * const previousUser = tokenStore.isRefreshTokenUsed(refreshJti);
66
+ * if (previousUser) {
67
+ * // Potential token theft - revoke all user tokens
68
+ * tokenStore.revokeAllUserTokens(previousUser);
69
+ * throw new SecurityError('Token reuse detected');
70
+ * }
71
+ * tokenStore.markRefreshTokenUsed(refreshJti, userId);
72
+ *
73
+ * // Clean up on shutdown
74
+ * process.on('SIGTERM', () => tokenStore.destroy());
75
+ * ```
76
+ */
77
+ export declare function createEnhancedTokenStore(options?: EnhancedTokenStoreOptions): EnhancedTokenStore;
78
+ /**
79
+ * Default allowed roles for role parsing
80
+ */
81
+ export declare const DEFAULT_ALLOWED_ROLES: readonly ["user", "admin", "moderator", "editor"];
82
+ /**
83
+ * Parses JSON-encoded roles string to array
84
+ *
85
+ * Safely parses a JSON array of role strings, filtering to only allowed roles.
86
+ * Returns default ['user'] role if parsing fails or no valid roles found.
87
+ *
88
+ * @param rolesJson - JSON string of roles (e.g., '["admin", "user"]')
89
+ * @param allowedRoles - Optional list of valid roles (defaults to DEFAULT_ALLOWED_ROLES)
90
+ * @returns Array of valid roles, defaults to ['user'] if parsing fails
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * import { parseUserRoles } from '@veloxts/auth';
95
+ *
96
+ * const roles = parseUserRoles(user.roles);
97
+ * // Input: '["admin", "user"]' -> Output: ['admin', 'user']
98
+ * // Input: null -> Output: ['user']
99
+ * // Input: 'invalid' -> Output: ['user']
100
+ *
101
+ * // With custom allowed roles
102
+ * const roles = parseUserRoles(user.roles, ['admin', 'superadmin']);
103
+ * ```
104
+ */
105
+ export declare function parseUserRoles(rolesJson: string | null, allowedRoles?: readonly string[]): string[];
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Enhanced Token Store for JWT Revocation
3
+ *
4
+ * Provides an enhanced in-memory token store with:
5
+ * - Token revocation with expiry
6
+ * - Refresh token reuse detection
7
+ * - Automatic cleanup of expired entries
8
+ *
9
+ * @module auth/token-store
10
+ */
11
+ // ============================================================================
12
+ // Implementation
13
+ // ============================================================================
14
+ /**
15
+ * Creates an enhanced in-memory token store
16
+ *
17
+ * Provides token revocation with expiry tracking and refresh token
18
+ * reuse detection for security.
19
+ *
20
+ * **WARNING: NOT suitable for production!**
21
+ * Use Redis or database-backed store for:
22
+ * - Persistence across server restarts
23
+ * - Horizontal scaling (multiple server instances)
24
+ * - Proper token revocation across deployments
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { createEnhancedTokenStore } from '@veloxts/auth';
29
+ *
30
+ * // Create store with defaults
31
+ * const tokenStore = createEnhancedTokenStore();
32
+ *
33
+ * // Revoke token on logout
34
+ * tokenStore.revoke(accessTokenJti);
35
+ *
36
+ * // Detect refresh token reuse (security measure)
37
+ * const previousUser = tokenStore.isRefreshTokenUsed(refreshJti);
38
+ * if (previousUser) {
39
+ * // Potential token theft - revoke all user tokens
40
+ * tokenStore.revokeAllUserTokens(previousUser);
41
+ * throw new SecurityError('Token reuse detected');
42
+ * }
43
+ * tokenStore.markRefreshTokenUsed(refreshJti, userId);
44
+ *
45
+ * // Clean up on shutdown
46
+ * process.on('SIGTERM', () => tokenStore.destroy());
47
+ * ```
48
+ */
49
+ export function createEnhancedTokenStore(options) {
50
+ const { cleanupIntervalMs = 5 * 60 * 1000, defaultExpiryMs = 7 * 24 * 60 * 60 * 1000 } = options ?? {};
51
+ const revokedTokens = new Map();
52
+ const usedRefreshTokens = new Map();
53
+ // Track pending timeouts to prevent memory leaks on destroy()
54
+ const pendingTimeouts = new Set();
55
+ const cleanup = () => {
56
+ const now = Date.now();
57
+ for (const [jti, expiry] of revokedTokens.entries()) {
58
+ if (now > expiry) {
59
+ revokedTokens.delete(jti);
60
+ }
61
+ }
62
+ };
63
+ const cleanupInterval = setInterval(cleanup, cleanupIntervalMs);
64
+ return {
65
+ revoke(jti, expiresInMs = defaultExpiryMs) {
66
+ revokedTokens.set(jti, Date.now() + expiresInMs);
67
+ },
68
+ isRevoked(jti) {
69
+ const expiry = revokedTokens.get(jti);
70
+ if (!expiry)
71
+ return false;
72
+ if (Date.now() > expiry) {
73
+ revokedTokens.delete(jti);
74
+ return false;
75
+ }
76
+ return true;
77
+ },
78
+ markRefreshTokenUsed(jti, userId) {
79
+ usedRefreshTokens.set(jti, userId);
80
+ // Auto-expire after default expiry, track timeout for cleanup
81
+ const timeout = setTimeout(() => {
82
+ usedRefreshTokens.delete(jti);
83
+ pendingTimeouts.delete(timeout);
84
+ }, defaultExpiryMs);
85
+ pendingTimeouts.add(timeout);
86
+ },
87
+ isRefreshTokenUsed(jti) {
88
+ return usedRefreshTokens.get(jti);
89
+ },
90
+ revokeAllUserTokens(userId) {
91
+ // Placeholder - in production, implement proper user->token mapping
92
+ console.warn(`[Security] Token reuse detected for user ${userId}. ` +
93
+ 'All tokens should be revoked. Implement proper user->token mapping for production.');
94
+ },
95
+ clear() {
96
+ // Clear pending timeouts since we're clearing the tokens they reference
97
+ for (const timeout of pendingTimeouts) {
98
+ clearTimeout(timeout);
99
+ }
100
+ pendingTimeouts.clear();
101
+ revokedTokens.clear();
102
+ usedRefreshTokens.clear();
103
+ },
104
+ destroy() {
105
+ clearInterval(cleanupInterval);
106
+ // Clear all pending timeouts to prevent memory leaks
107
+ for (const timeout of pendingTimeouts) {
108
+ clearTimeout(timeout);
109
+ }
110
+ pendingTimeouts.clear();
111
+ revokedTokens.clear();
112
+ usedRefreshTokens.clear();
113
+ },
114
+ };
115
+ }
116
+ /**
117
+ * Default allowed roles for role parsing
118
+ */
119
+ export const DEFAULT_ALLOWED_ROLES = ['user', 'admin', 'moderator', 'editor'];
120
+ /**
121
+ * Parses JSON-encoded roles string to array
122
+ *
123
+ * Safely parses a JSON array of role strings, filtering to only allowed roles.
124
+ * Returns default ['user'] role if parsing fails or no valid roles found.
125
+ *
126
+ * @param rolesJson - JSON string of roles (e.g., '["admin", "user"]')
127
+ * @param allowedRoles - Optional list of valid roles (defaults to DEFAULT_ALLOWED_ROLES)
128
+ * @returns Array of valid roles, defaults to ['user'] if parsing fails
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * import { parseUserRoles } from '@veloxts/auth';
133
+ *
134
+ * const roles = parseUserRoles(user.roles);
135
+ * // Input: '["admin", "user"]' -> Output: ['admin', 'user']
136
+ * // Input: null -> Output: ['user']
137
+ * // Input: 'invalid' -> Output: ['user']
138
+ *
139
+ * // With custom allowed roles
140
+ * const roles = parseUserRoles(user.roles, ['admin', 'superadmin']);
141
+ * ```
142
+ */
143
+ export function parseUserRoles(rolesJson, allowedRoles = DEFAULT_ALLOWED_ROLES) {
144
+ if (!rolesJson)
145
+ return ['user'];
146
+ try {
147
+ const parsed = JSON.parse(rolesJson);
148
+ if (!Array.isArray(parsed)) {
149
+ return ['user'];
150
+ }
151
+ const validRoles = parsed
152
+ .filter((role) => typeof role === 'string')
153
+ .filter((role) => allowedRoles.includes(role));
154
+ return validRoles.length > 0 ? validRoles : ['user'];
155
+ }
156
+ catch {
157
+ return ['user'];
158
+ }
159
+ }
package/dist/types.d.ts CHANGED
@@ -24,25 +24,36 @@ export declare class AuthError extends Error {
24
24
  /**
25
25
  * Base user interface for authenticated requests
26
26
  *
27
- * Applications should extend this via declaration merging:
27
+ * Applications should extend this via declaration merging to add
28
+ * custom properties without using index signatures:
29
+ *
28
30
  * @example
29
31
  * ```typescript
30
32
  * declare module '@veloxts/auth' {
31
33
  * interface User {
32
- * roles: ('admin' | 'user')[];
33
- * permissions: string[];
34
+ * name?: string;
35
+ * avatarUrl?: string;
36
+ * tenantId?: string;
34
37
  * }
35
38
  * }
36
39
  * ```
40
+ *
41
+ * This approach provides:
42
+ * - Full type safety (no implicit `unknown` properties)
43
+ * - Better IDE autocomplete
44
+ * - Compile-time errors for typos
37
45
  */
38
46
  export interface User {
47
+ /** Unique user identifier */
39
48
  id: string;
49
+ /** User email address */
40
50
  email: string;
51
+ /** Whether the user's email has been verified */
52
+ emailVerified?: boolean;
41
53
  /** User roles for authorization */
42
54
  roles?: string[];
43
55
  /** User permissions for fine-grained access control */
44
56
  permissions?: string[];
45
- [key: string]: unknown;
46
57
  }
47
58
  /**
48
59
  * Payload stored in JWT tokens
@@ -120,25 +131,6 @@ export interface HashConfig {
120
131
  /** argon2 parallelism (default: 4) */
121
132
  argon2Parallelism?: number;
122
133
  }
123
- /**
124
- * Legacy session cookie configuration (used by AuthConfig)
125
- *
126
- * @deprecated Use SessionConfig from session.ts for full session management
127
- */
128
- export interface LegacySessionConfig {
129
- /** Cookie name (default: 'velox.session') */
130
- cookieName?: string;
131
- /** Session expiration in seconds (default: 86400 = 24h) */
132
- maxAge?: number;
133
- /** Cookie path (default: '/') */
134
- path?: string;
135
- /** HTTP only flag (default: true) */
136
- httpOnly?: boolean;
137
- /** Secure flag - use HTTPS only (default: true in production) */
138
- secure?: boolean;
139
- /** SameSite policy (default: 'lax') */
140
- sameSite?: 'strict' | 'lax' | 'none';
141
- }
142
134
  /**
143
135
  * Rate limiting configuration
144
136
  */
@@ -162,11 +154,6 @@ export interface AuthConfig {
162
154
  jwt: JwtConfig;
163
155
  /** Password hashing configuration */
164
156
  hash?: HashConfig;
165
- /**
166
- * Legacy session cookie configuration
167
- * @deprecated Use createSessionMiddleware from session.ts for full session management
168
- */
169
- session?: LegacySessionConfig;
170
157
  /** Rate limiting configuration */
171
158
  rateLimit?: RateLimitConfig;
172
159
  /**
@@ -177,6 +164,7 @@ export interface AuthConfig {
177
164
  /**
178
165
  * Token blacklist checker - check if token is revoked
179
166
  * Called on every authenticated request
167
+ * @deprecated Use `tokenStore` with TokenStore interface instead
180
168
  */
181
169
  isTokenRevoked?: (tokenId: string) => Promise<boolean>;
182
170
  /**
@@ -234,16 +222,65 @@ export interface AuthMiddlewareOptions {
234
222
  guards?: Array<GuardDefinition | string>;
235
223
  }
236
224
  /**
237
- * Authenticated request context extension
225
+ * Base auth context shared by all auth modes
226
+ */
227
+ export interface BaseAuthContext {
228
+ /** Whether the request is authenticated */
229
+ isAuthenticated: boolean;
230
+ }
231
+ /**
232
+ * Auth context for native JWT authentication (authPlugin)
233
+ *
234
+ * This context is set when using the built-in authPlugin with JWT tokens.
235
+ * Provides access to the decoded token payload.
238
236
  */
239
- export interface AuthContext {
237
+ export interface NativeAuthContext extends BaseAuthContext {
238
+ /** Discriminant for native JWT auth mode */
239
+ authMode: 'native';
240
240
  /** Authenticated user (undefined if optional auth and no token) */
241
241
  user?: User;
242
+ /** Raw JWT token string (if extracted from request) */
243
+ token?: string;
242
244
  /** Decoded token payload */
243
- token?: TokenPayload;
244
- /** Check if user is authenticated */
245
- isAuthenticated: boolean;
245
+ payload?: TokenPayload;
246
246
  }
247
+ /**
248
+ * Auth context for external authentication adapters
249
+ *
250
+ * This context is set when using an AuthAdapter (BetterAuth, Clerk, Auth0, etc.).
251
+ * Provides access to the provider's session data.
252
+ */
253
+ export interface AdapterAuthContext extends BaseAuthContext {
254
+ /** Discriminant for adapter auth mode */
255
+ authMode: 'adapter';
256
+ /** Authenticated user (undefined if not authenticated) */
257
+ user?: User;
258
+ /** Provider identifier (e.g., 'better-auth', 'clerk', 'auth0') */
259
+ providerId: string;
260
+ /** Provider-specific session data */
261
+ session?: unknown;
262
+ }
263
+ /**
264
+ * Authenticated request context extension
265
+ *
266
+ * This is a discriminated union based on `authMode`:
267
+ * - `'native'`: Built-in JWT authentication via authPlugin
268
+ * - `'adapter'`: External provider via AuthAdapter
269
+ *
270
+ * Use the `authMode` discriminant for type-safe access to mode-specific properties:
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * if (ctx.auth?.authMode === 'native') {
275
+ * // Access JWT-specific properties
276
+ * console.log(ctx.auth.payload?.sub);
277
+ * } else if (ctx.auth?.authMode === 'adapter') {
278
+ * // Access adapter-specific properties
279
+ * console.log(ctx.auth.providerId);
280
+ * }
281
+ * ```
282
+ */
283
+ export type AuthContext = NativeAuthContext | AdapterAuthContext;
247
284
  declare module '@veloxts/core' {
248
285
  interface BaseContext {
249
286
  /** Auth context - available when auth middleware is used */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/auth",
3
- "version": "0.6.68",
3
+ "version": "0.6.70",
4
4
  "description": "Authentication and authorization system for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -57,8 +57,8 @@
57
57
  "dependencies": {
58
58
  "@fastify/cookie": "11.0.2",
59
59
  "fastify": "5.6.2",
60
- "@veloxts/router": "0.6.68",
61
- "@veloxts/core": "0.6.68"
60
+ "@veloxts/router": "0.6.70",
61
+ "@veloxts/core": "0.6.70"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "argon2": ">=0.30.0",
@@ -82,8 +82,8 @@
82
82
  "fastify-plugin": "5.1.0",
83
83
  "typescript": "5.9.3",
84
84
  "vitest": "4.0.16",
85
- "@veloxts/testing": "0.6.68",
86
- "@veloxts/validation": "0.6.68"
85
+ "@veloxts/testing": "0.6.70",
86
+ "@veloxts/validation": "0.6.70"
87
87
  },
88
88
  "keywords": [
89
89
  "velox",