@parsrun/auth 0.1.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 (54) hide show
  1. package/README.md +133 -0
  2. package/dist/adapters/hono.d.ts +9 -0
  3. package/dist/adapters/hono.js +6 -0
  4. package/dist/adapters/hono.js.map +1 -0
  5. package/dist/adapters/index.d.ts +9 -0
  6. package/dist/adapters/index.js +7 -0
  7. package/dist/adapters/index.js.map +1 -0
  8. package/dist/authorization-By1Xp8Za.d.ts +213 -0
  9. package/dist/base-BKyR8rcE.d.ts +646 -0
  10. package/dist/chunk-42MGHABB.js +263 -0
  11. package/dist/chunk-42MGHABB.js.map +1 -0
  12. package/dist/chunk-7GOBAL4G.js +3 -0
  13. package/dist/chunk-7GOBAL4G.js.map +1 -0
  14. package/dist/chunk-G5I3T73A.js +152 -0
  15. package/dist/chunk-G5I3T73A.js.map +1 -0
  16. package/dist/chunk-IB4WUQDZ.js +410 -0
  17. package/dist/chunk-IB4WUQDZ.js.map +1 -0
  18. package/dist/chunk-MOG4Y6I7.js +415 -0
  19. package/dist/chunk-MOG4Y6I7.js.map +1 -0
  20. package/dist/chunk-NK4TJV2W.js +295 -0
  21. package/dist/chunk-NK4TJV2W.js.map +1 -0
  22. package/dist/chunk-RHNVRCF3.js +838 -0
  23. package/dist/chunk-RHNVRCF3.js.map +1 -0
  24. package/dist/chunk-YTCPXJR5.js +570 -0
  25. package/dist/chunk-YTCPXJR5.js.map +1 -0
  26. package/dist/cloudflare-kv-L64CZKDK.js +105 -0
  27. package/dist/cloudflare-kv-L64CZKDK.js.map +1 -0
  28. package/dist/deno-kv-F55HKKP6.js +111 -0
  29. package/dist/deno-kv-F55HKKP6.js.map +1 -0
  30. package/dist/index-C3kz9XqE.d.ts +226 -0
  31. package/dist/index-DOGcetyD.d.ts +1041 -0
  32. package/dist/index.d.ts +1579 -0
  33. package/dist/index.js +4294 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/jwt-manager-CH8H0kmm.d.ts +182 -0
  36. package/dist/providers/index.d.ts +90 -0
  37. package/dist/providers/index.js +3 -0
  38. package/dist/providers/index.js.map +1 -0
  39. package/dist/providers/otp/index.d.ts +3 -0
  40. package/dist/providers/otp/index.js +4 -0
  41. package/dist/providers/otp/index.js.map +1 -0
  42. package/dist/redis-5TIS6XCA.js +121 -0
  43. package/dist/redis-5TIS6XCA.js.map +1 -0
  44. package/dist/security/index.d.ts +301 -0
  45. package/dist/security/index.js +5 -0
  46. package/dist/security/index.js.map +1 -0
  47. package/dist/session/index.d.ts +117 -0
  48. package/dist/session/index.js +4 -0
  49. package/dist/session/index.js.map +1 -0
  50. package/dist/storage/index.d.ts +97 -0
  51. package/dist/storage/index.js +3 -0
  52. package/dist/storage/index.js.map +1 -0
  53. package/dist/types-DSjafxJ4.d.ts +193 -0
  54. package/package.json +102 -0
