@juspay/neurolink 9.31.2 → 9.32.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 (161) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/auth/AuthProviderFactory.d.ts +71 -0
  3. package/dist/auth/AuthProviderFactory.js +111 -0
  4. package/dist/auth/AuthProviderRegistry.d.ts +33 -0
  5. package/dist/auth/AuthProviderRegistry.js +190 -0
  6. package/dist/auth/RequestContext.d.ts +23 -0
  7. package/dist/auth/RequestContext.js +78 -0
  8. package/dist/auth/authContext.d.ts +198 -0
  9. package/dist/auth/authContext.js +314 -0
  10. package/dist/auth/errors.d.ts +63 -0
  11. package/dist/auth/errors.js +39 -0
  12. package/dist/auth/index.d.ts +20 -8
  13. package/dist/auth/index.js +35 -7
  14. package/dist/auth/middleware/AuthMiddleware.d.ts +181 -0
  15. package/dist/auth/middleware/AuthMiddleware.js +519 -0
  16. package/dist/auth/middleware/rateLimitByUser.d.ts +282 -0
  17. package/dist/auth/middleware/rateLimitByUser.js +554 -0
  18. package/dist/auth/providers/BaseAuthProvider.d.ts +259 -0
  19. package/dist/auth/providers/BaseAuthProvider.js +723 -0
  20. package/dist/auth/providers/CognitoProvider.d.ts +61 -0
  21. package/dist/auth/providers/CognitoProvider.js +304 -0
  22. package/dist/auth/providers/KeycloakProvider.d.ts +61 -0
  23. package/dist/auth/providers/KeycloakProvider.js +393 -0
  24. package/dist/auth/providers/auth0.d.ts +59 -0
  25. package/dist/auth/providers/auth0.js +274 -0
  26. package/dist/auth/providers/betterAuth.d.ts +51 -0
  27. package/dist/auth/providers/betterAuth.js +182 -0
  28. package/dist/auth/providers/clerk.d.ts +65 -0
  29. package/dist/auth/providers/clerk.js +317 -0
  30. package/dist/auth/providers/custom.d.ts +64 -0
  31. package/dist/auth/providers/custom.js +112 -0
  32. package/dist/auth/providers/firebase.d.ts +63 -0
  33. package/dist/auth/providers/firebase.js +226 -0
  34. package/dist/auth/providers/jwt.d.ts +68 -0
  35. package/dist/auth/providers/jwt.js +212 -0
  36. package/dist/auth/providers/oauth2.d.ts +73 -0
  37. package/dist/auth/providers/oauth2.js +303 -0
  38. package/dist/auth/providers/supabase.d.ts +63 -0
  39. package/dist/auth/providers/supabase.js +259 -0
  40. package/dist/auth/providers/workos.d.ts +61 -0
  41. package/dist/auth/providers/workos.js +284 -0
  42. package/dist/auth/serverBridge.d.ts +14 -0
  43. package/dist/auth/serverBridge.js +25 -0
  44. package/dist/auth/sessionManager.d.ts +142 -0
  45. package/dist/auth/sessionManager.js +437 -0
  46. package/dist/cli/commands/authProviders.d.ts +43 -0
  47. package/dist/cli/commands/authProviders.js +399 -0
  48. package/dist/cli/factories/authCommandFactory.d.ts +23 -5
  49. package/dist/cli/factories/authCommandFactory.js +108 -5
  50. package/dist/cli/parser.js +1 -1
  51. package/dist/client/auth/AuthProviderFactory.js +111 -0
  52. package/dist/client/auth/AuthProviderRegistry.js +190 -0
  53. package/dist/client/auth/RequestContext.js +78 -0
  54. package/dist/client/auth/accountPool.js +178 -0
  55. package/dist/client/auth/authContext.js +314 -0
  56. package/dist/client/auth/errors.js +39 -0
  57. package/dist/client/auth/index.js +61 -0
  58. package/dist/client/auth/middleware/AuthMiddleware.js +519 -0
  59. package/dist/client/auth/middleware/rateLimitByUser.js +554 -0
  60. package/dist/client/auth/providers/BaseAuthProvider.js +723 -0
  61. package/dist/client/auth/providers/CognitoProvider.js +304 -0
  62. package/dist/client/auth/providers/KeycloakProvider.js +393 -0
  63. package/dist/client/auth/providers/auth0.js +274 -0
  64. package/dist/client/auth/providers/betterAuth.js +182 -0
  65. package/dist/client/auth/providers/clerk.js +317 -0
  66. package/dist/client/auth/providers/custom.js +112 -0
  67. package/dist/client/auth/providers/firebase.js +226 -0
  68. package/dist/client/auth/providers/jwt.js +212 -0
  69. package/dist/client/auth/providers/oauth2.js +303 -0
  70. package/dist/client/auth/providers/supabase.js +259 -0
  71. package/dist/client/auth/providers/workos.js +284 -0
  72. package/dist/client/auth/serverBridge.js +25 -0
  73. package/dist/client/auth/sessionManager.js +437 -0
  74. package/dist/client/core/infrastructure/baseRegistry.js +5 -1
  75. package/dist/client/index.js +25 -0
  76. package/dist/client/mcp/toolRegistry.js +11 -1
  77. package/dist/client/neurolink.js +218 -0
  78. package/dist/client/rag/ChunkerRegistry.js +2 -2
  79. package/dist/client/rag/metadata/MetadataExtractorRegistry.js +2 -2
  80. package/dist/client/rag/reranker/RerankerRegistry.js +2 -2
  81. package/dist/client/server/routes/agentRoutes.js +20 -2
  82. package/dist/client/types/authTypes.js +2 -1
  83. package/dist/core/infrastructure/baseRegistry.d.ts +3 -1
  84. package/dist/core/infrastructure/baseRegistry.js +5 -1
  85. package/dist/index.d.ts +1 -0
  86. package/dist/index.js +25 -0
  87. package/dist/lib/auth/AuthProviderFactory.d.ts +71 -0
  88. package/dist/lib/auth/AuthProviderFactory.js +112 -0
  89. package/dist/lib/auth/AuthProviderRegistry.d.ts +33 -0
  90. package/dist/lib/auth/AuthProviderRegistry.js +191 -0
  91. package/dist/lib/auth/RequestContext.d.ts +23 -0
  92. package/dist/lib/auth/RequestContext.js +79 -0
  93. package/dist/lib/auth/authContext.d.ts +198 -0
  94. package/dist/lib/auth/authContext.js +315 -0
  95. package/dist/lib/auth/errors.d.ts +63 -0
  96. package/dist/lib/auth/errors.js +40 -0
  97. package/dist/lib/auth/index.d.ts +20 -8
  98. package/dist/lib/auth/index.js +35 -7
  99. package/dist/lib/auth/middleware/AuthMiddleware.d.ts +181 -0
  100. package/dist/lib/auth/middleware/AuthMiddleware.js +520 -0
  101. package/dist/lib/auth/middleware/rateLimitByUser.d.ts +282 -0
  102. package/dist/lib/auth/middleware/rateLimitByUser.js +555 -0
  103. package/dist/lib/auth/providers/BaseAuthProvider.d.ts +259 -0
  104. package/dist/lib/auth/providers/BaseAuthProvider.js +724 -0
  105. package/dist/lib/auth/providers/CognitoProvider.d.ts +61 -0
  106. package/dist/lib/auth/providers/CognitoProvider.js +305 -0
  107. package/dist/lib/auth/providers/KeycloakProvider.d.ts +61 -0
  108. package/dist/lib/auth/providers/KeycloakProvider.js +394 -0
  109. package/dist/lib/auth/providers/auth0.d.ts +59 -0
  110. package/dist/lib/auth/providers/auth0.js +275 -0
  111. package/dist/lib/auth/providers/betterAuth.d.ts +51 -0
  112. package/dist/lib/auth/providers/betterAuth.js +183 -0
  113. package/dist/lib/auth/providers/clerk.d.ts +65 -0
  114. package/dist/lib/auth/providers/clerk.js +318 -0
  115. package/dist/lib/auth/providers/custom.d.ts +64 -0
  116. package/dist/lib/auth/providers/custom.js +113 -0
  117. package/dist/lib/auth/providers/firebase.d.ts +63 -0
  118. package/dist/lib/auth/providers/firebase.js +227 -0
  119. package/dist/lib/auth/providers/jwt.d.ts +68 -0
  120. package/dist/lib/auth/providers/jwt.js +213 -0
  121. package/dist/lib/auth/providers/oauth2.d.ts +73 -0
  122. package/dist/lib/auth/providers/oauth2.js +304 -0
  123. package/dist/lib/auth/providers/supabase.d.ts +63 -0
  124. package/dist/lib/auth/providers/supabase.js +260 -0
  125. package/dist/lib/auth/providers/workos.d.ts +61 -0
  126. package/dist/lib/auth/providers/workos.js +285 -0
  127. package/dist/lib/auth/serverBridge.d.ts +14 -0
  128. package/dist/lib/auth/serverBridge.js +26 -0
  129. package/dist/lib/auth/sessionManager.d.ts +142 -0
  130. package/dist/lib/auth/sessionManager.js +438 -0
  131. package/dist/lib/core/infrastructure/baseRegistry.d.ts +3 -1
  132. package/dist/lib/core/infrastructure/baseRegistry.js +5 -1
  133. package/dist/lib/index.d.ts +1 -0
  134. package/dist/lib/index.js +25 -0
  135. package/dist/lib/mcp/toolRegistry.js +11 -1
  136. package/dist/lib/neurolink.d.ts +42 -1
  137. package/dist/lib/neurolink.js +218 -0
  138. package/dist/lib/rag/ChunkerRegistry.js +2 -2
  139. package/dist/lib/rag/metadata/MetadataExtractorRegistry.js +2 -2
  140. package/dist/lib/rag/reranker/RerankerRegistry.js +2 -2
  141. package/dist/lib/server/routes/agentRoutes.js +20 -2
  142. package/dist/lib/types/authTypes.d.ts +937 -1
  143. package/dist/lib/types/authTypes.js +2 -1
  144. package/dist/lib/types/configTypes.d.ts +46 -0
  145. package/dist/lib/types/generateTypes.d.ts +6 -0
  146. package/dist/lib/types/index.d.ts +1 -0
  147. package/dist/lib/types/streamTypes.d.ts +6 -0
  148. package/dist/mcp/toolRegistry.js +11 -1
  149. package/dist/neurolink.d.ts +42 -1
  150. package/dist/neurolink.js +218 -0
  151. package/dist/rag/ChunkerRegistry.js +2 -2
  152. package/dist/rag/metadata/MetadataExtractorRegistry.js +2 -2
  153. package/dist/rag/reranker/RerankerRegistry.js +2 -2
  154. package/dist/server/routes/agentRoutes.js +20 -2
  155. package/dist/types/authTypes.d.ts +937 -1
  156. package/dist/types/authTypes.js +2 -1
  157. package/dist/types/configTypes.d.ts +46 -0
  158. package/dist/types/generateTypes.d.ts +6 -0
  159. package/dist/types/index.d.ts +1 -0
  160. package/dist/types/streamTypes.d.ts +6 -0
  161. package/package.json +2 -1
