@veloxts/auth 0.6.84 → 0.6.86

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,31 @@
1
+ /**
2
+ * Shared utilities for auth adapters
3
+ *
4
+ * @module auth/adapters/utils
5
+ * @internal
6
+ */
7
+ /**
8
+ * Extract Bearer token from Authorization header
9
+ *
10
+ * @param headerValue - Authorization header value
11
+ * @returns Token string or null if not a Bearer token
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const token = extractBearerToken('Bearer eyJhbGci...');
16
+ * // Returns: 'eyJhbGci...'
17
+ *
18
+ * const invalid = extractBearerToken('Basic abc123');
19
+ * // Returns: null
20
+ * ```
21
+ */
22
+ export declare function extractBearerToken(headerValue: string): string | null;
23
+ /**
24
+ * Validates that a string value is non-empty
25
+ *
26
+ * @param value - Value to check
27
+ * @param fieldName - Field name for error message
28
+ * @returns The trimmed value if valid
29
+ * @throws Error if value is empty or whitespace-only
30
+ */
31
+ export declare function validateNonEmptyString(value: string | undefined, fieldName: string): string;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Shared utilities for auth adapters
3
+ *
4
+ * @module auth/adapters/utils
5
+ * @internal
6
+ */
7
+ // ============================================================================
8
+ // Token Extraction
9
+ // ============================================================================
10
+ /**
11
+ * Extract Bearer token from Authorization header
12
+ *
13
+ * @param headerValue - Authorization header value
14
+ * @returns Token string or null if not a Bearer token
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const token = extractBearerToken('Bearer eyJhbGci...');
19
+ * // Returns: 'eyJhbGci...'
20
+ *
21
+ * const invalid = extractBearerToken('Basic abc123');
22
+ * // Returns: null
23
+ * ```
24
+ */
25
+ export function extractBearerToken(headerValue) {
26
+ const parts = headerValue.split(' ');
27
+ if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') {
28
+ return null;
29
+ }
30
+ // Trim whitespace from token to handle malformed headers
31
+ return parts[1].trim();
32
+ }
33
+ // ============================================================================
34
+ // Validation Helpers
35
+ // ============================================================================
36
+ /**
37
+ * Validates that a string value is non-empty
38
+ *
39
+ * @param value - Value to check
40
+ * @param fieldName - Field name for error message
41
+ * @returns The trimmed value if valid
42
+ * @throws Error if value is empty or whitespace-only
43
+ */
44
+ export function validateNonEmptyString(value, fieldName) {
45
+ if (!value || value.trim() === '') {
46
+ throw new Error(`${fieldName} is required and cannot be empty`);
47
+ }
48
+ return value.trim();
49
+ }
@@ -70,6 +70,70 @@ export function clearAuthRateLimitStore() {
70
70
  // Start cleanup on module load
71
71
  startCleanup();
72
72
  // ============================================================================
73
+ // Configuration Helpers
74
+ // ============================================================================
75
+ /** Time constants for readability */
76
+ const FIFTEEN_MINUTES_MS = 15 * 60 * 1000;
77
+ const ONE_HOUR_MS = 60 * 60 * 1000;
78
+ const ONE_MINUTE_MS = 60 * 1000;
79
+ /** IP-only key generator */
80
+ const ipOnlyKeyGenerator = (ctx) => ctx.request.ip ?? 'unknown';
81
+ /**
82
+ * Default key generator combining IP and identifier
83
+ */
84
+ function defaultKeyGenerator(ctx, identifier) {
85
+ const ip = ctx.request.ip ?? 'unknown';
86
+ return identifier ? `${ip}:${identifier.toLowerCase()}` : ip;
87
+ }
88
+ const OPERATION_DEFAULTS = {
89
+ login: {
90
+ maxAttempts: 5,
91
+ windowMs: FIFTEEN_MINUTES_MS,
92
+ lockoutDurationMs: FIFTEEN_MINUTES_MS,
93
+ keyGenerator: defaultKeyGenerator,
94
+ message: 'Too many login attempts. Please try again later.',
95
+ progressiveBackoff: true,
96
+ },
97
+ register: {
98
+ maxAttempts: 3,
99
+ windowMs: ONE_HOUR_MS,
100
+ lockoutDurationMs: ONE_HOUR_MS,
101
+ keyGenerator: ipOnlyKeyGenerator,
102
+ message: 'Too many registration attempts. Please try again later.',
103
+ progressiveBackoff: false,
104
+ },
105
+ passwordReset: {
106
+ maxAttempts: 3,
107
+ windowMs: ONE_HOUR_MS,
108
+ lockoutDurationMs: ONE_HOUR_MS,
109
+ keyGenerator: ipOnlyKeyGenerator,
110
+ message: 'Too many password reset attempts. Please try again later.',
111
+ progressiveBackoff: false,
112
+ },
113
+ refresh: {
114
+ maxAttempts: 10,
115
+ windowMs: ONE_MINUTE_MS,
116
+ lockoutDurationMs: ONE_MINUTE_MS,
117
+ keyGenerator: ipOnlyKeyGenerator,
118
+ message: 'Too many token refresh attempts. Please try again later.',
119
+ progressiveBackoff: false,
120
+ },
121
+ };
122
+ /**
123
+ * Build a complete rate limit config by merging user config with defaults
124
+ */
125
+ function buildRateLimitConfig(userConfig, operation) {
126
+ const defaults = OPERATION_DEFAULTS[operation];
127
+ return {
128
+ maxAttempts: userConfig?.maxAttempts ?? defaults.maxAttempts,
129
+ windowMs: userConfig?.windowMs ?? defaults.windowMs,
130
+ lockoutDurationMs: userConfig?.lockoutDurationMs ?? defaults.lockoutDurationMs,
131
+ keyGenerator: userConfig?.keyGenerator ?? defaults.keyGenerator,
132
+ message: userConfig?.message ?? defaults.message,
133
+ progressiveBackoff: userConfig?.progressiveBackoff ?? defaults.progressiveBackoff,
134
+ };
135
+ }
136
+ // ============================================================================
73
137
  // Auth Rate Limiter
74
138
  // ============================================================================
75
139
  /**
@@ -96,38 +160,17 @@ startCleanup();
96
160
  * ```
97
161
  */
98
162
  export function createAuthRateLimiter(config = {}) {
99
- // Default configurations
100
- const loginConfig = {
101
- maxAttempts: config.login?.maxAttempts ?? 5,
102
- windowMs: config.login?.windowMs ?? 15 * 60 * 1000, // 15 minutes
103
- lockoutDurationMs: config.login?.lockoutDurationMs ?? 15 * 60 * 1000,
104
- keyGenerator: config.login?.keyGenerator ?? defaultKeyGenerator,
105
- message: config.login?.message ?? 'Too many login attempts. Please try again later.',
106
- progressiveBackoff: config.login?.progressiveBackoff ?? true,
107
- };
108
- const registerConfig = {
109
- maxAttempts: config.register?.maxAttempts ?? 3,
110
- windowMs: config.register?.windowMs ?? 60 * 60 * 1000, // 1 hour
111
- lockoutDurationMs: config.register?.lockoutDurationMs ?? 60 * 60 * 1000,
112
- keyGenerator: config.register?.keyGenerator ?? ((ctx) => ctx.request.ip ?? 'unknown'),
113
- message: config.register?.message ?? 'Too many registration attempts. Please try again later.',
114
- progressiveBackoff: config.register?.progressiveBackoff ?? false,
115
- };
116
- const passwordResetConfig = {
117
- maxAttempts: config.passwordReset?.maxAttempts ?? 3,
118
- windowMs: config.passwordReset?.windowMs ?? 60 * 60 * 1000, // 1 hour
119
- lockoutDurationMs: config.passwordReset?.lockoutDurationMs ?? 60 * 60 * 1000,
120
- keyGenerator: config.passwordReset?.keyGenerator ?? ((ctx) => ctx.request.ip ?? 'unknown'),
121
- message: config.passwordReset?.message ?? 'Too many password reset attempts. Please try again later.',
122
- progressiveBackoff: config.passwordReset?.progressiveBackoff ?? false,
123
- };
124
- const refreshConfig = {
125
- maxAttempts: config.refresh?.maxAttempts ?? 10,
126
- windowMs: config.refresh?.windowMs ?? 60 * 1000, // 1 minute
127
- lockoutDurationMs: config.refresh?.lockoutDurationMs ?? 60 * 1000,
128
- keyGenerator: config.refresh?.keyGenerator ?? ((ctx) => ctx.request.ip ?? 'unknown'),
129
- message: config.refresh?.message ?? 'Too many token refresh attempts. Please try again later.',
130
- progressiveBackoff: config.refresh?.progressiveBackoff ?? false,
163
+ // Build configurations using helper
164
+ const loginConfig = buildRateLimitConfig(config.login, 'login');
165
+ const registerConfig = buildRateLimitConfig(config.register, 'register');
166
+ const passwordResetConfig = buildRateLimitConfig(config.passwordReset, 'passwordReset');
167
+ const refreshConfig = buildRateLimitConfig(config.refresh, 'refresh');
168
+ // Config lookup for operations
169
+ const configs = {
170
+ login: loginConfig,
171
+ register: registerConfig,
172
+ 'password-reset': passwordResetConfig,
173
+ refresh: refreshConfig,
131
174
  };
132
175
  return {
133
176
  /**
@@ -178,18 +221,14 @@ export function createAuthRateLimiter(config = {}) {
178
221
  */
179
222
  recordFailure: (key, operation) => {
180
223
  const fullKey = `auth:${operation}:${key}`;
181
- const config = operation === 'login'
182
- ? loginConfig
183
- : operation === 'register'
184
- ? registerConfig
185
- : passwordResetConfig;
224
+ const operationConfig = configs[operation];
186
225
  const now = Date.now();
187
226
  const entry = authRateLimitStore.get(fullKey);
188
227
  if (!entry || entry.windowResetAt <= now) {
189
228
  // Start new window
190
229
  authRateLimitStore.set(fullKey, {
191
230
  attempts: 1,
192
- windowResetAt: now + config.windowMs,
231
+ windowResetAt: now + operationConfig.windowMs,
193
232
  lockoutUntil: null,
194
233
  lockoutCount: entry?.lockoutCount ?? 0,
195
234
  });
@@ -198,9 +237,11 @@ export function createAuthRateLimiter(config = {}) {
198
237
  // Increment in current window
199
238
  entry.attempts++;
200
239
  // Check if lockout should trigger
201
- if (entry.attempts >= config.maxAttempts) {
202
- const lockoutMultiplier = config.progressiveBackoff ? 2 ** entry.lockoutCount : 1;
203
- entry.lockoutUntil = now + config.lockoutDurationMs * lockoutMultiplier;
240
+ if (entry.attempts >= operationConfig.maxAttempts) {
241
+ const lockoutMultiplier = operationConfig.progressiveBackoff
242
+ ? 2 ** entry.lockoutCount
243
+ : 1;
244
+ entry.lockoutUntil = now + operationConfig.lockoutDurationMs * lockoutMultiplier;
204
245
  entry.lockoutCount++;
205
246
  }
206
247
  }
@@ -231,31 +272,18 @@ export function createAuthRateLimiter(config = {}) {
231
272
  */
232
273
  getRemainingAttempts: (key, operation) => {
233
274
  const fullKey = `auth:${operation}:${key}`;
234
- const config = operation === 'login'
235
- ? loginConfig
236
- : operation === 'register'
237
- ? registerConfig
238
- : operation === 'refresh'
239
- ? refreshConfig
240
- : passwordResetConfig;
275
+ const operationConfig = configs[operation];
241
276
  const entry = authRateLimitStore.get(fullKey);
242
277
  if (!entry || entry.windowResetAt <= Date.now()) {
243
- return config.maxAttempts;
278
+ return operationConfig.maxAttempts;
244
279
  }
245
- return Math.max(0, config.maxAttempts - entry.attempts);
280
+ return Math.max(0, operationConfig.maxAttempts - entry.attempts);
246
281
  },
247
282
  };
248
283
  }
249
284
  // ============================================================================
250
285
  // Internal Helpers
251
286
  // ============================================================================
252
- /**
253
- * Default key generator combining IP and identifier
254
- */
255
- function defaultKeyGenerator(ctx, identifier) {
256
- const ip = ctx.request.ip ?? 'unknown';
257
- return identifier ? `${ip}:${identifier.toLowerCase()}` : ip;
258
- }
259
287
  /**
260
288
  * Creates the actual rate limit middleware
261
289
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/auth",
3
- "version": "0.6.84",
3
+ "version": "0.6.86",
4
4
  "description": "Authentication and authorization system for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -61,8 +61,8 @@
61
61
  "dependencies": {
62
62
  "@fastify/cookie": "11.0.2",
63
63
  "fastify": "5.6.2",
64
- "@veloxts/router": "0.6.84",
65
- "@veloxts/core": "0.6.84"
64
+ "@veloxts/core": "0.6.86",
65
+ "@veloxts/router": "0.6.86"
66
66
  },
67
67
  "peerDependencies": {
68
68
  "argon2": ">=0.30.0",
@@ -86,8 +86,8 @@
86
86
  "fastify-plugin": "5.1.0",
87
87
  "typescript": "5.9.3",
88
88
  "vitest": "4.0.16",
89
- "@veloxts/testing": "0.6.84",
90
- "@veloxts/validation": "0.6.84"
89
+ "@veloxts/testing": "0.6.86",
90
+ "@veloxts/validation": "0.6.86"
91
91
  },
92
92
  "keywords": [
93
93
  "velox",