@revealui/security 0.0.1-pre.1 → 0.2.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/LICENSE +22 -202
- package/LICENSE.commercial +112 -0
- package/dist/index.d.ts +1358 -0
- package/dist/index.js +2247 -1654
- package/dist/index.js.map +1 -1
- package/package.json +27 -74
package/dist/index.js
CHANGED
|
@@ -1,1841 +1,2434 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
};
|
|
1
|
+
// src/audit.ts
|
|
2
|
+
var AuditSystem = class {
|
|
3
|
+
storage;
|
|
4
|
+
filters = [];
|
|
5
|
+
constructor(storage) {
|
|
6
|
+
this.storage = storage;
|
|
50
7
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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"
|
|
8
|
+
/**
|
|
9
|
+
* Log audit event
|
|
10
|
+
*/
|
|
11
|
+
async log(event) {
|
|
12
|
+
const fullEvent = {
|
|
13
|
+
...event,
|
|
14
|
+
id: crypto.randomUUID(),
|
|
15
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
89
16
|
};
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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);
|
|
17
|
+
const shouldLog = this.filters.every((filter) => filter(fullEvent));
|
|
18
|
+
if (!shouldLog) {
|
|
19
|
+
return;
|
|
103
20
|
}
|
|
104
|
-
|
|
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) {
|
|
21
|
+
await this.storage.write(fullEvent);
|
|
153
22
|
}
|
|
154
23
|
/**
|
|
155
|
-
*
|
|
24
|
+
* Log authentication event
|
|
156
25
|
*/
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
}
|
|
26
|
+
async logAuth(type, actorId, result, metadata) {
|
|
27
|
+
await this.log({
|
|
28
|
+
type,
|
|
29
|
+
severity: result === "failure" ? "medium" : "low",
|
|
30
|
+
actor: {
|
|
31
|
+
id: actorId,
|
|
32
|
+
type: "user"
|
|
33
|
+
},
|
|
34
|
+
action: type.replace("auth.", ""),
|
|
35
|
+
result,
|
|
36
|
+
metadata
|
|
37
|
+
});
|
|
220
38
|
}
|
|
221
39
|
/**
|
|
222
|
-
*
|
|
40
|
+
* Log data access event
|
|
223
41
|
*/
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
}
|
|
42
|
+
async logDataAccess(action, actorId, resourceType, resourceId, result, changes) {
|
|
43
|
+
await this.log({
|
|
44
|
+
type: `data.${action}`,
|
|
45
|
+
severity: action === "delete" ? "high" : "medium",
|
|
46
|
+
actor: {
|
|
47
|
+
id: actorId,
|
|
48
|
+
type: "user"
|
|
49
|
+
},
|
|
50
|
+
resource: {
|
|
51
|
+
type: resourceType,
|
|
52
|
+
id: resourceId
|
|
53
|
+
},
|
|
54
|
+
action,
|
|
55
|
+
result,
|
|
56
|
+
changes
|
|
57
|
+
});
|
|
281
58
|
}
|
|
282
59
|
/**
|
|
283
|
-
*
|
|
60
|
+
* Log permission change
|
|
284
61
|
*/
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
};
|
|
62
|
+
async logPermissionChange(action, actorId, targetUserId, permission, result) {
|
|
63
|
+
await this.log({
|
|
64
|
+
type: `permission.${action}`,
|
|
65
|
+
severity: "high",
|
|
66
|
+
actor: {
|
|
67
|
+
id: actorId,
|
|
68
|
+
type: "user"
|
|
69
|
+
},
|
|
70
|
+
resource: {
|
|
71
|
+
type: "user",
|
|
72
|
+
id: targetUserId
|
|
73
|
+
},
|
|
74
|
+
action,
|
|
75
|
+
result,
|
|
76
|
+
metadata: {
|
|
77
|
+
permission
|
|
318
78
|
}
|
|
319
|
-
|
|
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
|
-
}
|
|
79
|
+
});
|
|
344
80
|
}
|
|
345
81
|
/**
|
|
346
|
-
*
|
|
82
|
+
* Log security event
|
|
347
83
|
*/
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
`
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
84
|
+
async logSecurityEvent(type, severity, actorId, message, metadata) {
|
|
85
|
+
await this.log({
|
|
86
|
+
type: `security.${type}`,
|
|
87
|
+
severity,
|
|
88
|
+
actor: {
|
|
89
|
+
id: actorId,
|
|
90
|
+
type: "user"
|
|
91
|
+
},
|
|
92
|
+
action: type,
|
|
93
|
+
result: "failure",
|
|
94
|
+
message,
|
|
95
|
+
metadata
|
|
96
|
+
});
|
|
358
97
|
}
|
|
359
98
|
/**
|
|
360
|
-
*
|
|
99
|
+
* Log GDPR event
|
|
361
100
|
*/
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
`
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
return null;
|
|
101
|
+
async logGDPREvent(type, actorId, result, metadata) {
|
|
102
|
+
await this.log({
|
|
103
|
+
type: `gdpr.${type}`,
|
|
104
|
+
severity: "high",
|
|
105
|
+
actor: {
|
|
106
|
+
id: actorId,
|
|
107
|
+
type: "user"
|
|
108
|
+
},
|
|
109
|
+
action: type,
|
|
110
|
+
result,
|
|
111
|
+
metadata
|
|
112
|
+
});
|
|
375
113
|
}
|
|
376
114
|
/**
|
|
377
|
-
*
|
|
115
|
+
* Query audit logs
|
|
378
116
|
*/
|
|
379
|
-
|
|
380
|
-
this.
|
|
117
|
+
async query(query) {
|
|
118
|
+
return this.storage.query(query);
|
|
381
119
|
}
|
|
382
120
|
/**
|
|
383
|
-
*
|
|
121
|
+
* Count audit logs
|
|
384
122
|
*/
|
|
385
|
-
|
|
386
|
-
return
|
|
387
|
-
totalKeys: this.inMemoryStore.size,
|
|
388
|
-
memoryUsage: this.inMemoryStore.size
|
|
389
|
-
};
|
|
123
|
+
async count(query) {
|
|
124
|
+
return this.storage.count(query);
|
|
390
125
|
}
|
|
391
126
|
/**
|
|
392
|
-
*
|
|
127
|
+
* Add filter
|
|
393
128
|
*/
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
});
|
|
129
|
+
addFilter(filter) {
|
|
130
|
+
this.filters.push(filter);
|
|
405
131
|
}
|
|
406
132
|
/**
|
|
407
|
-
*
|
|
133
|
+
* Remove filter
|
|
408
134
|
*/
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
135
|
+
removeFilter(filter) {
|
|
136
|
+
const index = this.filters.indexOf(filter);
|
|
137
|
+
if (index > -1) {
|
|
138
|
+
this.filters.splice(index, 1);
|
|
412
139
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
var InMemoryAuditStorage = class {
|
|
143
|
+
events = [];
|
|
144
|
+
maxEvents;
|
|
145
|
+
constructor(maxEvents = 1e4) {
|
|
146
|
+
this.maxEvents = maxEvents;
|
|
147
|
+
}
|
|
148
|
+
async write(event) {
|
|
149
|
+
this.events.push(event);
|
|
150
|
+
if (this.events.length > this.maxEvents) {
|
|
151
|
+
this.events.shift();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async query(query) {
|
|
155
|
+
let results = [...this.events];
|
|
156
|
+
if (query.types && query.types.length > 0) {
|
|
157
|
+
results = results.filter((e) => query.types?.includes(e.type));
|
|
158
|
+
}
|
|
159
|
+
if (query.actorId) {
|
|
160
|
+
results = results.filter((e) => e.actor.id === query.actorId);
|
|
161
|
+
}
|
|
162
|
+
if (query.resourceType) {
|
|
163
|
+
results = results.filter((e) => e.resource?.type === query.resourceType);
|
|
164
|
+
}
|
|
165
|
+
if (query.resourceId) {
|
|
166
|
+
results = results.filter((e) => e.resource?.id === query.resourceId);
|
|
167
|
+
}
|
|
168
|
+
if (query.startDate) {
|
|
169
|
+
const startDate = query.startDate;
|
|
170
|
+
results = results.filter((e) => new Date(e.timestamp) >= startDate);
|
|
171
|
+
}
|
|
172
|
+
if (query.endDate) {
|
|
173
|
+
const endDate = query.endDate;
|
|
174
|
+
results = results.filter((e) => new Date(e.timestamp) <= endDate);
|
|
175
|
+
}
|
|
176
|
+
if (query.severity && query.severity.length > 0) {
|
|
177
|
+
results = results.filter((e) => query.severity?.includes(e.severity));
|
|
178
|
+
}
|
|
179
|
+
if (query.result && query.result.length > 0) {
|
|
180
|
+
results = results.filter((e) => query.result?.includes(e.result));
|
|
181
|
+
}
|
|
182
|
+
results.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
183
|
+
const offset = query.offset || 0;
|
|
184
|
+
const limit = query.limit || 100;
|
|
185
|
+
return results.slice(offset, offset + limit);
|
|
186
|
+
}
|
|
187
|
+
async count(query) {
|
|
188
|
+
const results = await this.query({ ...query, limit: void 0, offset: void 0 });
|
|
189
|
+
return results.length;
|
|
419
190
|
}
|
|
420
191
|
/**
|
|
421
|
-
*
|
|
192
|
+
* Clear all events
|
|
422
193
|
*/
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
clearInterval(this.cleanupInterval);
|
|
426
|
-
this.cleanupInterval = null;
|
|
427
|
-
}
|
|
194
|
+
clear() {
|
|
195
|
+
this.events = [];
|
|
428
196
|
}
|
|
429
197
|
/**
|
|
430
|
-
*
|
|
198
|
+
* Get all events
|
|
431
199
|
*/
|
|
432
|
-
|
|
433
|
-
this.
|
|
434
|
-
this.inMemoryStore.clear();
|
|
200
|
+
getAll() {
|
|
201
|
+
return [...this.events];
|
|
435
202
|
}
|
|
436
203
|
};
|
|
437
|
-
|
|
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) {
|
|
204
|
+
function AuditTrail(type, action, options) {
|
|
468
205
|
return (_target, _propertyKey, descriptor) => {
|
|
469
206
|
const originalMethod = descriptor.value;
|
|
470
207
|
descriptor.value = async function(...args) {
|
|
471
|
-
const
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
208
|
+
const actorId = this.user?.id || "system";
|
|
209
|
+
const before = options?.captureChanges ? args[0] : void 0;
|
|
210
|
+
let result = "success";
|
|
211
|
+
let error;
|
|
212
|
+
try {
|
|
213
|
+
const returnValue = await originalMethod.apply(this, args);
|
|
214
|
+
if (this.audit) {
|
|
215
|
+
await this.audit.log({
|
|
216
|
+
type,
|
|
217
|
+
severity: options?.severity || "medium",
|
|
218
|
+
actor: {
|
|
219
|
+
id: actorId,
|
|
220
|
+
type: "user"
|
|
221
|
+
},
|
|
222
|
+
resource: options?.resourceType ? {
|
|
223
|
+
type: options.resourceType,
|
|
224
|
+
id: args[0]?.id || "unknown"
|
|
225
|
+
} : void 0,
|
|
226
|
+
action,
|
|
227
|
+
result,
|
|
228
|
+
changes: options?.captureChanges ? {
|
|
229
|
+
before,
|
|
230
|
+
after: returnValue
|
|
231
|
+
} : void 0
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return returnValue;
|
|
235
|
+
} catch (err) {
|
|
236
|
+
result = "failure";
|
|
237
|
+
error = err;
|
|
238
|
+
if (this.audit) {
|
|
239
|
+
await this.audit.log({
|
|
240
|
+
type,
|
|
241
|
+
severity: "high",
|
|
242
|
+
actor: {
|
|
243
|
+
id: actorId,
|
|
244
|
+
type: "user"
|
|
245
|
+
},
|
|
246
|
+
resource: options?.resourceType ? {
|
|
247
|
+
type: options.resourceType,
|
|
248
|
+
id: args[0]?.id || "unknown"
|
|
249
|
+
} : void 0,
|
|
250
|
+
action,
|
|
251
|
+
result,
|
|
252
|
+
message: error.message
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
throw error;
|
|
483
256
|
}
|
|
484
|
-
return await originalMethod.apply(this, args);
|
|
485
257
|
};
|
|
486
258
|
return descriptor;
|
|
487
259
|
};
|
|
488
260
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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) {
|
|
261
|
+
function createAuditMiddleware(audit2, getUser) {
|
|
262
|
+
return async (request, next) => {
|
|
263
|
+
const user = getUser(request);
|
|
264
|
+
const startTime = Date.now();
|
|
534
265
|
try {
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
266
|
+
const response = await next();
|
|
267
|
+
await audit2.log({
|
|
268
|
+
type: "data.read",
|
|
269
|
+
severity: "low",
|
|
270
|
+
actor: {
|
|
271
|
+
id: user.id,
|
|
272
|
+
type: "user",
|
|
273
|
+
ip: user.ip,
|
|
274
|
+
userAgent: user.userAgent
|
|
275
|
+
},
|
|
276
|
+
action: request.method,
|
|
277
|
+
result: "success",
|
|
278
|
+
metadata: {
|
|
279
|
+
path: request.url,
|
|
280
|
+
duration: Date.now() - startTime,
|
|
281
|
+
status: response.status
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
return response;
|
|
543
285
|
} catch (error) {
|
|
544
|
-
|
|
286
|
+
await audit2.log({
|
|
287
|
+
type: "data.read",
|
|
288
|
+
severity: "medium",
|
|
289
|
+
actor: {
|
|
290
|
+
id: user.id,
|
|
291
|
+
type: "user",
|
|
292
|
+
ip: user.ip,
|
|
293
|
+
userAgent: user.userAgent
|
|
294
|
+
},
|
|
295
|
+
action: request.method,
|
|
296
|
+
result: "failure",
|
|
297
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
298
|
+
metadata: {
|
|
299
|
+
path: request.url,
|
|
300
|
+
duration: Date.now() - startTime
|
|
301
|
+
}
|
|
302
|
+
});
|
|
545
303
|
throw error;
|
|
546
304
|
}
|
|
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";
|
|
305
|
+
};
|
|
588
306
|
}
|
|
589
|
-
var
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
};
|
|
307
|
+
var AuditReportGenerator = class {
|
|
308
|
+
constructor(audit2) {
|
|
309
|
+
this.audit = audit2;
|
|
601
310
|
}
|
|
602
311
|
/**
|
|
603
|
-
*
|
|
312
|
+
* Generate security report
|
|
604
313
|
*/
|
|
605
|
-
async
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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");
|
|
314
|
+
async generateSecurityReport(startDate, endDate) {
|
|
315
|
+
const allEvents = await this.audit.query({
|
|
316
|
+
startDate,
|
|
317
|
+
endDate
|
|
318
|
+
});
|
|
319
|
+
const securityViolations = allEvents.filter((e) => e.type.startsWith("security.")).length;
|
|
320
|
+
const failedLogins = allEvents.filter((e) => e.type === "auth.failed_login").length;
|
|
321
|
+
const permissionChanges = allEvents.filter((e) => e.type.startsWith("permission.")).length;
|
|
322
|
+
const dataExports = allEvents.filter((e) => e.type === "data.export").length;
|
|
323
|
+
const criticalEvents = allEvents.filter((e) => e.severity === "critical");
|
|
324
|
+
return {
|
|
325
|
+
totalEvents: allEvents.length,
|
|
326
|
+
securityViolations,
|
|
327
|
+
failedLogins,
|
|
328
|
+
permissionChanges,
|
|
329
|
+
dataExports,
|
|
330
|
+
criticalEvents
|
|
331
|
+
};
|
|
643
332
|
}
|
|
644
333
|
/**
|
|
645
|
-
*
|
|
334
|
+
* Generate user activity report
|
|
646
335
|
*/
|
|
647
|
-
async
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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");
|
|
336
|
+
async generateUserActivityReport(userId, startDate, endDate) {
|
|
337
|
+
const events = await this.audit.query({
|
|
338
|
+
actorId: userId,
|
|
339
|
+
startDate,
|
|
340
|
+
endDate
|
|
341
|
+
});
|
|
342
|
+
const actionsByType = events.reduce(
|
|
343
|
+
(acc, event) => {
|
|
344
|
+
acc[event.type] = (acc[event.type] || 0) + 1;
|
|
345
|
+
return acc;
|
|
346
|
+
},
|
|
347
|
+
{}
|
|
348
|
+
);
|
|
349
|
+
const failedActions = events.filter((e) => e.result === "failure").length;
|
|
350
|
+
return {
|
|
351
|
+
totalActions: events.length,
|
|
352
|
+
actionsByType,
|
|
353
|
+
failedActions,
|
|
354
|
+
recentActions: events.slice(0, 10)
|
|
355
|
+
};
|
|
692
356
|
}
|
|
693
357
|
/**
|
|
694
|
-
*
|
|
358
|
+
* Generate compliance report
|
|
695
359
|
*/
|
|
696
|
-
async
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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");
|
|
360
|
+
async generateComplianceReport(startDate, endDate) {
|
|
361
|
+
const events = await this.audit.query({
|
|
362
|
+
startDate,
|
|
363
|
+
endDate
|
|
364
|
+
});
|
|
365
|
+
const dataAccesses = events.filter((e) => e.type === "data.read").length;
|
|
366
|
+
const dataModifications = events.filter(
|
|
367
|
+
(e) => e.type === "data.update" || e.type === "data.create"
|
|
368
|
+
).length;
|
|
369
|
+
const dataDeletions = events.filter((e) => e.type === "data.delete").length;
|
|
370
|
+
const gdprRequests = events.filter((e) => e.type.startsWith("gdpr.")).length;
|
|
371
|
+
const auditTrailComplete = this.checkAuditTrailContinuity(events);
|
|
372
|
+
return {
|
|
373
|
+
dataAccesses,
|
|
374
|
+
dataModifications,
|
|
375
|
+
dataDeletions,
|
|
376
|
+
gdprRequests,
|
|
377
|
+
auditTrailComplete
|
|
378
|
+
};
|
|
735
379
|
}
|
|
736
380
|
/**
|
|
737
|
-
*
|
|
381
|
+
* Check audit trail continuity
|
|
738
382
|
*/
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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");
|
|
383
|
+
checkAuditTrailContinuity(events) {
|
|
384
|
+
if (events.length === 0) return true;
|
|
385
|
+
const sorted = events.sort(
|
|
386
|
+
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
387
|
+
);
|
|
388
|
+
return sorted.length > 0;
|
|
760
389
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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");
|
|
390
|
+
};
|
|
391
|
+
var audit = new AuditSystem(new InMemoryAuditStorage());
|
|
392
|
+
|
|
393
|
+
// src/auth.ts
|
|
394
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
395
|
+
var OAuthProviders = {
|
|
396
|
+
google: {
|
|
397
|
+
authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
398
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
399
|
+
userInfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
400
|
+
scope: ["openid", "email", "profile"]
|
|
401
|
+
},
|
|
402
|
+
github: {
|
|
403
|
+
authorizationUrl: "https://github.com/login/oauth/authorize",
|
|
404
|
+
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
405
|
+
userInfoUrl: "https://api.github.com/user",
|
|
406
|
+
scope: ["user:email"]
|
|
407
|
+
},
|
|
408
|
+
microsoft: {
|
|
409
|
+
authorizationUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
|
410
|
+
tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
|
411
|
+
userInfoUrl: "https://graph.microsoft.com/v1.0/me",
|
|
412
|
+
scope: ["openid", "email", "profile"]
|
|
794
413
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
throw new Error("PAYLOAD_SECRET environment variable is required for encryption");
|
|
804
|
-
}
|
|
805
|
-
return scryptAsync(secret, salt, effectiveOptions.keyLength);
|
|
414
|
+
};
|
|
415
|
+
var OAuthClient = class {
|
|
416
|
+
config;
|
|
417
|
+
constructor(config) {
|
|
418
|
+
this.config = {
|
|
419
|
+
...OAuthProviders[config.provider],
|
|
420
|
+
...config
|
|
421
|
+
};
|
|
806
422
|
}
|
|
807
423
|
/**
|
|
808
|
-
*
|
|
424
|
+
* Get authorization URL
|
|
809
425
|
*/
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
426
|
+
getAuthorizationUrl(state) {
|
|
427
|
+
const params = new URLSearchParams({
|
|
428
|
+
client_id: this.config.clientId,
|
|
429
|
+
redirect_uri: this.config.redirectUri,
|
|
430
|
+
response_type: "code",
|
|
431
|
+
scope: (this.config.scope || []).join(" ")
|
|
815
432
|
});
|
|
816
|
-
if (
|
|
817
|
-
|
|
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
|
-
);
|
|
433
|
+
if (state) {
|
|
434
|
+
params.append("state", state);
|
|
836
435
|
}
|
|
436
|
+
return `${this.config.authorizationUrl}?${params.toString()}`;
|
|
837
437
|
}
|
|
838
438
|
/**
|
|
839
|
-
*
|
|
439
|
+
* Exchange code for token
|
|
840
440
|
*/
|
|
841
|
-
async
|
|
842
|
-
this.
|
|
843
|
-
|
|
441
|
+
async exchangeCodeForToken(code) {
|
|
442
|
+
if (!this.config.tokenUrl) throw new Error("tokenUrl is required for OAuth");
|
|
443
|
+
const response = await fetch(this.config.tokenUrl, {
|
|
444
|
+
method: "POST",
|
|
445
|
+
headers: {
|
|
446
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
447
|
+
},
|
|
448
|
+
body: new URLSearchParams({
|
|
449
|
+
client_id: this.config.clientId,
|
|
450
|
+
client_secret: this.config.clientSecret,
|
|
451
|
+
code,
|
|
452
|
+
grant_type: "authorization_code",
|
|
453
|
+
redirect_uri: this.config.redirectUri
|
|
454
|
+
})
|
|
844
455
|
});
|
|
456
|
+
if (!response.ok) {
|
|
457
|
+
let detail = "";
|
|
458
|
+
try {
|
|
459
|
+
const body = await response.text();
|
|
460
|
+
detail = `: ${response.status} ${body.slice(0, 200)}`;
|
|
461
|
+
} catch {
|
|
462
|
+
detail = `: ${response.status}`;
|
|
463
|
+
}
|
|
464
|
+
throw new Error(`Failed to exchange code for token${detail}`);
|
|
465
|
+
}
|
|
466
|
+
return response.json();
|
|
845
467
|
}
|
|
846
468
|
/**
|
|
847
|
-
*
|
|
469
|
+
* Get user info
|
|
848
470
|
*/
|
|
849
|
-
async
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
471
|
+
async getUserInfo(accessToken) {
|
|
472
|
+
if (!this.config.userInfoUrl) throw new Error("userInfoUrl is required for OAuth");
|
|
473
|
+
const response = await fetch(this.config.userInfoUrl, {
|
|
474
|
+
headers: {
|
|
475
|
+
// biome-ignore lint/style/useNamingConvention: HTTP header convention
|
|
476
|
+
Authorization: `Bearer ${accessToken}`
|
|
855
477
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
478
|
+
});
|
|
479
|
+
if (!response.ok) {
|
|
480
|
+
let detail = "";
|
|
481
|
+
try {
|
|
482
|
+
const body = await response.text();
|
|
483
|
+
detail = `: ${response.status} ${body.slice(0, 200)}`;
|
|
484
|
+
} catch {
|
|
485
|
+
detail = `: ${response.status}`;
|
|
859
486
|
}
|
|
860
|
-
|
|
861
|
-
throw new Error(
|
|
862
|
-
`EncryptionService health check failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
863
|
-
);
|
|
487
|
+
throw new Error(`Failed to fetch user info${detail}`);
|
|
864
488
|
}
|
|
489
|
+
return response.json();
|
|
865
490
|
}
|
|
866
491
|
};
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
var
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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()
|
|
492
|
+
var PH_ITERATIONS = 1e5;
|
|
493
|
+
var PH_KEY_LENGTH = 64;
|
|
494
|
+
var PH_DIGEST = "sha512";
|
|
495
|
+
async function hashPassword(password) {
|
|
496
|
+
const { pbkdf2, randomBytes: rb } = await import("crypto");
|
|
497
|
+
const salt = rb(16).toString("hex");
|
|
498
|
+
return new Promise((resolve, reject) => {
|
|
499
|
+
pbkdf2(password, salt, PH_ITERATIONS, PH_KEY_LENGTH, PH_DIGEST, (err, derivedKey) => {
|
|
500
|
+
if (err) reject(err);
|
|
501
|
+
else resolve(`${salt}:${derivedKey.toString("hex")}`);
|
|
502
|
+
});
|
|
967
503
|
});
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
504
|
+
}
|
|
505
|
+
async function verifyPassword(password, storedHash) {
|
|
506
|
+
const { pbkdf2, timingSafeEqual: tse } = await import("crypto");
|
|
507
|
+
const [salt, hash] = storedHash.split(":");
|
|
508
|
+
if (!(salt && hash)) {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
return new Promise((resolve, reject) => {
|
|
512
|
+
pbkdf2(password, salt, PH_ITERATIONS, PH_KEY_LENGTH, PH_DIGEST, (err, derivedKey) => {
|
|
513
|
+
if (err) reject(err);
|
|
514
|
+
else {
|
|
515
|
+
const derived = Buffer.from(derivedKey.toString("hex"), "utf-8");
|
|
516
|
+
const expected = Buffer.from(hash, "utf-8");
|
|
517
|
+
if (derived.length !== expected.length) {
|
|
518
|
+
resolve(false);
|
|
519
|
+
} else {
|
|
520
|
+
resolve(tse(derived, expected));
|
|
521
|
+
}
|
|
522
|
+
}
|
|
982
523
|
});
|
|
983
|
-
}
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
var PasswordHasher = {
|
|
527
|
+
hash: hashPassword,
|
|
528
|
+
verify: verifyPassword
|
|
984
529
|
};
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
530
|
+
function base32Encode(buffer) {
|
|
531
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
532
|
+
let result = "";
|
|
533
|
+
let bits = 0;
|
|
534
|
+
let value = 0;
|
|
535
|
+
for (const byte of buffer) {
|
|
536
|
+
if (byte === void 0) continue;
|
|
537
|
+
value = value << 8 | byte;
|
|
538
|
+
bits += 8;
|
|
539
|
+
while (bits >= 5) {
|
|
540
|
+
result += alphabet[value >>> bits - 5 & 31];
|
|
541
|
+
bits -= 5;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (bits > 0) {
|
|
545
|
+
result += alphabet[value << 5 - bits & 31];
|
|
546
|
+
}
|
|
547
|
+
return result;
|
|
548
|
+
}
|
|
549
|
+
function totpHmac(key, message) {
|
|
550
|
+
const hmacDigest = createHmac("sha1", key).update(message).digest();
|
|
551
|
+
return new Uint8Array(hmacDigest);
|
|
552
|
+
}
|
|
553
|
+
function generateSecret() {
|
|
554
|
+
const crypto2 = globalThis.crypto;
|
|
555
|
+
if (!crypto2) {
|
|
556
|
+
throw new Error("Crypto API not available");
|
|
557
|
+
}
|
|
558
|
+
const buffer = new Uint8Array(20);
|
|
559
|
+
crypto2.getRandomValues(buffer);
|
|
560
|
+
return base32Encode(buffer);
|
|
561
|
+
}
|
|
562
|
+
function generateCode(secret, timestamp) {
|
|
563
|
+
const time = Math.floor((timestamp || Date.now()) / 3e4);
|
|
564
|
+
const hmacDigest = totpHmac(secret, time.toString());
|
|
565
|
+
const offset = hmacDigest[hmacDigest.length - 1] & 15;
|
|
566
|
+
const b0 = hmacDigest[offset] & 127;
|
|
567
|
+
const b1 = hmacDigest[offset + 1] & 255;
|
|
568
|
+
const b2 = hmacDigest[offset + 2] & 255;
|
|
569
|
+
const b3 = hmacDigest[offset + 3] & 255;
|
|
570
|
+
const code = (b0 << 24 | b1 << 16 | b2 << 8 | b3) % 1e6;
|
|
571
|
+
return code.toString().padStart(6, "0");
|
|
572
|
+
}
|
|
573
|
+
function verifyCode(secret, code, window = 1) {
|
|
574
|
+
const timestamp = Date.now();
|
|
575
|
+
for (let i = -window; i <= window; i++) {
|
|
576
|
+
const testTime = timestamp + i * 3e4;
|
|
577
|
+
const testCode = generateCode(secret, testTime);
|
|
578
|
+
if (testCode.length === code.length && timingSafeEqual(Buffer.from(testCode), Buffer.from(code))) {
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
var TwoFactorAuth = {
|
|
585
|
+
generateSecret,
|
|
586
|
+
generateCode,
|
|
587
|
+
verifyCode
|
|
991
588
|
};
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
589
|
+
|
|
590
|
+
// src/authorization.ts
|
|
591
|
+
var AuthorizationSystem = class {
|
|
592
|
+
roles = /* @__PURE__ */ new Map();
|
|
593
|
+
policies = /* @__PURE__ */ new Map();
|
|
594
|
+
/**
|
|
595
|
+
* Register role
|
|
596
|
+
*/
|
|
597
|
+
registerRole(role) {
|
|
598
|
+
this.roles.set(role.id, role);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Get role
|
|
602
|
+
*/
|
|
603
|
+
getRole(roleId) {
|
|
604
|
+
return this.roles.get(roleId);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Register policy
|
|
608
|
+
*/
|
|
609
|
+
registerPolicy(policy) {
|
|
610
|
+
this.policies.set(policy.id, policy);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Check if user has permission (RBAC)
|
|
614
|
+
*/
|
|
615
|
+
hasPermission(userRoles, resource, action) {
|
|
616
|
+
const permissions = this.getUserPermissions(userRoles);
|
|
617
|
+
return permissions.some(
|
|
618
|
+
(permission) => this.matchesResource(permission.resource, resource) && this.matchesAction(permission.action, action)
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Check access with policies (ABAC)
|
|
623
|
+
*/
|
|
624
|
+
checkAccess(context, resource, action) {
|
|
625
|
+
if (this.hasPermission(context.user.roles, resource, action)) {
|
|
626
|
+
return { allowed: true };
|
|
1000
627
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
628
|
+
const applicablePolicies = this.getApplicablePolicies(resource, action, context);
|
|
629
|
+
applicablePolicies.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
630
|
+
for (const policy of applicablePolicies) {
|
|
631
|
+
if (this.evaluateConditions(policy.conditions || [], context)) {
|
|
632
|
+
return {
|
|
633
|
+
allowed: policy.effect === "allow",
|
|
634
|
+
reason: policy.effect === "deny" ? `Denied by policy: ${policy.name}` : void 0
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return { allowed: false, reason: "No matching policy" };
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Get all permissions for roles
|
|
642
|
+
*/
|
|
643
|
+
getUserPermissions(roleIds) {
|
|
644
|
+
const permissions = [];
|
|
645
|
+
const visited = /* @__PURE__ */ new Set();
|
|
646
|
+
const addRolePermissions = (roleId) => {
|
|
647
|
+
if (visited.has(roleId)) return;
|
|
648
|
+
visited.add(roleId);
|
|
649
|
+
const role = this.roles.get(roleId);
|
|
650
|
+
if (!role) return;
|
|
651
|
+
permissions.push(...role.permissions);
|
|
652
|
+
if (role.inherits) {
|
|
653
|
+
role.inherits.forEach((inheritedRoleId) => {
|
|
654
|
+
addRolePermissions(inheritedRoleId);
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
roleIds.forEach(addRolePermissions);
|
|
659
|
+
return permissions;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Get applicable policies
|
|
663
|
+
*/
|
|
664
|
+
getApplicablePolicies(resource, action, _context) {
|
|
665
|
+
return Array.from(this.policies.values()).filter((policy) => {
|
|
666
|
+
const resourceMatches = policy.resources.some((r) => this.matchesResource(r, resource));
|
|
667
|
+
const actionMatches = policy.actions.some((a) => this.matchesAction(a, action));
|
|
668
|
+
return resourceMatches && actionMatches;
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Match resource pattern
|
|
673
|
+
*/
|
|
674
|
+
matchesResource(pattern, resource) {
|
|
675
|
+
if (pattern === "*") return true;
|
|
676
|
+
if (pattern === resource) return true;
|
|
677
|
+
const regex = new RegExp(
|
|
678
|
+
`^${pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".")}$`
|
|
679
|
+
);
|
|
680
|
+
return regex.test(resource);
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Match action pattern
|
|
684
|
+
*/
|
|
685
|
+
matchesAction(pattern, action) {
|
|
686
|
+
if (pattern === "*") return true;
|
|
687
|
+
if (pattern === action) return true;
|
|
688
|
+
const regex = new RegExp(
|
|
689
|
+
`^${pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".")}$`
|
|
690
|
+
);
|
|
691
|
+
return regex.test(action);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Evaluate policy conditions
|
|
695
|
+
*/
|
|
696
|
+
evaluateConditions(conditions, context) {
|
|
697
|
+
return conditions.every((condition) => {
|
|
698
|
+
const value = this.getContextValue(condition.field, context);
|
|
699
|
+
return this.evaluateCondition(condition, value);
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Get value from context
|
|
704
|
+
*/
|
|
705
|
+
getContextValue(field, context) {
|
|
706
|
+
const parts = field.split(".");
|
|
707
|
+
let value = context;
|
|
708
|
+
for (const part of parts) {
|
|
709
|
+
if (value && typeof value === "object" && part in value) {
|
|
710
|
+
value = value[part];
|
|
711
|
+
} else {
|
|
712
|
+
return void 0;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return value;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Evaluate single condition
|
|
719
|
+
*/
|
|
720
|
+
evaluateCondition(condition, value) {
|
|
721
|
+
switch (condition.operator) {
|
|
722
|
+
case "eq":
|
|
723
|
+
return value === condition.value;
|
|
724
|
+
case "ne":
|
|
725
|
+
return value !== condition.value;
|
|
726
|
+
case "gt":
|
|
727
|
+
return typeof value === "number" && value > condition.value;
|
|
728
|
+
case "gte":
|
|
729
|
+
return typeof value === "number" && value >= condition.value;
|
|
730
|
+
case "lt":
|
|
731
|
+
return typeof value === "number" && value < condition.value;
|
|
732
|
+
case "lte":
|
|
733
|
+
return typeof value === "number" && value <= condition.value;
|
|
734
|
+
case "in":
|
|
735
|
+
return Array.isArray(condition.value) && condition.value.includes(value);
|
|
736
|
+
case "contains":
|
|
737
|
+
return typeof value === "string" && typeof condition.value === "string" && value.includes(condition.value);
|
|
738
|
+
default:
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Check if user owns resource
|
|
744
|
+
*/
|
|
745
|
+
ownsResource(userId, resource) {
|
|
746
|
+
return resource.owner === userId;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Clear all roles and policies
|
|
750
|
+
*/
|
|
751
|
+
clear() {
|
|
752
|
+
this.roles.clear();
|
|
753
|
+
this.policies.clear();
|
|
754
|
+
}
|
|
1009
755
|
};
|
|
1010
|
-
var
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
756
|
+
var authorization = new AuthorizationSystem();
|
|
757
|
+
var CommonRoles = {
|
|
758
|
+
owner: {
|
|
759
|
+
id: "owner",
|
|
760
|
+
name: "Owner",
|
|
761
|
+
description: "Full control \u2014 inherits admin",
|
|
762
|
+
permissions: [{ resource: "*", action: "*" }],
|
|
763
|
+
inherits: ["admin"]
|
|
764
|
+
},
|
|
765
|
+
admin: {
|
|
766
|
+
id: "admin",
|
|
767
|
+
name: "Administrator",
|
|
768
|
+
description: "Full system access",
|
|
769
|
+
permissions: [{ resource: "*", action: "*" }]
|
|
770
|
+
},
|
|
771
|
+
editor: {
|
|
772
|
+
id: "editor",
|
|
773
|
+
name: "Editor",
|
|
774
|
+
description: "Can read and modify content",
|
|
775
|
+
permissions: [
|
|
776
|
+
{ resource: "content", action: "read" },
|
|
777
|
+
{ resource: "content", action: "create" },
|
|
778
|
+
{ resource: "content", action: "update" },
|
|
779
|
+
{ resource: "profile", action: "read" },
|
|
780
|
+
{ resource: "profile", action: "update" },
|
|
781
|
+
{ resource: "sites", action: "read" },
|
|
782
|
+
{ resource: "marketplace", action: "read" }
|
|
783
|
+
]
|
|
784
|
+
},
|
|
785
|
+
viewer: {
|
|
786
|
+
id: "viewer",
|
|
787
|
+
name: "Viewer",
|
|
788
|
+
description: "Read-only access",
|
|
789
|
+
permissions: [
|
|
790
|
+
{ resource: "content", action: "read" },
|
|
791
|
+
{ resource: "profile", action: "read" },
|
|
792
|
+
{ resource: "sites", action: "read" },
|
|
793
|
+
{ resource: "public", action: "read" }
|
|
794
|
+
]
|
|
795
|
+
},
|
|
796
|
+
agent: {
|
|
797
|
+
id: "agent",
|
|
798
|
+
name: "AI Agent",
|
|
799
|
+
description: "Can execute tasks and read content",
|
|
800
|
+
permissions: [
|
|
801
|
+
{ resource: "tasks", action: "create" },
|
|
802
|
+
{ resource: "tasks", action: "read" },
|
|
803
|
+
{ resource: "content", action: "read" },
|
|
804
|
+
{ resource: "rag", action: "read" },
|
|
805
|
+
{ resource: "rag", action: "create" }
|
|
806
|
+
]
|
|
807
|
+
},
|
|
808
|
+
contributor: {
|
|
809
|
+
id: "contributor",
|
|
810
|
+
name: "Contributor",
|
|
811
|
+
description: "Can suggest changes \u2014 create drafts but not publish or delete",
|
|
812
|
+
permissions: [
|
|
813
|
+
{ resource: "content", action: "read" },
|
|
814
|
+
{ resource: "content", action: "create" },
|
|
815
|
+
{ resource: "profile", action: "read" },
|
|
816
|
+
{ resource: "profile", action: "update" }
|
|
817
|
+
]
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
var PermissionBuilder = class {
|
|
821
|
+
permission = {};
|
|
822
|
+
resource(resource) {
|
|
823
|
+
this.permission.resource = resource;
|
|
824
|
+
return this;
|
|
825
|
+
}
|
|
826
|
+
action(action) {
|
|
827
|
+
this.permission.action = action;
|
|
828
|
+
return this;
|
|
829
|
+
}
|
|
830
|
+
conditions(conditions) {
|
|
831
|
+
this.permission.conditions = conditions;
|
|
832
|
+
return this;
|
|
833
|
+
}
|
|
834
|
+
build() {
|
|
835
|
+
if (!(this.permission.resource && this.permission.action)) {
|
|
836
|
+
throw new Error("Resource and action are required");
|
|
1020
837
|
}
|
|
838
|
+
return this.permission;
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
var PolicyBuilder = class {
|
|
842
|
+
policy = {
|
|
843
|
+
effect: "allow",
|
|
844
|
+
resources: [],
|
|
845
|
+
actions: [],
|
|
846
|
+
conditions: []
|
|
1021
847
|
};
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
848
|
+
id(id) {
|
|
849
|
+
this.policy.id = id;
|
|
850
|
+
return this;
|
|
851
|
+
}
|
|
852
|
+
name(name) {
|
|
853
|
+
this.policy.name = name;
|
|
854
|
+
return this;
|
|
855
|
+
}
|
|
856
|
+
allow() {
|
|
857
|
+
this.policy.effect = "allow";
|
|
858
|
+
return this;
|
|
859
|
+
}
|
|
860
|
+
deny() {
|
|
861
|
+
this.policy.effect = "deny";
|
|
862
|
+
return this;
|
|
863
|
+
}
|
|
864
|
+
resources(...resources) {
|
|
865
|
+
this.policy.resources = resources;
|
|
866
|
+
return this;
|
|
867
|
+
}
|
|
868
|
+
actions(...actions) {
|
|
869
|
+
this.policy.actions = actions;
|
|
870
|
+
return this;
|
|
871
|
+
}
|
|
872
|
+
condition(field, operator, value) {
|
|
873
|
+
if (!this.policy.conditions) {
|
|
874
|
+
this.policy.conditions = [];
|
|
875
|
+
}
|
|
876
|
+
this.policy.conditions.push({ field, operator, value });
|
|
877
|
+
return this;
|
|
878
|
+
}
|
|
879
|
+
priority(priority) {
|
|
880
|
+
this.policy.priority = priority;
|
|
881
|
+
return this;
|
|
882
|
+
}
|
|
883
|
+
build() {
|
|
884
|
+
if (!(this.policy.id && this.policy.name)) {
|
|
885
|
+
throw new Error("ID and name are required");
|
|
886
|
+
}
|
|
887
|
+
return this.policy;
|
|
888
|
+
}
|
|
1029
889
|
};
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
890
|
+
function RequirePermission(resource, action) {
|
|
891
|
+
return (_target, _propertyKey, descriptor) => {
|
|
892
|
+
const originalMethod = descriptor.value;
|
|
893
|
+
descriptor.value = function(...args) {
|
|
894
|
+
const userRoles = this.user?.roles || [];
|
|
895
|
+
if (!authorization.hasPermission(userRoles, resource, action)) {
|
|
896
|
+
throw new Error(`Permission denied: ${resource}:${action}`);
|
|
897
|
+
}
|
|
898
|
+
return originalMethod.apply(this, args);
|
|
899
|
+
};
|
|
900
|
+
return descriptor;
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
function RequireRole(requiredRole) {
|
|
904
|
+
return (_target, _propertyKey, descriptor) => {
|
|
905
|
+
const originalMethod = descriptor.value;
|
|
906
|
+
descriptor.value = function(...args) {
|
|
907
|
+
const userRoles = this.user?.roles || [];
|
|
908
|
+
if (!userRoles.includes(requiredRole)) {
|
|
909
|
+
throw new Error(`Role required: ${requiredRole}`);
|
|
910
|
+
}
|
|
911
|
+
return originalMethod.apply(this, args);
|
|
912
|
+
};
|
|
913
|
+
return descriptor;
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
function createAuthorizationMiddleware(getUser, resource, action) {
|
|
917
|
+
return (request, next) => {
|
|
918
|
+
const user = getUser(request);
|
|
919
|
+
if (!authorization.hasPermission(user.roles, resource, action)) {
|
|
920
|
+
throw new Error(`Permission denied: ${resource}:${action}`);
|
|
1040
921
|
}
|
|
922
|
+
return next();
|
|
1041
923
|
};
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
)
|
|
1048
|
-
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
924
|
+
}
|
|
925
|
+
function canAccessResource(userId, userRoles, resource, action) {
|
|
926
|
+
if (authorization.hasPermission(userRoles, resource.type, action)) {
|
|
927
|
+
return true;
|
|
928
|
+
}
|
|
929
|
+
if (authorization.ownsResource(userId, resource)) {
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
function checkAttributeAccess(context, resource, action, requiredAttributes) {
|
|
935
|
+
const { allowed } = authorization.checkAccess(context, resource, action);
|
|
936
|
+
if (!allowed) {
|
|
937
|
+
return false;
|
|
938
|
+
}
|
|
939
|
+
if (requiredAttributes) {
|
|
940
|
+
const userAttributes = context.user.attributes || {};
|
|
941
|
+
return Object.entries(requiredAttributes).every(
|
|
942
|
+
([key, value]) => userAttributes[key] === value
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
return true;
|
|
946
|
+
}
|
|
947
|
+
var PermissionCache = class {
|
|
948
|
+
cache = /* @__PURE__ */ new Map();
|
|
949
|
+
ttl;
|
|
950
|
+
maxEntries;
|
|
951
|
+
constructor(ttl = 3e5, maxEntries = 1e4) {
|
|
952
|
+
this.ttl = ttl;
|
|
953
|
+
this.maxEntries = maxEntries;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Get cached permission
|
|
957
|
+
*/
|
|
958
|
+
get(userId, resource, action) {
|
|
959
|
+
const key = this.getCacheKey(userId, resource, action);
|
|
960
|
+
const cached = this.cache.get(key);
|
|
961
|
+
if (!cached) {
|
|
962
|
+
return void 0;
|
|
963
|
+
}
|
|
964
|
+
if (Date.now() > cached.expiresAt) {
|
|
965
|
+
this.cache.delete(key);
|
|
966
|
+
return void 0;
|
|
967
|
+
}
|
|
968
|
+
return cached.allowed;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Set cached permission
|
|
972
|
+
*/
|
|
973
|
+
set(userId, resource, action, allowed) {
|
|
974
|
+
const key = this.getCacheKey(userId, resource, action);
|
|
975
|
+
if (this.cache.size >= this.maxEntries) {
|
|
976
|
+
const now = Date.now();
|
|
977
|
+
for (const [k, v] of this.cache) {
|
|
978
|
+
if (now > v.expiresAt) this.cache.delete(k);
|
|
979
|
+
}
|
|
980
|
+
if (this.cache.size >= this.maxEntries) {
|
|
981
|
+
const excess = this.cache.size - this.maxEntries + 1;
|
|
982
|
+
const keys = this.cache.keys();
|
|
983
|
+
for (let i = 0; i < excess; i++) {
|
|
984
|
+
const next = keys.next();
|
|
985
|
+
if (!next.done) this.cache.delete(next.value);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
this.cache.set(key, {
|
|
990
|
+
allowed,
|
|
991
|
+
expiresAt: Date.now() + this.ttl
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Clear cache for user
|
|
996
|
+
*/
|
|
997
|
+
clearUser(userId) {
|
|
998
|
+
for (const key of this.cache.keys()) {
|
|
999
|
+
if (key.startsWith(`${userId}:`)) {
|
|
1000
|
+
this.cache.delete(key);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Clear all cache
|
|
1006
|
+
*/
|
|
1007
|
+
clear() {
|
|
1008
|
+
this.cache.clear();
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Get cache key
|
|
1012
|
+
*/
|
|
1013
|
+
getCacheKey(userId, resource, action) {
|
|
1014
|
+
return `${userId}:${resource}:${action}`;
|
|
1015
|
+
}
|
|
1075
1016
|
};
|
|
1017
|
+
var permissionCache = new PermissionCache();
|
|
1076
1018
|
|
|
1077
|
-
// src/
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1019
|
+
// src/encryption.ts
|
|
1020
|
+
var DEFAULT_CONFIG = {
|
|
1021
|
+
algorithm: "AES-GCM",
|
|
1022
|
+
keySize: 256,
|
|
1023
|
+
ivSize: 12
|
|
1024
|
+
};
|
|
1025
|
+
var EncryptionSystem = class {
|
|
1026
|
+
config;
|
|
1027
|
+
keys = /* @__PURE__ */ new Map();
|
|
1028
|
+
constructor(config = {}) {
|
|
1029
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Generate encryption key
|
|
1033
|
+
*/
|
|
1034
|
+
async generateKey(keyId) {
|
|
1035
|
+
const crypto2 = globalThis.crypto;
|
|
1036
|
+
if (!crypto2) {
|
|
1037
|
+
throw new Error("Crypto API not available");
|
|
1038
|
+
}
|
|
1039
|
+
const key = await crypto2.subtle.generateKey(
|
|
1040
|
+
{
|
|
1041
|
+
name: this.config.algorithm,
|
|
1042
|
+
length: this.config.keySize
|
|
1043
|
+
},
|
|
1044
|
+
this.config.extractable ?? false,
|
|
1045
|
+
// non-extractable by default — prevents key exfiltration
|
|
1046
|
+
["encrypt", "decrypt"]
|
|
1047
|
+
);
|
|
1048
|
+
if (keyId) {
|
|
1049
|
+
this.keys.set(keyId, key);
|
|
1050
|
+
}
|
|
1051
|
+
return key;
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Import key from raw data
|
|
1055
|
+
*/
|
|
1056
|
+
async importKey(keyData, keyId) {
|
|
1057
|
+
const crypto2 = globalThis.crypto;
|
|
1058
|
+
if (!crypto2) {
|
|
1059
|
+
throw new Error("Crypto API not available");
|
|
1060
|
+
}
|
|
1061
|
+
const key = await crypto2.subtle.importKey(
|
|
1062
|
+
"raw",
|
|
1063
|
+
keyData,
|
|
1064
|
+
{
|
|
1065
|
+
name: this.config.algorithm,
|
|
1066
|
+
length: this.config.keySize
|
|
1067
|
+
},
|
|
1068
|
+
true,
|
|
1069
|
+
["encrypt", "decrypt"]
|
|
1070
|
+
);
|
|
1071
|
+
if (keyId) {
|
|
1072
|
+
this.keys.set(keyId, key);
|
|
1073
|
+
}
|
|
1074
|
+
return key;
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Export key to raw data
|
|
1078
|
+
*/
|
|
1079
|
+
async exportKey(key) {
|
|
1080
|
+
const crypto2 = globalThis.crypto;
|
|
1081
|
+
if (!crypto2) {
|
|
1082
|
+
throw new Error("Crypto API not available");
|
|
1083
|
+
}
|
|
1084
|
+
return crypto2.subtle.exportKey("raw", key);
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Encrypt data
|
|
1088
|
+
*/
|
|
1089
|
+
async encrypt(data, keyOrId) {
|
|
1090
|
+
const crypto2 = globalThis.crypto;
|
|
1091
|
+
if (!crypto2) {
|
|
1092
|
+
throw new Error("Crypto API not available");
|
|
1093
|
+
}
|
|
1094
|
+
const key = typeof keyOrId === "string" ? this.keys.get(keyOrId) : keyOrId;
|
|
1095
|
+
if (!key) {
|
|
1096
|
+
throw new Error("Key not found");
|
|
1097
|
+
}
|
|
1098
|
+
const iv = crypto2.getRandomValues(new Uint8Array(this.config.ivSize || 12));
|
|
1099
|
+
const encoder = new TextEncoder();
|
|
1100
|
+
const encodedData = encoder.encode(data);
|
|
1101
|
+
const encrypted = await crypto2.subtle.encrypt(
|
|
1102
|
+
{
|
|
1103
|
+
name: this.config.algorithm,
|
|
1104
|
+
iv
|
|
1105
|
+
},
|
|
1106
|
+
key,
|
|
1107
|
+
encodedData
|
|
1108
|
+
);
|
|
1109
|
+
const encryptedArray = new Uint8Array(encrypted);
|
|
1110
|
+
const ivArray = new Uint8Array(iv);
|
|
1111
|
+
return {
|
|
1112
|
+
data: this.arrayBufferToBase64(encryptedArray),
|
|
1113
|
+
iv: this.arrayBufferToBase64(ivArray),
|
|
1114
|
+
algorithm: this.config.algorithm
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Decrypt data
|
|
1119
|
+
*/
|
|
1120
|
+
async decrypt(encryptedData, keyOrId) {
|
|
1121
|
+
const crypto2 = globalThis.crypto;
|
|
1122
|
+
if (!crypto2) {
|
|
1123
|
+
throw new Error("Crypto API not available");
|
|
1124
|
+
}
|
|
1125
|
+
const key = typeof keyOrId === "string" ? this.keys.get(keyOrId) : keyOrId;
|
|
1126
|
+
if (!key) {
|
|
1127
|
+
throw new Error("Key not found");
|
|
1128
|
+
}
|
|
1129
|
+
const data = this.base64ToArrayBuffer(encryptedData.data);
|
|
1130
|
+
const iv = this.base64ToArrayBuffer(encryptedData.iv);
|
|
1131
|
+
const decrypted = await crypto2.subtle.decrypt(
|
|
1132
|
+
{
|
|
1133
|
+
name: encryptedData.algorithm,
|
|
1134
|
+
iv
|
|
1135
|
+
},
|
|
1136
|
+
key,
|
|
1137
|
+
data
|
|
1138
|
+
);
|
|
1139
|
+
const decoder = new TextDecoder();
|
|
1140
|
+
return decoder.decode(decrypted);
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Encrypt object
|
|
1144
|
+
*/
|
|
1145
|
+
async encryptObject(obj, keyOrId) {
|
|
1146
|
+
const json = JSON.stringify(obj);
|
|
1147
|
+
return this.encrypt(json, keyOrId);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Decrypt object
|
|
1151
|
+
*/
|
|
1152
|
+
async decryptObject(encryptedData, keyOrId) {
|
|
1153
|
+
const json = await this.decrypt(encryptedData, keyOrId);
|
|
1154
|
+
return JSON.parse(json);
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Hash data
|
|
1158
|
+
*/
|
|
1159
|
+
async hash(data, algorithm = "SHA-256") {
|
|
1160
|
+
const crypto2 = globalThis.crypto;
|
|
1161
|
+
if (!crypto2) {
|
|
1162
|
+
throw new Error("Crypto API not available");
|
|
1163
|
+
}
|
|
1164
|
+
const encoder = new TextEncoder();
|
|
1165
|
+
const encodedData = encoder.encode(data);
|
|
1166
|
+
const hashBuffer = await crypto2.subtle.digest(algorithm, encodedData);
|
|
1167
|
+
return this.arrayBufferToBase64(new Uint8Array(hashBuffer));
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Generate random bytes
|
|
1171
|
+
*/
|
|
1172
|
+
randomBytes(length) {
|
|
1173
|
+
const crypto2 = globalThis.crypto;
|
|
1174
|
+
if (!crypto2) {
|
|
1175
|
+
throw new Error("Crypto API not available");
|
|
1176
|
+
}
|
|
1177
|
+
return crypto2.getRandomValues(new Uint8Array(length));
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Generate random string
|
|
1181
|
+
*/
|
|
1182
|
+
randomString(length, charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") {
|
|
1183
|
+
const maxValid = 256 - 256 % charset.length;
|
|
1184
|
+
const result = [];
|
|
1185
|
+
while (result.length < length) {
|
|
1186
|
+
const bytes = this.randomBytes(length - result.length + 16);
|
|
1187
|
+
for (const byte of bytes) {
|
|
1188
|
+
if (byte < maxValid) {
|
|
1189
|
+
result.push(charset[byte % charset.length]);
|
|
1190
|
+
if (result.length === length) break;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
return result.join("");
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Convert ArrayBuffer to base64
|
|
1198
|
+
*/
|
|
1199
|
+
arrayBufferToBase64(buffer) {
|
|
1200
|
+
const bytes = Array.from(buffer);
|
|
1201
|
+
const binary = bytes.map((byte) => String.fromCharCode(byte)).join("");
|
|
1202
|
+
if (typeof btoa !== "undefined") {
|
|
1203
|
+
return btoa(binary);
|
|
1204
|
+
}
|
|
1205
|
+
if (typeof Buffer !== "undefined") {
|
|
1206
|
+
return Buffer.from(binary, "binary").toString("base64");
|
|
1207
|
+
}
|
|
1208
|
+
throw new Error("No base64 encoding available");
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Convert base64 to ArrayBuffer
|
|
1212
|
+
*/
|
|
1213
|
+
base64ToArrayBuffer(base64) {
|
|
1214
|
+
let binary;
|
|
1215
|
+
if (typeof atob !== "undefined") {
|
|
1216
|
+
binary = atob(base64);
|
|
1217
|
+
} else if (typeof Buffer !== "undefined") {
|
|
1218
|
+
binary = Buffer.from(base64, "base64").toString("binary");
|
|
1219
|
+
} else {
|
|
1220
|
+
throw new Error("No base64 decoding available");
|
|
1221
|
+
}
|
|
1222
|
+
const bytes = new Uint8Array(binary.length);
|
|
1223
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1224
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1225
|
+
}
|
|
1226
|
+
return bytes;
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Store key
|
|
1230
|
+
*/
|
|
1231
|
+
storeKey(keyId, key) {
|
|
1232
|
+
this.keys.set(keyId, key);
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Get key
|
|
1236
|
+
*/
|
|
1237
|
+
getKey(keyId) {
|
|
1238
|
+
return this.keys.get(keyId);
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Remove key
|
|
1242
|
+
*/
|
|
1243
|
+
removeKey(keyId) {
|
|
1244
|
+
this.keys.delete(keyId);
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Clear all keys
|
|
1248
|
+
*/
|
|
1249
|
+
clearKeys() {
|
|
1250
|
+
this.keys.clear();
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
var encryption = new EncryptionSystem();
|
|
1254
|
+
var FieldEncryption = class {
|
|
1255
|
+
encryption;
|
|
1256
|
+
key = null;
|
|
1257
|
+
constructor(encryption2) {
|
|
1258
|
+
this.encryption = encryption2;
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Initialize with key
|
|
1262
|
+
*/
|
|
1263
|
+
async initialize(key) {
|
|
1264
|
+
this.key = key;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Encrypt field
|
|
1268
|
+
*/
|
|
1269
|
+
async encryptField(value) {
|
|
1270
|
+
if (!this.key) {
|
|
1271
|
+
throw new Error("Encryption not initialized");
|
|
1272
|
+
}
|
|
1273
|
+
const stringValue = typeof value === "string" ? value : JSON.stringify(value);
|
|
1274
|
+
return this.encryption.encrypt(stringValue, this.key);
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Decrypt field
|
|
1278
|
+
*/
|
|
1279
|
+
async decryptField(encryptedData) {
|
|
1280
|
+
if (!this.key) {
|
|
1281
|
+
throw new Error("Encryption not initialized");
|
|
1282
|
+
}
|
|
1283
|
+
const decrypted = await this.encryption.decrypt(encryptedData, this.key);
|
|
1284
|
+
try {
|
|
1285
|
+
return JSON.parse(decrypted);
|
|
1286
|
+
} catch {
|
|
1287
|
+
return decrypted;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Encrypt object fields
|
|
1292
|
+
*/
|
|
1293
|
+
async encryptFields(obj, fields) {
|
|
1294
|
+
const result = { ...obj };
|
|
1295
|
+
for (const field of fields) {
|
|
1296
|
+
if (field in result) {
|
|
1297
|
+
result[field] = await this.encryptField(result[field]);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return result;
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Decrypt object fields
|
|
1304
|
+
*/
|
|
1305
|
+
async decryptFields(obj, fields) {
|
|
1306
|
+
const result = { ...obj };
|
|
1307
|
+
for (const field of fields) {
|
|
1308
|
+
if (field in result && typeof result[field] === "object" && result[field] !== null) {
|
|
1309
|
+
const encryptedData = result[field];
|
|
1310
|
+
if ("data" in encryptedData && "iv" in encryptedData) {
|
|
1311
|
+
result[field] = await this.decryptField(encryptedData);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return result;
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1318
|
+
var KeyRotationManager = class {
|
|
1319
|
+
encryption;
|
|
1320
|
+
currentKeyId;
|
|
1321
|
+
oldKeys = /* @__PURE__ */ new Map();
|
|
1322
|
+
keyCreationDates = /* @__PURE__ */ new Map();
|
|
1323
|
+
constructor(encryption2, initialKeyId) {
|
|
1324
|
+
this.encryption = encryption2;
|
|
1325
|
+
this.currentKeyId = initialKeyId;
|
|
1326
|
+
this.keyCreationDates.set(initialKeyId, /* @__PURE__ */ new Date());
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Rotate to new key
|
|
1330
|
+
*/
|
|
1331
|
+
async rotate(newKeyId, newKey) {
|
|
1332
|
+
const oldKey = this.encryption.getKey(this.currentKeyId);
|
|
1333
|
+
if (oldKey) {
|
|
1334
|
+
this.oldKeys.set(this.currentKeyId, oldKey);
|
|
1335
|
+
}
|
|
1336
|
+
this.encryption.storeKey(newKeyId, newKey);
|
|
1337
|
+
this.currentKeyId = newKeyId;
|
|
1338
|
+
this.keyCreationDates.set(newKeyId, /* @__PURE__ */ new Date());
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Re-encrypt data with new key
|
|
1342
|
+
*/
|
|
1343
|
+
async reencrypt(encryptedData, oldKeyId) {
|
|
1344
|
+
const oldKey = this.oldKeys.get(oldKeyId) || this.encryption.getKey(oldKeyId);
|
|
1345
|
+
const newKey = this.encryption.getKey(this.currentKeyId);
|
|
1346
|
+
if (!(oldKey && newKey)) {
|
|
1347
|
+
throw new Error("Keys not found");
|
|
1348
|
+
}
|
|
1349
|
+
const decrypted = await this.encryption.decrypt(encryptedData, oldKey);
|
|
1350
|
+
return this.encryption.encrypt(decrypted, newKey);
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Get current key ID
|
|
1354
|
+
*/
|
|
1355
|
+
getCurrentKeyId() {
|
|
1356
|
+
return this.currentKeyId;
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Clean up old keys created before the specified date.
|
|
1360
|
+
* Never removes the current active key.
|
|
1361
|
+
*/
|
|
1362
|
+
cleanupOldKeys(olderThan) {
|
|
1363
|
+
for (const [keyId, createdAt] of this.keyCreationDates.entries()) {
|
|
1364
|
+
if (keyId !== this.currentKeyId && createdAt < olderThan) {
|
|
1365
|
+
this.oldKeys.delete(keyId);
|
|
1366
|
+
this.encryption.removeKey(keyId);
|
|
1367
|
+
this.keyCreationDates.delete(keyId);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
var EnvelopeEncryption = class {
|
|
1373
|
+
encryption;
|
|
1374
|
+
masterKey;
|
|
1375
|
+
constructor(encryption2, masterKey) {
|
|
1376
|
+
this.encryption = encryption2;
|
|
1377
|
+
this.masterKey = masterKey;
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Encrypt with envelope encryption
|
|
1381
|
+
*/
|
|
1382
|
+
async encrypt(data) {
|
|
1383
|
+
const dek = await this.encryption.generateKey();
|
|
1384
|
+
const encryptedData = await this.encryption.encrypt(data, dek);
|
|
1385
|
+
const dekRaw = await this.encryption.exportKey(dek);
|
|
1386
|
+
const dekBase64 = this.arrayBufferToBase64(new Uint8Array(dekRaw));
|
|
1387
|
+
const encryptedKey = await this.encryption.encrypt(dekBase64, this.masterKey);
|
|
1388
|
+
return { encryptedData, encryptedKey };
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Decrypt with envelope encryption
|
|
1392
|
+
*/
|
|
1393
|
+
async decrypt(encryptedData, encryptedKey) {
|
|
1394
|
+
const dekBase64 = await this.encryption.decrypt(encryptedKey, this.masterKey);
|
|
1395
|
+
const dekRaw = this.base64ToArrayBuffer(dekBase64);
|
|
1396
|
+
const dek = await this.encryption.importKey(dekRaw.buffer);
|
|
1397
|
+
return this.encryption.decrypt(encryptedData, dek);
|
|
1398
|
+
}
|
|
1399
|
+
arrayBufferToBase64(buffer) {
|
|
1400
|
+
const bytes = Array.from(buffer);
|
|
1401
|
+
const binary = bytes.map((byte) => String.fromCharCode(byte)).join("");
|
|
1402
|
+
return typeof btoa !== "undefined" ? btoa(binary) : Buffer.from(binary, "binary").toString("base64");
|
|
1403
|
+
}
|
|
1404
|
+
base64ToArrayBuffer(base64) {
|
|
1405
|
+
const binary = typeof atob !== "undefined" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
|
|
1406
|
+
const bytes = new Uint8Array(binary.length);
|
|
1407
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1408
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1409
|
+
}
|
|
1410
|
+
return bytes;
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
function maskEmail(email) {
|
|
1414
|
+
const [local, domain] = email.split("@");
|
|
1415
|
+
if (!(local && domain)) return email;
|
|
1416
|
+
const maskedLocal = local.length > 2 ? local[0] + "*".repeat(local.length - 2) + local[local.length - 1] : `${local[0]}*`;
|
|
1417
|
+
return `${maskedLocal}@${domain}`;
|
|
1418
|
+
}
|
|
1419
|
+
function maskPhone(phone) {
|
|
1420
|
+
const digits = phone.replace(/\D/g, "");
|
|
1421
|
+
if (digits.length < 4) return phone;
|
|
1422
|
+
const lastFour = digits.slice(-4);
|
|
1423
|
+
const masked = "*".repeat(digits.length - 4) + lastFour;
|
|
1424
|
+
return phone.replace(/\d/g, (char, index) => {
|
|
1425
|
+
const digitIndex = phone.slice(0, index + 1).replace(/\D/g, "").length - 1;
|
|
1426
|
+
return masked[digitIndex] || char;
|
|
1427
|
+
});
|
|
1082
1428
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1429
|
+
function maskCreditCard(card) {
|
|
1430
|
+
const digits = card.replace(/\D/g, "");
|
|
1431
|
+
if (digits.length < 4) return card;
|
|
1432
|
+
const lastFour = digits.slice(-4);
|
|
1433
|
+
return `****-****-****-${lastFour}`;
|
|
1434
|
+
}
|
|
1435
|
+
function maskSSN(ssn) {
|
|
1436
|
+
const digits = ssn.replace(/\D/g, "");
|
|
1437
|
+
if (digits.length !== 9) return ssn;
|
|
1438
|
+
return `***-**-${digits.slice(-4)}`;
|
|
1439
|
+
}
|
|
1440
|
+
function maskString(str, keepChars = 1) {
|
|
1441
|
+
if (str.length <= keepChars * 2) {
|
|
1442
|
+
return "*".repeat(str.length);
|
|
1443
|
+
}
|
|
1444
|
+
const prefix = str.slice(0, keepChars);
|
|
1445
|
+
const suffix = str.slice(-keepChars);
|
|
1446
|
+
const masked = "*".repeat(str.length - keepChars * 2);
|
|
1447
|
+
return `${prefix}${masked}${suffix}`;
|
|
1448
|
+
}
|
|
1449
|
+
var DataMasking = {
|
|
1450
|
+
maskEmail,
|
|
1451
|
+
maskPhone,
|
|
1452
|
+
maskCreditCard,
|
|
1453
|
+
maskSSN,
|
|
1454
|
+
maskString
|
|
1087
1455
|
};
|
|
1088
|
-
|
|
1089
|
-
const
|
|
1090
|
-
|
|
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;
|
|
1456
|
+
function generateToken(length = 32) {
|
|
1457
|
+
const bytes = encryption.randomBytes(length);
|
|
1458
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1111
1459
|
}
|
|
1112
|
-
function
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1460
|
+
function generateUUID() {
|
|
1461
|
+
const crypto2 = globalThis.crypto;
|
|
1462
|
+
if (!crypto2) {
|
|
1463
|
+
throw new Error("Crypto API not available");
|
|
1464
|
+
}
|
|
1465
|
+
return crypto2.randomUUID();
|
|
1466
|
+
}
|
|
1467
|
+
function generateAPIKey(prefix = "sk") {
|
|
1468
|
+
const token = generateToken(32);
|
|
1469
|
+
return `${prefix}_${token}`;
|
|
1470
|
+
}
|
|
1471
|
+
function generateSessionID() {
|
|
1472
|
+
return generateToken(64);
|
|
1473
|
+
}
|
|
1474
|
+
var TokenGenerator = {
|
|
1475
|
+
generate: generateToken,
|
|
1476
|
+
generateUUID,
|
|
1477
|
+
generateAPIKey,
|
|
1478
|
+
generateSessionID
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1481
|
+
// src/gdpr.ts
|
|
1482
|
+
import { createHash, createHmac as createHmac2 } from "crypto";
|
|
1483
|
+
|
|
1484
|
+
// src/gdpr-storage.ts
|
|
1485
|
+
var InMemoryBreachStorage = class {
|
|
1486
|
+
breaches = /* @__PURE__ */ new Map();
|
|
1487
|
+
async setBreach(breach) {
|
|
1488
|
+
this.breaches.set(breach.id, breach);
|
|
1489
|
+
}
|
|
1490
|
+
async getBreach(id) {
|
|
1491
|
+
return this.breaches.get(id);
|
|
1492
|
+
}
|
|
1493
|
+
async getAllBreaches() {
|
|
1494
|
+
return Array.from(this.breaches.values());
|
|
1495
|
+
}
|
|
1496
|
+
async updateBreach(id, updates) {
|
|
1497
|
+
const existing = this.breaches.get(id);
|
|
1498
|
+
if (existing) {
|
|
1499
|
+
this.breaches.set(id, { ...existing, ...updates });
|
|
1125
1500
|
}
|
|
1126
|
-
}
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
var InMemoryGDPRStorage = class {
|
|
1504
|
+
consents = /* @__PURE__ */ new Map();
|
|
1505
|
+
deletionRequests = /* @__PURE__ */ new Map();
|
|
1506
|
+
// ── Consent Records ──────────────────────────────────────────────
|
|
1507
|
+
async setConsent(userId, type, record) {
|
|
1508
|
+
this.consents.set(`${userId}:${type}`, record);
|
|
1509
|
+
}
|
|
1510
|
+
async getConsent(userId, type) {
|
|
1511
|
+
return this.consents.get(`${userId}:${type}`);
|
|
1512
|
+
}
|
|
1513
|
+
async getConsentsByUser(userId) {
|
|
1514
|
+
return Array.from(this.consents.values()).filter((c) => c.userId === userId);
|
|
1515
|
+
}
|
|
1516
|
+
async getAllConsents() {
|
|
1517
|
+
return Array.from(this.consents.values());
|
|
1518
|
+
}
|
|
1519
|
+
// ── Deletion Requests ────────────────────────────────────────────
|
|
1520
|
+
async setDeletionRequest(request) {
|
|
1521
|
+
this.deletionRequests.set(request.id, request);
|
|
1522
|
+
}
|
|
1523
|
+
async getDeletionRequest(requestId) {
|
|
1524
|
+
return this.deletionRequests.get(requestId);
|
|
1525
|
+
}
|
|
1526
|
+
async getDeletionRequestsByUser(userId) {
|
|
1527
|
+
return Array.from(this.deletionRequests.values()).filter((r) => r.userId === userId);
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
|
|
1531
|
+
// src/logger.ts
|
|
1532
|
+
var securityLogger = console;
|
|
1533
|
+
function configureSecurityLogger(logger) {
|
|
1534
|
+
securityLogger = logger;
|
|
1535
|
+
}
|
|
1536
|
+
function getSecurityLogger() {
|
|
1537
|
+
return securityLogger;
|
|
1127
1538
|
}
|
|
1128
1539
|
|
|
1129
|
-
// src/
|
|
1130
|
-
var
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1540
|
+
// src/gdpr.ts
|
|
1541
|
+
var ConsentManager = class {
|
|
1542
|
+
storage;
|
|
1543
|
+
consentVersion = "1.0.0";
|
|
1544
|
+
constructor(storage) {
|
|
1545
|
+
this.storage = storage;
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Grant consent
|
|
1549
|
+
*/
|
|
1550
|
+
async grantConsent(userId, type, source = "explicit", expiresIn) {
|
|
1551
|
+
const consent = {
|
|
1552
|
+
id: crypto.randomUUID(),
|
|
1553
|
+
userId,
|
|
1554
|
+
type,
|
|
1555
|
+
granted: true,
|
|
1556
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1557
|
+
expiresAt: expiresIn ? new Date(Date.now() + expiresIn).toISOString() : void 0,
|
|
1558
|
+
source,
|
|
1559
|
+
version: this.consentVersion
|
|
1560
|
+
};
|
|
1561
|
+
await this.storage.setConsent(userId, type, consent);
|
|
1562
|
+
return consent;
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Revoke consent
|
|
1566
|
+
*/
|
|
1567
|
+
async revokeConsent(userId, type) {
|
|
1568
|
+
const existing = await this.storage.getConsent(userId, type);
|
|
1569
|
+
if (existing) {
|
|
1570
|
+
existing.granted = false;
|
|
1571
|
+
existing.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1572
|
+
await this.storage.setConsent(userId, type, existing);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Check if consent is granted
|
|
1577
|
+
*/
|
|
1578
|
+
async hasConsent(userId, type) {
|
|
1579
|
+
const consent = await this.storage.getConsent(userId, type);
|
|
1580
|
+
if (!consent?.granted) {
|
|
1581
|
+
return false;
|
|
1582
|
+
}
|
|
1583
|
+
if (consent.expiresAt && new Date(consent.expiresAt) < /* @__PURE__ */ new Date()) {
|
|
1584
|
+
return false;
|
|
1585
|
+
}
|
|
1586
|
+
return true;
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Get all consents for user
|
|
1590
|
+
*/
|
|
1591
|
+
async getUserConsents(userId) {
|
|
1592
|
+
return this.storage.getConsentsByUser(userId);
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Update consent version
|
|
1596
|
+
*/
|
|
1597
|
+
setConsentVersion(version) {
|
|
1598
|
+
this.consentVersion = version;
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Check if consent needs renewal
|
|
1602
|
+
*/
|
|
1603
|
+
async needsRenewal(userId, type, maxAge) {
|
|
1604
|
+
const consent = await this.storage.getConsent(userId, type);
|
|
1605
|
+
if (!consent?.granted) {
|
|
1606
|
+
return true;
|
|
1607
|
+
}
|
|
1608
|
+
const age = Date.now() - new Date(consent.timestamp).getTime();
|
|
1609
|
+
return age >= maxAge;
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Get consent statistics
|
|
1613
|
+
*/
|
|
1614
|
+
async getStatistics() {
|
|
1615
|
+
const consents = await this.storage.getAllConsents();
|
|
1616
|
+
const now = /* @__PURE__ */ new Date();
|
|
1617
|
+
const granted = consents.filter((c) => c.granted).length;
|
|
1618
|
+
const revoked = consents.filter((c) => !c.granted).length;
|
|
1619
|
+
const expired = consents.filter((c) => c.expiresAt && new Date(c.expiresAt) < now).length;
|
|
1620
|
+
const byType = consents.reduce(
|
|
1621
|
+
(acc, c) => {
|
|
1622
|
+
acc[c.type] = (acc[c.type] || 0) + 1;
|
|
1623
|
+
return acc;
|
|
1624
|
+
},
|
|
1625
|
+
{}
|
|
1626
|
+
);
|
|
1627
|
+
return {
|
|
1628
|
+
total: consents.length,
|
|
1629
|
+
granted,
|
|
1630
|
+
revoked,
|
|
1631
|
+
expired,
|
|
1632
|
+
byType
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1134
1635
|
};
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1636
|
+
function escapeCsvField(value) {
|
|
1637
|
+
let safe = /^[=+\-@\t\r]/.test(value) ? `'${value}` : value;
|
|
1638
|
+
safe = safe.replace(/"/g, '""');
|
|
1639
|
+
return `"${safe}"`;
|
|
1640
|
+
}
|
|
1641
|
+
var DataExportSystem = class {
|
|
1642
|
+
/**
|
|
1643
|
+
* Export user data
|
|
1644
|
+
*/
|
|
1645
|
+
async exportUserData(userId, getUserData, format = "json") {
|
|
1646
|
+
const data = await getUserData(userId);
|
|
1647
|
+
const exportData = {
|
|
1648
|
+
userId,
|
|
1649
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1650
|
+
data: {
|
|
1651
|
+
profile: data.profile,
|
|
1652
|
+
activities: data.activities,
|
|
1653
|
+
consents: data.consents,
|
|
1654
|
+
dataProcessing: []
|
|
1655
|
+
},
|
|
1656
|
+
format
|
|
1657
|
+
};
|
|
1658
|
+
return exportData;
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Format export as JSON
|
|
1662
|
+
*/
|
|
1663
|
+
formatAsJSON(exportData) {
|
|
1664
|
+
return JSON.stringify(exportData, null, 2);
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Format export as CSV
|
|
1668
|
+
*/
|
|
1669
|
+
formatAsCSV(exportData) {
|
|
1670
|
+
const lines = [];
|
|
1671
|
+
lines.push("Type,Key,Value");
|
|
1672
|
+
Object.entries(exportData.data.profile).forEach(([key, value]) => {
|
|
1673
|
+
lines.push(`Profile,${escapeCsvField(key)},${escapeCsvField(String(value))}`);
|
|
1674
|
+
});
|
|
1675
|
+
exportData.data.activities.forEach((activity, index) => {
|
|
1676
|
+
Object.entries(activity).forEach(([key, value]) => {
|
|
1677
|
+
lines.push(`Activity ${index + 1},${escapeCsvField(key)},${escapeCsvField(String(value))}`);
|
|
1678
|
+
});
|
|
1679
|
+
});
|
|
1680
|
+
return lines.join("\n");
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Create download link
|
|
1684
|
+
*/
|
|
1685
|
+
createDownloadLink(content, _filename, mimeType) {
|
|
1686
|
+
const blob = new Blob([content], { type: mimeType });
|
|
1687
|
+
return URL.createObjectURL(blob);
|
|
1688
|
+
}
|
|
1144
1689
|
};
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1690
|
+
var DataDeletionSystem = class {
|
|
1691
|
+
storage;
|
|
1692
|
+
constructor(storage) {
|
|
1693
|
+
this.storage = storage;
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Request data deletion
|
|
1697
|
+
*/
|
|
1698
|
+
async requestDeletion(userId, dataCategories, reason) {
|
|
1699
|
+
const request = {
|
|
1700
|
+
id: crypto.randomUUID(),
|
|
1701
|
+
userId,
|
|
1702
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1703
|
+
status: "pending",
|
|
1704
|
+
dataCategories,
|
|
1705
|
+
reason
|
|
1706
|
+
};
|
|
1707
|
+
await this.storage.setDeletionRequest(request);
|
|
1708
|
+
return request;
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Process deletion request
|
|
1712
|
+
*/
|
|
1713
|
+
async processDeletion(requestId, deleteData) {
|
|
1714
|
+
const request = await this.storage.getDeletionRequest(requestId);
|
|
1715
|
+
if (!request) {
|
|
1716
|
+
throw new Error("Deletion request not found");
|
|
1151
1717
|
}
|
|
1718
|
+
request.status = "processing";
|
|
1719
|
+
await this.storage.setDeletionRequest(request);
|
|
1720
|
+
try {
|
|
1721
|
+
const result = await deleteData(request.userId, request.dataCategories);
|
|
1722
|
+
request.status = "completed";
|
|
1723
|
+
request.processedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1724
|
+
request.deletedData = result.deleted;
|
|
1725
|
+
request.retainedData = result.retained;
|
|
1726
|
+
await this.storage.setDeletionRequest(request);
|
|
1727
|
+
} catch (error) {
|
|
1728
|
+
request.status = "failed";
|
|
1729
|
+
await this.storage.setDeletionRequest(request);
|
|
1730
|
+
throw error;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Get deletion request
|
|
1735
|
+
*/
|
|
1736
|
+
async getRequest(requestId) {
|
|
1737
|
+
return this.storage.getDeletionRequest(requestId);
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Get user deletion requests
|
|
1741
|
+
*/
|
|
1742
|
+
async getUserRequests(userId) {
|
|
1743
|
+
return this.storage.getDeletionRequestsByUser(userId);
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Check if data can be deleted
|
|
1747
|
+
*/
|
|
1748
|
+
canDelete(_dataCategory, legalBasis) {
|
|
1749
|
+
if (legalBasis === "legal_obligation" || legalBasis === "vital_interest") {
|
|
1750
|
+
return false;
|
|
1751
|
+
}
|
|
1752
|
+
return true;
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* Calculate retention period
|
|
1756
|
+
*/
|
|
1757
|
+
calculateRetentionEnd(createdAt, retentionPeriod) {
|
|
1758
|
+
return new Date(createdAt.getTime() + retentionPeriod * 24 * 60 * 60 * 1e3);
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Check if data should be deleted (retention period expired)
|
|
1762
|
+
*/
|
|
1763
|
+
shouldDelete(createdAt, retentionPeriod) {
|
|
1764
|
+
const retentionEnd = this.calculateRetentionEnd(createdAt, retentionPeriod);
|
|
1765
|
+
return /* @__PURE__ */ new Date() > retentionEnd;
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
function hashValue(value) {
|
|
1769
|
+
const digest = createHash("sha256").update(value).digest("hex");
|
|
1770
|
+
return `hash_${digest}`;
|
|
1771
|
+
}
|
|
1772
|
+
function anonymizeUser(user) {
|
|
1773
|
+
return {
|
|
1774
|
+
...user,
|
|
1775
|
+
email: hashValue(user.email),
|
|
1776
|
+
name: "Anonymous User",
|
|
1777
|
+
phone: void 0,
|
|
1778
|
+
address: void 0,
|
|
1779
|
+
ip: void 0
|
|
1152
1780
|
};
|
|
1153
1781
|
}
|
|
1154
|
-
function
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1782
|
+
function pseudonymize(value, key) {
|
|
1783
|
+
const hmac = createHmac2("sha256", key).update(value).digest("hex");
|
|
1784
|
+
return `pseudo_${hmac.substring(0, 16)}`;
|
|
1785
|
+
}
|
|
1786
|
+
function anonymizeDataset(data, sensitiveFields) {
|
|
1787
|
+
return data.map((item) => {
|
|
1788
|
+
const anonymized = { ...item };
|
|
1789
|
+
sensitiveFields.forEach((field) => {
|
|
1790
|
+
if (field in anonymized && typeof anonymized[field] === "string") {
|
|
1791
|
+
anonymized[field] = hashValue(anonymized[field]);
|
|
1792
|
+
}
|
|
1793
|
+
});
|
|
1794
|
+
return anonymized;
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
function checkKAnonymity(data, quasiIdentifiers, k) {
|
|
1798
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1799
|
+
data.forEach((item) => {
|
|
1800
|
+
const key = quasiIdentifiers.map((field) => String(item[field])).join("|");
|
|
1801
|
+
groups.set(key, (groups.get(key) || 0) + 1);
|
|
1162
1802
|
});
|
|
1803
|
+
return Array.from(groups.values()).every((count) => count >= k);
|
|
1163
1804
|
}
|
|
1164
|
-
var
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1805
|
+
var DataAnonymization = {
|
|
1806
|
+
anonymizeUser,
|
|
1807
|
+
pseudonymize,
|
|
1808
|
+
hashValue,
|
|
1809
|
+
anonymizeDataset,
|
|
1810
|
+
checkKAnonymity
|
|
1811
|
+
};
|
|
1812
|
+
var PrivacyPolicyManager = class {
|
|
1813
|
+
policies = /* @__PURE__ */ new Map();
|
|
1814
|
+
currentVersion = "1.0.0";
|
|
1815
|
+
/**
|
|
1816
|
+
* Add policy version
|
|
1817
|
+
*/
|
|
1818
|
+
addPolicy(version, content, effectiveDate) {
|
|
1819
|
+
this.policies.set(version, { version, content, effectiveDate });
|
|
1820
|
+
this.currentVersion = version;
|
|
1168
1821
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
return `session_${Date.now().toString(36)}`;
|
|
1175
|
-
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Get current policy
|
|
1824
|
+
*/
|
|
1825
|
+
getCurrentPolicy() {
|
|
1826
|
+
return this.policies.get(this.currentVersion);
|
|
1176
1827
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
}
|
|
1828
|
+
/**
|
|
1829
|
+
* Get policy by version
|
|
1830
|
+
*/
|
|
1831
|
+
getPolicy(version) {
|
|
1832
|
+
return this.policies.get(version);
|
|
1183
1833
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
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 };
|
|
1834
|
+
/**
|
|
1835
|
+
* Check if user accepted current policy
|
|
1836
|
+
*/
|
|
1837
|
+
hasAcceptedCurrent(userAcceptedVersion) {
|
|
1838
|
+
return userAcceptedVersion === this.currentVersion;
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Get all versions
|
|
1842
|
+
*/
|
|
1843
|
+
getAllVersions() {
|
|
1844
|
+
return Array.from(this.policies.keys());
|
|
1266
1845
|
}
|
|
1267
1846
|
};
|
|
1268
|
-
var
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1847
|
+
var CookieConsentManager = class {
|
|
1848
|
+
config = {
|
|
1849
|
+
necessary: true,
|
|
1850
|
+
functional: false,
|
|
1851
|
+
analytics: false,
|
|
1852
|
+
marketing: false
|
|
1853
|
+
};
|
|
1854
|
+
/**
|
|
1855
|
+
* Set consent configuration
|
|
1856
|
+
*/
|
|
1857
|
+
setConsent(config) {
|
|
1858
|
+
this.config = { ...this.config, ...config };
|
|
1859
|
+
this.saveToStorage();
|
|
1274
1860
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
}
|
|
1280
|
-
return this.repository;
|
|
1861
|
+
/**
|
|
1862
|
+
* Get consent configuration
|
|
1863
|
+
*/
|
|
1864
|
+
getConsent() {
|
|
1865
|
+
return { ...this.config };
|
|
1281
1866
|
}
|
|
1282
1867
|
/**
|
|
1283
|
-
*
|
|
1868
|
+
* Check if specific consent is granted
|
|
1284
1869
|
*/
|
|
1285
|
-
|
|
1286
|
-
|
|
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;
|
|
1870
|
+
hasConsent(type) {
|
|
1871
|
+
return this.config[type];
|
|
1310
1872
|
}
|
|
1311
1873
|
/**
|
|
1312
|
-
*
|
|
1874
|
+
* Save to storage
|
|
1313
1875
|
*/
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
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
|
-
};
|
|
1876
|
+
saveToStorage() {
|
|
1877
|
+
if (typeof localStorage !== "undefined") {
|
|
1878
|
+
localStorage.setItem("cookie-consent", JSON.stringify(this.config));
|
|
1375
1879
|
}
|
|
1376
1880
|
}
|
|
1377
1881
|
/**
|
|
1378
|
-
*
|
|
1882
|
+
* Load from storage
|
|
1379
1883
|
*/
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1384
|
-
if (!result.success) {
|
|
1884
|
+
loadFromStorage() {
|
|
1885
|
+
if (typeof localStorage !== "undefined") {
|
|
1886
|
+
const stored = localStorage.getItem("cookie-consent");
|
|
1887
|
+
if (stored) {
|
|
1385
1888
|
try {
|
|
1386
|
-
const
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1889
|
+
const parsed = JSON.parse(stored);
|
|
1890
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
1891
|
+
this.config = {
|
|
1892
|
+
necessary: true,
|
|
1893
|
+
// always required
|
|
1894
|
+
analytics: typeof parsed.analytics === "boolean" ? parsed.analytics : false,
|
|
1895
|
+
marketing: typeof parsed.marketing === "boolean" ? parsed.marketing : false,
|
|
1896
|
+
functional: typeof parsed.functional === "boolean" ? parsed.functional : true
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1390
1899
|
} catch {
|
|
1391
|
-
devLogger3.error("Session invalidation failed", {
|
|
1392
|
-
errors: result.errors
|
|
1393
|
-
});
|
|
1394
1900
|
}
|
|
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
1901
|
}
|
|
1405
|
-
return {
|
|
1406
|
-
success: false,
|
|
1407
|
-
errors: [
|
|
1408
|
-
{
|
|
1409
|
-
message: "Session invalidation failed",
|
|
1410
|
-
path: "invalidateSession"
|
|
1411
|
-
}
|
|
1412
|
-
]
|
|
1413
|
-
};
|
|
1414
1902
|
}
|
|
1415
1903
|
}
|
|
1416
1904
|
/**
|
|
1417
|
-
*
|
|
1905
|
+
* Clear consent
|
|
1418
1906
|
*/
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
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
|
-
};
|
|
1907
|
+
clearConsent() {
|
|
1908
|
+
this.config = {
|
|
1909
|
+
necessary: true,
|
|
1910
|
+
functional: false,
|
|
1911
|
+
analytics: false,
|
|
1912
|
+
marketing: false
|
|
1913
|
+
};
|
|
1914
|
+
if (typeof localStorage !== "undefined") {
|
|
1915
|
+
localStorage.removeItem("cookie-consent");
|
|
1455
1916
|
}
|
|
1456
1917
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
}
|
|
1918
|
+
};
|
|
1919
|
+
var DataBreachManager = class {
|
|
1920
|
+
storage;
|
|
1921
|
+
constructor(storage) {
|
|
1922
|
+
if (storage) {
|
|
1923
|
+
this.storage = storage;
|
|
1924
|
+
} else {
|
|
1925
|
+
getSecurityLogger().warn(
|
|
1926
|
+
"DataBreachManager: using in-memory storage \u2014 breach records will be lost on restart. For production GDPR compliance, pass a database-backed BreachStorage."
|
|
1927
|
+
);
|
|
1928
|
+
this.storage = new InMemoryBreachStorage();
|
|
1469
1929
|
}
|
|
1470
|
-
return result;
|
|
1471
1930
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Report data breach
|
|
1933
|
+
*/
|
|
1934
|
+
async reportBreach(breach) {
|
|
1935
|
+
const fullBreach = {
|
|
1936
|
+
...breach,
|
|
1937
|
+
id: crypto.randomUUID(),
|
|
1938
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1939
|
+
status: "detected"
|
|
1940
|
+
};
|
|
1941
|
+
await this.storage.setBreach(fullBreach);
|
|
1942
|
+
if (fullBreach.severity === "critical") {
|
|
1943
|
+
await this.notifyAuthorities(fullBreach);
|
|
1486
1944
|
}
|
|
1487
|
-
return
|
|
1945
|
+
return fullBreach;
|
|
1488
1946
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
} catch {
|
|
1499
|
-
devLogger3.error("Failed to update session", { errors: result.errors });
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
return result;
|
|
1947
|
+
/**
|
|
1948
|
+
* Notify authorities (required within 72 hours under GDPR)
|
|
1949
|
+
*/
|
|
1950
|
+
async notifyAuthorities(breach) {
|
|
1951
|
+
await this.storage.updateBreach(breach.id, {
|
|
1952
|
+
reportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1953
|
+
status: "notified"
|
|
1954
|
+
});
|
|
1955
|
+
getSecurityLogger().info("Breach reported to authorities", { breachId: breach.id });
|
|
1503
1956
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
errors: result.errors
|
|
1512
|
-
});
|
|
1513
|
-
} catch {
|
|
1514
|
-
devLogger3.error("Failed to delete session", { errors: result.errors });
|
|
1515
|
-
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Notify affected users
|
|
1959
|
+
*/
|
|
1960
|
+
async notifyAffectedUsers(breachId, notifyFn) {
|
|
1961
|
+
const breach = await this.storage.getBreach(breachId);
|
|
1962
|
+
if (!breach) {
|
|
1963
|
+
throw new Error("Breach not found");
|
|
1516
1964
|
}
|
|
1517
|
-
|
|
1965
|
+
for (const userId of breach.affectedUsers) {
|
|
1966
|
+
await notifyFn(userId, breach);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* Check if breach notification is required
|
|
1971
|
+
*/
|
|
1972
|
+
requiresNotification(breach) {
|
|
1973
|
+
return breach.severity === "high" || breach.severity === "critical" || breach.dataCategories.includes("sensitive") || breach.dataCategories.includes("financial");
|
|
1974
|
+
}
|
|
1975
|
+
/**
|
|
1976
|
+
* Get breach
|
|
1977
|
+
*/
|
|
1978
|
+
async getBreach(id) {
|
|
1979
|
+
return this.storage.getBreach(id);
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Get all breaches
|
|
1983
|
+
*/
|
|
1984
|
+
async getAllBreaches() {
|
|
1985
|
+
return this.storage.getAllBreaches();
|
|
1518
1986
|
}
|
|
1519
1987
|
};
|
|
1520
|
-
|
|
1988
|
+
function createConsentManager(storage) {
|
|
1989
|
+
return new ConsentManager(storage);
|
|
1990
|
+
}
|
|
1991
|
+
function createDataDeletionSystem(storage) {
|
|
1992
|
+
return new DataDeletionSystem(storage);
|
|
1993
|
+
}
|
|
1994
|
+
var dataExportSystem = new DataExportSystem();
|
|
1995
|
+
var privacyPolicyManager = new PrivacyPolicyManager();
|
|
1996
|
+
var cookieConsentManager = new CookieConsentManager();
|
|
1997
|
+
function createDataBreachManager(storage) {
|
|
1998
|
+
return new DataBreachManager(storage);
|
|
1999
|
+
}
|
|
2000
|
+
var dataBreachManager = new DataBreachManager();
|
|
1521
2001
|
|
|
1522
|
-
// src/
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
constructor(
|
|
1526
|
-
|
|
1527
|
-
this.code = code;
|
|
1528
|
-
this.cause = cause;
|
|
1529
|
-
this.name = "AppError";
|
|
1530
|
-
if (cause) {
|
|
1531
|
-
this.cause = cause;
|
|
1532
|
-
}
|
|
2002
|
+
// src/headers.ts
|
|
2003
|
+
var SecurityHeaders = class {
|
|
2004
|
+
config;
|
|
2005
|
+
constructor(config = {}) {
|
|
2006
|
+
this.config = config;
|
|
1533
2007
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
2008
|
+
/**
|
|
2009
|
+
* Get all security headers
|
|
2010
|
+
*/
|
|
2011
|
+
getHeaders() {
|
|
2012
|
+
const headers = {};
|
|
2013
|
+
if (this.config.contentSecurityPolicy) {
|
|
2014
|
+
headers["Content-Security-Policy"] = this.buildCSP(this.config.contentSecurityPolicy);
|
|
2015
|
+
}
|
|
2016
|
+
if (this.config.strictTransportSecurity) {
|
|
2017
|
+
headers["Strict-Transport-Security"] = this.buildHSTS(this.config.strictTransportSecurity);
|
|
2018
|
+
}
|
|
2019
|
+
if (this.config.xFrameOptions) {
|
|
2020
|
+
headers["X-Frame-Options"] = this.config.xFrameOptions;
|
|
2021
|
+
}
|
|
2022
|
+
if (this.config.xContentTypeOptions !== false) {
|
|
2023
|
+
headers["X-Content-Type-Options"] = "nosniff";
|
|
2024
|
+
}
|
|
2025
|
+
if (this.config.referrerPolicy) {
|
|
2026
|
+
headers["Referrer-Policy"] = this.config.referrerPolicy;
|
|
2027
|
+
}
|
|
2028
|
+
if (this.config.permissionsPolicy) {
|
|
2029
|
+
headers["Permissions-Policy"] = this.buildPermissionsPolicy(this.config.permissionsPolicy);
|
|
2030
|
+
}
|
|
2031
|
+
if (this.config.crossOriginEmbedderPolicy) {
|
|
2032
|
+
headers["Cross-Origin-Embedder-Policy"] = this.config.crossOriginEmbedderPolicy;
|
|
2033
|
+
}
|
|
2034
|
+
if (this.config.crossOriginOpenerPolicy) {
|
|
2035
|
+
headers["Cross-Origin-Opener-Policy"] = this.config.crossOriginOpenerPolicy;
|
|
2036
|
+
}
|
|
2037
|
+
if (this.config.crossOriginResourcePolicy) {
|
|
2038
|
+
headers["Cross-Origin-Resource-Policy"] = this.config.crossOriginResourcePolicy;
|
|
2039
|
+
}
|
|
2040
|
+
return headers;
|
|
1545
2041
|
}
|
|
1546
2042
|
/**
|
|
1547
|
-
*
|
|
1548
|
-
* @param credentials User login credentials
|
|
1549
|
-
* @param req Optional PayloadRequest for context
|
|
1550
|
-
* @returns Login result with user data and token
|
|
2043
|
+
* Build Content Security Policy header
|
|
1551
2044
|
*/
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
throw new AppError(
|
|
1580
|
-
ErrorCode.AUTHENTICATION_ERROR,
|
|
1581
|
-
"Login failed",
|
|
1582
|
-
error instanceof Error ? error : new Error(String(error))
|
|
1583
|
-
);
|
|
2045
|
+
buildCSP(config) {
|
|
2046
|
+
if (typeof config === "string") {
|
|
2047
|
+
return config;
|
|
2048
|
+
}
|
|
2049
|
+
const directives = [];
|
|
2050
|
+
const addDirective = (name, values) => {
|
|
2051
|
+
if (values && values.length > 0) {
|
|
2052
|
+
directives.push(`${name} ${values.join(" ")}`);
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
2055
|
+
addDirective("default-src", config.defaultSrc);
|
|
2056
|
+
addDirective("script-src", config.scriptSrc);
|
|
2057
|
+
addDirective("style-src", config.styleSrc);
|
|
2058
|
+
addDirective("img-src", config.imgSrc);
|
|
2059
|
+
addDirective("font-src", config.fontSrc);
|
|
2060
|
+
addDirective("connect-src", config.connectSrc);
|
|
2061
|
+
addDirective("frame-src", config.frameSrc);
|
|
2062
|
+
addDirective("object-src", config.objectSrc);
|
|
2063
|
+
addDirective("media-src", config.mediaSrc);
|
|
2064
|
+
addDirective("worker-src", config.workerSrc);
|
|
2065
|
+
addDirective("child-src", config.childSrc);
|
|
2066
|
+
addDirective("form-action", config.formAction);
|
|
2067
|
+
addDirective("frame-ancestors", config.frameAncestors);
|
|
2068
|
+
addDirective("base-uri", config.baseUri);
|
|
2069
|
+
addDirective("manifest-src", config.manifestSrc);
|
|
2070
|
+
if (config.upgradeInsecureRequests) {
|
|
2071
|
+
directives.push("upgrade-insecure-requests");
|
|
1584
2072
|
}
|
|
2073
|
+
if (config.blockAllMixedContent) {
|
|
2074
|
+
directives.push("block-all-mixed-content");
|
|
2075
|
+
}
|
|
2076
|
+
if (config.reportUri) {
|
|
2077
|
+
directives.push(`report-uri ${config.reportUri}`);
|
|
2078
|
+
}
|
|
2079
|
+
if (config.reportTo) {
|
|
2080
|
+
directives.push(`report-to ${config.reportTo}`);
|
|
2081
|
+
}
|
|
2082
|
+
return directives.join("; ");
|
|
1585
2083
|
}
|
|
1586
2084
|
/**
|
|
1587
|
-
*
|
|
1588
|
-
* @param userId User ID to verify
|
|
1589
|
-
* @returns User data if exists
|
|
2085
|
+
* Build HSTS header
|
|
1590
2086
|
*/
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
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
|
-
);
|
|
2087
|
+
buildHSTS(config) {
|
|
2088
|
+
if (config === true) {
|
|
2089
|
+
return "max-age=31536000; includeSubDomains";
|
|
2090
|
+
}
|
|
2091
|
+
if (config === false) {
|
|
2092
|
+
return "";
|
|
2093
|
+
}
|
|
2094
|
+
const parts = [`max-age=${config.maxAge}`];
|
|
2095
|
+
if (config.includeSubDomains) {
|
|
2096
|
+
parts.push("includeSubDomains");
|
|
1618
2097
|
}
|
|
2098
|
+
if (config.preload) {
|
|
2099
|
+
parts.push("preload");
|
|
2100
|
+
}
|
|
2101
|
+
return parts.join("; ");
|
|
1619
2102
|
}
|
|
1620
2103
|
/**
|
|
1621
|
-
*
|
|
1622
|
-
* @param email User email address
|
|
1623
|
-
* @returns Password reset token
|
|
2104
|
+
* Build Permissions-Policy header
|
|
1624
2105
|
*/
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
-
);
|
|
2106
|
+
buildPermissionsPolicy(config) {
|
|
2107
|
+
if (typeof config === "string") {
|
|
2108
|
+
return config;
|
|
1652
2109
|
}
|
|
2110
|
+
const policies = [];
|
|
2111
|
+
Object.entries(config).forEach(([feature, origins]) => {
|
|
2112
|
+
if (!origins || origins.length === 0) {
|
|
2113
|
+
policies.push(`${feature}=()`);
|
|
2114
|
+
} else if (origins.includes("*")) {
|
|
2115
|
+
policies.push(`${feature}=*`);
|
|
2116
|
+
} else {
|
|
2117
|
+
const originsList = origins.map((o) => `"${o}"`).join(" ");
|
|
2118
|
+
policies.push(`${feature}=(${originsList})`);
|
|
2119
|
+
}
|
|
2120
|
+
});
|
|
2121
|
+
return policies.join(", ");
|
|
1653
2122
|
}
|
|
1654
2123
|
/**
|
|
1655
|
-
*
|
|
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.
|
|
2124
|
+
* Apply headers to response
|
|
1662
2125
|
*/
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
);
|
|
2126
|
+
applyHeaders(response) {
|
|
2127
|
+
const headers = this.getHeaders();
|
|
2128
|
+
Object.entries(headers).forEach(([name, value]) => {
|
|
2129
|
+
response.headers.set(name, value);
|
|
2130
|
+
});
|
|
2131
|
+
return response;
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
var CORSManager = class {
|
|
2135
|
+
config;
|
|
2136
|
+
constructor(config = {}) {
|
|
2137
|
+
this.config = {
|
|
2138
|
+
origin: config.origin ?? [],
|
|
2139
|
+
methods: config.methods || ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
2140
|
+
allowedHeaders: config.allowedHeaders || ["Content-Type", "Authorization"],
|
|
2141
|
+
exposedHeaders: config.exposedHeaders || [],
|
|
2142
|
+
credentials: config.credentials ?? false,
|
|
2143
|
+
maxAge: config.maxAge || 86400,
|
|
2144
|
+
preflightContinue: config.preflightContinue ?? false,
|
|
2145
|
+
optionsSuccessStatus: config.optionsSuccessStatus || 204
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Check if origin is allowed
|
|
2150
|
+
*/
|
|
2151
|
+
isOriginAllowed(origin) {
|
|
2152
|
+
const { origin: allowedOrigin } = this.config;
|
|
2153
|
+
if (allowedOrigin === "*") {
|
|
2154
|
+
return true;
|
|
1693
2155
|
}
|
|
2156
|
+
if (typeof allowedOrigin === "function") {
|
|
2157
|
+
return allowedOrigin(origin);
|
|
2158
|
+
}
|
|
2159
|
+
if (typeof allowedOrigin === "string") {
|
|
2160
|
+
return origin === allowedOrigin;
|
|
2161
|
+
}
|
|
2162
|
+
if (Array.isArray(allowedOrigin)) {
|
|
2163
|
+
return allowedOrigin.includes(origin);
|
|
2164
|
+
}
|
|
2165
|
+
return false;
|
|
1694
2166
|
}
|
|
1695
2167
|
/**
|
|
1696
|
-
*
|
|
1697
|
-
* @param token Email verification token
|
|
1698
|
-
* @returns Verification success status
|
|
2168
|
+
* Get CORS headers
|
|
1699
2169
|
*/
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
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
|
-
);
|
|
2170
|
+
getCORSHeaders(origin) {
|
|
2171
|
+
const headers = {};
|
|
2172
|
+
if (this.isOriginAllowed(origin)) {
|
|
2173
|
+
headers["Access-Control-Allow-Origin"] = this.config.origin === "*" ? "*" : origin;
|
|
2174
|
+
}
|
|
2175
|
+
if (this.config.origin !== "*") {
|
|
2176
|
+
headers.Vary = "Origin";
|
|
2177
|
+
}
|
|
2178
|
+
if (this.config.credentials && this.config.origin !== "*") {
|
|
2179
|
+
headers["Access-Control-Allow-Credentials"] = "true";
|
|
1725
2180
|
}
|
|
2181
|
+
if (this.config.exposedHeaders.length > 0) {
|
|
2182
|
+
headers["Access-Control-Expose-Headers"] = this.config.exposedHeaders.join(", ");
|
|
2183
|
+
}
|
|
2184
|
+
return headers;
|
|
1726
2185
|
}
|
|
1727
2186
|
/**
|
|
1728
|
-
*
|
|
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.
|
|
2187
|
+
* Get preflight headers
|
|
1735
2188
|
*/
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
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
|
-
);
|
|
2189
|
+
getPreflightHeaders(origin) {
|
|
2190
|
+
const headers = this.getCORSHeaders(origin);
|
|
2191
|
+
headers["Access-Control-Allow-Methods"] = this.config.methods.join(", ");
|
|
2192
|
+
headers["Access-Control-Allow-Headers"] = this.config.allowedHeaders.join(", ");
|
|
2193
|
+
headers["Access-Control-Max-Age"] = this.config.maxAge.toString();
|
|
2194
|
+
return headers;
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Handle CORS request
|
|
2198
|
+
*/
|
|
2199
|
+
handleRequest(request) {
|
|
2200
|
+
const origin = request.headers.get("Origin");
|
|
2201
|
+
if (!origin) {
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
if (request.method === "OPTIONS") {
|
|
2205
|
+
return this.handlePreflight(request, origin);
|
|
1766
2206
|
}
|
|
2207
|
+
return null;
|
|
1767
2208
|
}
|
|
1768
2209
|
/**
|
|
1769
|
-
*
|
|
2210
|
+
* Handle preflight request
|
|
1770
2211
|
*/
|
|
1771
|
-
|
|
1772
|
-
this.
|
|
1773
|
-
|
|
2212
|
+
handlePreflight(_request, origin) {
|
|
2213
|
+
if (!this.isOriginAllowed(origin)) {
|
|
2214
|
+
return new Response(null, { status: 403 });
|
|
2215
|
+
}
|
|
2216
|
+
const headers = this.getPreflightHeaders(origin);
|
|
2217
|
+
return new Response(null, {
|
|
2218
|
+
status: this.config.optionsSuccessStatus,
|
|
2219
|
+
headers
|
|
1774
2220
|
});
|
|
1775
2221
|
}
|
|
1776
2222
|
/**
|
|
1777
|
-
*
|
|
2223
|
+
* Apply CORS headers to response
|
|
1778
2224
|
*/
|
|
1779
|
-
|
|
1780
|
-
this.
|
|
1781
|
-
|
|
2225
|
+
applyHeaders(response, origin) {
|
|
2226
|
+
if (!this.isOriginAllowed(origin)) {
|
|
2227
|
+
return response;
|
|
2228
|
+
}
|
|
2229
|
+
const headers = this.getCORSHeaders(origin);
|
|
2230
|
+
Object.entries(headers).forEach(([name, value]) => {
|
|
2231
|
+
response.headers.set(name, value);
|
|
1782
2232
|
});
|
|
2233
|
+
return response;
|
|
1783
2234
|
}
|
|
2235
|
+
};
|
|
2236
|
+
var SecurityPresets = {
|
|
1784
2237
|
/**
|
|
1785
|
-
*
|
|
2238
|
+
* Strict security (recommended for production)
|
|
1786
2239
|
*/
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
2240
|
+
strict: () => ({
|
|
2241
|
+
contentSecurityPolicy: {
|
|
2242
|
+
defaultSrc: ["'self'"],
|
|
2243
|
+
scriptSrc: ["'self'"],
|
|
2244
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
2245
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
2246
|
+
fontSrc: ["'self'", "data:"],
|
|
2247
|
+
connectSrc: ["'self'"],
|
|
2248
|
+
frameSrc: ["'none'"],
|
|
2249
|
+
objectSrc: ["'none'"],
|
|
2250
|
+
baseUri: ["'self'"],
|
|
2251
|
+
formAction: ["'self'"],
|
|
2252
|
+
frameAncestors: ["'none'"],
|
|
2253
|
+
upgradeInsecureRequests: true
|
|
2254
|
+
},
|
|
2255
|
+
strictTransportSecurity: {
|
|
2256
|
+
maxAge: 31536e3,
|
|
2257
|
+
includeSubDomains: true,
|
|
2258
|
+
preload: true
|
|
2259
|
+
},
|
|
2260
|
+
xFrameOptions: "DENY",
|
|
2261
|
+
xContentTypeOptions: true,
|
|
2262
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
2263
|
+
crossOriginEmbedderPolicy: "require-corp",
|
|
2264
|
+
crossOriginOpenerPolicy: "same-origin",
|
|
2265
|
+
crossOriginResourcePolicy: "same-origin"
|
|
2266
|
+
}),
|
|
2267
|
+
/**
|
|
2268
|
+
* Moderate security (balanced)
|
|
2269
|
+
*/
|
|
2270
|
+
moderate: () => ({
|
|
2271
|
+
contentSecurityPolicy: {
|
|
2272
|
+
defaultSrc: ["'self'"],
|
|
2273
|
+
scriptSrc: ["'self'", "'unsafe-inline'"],
|
|
2274
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
2275
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
2276
|
+
fontSrc: ["'self'", "data:", "https:"],
|
|
2277
|
+
connectSrc: ["'self'", "https:"],
|
|
2278
|
+
frameAncestors: ["'self'"]
|
|
2279
|
+
},
|
|
2280
|
+
strictTransportSecurity: {
|
|
2281
|
+
maxAge: 31536e3,
|
|
2282
|
+
includeSubDomains: true
|
|
2283
|
+
},
|
|
2284
|
+
xFrameOptions: "SAMEORIGIN",
|
|
2285
|
+
xContentTypeOptions: true,
|
|
2286
|
+
referrerPolicy: "origin-when-cross-origin"
|
|
2287
|
+
}),
|
|
2288
|
+
/**
|
|
2289
|
+
* Development (permissive)
|
|
2290
|
+
*/
|
|
2291
|
+
development: () => ({
|
|
2292
|
+
xContentTypeOptions: true,
|
|
2293
|
+
referrerPolicy: "no-referrer-when-downgrade"
|
|
2294
|
+
})
|
|
2295
|
+
};
|
|
2296
|
+
var CORSPresets = {
|
|
2297
|
+
/**
|
|
2298
|
+
* Strict CORS (same origin only)
|
|
2299
|
+
*/
|
|
2300
|
+
strict: () => ({
|
|
2301
|
+
origin: [],
|
|
2302
|
+
methods: ["GET", "POST", "PUT", "DELETE"],
|
|
2303
|
+
allowedHeaders: ["Content-Type", "Authorization"],
|
|
2304
|
+
credentials: true,
|
|
2305
|
+
maxAge: 86400
|
|
2306
|
+
}),
|
|
2307
|
+
/**
|
|
2308
|
+
* Moderate CORS (specific origins)
|
|
2309
|
+
*/
|
|
2310
|
+
moderate: (allowedOrigins) => ({
|
|
2311
|
+
origin: allowedOrigins,
|
|
2312
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
2313
|
+
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
|
|
2314
|
+
exposedHeaders: ["X-Total-Count"],
|
|
2315
|
+
credentials: true,
|
|
2316
|
+
maxAge: 86400
|
|
2317
|
+
}),
|
|
2318
|
+
/**
|
|
2319
|
+
* Permissive CORS (all origins) — development only.
|
|
2320
|
+
* Logs a warning if used when NODE_ENV === 'production'.
|
|
2321
|
+
*/
|
|
2322
|
+
permissive: () => {
|
|
2323
|
+
if (process.env.NODE_ENV === "production") {
|
|
2324
|
+
getSecurityLogger().warn(
|
|
2325
|
+
"[SecurityPresets] CORS permissive preset used in production \u2014 this allows all origins. Use moderate() with explicit origins instead."
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2328
|
+
return {
|
|
2329
|
+
origin: "*",
|
|
2330
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
2331
|
+
allowedHeaders: ["*"],
|
|
2332
|
+
credentials: false,
|
|
2333
|
+
maxAge: 86400
|
|
2334
|
+
};
|
|
2335
|
+
},
|
|
2336
|
+
/**
|
|
2337
|
+
* API CORS (public read-only APIs) — credentials disabled.
|
|
2338
|
+
* Logs a warning if used when NODE_ENV === 'production'.
|
|
2339
|
+
*/
|
|
2340
|
+
api: () => {
|
|
2341
|
+
if (process.env.NODE_ENV === "production") {
|
|
2342
|
+
getSecurityLogger().warn(
|
|
2343
|
+
'[SecurityPresets] CORS api preset uses origin:"*". For production, pass explicit origins to moderate() instead.'
|
|
2344
|
+
);
|
|
2345
|
+
}
|
|
2346
|
+
return {
|
|
2347
|
+
origin: "*",
|
|
2348
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
2349
|
+
allowedHeaders: ["Content-Type", "Authorization", "X-API-Key"],
|
|
2350
|
+
exposedHeaders: ["X-RateLimit-Limit", "X-RateLimit-Remaining", "X-RateLimit-Reset"],
|
|
2351
|
+
credentials: false,
|
|
2352
|
+
maxAge: 86400
|
|
2353
|
+
};
|
|
1791
2354
|
}
|
|
1792
2355
|
};
|
|
1793
|
-
function
|
|
1794
|
-
|
|
2356
|
+
function createSecurityMiddleware(securityConfig, corsConfig) {
|
|
2357
|
+
const security = new SecurityHeaders(securityConfig);
|
|
2358
|
+
const cors = new CORSManager(corsConfig);
|
|
2359
|
+
return async (request, next) => {
|
|
2360
|
+
const origin = request.headers.get("Origin");
|
|
2361
|
+
if (origin && request.method === "OPTIONS") {
|
|
2362
|
+
const preflightResponse = cors.handleRequest(request);
|
|
2363
|
+
if (preflightResponse) {
|
|
2364
|
+
return preflightResponse;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
const response = await next();
|
|
2368
|
+
security.applyHeaders(response);
|
|
2369
|
+
if (origin) {
|
|
2370
|
+
cors.applyHeaders(response, origin);
|
|
2371
|
+
}
|
|
2372
|
+
return response;
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
function setRateLimitHeaders(response, limit, remaining, reset) {
|
|
2376
|
+
response.headers.set("X-RateLimit-Limit", limit.toString());
|
|
2377
|
+
response.headers.set("X-RateLimit-Remaining", remaining.toString());
|
|
2378
|
+
response.headers.set("X-RateLimit-Reset", reset.toString());
|
|
1795
2379
|
}
|
|
1796
2380
|
export {
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2381
|
+
AuditReportGenerator,
|
|
2382
|
+
AuditSystem,
|
|
2383
|
+
AuditTrail,
|
|
2384
|
+
AuthorizationSystem,
|
|
2385
|
+
CORSManager,
|
|
2386
|
+
CORSPresets,
|
|
2387
|
+
CommonRoles,
|
|
2388
|
+
ConsentManager,
|
|
2389
|
+
CookieConsentManager,
|
|
2390
|
+
DataAnonymization,
|
|
2391
|
+
DataBreachManager,
|
|
2392
|
+
DataDeletionSystem,
|
|
2393
|
+
DataExportSystem,
|
|
2394
|
+
DataMasking,
|
|
2395
|
+
EncryptionSystem,
|
|
2396
|
+
EnvelopeEncryption,
|
|
2397
|
+
FieldEncryption,
|
|
2398
|
+
InMemoryAuditStorage,
|
|
2399
|
+
InMemoryBreachStorage,
|
|
2400
|
+
InMemoryGDPRStorage,
|
|
2401
|
+
KeyRotationManager,
|
|
2402
|
+
OAuthClient,
|
|
2403
|
+
OAuthProviders,
|
|
2404
|
+
PasswordHasher,
|
|
2405
|
+
PermissionBuilder,
|
|
2406
|
+
PermissionCache,
|
|
2407
|
+
PolicyBuilder,
|
|
2408
|
+
PrivacyPolicyManager,
|
|
2409
|
+
RequirePermission,
|
|
2410
|
+
RequireRole,
|
|
2411
|
+
SecurityHeaders,
|
|
2412
|
+
SecurityPresets,
|
|
2413
|
+
TokenGenerator,
|
|
2414
|
+
TwoFactorAuth,
|
|
2415
|
+
audit,
|
|
2416
|
+
authorization,
|
|
2417
|
+
canAccessResource,
|
|
2418
|
+
checkAttributeAccess,
|
|
2419
|
+
configureSecurityLogger,
|
|
2420
|
+
cookieConsentManager,
|
|
2421
|
+
createAuditMiddleware,
|
|
2422
|
+
createAuthorizationMiddleware,
|
|
2423
|
+
createConsentManager,
|
|
2424
|
+
createDataBreachManager,
|
|
2425
|
+
createDataDeletionSystem,
|
|
2426
|
+
createSecurityMiddleware,
|
|
2427
|
+
dataBreachManager,
|
|
2428
|
+
dataExportSystem,
|
|
2429
|
+
encryption,
|
|
2430
|
+
permissionCache,
|
|
2431
|
+
privacyPolicyManager,
|
|
2432
|
+
setRateLimitHeaders
|
|
1840
2433
|
};
|
|
1841
2434
|
//# sourceMappingURL=index.js.map
|