@@ -0,0 +1,554 @@
1
+ // src/lib/auth/middleware/rateLimitByUser.ts
2
+ import { logger } from "../../utils/logger.js";
3
+ /** Mask a userId for safe log output (first 4 chars + "***"). */
4
+ function maskUserId(id) {
5
+ return id.length > 4 ? `${id.slice(0, 4)}***` : "***";
6
+ }
7
+ /**
8
+ * In-memory storage for rate limiting (single instance deployments)
9
+ */
10
+ export class MemoryRateLimitStorage {
11
+ buckets = new Map();
12
+ cleanupInterval;
13
+ expiryMs;
14
+ constructor(cleanupIntervalMs = 60000, expiryMs = 3600000) {
15
+ this.expiryMs = expiryMs;
16
+ // Periodically cleanup expired buckets
17
+ this.cleanupInterval = setInterval(() => {
18
+ this.cleanupExpiredBuckets();
19
+ }, cleanupIntervalMs);
20
+ // Allow Node.js to exit gracefully even if the interval is still active
21
+ this.cleanupInterval.unref();
22
+ }
23
+ async getBucket(userId) {
24
+ return this.buckets.get(userId) || null;
25
+ }
26
+ async setBucket(userId, bucket) {
27
+ this.buckets.set(userId, bucket);
28
+ }
29
+ async deleteBucket(userId) {
30
+ this.buckets.delete(userId);
31
+ }
32
+ async healthCheck() {
33
+ return true;
34
+ }
35
+ async cleanup() {
36
+ if (this.cleanupInterval) {
37
+ clearInterval(this.cleanupInterval);
38
+ }
39
+ this.buckets.clear();
40
+ }
41
+ cleanupExpiredBuckets() {
42
+ const now = Date.now();
43
+ const expiryCutoff = now - this.expiryMs;
44
+ for (const [userId, bucket] of this.buckets.entries()) {
45
+ if (bucket.lastRefill < expiryCutoff) {
46
+ this.buckets.delete(userId);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * Redis-backed storage for rate limiting (distributed deployments)
53
+ */
54
+ export class RedisRateLimitStorage {
55
+ redisUrl;
56
+ prefix;
57
+ ttlSeconds;
58
+ client = null;
59
+ initPromise = null;
60
+ constructor(config) {
61
+ this.redisUrl = config.url;
62
+ this.prefix = config.prefix || "neurolink:ratelimit:";
63
+ const baseTtl = config.ttlSeconds || 3600; // 1 hour default TTL
64
+ const windowTtl = config.windowMs ? Math.ceil(config.windowMs / 1000) : 0;
65
+ this.ttlSeconds = Math.max(baseTtl, windowTtl);
66
+ }
67
+ async getClient() {
68
+ if (this.client) {
69
+ return this.client;
70
+ }
71
+ if (!this.initPromise) {
72
+ this.initPromise = this.createClient();
73
+ }
74
+ return this.initPromise;
75
+ }
76
+ async createClient() {
77
+ try {
78
+ // Dynamic import to avoid loading Redis unless needed
79
+ const { createClient } = await import("redis");
80
+ const client = createClient({ url: this.redisUrl });
81
+ await client.connect();
82
+ this.client = client;
83
+ return this.client;
84
+ }
85
+ catch {
86
+ this.initPromise = null;
87
+ throw new Error("Redis client not available for rate limiting");
88
+ }
89
+ }
90
+ async getBucket(userId) {
91
+ try {
92
+ const client = await this.getClient();
93
+ const key = `${this.prefix}${userId}`;
94
+ const data = await client.get(key);
95
+ if (!data) {
96
+ return null;
97
+ }
98
+ return JSON.parse(data);
99
+ }
100
+ catch (error) {
101
+ logger.warn("Redis rate limit getBucket failed:", error);
102
+ return null;
103
+ }
104
+ }
105
+ async setBucket(userId, bucket) {
106
+ try {
107
+ const client = await this.getClient();
108
+ const key = `${this.prefix}${userId}`;
109
+ await client.setEx(key, this.ttlSeconds, JSON.stringify(bucket));
110
+ }
111
+ catch (error) {
112
+ logger.warn("Redis rate limit setBucket failed:", error);
113
+ }
114
+ }
115
+ async deleteBucket(userId) {
116
+ try {
117
+ const client = await this.getClient();
118
+ const key = `${this.prefix}${userId}`;
119
+ await client.del(key);
120
+ }
121
+ catch (error) {
122
+ logger.warn("Redis rate limit deleteBucket failed:", error);
123
+ }
124
+ }
125
+ /**
126
+ * Atomically refill and consume one token using a Redis Lua script.
127
+ *
128
+ * The entire read-modify-write cycle runs inside Redis as a single
129
+ * atomic operation, so two parallel requests for the same user can
130
+ * never read the same token count.
131
+ */
132
+ async atomicConsume(userId, limit, windowMs, nowMs) {
133
+ try {
134
+ const client = await this.getClient();
135
+ const key = `${this.prefix}${userId}`;
136
+ // Lua script: refill tokens based on elapsed time, then try to consume one.
137
+ // KEYS[1] = bucket key
138
+ // ARGV[1] = limit (max tokens)
139
+ // ARGV[2] = windowMs
140
+ // ARGV[3] = nowMs (current timestamp)
141
+ // ARGV[4] = ttl in seconds
142
+ // ARGV[5] = userId
143
+ //
144
+ // Returns: [tokens (x1000), lastRefill, consumed (0/1)]
145
+ // – tokens are multiplied by 1000 to preserve 3 decimal places
146
+ // since Redis Lua returns only integers.
147
+ // – returns nil when the key does not exist (caller creates bucket).
148
+ const luaScript = `
149
+ local data = redis.call('GET', KEYS[1])
150
+ if not data then
151
+ return nil
152
+ end
153
+
154
+ local bucket = cjson.decode(data)
155
+ local limit = tonumber(ARGV[1])
156
+ local windowMs = tonumber(ARGV[2])
157
+ local nowMs = tonumber(ARGV[3])
158
+ local ttl = tonumber(ARGV[4])
159
+ local userId = ARGV[5]
160
+
161
+ -- Refill tokens
162
+ local elapsed = nowMs - bucket.lastRefill
163
+ local tokensToAdd = (elapsed / windowMs) * limit
164
+ local tokens = math.min(limit, bucket.tokens + tokensToAdd)
165
+ local lastRefill = nowMs
166
+
167
+ -- Try to consume
168
+ local consumed = 0
169
+ if tokens >= 1 then
170
+ tokens = tokens - 1
171
+ consumed = 1
172
+ end
173
+
174
+ -- Persist
175
+ bucket.tokens = tokens
176
+ bucket.lastRefill = lastRefill
177
+ bucket.userId = userId
178
+ redis.call('SETEX', KEYS[1], ttl, cjson.encode(bucket))
179
+
180
+ -- Return integers (tokens * 1000 to keep 3 decimal places)
181
+ return { math.floor(tokens * 1000), lastRefill, consumed }
182
+ `;
183
+ const result = await client.eval(luaScript, 1, key, String(limit), String(windowMs), String(nowMs), String(this.ttlSeconds), userId);
184
+ if (result === null || result === undefined) {
185
+ return null; // Key did not exist
186
+ }
187
+ const [tokensTimes1000, lastRefill, consumed] = result;
188
+ return {
189
+ bucket: {
190
+ tokens: tokensTimes1000 / 1000,
191
+ lastRefill,
192
+ userId,
193
+ },
194
+ consumed: consumed === 1,
195
+ };
196
+ }
197
+ catch (error) {
198
+ logger.warn("Redis atomicConsume failed, falling back to non-atomic:", error);
199
+ return null; // Fallback: caller will use get+set path
200
+ }
201
+ }
202
+ async healthCheck() {
203
+ try {
204
+ const client = await this.getClient();
205
+ const pong = await client.ping();
206
+ return pong === "PONG";
207
+ }
208
+ catch {
209
+ return false;
210
+ }
211
+ }
212
+ async cleanup() {
213
+ if (this.client) {
214
+ await this.client.quit();
215
+ this.client = null;
216
+ this.initPromise = null;
217
+ }
218
+ }
219
+ }
220
+ /**
221
+ * Token bucket rate limiter implementation
222
+ *
223
+ * Uses the token bucket algorithm which allows for burst traffic while
224
+ * maintaining an average rate limit. Tokens are continuously added to
225
+ * the bucket at a fixed rate, and each request consumes one token.
226
+ */
227
+ export class UserRateLimiter {
228
+ storage;
229
+ config;
230
+ constructor(config, storage) {
231
+ this.config = {
232
+ message: "Rate limit exceeded. Please try again later.",
233
+ ...config,
234
+ };
235
+ this.storage =
236
+ storage ||
237
+ new MemoryRateLimitStorage(Math.max(60000, config.windowMs), config.windowMs);
238
+ }
239
+ /**
240
+ * Get the rate limit for a specific user based on their roles
241
+ */
242
+ getLimitForUser(user) {
243
+ // Check user-specific limits first
244
+ if (this.config.userLimits && user.id in this.config.userLimits) {
245
+ return this.config.userLimits[user.id];
246
+ }
247
+ // Check role-based limits (use highest if user has multiple roles)
248
+ if (this.config.roleLimits) {
249
+ let maxLimit = this.config.maxRequests;
250
+ for (const role of user.roles) {
251
+ if (role in this.config.roleLimits) {
252
+ maxLimit = Math.max(maxLimit, this.config.roleLimits[role]);
253
+ }
254
+ }
255
+ return maxLimit;
256
+ }
257
+ return this.config.maxRequests;
258
+ }
259
+ /**
260
+ * Check if a user should skip rate limiting (based on roles)
261
+ */
262
+ shouldSkipRateLimit(user) {
263
+ if (!this.config.skipRoles || this.config.skipRoles.length === 0) {
264
+ return false;
265
+ }
266
+ return user.roles.some((role) => this.config.skipRoles?.includes(role) ?? false);
267
+ }
268
+ /**
269
+ * Consume a token from the user's bucket
270
+ * Returns the rate limit result
271
+ *
272
+ * When the storage backend supports `atomicConsume` (e.g. Redis with Lua),
273
+ * the entire refill-and-consume is executed as a single atomic operation,
274
+ * preventing race conditions where parallel requests both read the same
275
+ * token count and both succeed.
276
+ */
277
+ async consume(user) {
278
+ // Skip rate limiting for exempt roles
279
+ if (this.shouldSkipRateLimit(user)) {
280
+ return {
281
+ allowed: true,
282
+ remaining: Infinity,
283
+ resetIn: 0,
284
+ limit: Infinity,
285
+ };
286
+ }
287
+ const userId = user.id;
288
+ const limit = this.getLimitForUser(user);
289
+ const now = Date.now();
290
+ // Try atomic consume first (Redis Lua script – race-condition safe)
291
+ if (this.storage.atomicConsume) {
292
+ const atomicResult = await this.storage.atomicConsume(userId, limit, this.config.windowMs, now);
293
+ if (atomicResult !== null) {
294
+ const { bucket, consumed } = atomicResult;
295
+ if (consumed) {
296
+ return {
297
+ allowed: true,
298
+ remaining: Math.floor(bucket.tokens),
299
+ resetIn: Math.ceil(((limit - bucket.tokens) / limit) * this.config.windowMs),
300
+ limit,
301
+ };
302
+ }
303
+ // Rate limited
304
+ const resetIn = Math.ceil(((1 - bucket.tokens) / limit) * this.config.windowMs);
305
+ return {
306
+ allowed: false,
307
+ remaining: 0,
308
+ resetIn,
309
+ limit,
310
+ error: this.config.message,
311
+ };
312
+ }
313
+ // atomicConsume returned null → bucket does not exist yet.
314
+ // Fall through to create it below.
315
+ }
316
+ // Fallback: non-atomic get+set (safe for single-threaded in-memory storage)
317
+ let bucket = await this.storage.getBucket(userId);
318
+ if (!bucket) {
319
+ // Create new bucket with full tokens
320
+ bucket = {
321
+ tokens: limit,
322
+ lastRefill: now,
323
+ userId,
324
+ };
325
+ }
326
+ // Calculate tokens to add based on time elapsed
327
+ const timePassed = now - bucket.lastRefill;
328
+ const tokensToAdd = (timePassed / this.config.windowMs) * limit;
329
+ bucket.tokens = Math.min(limit, bucket.tokens + tokensToAdd);
330
+ bucket.lastRefill = now;
331
+ // Try to consume a token
332
+ if (bucket.tokens >= 1) {
333
+ bucket.tokens -= 1;
334
+ await this.storage.setBucket(userId, bucket);
335
+ return {
336
+ allowed: true,
337
+ remaining: Math.floor(bucket.tokens),
338
+ resetIn: Math.ceil(((limit - bucket.tokens) / limit) * this.config.windowMs),
339
+ limit,
340
+ };
341
+ }
342
+ // Rate limited
343
+ await this.storage.setBucket(userId, bucket);
344
+ const resetIn = Math.ceil(((1 - bucket.tokens) / limit) * this.config.windowMs);
345
+ return {
346
+ allowed: false,
347
+ remaining: 0,
348
+ resetIn,
349
+ limit,
350
+ error: this.config.message,
351
+ };
352
+ }
353
+ /**
354
+ * Get current rate limit status for a user without consuming a token
355
+ */
356
+ async getStatus(user) {
357
+ if (this.shouldSkipRateLimit(user)) {
358
+ return {
359
+ allowed: true,
360
+ remaining: Infinity,
361
+ resetIn: 0,
362
+ limit: Infinity,
363
+ };
364
+ }
365
+ const limit = this.getLimitForUser(user);
366
+ const bucket = await this.storage.getBucket(user.id);
367
+ if (!bucket) {
368
+ return {
369
+ allowed: true,
370
+ remaining: limit,
371
+ resetIn: 0,
372
+ limit,
373
+ };
374
+ }
375
+ // Calculate current tokens
376
+ const now = Date.now();
377
+ const timePassed = now - bucket.lastRefill;
378
+ const tokensToAdd = (timePassed / this.config.windowMs) * limit;
379
+ const currentTokens = Math.min(limit, bucket.tokens + tokensToAdd);
380
+ return {
381
+ allowed: currentTokens >= 1,
382
+ remaining: Math.floor(currentTokens),
383
+ resetIn: currentTokens >= 1
384
+ ? 0
385
+ : Math.ceil(((1 - currentTokens) / limit) * this.config.windowMs),
386
+ limit,
387
+ };
388
+ }
389
+ /**
390
+ * Reset rate limit for a user (admin action)
391
+ */
392
+ async resetUser(userId) {
393
+ await this.storage.deleteBucket(userId);
394
+ logger.debug(`Rate limit reset for user: ${maskUserId(userId)}`);
395
+ }
396
+ /**
397
+ * Check storage health
398
+ */
399
+ async healthCheck() {
400
+ return this.storage.healthCheck();
401
+ }
402
+ /**
403
+ * Cleanup resources
404
+ */
405
+ async cleanup() {
406
+ await this.storage.cleanup();
407
+ }
408
+ }
409
+ /**
410
+ * Create rate limiting middleware for authenticated requests
411
+ *
412
+ * @param config - Rate limit configuration
413
+ * @param storage - Optional custom storage backend
414
+ * @returns Middleware function
415
+ *
416
+ * @example
417
+ * ```typescript
418
+ * const rateLimitMiddleware = createRateLimitByUserMiddleware({
419
+ * maxRequests: 100,
420
+ * windowMs: 60000, // 1 minute
421
+ * roleLimits: {
422
+ * "premium": 500,
423
+ * "admin": 1000
424
+ * },
425
+ * skipRoles: ["super-admin"]
426
+ * });
427
+ *
428
+ * // Use in server
429
+ * app.use(async (request, context) => {
430
+ * const result = await rateLimitMiddleware(context);
431
+ * if (!result.proceed) {
432
+ * return result.response;
433
+ * }
434
+ * // Continue processing...
435
+ * });
436
+ * ```
437
+ */
438
+ export function createRateLimitByUserMiddleware(config, storage) {
439
+ const limiter = new UserRateLimiter(config, storage);
440
+ return async (context) => {
441
+ const result = await limiter.consume(context.user);
442
+ if (!result.allowed) {
443
+ const response = createRateLimitResponse(result);
444
+ return {
445
+ proceed: false,
446
+ rateLimitResult: result,
447
+ response,
448
+ };
449
+ }
450
+ return {
451
+ proceed: true,
452
+ rateLimitResult: result,
453
+ };
454
+ };
455
+ }
456
+ /**
457
+ * Create a combined auth and rate limit middleware
458
+ *
459
+ * @param authMiddleware - Authentication middleware function
460
+ * @param rateLimitConfig - Rate limit configuration
461
+ * @param storage - Optional custom storage backend
462
+ * @returns Combined middleware function
463
+ *
464
+ * @example
465
+ * ```typescript
466
+ * const protectedRoute = createAuthenticatedRateLimitMiddleware(
467
+ * createAuthMiddleware({ provider: authProvider }),
468
+ * { maxRequests: 100, windowMs: 60000 }
469
+ * );
470
+ *
471
+ * // Use in routes
472
+ * app.post("/api/generate", async (request) => {
473
+ * const result = await protectedRoute(request);
474
+ * if (!result.proceed) {
475
+ * return result.response;
476
+ * }
477
+ * // Handle request with result.context
478
+ * });
479
+ * ```
480
+ */
481
+ export function createAuthenticatedRateLimitMiddleware(authMiddleware, rateLimitConfig, storage) {
482
+ const limiter = new UserRateLimiter(rateLimitConfig, storage);
483
+ return async (context) => {
484
+ // First, authenticate
485
+ const authResult = await authMiddleware(context);
486
+ if (!authResult.proceed || !authResult.context) {
487
+ return authResult;
488
+ }
489
+ // Then, check rate limit
490
+ const rateLimitResult = await limiter.consume(authResult.context.user);
491
+ if (!rateLimitResult.allowed) {
492
+ return {
493
+ proceed: false,
494
+ context: authResult.context,
495
+ rateLimitResult,
496
+ response: createRateLimitResponse(rateLimitResult),
497
+ };
498
+ }
499
+ return {
500
+ proceed: true,
501
+ context: authResult.context,
502
+ rateLimitResult,
503
+ };
504
+ };
505
+ }
506
+ /**
507
+ * Create 429 Too Many Requests response
508
+ */
509
+ function createRateLimitResponse(result) {
510
+ return new Response(JSON.stringify({
511
+ error: "Too Many Requests",
512
+ message: result.error || "Rate limit exceeded",
513
+ statusCode: 429,
514
+ retryAfter: Math.ceil(result.resetIn / 1000), // In seconds
515
+ limit: result.limit,
516
+ remaining: result.remaining,
517
+ }), {
518
+ status: 429,
519
+ headers: {
520
+ "Content-Type": "application/json",
521
+ "Retry-After": String(Math.ceil(result.resetIn / 1000)),
522
+ "X-RateLimit-Limit": String(result.limit),
523
+ "X-RateLimit-Remaining": String(result.remaining),
524
+ "X-RateLimit-Reset": String(Date.now() + result.resetIn),
525
+ },
526
+ });
527
+ }
528
+ /**
529
+ * Create rate limit storage based on configuration
530
+ *
531
+ * @param config - Storage configuration
532
+ * @returns Appropriate storage backend
533
+ *
534
+ * @example
535
+ * ```typescript
536
+ * // Memory storage (default)
537
+ * const storage = createRateLimitStorage({ type: "memory" });
538
+ *
539
+ * // Redis storage
540
+ * const storage = createRateLimitStorage({
541
+ * type: "redis",
542
+ * redis: {
543
+ * url: "redis://localhost:6379",
544
+ * prefix: "myapp:ratelimit:"
545
+ * }
546
+ * });
547
+ * ```
548
+ */
549
+ export function createRateLimitStorage(config) {
550
+ if (config.type === "redis" && config.redis) {
551
+ return new RedisRateLimitStorage(config.redis);
552
+ }
553
+ return new MemoryRateLimitStorage(config.cleanupIntervalMs);
554
+ }