@revealui/security 0.0.1-pre.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.
package/dist/index.js ADDED
@@ -0,0 +1,1841 @@
1
+ // src/SecurityValidation.ts
2
+ import { z } from "zod";
3
+ var EnvironmentSchema = z.object({
4
+ PAYLOAD_SECRET: z.string().min(32, "PAYLOAD_SECRET must be at least 32 characters"),
5
+ MCP_ENCRYPTION_KEY: z.string().min(64, "MCP_ENCRYPTION_KEY must be at least 64 characters"),
6
+ TURSO_URL: z.string().url().optional(),
7
+ TURSO_AUTH_TOKEN: z.string().optional(),
8
+ SUPPORT_EMAIL: z.string().email().optional()
9
+ });
10
+ var RATE_LIMITS = {
11
+ CHAT: { windowMs: 60 * 1e3, maxRequests: 10 },
12
+ // 10 requests per minute
13
+ CONTENT: { windowMs: 60 * 1e3, maxRequests: 5 },
14
+ // 5 requests per minute
15
+ CONTACT: { windowMs: 15 * 60 * 1e3, maxRequests: 3 },
16
+ // 3 requests per 15 minutes
17
+ RECOMMENDATIONS: { windowMs: 60 * 1e3, maxRequests: 20 }
18
+ // 20 requests per minute
19
+ };
20
+ var rateLimitStore = /* @__PURE__ */ new Map();
21
+ async function validateEnvironment() {
22
+ const validation = EnvironmentSchema.safeParse(process.env);
23
+ if (!validation.success) {
24
+ const errors = validation.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(", ");
25
+ throw new Error(`Environment validation failed: ${errors}`);
26
+ }
27
+ }
28
+ async function checkRateLimit(key, config) {
29
+ const now = Date.now();
30
+ for (const [k, v] of rateLimitStore.entries()) {
31
+ if (v.resetTime < now) {
32
+ rateLimitStore.delete(k);
33
+ }
34
+ }
35
+ const entry = rateLimitStore.get(key);
36
+ if (!entry || entry.resetTime < now) {
37
+ rateLimitStore.set(key, { count: 1, resetTime: now + config.windowMs });
38
+ return {
39
+ allowed: true,
40
+ remaining: config.maxRequests - 1,
41
+ resetTime: now + config.windowMs
42
+ };
43
+ }
44
+ if (entry.count >= config.maxRequests) {
45
+ return {
46
+ allowed: false,
47
+ remaining: 0,
48
+ resetTime: entry.resetTime
49
+ };
50
+ }
51
+ entry.count++;
52
+ rateLimitStore.set(key, entry);
53
+ return {
54
+ allowed: true,
55
+ remaining: config.maxRequests - entry.count,
56
+ resetTime: entry.resetTime
57
+ };
58
+ }
59
+ function generateRateLimitKey(userId, action) {
60
+ return `rate_limit:${action}:${userId}`;
61
+ }
62
+ function generateIPRateLimitKey(ip, action) {
63
+ return `rate_limit:${action}:ip:${ip}`;
64
+ }
65
+ async function validateCSRFToken(token, sessionToken) {
66
+ return token === sessionToken && token.length > 0;
67
+ }
68
+ function sanitizeInput(input) {
69
+ return input.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/on\w+\s*=\s*["'][^"']*["']/gi, "").replace(/javascript:/gi, "").trim();
70
+ }
71
+ function validateAndSanitizeInput(input, schema) {
72
+ try {
73
+ const validation = schema.safeParse(input);
74
+ if (!validation.success) {
75
+ return {
76
+ success: false,
77
+ error: validation.error.issues.map((i) => i.message).join(", ")
78
+ };
79
+ }
80
+ const sanitized = sanitizeObject(validation.data);
81
+ return {
82
+ success: true,
83
+ data: sanitized
84
+ };
85
+ } catch (error) {
86
+ return {
87
+ success: false,
88
+ error: error instanceof Error ? error.message : "Validation failed"
89
+ };
90
+ }
91
+ }
92
+ function sanitizeObject(obj) {
93
+ if (typeof obj === "string") {
94
+ return sanitizeInput(obj);
95
+ }
96
+ if (Array.isArray(obj)) {
97
+ return obj.map(sanitizeObject);
98
+ }
99
+ if (obj && typeof obj === "object") {
100
+ const sanitized = {};
101
+ for (const [key, value] of Object.entries(obj)) {
102
+ sanitized[key] = sanitizeObject(value);
103
+ }
104
+ return sanitized;
105
+ }
106
+ return obj;
107
+ }
108
+ var SECURITY_HEADERS = {
109
+ "X-Content-Type-Options": "nosniff",
110
+ "X-Frame-Options": "DENY",
111
+ "X-XSS-Protection": "1; mode=block",
112
+ "Referrer-Policy": "strict-origin-when-cross-origin",
113
+ "Permissions-Policy": "camera=(), microphone=(), geolocation=()"
114
+ };
115
+ function logSecurityEvent(event, details, severity = "medium") {
116
+ console.log(`[SECURITY-${severity.toUpperCase()}] ${event}:`, {
117
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
118
+ severity,
119
+ ...details
120
+ });
121
+ }
122
+
123
+ // src/RateLimiterService.ts
124
+ var devLogger = {
125
+ debug: (...args) => console.debug("[security]", ...args),
126
+ info: (...args) => console.log("[security]", ...args),
127
+ warn: (...args) => console.warn("[security]", ...args),
128
+ error: (...args) => console.error("[security]", ...args),
129
+ forService: (_name) => ({
130
+ debug: (...args) => console.debug("[security]", ...args),
131
+ info: (...args) => console.log("[security]", ...args),
132
+ warn: (...args) => console.warn("[security]", ...args),
133
+ error: (...args) => console.error("[security]", ...args)
134
+ })
135
+ };
136
+ var RateLimiterService = class {
137
+ config;
138
+ inMemoryStore = /* @__PURE__ */ new Map();
139
+ cleanupInterval = null;
140
+ constructor(config) {
141
+ this.config = {
142
+ windowMs: config.windowMs ?? 15 * 60 * 1e3,
143
+ // 15 minutes
144
+ maxRequests: config.maxRequests ?? 100,
145
+ skipSuccessfulRequests: config.skipSuccessfulRequests ?? false,
146
+ skipFailedRequests: config.skipFailedRequests ?? false,
147
+ standardHeaders: config.standardHeaders ?? true,
148
+ legacyHeaders: config.legacyHeaders ?? false
149
+ };
150
+ this.startCleanupInterval();
151
+ }
152
+ setPayload(_payload) {
153
+ }
154
+ /**
155
+ * Check rate limit using token bucket algorithm
156
+ */
157
+ checkTokenBucket(key, tokens = 1, capacity = 10, refillRate = 1, refillTime = 100) {
158
+ try {
159
+ const now = Date.now();
160
+ const bucketKey = `rate_limit:token_bucket:${key}`;
161
+ const current = this.inMemoryStore.get(bucketKey);
162
+ let currentTokens = capacity;
163
+ let lastRefill = now;
164
+ if (current) {
165
+ currentTokens = current.count;
166
+ lastRefill = current.lastRequestTime;
167
+ }
168
+ const timePassed = now - lastRefill;
169
+ const refillAmount = Math.floor(timePassed / refillTime) * refillRate;
170
+ currentTokens = Math.min(capacity, currentTokens + refillAmount);
171
+ if (currentTokens >= tokens) {
172
+ currentTokens = currentTokens - tokens;
173
+ this.inMemoryStore.set(bucketKey, {
174
+ count: currentTokens,
175
+ resetTime: now + refillTime,
176
+ lastRequestTime: now
177
+ });
178
+ devLogger.debug("Rate limit check", {
179
+ key,
180
+ algorithm: "token_bucket",
181
+ allowed: true,
182
+ remaining: currentTokens,
183
+ resetTime: now + refillTime,
184
+ tokens,
185
+ capacity,
186
+ refillRate
187
+ });
188
+ return {
189
+ allowed: true,
190
+ remaining: currentTokens,
191
+ resetTime: now + refillTime
192
+ };
193
+ }
194
+ const resetTime = lastRefill + refillTime;
195
+ devLogger.warn("Rate limit blocked", {
196
+ key,
197
+ algorithm: "token_bucket",
198
+ remaining: currentTokens,
199
+ resetTime,
200
+ tokens
201
+ });
202
+ return {
203
+ allowed: false,
204
+ remaining: currentTokens,
205
+ resetTime,
206
+ retryAfter: Math.ceil((resetTime - now) / 1e3)
207
+ };
208
+ } catch (error) {
209
+ devLogger.error("Rate limiter error", error instanceof Error ? error : void 0, {
210
+ key,
211
+ algorithm: "token_bucket",
212
+ error: error instanceof Error ? error.message : "Unknown error"
213
+ });
214
+ return {
215
+ allowed: true,
216
+ remaining: capacity,
217
+ resetTime: Date.now() + refillTime
218
+ };
219
+ }
220
+ }
221
+ /**
222
+ * Check rate limit using sliding window algorithm
223
+ */
224
+ checkSlidingWindow(key, windowMs = this.config.windowMs, maxRequests = this.config.maxRequests) {
225
+ try {
226
+ const now = Date.now();
227
+ const windowKey = `rate_limit:sliding_window:${key}`;
228
+ const current = this.inMemoryStore.get(windowKey);
229
+ let requests = [];
230
+ if (current) {
231
+ requests = [current.count];
232
+ }
233
+ const windowStart = now - windowMs;
234
+ requests = requests.filter((timestamp) => timestamp >= windowStart);
235
+ if (requests.length < maxRequests) {
236
+ requests.push(now);
237
+ this.inMemoryStore.set(windowKey, {
238
+ count: requests.length,
239
+ resetTime: now + windowMs,
240
+ lastRequestTime: now
241
+ });
242
+ devLogger.debug("Rate limit check", {
243
+ key,
244
+ algorithm: "sliding_window",
245
+ allowed: true,
246
+ remaining: maxRequests - requests.length,
247
+ resetTime: now + windowMs
248
+ });
249
+ return {
250
+ allowed: true,
251
+ remaining: maxRequests - requests.length,
252
+ resetTime: now + windowMs
253
+ };
254
+ }
255
+ const oldest = Math.min(...requests);
256
+ const resetTime = oldest + windowMs;
257
+ devLogger.warn("Rate limit blocked", {
258
+ key,
259
+ algorithm: "sliding_window",
260
+ remaining: 0,
261
+ resetTime
262
+ });
263
+ return {
264
+ allowed: false,
265
+ remaining: 0,
266
+ resetTime,
267
+ retryAfter: Math.ceil((resetTime - now) / 1e3)
268
+ };
269
+ } catch (error) {
270
+ devLogger.error("Rate limiter error", error instanceof Error ? error : void 0, {
271
+ key,
272
+ algorithm: "sliding_window",
273
+ error: error instanceof Error ? error.message : "Unknown error"
274
+ });
275
+ return {
276
+ allowed: true,
277
+ remaining: maxRequests,
278
+ resetTime: Date.now() + windowMs
279
+ };
280
+ }
281
+ }
282
+ /**
283
+ * Check rate limit using leaky bucket algorithm
284
+ */
285
+ checkLeakyBucket(key, capacity = 10, leakRate = 1, leakTime = 100) {
286
+ try {
287
+ const now = Date.now();
288
+ const bucketKey = `rate_limit:leaky_bucket:${key}`;
289
+ const current = this.inMemoryStore.get(bucketKey);
290
+ let currentLevel = 0;
291
+ let lastLeak = now;
292
+ if (current) {
293
+ currentLevel = current.count;
294
+ lastLeak = current.lastRequestTime;
295
+ }
296
+ const timePassed = now - lastLeak;
297
+ const leakAmount = Math.floor(timePassed / leakTime) * leakRate;
298
+ currentLevel = Math.max(0, currentLevel - leakAmount);
299
+ if (currentLevel < capacity) {
300
+ currentLevel++;
301
+ this.inMemoryStore.set(bucketKey, {
302
+ count: currentLevel,
303
+ resetTime: now + leakTime,
304
+ lastRequestTime: now
305
+ });
306
+ devLogger.debug("Rate limit check", {
307
+ key,
308
+ algorithm: "leaky_bucket",
309
+ allowed: true,
310
+ remaining: capacity - currentLevel,
311
+ resetTime: now + leakTime
312
+ });
313
+ return {
314
+ allowed: true,
315
+ remaining: capacity - currentLevel,
316
+ resetTime: now + leakTime
317
+ };
318
+ }
319
+ const resetTime = lastLeak + leakTime;
320
+ devLogger.warn("Rate limit blocked", {
321
+ key,
322
+ algorithm: "leaky_bucket",
323
+ remaining: 0,
324
+ resetTime
325
+ });
326
+ return {
327
+ allowed: false,
328
+ remaining: 0,
329
+ resetTime,
330
+ retryAfter: Math.ceil((resetTime - now) / 1e3)
331
+ };
332
+ } catch (error) {
333
+ devLogger.error("Rate limiter error", error instanceof Error ? error : void 0, {
334
+ key,
335
+ algorithm: "leaky_bucket",
336
+ error: error instanceof Error ? error.message : "Unknown error"
337
+ });
338
+ return {
339
+ allowed: true,
340
+ remaining: capacity,
341
+ resetTime: Date.now() + leakTime
342
+ };
343
+ }
344
+ }
345
+ /**
346
+ * Reset rate limit for a key
347
+ */
348
+ resetRateLimit(key) {
349
+ const patterns = [
350
+ `rate_limit:token_bucket:${key}`,
351
+ `rate_limit:sliding_window:${key}`,
352
+ `rate_limit:leaky_bucket:${key}`
353
+ ];
354
+ for (const pattern of patterns) {
355
+ this.inMemoryStore.delete(pattern);
356
+ }
357
+ devLogger.debug("Rate limit reset", { key });
358
+ }
359
+ /**
360
+ * Get current rate limit state
361
+ */
362
+ getRateLimitState(key) {
363
+ const patterns = [
364
+ `rate_limit:token_bucket:${key}`,
365
+ `rate_limit:sliding_window:${key}`,
366
+ `rate_limit:leaky_bucket:${key}`
367
+ ];
368
+ for (const pattern of patterns) {
369
+ const state = this.inMemoryStore.get(pattern);
370
+ if (state) {
371
+ return state;
372
+ }
373
+ }
374
+ return null;
375
+ }
376
+ /**
377
+ * Update rate limit state
378
+ */
379
+ updateRateLimitState(key, state) {
380
+ this.inMemoryStore.set(key, state);
381
+ }
382
+ /**
383
+ * Get rate limiter statistics
384
+ */
385
+ getStats() {
386
+ return {
387
+ totalKeys: this.inMemoryStore.size,
388
+ memoryUsage: this.inMemoryStore.size
389
+ };
390
+ }
391
+ /**
392
+ * Clean up expired entries
393
+ */
394
+ cleanup() {
395
+ const now = Date.now();
396
+ const maxAge = 24 * 60 * 60 * 1e3;
397
+ for (const [key, state] of this.inMemoryStore.entries()) {
398
+ if (now - state.lastRequestTime > maxAge) {
399
+ this.inMemoryStore.delete(key);
400
+ }
401
+ }
402
+ devLogger.debug("Cleaned up expired rate limit entries", {
403
+ remaining: this.inMemoryStore.size
404
+ });
405
+ }
406
+ /**
407
+ * Start cleanup interval
408
+ */
409
+ startCleanupInterval() {
410
+ if (this.cleanupInterval) {
411
+ clearInterval(this.cleanupInterval);
412
+ }
413
+ this.cleanupInterval = setInterval(
414
+ () => {
415
+ this.cleanup();
416
+ },
417
+ 60 * 60 * 1e3
418
+ );
419
+ }
420
+ /**
421
+ * Stop cleanup interval
422
+ */
423
+ stopCleanupInterval() {
424
+ if (this.cleanupInterval) {
425
+ clearInterval(this.cleanupInterval);
426
+ this.cleanupInterval = null;
427
+ }
428
+ }
429
+ /**
430
+ * Dispose of the service
431
+ */
432
+ dispose() {
433
+ this.stopCleanupInterval();
434
+ this.inMemoryStore.clear();
435
+ }
436
+ };
437
+ var getRateLimiterService = (config) => {
438
+ return new RateLimiterService(config ?? {});
439
+ };
440
+
441
+ // src/RateLimitingService.ts
442
+ import { headers } from "next/headers";
443
+ var RATE_LIMITS2 = { default: { limit: 100, windowMs: 6e4 } };
444
+ var generateRateLimitKey2 = (id) => `rate:${id}`;
445
+ var generateIPRateLimitKey2 = (ip) => `rate:ip:${ip}`;
446
+ async function checkRateLimit2(_key, _limit, _windowMs) {
447
+ return { allowed: true, remaining: _limit, resetTime: Date.now() + _windowMs };
448
+ }
449
+ async function withRateLimit(action, config) {
450
+ return async (...args) => {
451
+ const headersList = await headers();
452
+ const ip = headersList.get("x-forwarded-for") ?? headersList.get("x-real-ip") ?? "unknown";
453
+ const key = config.customKey ?? (config.userId ? generateRateLimitKey2(config.userId) : generateIPRateLimitKey2(ip));
454
+ const rateLimitResult = await checkRateLimit2(
455
+ key,
456
+ RATE_LIMITS2[config.type].limit,
457
+ RATE_LIMITS2[config.type].windowMs
458
+ );
459
+ if (!rateLimitResult.allowed) {
460
+ throw new Error(
461
+ `Rate limit exceeded. Try again in ${Math.ceil((rateLimitResult.resetTime - Date.now()) / 1e3)} seconds.`
462
+ );
463
+ }
464
+ return await action(...args);
465
+ };
466
+ }
467
+ function RateLimited(type, userId) {
468
+ return (_target, _propertyKey, descriptor) => {
469
+ const originalMethod = descriptor.value;
470
+ descriptor.value = async function(...args) {
471
+ const headersList = await headers();
472
+ const ip = headersList.get("x-forwarded-for") ?? headersList.get("x-real-ip") ?? "unknown";
473
+ const key = userId ? generateRateLimitKey2(userId) : generateIPRateLimitKey2(ip);
474
+ const rateLimitResult = await checkRateLimit2(
475
+ key,
476
+ RATE_LIMITS2[type].limit,
477
+ RATE_LIMITS2[type].windowMs
478
+ );
479
+ if (!rateLimitResult.allowed) {
480
+ throw new Error(
481
+ `Rate limit exceeded. Try again in ${Math.ceil((rateLimitResult.resetTime - Date.now()) / 1e3)} seconds.`
482
+ );
483
+ }
484
+ return await originalMethod.apply(this, args);
485
+ };
486
+ return descriptor;
487
+ };
488
+ }
489
+ async function getClientIP() {
490
+ const headersList = await headers();
491
+ return headersList.get("x-forwarded-for") ?? headersList.get("x-real-ip") ?? "unknown";
492
+ }
493
+ async function isTrustedSource() {
494
+ const headersList = await headers();
495
+ const userAgent = headersList.get("user-agent") || "";
496
+ const origin = headersList.get("origin") || "";
497
+ const botPatterns = [/bot/i, /crawler/i, /spider/i, /scraper/i, /curl/i, /wget/i];
498
+ const isBot = botPatterns.some((pattern) => pattern.test(userAgent));
499
+ const trustedOrigins = [
500
+ process.env.NEXT_PUBLIC_APP_URL,
501
+ "http://localhost:3000",
502
+ "https://localhost:3000"
503
+ ].filter(Boolean);
504
+ const isTrustedOrigin = trustedOrigins.includes(origin);
505
+ return !isBot && isTrustedOrigin;
506
+ }
507
+ async function checkUserRateLimit(userId, action, customConfig) {
508
+ const config = { ...RATE_LIMITS2[action], ...customConfig };
509
+ const key = generateRateLimitKey2(userId);
510
+ return await checkRateLimit2(key, config.limit, config.windowMs);
511
+ }
512
+ async function checkAnonymousRateLimit(action, customConfig) {
513
+ const ip = await getClientIP();
514
+ const config = { ...RATE_LIMITS2[action], ...customConfig };
515
+ const key = generateIPRateLimitKey2(ip);
516
+ return await checkRateLimit2(key, config.limit, config.windowMs);
517
+ }
518
+
519
+ // src/CSRFService.ts
520
+ import { createHmac, randomBytes } from "crypto";
521
+ var CSRFService = class {
522
+ constructor(payload, options = {}) {
523
+ this.payload = payload;
524
+ this.options = {
525
+ algorithm: options.algorithm ?? "sha256",
526
+ encoding: options.encoding ?? "hex",
527
+ tokenLength: options.tokenLength ?? 32,
528
+ tokenExpiry: options.tokenExpiry ?? 24 * 60 * 60 * 1e3
529
+ // 24 hours
530
+ };
531
+ }
532
+ options;
533
+ async generateCsrfToken(sessionId) {
534
+ try {
535
+ const token = randomBytes(this.options.tokenLength);
536
+ const hmac = createHmac(this.options.algorithm, process.env.PAYLOAD_SECRET ?? "");
537
+ hmac.update(sessionId);
538
+ hmac.update(token);
539
+ const signature = hmac.digest(this.options.encoding);
540
+ const csrfToken = `${token.toString(this.options.encoding)}.${signature}`;
541
+ this.payload.logger.debug("CSRF token generated successfully");
542
+ return csrfToken;
543
+ } catch (error) {
544
+ this.payload.logger.error("Failed to generate CSRF token", error);
545
+ throw error;
546
+ }
547
+ }
548
+ async validateCsrfToken(token, sessionId) {
549
+ try {
550
+ const [tokenPart, signature] = token.split(".");
551
+ if (!tokenPart || !signature) {
552
+ this.payload.logger.warn("Invalid CSRF token format");
553
+ return false;
554
+ }
555
+ const hmac = createHmac(this.options.algorithm, process.env.PAYLOAD_SECRET ?? "");
556
+ hmac.update(sessionId);
557
+ hmac.update(Buffer.from(tokenPart, this.options.encoding));
558
+ const expectedSignature = hmac.digest(this.options.encoding);
559
+ const isValid = signature === expectedSignature;
560
+ if (isValid) {
561
+ this.payload.logger.debug("CSRF token validated successfully");
562
+ } else {
563
+ this.payload.logger.warn("CSRF token validation failed");
564
+ }
565
+ return isValid;
566
+ } catch (error) {
567
+ this.payload.logger.error("Failed to validate CSRF token", error);
568
+ return false;
569
+ }
570
+ }
571
+ };
572
+
573
+ // src/EncryptionService.ts
574
+ import { InfrastructureService } from "@revealui/core";
575
+ import {
576
+ createCipheriv,
577
+ createDecipheriv,
578
+ randomBytes as randomBytes2,
579
+ scrypt as scryptCallback
580
+ } from "crypto";
581
+ import { promisify } from "util";
582
+ var scryptAsync = promisify(scryptCallback);
583
+ function isAuthenticatedCipher(cipher) {
584
+ return typeof cipher.getAuthTag === "function";
585
+ }
586
+ function isAuthenticatedDecipher(decipher) {
587
+ return typeof decipher.setAuthTag === "function";
588
+ }
589
+ var EncryptionService = class extends InfrastructureService {
590
+ options;
591
+ constructor(payload, options = {}) {
592
+ super(payload, "EncryptionService");
593
+ this.options = {
594
+ algorithm: options.algorithm ?? "aes-256-gcm",
595
+ keyLength: options.keyLength ?? 32,
596
+ saltLength: options.saltLength ?? 16,
597
+ ivLength: options.ivLength ?? 12,
598
+ iterations: options.iterations ?? 1e5,
599
+ encoding: options.encoding ?? "hex"
600
+ };
601
+ }
602
+ /**
603
+ * Encrypts data using the configured algorithm with comprehensive error handling.
604
+ */
605
+ async encrypt(data, customOptions) {
606
+ return this.executeInfrastructureOperation(async () => {
607
+ if (typeof data !== "string" || data.length === 0) {
608
+ throw new Error("Data to encrypt must be a non-empty string");
609
+ }
610
+ const effectiveOptions = { ...this.options, ...customOptions };
611
+ this.logger.debug("Starting data encryption", {
612
+ algorithm: effectiveOptions.algorithm,
613
+ dataLength: data.length,
614
+ serviceName: this.serviceName,
615
+ operation: "encrypt"
616
+ });
617
+ const salt = randomBytes2(effectiveOptions.saltLength);
618
+ const iv = randomBytes2(effectiveOptions.ivLength);
619
+ const key = await this.deriveKey(salt, effectiveOptions);
620
+ const cipher = createCipheriv(effectiveOptions.algorithm, key, iv);
621
+ const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
622
+ if (isAuthenticatedCipher(cipher) !== true) {
623
+ throw new Error("Cipher does not support authentication");
624
+ }
625
+ const authTag = cipher.getAuthTag();
626
+ const result = {
627
+ data: encrypted.toString(effectiveOptions.encoding),
628
+ metadata: {
629
+ algorithm: effectiveOptions.algorithm,
630
+ iv: iv.toString(effectiveOptions.encoding),
631
+ authTag: authTag.toString(effectiveOptions.encoding),
632
+ salt: salt.toString(effectiveOptions.encoding)
633
+ }
634
+ };
635
+ this.logger.debug("Data encrypted successfully", {
636
+ algorithm: effectiveOptions.algorithm,
637
+ originalLength: data.length,
638
+ encryptedLength: result.data.length,
639
+ serviceName: this.serviceName
640
+ });
641
+ return result;
642
+ }, "encrypt");
643
+ }
644
+ /**
645
+ * Decrypts data using the metadata provided with comprehensive validation.
646
+ */
647
+ async decrypt(encryptedData) {
648
+ return this.executeInfrastructureOperation(async () => {
649
+ if (!encryptedData || !encryptedData.data || !encryptedData.metadata) {
650
+ throw new Error("Invalid encrypted data structure");
651
+ }
652
+ const { data, metadata } = encryptedData;
653
+ const requiredFields = ["algorithm", "iv", "authTag", "salt"];
654
+ for (const field of requiredFields) {
655
+ const fieldValue = metadata[field];
656
+ if (fieldValue === void 0 || fieldValue === null || fieldValue === "") {
657
+ throw new Error(`Missing required encryption metadata field: ${field}`);
658
+ }
659
+ }
660
+ this.logger.debug("Starting data decryption", {
661
+ algorithm: metadata.algorithm,
662
+ encryptedLength: data.length,
663
+ serviceName: this.serviceName,
664
+ operation: "decrypt"
665
+ });
666
+ const key = await this.deriveKey(Buffer.from(metadata.salt, this.options.encoding), {
667
+ ...this.options,
668
+ algorithm: metadata.algorithm
669
+ });
670
+ const decipher = createDecipheriv(
671
+ metadata.algorithm,
672
+ key,
673
+ Buffer.from(metadata.iv, this.options.encoding)
674
+ );
675
+ if (isAuthenticatedDecipher(decipher) !== true) {
676
+ throw new Error("Decipher does not support authentication");
677
+ }
678
+ decipher.setAuthTag(Buffer.from(metadata.authTag, this.options.encoding));
679
+ const decrypted = Buffer.concat([
680
+ decipher.update(Buffer.from(data, this.options.encoding)),
681
+ decipher.final()
682
+ ]);
683
+ const result = decrypted.toString("utf8");
684
+ this.logger.debug("Data decrypted successfully", {
685
+ algorithm: metadata.algorithm,
686
+ encryptedLength: data.length,
687
+ decryptedLength: result.length,
688
+ serviceName: this.serviceName
689
+ });
690
+ return result;
691
+ }, "decrypt");
692
+ }
693
+ /**
694
+ * Encrypts multiple data items in batch with optimized processing.
695
+ */
696
+ async encryptBatch(dataItems, customOptions) {
697
+ return this.executeInfrastructureOperation(async () => {
698
+ if (!Array.isArray(dataItems) || dataItems.length === 0) {
699
+ throw new Error("Data items array must contain at least one item");
700
+ }
701
+ this.logger.info("Starting batch encryption", {
702
+ itemCount: dataItems.length,
703
+ serviceName: this.serviceName,
704
+ operation: "encryptBatch"
705
+ });
706
+ const successful = [];
707
+ const failed = [];
708
+ const encryptionPromises = dataItems.map(async (data) => {
709
+ try {
710
+ const result = await this.encrypt(data, customOptions);
711
+ if (result.success && result.data) {
712
+ successful.push(result.data);
713
+ } else {
714
+ failed.push({
715
+ data,
716
+ error: result.error ?? "Unknown encryption error"
717
+ });
718
+ }
719
+ } catch (error) {
720
+ failed.push({
721
+ data,
722
+ error: error instanceof Error ? error.message : "Unknown encryption error"
723
+ });
724
+ }
725
+ });
726
+ await Promise.allSettled(encryptionPromises);
727
+ this.logger.info("Batch encryption completed", {
728
+ totalItems: dataItems.length,
729
+ successful: successful.length,
730
+ failed: failed.length,
731
+ serviceName: this.serviceName
732
+ });
733
+ return { successful, failed };
734
+ }, "encryptBatch");
735
+ }
736
+ /**
737
+ * Generates cryptographically secure random data.
738
+ */
739
+ async generateRandomData(length = 32, encoding = "hex") {
740
+ return this.executeInfrastructureOperation(async () => {
741
+ if (length <= 0 || length > 1024) {
742
+ throw new Error("Random data length must be between 1 and 1024 bytes");
743
+ }
744
+ this.logger.debug("Generating random data", {
745
+ length,
746
+ encoding,
747
+ serviceName: this.serviceName,
748
+ operation: "generateRandomData"
749
+ });
750
+ const randomData = randomBytes2(length);
751
+ const result = randomData.toString(encoding);
752
+ this.logger.debug("Random data generated successfully", {
753
+ length,
754
+ encoding,
755
+ outputLength: result.length,
756
+ serviceName: this.serviceName
757
+ });
758
+ return result;
759
+ }, "generateRandomData");
760
+ }
761
+ /**
762
+ * Validates the integrity of encrypted data by attempting decryption.
763
+ */
764
+ async validateEncryptedData(encryptedData) {
765
+ return this.executeInfrastructureOperation(async () => {
766
+ if (!encryptedData) {
767
+ throw new Error("Encrypted data is required");
768
+ }
769
+ this.logger.debug("Validating encrypted data integrity", {
770
+ algorithm: encryptedData.metadata.algorithm,
771
+ serviceName: this.serviceName,
772
+ operation: "validateEncryptedData"
773
+ });
774
+ try {
775
+ const decryptResult = await this.decrypt(encryptedData);
776
+ const valid = decryptResult.success && !!decryptResult.data;
777
+ this.logger.debug("Encrypted data validation completed", {
778
+ valid,
779
+ serviceName: this.serviceName
780
+ });
781
+ return valid ? { valid, error: void 0 } : {
782
+ valid,
783
+ error: decryptResult.error ?? "Decryption failed"
784
+ };
785
+ } catch (error) {
786
+ const errorMessage = error instanceof Error ? error.message : "Unknown validation error";
787
+ this.logger.debug("Encrypted data validation failed", {
788
+ error: errorMessage,
789
+ serviceName: this.serviceName
790
+ });
791
+ return { valid: false, error: errorMessage };
792
+ }
793
+ }, "validateEncryptedData");
794
+ }
795
+ /**
796
+ * Derives encryption key from password and salt using PBKDF2.
797
+ * @private
798
+ */
799
+ async deriveKey(salt, options = {}) {
800
+ const effectiveOptions = { ...this.options, ...options };
801
+ const secret = process.env.PAYLOAD_SECRET;
802
+ if (!secret || secret === "") {
803
+ throw new Error("PAYLOAD_SECRET environment variable is required for encryption");
804
+ }
805
+ return scryptAsync(secret, salt, effectiveOptions.keyLength);
806
+ }
807
+ /**
808
+ * @inheritdoc
809
+ */
810
+ async onInitialize() {
811
+ this.logger.info("Initializing EncryptionService", {
812
+ algorithm: this.options.algorithm,
813
+ keyLength: this.options.keyLength,
814
+ serviceName: this.serviceName
815
+ });
816
+ if (!process.env.PAYLOAD_SECRET) {
817
+ throw new Error("PAYLOAD_SECRET environment variable is required for EncryptionService");
818
+ }
819
+ try {
820
+ const testData = "encryption-test-data";
821
+ const encrypted = await this.encrypt(testData);
822
+ if (!encrypted.success || !encrypted.data) {
823
+ throw new Error("Encryption test failed");
824
+ }
825
+ const decrypted = await this.decrypt(encrypted.data);
826
+ if (!decrypted.success || decrypted.data !== testData) {
827
+ throw new Error("Decryption test failed");
828
+ }
829
+ this.logger.debug("EncryptionService initialization test passed", {
830
+ serviceName: this.serviceName
831
+ });
832
+ } catch (error) {
833
+ throw new Error(
834
+ `EncryptionService initialization failed: ${error instanceof Error ? error.message : "Unknown error"}`
835
+ );
836
+ }
837
+ }
838
+ /**
839
+ * @inheritdoc
840
+ */
841
+ async onCleanup() {
842
+ this.logger.info("Cleaning up EncryptionService", {
843
+ serviceName: this.serviceName
844
+ });
845
+ }
846
+ /**
847
+ * @inheritdoc
848
+ */
849
+ async onHealthCheck() {
850
+ try {
851
+ const testData = "health-check-data";
852
+ const encrypted = await this.encrypt(testData);
853
+ if (!encrypted.success || !encrypted.data) {
854
+ throw new Error("Health check encryption failed");
855
+ }
856
+ const decrypted = await this.decrypt(encrypted.data);
857
+ if (!decrypted.success || decrypted.data !== testData) {
858
+ throw new Error("Health check decryption failed");
859
+ }
860
+ } catch (error) {
861
+ throw new Error(
862
+ `EncryptionService health check failed: ${error instanceof Error ? error.message : "Unknown error"}`
863
+ );
864
+ }
865
+ }
866
+ };
867
+
868
+ // src/SecurityMonitoringService.ts
869
+ var devLogger2 = {
870
+ info: (...args) => console.log("[security]", ...args),
871
+ warn: (...args) => console.warn("[security]", ...args),
872
+ error: (...args) => console.error("[security]", ...args)
873
+ };
874
+ var createSecurityEvent = (type, severity, message, options) => {
875
+ const baseEvent = {
876
+ id: crypto.randomUUID(),
877
+ type,
878
+ severity,
879
+ message,
880
+ timestamp: /* @__PURE__ */ new Date(),
881
+ ...options?.metadata ? { metadata: options.metadata } : {},
882
+ ...options?.userId !== void 0 ? { userId: options.userId } : {},
883
+ ...options?.ipAddress !== void 0 ? { ipAddress: options.ipAddress } : {},
884
+ ...options?.userAgent !== void 0 ? { userAgent: options.userAgent } : {}
885
+ };
886
+ return baseEvent;
887
+ };
888
+ var calculateSecurityMetrics = (events2) => {
889
+ if (events2.length === 0) {
890
+ return {
891
+ totalEvents: 0,
892
+ eventsByType: {},
893
+ eventsBySeverity: {},
894
+ averageResponseTime: 0
895
+ };
896
+ }
897
+ const eventsByType = events2.reduce((acc, event) => {
898
+ acc[event.type] = (acc[event.type] || 0) + 1;
899
+ return acc;
900
+ }, {});
901
+ const eventsBySeverity = events2.reduce((acc, event) => {
902
+ acc[event.severity] = (acc[event.severity] || 0) + 1;
903
+ return acc;
904
+ }, {});
905
+ const lastEventTime = events2.length > 0 ? new Date(Math.max(...events2.map((e) => e.timestamp.getTime()))) : void 0;
906
+ return {
907
+ totalEvents: events2.length,
908
+ eventsByType,
909
+ eventsBySeverity,
910
+ averageResponseTime: 0,
911
+ // Would be calculated from actual response times
912
+ ...lastEventTime !== void 0 ? { lastEventTime } : {}
913
+ };
914
+ };
915
+ var shouldTriggerAlert = (event, thresholds) => {
916
+ const now = /* @__PURE__ */ new Date();
917
+ const timeWindowStart = new Date(now.getTime() - thresholds.timeWindowMs);
918
+ const recentEvents = events.filter(
919
+ (event2) => event2.timestamp >= timeWindowStart && event2.timestamp <= now
920
+ );
921
+ const criticalCount = recentEvents.filter((e) => e.severity === "critical").length;
922
+ const highCount = recentEvents.filter((e) => e.severity === "high").length;
923
+ const mediumCount = recentEvents.filter((e) => e.severity === "medium").length;
924
+ if (criticalCount >= thresholds.criticalCount) return true;
925
+ if (highCount >= thresholds.highCount) return true;
926
+ if (mediumCount >= thresholds.mediumCount) return true;
927
+ if (event.severity === "critical") return true;
928
+ return false;
929
+ };
930
+ var createSecurityAlert = (event, alertType = "threshold_exceeded") => ({
931
+ id: crypto.randomUUID(),
932
+ type: alertType,
933
+ severity: event.severity,
934
+ message: `Security alert: ${event.message}`,
935
+ timestamp: /* @__PURE__ */ new Date(),
936
+ metadata: {
937
+ originalEventId: event.id,
938
+ originalEventType: event.type,
939
+ ...event.metadata
940
+ }
941
+ });
942
+ var filterEvents = (events2, criteria) => {
943
+ return events2.filter((event) => {
944
+ if (criteria.type && event.type !== criteria.type) return false;
945
+ if (criteria.severity && event.severity !== criteria.severity) return false;
946
+ if (criteria.userId && event.userId !== criteria.userId) return false;
947
+ if (criteria.timeRange) {
948
+ const eventTime = event.timestamp.getTime();
949
+ const startTime = criteria.timeRange.start.getTime();
950
+ const endTime = criteria.timeRange.end.getTime();
951
+ if (eventTime < startTime || eventTime > endTime) return false;
952
+ }
953
+ return true;
954
+ });
955
+ };
956
+ var events = [];
957
+ var alerts = [];
958
+ var addSecurityEvent = (event) => {
959
+ events.push(event);
960
+ devLogger2.info("Security event recorded", {
961
+ eventId: event.id,
962
+ type: event.type,
963
+ severity: event.severity,
964
+ message: event.message,
965
+ userId: event.userId,
966
+ timestamp: event.timestamp.toISOString()
967
+ });
968
+ if (shouldTriggerAlert(event, {
969
+ criticalCount: 1,
970
+ highCount: 5,
971
+ mediumCount: 10,
972
+ timeWindowMs: 6e4
973
+ // 1 minute
974
+ })) {
975
+ const alert = createSecurityAlert(event);
976
+ alerts.push(alert);
977
+ devLogger2.warn("Security alert triggered", {
978
+ alertId: alert.id,
979
+ eventId: event.id,
980
+ severity: alert.severity,
981
+ message: alert.message
982
+ });
983
+ }
984
+ };
985
+ var getSecurityEvents = () => [...events];
986
+ var getSecurityAlerts = () => [...alerts];
987
+ var getSecurityMetrics = () => calculateSecurityMetrics(events);
988
+ var clearSecurityData = () => {
989
+ events = [];
990
+ alerts = [];
991
+ };
992
+ var logAuthenticationEvent = (userId, success, options) => {
993
+ const eventOptions = {
994
+ userId,
995
+ ...options?.ipAddress !== void 0 ? { ipAddress: options.ipAddress } : {},
996
+ ...options?.userAgent !== void 0 ? { userAgent: options.userAgent } : {},
997
+ metadata: {
998
+ success,
999
+ ...options?.metadata
1000
+ }
1001
+ };
1002
+ const event = createSecurityEvent(
1003
+ "authentication",
1004
+ success ? "low" : "high",
1005
+ `Authentication ${success ? "successful" : "failed"} for user ${userId}`,
1006
+ eventOptions
1007
+ );
1008
+ addSecurityEvent(event);
1009
+ };
1010
+ var logAuthorizationEvent = (userId, resource, action, granted, options) => {
1011
+ const eventOptions = {
1012
+ userId,
1013
+ ...options?.ipAddress !== void 0 ? { ipAddress: options.ipAddress } : {},
1014
+ ...options?.userAgent !== void 0 ? { userAgent: options.userAgent } : {},
1015
+ metadata: {
1016
+ resource,
1017
+ action,
1018
+ granted,
1019
+ ...options?.metadata
1020
+ }
1021
+ };
1022
+ const event = createSecurityEvent(
1023
+ "authorization",
1024
+ granted ? "low" : "medium",
1025
+ `Authorization ${granted ? "granted" : "denied"} for user ${userId} on ${resource}:${action}`,
1026
+ eventOptions
1027
+ );
1028
+ addSecurityEvent(event);
1029
+ };
1030
+ var logDataAccessEvent = (userId, collection, operation, recordId, options) => {
1031
+ const eventOptions = {
1032
+ userId,
1033
+ ...options?.ipAddress !== void 0 ? { ipAddress: options.ipAddress } : {},
1034
+ ...options?.userAgent !== void 0 ? { userAgent: options.userAgent } : {},
1035
+ metadata: {
1036
+ collection,
1037
+ operation,
1038
+ recordId,
1039
+ ...options?.metadata
1040
+ }
1041
+ };
1042
+ const event = createSecurityEvent(
1043
+ "data_access",
1044
+ operation === "delete" ? "medium" : "low",
1045
+ `Data access: ${operation} on ${collection}${recordId ? ` (${recordId})` : ""} by user ${userId}`,
1046
+ eventOptions
1047
+ );
1048
+ addSecurityEvent(event);
1049
+ };
1050
+ var getUserEvents = (userId) => filterEvents(events, { userId });
1051
+ var getEventsBySeverity = (severity) => filterEvents(events, { severity });
1052
+ var getEventsInTimeRange = (start, end) => filterEvents(events, { timeRange: { start, end } });
1053
+ var SecurityMonitoringService = {
1054
+ // Event management
1055
+ addEvent: addSecurityEvent,
1056
+ getEvents: getSecurityEvents,
1057
+ getUserEvents,
1058
+ getEventsBySeverity,
1059
+ getEventsInTimeRange,
1060
+ // Alert management
1061
+ getAlerts: getSecurityAlerts,
1062
+ // Metrics
1063
+ getMetrics: getSecurityMetrics,
1064
+ // Utility functions
1065
+ logAuthenticationEvent,
1066
+ logAuthorizationEvent,
1067
+ logDataAccessEvent,
1068
+ // Testing utilities
1069
+ clearData: clearSecurityData,
1070
+ // Pure functions for composition
1071
+ createEvent: createSecurityEvent,
1072
+ createAlert: createSecurityAlert,
1073
+ filterEvents,
1074
+ calculateMetrics: calculateSecurityMetrics
1075
+ };
1076
+
1077
+ // src/SecurityService.ts
1078
+ import { NextResponse } from "next/server";
1079
+ import { createHmac as createHmac2 } from "crypto";
1080
+ async function getPayloadClient() {
1081
+ return { logger: console };
1082
+ }
1083
+ var logger = {
1084
+ info: (message, ...args) => console.log(`[SecurityService] ${message}`, ...args),
1085
+ warn: (message, ...args) => console.warn(`[SecurityService] ${message}`, ...args),
1086
+ error: (message, ...args) => console.error(`[SecurityService] ${message}`, ...args)
1087
+ };
1088
+ async function securityMiddleware(req) {
1089
+ const response = new NextResponse();
1090
+ try {
1091
+ const payload = await getPayloadClient();
1092
+ payload.logger.info(`Security middleware started for ${req.method} ${req.url}`);
1093
+ } catch {
1094
+ logger.info(`Security middleware started for ${req.method} ${req.url}`);
1095
+ }
1096
+ response.headers.set("X-Content-Type-Options", "nosniff");
1097
+ response.headers.set("X-Frame-Options", "DENY");
1098
+ response.headers.set("X-XSS-Protection", "1; mode=block");
1099
+ response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
1100
+ response.headers.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
1101
+ const isApiRoute = req.nextUrl.pathname.startsWith("/api");
1102
+ const csp = isApiRoute ? "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'" : "default-src 'self'";
1103
+ response.headers.set("Content-Security-Policy", csp);
1104
+ try {
1105
+ const payload = await getPayloadClient();
1106
+ payload.logger.info("Security middleware completed");
1107
+ } catch {
1108
+ logger.info("Security middleware completed");
1109
+ }
1110
+ return;
1111
+ }
1112
+ function createSecurity(config) {
1113
+ return {
1114
+ generateCsrfToken(sessionId) {
1115
+ return createHmac2("sha256", config.csrfSecret).update(sessionId).digest("hex");
1116
+ },
1117
+ isTokenExpired(exp) {
1118
+ return Date.now() / 1e3 > exp;
1119
+ },
1120
+ getAccessTokenExpiry() {
1121
+ return config.tokenExpiry.access;
1122
+ },
1123
+ getRefreshTokenExpiry() {
1124
+ return config.tokenExpiry.refresh;
1125
+ }
1126
+ };
1127
+ }
1128
+
1129
+ // src/SessionService.ts
1130
+ var logger2 = {
1131
+ info: (message, ...args) => console.log(`[SessionService] ${message}`, ...args),
1132
+ warn: (message, ...args) => console.warn(`[SessionService] ${message}`, ...args),
1133
+ error: (message, ...args) => console.error(`[SessionService] ${message}`, ...args)
1134
+ };
1135
+ var devLogger3 = {
1136
+ info: (...args) => logger2.info(String(args[0]), ...args.slice(1)),
1137
+ warn: (...args) => logger2.warn(String(args[0]), ...args.slice(1)),
1138
+ error: (...args) => logger2.error(String(args[0]), ...args.slice(1)),
1139
+ forService: (_name) => ({
1140
+ info: (...args) => logger2.info(String(args[0]), ...args.slice(1)),
1141
+ warn: (...args) => logger2.warn(String(args[0]), ...args.slice(1)),
1142
+ error: (...args) => logger2.error(String(args[0]), ...args.slice(1))
1143
+ })
1144
+ };
1145
+ async function getPayloadClient2() {
1146
+ return {
1147
+ logger: {
1148
+ info: (...args) => logger2.info(String(args[0]), ...args.slice(1)),
1149
+ warn: (...args) => logger2.warn(String(args[0]), ...args.slice(1)),
1150
+ error: (...args) => logger2.error(String(args[0]), ...args.slice(1))
1151
+ }
1152
+ };
1153
+ }
1154
+ function generateUUID() {
1155
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
1156
+ return crypto.randomUUID();
1157
+ }
1158
+ return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, (c) => {
1159
+ const r = Math.random() * 16 | 0;
1160
+ const v = c === "x" ? r : r & 3 | 8;
1161
+ return v.toString(16);
1162
+ });
1163
+ }
1164
+ var SessionRepository = class {
1165
+ constructor(payload) {
1166
+ this.payload = payload;
1167
+ void this.payload;
1168
+ }
1169
+ sessions = /* @__PURE__ */ new Map();
1170
+ createSessionId() {
1171
+ try {
1172
+ return generateUUID();
1173
+ } catch {
1174
+ return `session_${Date.now().toString(36)}`;
1175
+ }
1176
+ }
1177
+ createToken() {
1178
+ try {
1179
+ return `token_${generateUUID()}`;
1180
+ } catch {
1181
+ return `token_${Date.now().toString(36)}`;
1182
+ }
1183
+ }
1184
+ async create(data) {
1185
+ if (!data.userId) {
1186
+ return {
1187
+ success: false,
1188
+ errors: [{ message: "userId is required", path: "create" }]
1189
+ };
1190
+ }
1191
+ const now = /* @__PURE__ */ new Date();
1192
+ const session = {
1193
+ id: data.id ?? this.createSessionId(),
1194
+ userId: data.userId,
1195
+ token: data.token ?? this.createToken(),
1196
+ expiresAt: data.expiresAt ? new Date(data.expiresAt) : new Date(now.getTime() + 1e3 * 60 * 60),
1197
+ lastActiveAt: data.lastActiveAt ? new Date(data.lastActiveAt) : now,
1198
+ isActive: data.isActive ?? true,
1199
+ updatedAt: now,
1200
+ createdAt: now,
1201
+ ...data.device !== void 0 ? { device: data.device } : {}
1202
+ };
1203
+ this.sessions.set(session.id, session);
1204
+ return { success: true, data: session };
1205
+ }
1206
+ async findById(id) {
1207
+ const session = this.sessions.get(String(id));
1208
+ if (!session) {
1209
+ return {
1210
+ success: false,
1211
+ errors: [{ message: "Session not found", path: "findById" }]
1212
+ };
1213
+ }
1214
+ return { success: true, data: session };
1215
+ }
1216
+ async delete(id) {
1217
+ const key = String(id);
1218
+ if (!this.sessions.has(key)) {
1219
+ return {
1220
+ success: false,
1221
+ errors: [{ message: "Session not found", path: "delete" }]
1222
+ };
1223
+ }
1224
+ this.sessions.delete(key);
1225
+ return { success: true };
1226
+ }
1227
+ async revokeAllUserSessions(userId) {
1228
+ let removed = false;
1229
+ for (const [key, session] of this.sessions.entries()) {
1230
+ if (session.userId === userId) {
1231
+ this.sessions.delete(key);
1232
+ removed = true;
1233
+ }
1234
+ }
1235
+ if (!removed) {
1236
+ return {
1237
+ success: false,
1238
+ errors: [{ message: "No sessions found for user", path: "revokeAllUserSessions" }]
1239
+ };
1240
+ }
1241
+ return { success: true };
1242
+ }
1243
+ async findAll() {
1244
+ return { success: true, data: Array.from(this.sessions.values()) };
1245
+ }
1246
+ async update(id, data) {
1247
+ const existing = this.sessions.get(id);
1248
+ if (!existing) {
1249
+ return {
1250
+ success: false,
1251
+ errors: [{ message: "Session not found", path: "update" }]
1252
+ };
1253
+ }
1254
+ const updated = {
1255
+ ...existing,
1256
+ ...data.userId !== void 0 ? { userId: data.userId } : {},
1257
+ ...data.token !== void 0 ? { token: data.token } : {},
1258
+ ...data.device !== void 0 ? { device: data.device } : {},
1259
+ ...data.expiresAt !== void 0 ? { expiresAt: new Date(data.expiresAt) } : {},
1260
+ ...data.lastActiveAt !== void 0 ? { lastActiveAt: new Date(data.lastActiveAt) } : {},
1261
+ ...data.isActive !== void 0 ? { isActive: data.isActive } : {},
1262
+ updatedAt: /* @__PURE__ */ new Date()
1263
+ };
1264
+ this.sessions.set(id, updated);
1265
+ return { success: true, data: updated };
1266
+ }
1267
+ };
1268
+ var SessionService = class {
1269
+ repository = null;
1270
+ constructor(repository) {
1271
+ if (repository) {
1272
+ this.repository = repository;
1273
+ }
1274
+ }
1275
+ async getRepository() {
1276
+ if (!this.repository) {
1277
+ const payload = await getPayloadClient2();
1278
+ this.repository = new SessionRepository(payload);
1279
+ }
1280
+ return this.repository;
1281
+ }
1282
+ /**
1283
+ * Creates a new session for a user.
1284
+ */
1285
+ async createSession(data) {
1286
+ if (!data.userId || typeof data.userId !== "string") {
1287
+ return {
1288
+ success: false,
1289
+ errors: [
1290
+ {
1291
+ message: "userId is required",
1292
+ path: "userId"
1293
+ }
1294
+ ]
1295
+ };
1296
+ }
1297
+ const repository = await this.getRepository();
1298
+ const result = await repository.create(data);
1299
+ if (!result.success) {
1300
+ try {
1301
+ const payload = await getPayloadClient2();
1302
+ payload.logger.error("Failed to create session", {
1303
+ errors: result.errors
1304
+ });
1305
+ } catch {
1306
+ devLogger3.error("Failed to create session", { errors: result.errors });
1307
+ }
1308
+ }
1309
+ return result;
1310
+ }
1311
+ /**
1312
+ * Validates a session by ID, checks for expiration, and deletes if expired.
1313
+ */
1314
+ async validateSession(sessionId) {
1315
+ try {
1316
+ const repository = await this.getRepository();
1317
+ const result = await repository.findById(sessionId);
1318
+ if (!result.success || !result.data) {
1319
+ try {
1320
+ const payload = await getPayloadClient2();
1321
+ payload.logger.warn("Invalid session", { sessionId, errors: result.errors });
1322
+ } catch {
1323
+ devLogger3.warn("Invalid session", { sessionId, errors: result.errors });
1324
+ }
1325
+ return {
1326
+ success: false,
1327
+ errors: result.errors ?? [{ message: "Session not found", path: "validateSession" }]
1328
+ };
1329
+ }
1330
+ const session = result.data;
1331
+ if (session.expiresAt.getTime() < Date.now()) {
1332
+ await repository.delete(sessionId);
1333
+ try {
1334
+ const payload = await getPayloadClient2();
1335
+ payload.logger.info("Session expired and deleted", { sessionId });
1336
+ } catch {
1337
+ devLogger3.info("Session expired and deleted", { sessionId });
1338
+ }
1339
+ return {
1340
+ success: false,
1341
+ errors: [{ message: "Session expired", path: "validateSession" }]
1342
+ };
1343
+ }
1344
+ const userResult = { success: true, data: { id: session.userId } };
1345
+ if (!userResult.success || !userResult.data) {
1346
+ try {
1347
+ const payload = await getPayloadClient2();
1348
+ payload.logger.error("User not found for session", {
1349
+ sessionId,
1350
+ userId: session.userId
1351
+ });
1352
+ } catch {
1353
+ devLogger3.error("User not found for session", {
1354
+ sessionId,
1355
+ userId: session.userId
1356
+ });
1357
+ }
1358
+ return {
1359
+ success: false,
1360
+ errors: [{ message: "User not found for session", path: "validateSession" }]
1361
+ };
1362
+ }
1363
+ return { success: true, data: session };
1364
+ } catch (error) {
1365
+ try {
1366
+ const payload = await getPayloadClient2();
1367
+ payload.logger.error("Session validation failed", { error });
1368
+ } catch {
1369
+ devLogger3.error("Session validation failed", { error });
1370
+ }
1371
+ return {
1372
+ success: false,
1373
+ errors: [{ message: "Session validation failed", path: "validateSession" }]
1374
+ };
1375
+ }
1376
+ }
1377
+ /**
1378
+ * Invalidates (deletes) a session by ID.
1379
+ */
1380
+ async invalidateSession(sessionId) {
1381
+ try {
1382
+ const repository = await this.getRepository();
1383
+ const result = await repository.delete(sessionId);
1384
+ if (!result.success) {
1385
+ try {
1386
+ const payload = await getPayloadClient2();
1387
+ payload.logger.error("Session invalidation failed", {
1388
+ errors: result.errors
1389
+ });
1390
+ } catch {
1391
+ devLogger3.error("Session invalidation failed", {
1392
+ errors: result.errors
1393
+ });
1394
+ }
1395
+ return result;
1396
+ }
1397
+ return result;
1398
+ } catch (error) {
1399
+ try {
1400
+ const payload = await getPayloadClient2();
1401
+ payload.logger.error("Session invalidation failed", { error });
1402
+ } catch {
1403
+ devLogger3.error("Session invalidation failed", { error });
1404
+ }
1405
+ return {
1406
+ success: false,
1407
+ errors: [
1408
+ {
1409
+ message: "Session invalidation failed",
1410
+ path: "invalidateSession"
1411
+ }
1412
+ ]
1413
+ };
1414
+ }
1415
+ }
1416
+ /**
1417
+ * Invalidates (deletes) all sessions for a user by userId.
1418
+ */
1419
+ async invalidateAllUserSessions(userId) {
1420
+ try {
1421
+ const repository = await this.getRepository();
1422
+ const result = await repository.revokeAllUserSessions(userId);
1423
+ if (!result.success) {
1424
+ try {
1425
+ const payload = await getPayloadClient2();
1426
+ payload.logger.error("User sessions invalidation failed", {
1427
+ errors: result.errors
1428
+ });
1429
+ } catch {
1430
+ devLogger3.error("User sessions invalidation failed", {
1431
+ errors: result.errors
1432
+ });
1433
+ }
1434
+ return result;
1435
+ }
1436
+ return result;
1437
+ } catch (error) {
1438
+ try {
1439
+ const payload = await getPayloadClient2();
1440
+ payload.logger.error("User sessions invalidation failed", {
1441
+ error
1442
+ });
1443
+ } catch {
1444
+ devLogger3.error("User sessions invalidation failed", { error });
1445
+ }
1446
+ return {
1447
+ success: false,
1448
+ errors: [
1449
+ {
1450
+ message: "Failed to invalidate user sessions",
1451
+ path: "invalidateAllUserSessions"
1452
+ }
1453
+ ]
1454
+ };
1455
+ }
1456
+ }
1457
+ async getSessionById(id) {
1458
+ const repository = await this.getRepository();
1459
+ const result = await repository.findById(id);
1460
+ if (!result.success) {
1461
+ try {
1462
+ const payload = await getPayloadClient2();
1463
+ payload.logger.warn("Session not found", {
1464
+ errors: result.errors
1465
+ });
1466
+ } catch {
1467
+ devLogger3.warn("Session not found", { errors: result.errors });
1468
+ }
1469
+ }
1470
+ return result;
1471
+ }
1472
+ async getAllSessions() {
1473
+ const repository = await this.getRepository();
1474
+ const result = await repository.findAll();
1475
+ if (!result.success) {
1476
+ try {
1477
+ const payload = await getPayloadClient2();
1478
+ payload.logger.error("Failed to fetch sessions", {
1479
+ errors: result.errors
1480
+ });
1481
+ } catch {
1482
+ devLogger3.error("Failed to fetch sessions", {
1483
+ errors: result.errors
1484
+ });
1485
+ }
1486
+ }
1487
+ return result;
1488
+ }
1489
+ async updateSession(id, data) {
1490
+ const repository = await this.getRepository();
1491
+ const result = await repository.update(id, data);
1492
+ if (!result.success) {
1493
+ try {
1494
+ const payload = await getPayloadClient2();
1495
+ payload.logger.error("Failed to update session", {
1496
+ errors: result.errors
1497
+ });
1498
+ } catch {
1499
+ devLogger3.error("Failed to update session", { errors: result.errors });
1500
+ }
1501
+ }
1502
+ return result;
1503
+ }
1504
+ async deleteSession(id) {
1505
+ const repository = await this.getRepository();
1506
+ const result = await repository.delete(id);
1507
+ if (!result.success) {
1508
+ try {
1509
+ const payload = await getPayloadClient2();
1510
+ payload.logger.error("Failed to delete session", {
1511
+ errors: result.errors
1512
+ });
1513
+ } catch {
1514
+ devLogger3.error("Failed to delete session", { errors: result.errors });
1515
+ }
1516
+ }
1517
+ return result;
1518
+ }
1519
+ };
1520
+ var sessionService = new SessionService();
1521
+
1522
+ // src/TokenService.ts
1523
+ import { InfrastructureService as InfrastructureService2 } from "@revealui/core";
1524
+ var AppError = class extends Error {
1525
+ constructor(code, message, cause) {
1526
+ super(message);
1527
+ this.code = code;
1528
+ this.cause = cause;
1529
+ this.name = "AppError";
1530
+ if (cause) {
1531
+ this.cause = cause;
1532
+ }
1533
+ }
1534
+ };
1535
+ var ErrorCode = {
1536
+ TOKEN_INVALID: "TOKEN_INVALID",
1537
+ TOKEN_EXPIRED: "TOKEN_EXPIRED",
1538
+ AUTHENTICATION_ERROR: "AUTHENTICATION_ERROR",
1539
+ INTERNAL_ERROR: "INTERNAL_ERROR"
1540
+ };
1541
+ var TokenService = class extends InfrastructureService2 {
1542
+ constructor(payload) {
1543
+ super(payload, "TokenService");
1544
+ this.payload = payload;
1545
+ }
1546
+ /**
1547
+ * Authenticate user and generate access token using PayloadCMS native login
1548
+ * @param credentials User login credentials
1549
+ * @param req Optional PayloadRequest for context
1550
+ * @returns Login result with user data and token
1551
+ */
1552
+ async login(credentials, req) {
1553
+ try {
1554
+ this.logger.debug("Attempting user login", {
1555
+ email: credentials.email,
1556
+ serviceName: this.serviceName,
1557
+ operation: "login"
1558
+ });
1559
+ const result = await this.payload.login({
1560
+ collection: "users",
1561
+ data: {
1562
+ email: credentials.email,
1563
+ password: credentials.password
1564
+ },
1565
+ ...req && { req }
1566
+ });
1567
+ this.logger.info("User login successful", {
1568
+ userId: result.user.id,
1569
+ email: result.user.email,
1570
+ serviceName: this.serviceName
1571
+ });
1572
+ return result;
1573
+ } catch (error) {
1574
+ this.logger.error("User login failed", {
1575
+ email: credentials.email,
1576
+ error: error instanceof Error ? error.message : "Unknown error",
1577
+ serviceName: this.serviceName
1578
+ });
1579
+ throw new AppError(
1580
+ ErrorCode.AUTHENTICATION_ERROR,
1581
+ "Login failed",
1582
+ error instanceof Error ? error : new Error(String(error))
1583
+ );
1584
+ }
1585
+ }
1586
+ /**
1587
+ * Verify user by ID using PayloadCMS native functionality
1588
+ * @param userId User ID to verify
1589
+ * @returns User data if exists
1590
+ */
1591
+ async verifyUser(userId) {
1592
+ try {
1593
+ this.logger.debug("Verifying user", {
1594
+ userId,
1595
+ serviceName: this.serviceName,
1596
+ operation: "verifyUser"
1597
+ });
1598
+ const user = await this.payload.findByID({
1599
+ collection: "users",
1600
+ id: userId
1601
+ });
1602
+ this.logger.debug("User verification successful", {
1603
+ userId: user.id,
1604
+ serviceName: this.serviceName
1605
+ });
1606
+ return user;
1607
+ } catch (error) {
1608
+ this.logger.error("User verification failed", {
1609
+ userId,
1610
+ error: error instanceof Error ? error.message : "Unknown error",
1611
+ serviceName: this.serviceName
1612
+ });
1613
+ throw new AppError(
1614
+ ErrorCode.AUTHENTICATION_ERROR,
1615
+ "User verification failed",
1616
+ error instanceof Error ? error : new Error(String(error))
1617
+ );
1618
+ }
1619
+ }
1620
+ /**
1621
+ * Generate password reset token using PayloadCMS native functionality
1622
+ * @param email User email address
1623
+ * @returns Password reset token
1624
+ */
1625
+ async generatePasswordResetToken(email) {
1626
+ try {
1627
+ this.logger.debug("Generating password reset token", {
1628
+ email,
1629
+ serviceName: this.serviceName,
1630
+ operation: "generatePasswordResetToken"
1631
+ });
1632
+ const token = await this.payload.forgotPassword({
1633
+ collection: "users",
1634
+ data: { email }
1635
+ });
1636
+ this.logger.info("Password reset token generated", {
1637
+ email,
1638
+ serviceName: this.serviceName
1639
+ });
1640
+ return token;
1641
+ } catch (error) {
1642
+ this.logger.error("Password reset token generation failed", {
1643
+ email,
1644
+ error: error instanceof Error ? error.message : "Unknown error",
1645
+ serviceName: this.serviceName
1646
+ });
1647
+ throw new AppError(
1648
+ ErrorCode.INTERNAL_ERROR,
1649
+ "Failed to generate password reset token",
1650
+ error instanceof Error ? error : new Error(String(error))
1651
+ );
1652
+ }
1653
+ }
1654
+ /**
1655
+ * Reset user password using PayloadCMS native functionality
1656
+ * @param token Password reset token
1657
+ * @param newPassword New password
1658
+ * @returns Reset result with user and new token
1659
+ *
1660
+ * NOTE: Uses overrideAccess: true as this is a system-level password reset operation
1661
+ * that requires bypassing normal access control for security token validation.
1662
+ */
1663
+ async resetPassword(token, newPassword) {
1664
+ try {
1665
+ this.logger.debug("Resetting password", {
1666
+ serviceName: this.serviceName,
1667
+ operation: "resetPassword"
1668
+ });
1669
+ const result = await this.payload.resetPassword({
1670
+ collection: "users",
1671
+ data: {
1672
+ token,
1673
+ password: newPassword
1674
+ },
1675
+ overrideAccess: true
1676
+ // System-level operation for password reset flow
1677
+ });
1678
+ this.logger.info("Password reset successful", {
1679
+ userId: result.user?.id,
1680
+ serviceName: this.serviceName
1681
+ });
1682
+ return result;
1683
+ } catch (error) {
1684
+ this.logger.error("Password reset failed", {
1685
+ error: error instanceof Error ? error.message : "Unknown error",
1686
+ serviceName: this.serviceName
1687
+ });
1688
+ throw new AppError(
1689
+ ErrorCode.AUTHENTICATION_ERROR,
1690
+ "Password reset failed",
1691
+ error instanceof Error ? error : new Error(String(error))
1692
+ );
1693
+ }
1694
+ }
1695
+ /**
1696
+ * Verify email using PayloadCMS native functionality
1697
+ * @param token Email verification token
1698
+ * @returns Verification success status
1699
+ */
1700
+ async verifyEmail(token) {
1701
+ try {
1702
+ this.logger.debug("Verifying email", {
1703
+ serviceName: this.serviceName,
1704
+ operation: "verifyEmail"
1705
+ });
1706
+ const result = await this.payload.verifyEmail({
1707
+ collection: "users",
1708
+ token
1709
+ });
1710
+ this.logger.info("Email verification completed", {
1711
+ success: result,
1712
+ serviceName: this.serviceName
1713
+ });
1714
+ return result;
1715
+ } catch (error) {
1716
+ this.logger.error("Email verification failed", {
1717
+ error: error instanceof Error ? error.message : "Unknown error",
1718
+ serviceName: this.serviceName
1719
+ });
1720
+ throw new AppError(
1721
+ ErrorCode.AUTHENTICATION_ERROR,
1722
+ "Email verification failed",
1723
+ error instanceof Error ? error : new Error(String(error))
1724
+ );
1725
+ }
1726
+ }
1727
+ /**
1728
+ * Unlock user account using PayloadCMS native functionality
1729
+ * @param email User email address
1730
+ * @param password User password
1731
+ * @returns Unlock success status
1732
+ *
1733
+ * NOTE: Uses overrideAccess: true as this is a system-level account unlock operation
1734
+ * that requires bypassing normal access control for account recovery.
1735
+ */
1736
+ async unlockUser(email, password) {
1737
+ try {
1738
+ this.logger.debug("Unlocking user account", {
1739
+ email,
1740
+ serviceName: this.serviceName,
1741
+ operation: "unlockUser"
1742
+ });
1743
+ const result = await this.payload.unlock({
1744
+ collection: "users",
1745
+ data: { email, password },
1746
+ overrideAccess: true
1747
+ // System-level operation for account recovery
1748
+ });
1749
+ this.logger.info("User account unlock completed", {
1750
+ email,
1751
+ success: result,
1752
+ serviceName: this.serviceName
1753
+ });
1754
+ return result;
1755
+ } catch (error) {
1756
+ this.logger.error("User account unlock failed", {
1757
+ email,
1758
+ error: error instanceof Error ? error.message : "Unknown error",
1759
+ serviceName: this.serviceName
1760
+ });
1761
+ throw new AppError(
1762
+ ErrorCode.AUTHENTICATION_ERROR,
1763
+ "User account unlock failed",
1764
+ error instanceof Error ? error : new Error(String(error))
1765
+ );
1766
+ }
1767
+ }
1768
+ /**
1769
+ * @inheritdoc
1770
+ */
1771
+ onInitialize() {
1772
+ this.logger.info("TokenService initialized with PayloadCMS native auth", {
1773
+ serviceName: this.serviceName
1774
+ });
1775
+ }
1776
+ /**
1777
+ * @inheritdoc
1778
+ */
1779
+ onCleanup() {
1780
+ this.logger.info("TokenService cleaned up", {
1781
+ serviceName: this.serviceName
1782
+ });
1783
+ }
1784
+ /**
1785
+ * @inheritdoc
1786
+ */
1787
+ onHealthCheck() {
1788
+ this.logger.debug("TokenService health check passed", {
1789
+ serviceName: this.serviceName
1790
+ });
1791
+ }
1792
+ };
1793
+ function createTokenService(payload) {
1794
+ return new TokenService(payload);
1795
+ }
1796
+ export {
1797
+ CSRFService,
1798
+ EncryptionService,
1799
+ RATE_LIMITS,
1800
+ RateLimited,
1801
+ RateLimiterService,
1802
+ SECURITY_HEADERS,
1803
+ SecurityMonitoringService,
1804
+ SessionService,
1805
+ TokenService,
1806
+ addSecurityEvent,
1807
+ calculateSecurityMetrics,
1808
+ checkAnonymousRateLimit,
1809
+ checkRateLimit,
1810
+ checkUserRateLimit,
1811
+ clearSecurityData,
1812
+ createSecurity,
1813
+ createSecurityAlert,
1814
+ createSecurityEvent,
1815
+ createTokenService,
1816
+ filterEvents,
1817
+ generateIPRateLimitKey,
1818
+ generateRateLimitKey,
1819
+ getClientIP,
1820
+ getEventsBySeverity,
1821
+ getEventsInTimeRange,
1822
+ getRateLimiterService,
1823
+ getSecurityAlerts,
1824
+ getSecurityEvents,
1825
+ getSecurityMetrics,
1826
+ getUserEvents,
1827
+ isTrustedSource,
1828
+ logAuthenticationEvent,
1829
+ logAuthorizationEvent,
1830
+ logDataAccessEvent,
1831
+ logSecurityEvent,
1832
+ sanitizeInput,
1833
+ securityMiddleware,
1834
+ sessionService,
1835
+ shouldTriggerAlert,
1836
+ validateAndSanitizeInput,
1837
+ validateCSRFToken,
1838
+ validateEnvironment,
1839
+ withRateLimit
1840
+ };
1841
+ //# sourceMappingURL=index.js.map