@revealui/security 0.0.1-pre.1 → 0.2.1
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 +111 -0
- package/dist/index.d.ts +1357 -0
- package/dist/index.js +2241 -1653
- package/dist/index.js.map +1 -1
- package/package.json +28 -75
package/dist/index.js
CHANGED
|
@@ -1,1841 +1,2429 @@
|
|
|
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
|
-
throw new Error("Health check encryption failed");
|
|
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
|
+
Authorization: `Bearer ${accessToken}`
|
|
855
476
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
477
|
+
});
|
|
478
|
+
if (!response.ok) {
|
|
479
|
+
let detail = "";
|
|
480
|
+
try {
|
|
481
|
+
const body = await response.text();
|
|
482
|
+
detail = `: ${response.status} ${body.slice(0, 200)}`;
|
|
483
|
+
} catch {
|
|
484
|
+
detail = `: ${response.status}`;
|
|
859
485
|
}
|
|
860
|
-
|
|
861
|
-
throw new Error(
|
|
862
|
-
`EncryptionService health check failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
863
|
-
);
|
|
486
|
+
throw new Error(`Failed to fetch user info${detail}`);
|
|
864
487
|
}
|
|
488
|
+
return response.json();
|
|
865
489
|
}
|
|
866
490
|
};
|
|
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()
|
|
491
|
+
var PH_ITERATIONS = 1e5;
|
|
492
|
+
var PH_KEY_LENGTH = 64;
|
|
493
|
+
var PH_DIGEST = "sha512";
|
|
494
|
+
async function hashPassword(password) {
|
|
495
|
+
const { pbkdf2, randomBytes: rb } = await import("crypto");
|
|
496
|
+
const salt = rb(16).toString("hex");
|
|
497
|
+
return new Promise((resolve, reject) => {
|
|
498
|
+
pbkdf2(password, salt, PH_ITERATIONS, PH_KEY_LENGTH, PH_DIGEST, (err, derivedKey) => {
|
|
499
|
+
if (err) reject(err);
|
|
500
|
+
else resolve(`${salt}:${derivedKey.toString("hex")}`);
|
|
501
|
+
});
|
|
967
502
|
});
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
503
|
+
}
|
|
504
|
+
async function verifyPassword(password, storedHash) {
|
|
505
|
+
const { pbkdf2, timingSafeEqual: tse } = await import("crypto");
|
|
506
|
+
const [salt, hash] = storedHash.split(":");
|
|
507
|
+
if (!(salt && hash)) {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
return new Promise((resolve, reject) => {
|
|
511
|
+
pbkdf2(password, salt, PH_ITERATIONS, PH_KEY_LENGTH, PH_DIGEST, (err, derivedKey) => {
|
|
512
|
+
if (err) reject(err);
|
|
513
|
+
else {
|
|
514
|
+
const derived = Buffer.from(derivedKey.toString("hex"), "utf-8");
|
|
515
|
+
const expected = Buffer.from(hash, "utf-8");
|
|
516
|
+
if (derived.length !== expected.length) {
|
|
517
|
+
resolve(false);
|
|
518
|
+
} else {
|
|
519
|
+
resolve(tse(derived, expected));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
982
522
|
});
|
|
983
|
-
}
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
var PasswordHasher = {
|
|
526
|
+
hash: hashPassword,
|
|
527
|
+
verify: verifyPassword
|
|
984
528
|
};
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
metadata: {
|
|
998
|
-
success,
|
|
999
|
-
...options?.metadata
|
|
529
|
+
function base32Encode(buffer) {
|
|
530
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
531
|
+
let result = "";
|
|
532
|
+
let bits = 0;
|
|
533
|
+
let value = 0;
|
|
534
|
+
for (const byte of buffer) {
|
|
535
|
+
if (byte === void 0) continue;
|
|
536
|
+
value = value << 8 | byte;
|
|
537
|
+
bits += 8;
|
|
538
|
+
while (bits >= 5) {
|
|
539
|
+
result += alphabet[value >>> bits - 5 & 31];
|
|
540
|
+
bits -= 5;
|
|
1000
541
|
}
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
542
|
+
}
|
|
543
|
+
if (bits > 0) {
|
|
544
|
+
result += alphabet[value << 5 - bits & 31];
|
|
545
|
+
}
|
|
546
|
+
return result;
|
|
547
|
+
}
|
|
548
|
+
function totpHmac(key, message) {
|
|
549
|
+
const hmacDigest = createHmac("sha1", key).update(message).digest();
|
|
550
|
+
return new Uint8Array(hmacDigest);
|
|
551
|
+
}
|
|
552
|
+
function generateSecret() {
|
|
553
|
+
const crypto2 = globalThis.crypto;
|
|
554
|
+
if (!crypto2) {
|
|
555
|
+
throw new Error("Crypto API not available");
|
|
556
|
+
}
|
|
557
|
+
const buffer = new Uint8Array(20);
|
|
558
|
+
crypto2.getRandomValues(buffer);
|
|
559
|
+
return base32Encode(buffer);
|
|
560
|
+
}
|
|
561
|
+
function generateCode(secret, timestamp) {
|
|
562
|
+
const time = Math.floor((timestamp || Date.now()) / 3e4);
|
|
563
|
+
const hmacDigest = totpHmac(secret, time.toString());
|
|
564
|
+
const offset = hmacDigest[hmacDigest.length - 1] & 15;
|
|
565
|
+
const b0 = hmacDigest[offset] & 127;
|
|
566
|
+
const b1 = hmacDigest[offset + 1] & 255;
|
|
567
|
+
const b2 = hmacDigest[offset + 2] & 255;
|
|
568
|
+
const b3 = hmacDigest[offset + 3] & 255;
|
|
569
|
+
const code = (b0 << 24 | b1 << 16 | b2 << 8 | b3) % 1e6;
|
|
570
|
+
return code.toString().padStart(6, "0");
|
|
571
|
+
}
|
|
572
|
+
function verifyCode(secret, code, window = 1) {
|
|
573
|
+
const timestamp = Date.now();
|
|
574
|
+
for (let i = -window; i <= window; i++) {
|
|
575
|
+
const testTime = timestamp + i * 3e4;
|
|
576
|
+
const testCode = generateCode(secret, testTime);
|
|
577
|
+
if (testCode.length === code.length && timingSafeEqual(Buffer.from(testCode), Buffer.from(code))) {
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
var TwoFactorAuth = {
|
|
584
|
+
generateSecret,
|
|
585
|
+
generateCode,
|
|
586
|
+
verifyCode
|
|
1009
587
|
};
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
588
|
+
|
|
589
|
+
// src/authorization.ts
|
|
590
|
+
var AuthorizationSystem = class {
|
|
591
|
+
roles = /* @__PURE__ */ new Map();
|
|
592
|
+
policies = /* @__PURE__ */ new Map();
|
|
593
|
+
/**
|
|
594
|
+
* Register role
|
|
595
|
+
*/
|
|
596
|
+
registerRole(role) {
|
|
597
|
+
this.roles.set(role.id, role);
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Get role
|
|
601
|
+
*/
|
|
602
|
+
getRole(roleId) {
|
|
603
|
+
return this.roles.get(roleId);
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Register policy
|
|
607
|
+
*/
|
|
608
|
+
registerPolicy(policy) {
|
|
609
|
+
this.policies.set(policy.id, policy);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Check if user has permission (RBAC)
|
|
613
|
+
*/
|
|
614
|
+
hasPermission(userRoles, resource, action) {
|
|
615
|
+
const permissions = this.getUserPermissions(userRoles);
|
|
616
|
+
return permissions.some(
|
|
617
|
+
(permission) => this.matchesResource(permission.resource, resource) && this.matchesAction(permission.action, action)
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Check access with policies (ABAC)
|
|
622
|
+
*/
|
|
623
|
+
checkAccess(context, resource, action) {
|
|
624
|
+
if (this.hasPermission(context.user.roles, resource, action)) {
|
|
625
|
+
return { allowed: true };
|
|
1020
626
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
627
|
+
const applicablePolicies = this.getApplicablePolicies(resource, action, context);
|
|
628
|
+
applicablePolicies.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
629
|
+
for (const policy of applicablePolicies) {
|
|
630
|
+
if (this.evaluateConditions(policy.conditions || [], context)) {
|
|
631
|
+
return {
|
|
632
|
+
allowed: policy.effect === "allow",
|
|
633
|
+
reason: policy.effect === "deny" ? `Denied by policy: ${policy.name}` : void 0
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return { allowed: false, reason: "No matching policy" };
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Get all permissions for roles
|
|
641
|
+
*/
|
|
642
|
+
getUserPermissions(roleIds) {
|
|
643
|
+
const permissions = [];
|
|
644
|
+
const visited = /* @__PURE__ */ new Set();
|
|
645
|
+
const addRolePermissions = (roleId) => {
|
|
646
|
+
if (visited.has(roleId)) return;
|
|
647
|
+
visited.add(roleId);
|
|
648
|
+
const role = this.roles.get(roleId);
|
|
649
|
+
if (!role) return;
|
|
650
|
+
permissions.push(...role.permissions);
|
|
651
|
+
if (role.inherits) {
|
|
652
|
+
role.inherits.forEach((inheritedRoleId) => {
|
|
653
|
+
addRolePermissions(inheritedRoleId);
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
roleIds.forEach(addRolePermissions);
|
|
658
|
+
return permissions;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Get applicable policies
|
|
662
|
+
*/
|
|
663
|
+
getApplicablePolicies(resource, action, _context) {
|
|
664
|
+
return Array.from(this.policies.values()).filter((policy) => {
|
|
665
|
+
const resourceMatches = policy.resources.some((r) => this.matchesResource(r, resource));
|
|
666
|
+
const actionMatches = policy.actions.some((a) => this.matchesAction(a, action));
|
|
667
|
+
return resourceMatches && actionMatches;
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Match resource pattern
|
|
672
|
+
*/
|
|
673
|
+
matchesResource(pattern, resource) {
|
|
674
|
+
if (pattern === "*") return true;
|
|
675
|
+
if (pattern === resource) return true;
|
|
676
|
+
const regex = new RegExp(
|
|
677
|
+
`^${pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".")}$`
|
|
678
|
+
);
|
|
679
|
+
return regex.test(resource);
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Match action pattern
|
|
683
|
+
*/
|
|
684
|
+
matchesAction(pattern, action) {
|
|
685
|
+
if (pattern === "*") return true;
|
|
686
|
+
if (pattern === action) return true;
|
|
687
|
+
const regex = new RegExp(
|
|
688
|
+
`^${pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".")}$`
|
|
689
|
+
);
|
|
690
|
+
return regex.test(action);
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Evaluate policy conditions
|
|
694
|
+
*/
|
|
695
|
+
evaluateConditions(conditions, context) {
|
|
696
|
+
return conditions.every((condition) => {
|
|
697
|
+
const value = this.getContextValue(condition.field, context);
|
|
698
|
+
return this.evaluateCondition(condition, value);
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Get value from context
|
|
703
|
+
*/
|
|
704
|
+
getContextValue(field, context) {
|
|
705
|
+
const parts = field.split(".");
|
|
706
|
+
let value = context;
|
|
707
|
+
for (const part of parts) {
|
|
708
|
+
if (value && typeof value === "object" && part in value) {
|
|
709
|
+
value = value[part];
|
|
710
|
+
} else {
|
|
711
|
+
return void 0;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return value;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Evaluate single condition
|
|
718
|
+
*/
|
|
719
|
+
evaluateCondition(condition, value) {
|
|
720
|
+
switch (condition.operator) {
|
|
721
|
+
case "eq":
|
|
722
|
+
return value === condition.value;
|
|
723
|
+
case "ne":
|
|
724
|
+
return value !== condition.value;
|
|
725
|
+
case "gt":
|
|
726
|
+
return typeof value === "number" && value > condition.value;
|
|
727
|
+
case "gte":
|
|
728
|
+
return typeof value === "number" && value >= condition.value;
|
|
729
|
+
case "lt":
|
|
730
|
+
return typeof value === "number" && value < condition.value;
|
|
731
|
+
case "lte":
|
|
732
|
+
return typeof value === "number" && value <= condition.value;
|
|
733
|
+
case "in":
|
|
734
|
+
return Array.isArray(condition.value) && condition.value.includes(value);
|
|
735
|
+
case "contains":
|
|
736
|
+
return typeof value === "string" && typeof condition.value === "string" && value.includes(condition.value);
|
|
737
|
+
default:
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Check if user owns resource
|
|
743
|
+
*/
|
|
744
|
+
ownsResource(userId, resource) {
|
|
745
|
+
return resource.owner === userId;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Clear all roles and policies
|
|
749
|
+
*/
|
|
750
|
+
clear() {
|
|
751
|
+
this.roles.clear();
|
|
752
|
+
this.policies.clear();
|
|
753
|
+
}
|
|
1029
754
|
};
|
|
1030
|
-
var
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
755
|
+
var authorization = new AuthorizationSystem();
|
|
756
|
+
var CommonRoles = {
|
|
757
|
+
owner: {
|
|
758
|
+
id: "owner",
|
|
759
|
+
name: "Owner",
|
|
760
|
+
description: "Full control \u2014 inherits admin",
|
|
761
|
+
permissions: [{ resource: "*", action: "*" }],
|
|
762
|
+
inherits: ["admin"]
|
|
763
|
+
},
|
|
764
|
+
admin: {
|
|
765
|
+
id: "admin",
|
|
766
|
+
name: "Administrator",
|
|
767
|
+
description: "Full system access",
|
|
768
|
+
permissions: [{ resource: "*", action: "*" }]
|
|
769
|
+
},
|
|
770
|
+
editor: {
|
|
771
|
+
id: "editor",
|
|
772
|
+
name: "Editor",
|
|
773
|
+
description: "Can read and modify content",
|
|
774
|
+
permissions: [
|
|
775
|
+
{ resource: "content", action: "read" },
|
|
776
|
+
{ resource: "content", action: "create" },
|
|
777
|
+
{ resource: "content", action: "update" },
|
|
778
|
+
{ resource: "profile", action: "read" },
|
|
779
|
+
{ resource: "profile", action: "update" },
|
|
780
|
+
{ resource: "sites", action: "read" },
|
|
781
|
+
{ resource: "marketplace", action: "read" }
|
|
782
|
+
]
|
|
783
|
+
},
|
|
784
|
+
viewer: {
|
|
785
|
+
id: "viewer",
|
|
786
|
+
name: "Viewer",
|
|
787
|
+
description: "Read-only access",
|
|
788
|
+
permissions: [
|
|
789
|
+
{ resource: "content", action: "read" },
|
|
790
|
+
{ resource: "profile", action: "read" },
|
|
791
|
+
{ resource: "sites", action: "read" },
|
|
792
|
+
{ resource: "public", action: "read" }
|
|
793
|
+
]
|
|
794
|
+
},
|
|
795
|
+
agent: {
|
|
796
|
+
id: "agent",
|
|
797
|
+
name: "AI Agent",
|
|
798
|
+
description: "Can execute tasks and read content",
|
|
799
|
+
permissions: [
|
|
800
|
+
{ resource: "tasks", action: "create" },
|
|
801
|
+
{ resource: "tasks", action: "read" },
|
|
802
|
+
{ resource: "content", action: "read" },
|
|
803
|
+
{ resource: "rag", action: "read" },
|
|
804
|
+
{ resource: "rag", action: "create" }
|
|
805
|
+
]
|
|
806
|
+
},
|
|
807
|
+
contributor: {
|
|
808
|
+
id: "contributor",
|
|
809
|
+
name: "Contributor",
|
|
810
|
+
description: "Can suggest changes \u2014 create drafts but not publish or delete",
|
|
811
|
+
permissions: [
|
|
812
|
+
{ resource: "content", action: "read" },
|
|
813
|
+
{ resource: "content", action: "create" },
|
|
814
|
+
{ resource: "profile", action: "read" },
|
|
815
|
+
{ resource: "profile", action: "update" }
|
|
816
|
+
]
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
var PermissionBuilder = class {
|
|
820
|
+
permission = {};
|
|
821
|
+
resource(resource) {
|
|
822
|
+
this.permission.resource = resource;
|
|
823
|
+
return this;
|
|
824
|
+
}
|
|
825
|
+
action(action) {
|
|
826
|
+
this.permission.action = action;
|
|
827
|
+
return this;
|
|
828
|
+
}
|
|
829
|
+
conditions(conditions) {
|
|
830
|
+
this.permission.conditions = conditions;
|
|
831
|
+
return this;
|
|
832
|
+
}
|
|
833
|
+
build() {
|
|
834
|
+
if (!(this.permission.resource && this.permission.action)) {
|
|
835
|
+
throw new Error("Resource and action are required");
|
|
1040
836
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
"data_access",
|
|
1044
|
-
operation === "delete" ? "medium" : "low",
|
|
1045
|
-
`Data access: ${operation} on ${collection}${recordId ? ` (${recordId})` : ""} by user ${userId}`,
|
|
1046
|
-
eventOptions
|
|
1047
|
-
);
|
|
1048
|
-
addSecurityEvent(event);
|
|
837
|
+
return this.permission;
|
|
838
|
+
}
|
|
1049
839
|
};
|
|
1050
|
-
var
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
840
|
+
var PolicyBuilder = class {
|
|
841
|
+
policy = {
|
|
842
|
+
effect: "allow",
|
|
843
|
+
resources: [],
|
|
844
|
+
actions: [],
|
|
845
|
+
conditions: []
|
|
846
|
+
};
|
|
847
|
+
id(id) {
|
|
848
|
+
this.policy.id = id;
|
|
849
|
+
return this;
|
|
850
|
+
}
|
|
851
|
+
name(name) {
|
|
852
|
+
this.policy.name = name;
|
|
853
|
+
return this;
|
|
854
|
+
}
|
|
855
|
+
allow() {
|
|
856
|
+
this.policy.effect = "allow";
|
|
857
|
+
return this;
|
|
858
|
+
}
|
|
859
|
+
deny() {
|
|
860
|
+
this.policy.effect = "deny";
|
|
861
|
+
return this;
|
|
862
|
+
}
|
|
863
|
+
resources(...resources) {
|
|
864
|
+
this.policy.resources = resources;
|
|
865
|
+
return this;
|
|
866
|
+
}
|
|
867
|
+
actions(...actions) {
|
|
868
|
+
this.policy.actions = actions;
|
|
869
|
+
return this;
|
|
870
|
+
}
|
|
871
|
+
condition(field, operator, value) {
|
|
872
|
+
if (!this.policy.conditions) {
|
|
873
|
+
this.policy.conditions = [];
|
|
874
|
+
}
|
|
875
|
+
this.policy.conditions.push({ field, operator, value });
|
|
876
|
+
return this;
|
|
877
|
+
}
|
|
878
|
+
priority(priority) {
|
|
879
|
+
this.policy.priority = priority;
|
|
880
|
+
return this;
|
|
881
|
+
}
|
|
882
|
+
build() {
|
|
883
|
+
if (!(this.policy.id && this.policy.name)) {
|
|
884
|
+
throw new Error("ID and name are required");
|
|
885
|
+
}
|
|
886
|
+
return this.policy;
|
|
887
|
+
}
|
|
1075
888
|
};
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
889
|
+
function RequirePermission(resource, action) {
|
|
890
|
+
return (_target, _propertyKey, descriptor) => {
|
|
891
|
+
const originalMethod = descriptor.value;
|
|
892
|
+
descriptor.value = function(...args) {
|
|
893
|
+
const userRoles = this.user?.roles || [];
|
|
894
|
+
if (!authorization.hasPermission(userRoles, resource, action)) {
|
|
895
|
+
throw new Error(`Permission denied: ${resource}:${action}`);
|
|
896
|
+
}
|
|
897
|
+
return originalMethod.apply(this, args);
|
|
898
|
+
};
|
|
899
|
+
return descriptor;
|
|
900
|
+
};
|
|
1082
901
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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;
|
|
902
|
+
function RequireRole(requiredRole) {
|
|
903
|
+
return (_target, _propertyKey, descriptor) => {
|
|
904
|
+
const originalMethod = descriptor.value;
|
|
905
|
+
descriptor.value = function(...args) {
|
|
906
|
+
const userRoles = this.user?.roles || [];
|
|
907
|
+
if (!userRoles.includes(requiredRole)) {
|
|
908
|
+
throw new Error(`Role required: ${requiredRole}`);
|
|
909
|
+
}
|
|
910
|
+
return originalMethod.apply(this, args);
|
|
911
|
+
};
|
|
912
|
+
return descriptor;
|
|
913
|
+
};
|
|
1111
914
|
}
|
|
1112
|
-
function
|
|
1113
|
-
return {
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
isTokenExpired(exp) {
|
|
1118
|
-
return Date.now() / 1e3 > exp;
|
|
1119
|
-
},
|
|
1120
|
-
getAccessTokenExpiry() {
|
|
1121
|
-
return config.tokenExpiry.access;
|
|
1122
|
-
},
|
|
1123
|
-
getRefreshTokenExpiry() {
|
|
1124
|
-
return config.tokenExpiry.refresh;
|
|
915
|
+
function createAuthorizationMiddleware(getUser, resource, action) {
|
|
916
|
+
return (request, next) => {
|
|
917
|
+
const user = getUser(request);
|
|
918
|
+
if (!authorization.hasPermission(user.roles, resource, action)) {
|
|
919
|
+
throw new Error(`Permission denied: ${resource}:${action}`);
|
|
1125
920
|
}
|
|
921
|
+
return next();
|
|
1126
922
|
};
|
|
1127
923
|
}
|
|
924
|
+
function canAccessResource(userId, userRoles, resource, action) {
|
|
925
|
+
if (authorization.hasPermission(userRoles, resource.type, action)) {
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
if (authorization.ownsResource(userId, resource)) {
|
|
929
|
+
return true;
|
|
930
|
+
}
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
function checkAttributeAccess(context, resource, action, requiredAttributes) {
|
|
934
|
+
const { allowed } = authorization.checkAccess(context, resource, action);
|
|
935
|
+
if (!allowed) {
|
|
936
|
+
return false;
|
|
937
|
+
}
|
|
938
|
+
if (requiredAttributes) {
|
|
939
|
+
const userAttributes = context.user.attributes || {};
|
|
940
|
+
return Object.entries(requiredAttributes).every(
|
|
941
|
+
([key, value]) => userAttributes[key] === value
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
return true;
|
|
945
|
+
}
|
|
946
|
+
var PermissionCache = class {
|
|
947
|
+
cache = /* @__PURE__ */ new Map();
|
|
948
|
+
ttl;
|
|
949
|
+
maxEntries;
|
|
950
|
+
constructor(ttl = 3e5, maxEntries = 1e4) {
|
|
951
|
+
this.ttl = ttl;
|
|
952
|
+
this.maxEntries = maxEntries;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Get cached permission
|
|
956
|
+
*/
|
|
957
|
+
get(userId, resource, action) {
|
|
958
|
+
const key = this.getCacheKey(userId, resource, action);
|
|
959
|
+
const cached = this.cache.get(key);
|
|
960
|
+
if (!cached) {
|
|
961
|
+
return void 0;
|
|
962
|
+
}
|
|
963
|
+
if (Date.now() > cached.expiresAt) {
|
|
964
|
+
this.cache.delete(key);
|
|
965
|
+
return void 0;
|
|
966
|
+
}
|
|
967
|
+
return cached.allowed;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Set cached permission
|
|
971
|
+
*/
|
|
972
|
+
set(userId, resource, action, allowed) {
|
|
973
|
+
const key = this.getCacheKey(userId, resource, action);
|
|
974
|
+
if (this.cache.size >= this.maxEntries) {
|
|
975
|
+
const now = Date.now();
|
|
976
|
+
for (const [k, v] of this.cache) {
|
|
977
|
+
if (now > v.expiresAt) this.cache.delete(k);
|
|
978
|
+
}
|
|
979
|
+
if (this.cache.size >= this.maxEntries) {
|
|
980
|
+
const excess = this.cache.size - this.maxEntries + 1;
|
|
981
|
+
const keys = this.cache.keys();
|
|
982
|
+
for (let i = 0; i < excess; i++) {
|
|
983
|
+
const next = keys.next();
|
|
984
|
+
if (!next.done) this.cache.delete(next.value);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
this.cache.set(key, {
|
|
989
|
+
allowed,
|
|
990
|
+
expiresAt: Date.now() + this.ttl
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Clear cache for user
|
|
995
|
+
*/
|
|
996
|
+
clearUser(userId) {
|
|
997
|
+
for (const key of this.cache.keys()) {
|
|
998
|
+
if (key.startsWith(`${userId}:`)) {
|
|
999
|
+
this.cache.delete(key);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Clear all cache
|
|
1005
|
+
*/
|
|
1006
|
+
clear() {
|
|
1007
|
+
this.cache.clear();
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Get cache key
|
|
1011
|
+
*/
|
|
1012
|
+
getCacheKey(userId, resource, action) {
|
|
1013
|
+
return `${userId}:${resource}:${action}`;
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
var permissionCache = new PermissionCache();
|
|
1128
1017
|
|
|
1129
|
-
// src/
|
|
1130
|
-
var
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1018
|
+
// src/encryption.ts
|
|
1019
|
+
var DEFAULT_CONFIG = {
|
|
1020
|
+
algorithm: "AES-GCM",
|
|
1021
|
+
keySize: 256,
|
|
1022
|
+
ivSize: 12
|
|
1134
1023
|
};
|
|
1135
|
-
var
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1024
|
+
var EncryptionSystem = class {
|
|
1025
|
+
config;
|
|
1026
|
+
keys = /* @__PURE__ */ new Map();
|
|
1027
|
+
constructor(config = {}) {
|
|
1028
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Generate encryption key
|
|
1032
|
+
*/
|
|
1033
|
+
async generateKey(keyId) {
|
|
1034
|
+
const crypto2 = globalThis.crypto;
|
|
1035
|
+
if (!crypto2) {
|
|
1036
|
+
throw new Error("Crypto API not available");
|
|
1037
|
+
}
|
|
1038
|
+
const key = await crypto2.subtle.generateKey(
|
|
1039
|
+
{
|
|
1040
|
+
name: this.config.algorithm,
|
|
1041
|
+
length: this.config.keySize
|
|
1042
|
+
},
|
|
1043
|
+
this.config.extractable ?? false,
|
|
1044
|
+
// non-extractable by default — prevents key exfiltration
|
|
1045
|
+
["encrypt", "decrypt"]
|
|
1046
|
+
);
|
|
1047
|
+
if (keyId) {
|
|
1048
|
+
this.keys.set(keyId, key);
|
|
1049
|
+
}
|
|
1050
|
+
return key;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Import key from raw data
|
|
1054
|
+
*/
|
|
1055
|
+
async importKey(keyData, keyId) {
|
|
1056
|
+
const crypto2 = globalThis.crypto;
|
|
1057
|
+
if (!crypto2) {
|
|
1058
|
+
throw new Error("Crypto API not available");
|
|
1059
|
+
}
|
|
1060
|
+
const key = await crypto2.subtle.importKey(
|
|
1061
|
+
"raw",
|
|
1062
|
+
keyData,
|
|
1063
|
+
{
|
|
1064
|
+
name: this.config.algorithm,
|
|
1065
|
+
length: this.config.keySize
|
|
1066
|
+
},
|
|
1067
|
+
this.config.extractable ?? false,
|
|
1068
|
+
// non-extractable by default — prevents key exfiltration
|
|
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
|
+
}
|
|
1144
1252
|
};
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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");
|
|
1151
1272
|
}
|
|
1152
|
-
|
|
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
|
+
});
|
|
1428
|
+
}
|
|
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
|
|
1455
|
+
};
|
|
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("");
|
|
1153
1459
|
}
|
|
1154
1460
|
function generateUUID() {
|
|
1155
|
-
|
|
1156
|
-
|
|
1461
|
+
const crypto2 = globalThis.crypto;
|
|
1462
|
+
if (!crypto2) {
|
|
1463
|
+
throw new Error("Crypto API not available");
|
|
1157
1464
|
}
|
|
1158
|
-
return
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
}
|
|
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/logger.ts
|
|
1485
|
+
var securityLogger = console;
|
|
1486
|
+
function configureSecurityLogger(logger) {
|
|
1487
|
+
securityLogger = logger;
|
|
1488
|
+
}
|
|
1489
|
+
function getSecurityLogger() {
|
|
1490
|
+
return securityLogger;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// src/gdpr.ts
|
|
1494
|
+
var ConsentManager = class {
|
|
1495
|
+
storage;
|
|
1496
|
+
consentVersion = "1.0.0";
|
|
1497
|
+
constructor(storage) {
|
|
1498
|
+
this.storage = storage;
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Grant consent
|
|
1502
|
+
*/
|
|
1503
|
+
async grantConsent(userId, type, source = "explicit", expiresIn) {
|
|
1504
|
+
const consent = {
|
|
1505
|
+
id: crypto.randomUUID(),
|
|
1506
|
+
userId,
|
|
1507
|
+
type,
|
|
1508
|
+
granted: true,
|
|
1509
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1510
|
+
expiresAt: expiresIn ? new Date(Date.now() + expiresIn).toISOString() : void 0,
|
|
1511
|
+
source,
|
|
1512
|
+
version: this.consentVersion
|
|
1513
|
+
};
|
|
1514
|
+
await this.storage.setConsent(userId, type, consent);
|
|
1515
|
+
return consent;
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Revoke consent
|
|
1519
|
+
*/
|
|
1520
|
+
async revokeConsent(userId, type) {
|
|
1521
|
+
const existing = await this.storage.getConsent(userId, type);
|
|
1522
|
+
if (existing) {
|
|
1523
|
+
existing.granted = false;
|
|
1524
|
+
existing.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1525
|
+
await this.storage.setConsent(userId, type, existing);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Check if consent is granted
|
|
1530
|
+
*/
|
|
1531
|
+
async hasConsent(userId, type) {
|
|
1532
|
+
const consent = await this.storage.getConsent(userId, type);
|
|
1533
|
+
if (!consent?.granted) {
|
|
1534
|
+
return false;
|
|
1535
|
+
}
|
|
1536
|
+
if (consent.expiresAt && new Date(consent.expiresAt) < /* @__PURE__ */ new Date()) {
|
|
1537
|
+
return false;
|
|
1538
|
+
}
|
|
1539
|
+
return true;
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Get all consents for user
|
|
1543
|
+
*/
|
|
1544
|
+
async getUserConsents(userId) {
|
|
1545
|
+
return this.storage.getConsentsByUser(userId);
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Update consent version
|
|
1549
|
+
*/
|
|
1550
|
+
setConsentVersion(version) {
|
|
1551
|
+
this.consentVersion = version;
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Check if consent needs renewal
|
|
1555
|
+
*/
|
|
1556
|
+
async needsRenewal(userId, type, maxAge) {
|
|
1557
|
+
const consent = await this.storage.getConsent(userId, type);
|
|
1558
|
+
if (!consent?.granted) {
|
|
1559
|
+
return true;
|
|
1560
|
+
}
|
|
1561
|
+
const age = Date.now() - new Date(consent.timestamp).getTime();
|
|
1562
|
+
return age >= maxAge;
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Get consent statistics
|
|
1566
|
+
*/
|
|
1567
|
+
async getStatistics() {
|
|
1568
|
+
const consents = await this.storage.getAllConsents();
|
|
1569
|
+
const now = /* @__PURE__ */ new Date();
|
|
1570
|
+
const granted = consents.filter((c) => c.granted).length;
|
|
1571
|
+
const revoked = consents.filter((c) => !c.granted).length;
|
|
1572
|
+
const expired = consents.filter((c) => c.expiresAt && new Date(c.expiresAt) < now).length;
|
|
1573
|
+
const byType = consents.reduce(
|
|
1574
|
+
(acc, c) => {
|
|
1575
|
+
acc[c.type] = (acc[c.type] || 0) + 1;
|
|
1576
|
+
return acc;
|
|
1577
|
+
},
|
|
1578
|
+
{}
|
|
1579
|
+
);
|
|
1580
|
+
return {
|
|
1581
|
+
total: consents.length,
|
|
1582
|
+
granted,
|
|
1583
|
+
revoked,
|
|
1584
|
+
expired,
|
|
1585
|
+
byType
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
function escapeCsvField(value) {
|
|
1590
|
+
let safe = /^[=+\-@\t\r]/.test(value) ? `'${value}` : value;
|
|
1591
|
+
safe = safe.replace(/"/g, '""');
|
|
1592
|
+
return `"${safe}"`;
|
|
1163
1593
|
}
|
|
1164
|
-
var
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1594
|
+
var DataExportSystem = class {
|
|
1595
|
+
/**
|
|
1596
|
+
* Export user data
|
|
1597
|
+
*/
|
|
1598
|
+
async exportUserData(userId, getUserData, format = "json") {
|
|
1599
|
+
const data = await getUserData(userId);
|
|
1600
|
+
const exportData = {
|
|
1601
|
+
userId,
|
|
1602
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1603
|
+
data: {
|
|
1604
|
+
profile: data.profile,
|
|
1605
|
+
activities: data.activities,
|
|
1606
|
+
consents: data.consents,
|
|
1607
|
+
dataProcessing: []
|
|
1608
|
+
},
|
|
1609
|
+
format
|
|
1610
|
+
};
|
|
1611
|
+
return exportData;
|
|
1168
1612
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1613
|
+
/**
|
|
1614
|
+
* Format export as JSON
|
|
1615
|
+
*/
|
|
1616
|
+
formatAsJSON(exportData) {
|
|
1617
|
+
return JSON.stringify(exportData, null, 2);
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Format export as CSV
|
|
1621
|
+
*/
|
|
1622
|
+
formatAsCSV(exportData) {
|
|
1623
|
+
const lines = [];
|
|
1624
|
+
lines.push("Type,Key,Value");
|
|
1625
|
+
Object.entries(exportData.data.profile).forEach(([key, value]) => {
|
|
1626
|
+
lines.push(`Profile,${escapeCsvField(key)},${escapeCsvField(String(value))}`);
|
|
1627
|
+
});
|
|
1628
|
+
exportData.data.activities.forEach((activity, index) => {
|
|
1629
|
+
Object.entries(activity).forEach(([key, value]) => {
|
|
1630
|
+
lines.push(`Activity ${index + 1},${escapeCsvField(key)},${escapeCsvField(String(value))}`);
|
|
1631
|
+
});
|
|
1632
|
+
});
|
|
1633
|
+
return lines.join("\n");
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Create download link
|
|
1637
|
+
*/
|
|
1638
|
+
createDownloadLink(content, _filename, mimeType) {
|
|
1639
|
+
const blob = new Blob([content], { type: mimeType });
|
|
1640
|
+
return URL.createObjectURL(blob);
|
|
1641
|
+
}
|
|
1642
|
+
};
|
|
1643
|
+
var DataDeletionSystem = class {
|
|
1644
|
+
storage;
|
|
1645
|
+
constructor(storage) {
|
|
1646
|
+
this.storage = storage;
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Request data deletion
|
|
1650
|
+
*/
|
|
1651
|
+
async requestDeletion(userId, dataCategories, reason) {
|
|
1652
|
+
const request = {
|
|
1653
|
+
id: crypto.randomUUID(),
|
|
1654
|
+
userId,
|
|
1655
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1656
|
+
status: "pending",
|
|
1657
|
+
dataCategories,
|
|
1658
|
+
reason
|
|
1659
|
+
};
|
|
1660
|
+
await this.storage.setDeletionRequest(request);
|
|
1661
|
+
return request;
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Process deletion request
|
|
1665
|
+
*/
|
|
1666
|
+
async processDeletion(requestId, deleteData) {
|
|
1667
|
+
const request = await this.storage.getDeletionRequest(requestId);
|
|
1668
|
+
if (!request) {
|
|
1669
|
+
throw new Error("Deletion request not found");
|
|
1670
|
+
}
|
|
1671
|
+
request.status = "processing";
|
|
1672
|
+
await this.storage.setDeletionRequest(request);
|
|
1171
1673
|
try {
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1674
|
+
const result = await deleteData(request.userId, request.dataCategories);
|
|
1675
|
+
request.status = "completed";
|
|
1676
|
+
request.processedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1677
|
+
request.deletedData = result.deleted;
|
|
1678
|
+
request.retainedData = result.retained;
|
|
1679
|
+
await this.storage.setDeletionRequest(request);
|
|
1680
|
+
} catch (error) {
|
|
1681
|
+
request.status = "failed";
|
|
1682
|
+
await this.storage.setDeletionRequest(request);
|
|
1683
|
+
throw error;
|
|
1175
1684
|
}
|
|
1176
1685
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1686
|
+
/**
|
|
1687
|
+
* Get deletion request
|
|
1688
|
+
*/
|
|
1689
|
+
async getRequest(requestId) {
|
|
1690
|
+
return this.storage.getDeletionRequest(requestId);
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Get user deletion requests
|
|
1694
|
+
*/
|
|
1695
|
+
async getUserRequests(userId) {
|
|
1696
|
+
return this.storage.getDeletionRequestsByUser(userId);
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* Check if data can be deleted
|
|
1700
|
+
*/
|
|
1701
|
+
canDelete(_dataCategory, legalBasis) {
|
|
1702
|
+
if (legalBasis === "legal_obligation" || legalBasis === "vital_interest") {
|
|
1703
|
+
return false;
|
|
1704
|
+
}
|
|
1705
|
+
return true;
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Calculate retention period
|
|
1709
|
+
*/
|
|
1710
|
+
calculateRetentionEnd(createdAt, retentionPeriod) {
|
|
1711
|
+
return new Date(createdAt.getTime() + retentionPeriod * 24 * 60 * 60 * 1e3);
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Check if data should be deleted (retention period expired)
|
|
1715
|
+
*/
|
|
1716
|
+
shouldDelete(createdAt, retentionPeriod) {
|
|
1717
|
+
const retentionEnd = this.calculateRetentionEnd(createdAt, retentionPeriod);
|
|
1718
|
+
return /* @__PURE__ */ new Date() > retentionEnd;
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
function hashValue(value) {
|
|
1722
|
+
const digest = createHash("sha256").update(value).digest("hex");
|
|
1723
|
+
return `hash_${digest}`;
|
|
1724
|
+
}
|
|
1725
|
+
function anonymizeUser(user) {
|
|
1726
|
+
return {
|
|
1727
|
+
...user,
|
|
1728
|
+
email: hashValue(user.email),
|
|
1729
|
+
name: "Anonymous User",
|
|
1730
|
+
phone: void 0,
|
|
1731
|
+
address: void 0,
|
|
1732
|
+
ip: void 0
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
function pseudonymize(value, key) {
|
|
1736
|
+
const hmac = createHmac2("sha256", key).update(value).digest("hex");
|
|
1737
|
+
return `pseudo_${hmac.substring(0, 16)}`;
|
|
1738
|
+
}
|
|
1739
|
+
function anonymizeDataset(data, sensitiveFields) {
|
|
1740
|
+
return data.map((item) => {
|
|
1741
|
+
const anonymized = { ...item };
|
|
1742
|
+
sensitiveFields.forEach((field) => {
|
|
1743
|
+
if (field in anonymized && typeof anonymized[field] === "string") {
|
|
1744
|
+
anonymized[field] = hashValue(anonymized[field]);
|
|
1745
|
+
}
|
|
1746
|
+
});
|
|
1747
|
+
return anonymized;
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
function checkKAnonymity(data, quasiIdentifiers, k) {
|
|
1751
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1752
|
+
data.forEach((item) => {
|
|
1753
|
+
const key = quasiIdentifiers.map((field) => String(item[field])).join("|");
|
|
1754
|
+
groups.set(key, (groups.get(key) || 0) + 1);
|
|
1755
|
+
});
|
|
1756
|
+
return Array.from(groups.values()).every((count) => count >= k);
|
|
1757
|
+
}
|
|
1758
|
+
var DataAnonymization = {
|
|
1759
|
+
anonymizeUser,
|
|
1760
|
+
pseudonymize,
|
|
1761
|
+
hashValue,
|
|
1762
|
+
anonymizeDataset,
|
|
1763
|
+
checkKAnonymity
|
|
1764
|
+
};
|
|
1765
|
+
var PrivacyPolicyManager = class {
|
|
1766
|
+
policies = /* @__PURE__ */ new Map();
|
|
1767
|
+
currentVersion = "1.0.0";
|
|
1768
|
+
/**
|
|
1769
|
+
* Add policy version
|
|
1770
|
+
*/
|
|
1771
|
+
addPolicy(version, content, effectiveDate) {
|
|
1772
|
+
this.policies.set(version, { version, content, effectiveDate });
|
|
1773
|
+
this.currentVersion = version;
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Get current policy
|
|
1777
|
+
*/
|
|
1778
|
+
getCurrentPolicy() {
|
|
1779
|
+
return this.policies.get(this.currentVersion);
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Get policy by version
|
|
1783
|
+
*/
|
|
1784
|
+
getPolicy(version) {
|
|
1785
|
+
return this.policies.get(version);
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Check if user accepted current policy
|
|
1789
|
+
*/
|
|
1790
|
+
hasAcceptedCurrent(userAcceptedVersion) {
|
|
1791
|
+
return userAcceptedVersion === this.currentVersion;
|
|
1183
1792
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
};
|
|
1190
|
-
}
|
|
1191
|
-
const now = /* @__PURE__ */ new Date();
|
|
1192
|
-
const session = {
|
|
1193
|
-
id: data.id ?? this.createSessionId(),
|
|
1194
|
-
userId: data.userId,
|
|
1195
|
-
token: data.token ?? this.createToken(),
|
|
1196
|
-
expiresAt: data.expiresAt ? new Date(data.expiresAt) : new Date(now.getTime() + 1e3 * 60 * 60),
|
|
1197
|
-
lastActiveAt: data.lastActiveAt ? new Date(data.lastActiveAt) : now,
|
|
1198
|
-
isActive: data.isActive ?? true,
|
|
1199
|
-
updatedAt: now,
|
|
1200
|
-
createdAt: now,
|
|
1201
|
-
...data.device !== void 0 ? { device: data.device } : {}
|
|
1202
|
-
};
|
|
1203
|
-
this.sessions.set(session.id, session);
|
|
1204
|
-
return { success: true, data: session };
|
|
1205
|
-
}
|
|
1206
|
-
async findById(id) {
|
|
1207
|
-
const session = this.sessions.get(String(id));
|
|
1208
|
-
if (!session) {
|
|
1209
|
-
return {
|
|
1210
|
-
success: false,
|
|
1211
|
-
errors: [{ message: "Session not found", path: "findById" }]
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1214
|
-
return { success: true, data: session };
|
|
1215
|
-
}
|
|
1216
|
-
async delete(id) {
|
|
1217
|
-
const key = String(id);
|
|
1218
|
-
if (!this.sessions.has(key)) {
|
|
1219
|
-
return {
|
|
1220
|
-
success: false,
|
|
1221
|
-
errors: [{ message: "Session not found", path: "delete" }]
|
|
1222
|
-
};
|
|
1223
|
-
}
|
|
1224
|
-
this.sessions.delete(key);
|
|
1225
|
-
return { success: true };
|
|
1226
|
-
}
|
|
1227
|
-
async revokeAllUserSessions(userId) {
|
|
1228
|
-
let removed = false;
|
|
1229
|
-
for (const [key, session] of this.sessions.entries()) {
|
|
1230
|
-
if (session.userId === userId) {
|
|
1231
|
-
this.sessions.delete(key);
|
|
1232
|
-
removed = true;
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
if (!removed) {
|
|
1236
|
-
return {
|
|
1237
|
-
success: false,
|
|
1238
|
-
errors: [{ message: "No sessions found for user", path: "revokeAllUserSessions" }]
|
|
1239
|
-
};
|
|
1240
|
-
}
|
|
1241
|
-
return { success: true };
|
|
1242
|
-
}
|
|
1243
|
-
async findAll() {
|
|
1244
|
-
return { success: true, data: Array.from(this.sessions.values()) };
|
|
1245
|
-
}
|
|
1246
|
-
async update(id, data) {
|
|
1247
|
-
const existing = this.sessions.get(id);
|
|
1248
|
-
if (!existing) {
|
|
1249
|
-
return {
|
|
1250
|
-
success: false,
|
|
1251
|
-
errors: [{ message: "Session not found", path: "update" }]
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
const updated = {
|
|
1255
|
-
...existing,
|
|
1256
|
-
...data.userId !== void 0 ? { userId: data.userId } : {},
|
|
1257
|
-
...data.token !== void 0 ? { token: data.token } : {},
|
|
1258
|
-
...data.device !== void 0 ? { device: data.device } : {},
|
|
1259
|
-
...data.expiresAt !== void 0 ? { expiresAt: new Date(data.expiresAt) } : {},
|
|
1260
|
-
...data.lastActiveAt !== void 0 ? { lastActiveAt: new Date(data.lastActiveAt) } : {},
|
|
1261
|
-
...data.isActive !== void 0 ? { isActive: data.isActive } : {},
|
|
1262
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1263
|
-
};
|
|
1264
|
-
this.sessions.set(id, updated);
|
|
1265
|
-
return { success: true, data: updated };
|
|
1793
|
+
/**
|
|
1794
|
+
* Get all versions
|
|
1795
|
+
*/
|
|
1796
|
+
getAllVersions() {
|
|
1797
|
+
return Array.from(this.policies.keys());
|
|
1266
1798
|
}
|
|
1267
1799
|
};
|
|
1268
|
-
var
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1800
|
+
var CookieConsentManager = class {
|
|
1801
|
+
config = {
|
|
1802
|
+
necessary: true,
|
|
1803
|
+
functional: false,
|
|
1804
|
+
analytics: false,
|
|
1805
|
+
marketing: false
|
|
1806
|
+
};
|
|
1807
|
+
/**
|
|
1808
|
+
* Set consent configuration
|
|
1809
|
+
*/
|
|
1810
|
+
setConsent(config) {
|
|
1811
|
+
this.config = { ...this.config, ...config };
|
|
1812
|
+
this.saveToStorage();
|
|
1274
1813
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
}
|
|
1280
|
-
return this.repository;
|
|
1814
|
+
/**
|
|
1815
|
+
* Get consent configuration
|
|
1816
|
+
*/
|
|
1817
|
+
getConsent() {
|
|
1818
|
+
return { ...this.config };
|
|
1281
1819
|
}
|
|
1282
1820
|
/**
|
|
1283
|
-
*
|
|
1821
|
+
* Check if specific consent is granted
|
|
1284
1822
|
*/
|
|
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;
|
|
1823
|
+
hasConsent(type) {
|
|
1824
|
+
return this.config[type];
|
|
1310
1825
|
}
|
|
1311
1826
|
/**
|
|
1312
|
-
*
|
|
1827
|
+
* Save to storage
|
|
1313
1828
|
*/
|
|
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
|
-
};
|
|
1829
|
+
saveToStorage() {
|
|
1830
|
+
if (typeof localStorage !== "undefined") {
|
|
1831
|
+
localStorage.setItem("cookie-consent", JSON.stringify(this.config));
|
|
1375
1832
|
}
|
|
1376
1833
|
}
|
|
1377
1834
|
/**
|
|
1378
|
-
*
|
|
1835
|
+
* Load from storage
|
|
1379
1836
|
*/
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1384
|
-
if (!result.success) {
|
|
1837
|
+
loadFromStorage() {
|
|
1838
|
+
if (typeof localStorage !== "undefined") {
|
|
1839
|
+
const stored = localStorage.getItem("cookie-consent");
|
|
1840
|
+
if (stored) {
|
|
1385
1841
|
try {
|
|
1386
|
-
const
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1842
|
+
const parsed = JSON.parse(stored);
|
|
1843
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
1844
|
+
this.config = {
|
|
1845
|
+
necessary: true,
|
|
1846
|
+
// always required
|
|
1847
|
+
analytics: typeof parsed.analytics === "boolean" ? parsed.analytics : false,
|
|
1848
|
+
marketing: typeof parsed.marketing === "boolean" ? parsed.marketing : false,
|
|
1849
|
+
functional: typeof parsed.functional === "boolean" ? parsed.functional : true
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1390
1852
|
} catch {
|
|
1391
|
-
devLogger3.error("Session invalidation failed", {
|
|
1392
|
-
errors: result.errors
|
|
1393
|
-
});
|
|
1394
1853
|
}
|
|
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
1854
|
}
|
|
1405
|
-
return {
|
|
1406
|
-
success: false,
|
|
1407
|
-
errors: [
|
|
1408
|
-
{
|
|
1409
|
-
message: "Session invalidation failed",
|
|
1410
|
-
path: "invalidateSession"
|
|
1411
|
-
}
|
|
1412
|
-
]
|
|
1413
|
-
};
|
|
1414
1855
|
}
|
|
1415
1856
|
}
|
|
1416
1857
|
/**
|
|
1417
|
-
*
|
|
1858
|
+
* Clear consent
|
|
1418
1859
|
*/
|
|
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
|
-
};
|
|
1860
|
+
clearConsent() {
|
|
1861
|
+
this.config = {
|
|
1862
|
+
necessary: true,
|
|
1863
|
+
functional: false,
|
|
1864
|
+
analytics: false,
|
|
1865
|
+
marketing: false
|
|
1866
|
+
};
|
|
1867
|
+
if (typeof localStorage !== "undefined") {
|
|
1868
|
+
localStorage.removeItem("cookie-consent");
|
|
1455
1869
|
}
|
|
1456
1870
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
const payload = await getPayloadClient2();
|
|
1463
|
-
payload.logger.warn("Session not found", {
|
|
1464
|
-
errors: result.errors
|
|
1465
|
-
});
|
|
1466
|
-
} catch {
|
|
1467
|
-
devLogger3.warn("Session not found", { errors: result.errors });
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
return result;
|
|
1871
|
+
};
|
|
1872
|
+
var DataBreachManager = class {
|
|
1873
|
+
storage;
|
|
1874
|
+
constructor(storage) {
|
|
1875
|
+
this.storage = storage;
|
|
1471
1876
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Report data breach
|
|
1879
|
+
*/
|
|
1880
|
+
async reportBreach(breach) {
|
|
1881
|
+
const fullBreach = {
|
|
1882
|
+
...breach,
|
|
1883
|
+
id: crypto.randomUUID(),
|
|
1884
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1885
|
+
status: "detected"
|
|
1886
|
+
};
|
|
1887
|
+
await this.storage.setBreach(fullBreach);
|
|
1888
|
+
if (fullBreach.severity === "critical") {
|
|
1889
|
+
await this.notifyAuthorities(fullBreach);
|
|
1486
1890
|
}
|
|
1487
|
-
return
|
|
1891
|
+
return fullBreach;
|
|
1488
1892
|
}
|
|
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;
|
|
1893
|
+
/**
|
|
1894
|
+
* Notify authorities (required within 72 hours under GDPR)
|
|
1895
|
+
*/
|
|
1896
|
+
async notifyAuthorities(breach) {
|
|
1897
|
+
await this.storage.updateBreach(breach.id, {
|
|
1898
|
+
reportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1899
|
+
status: "notified"
|
|
1900
|
+
});
|
|
1901
|
+
getSecurityLogger().info("Breach reported to authorities", { breachId: breach.id });
|
|
1503
1902
|
}
|
|
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
|
-
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Notify affected users
|
|
1905
|
+
*/
|
|
1906
|
+
async notifyAffectedUsers(breachId, notifyFn) {
|
|
1907
|
+
const breach = await this.storage.getBreach(breachId);
|
|
1908
|
+
if (!breach) {
|
|
1909
|
+
throw new Error("Breach not found");
|
|
1516
1910
|
}
|
|
1517
|
-
|
|
1911
|
+
for (const userId of breach.affectedUsers) {
|
|
1912
|
+
await notifyFn(userId, breach);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Check if breach notification is required
|
|
1917
|
+
*/
|
|
1918
|
+
requiresNotification(breach) {
|
|
1919
|
+
return breach.severity === "high" || breach.severity === "critical" || breach.dataCategories.includes("sensitive") || breach.dataCategories.includes("financial");
|
|
1920
|
+
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Get breach
|
|
1923
|
+
*/
|
|
1924
|
+
async getBreach(id) {
|
|
1925
|
+
return this.storage.getBreach(id);
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Get all breaches
|
|
1929
|
+
*/
|
|
1930
|
+
async getAllBreaches() {
|
|
1931
|
+
return this.storage.getAllBreaches();
|
|
1518
1932
|
}
|
|
1519
1933
|
};
|
|
1520
|
-
|
|
1934
|
+
function createConsentManager(storage) {
|
|
1935
|
+
return new ConsentManager(storage);
|
|
1936
|
+
}
|
|
1937
|
+
function createDataDeletionSystem(storage) {
|
|
1938
|
+
return new DataDeletionSystem(storage);
|
|
1939
|
+
}
|
|
1940
|
+
var dataExportSystem = new DataExportSystem();
|
|
1941
|
+
var privacyPolicyManager = new PrivacyPolicyManager();
|
|
1942
|
+
var cookieConsentManager = new CookieConsentManager();
|
|
1943
|
+
function createDataBreachManager(storage) {
|
|
1944
|
+
return new DataBreachManager(storage);
|
|
1945
|
+
}
|
|
1521
1946
|
|
|
1522
|
-
// src/
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
this.
|
|
1530
|
-
|
|
1531
|
-
|
|
1947
|
+
// src/gdpr-storage.ts
|
|
1948
|
+
var InMemoryBreachStorage = class {
|
|
1949
|
+
breaches = /* @__PURE__ */ new Map();
|
|
1950
|
+
async setBreach(breach) {
|
|
1951
|
+
this.breaches.set(breach.id, breach);
|
|
1952
|
+
}
|
|
1953
|
+
async getBreach(id) {
|
|
1954
|
+
return this.breaches.get(id);
|
|
1955
|
+
}
|
|
1956
|
+
async getAllBreaches() {
|
|
1957
|
+
return Array.from(this.breaches.values());
|
|
1958
|
+
}
|
|
1959
|
+
async updateBreach(id, updates) {
|
|
1960
|
+
const existing = this.breaches.get(id);
|
|
1961
|
+
if (existing) {
|
|
1962
|
+
this.breaches.set(id, { ...existing, ...updates });
|
|
1532
1963
|
}
|
|
1533
1964
|
}
|
|
1534
1965
|
};
|
|
1535
|
-
var
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1966
|
+
var InMemoryGDPRStorage = class {
|
|
1967
|
+
consents = /* @__PURE__ */ new Map();
|
|
1968
|
+
deletionRequests = /* @__PURE__ */ new Map();
|
|
1969
|
+
// ── Consent Records ──────────────────────────────────────────────
|
|
1970
|
+
async setConsent(userId, type, record) {
|
|
1971
|
+
this.consents.set(`${userId}:${type}`, record);
|
|
1972
|
+
}
|
|
1973
|
+
async getConsent(userId, type) {
|
|
1974
|
+
return this.consents.get(`${userId}:${type}`);
|
|
1975
|
+
}
|
|
1976
|
+
async getConsentsByUser(userId) {
|
|
1977
|
+
return Array.from(this.consents.values()).filter((c) => c.userId === userId);
|
|
1978
|
+
}
|
|
1979
|
+
async getAllConsents() {
|
|
1980
|
+
return Array.from(this.consents.values());
|
|
1981
|
+
}
|
|
1982
|
+
// ── Deletion Requests ────────────────────────────────────────────
|
|
1983
|
+
async setDeletionRequest(request) {
|
|
1984
|
+
this.deletionRequests.set(request.id, request);
|
|
1985
|
+
}
|
|
1986
|
+
async getDeletionRequest(requestId) {
|
|
1987
|
+
return this.deletionRequests.get(requestId);
|
|
1988
|
+
}
|
|
1989
|
+
async getDeletionRequestsByUser(userId) {
|
|
1990
|
+
return Array.from(this.deletionRequests.values()).filter((r) => r.userId === userId);
|
|
1991
|
+
}
|
|
1540
1992
|
};
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1993
|
+
|
|
1994
|
+
// src/headers.ts
|
|
1995
|
+
var SecurityHeaders = class {
|
|
1996
|
+
config;
|
|
1997
|
+
constructor(config = {}) {
|
|
1998
|
+
this.config = config;
|
|
1545
1999
|
}
|
|
1546
2000
|
/**
|
|
1547
|
-
*
|
|
1548
|
-
* @param credentials User login credentials
|
|
1549
|
-
* @param req Optional PayloadRequest for context
|
|
1550
|
-
* @returns Login result with user data and token
|
|
2001
|
+
* Get all security headers
|
|
1551
2002
|
*/
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
this.
|
|
1568
|
-
userId: result.user.id,
|
|
1569
|
-
email: result.user.email,
|
|
1570
|
-
serviceName: this.serviceName
|
|
1571
|
-
});
|
|
1572
|
-
return result;
|
|
1573
|
-
} catch (error) {
|
|
1574
|
-
this.logger.error("User login failed", {
|
|
1575
|
-
email: credentials.email,
|
|
1576
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1577
|
-
serviceName: this.serviceName
|
|
1578
|
-
});
|
|
1579
|
-
throw new AppError(
|
|
1580
|
-
ErrorCode.AUTHENTICATION_ERROR,
|
|
1581
|
-
"Login failed",
|
|
1582
|
-
error instanceof Error ? error : new Error(String(error))
|
|
1583
|
-
);
|
|
2003
|
+
getHeaders() {
|
|
2004
|
+
const headers = {};
|
|
2005
|
+
if (this.config.contentSecurityPolicy) {
|
|
2006
|
+
headers["Content-Security-Policy"] = this.buildCSP(this.config.contentSecurityPolicy);
|
|
2007
|
+
}
|
|
2008
|
+
if (this.config.strictTransportSecurity) {
|
|
2009
|
+
headers["Strict-Transport-Security"] = this.buildHSTS(this.config.strictTransportSecurity);
|
|
2010
|
+
}
|
|
2011
|
+
if (this.config.xFrameOptions) {
|
|
2012
|
+
headers["X-Frame-Options"] = this.config.xFrameOptions;
|
|
2013
|
+
}
|
|
2014
|
+
if (this.config.xContentTypeOptions !== false) {
|
|
2015
|
+
headers["X-Content-Type-Options"] = "nosniff";
|
|
2016
|
+
}
|
|
2017
|
+
if (this.config.referrerPolicy) {
|
|
2018
|
+
headers["Referrer-Policy"] = this.config.referrerPolicy;
|
|
1584
2019
|
}
|
|
2020
|
+
if (this.config.permissionsPolicy) {
|
|
2021
|
+
headers["Permissions-Policy"] = this.buildPermissionsPolicy(this.config.permissionsPolicy);
|
|
2022
|
+
}
|
|
2023
|
+
if (this.config.crossOriginEmbedderPolicy) {
|
|
2024
|
+
headers["Cross-Origin-Embedder-Policy"] = this.config.crossOriginEmbedderPolicy;
|
|
2025
|
+
}
|
|
2026
|
+
if (this.config.crossOriginOpenerPolicy) {
|
|
2027
|
+
headers["Cross-Origin-Opener-Policy"] = this.config.crossOriginOpenerPolicy;
|
|
2028
|
+
}
|
|
2029
|
+
if (this.config.crossOriginResourcePolicy) {
|
|
2030
|
+
headers["Cross-Origin-Resource-Policy"] = this.config.crossOriginResourcePolicy;
|
|
2031
|
+
}
|
|
2032
|
+
return headers;
|
|
1585
2033
|
}
|
|
1586
2034
|
/**
|
|
1587
|
-
*
|
|
1588
|
-
* @param userId User ID to verify
|
|
1589
|
-
* @returns User data if exists
|
|
2035
|
+
* Build Content Security Policy header
|
|
1590
2036
|
*/
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
);
|
|
2037
|
+
buildCSP(config) {
|
|
2038
|
+
if (typeof config === "string") {
|
|
2039
|
+
return config;
|
|
2040
|
+
}
|
|
2041
|
+
const directives = [];
|
|
2042
|
+
const addDirective = (name, values) => {
|
|
2043
|
+
if (values && values.length > 0) {
|
|
2044
|
+
directives.push(`${name} ${values.join(" ")}`);
|
|
2045
|
+
}
|
|
2046
|
+
};
|
|
2047
|
+
addDirective("default-src", config.defaultSrc);
|
|
2048
|
+
addDirective("script-src", config.scriptSrc);
|
|
2049
|
+
addDirective("style-src", config.styleSrc);
|
|
2050
|
+
addDirective("img-src", config.imgSrc);
|
|
2051
|
+
addDirective("font-src", config.fontSrc);
|
|
2052
|
+
addDirective("connect-src", config.connectSrc);
|
|
2053
|
+
addDirective("frame-src", config.frameSrc);
|
|
2054
|
+
addDirective("object-src", config.objectSrc);
|
|
2055
|
+
addDirective("media-src", config.mediaSrc);
|
|
2056
|
+
addDirective("worker-src", config.workerSrc);
|
|
2057
|
+
addDirective("child-src", config.childSrc);
|
|
2058
|
+
addDirective("form-action", config.formAction);
|
|
2059
|
+
addDirective("frame-ancestors", config.frameAncestors);
|
|
2060
|
+
addDirective("base-uri", config.baseUri);
|
|
2061
|
+
addDirective("manifest-src", config.manifestSrc);
|
|
2062
|
+
if (config.upgradeInsecureRequests) {
|
|
2063
|
+
directives.push("upgrade-insecure-requests");
|
|
2064
|
+
}
|
|
2065
|
+
if (config.blockAllMixedContent) {
|
|
2066
|
+
directives.push("block-all-mixed-content");
|
|
2067
|
+
}
|
|
2068
|
+
if (config.reportUri) {
|
|
2069
|
+
directives.push(`report-uri ${config.reportUri}`);
|
|
1618
2070
|
}
|
|
2071
|
+
if (config.reportTo) {
|
|
2072
|
+
directives.push(`report-to ${config.reportTo}`);
|
|
2073
|
+
}
|
|
2074
|
+
return directives.join("; ");
|
|
1619
2075
|
}
|
|
1620
2076
|
/**
|
|
1621
|
-
*
|
|
1622
|
-
* @param email User email address
|
|
1623
|
-
* @returns Password reset token
|
|
2077
|
+
* Build HSTS header
|
|
1624
2078
|
*/
|
|
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
|
-
);
|
|
2079
|
+
buildHSTS(config) {
|
|
2080
|
+
if (config === true) {
|
|
2081
|
+
return "max-age=31536000; includeSubDomains";
|
|
1652
2082
|
}
|
|
2083
|
+
if (config === false) {
|
|
2084
|
+
return "";
|
|
2085
|
+
}
|
|
2086
|
+
const parts = [`max-age=${config.maxAge}`];
|
|
2087
|
+
if (config.includeSubDomains) {
|
|
2088
|
+
parts.push("includeSubDomains");
|
|
2089
|
+
}
|
|
2090
|
+
if (config.preload) {
|
|
2091
|
+
parts.push("preload");
|
|
2092
|
+
}
|
|
2093
|
+
return parts.join("; ");
|
|
1653
2094
|
}
|
|
1654
2095
|
/**
|
|
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.
|
|
2096
|
+
* Build Permissions-Policy header
|
|
1662
2097
|
*/
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
serviceName: this.serviceName,
|
|
1667
|
-
operation: "resetPassword"
|
|
1668
|
-
});
|
|
1669
|
-
const result = await this.payload.resetPassword({
|
|
1670
|
-
collection: "users",
|
|
1671
|
-
data: {
|
|
1672
|
-
token,
|
|
1673
|
-
password: newPassword
|
|
1674
|
-
},
|
|
1675
|
-
overrideAccess: true
|
|
1676
|
-
// System-level operation for password reset flow
|
|
1677
|
-
});
|
|
1678
|
-
this.logger.info("Password reset successful", {
|
|
1679
|
-
userId: result.user?.id,
|
|
1680
|
-
serviceName: this.serviceName
|
|
1681
|
-
});
|
|
1682
|
-
return result;
|
|
1683
|
-
} catch (error) {
|
|
1684
|
-
this.logger.error("Password reset failed", {
|
|
1685
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1686
|
-
serviceName: this.serviceName
|
|
1687
|
-
});
|
|
1688
|
-
throw new AppError(
|
|
1689
|
-
ErrorCode.AUTHENTICATION_ERROR,
|
|
1690
|
-
"Password reset failed",
|
|
1691
|
-
error instanceof Error ? error : new Error(String(error))
|
|
1692
|
-
);
|
|
2098
|
+
buildPermissionsPolicy(config) {
|
|
2099
|
+
if (typeof config === "string") {
|
|
2100
|
+
return config;
|
|
1693
2101
|
}
|
|
2102
|
+
const policies = [];
|
|
2103
|
+
Object.entries(config).forEach(([feature, origins]) => {
|
|
2104
|
+
if (!origins || origins.length === 0) {
|
|
2105
|
+
policies.push(`${feature}=()`);
|
|
2106
|
+
} else if (origins.includes("*")) {
|
|
2107
|
+
policies.push(`${feature}=*`);
|
|
2108
|
+
} else {
|
|
2109
|
+
const originsList = origins.map((o) => `"${o}"`).join(" ");
|
|
2110
|
+
policies.push(`${feature}=(${originsList})`);
|
|
2111
|
+
}
|
|
2112
|
+
});
|
|
2113
|
+
return policies.join(", ");
|
|
1694
2114
|
}
|
|
1695
2115
|
/**
|
|
1696
|
-
*
|
|
1697
|
-
* @param token Email verification token
|
|
1698
|
-
* @returns Verification success status
|
|
2116
|
+
* Apply headers to response
|
|
1699
2117
|
*/
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
2118
|
+
applyHeaders(response) {
|
|
2119
|
+
const headers = this.getHeaders();
|
|
2120
|
+
Object.entries(headers).forEach(([name, value]) => {
|
|
2121
|
+
response.headers.set(name, value);
|
|
2122
|
+
});
|
|
2123
|
+
return response;
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
var CORSManager = class {
|
|
2127
|
+
config;
|
|
2128
|
+
constructor(config = {}) {
|
|
2129
|
+
this.config = {
|
|
2130
|
+
origin: config.origin ?? [],
|
|
2131
|
+
methods: config.methods || ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
2132
|
+
allowedHeaders: config.allowedHeaders || ["Content-Type", "Authorization"],
|
|
2133
|
+
exposedHeaders: config.exposedHeaders || [],
|
|
2134
|
+
credentials: config.credentials ?? false,
|
|
2135
|
+
maxAge: config.maxAge || 86400,
|
|
2136
|
+
preflightContinue: config.preflightContinue ?? false,
|
|
2137
|
+
optionsSuccessStatus: config.optionsSuccessStatus || 204
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
/**
|
|
2141
|
+
* Check if origin is allowed
|
|
2142
|
+
*/
|
|
2143
|
+
isOriginAllowed(origin) {
|
|
2144
|
+
const { origin: allowedOrigin } = this.config;
|
|
2145
|
+
if (allowedOrigin === "*") {
|
|
2146
|
+
return true;
|
|
2147
|
+
}
|
|
2148
|
+
if (typeof allowedOrigin === "function") {
|
|
2149
|
+
return allowedOrigin(origin);
|
|
2150
|
+
}
|
|
2151
|
+
if (typeof allowedOrigin === "string") {
|
|
2152
|
+
return origin === allowedOrigin;
|
|
1725
2153
|
}
|
|
2154
|
+
if (Array.isArray(allowedOrigin)) {
|
|
2155
|
+
return allowedOrigin.includes(origin);
|
|
2156
|
+
}
|
|
2157
|
+
return false;
|
|
1726
2158
|
}
|
|
1727
2159
|
/**
|
|
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.
|
|
2160
|
+
* Get CORS headers
|
|
1735
2161
|
*/
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
serviceName: this.serviceName,
|
|
1741
|
-
operation: "unlockUser"
|
|
1742
|
-
});
|
|
1743
|
-
const result = await this.payload.unlock({
|
|
1744
|
-
collection: "users",
|
|
1745
|
-
data: { email, password },
|
|
1746
|
-
overrideAccess: true
|
|
1747
|
-
// System-level operation for account recovery
|
|
1748
|
-
});
|
|
1749
|
-
this.logger.info("User account unlock completed", {
|
|
1750
|
-
email,
|
|
1751
|
-
success: result,
|
|
1752
|
-
serviceName: this.serviceName
|
|
1753
|
-
});
|
|
1754
|
-
return result;
|
|
1755
|
-
} catch (error) {
|
|
1756
|
-
this.logger.error("User account unlock failed", {
|
|
1757
|
-
email,
|
|
1758
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1759
|
-
serviceName: this.serviceName
|
|
1760
|
-
});
|
|
1761
|
-
throw new AppError(
|
|
1762
|
-
ErrorCode.AUTHENTICATION_ERROR,
|
|
1763
|
-
"User account unlock failed",
|
|
1764
|
-
error instanceof Error ? error : new Error(String(error))
|
|
1765
|
-
);
|
|
2162
|
+
getCORSHeaders(origin) {
|
|
2163
|
+
const headers = {};
|
|
2164
|
+
if (this.config.origin !== "*") {
|
|
2165
|
+
headers.Vary = "Origin";
|
|
1766
2166
|
}
|
|
2167
|
+
if (!this.isOriginAllowed(origin)) {
|
|
2168
|
+
return headers;
|
|
2169
|
+
}
|
|
2170
|
+
headers["Access-Control-Allow-Origin"] = this.config.origin === "*" ? "*" : origin;
|
|
2171
|
+
if (this.config.credentials && this.config.origin !== "*") {
|
|
2172
|
+
headers["Access-Control-Allow-Credentials"] = "true";
|
|
2173
|
+
}
|
|
2174
|
+
if (this.config.exposedHeaders.length > 0) {
|
|
2175
|
+
headers["Access-Control-Expose-Headers"] = this.config.exposedHeaders.join(", ");
|
|
2176
|
+
}
|
|
2177
|
+
return headers;
|
|
1767
2178
|
}
|
|
1768
2179
|
/**
|
|
1769
|
-
*
|
|
2180
|
+
* Get preflight headers
|
|
1770
2181
|
*/
|
|
1771
|
-
|
|
1772
|
-
this.
|
|
1773
|
-
|
|
1774
|
-
|
|
2182
|
+
getPreflightHeaders(origin) {
|
|
2183
|
+
const headers = this.getCORSHeaders(origin);
|
|
2184
|
+
if (!this.isOriginAllowed(origin)) {
|
|
2185
|
+
return headers;
|
|
2186
|
+
}
|
|
2187
|
+
headers["Access-Control-Allow-Methods"] = this.config.methods.join(", ");
|
|
2188
|
+
headers["Access-Control-Allow-Headers"] = this.config.allowedHeaders.join(", ");
|
|
2189
|
+
headers["Access-Control-Max-Age"] = this.config.maxAge.toString();
|
|
2190
|
+
return headers;
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* Handle CORS request
|
|
2194
|
+
*/
|
|
2195
|
+
handleRequest(request) {
|
|
2196
|
+
const origin = request.headers.get("Origin");
|
|
2197
|
+
if (!origin) {
|
|
2198
|
+
return null;
|
|
2199
|
+
}
|
|
2200
|
+
if (request.method === "OPTIONS") {
|
|
2201
|
+
return this.handlePreflight(request, origin);
|
|
2202
|
+
}
|
|
2203
|
+
return null;
|
|
1775
2204
|
}
|
|
1776
2205
|
/**
|
|
1777
|
-
*
|
|
2206
|
+
* Handle preflight request
|
|
1778
2207
|
*/
|
|
1779
|
-
|
|
1780
|
-
this.
|
|
1781
|
-
|
|
2208
|
+
handlePreflight(_request, origin) {
|
|
2209
|
+
if (!this.isOriginAllowed(origin)) {
|
|
2210
|
+
return new Response(null, { status: 403 });
|
|
2211
|
+
}
|
|
2212
|
+
const headers = this.getPreflightHeaders(origin);
|
|
2213
|
+
return new Response(null, {
|
|
2214
|
+
status: this.config.optionsSuccessStatus,
|
|
2215
|
+
headers
|
|
1782
2216
|
});
|
|
1783
2217
|
}
|
|
1784
2218
|
/**
|
|
1785
|
-
*
|
|
2219
|
+
* Apply CORS headers to response
|
|
1786
2220
|
*/
|
|
1787
|
-
|
|
1788
|
-
this.
|
|
1789
|
-
|
|
2221
|
+
applyHeaders(response, origin) {
|
|
2222
|
+
if (!this.isOriginAllowed(origin)) {
|
|
2223
|
+
return response;
|
|
2224
|
+
}
|
|
2225
|
+
const headers = this.getCORSHeaders(origin);
|
|
2226
|
+
Object.entries(headers).forEach(([name, value]) => {
|
|
2227
|
+
response.headers.set(name, value);
|
|
1790
2228
|
});
|
|
2229
|
+
return response;
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
var SecurityPresets = {
|
|
2233
|
+
/**
|
|
2234
|
+
* Strict security (recommended for production)
|
|
2235
|
+
*/
|
|
2236
|
+
strict: () => ({
|
|
2237
|
+
contentSecurityPolicy: {
|
|
2238
|
+
defaultSrc: ["'self'"],
|
|
2239
|
+
scriptSrc: ["'self'"],
|
|
2240
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
2241
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
2242
|
+
fontSrc: ["'self'", "data:"],
|
|
2243
|
+
connectSrc: ["'self'"],
|
|
2244
|
+
frameSrc: ["'none'"],
|
|
2245
|
+
objectSrc: ["'none'"],
|
|
2246
|
+
baseUri: ["'self'"],
|
|
2247
|
+
formAction: ["'self'"],
|
|
2248
|
+
frameAncestors: ["'none'"],
|
|
2249
|
+
upgradeInsecureRequests: true
|
|
2250
|
+
},
|
|
2251
|
+
strictTransportSecurity: {
|
|
2252
|
+
maxAge: 31536e3,
|
|
2253
|
+
includeSubDomains: true,
|
|
2254
|
+
preload: true
|
|
2255
|
+
},
|
|
2256
|
+
xFrameOptions: "DENY",
|
|
2257
|
+
xContentTypeOptions: true,
|
|
2258
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
2259
|
+
crossOriginEmbedderPolicy: "require-corp",
|
|
2260
|
+
crossOriginOpenerPolicy: "same-origin",
|
|
2261
|
+
crossOriginResourcePolicy: "same-origin"
|
|
2262
|
+
}),
|
|
2263
|
+
/**
|
|
2264
|
+
* Moderate security (balanced)
|
|
2265
|
+
*/
|
|
2266
|
+
moderate: () => ({
|
|
2267
|
+
contentSecurityPolicy: {
|
|
2268
|
+
defaultSrc: ["'self'"],
|
|
2269
|
+
scriptSrc: ["'self'", "'unsafe-inline'"],
|
|
2270
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
2271
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
2272
|
+
fontSrc: ["'self'", "data:", "https:"],
|
|
2273
|
+
connectSrc: ["'self'", "https:"],
|
|
2274
|
+
frameAncestors: ["'self'"]
|
|
2275
|
+
},
|
|
2276
|
+
strictTransportSecurity: {
|
|
2277
|
+
maxAge: 31536e3,
|
|
2278
|
+
includeSubDomains: true
|
|
2279
|
+
},
|
|
2280
|
+
xFrameOptions: "SAMEORIGIN",
|
|
2281
|
+
xContentTypeOptions: true,
|
|
2282
|
+
referrerPolicy: "origin-when-cross-origin"
|
|
2283
|
+
}),
|
|
2284
|
+
/**
|
|
2285
|
+
* Development (permissive)
|
|
2286
|
+
*/
|
|
2287
|
+
development: () => ({
|
|
2288
|
+
xContentTypeOptions: true,
|
|
2289
|
+
referrerPolicy: "no-referrer-when-downgrade"
|
|
2290
|
+
})
|
|
2291
|
+
};
|
|
2292
|
+
var CORSPresets = {
|
|
2293
|
+
/**
|
|
2294
|
+
* Strict CORS (same origin only)
|
|
2295
|
+
*/
|
|
2296
|
+
strict: () => ({
|
|
2297
|
+
origin: [],
|
|
2298
|
+
methods: ["GET", "POST", "PUT", "DELETE"],
|
|
2299
|
+
allowedHeaders: ["Content-Type", "Authorization"],
|
|
2300
|
+
credentials: true,
|
|
2301
|
+
maxAge: 86400
|
|
2302
|
+
}),
|
|
2303
|
+
/**
|
|
2304
|
+
* Moderate CORS (specific origins)
|
|
2305
|
+
*/
|
|
2306
|
+
moderate: (allowedOrigins) => ({
|
|
2307
|
+
origin: allowedOrigins,
|
|
2308
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
2309
|
+
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
|
|
2310
|
+
exposedHeaders: ["X-Total-Count"],
|
|
2311
|
+
credentials: true,
|
|
2312
|
+
maxAge: 86400
|
|
2313
|
+
}),
|
|
2314
|
+
/**
|
|
2315
|
+
* Permissive CORS (all origins) — development only.
|
|
2316
|
+
* Logs a warning if used when NODE_ENV === 'production'.
|
|
2317
|
+
*/
|
|
2318
|
+
permissive: () => {
|
|
2319
|
+
if (process.env.NODE_ENV === "production") {
|
|
2320
|
+
getSecurityLogger().warn(
|
|
2321
|
+
"[SecurityPresets] CORS permissive preset used in production \u2014 this allows all origins. Use moderate() with explicit origins instead."
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
return {
|
|
2325
|
+
origin: "*",
|
|
2326
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
2327
|
+
allowedHeaders: ["*"],
|
|
2328
|
+
credentials: false,
|
|
2329
|
+
maxAge: 86400
|
|
2330
|
+
};
|
|
2331
|
+
},
|
|
2332
|
+
/**
|
|
2333
|
+
* API CORS (public read-only APIs) — credentials disabled.
|
|
2334
|
+
* Logs a warning if used when NODE_ENV === 'production'.
|
|
2335
|
+
*/
|
|
2336
|
+
api: () => {
|
|
2337
|
+
if (process.env.NODE_ENV === "production") {
|
|
2338
|
+
getSecurityLogger().warn(
|
|
2339
|
+
'[SecurityPresets] CORS api preset uses origin:"*". For production, pass explicit origins to moderate() instead.'
|
|
2340
|
+
);
|
|
2341
|
+
}
|
|
2342
|
+
return {
|
|
2343
|
+
origin: "*",
|
|
2344
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
2345
|
+
allowedHeaders: ["Content-Type", "Authorization", "X-API-Key"],
|
|
2346
|
+
exposedHeaders: ["X-RateLimit-Limit", "X-RateLimit-Remaining", "X-RateLimit-Reset"],
|
|
2347
|
+
credentials: false,
|
|
2348
|
+
maxAge: 86400
|
|
2349
|
+
};
|
|
1791
2350
|
}
|
|
1792
2351
|
};
|
|
1793
|
-
function
|
|
1794
|
-
|
|
2352
|
+
function createSecurityMiddleware(securityConfig, corsConfig) {
|
|
2353
|
+
const security = new SecurityHeaders(securityConfig);
|
|
2354
|
+
const cors = new CORSManager(corsConfig);
|
|
2355
|
+
return async (request, next) => {
|
|
2356
|
+
const origin = request.headers.get("Origin");
|
|
2357
|
+
if (origin && request.method === "OPTIONS") {
|
|
2358
|
+
const preflightResponse = cors.handleRequest(request);
|
|
2359
|
+
if (preflightResponse) {
|
|
2360
|
+
return preflightResponse;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
const response = await next();
|
|
2364
|
+
security.applyHeaders(response);
|
|
2365
|
+
if (origin) {
|
|
2366
|
+
cors.applyHeaders(response, origin);
|
|
2367
|
+
}
|
|
2368
|
+
return response;
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
function setRateLimitHeaders(response, limit, remaining, reset) {
|
|
2372
|
+
response.headers.set("X-RateLimit-Limit", limit.toString());
|
|
2373
|
+
response.headers.set("X-RateLimit-Remaining", remaining.toString());
|
|
2374
|
+
response.headers.set("X-RateLimit-Reset", reset.toString());
|
|
1795
2375
|
}
|
|
1796
2376
|
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
|
-
|
|
2377
|
+
AuditReportGenerator,
|
|
2378
|
+
AuditSystem,
|
|
2379
|
+
AuditTrail,
|
|
2380
|
+
AuthorizationSystem,
|
|
2381
|
+
CORSManager,
|
|
2382
|
+
CORSPresets,
|
|
2383
|
+
CommonRoles,
|
|
2384
|
+
ConsentManager,
|
|
2385
|
+
CookieConsentManager,
|
|
2386
|
+
DataAnonymization,
|
|
2387
|
+
DataBreachManager,
|
|
2388
|
+
DataDeletionSystem,
|
|
2389
|
+
DataExportSystem,
|
|
2390
|
+
DataMasking,
|
|
2391
|
+
EncryptionSystem,
|
|
2392
|
+
EnvelopeEncryption,
|
|
2393
|
+
FieldEncryption,
|
|
2394
|
+
InMemoryAuditStorage,
|
|
2395
|
+
InMemoryBreachStorage,
|
|
2396
|
+
InMemoryGDPRStorage,
|
|
2397
|
+
KeyRotationManager,
|
|
2398
|
+
OAuthClient,
|
|
2399
|
+
OAuthProviders,
|
|
2400
|
+
PasswordHasher,
|
|
2401
|
+
PermissionBuilder,
|
|
2402
|
+
PermissionCache,
|
|
2403
|
+
PolicyBuilder,
|
|
2404
|
+
PrivacyPolicyManager,
|
|
2405
|
+
RequirePermission,
|
|
2406
|
+
RequireRole,
|
|
2407
|
+
SecurityHeaders,
|
|
2408
|
+
SecurityPresets,
|
|
2409
|
+
TokenGenerator,
|
|
2410
|
+
TwoFactorAuth,
|
|
2411
|
+
audit,
|
|
2412
|
+
authorization,
|
|
2413
|
+
canAccessResource,
|
|
2414
|
+
checkAttributeAccess,
|
|
2415
|
+
configureSecurityLogger,
|
|
2416
|
+
cookieConsentManager,
|
|
2417
|
+
createAuditMiddleware,
|
|
2418
|
+
createAuthorizationMiddleware,
|
|
2419
|
+
createConsentManager,
|
|
2420
|
+
createDataBreachManager,
|
|
2421
|
+
createDataDeletionSystem,
|
|
2422
|
+
createSecurityMiddleware,
|
|
2423
|
+
dataExportSystem,
|
|
2424
|
+
encryption,
|
|
2425
|
+
permissionCache,
|
|
2426
|
+
privacyPolicyManager,
|
|
2427
|
+
setRateLimitHeaders
|
|
1840
2428
|
};
|
|
1841
2429
|
//# sourceMappingURL=index.js.map
|