@@ -0,0 +1,410 @@
1
+ import { StorageKeys } from './chunk-42MGHABB.js';
2
+
3
+ // src/providers/otp/otp-manager.ts
4
+ function secureRandomInt(min, max) {
5
+ const range = max - min;
6
+ const bytesNeeded = Math.ceil(Math.log2(range) / 8);
7
+ const maxValid = Math.floor(256 ** bytesNeeded / range) * range - 1;
8
+ let randomValue;
9
+ const randomBytes = new Uint8Array(bytesNeeded);
10
+ do {
11
+ crypto.getRandomValues(randomBytes);
12
+ randomValue = randomBytes.reduce((acc, byte, i) => acc + byte * 256 ** i, 0);
13
+ } while (randomValue > maxValid);
14
+ return min + randomValue % range;
15
+ }
16
+ var OTPManager = class {
17
+ storage;
18
+ config;
19
+ constructor(storage, config) {
20
+ this.storage = storage;
21
+ this.config = {
22
+ length: config?.length ?? 6,
23
+ expiresIn: config?.expiresIn ?? 600,
24
+ maxAttempts: config?.maxAttempts ?? 3,
25
+ rateLimit: config?.rateLimit ?? 5,
26
+ rateLimitWindow: config?.rateLimitWindow ?? 900
27
+ };
28
+ }
29
+ /**
30
+ * Generate OTP code
31
+ */
32
+ generateCode(length) {
33
+ const len = length ?? this.config.length;
34
+ const min = Math.pow(10, len - 1);
35
+ const max = Math.pow(10, len) - 1;
36
+ return secureRandomInt(min, max + 1).toString().padStart(len, "0");
37
+ }
38
+ /**
39
+ * Get storage key for OTP
40
+ */
41
+ getOTPKey(identifier, type) {
42
+ const normalizedId = type === "email" ? identifier.toLowerCase() : identifier;
43
+ return StorageKeys.otp(normalizedId, type);
44
+ }
45
+ /**
46
+ * Get storage key for rate limiting
47
+ */
48
+ getRateLimitKey(identifier, type) {
49
+ const normalizedId = type === "email" ? identifier.toLowerCase() : identifier;
50
+ return StorageKeys.rateLimit(`otp:${type}:${normalizedId}`);
51
+ }
52
+ /**
53
+ * Check rate limit for OTP requests
54
+ */
55
+ async checkRateLimit(identifier, type) {
56
+ const key = this.getRateLimitKey(identifier, type);
57
+ const record = await this.storage.get(key);
58
+ const now = Date.now();
59
+ const windowStart = record?.windowStart ? new Date(record.windowStart).getTime() : now;
60
+ const windowEnd = windowStart + this.config.rateLimitWindow * 1e3;
61
+ if (now > windowEnd) {
62
+ return {
63
+ allowed: true,
64
+ remainingRequests: this.config.rateLimit - 1,
65
+ resetAt: new Date(now + this.config.rateLimitWindow * 1e3)
66
+ };
67
+ }
68
+ const currentCount = record?.count ?? 0;
69
+ if (currentCount >= this.config.rateLimit) {
70
+ const resetAt = new Date(windowEnd);
71
+ const minutesLeft = Math.ceil((windowEnd - now) / 6e4);
72
+ return {
73
+ allowed: false,
74
+ remainingRequests: 0,
75
+ resetAt,
76
+ message: `Too many OTP requests. Please try again in ${minutesLeft} minutes.`
77
+ };
78
+ }
79
+ return {
80
+ allowed: true,
81
+ remainingRequests: this.config.rateLimit - currentCount - 1,
82
+ resetAt: new Date(windowEnd)
83
+ };
84
+ }
85
+ /**
86
+ * Increment rate limit counter
87
+ */
88
+ async incrementRateLimit(identifier, type) {
89
+ const key = this.getRateLimitKey(identifier, type);
90
+ const record = await this.storage.get(key);
91
+ const now = Date.now();
92
+ const windowStart = record?.windowStart ? new Date(record.windowStart).getTime() : now;
93
+ const windowEnd = windowStart + this.config.rateLimitWindow * 1e3;
94
+ if (now > windowEnd) {
95
+ await this.storage.set(
96
+ key,
97
+ { count: 1, windowStart: new Date(now).toISOString() },
98
+ this.config.rateLimitWindow
99
+ );
100
+ return;
101
+ }
102
+ const newCount = (record?.count ?? 0) + 1;
103
+ const ttl = Math.ceil((windowEnd - now) / 1e3);
104
+ await this.storage.set(
105
+ key,
106
+ { count: newCount, windowStart: record?.windowStart ?? new Date(now).toISOString() },
107
+ ttl
108
+ );
109
+ }
110
+ /**
111
+ * Store OTP for verification
112
+ */
113
+ async store(identifier, type, options) {
114
+ const rateLimitCheck = await this.checkRateLimit(identifier, type);
115
+ if (!rateLimitCheck.allowed) {
116
+ return {
117
+ success: false,
118
+ error: rateLimitCheck.message,
119
+ remainingRequests: 0
120
+ };
121
+ }
122
+ const code = this.generateCode();
123
+ const expiresAt = new Date(Date.now() + this.config.expiresIn * 1e3);
124
+ const otpRecord = {
125
+ code,
126
+ identifier: type === "email" ? identifier.toLowerCase() : identifier,
127
+ type,
128
+ expiresAt: expiresAt.toISOString(),
129
+ attempts: 0,
130
+ maxAttempts: this.config.maxAttempts,
131
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
132
+ tenantId: options?.tenantId
133
+ };
134
+ const key = this.getOTPKey(identifier, type);
135
+ await this.storage.set(key, otpRecord, this.config.expiresIn);
136
+ await this.incrementRateLimit(identifier, type);
137
+ return {
138
+ success: true,
139
+ code,
140
+ expiresAt,
141
+ remainingRequests: rateLimitCheck.remainingRequests
142
+ };
143
+ }
144
+ /**
145
+ * Verify OTP code
146
+ */
147
+ async verify(identifier, type, code, options) {
148
+ if (options?.testUser) {
149
+ return {
150
+ success: true,
151
+ message: "OTP verified successfully (test user)."
152
+ };
153
+ }
154
+ const key = this.getOTPKey(identifier, type);
155
+ const record = await this.storage.get(key);
156
+ if (!record) {
157
+ return {
158
+ success: false,
159
+ message: `No OTP found for this ${type}. Please request a new one.`
160
+ };
161
+ }
162
+ if (/* @__PURE__ */ new Date() > new Date(record.expiresAt)) {
163
+ await this.storage.delete(key);
164
+ return {
165
+ success: false,
166
+ message: "OTP has expired. Please request a new one."
167
+ };
168
+ }
169
+ if (record.attempts >= record.maxAttempts) {
170
+ await this.storage.delete(key);
171
+ return {
172
+ success: false,
173
+ message: "Too many failed attempts. Please request a new OTP."
174
+ };
175
+ }
176
+ if (record.code !== code) {
177
+ record.attempts++;
178
+ const attemptsLeft = record.maxAttempts - record.attempts;
179
+ if (attemptsLeft <= 0) {
180
+ await this.storage.delete(key);
181
+ return {
182
+ success: false,
183
+ message: "Invalid OTP. Too many failed attempts."
184
+ };
185
+ }
186
+ const ttl = Math.ceil(
187
+ (new Date(record.expiresAt).getTime() - Date.now()) / 1e3
188
+ );
189
+ if (ttl > 0) {
190
+ await this.storage.set(key, record, ttl);
191
+ }
192
+ return {
193
+ success: false,
194
+ message: "Invalid OTP code.",
195
+ attemptsLeft
196
+ };
197
+ }
198
+ await this.storage.delete(key);
199
+ return {
200
+ success: true,
201
+ message: "OTP verified successfully."
202
+ };
203
+ }
204
+ /**
205
+ * Check if valid OTP exists
206
+ */
207
+ async hasValidOTP(identifier, type) {
208
+ const key = this.getOTPKey(identifier, type);
209
+ const record = await this.storage.get(key);
210
+ if (!record) return false;
211
+ if (/* @__PURE__ */ new Date() > new Date(record.expiresAt)) return false;
212
+ if (record.attempts >= record.maxAttempts) return false;
213
+ return true;
214
+ }
215
+ /**
216
+ * Get OTP info (for debugging)
217
+ */
218
+ async getInfo(identifier, type) {
219
+ const key = this.getOTPKey(identifier, type);
220
+ const record = await this.storage.get(key);
221
+ if (!record) {
222
+ return { exists: false };
223
+ }
224
+ return {
225
+ exists: true,
226
+ expiresAt: new Date(record.expiresAt),
227
+ attempts: record.attempts,
228
+ attemptsLeft: record.maxAttempts - record.attempts
229
+ };
230
+ }
231
+ /**
232
+ * Delete OTP (for testing/cleanup)
233
+ */
234
+ async delete(identifier, type) {
235
+ const key = this.getOTPKey(identifier, type);
236
+ await this.storage.delete(key);
237
+ }
238
+ /**
239
+ * Get rate limit info
240
+ */
241
+ async getRateLimitInfo(identifier, type) {
242
+ const key = this.getRateLimitKey(identifier, type);
243
+ const record = await this.storage.get(key);
244
+ const now = Date.now();
245
+ const windowStart = record?.windowStart ? new Date(record.windowStart).getTime() : now;
246
+ const windowEnd = windowStart + this.config.rateLimitWindow * 1e3;
247
+ const currentCount = record?.count ?? 0;
248
+ return {
249
+ requestsUsed: currentCount,
250
+ remainingRequests: Math.max(0, this.config.rateLimit - currentCount),
251
+ resetAt: new Date(windowEnd)
252
+ };
253
+ }
254
+ };
255
+ function createOTPManager(storage, config) {
256
+ return new OTPManager(storage, config);
257
+ }
258
+
259
+ // src/providers/otp/index.ts
260
+ var OTPProvider = class {
261
+ name = "otp";
262
+ type = "otp";
263
+ otpManager;
264
+ config;
265
+ emailSend;
266
+ smsSend;
267
+ _enabled;
268
+ constructor(storage, config) {
269
+ this.config = config;
270
+ this._enabled = config.enabled !== false;
271
+ const otpManagerConfig = {
272
+ length: config.email?.length ?? config.sms?.length ?? 6,
273
+ expiresIn: config.email?.expiresIn ?? config.sms?.expiresIn ?? 600,
274
+ maxAttempts: config.email?.maxAttempts ?? config.sms?.maxAttempts ?? 3,
275
+ rateLimit: config.email?.rateLimit ?? config.sms?.rateLimit ?? 5,
276
+ rateLimitWindow: config.email?.rateLimitWindow ?? config.sms?.rateLimitWindow ?? 900
277
+ };
278
+ this.otpManager = new OTPManager(storage, otpManagerConfig);
279
+ this.emailSend = config.email?.send;
280
+ this.smsSend = config.sms?.send;
281
+ }
282
+ get enabled() {
283
+ return this._enabled;
284
+ }
285
+ /**
286
+ * Request OTP (send to email or SMS)
287
+ */
288
+ async requestOTP(input) {
289
+ const { identifier, type, tenantId } = input;
290
+ if (type === "email" && this.config.email?.enabled === false) {
291
+ return {
292
+ success: false,
293
+ error: "Email OTP is not enabled"
294
+ };
295
+ }
296
+ if (type === "sms" && this.config.sms?.enabled !== true) {
297
+ return {
298
+ success: false,
299
+ error: "SMS OTP is not enabled"
300
+ };
301
+ }
302
+ const sendFn = type === "email" ? this.emailSend : this.smsSend;
303
+ if (!sendFn) {
304
+ return {
305
+ success: false,
306
+ error: `${type === "email" ? "Email" : "SMS"} send function not configured`
307
+ };
308
+ }
309
+ const result = await this.otpManager.store(identifier, type, { tenantId });
310
+ if (!result.success) {
311
+ return {
312
+ success: false,
313
+ error: result.error,
314
+ remainingRequests: result.remainingRequests
315
+ };
316
+ }
317
+ try {
318
+ await sendFn(identifier, result.code);
319
+ } catch (error) {
320
+ await this.otpManager.delete(identifier, type);
321
+ return {
322
+ success: false,
323
+ error: `Failed to send ${type === "email" ? "email" : "SMS"}: ${error instanceof Error ? error.message : "Unknown error"}`
324
+ };
325
+ }
326
+ return {
327
+ success: true,
328
+ expiresAt: result.expiresAt,
329
+ remainingRequests: result.remainingRequests
330
+ };
331
+ }
332
+ /**
333
+ * Verify OTP (implements AuthProvider.verify)
334
+ */
335
+ async verify(input) {
336
+ const { identifier, type, code } = input;
337
+ const result = await this.otpManager.verify(
338
+ identifier,
339
+ type,
340
+ code
341
+ );
342
+ return {
343
+ success: result.success,
344
+ attemptsLeft: result.attemptsLeft,
345
+ error: result.success ? void 0 : result.message
346
+ };
347
+ }
348
+ /**
349
+ * Authenticate with OTP (implements AuthProvider.authenticate)
350
+ * This verifies OTP but doesn't create session - that's done by auth engine
351
+ */
352
+ async authenticate(input) {
353
+ const { identifier, credential, data } = input;
354
+ if (!identifier || !credential) {
355
+ return {
356
+ success: false,
357
+ error: "Identifier and OTP code are required",
358
+ errorCode: "INVALID_INPUT"
359
+ };
360
+ }
361
+ const type = data?.["type"] ?? "email";
362
+ const verifyResult = await this.otpManager.verify(identifier, type, credential);
363
+ if (!verifyResult.success) {
364
+ return {
365
+ success: false,
366
+ error: verifyResult.message,
367
+ errorCode: "INVALID_OTP"
368
+ };
369
+ }
370
+ return {
371
+ success: true
372
+ };
373
+ }
374
+ /**
375
+ * Get provider info
376
+ */
377
+ getInfo() {
378
+ return {
379
+ name: this.name,
380
+ type: this.type,
381
+ enabled: this.enabled,
382
+ displayName: "One-Time Password"
383
+ };
384
+ }
385
+ /**
386
+ * Check if valid OTP exists
387
+ */
388
+ async hasValidOTP(identifier, type) {
389
+ return this.otpManager.hasValidOTP(identifier, type);
390
+ }
391
+ /**
392
+ * Get OTP info (for debugging)
393
+ */
394
+ async getOTPInfo(identifier, type) {
395
+ return this.otpManager.getInfo(identifier, type);
396
+ }
397
+ /**
398
+ * Get rate limit info
399
+ */
400
+ async getRateLimitInfo(identifier, type) {
401
+ return this.otpManager.getRateLimitInfo(identifier, type);
402
+ }
403
+ };
404
+ function createOTPProvider(storage, config) {
405
+ return new OTPProvider(storage, config);
406
+ }
407
+
408
+ export { OTPManager, OTPProvider, createOTPManager, createOTPProvider };
409
+ //# sourceMappingURL=chunk-IB4WUQDZ.js.map
410
+ //# sourceMappingURL=chunk-IB4WUQDZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/otp/otp-manager.ts","../src/providers/otp/index.ts"],"names":[],"mappings":"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","file":"chunk-IB4WUQDZ.js","sourcesContent":["/**\n * OTP Manager\n * Handles OTP generation, storage, and verification\n * Uses KVStorage interface for multi-runtime support\n */\n\nimport type { KVStorage } from '../../storage/types.js';\nimport { StorageKeys } from '../../storage/index.js';\n\n/**\n * OTP configuration\n */\nexport interface OTPConfig {\n /** OTP code length (default: 6) */\n length?: number;\n /** OTP expiry in seconds (default: 600 = 10 minutes) */\n expiresIn?: number;\n /** Maximum verification attempts (default: 3) */\n maxAttempts?: number;\n /** Rate limit: max requests per window (default: 5) */\n rateLimit?: number;\n /** Rate limit window in seconds (default: 900 = 15 minutes) */\n rateLimitWindow?: number;\n}\n\n/**\n * OTP record stored in KV\n */\nexport interface OTPRecord {\n /** OTP code */\n code: string;\n /** Target identifier (email/phone) */\n identifier: string;\n /** OTP type */\n type: 'email' | 'sms';\n /** Expiry timestamp (ISO string) */\n expiresAt: string;\n /** Failed verification attempts */\n attempts: number;\n /** Maximum allowed attempts */\n maxAttempts: number;\n /** Creation timestamp (ISO string) */\n createdAt: string;\n /** Tenant ID (for multi-tenant) */\n tenantId?: string;\n}\n\n/**\n * Rate limit record\n */\ninterface RateLimitRecord {\n count: number;\n windowStart: string;\n}\n\n/**\n * OTP storage result\n */\nexport interface StoreOTPResult {\n success: boolean;\n code?: string;\n expiresAt?: Date;\n error?: string;\n remainingRequests?: number;\n}\n\n/**\n * OTP verification result\n */\nexport interface VerifyOTPResult {\n success: boolean;\n message: string;\n attemptsLeft?: number;\n}\n\n/**\n * Rate limit check result\n */\nexport interface RateLimitCheck {\n allowed: boolean;\n remainingRequests: number;\n resetAt: Date;\n message?: string;\n}\n\n/**\n * Generate cryptographically secure random integer\n */\nfunction secureRandomInt(min: number, max: number): number {\n const range = max - min;\n const bytesNeeded = Math.ceil(Math.log2(range) / 8);\n const maxValid = Math.floor(256 ** bytesNeeded / range) * range - 1;\n\n let randomValue: number;\n const randomBytes = new Uint8Array(bytesNeeded);\n\n do {\n crypto.getRandomValues(randomBytes);\n randomValue = randomBytes.reduce((acc, byte, i) => acc + byte * 256 ** i, 0);\n } while (randomValue > maxValid);\n\n return min + (randomValue % range);\n}\n\n/**\n * OTP Manager class\n */\nexport class OTPManager {\n private storage: KVStorage;\n private config: Required<OTPConfig>;\n\n constructor(storage: KVStorage, config?: OTPConfig) {\n this.storage = storage;\n this.config = {\n length: config?.length ?? 6,\n expiresIn: config?.expiresIn ?? 600,\n maxAttempts: config?.maxAttempts ?? 3,\n rateLimit: config?.rateLimit ?? 5,\n rateLimitWindow: config?.rateLimitWindow ?? 900,\n };\n }\n\n /**\n * Generate OTP code\n */\n generateCode(length?: number): string {\n const len = length ?? this.config.length;\n const min = Math.pow(10, len - 1);\n const max = Math.pow(10, len) - 1;\n return secureRandomInt(min, max + 1).toString().padStart(len, '0');\n }\n\n /**\n * Get storage key for OTP\n */\n private getOTPKey(identifier: string, type: 'email' | 'sms'): string {\n const normalizedId = type === 'email' ? identifier.toLowerCase() : identifier;\n return StorageKeys.otp(normalizedId, type);\n }\n\n /**\n * Get storage key for rate limiting\n */\n private getRateLimitKey(identifier: string, type: 'email' | 'sms'): string {\n const normalizedId = type === 'email' ? identifier.toLowerCase() : identifier;\n return StorageKeys.rateLimit(`otp:${type}:${normalizedId}`);\n }\n\n /**\n * Check rate limit for OTP requests\n */\n async checkRateLimit(\n identifier: string,\n type: 'email' | 'sms'\n ): Promise<RateLimitCheck> {\n const key = this.getRateLimitKey(identifier, type);\n const record = await this.storage.get<RateLimitRecord>(key);\n\n const now = Date.now();\n const windowStart = record?.windowStart\n ? new Date(record.windowStart).getTime()\n : now;\n const windowEnd = windowStart + this.config.rateLimitWindow * 1000;\n\n // Window expired, reset\n if (now > windowEnd) {\n return {\n allowed: true,\n remainingRequests: this.config.rateLimit - 1,\n resetAt: new Date(now + this.config.rateLimitWindow * 1000),\n };\n }\n\n const currentCount = record?.count ?? 0;\n\n if (currentCount >= this.config.rateLimit) {\n const resetAt = new Date(windowEnd);\n const minutesLeft = Math.ceil((windowEnd - now) / 60000);\n return {\n allowed: false,\n remainingRequests: 0,\n resetAt,\n message: `Too many OTP requests. Please try again in ${minutesLeft} minutes.`,\n };\n }\n\n return {\n allowed: true,\n remainingRequests: this.config.rateLimit - currentCount - 1,\n resetAt: new Date(windowEnd),\n };\n }\n\n /**\n * Increment rate limit counter\n */\n private async incrementRateLimit(\n identifier: string,\n type: 'email' | 'sms'\n ): Promise<void> {\n const key = this.getRateLimitKey(identifier, type);\n const record = await this.storage.get<RateLimitRecord>(key);\n\n const now = Date.now();\n const windowStart = record?.windowStart\n ? new Date(record.windowStart).getTime()\n : now;\n const windowEnd = windowStart + this.config.rateLimitWindow * 1000;\n\n // Window expired, start new one\n if (now > windowEnd) {\n await this.storage.set<RateLimitRecord>(\n key,\n { count: 1, windowStart: new Date(now).toISOString() },\n this.config.rateLimitWindow\n );\n return;\n }\n\n // Increment existing window\n const newCount = (record?.count ?? 0) + 1;\n const ttl = Math.ceil((windowEnd - now) / 1000);\n\n await this.storage.set<RateLimitRecord>(\n key,\n { count: newCount, windowStart: record?.windowStart ?? new Date(now).toISOString() },\n ttl\n );\n }\n\n /**\n * Store OTP for verification\n */\n async store(\n identifier: string,\n type: 'email' | 'sms',\n options?: { tenantId?: string; testMode?: boolean }\n ): Promise<StoreOTPResult> {\n // Check rate limit\n const rateLimitCheck = await this.checkRateLimit(identifier, type);\n if (!rateLimitCheck.allowed) {\n return {\n success: false,\n error: rateLimitCheck.message,\n remainingRequests: 0,\n };\n }\n\n const code = this.generateCode();\n const expiresAt = new Date(Date.now() + this.config.expiresIn * 1000);\n\n const otpRecord: OTPRecord = {\n code,\n identifier: type === 'email' ? identifier.toLowerCase() : identifier,\n type,\n expiresAt: expiresAt.toISOString(),\n attempts: 0,\n maxAttempts: this.config.maxAttempts,\n createdAt: new Date().toISOString(),\n tenantId: options?.tenantId,\n };\n\n const key = this.getOTPKey(identifier, type);\n await this.storage.set(key, otpRecord, this.config.expiresIn);\n\n // Increment rate limit\n await this.incrementRateLimit(identifier, type);\n\n return {\n success: true,\n code,\n expiresAt,\n remainingRequests: rateLimitCheck.remainingRequests,\n };\n }\n\n /**\n * Verify OTP code\n */\n async verify(\n identifier: string,\n type: 'email' | 'sms',\n code: string,\n options?: { testUser?: boolean }\n ): Promise<VerifyOTPResult> {\n // Test user bypass\n if (options?.testUser) {\n return {\n success: true,\n message: 'OTP verified successfully (test user).',\n };\n }\n\n const key = this.getOTPKey(identifier, type);\n const record = await this.storage.get<OTPRecord>(key);\n\n if (!record) {\n return {\n success: false,\n message: `No OTP found for this ${type}. Please request a new one.`,\n };\n }\n\n // Check expiry\n if (new Date() > new Date(record.expiresAt)) {\n await this.storage.delete(key);\n return {\n success: false,\n message: 'OTP has expired. Please request a new one.',\n };\n }\n\n // Check attempts\n if (record.attempts >= record.maxAttempts) {\n await this.storage.delete(key);\n return {\n success: false,\n message: 'Too many failed attempts. Please request a new OTP.',\n };\n }\n\n // Verify code\n if (record.code !== code) {\n record.attempts++;\n const attemptsLeft = record.maxAttempts - record.attempts;\n\n if (attemptsLeft <= 0) {\n await this.storage.delete(key);\n return {\n success: false,\n message: 'Invalid OTP. Too many failed attempts.',\n };\n }\n\n // Update attempts count\n const ttl = Math.ceil(\n (new Date(record.expiresAt).getTime() - Date.now()) / 1000\n );\n if (ttl > 0) {\n await this.storage.set(key, record, ttl);\n }\n\n return {\n success: false,\n message: 'Invalid OTP code.',\n attemptsLeft,\n };\n }\n\n // Success - remove OTP\n await this.storage.delete(key);\n\n return {\n success: true,\n message: 'OTP verified successfully.',\n };\n }\n\n /**\n * Check if valid OTP exists\n */\n async hasValidOTP(identifier: string, type: 'email' | 'sms'): Promise<boolean> {\n const key = this.getOTPKey(identifier, type);\n const record = await this.storage.get<OTPRecord>(key);\n\n if (!record) return false;\n if (new Date() > new Date(record.expiresAt)) return false;\n if (record.attempts >= record.maxAttempts) return false;\n\n return true;\n }\n\n /**\n * Get OTP info (for debugging)\n */\n async getInfo(\n identifier: string,\n type: 'email' | 'sms'\n ): Promise<{\n exists: boolean;\n expiresAt?: Date;\n attempts?: number;\n attemptsLeft?: number;\n }> {\n const key = this.getOTPKey(identifier, type);\n const record = await this.storage.get<OTPRecord>(key);\n\n if (!record) {\n return { exists: false };\n }\n\n return {\n exists: true,\n expiresAt: new Date(record.expiresAt),\n attempts: record.attempts,\n attemptsLeft: record.maxAttempts - record.attempts,\n };\n }\n\n /**\n * Delete OTP (for testing/cleanup)\n */\n async delete(identifier: string, type: 'email' | 'sms'): Promise<void> {\n const key = this.getOTPKey(identifier, type);\n await this.storage.delete(key);\n }\n\n /**\n * Get rate limit info\n */\n async getRateLimitInfo(\n identifier: string,\n type: 'email' | 'sms'\n ): Promise<{\n requestsUsed: number;\n remainingRequests: number;\n resetAt: Date;\n }> {\n const key = this.getRateLimitKey(identifier, type);\n const record = await this.storage.get<RateLimitRecord>(key);\n\n const now = Date.now();\n const windowStart = record?.windowStart\n ? new Date(record.windowStart).getTime()\n : now;\n const windowEnd = windowStart + this.config.rateLimitWindow * 1000;\n\n const currentCount = record?.count ?? 0;\n\n return {\n requestsUsed: currentCount,\n remainingRequests: Math.max(0, this.config.rateLimit - currentCount),\n resetAt: new Date(windowEnd),\n };\n }\n}\n\n/**\n * Create OTP manager instance\n */\nexport function createOTPManager(\n storage: KVStorage,\n config?: OTPConfig\n): OTPManager {\n return new OTPManager(storage, config);\n}\n","/**\n * OTP Authentication Provider\n * Email and SMS one-time password authentication\n */\n\nimport type { KVStorage } from '../../storage/types.js';\nimport type { OtpConfig } from '../../config.js';\nimport type {\n AuthProvider,\n AuthInput,\n AuthResult,\n VerifyInput,\n VerifyResult,\n ProviderInfo,\n} from '../base.js';\nimport { OTPManager, type OTPConfig as OTPManagerConfig } from './otp-manager.js';\n\n// Re-export OTP manager\nexport { OTPManager, createOTPManager, type OTPConfig } from './otp-manager.js';\n\n/**\n * OTP request input\n */\nexport interface RequestOTPInput {\n /** Email or phone number */\n identifier: string;\n /** OTP type */\n type: 'email' | 'sms';\n /** Tenant ID (for multi-tenant) */\n tenantId?: string;\n}\n\n/**\n * OTP request result\n */\nexport interface RequestOTPResult {\n success: boolean;\n expiresAt?: Date;\n error?: string;\n remainingRequests?: number;\n}\n\n/**\n * OTP Provider\n */\nexport class OTPProvider implements AuthProvider {\n readonly name = 'otp';\n readonly type = 'otp' as const;\n\n private otpManager: OTPManager;\n private config: OtpConfig;\n private emailSend?: (to: string, code: string) => Promise<void>;\n private smsSend?: (to: string, code: string) => Promise<void>;\n private _enabled: boolean;\n\n constructor(\n storage: KVStorage,\n config: OtpConfig\n ) {\n this.config = config;\n this._enabled = config.enabled !== false;\n\n // Create OTP manager with email config (primary)\n const otpManagerConfig: OTPManagerConfig = {\n length: config.email?.length ?? config.sms?.length ?? 6,\n expiresIn: config.email?.expiresIn ?? config.sms?.expiresIn ?? 600,\n maxAttempts: config.email?.maxAttempts ?? config.sms?.maxAttempts ?? 3,\n rateLimit: config.email?.rateLimit ?? config.sms?.rateLimit ?? 5,\n rateLimitWindow: config.email?.rateLimitWindow ?? config.sms?.rateLimitWindow ?? 900,\n };\n\n this.otpManager = new OTPManager(storage, otpManagerConfig);\n this.emailSend = config.email?.send;\n this.smsSend = config.sms?.send;\n }\n\n get enabled(): boolean {\n return this._enabled;\n }\n\n /**\n * Request OTP (send to email or SMS)\n */\n async requestOTP(input: RequestOTPInput): Promise<RequestOTPResult> {\n const { identifier, type, tenantId } = input;\n\n // Check if type is enabled\n if (type === 'email' && this.config.email?.enabled === false) {\n return {\n success: false,\n error: 'Email OTP is not enabled',\n };\n }\n\n if (type === 'sms' && this.config.sms?.enabled !== true) {\n return {\n success: false,\n error: 'SMS OTP is not enabled',\n };\n }\n\n // Get send function\n const sendFn = type === 'email' ? this.emailSend : this.smsSend;\n if (!sendFn) {\n return {\n success: false,\n error: `${type === 'email' ? 'Email' : 'SMS'} send function not configured`,\n };\n }\n\n // Store OTP\n const result = await this.otpManager.store(identifier, type, { tenantId });\n\n if (!result.success) {\n return {\n success: false,\n error: result.error,\n remainingRequests: result.remainingRequests,\n };\n }\n\n // Send OTP\n try {\n await sendFn(identifier, result.code!);\n } catch (error) {\n // Delete OTP if send fails\n await this.otpManager.delete(identifier, type);\n return {\n success: false,\n error: `Failed to send ${type === 'email' ? 'email' : 'SMS'}: ${\n error instanceof Error ? error.message : 'Unknown error'\n }`,\n };\n }\n\n return {\n success: true,\n expiresAt: result.expiresAt,\n remainingRequests: result.remainingRequests,\n };\n }\n\n /**\n * Verify OTP (implements AuthProvider.verify)\n */\n async verify(input: VerifyInput): Promise<VerifyResult> {\n const { identifier, type, code } = input;\n\n const result = await this.otpManager.verify(\n identifier,\n type as 'email' | 'sms',\n code\n );\n\n return {\n success: result.success,\n attemptsLeft: result.attemptsLeft,\n error: result.success ? undefined : result.message,\n };\n }\n\n /**\n * Authenticate with OTP (implements AuthProvider.authenticate)\n * This verifies OTP but doesn't create session - that's done by auth engine\n */\n async authenticate(input: AuthInput): Promise<AuthResult> {\n const { identifier, credential, data } = input;\n\n if (!identifier || !credential) {\n return {\n success: false,\n error: 'Identifier and OTP code are required',\n errorCode: 'INVALID_INPUT',\n };\n }\n\n const type = (data?.['type'] as 'email' | 'sms') ?? 'email';\n\n const verifyResult = await this.otpManager.verify(identifier, type, credential);\n\n if (!verifyResult.success) {\n return {\n success: false,\n error: verifyResult.message,\n errorCode: 'INVALID_OTP',\n };\n }\n\n // Return success - auth engine will handle user lookup/creation and session\n return {\n success: true,\n };\n }\n\n /**\n * Get provider info\n */\n getInfo(): ProviderInfo {\n return {\n name: this.name,\n type: this.type,\n enabled: this.enabled,\n displayName: 'One-Time Password',\n };\n }\n\n /**\n * Check if valid OTP exists\n */\n async hasValidOTP(identifier: string, type: 'email' | 'sms'): Promise<boolean> {\n return this.otpManager.hasValidOTP(identifier, type);\n }\n\n /**\n * Get OTP info (for debugging)\n */\n async getOTPInfo(identifier: string, type: 'email' | 'sms') {\n return this.otpManager.getInfo(identifier, type);\n }\n\n /**\n * Get rate limit info\n */\n async getRateLimitInfo(identifier: string, type: 'email' | 'sms') {\n return this.otpManager.getRateLimitInfo(identifier, type);\n }\n}\n\n/**\n * Create OTP provider\n */\nexport function createOTPProvider(\n storage: KVStorage,\n config: OtpConfig\n): OTPProvider {\n return new OTPProvider(storage, config);\n}\n"]}