@sonicjs-cms/core 2.8.1 → 2.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/{app-CYEm1ytG.d.cts → app-DnQ26Lho.d.cts} +3 -0
  2. package/dist/{app-CYEm1ytG.d.ts → app-DnQ26Lho.d.ts} +3 -0
  3. package/dist/{chunk-S6K2H2TS.cjs → chunk-3G7XX4UI.cjs} +9 -9
  4. package/dist/{chunk-S6K2H2TS.cjs.map → chunk-3G7XX4UI.cjs.map} +1 -1
  5. package/dist/{chunk-KAT3OKHE.js → chunk-5XAI2XUF.js} +33 -37
  6. package/dist/chunk-5XAI2XUF.js.map +1 -0
  7. package/dist/{chunk-H7AMQWVI.js → chunk-74XCYEI7.js} +3 -3
  8. package/dist/{chunk-H7AMQWVI.js.map → chunk-74XCYEI7.js.map} +1 -1
  9. package/dist/{chunk-FZRZYQYU.js → chunk-CH5UHZVM.js} +2604 -2364
  10. package/dist/chunk-CH5UHZVM.js.map +1 -0
  11. package/dist/{chunk-7Q2XPM2U.js → chunk-GTFMI24U.js} +21 -2
  12. package/dist/chunk-GTFMI24U.js.map +1 -0
  13. package/dist/{chunk-SKLRRFJJ.cjs → chunk-HXHVU5GM.cjs} +21 -2
  14. package/dist/chunk-HXHVU5GM.cjs.map +1 -0
  15. package/dist/{chunk-WDQZYCQO.cjs → chunk-JDIM5AG7.cjs} +32 -39
  16. package/dist/chunk-JDIM5AG7.cjs.map +1 -0
  17. package/dist/{chunk-VCH6HXVP.js → chunk-JJS7JZCH.js} +58 -4
  18. package/dist/chunk-JJS7JZCH.js.map +1 -0
  19. package/dist/chunk-K4Q4SFJJ.cjs +568 -0
  20. package/dist/chunk-K4Q4SFJJ.cjs.map +1 -0
  21. package/dist/{chunk-SHCYIZAN.cjs → chunk-LTKV7AE5.cjs} +58 -4
  22. package/dist/chunk-LTKV7AE5.cjs.map +1 -0
  23. package/dist/chunk-MNWKYY5E.cjs +44 -0
  24. package/dist/chunk-MNWKYY5E.cjs.map +1 -0
  25. package/dist/{chunk-JVRRG36J.cjs → chunk-R4WR3VTN.cjs} +2393 -2153
  26. package/dist/chunk-R4WR3VTN.cjs.map +1 -0
  27. package/dist/chunk-TQABQWOP.js +39 -0
  28. package/dist/chunk-TQABQWOP.js.map +1 -0
  29. package/dist/chunk-Y3VMEGY2.js +541 -0
  30. package/dist/chunk-Y3VMEGY2.js.map +1 -0
  31. package/dist/{collection-config-BF95LgQb.d.cts → collection-config-i8EaAF7z.d.cts} +2 -1
  32. package/dist/{collection-config-BF95LgQb.d.ts → collection-config-i8EaAF7z.d.ts} +2 -1
  33. package/dist/{filter-bar.template-By4jeiw_.d.cts → filter-bar.template-Daw8ZDoq.d.cts} +1 -0
  34. package/dist/{filter-bar.template-By4jeiw_.d.ts → filter-bar.template-Daw8ZDoq.d.ts} +1 -0
  35. package/dist/index.cjs +112 -111
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +6 -6
  38. package/dist/index.d.ts +6 -6
  39. package/dist/index.js +16 -15
  40. package/dist/index.js.map +1 -1
  41. package/dist/middleware.cjs +43 -23
  42. package/dist/middleware.d.cts +86 -6
  43. package/dist/middleware.d.ts +86 -6
  44. package/dist/middleware.js +2 -2
  45. package/dist/migrations-7X4RPH7O.cjs +13 -0
  46. package/dist/{migrations-76NR5BVF.cjs.map → migrations-7X4RPH7O.cjs.map} +1 -1
  47. package/dist/migrations-KHWFJ2HN.js +4 -0
  48. package/dist/{migrations-2NTJ44OR.js.map → migrations-KHWFJ2HN.js.map} +1 -1
  49. package/dist/{plugin-bootstrap-C7Mj00Ud.d.ts → plugin-bootstrap-CJozpgmI.d.cts} +1 -1
  50. package/dist/{plugin-bootstrap-DKB5f8-E.d.cts → plugin-bootstrap-DU5VmuHZ.d.ts} +1 -1
  51. package/dist/routes.cjs +29 -28
  52. package/dist/routes.d.cts +1 -1
  53. package/dist/routes.d.ts +1 -1
  54. package/dist/routes.js +6 -5
  55. package/dist/services.cjs +2 -2
  56. package/dist/services.d.cts +2 -2
  57. package/dist/services.d.ts +2 -2
  58. package/dist/services.js +1 -1
  59. package/dist/templates.cjs +20 -19
  60. package/dist/templates.d.cts +1 -1
  61. package/dist/templates.d.ts +1 -1
  62. package/dist/templates.js +3 -2
  63. package/dist/types.d.cts +1 -1
  64. package/dist/types.d.ts +1 -1
  65. package/dist/utils.cjs +24 -23
  66. package/dist/utils.d.cts +2 -2
  67. package/dist/utils.d.ts +2 -2
  68. package/dist/utils.js +2 -1
  69. package/dist/{version-vktVAxhe.d.cts → version-C_CXrN_T.d.cts} +5 -0
  70. package/dist/{version-vktVAxhe.d.ts → version-C_CXrN_T.d.ts} +5 -0
  71. package/migrations/032_user_profiles.sql +36 -0
  72. package/package.json +2 -2
  73. package/dist/chunk-7Q2XPM2U.js.map +0 -1
  74. package/dist/chunk-FZRZYQYU.js.map +0 -1
  75. package/dist/chunk-GIWIJNBH.cjs +0 -243
  76. package/dist/chunk-GIWIJNBH.cjs.map +0 -1
  77. package/dist/chunk-JVRRG36J.cjs.map +0 -1
  78. package/dist/chunk-KAT3OKHE.js.map +0 -1
  79. package/dist/chunk-QWTS6NSP.js +0 -221
  80. package/dist/chunk-QWTS6NSP.js.map +0 -1
  81. package/dist/chunk-SHCYIZAN.cjs.map +0 -1
  82. package/dist/chunk-SKLRRFJJ.cjs.map +0 -1
  83. package/dist/chunk-VCH6HXVP.js.map +0 -1
  84. package/dist/chunk-WDQZYCQO.cjs.map +0 -1
  85. package/dist/migrations-2NTJ44OR.js +0 -4
  86. package/dist/migrations-76NR5BVF.cjs +0 -13
@@ -0,0 +1,568 @@
1
+ 'use strict';
2
+
3
+ var chunkMPT5PA6U_cjs = require('./chunk-MPT5PA6U.cjs');
4
+ var chunkHXHVU5GM_cjs = require('./chunk-HXHVU5GM.cjs');
5
+ var chunkRCQ2HIQD_cjs = require('./chunk-RCQ2HIQD.cjs');
6
+ var jwt = require('hono/jwt');
7
+ var cookie = require('hono/cookie');
8
+
9
+ // src/middleware/bootstrap.ts
10
+ var bootstrapComplete = false;
11
+ function verifySecurityConfig(env) {
12
+ const warnings = [];
13
+ if (!env.JWT_SECRET) {
14
+ warnings.push(
15
+ "JWT_SECRET is not set \u2014 using hardcoded fallback. Set via `wrangler secret put JWT_SECRET`"
16
+ );
17
+ } else if (env.JWT_SECRET.includes("change-in-production")) {
18
+ warnings.push(
19
+ "JWT_SECRET contains the default value \u2014 tokens are forgeable. Generate a strong random secret"
20
+ );
21
+ }
22
+ if (!env.CORS_ORIGINS) {
23
+ warnings.push(
24
+ "CORS_ORIGINS is not set \u2014 all cross-origin API requests will be rejected"
25
+ );
26
+ }
27
+ if (!env.ENVIRONMENT) {
28
+ warnings.push(
29
+ 'ENVIRONMENT is not set \u2014 HSTS header will not be applied. Set to "production" or "development"'
30
+ );
31
+ }
32
+ if (warnings.length === 0) {
33
+ return;
34
+ }
35
+ const isProduction = env.ENVIRONMENT === "production";
36
+ for (const warning of warnings) {
37
+ console.warn(`[SonicJS Security] ${warning}`);
38
+ }
39
+ if (isProduction) {
40
+ const hasCritical = !env.JWT_SECRET || env.JWT_SECRET.includes("change-in-production");
41
+ if (hasCritical) {
42
+ throw new Error(
43
+ "[SonicJS Security] CRITICAL: Production deployment is missing a secure JWT_SECRET. Set it via `wrangler secret put JWT_SECRET` before deploying."
44
+ );
45
+ }
46
+ }
47
+ }
48
+ function bootstrapMiddleware(config = {}) {
49
+ return async (c, next) => {
50
+ if (bootstrapComplete) {
51
+ return next();
52
+ }
53
+ const path = c.req.path;
54
+ if (path.startsWith("/images/") || path.startsWith("/assets/") || path === "/health" || path.endsWith(".js") || path.endsWith(".css") || path.endsWith(".png") || path.endsWith(".jpg") || path.endsWith(".ico")) {
55
+ return next();
56
+ }
57
+ try {
58
+ console.log("[Bootstrap] Starting system initialization...");
59
+ console.log("[Bootstrap] Running database migrations...");
60
+ const migrationService = new chunkHXHVU5GM_cjs.MigrationService(c.env.DB);
61
+ await migrationService.runPendingMigrations();
62
+ console.log("[Bootstrap] Syncing collection configurations...");
63
+ try {
64
+ await chunkMPT5PA6U_cjs.syncCollections(c.env.DB);
65
+ } catch (error) {
66
+ console.error("[Bootstrap] Error syncing collections:", error);
67
+ }
68
+ if (!config.plugins?.disableAll) {
69
+ console.log("[Bootstrap] Bootstrapping core plugins...");
70
+ const bootstrapService = new chunkMPT5PA6U_cjs.PluginBootstrapService(c.env.DB);
71
+ const needsBootstrap = await bootstrapService.isBootstrapNeeded();
72
+ if (needsBootstrap) {
73
+ await bootstrapService.bootstrapCorePlugins();
74
+ }
75
+ } else {
76
+ console.log("[Bootstrap] Plugin bootstrap skipped (disableAll is true)");
77
+ }
78
+ bootstrapComplete = true;
79
+ console.log("[Bootstrap] System initialization completed");
80
+ } catch (error) {
81
+ console.error("[Bootstrap] Error during system initialization:", error);
82
+ }
83
+ verifySecurityConfig(c.env);
84
+ return next();
85
+ };
86
+ }
87
+ var JWT_SECRET_FALLBACK = "your-super-secret-jwt-key-change-in-production";
88
+ var AuthManager = class {
89
+ static async generateToken(userId, email, role, secret) {
90
+ const payload = {
91
+ userId,
92
+ email,
93
+ role,
94
+ exp: Math.floor(Date.now() / 1e3) + 60 * 60 * 24,
95
+ // 24 hours
96
+ iat: Math.floor(Date.now() / 1e3)
97
+ };
98
+ return await jwt.sign(payload, secret || JWT_SECRET_FALLBACK, "HS256");
99
+ }
100
+ static async verifyToken(token, secret) {
101
+ try {
102
+ const payload = await jwt.verify(token, secret || JWT_SECRET_FALLBACK, "HS256");
103
+ if (payload.exp < Math.floor(Date.now() / 1e3)) {
104
+ return null;
105
+ }
106
+ return payload;
107
+ } catch (error) {
108
+ console.error("Token verification failed:", error);
109
+ return null;
110
+ }
111
+ }
112
+ static async hashPassword(password) {
113
+ const iterations = 1e5;
114
+ const salt = new Uint8Array(16);
115
+ crypto.getRandomValues(salt);
116
+ const encoder = new TextEncoder();
117
+ const keyMaterial = await crypto.subtle.importKey(
118
+ "raw",
119
+ encoder.encode(password),
120
+ "PBKDF2",
121
+ false,
122
+ ["deriveBits"]
123
+ );
124
+ const hashBuffer = await crypto.subtle.deriveBits(
125
+ {
126
+ name: "PBKDF2",
127
+ salt,
128
+ iterations,
129
+ hash: "SHA-256"
130
+ },
131
+ keyMaterial,
132
+ 256
133
+ );
134
+ const saltHex = Array.from(salt).map((b) => b.toString(16).padStart(2, "0")).join("");
135
+ const hashHex = Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
136
+ return `pbkdf2:${iterations}:${saltHex}:${hashHex}`;
137
+ }
138
+ static async hashPasswordLegacy(password) {
139
+ const encoder = new TextEncoder();
140
+ const data = encoder.encode(password + "salt-change-in-production");
141
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
142
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
143
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
144
+ }
145
+ static async verifyPassword(password, storedHash) {
146
+ if (storedHash.startsWith("pbkdf2:")) {
147
+ const parts = storedHash.split(":");
148
+ if (parts.length !== 4) return false;
149
+ const iterationsStr = parts[1];
150
+ const saltHex = parts[2];
151
+ const expectedHashHex = parts[3];
152
+ const iterations = parseInt(iterationsStr, 10);
153
+ const saltBytes = saltHex.match(/.{2}/g);
154
+ if (!saltBytes) return false;
155
+ const salt = new Uint8Array(saltBytes.map((byte) => parseInt(byte, 16)));
156
+ const encoder = new TextEncoder();
157
+ const keyMaterial = await crypto.subtle.importKey(
158
+ "raw",
159
+ encoder.encode(password),
160
+ "PBKDF2",
161
+ false,
162
+ ["deriveBits"]
163
+ );
164
+ const hashBuffer = await crypto.subtle.deriveBits(
165
+ {
166
+ name: "PBKDF2",
167
+ salt,
168
+ iterations,
169
+ hash: "SHA-256"
170
+ },
171
+ keyMaterial,
172
+ 256
173
+ );
174
+ const actualHashHex = Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
175
+ if (actualHashHex.length !== expectedHashHex.length) return false;
176
+ let result2 = 0;
177
+ for (let i = 0; i < actualHashHex.length; i++) {
178
+ result2 |= actualHashHex.charCodeAt(i) ^ expectedHashHex.charCodeAt(i);
179
+ }
180
+ return result2 === 0;
181
+ }
182
+ const legacyHash = await this.hashPasswordLegacy(password);
183
+ if (legacyHash.length !== storedHash.length) return false;
184
+ let result = 0;
185
+ for (let i = 0; i < legacyHash.length; i++) {
186
+ result |= legacyHash.charCodeAt(i) ^ storedHash.charCodeAt(i);
187
+ }
188
+ return result === 0;
189
+ }
190
+ static isLegacyHash(storedHash) {
191
+ return !storedHash.startsWith("pbkdf2:");
192
+ }
193
+ /**
194
+ * Set authentication cookie - useful for plugins implementing alternative auth methods
195
+ * @param c - Hono context
196
+ * @param token - JWT token to set in cookie
197
+ * @param options - Optional cookie configuration
198
+ */
199
+ static setAuthCookie(c, token, options) {
200
+ cookie.setCookie(c, "auth_token", token, {
201
+ httpOnly: options?.httpOnly ?? true,
202
+ secure: options?.secure ?? true,
203
+ sameSite: options?.sameSite ?? "Strict",
204
+ maxAge: options?.maxAge ?? 60 * 60 * 24
205
+ // 24 hours default
206
+ });
207
+ }
208
+ };
209
+ var requireAuth = () => {
210
+ return async (c, next) => {
211
+ try {
212
+ let token = c.req.header("Authorization")?.replace("Bearer ", "");
213
+ if (!token) {
214
+ token = cookie.getCookie(c, "auth_token");
215
+ }
216
+ if (!token) {
217
+ const acceptHeader = c.req.header("Accept") || "";
218
+ if (acceptHeader.includes("text/html")) {
219
+ return c.redirect("/auth/login?error=Please login to access the admin area");
220
+ }
221
+ return c.json({ error: "Authentication required" }, 401);
222
+ }
223
+ const kv = c.env?.KV;
224
+ let payload = null;
225
+ if (kv) {
226
+ const cacheKey = `auth:${token.substring(0, 20)}`;
227
+ const cached = await kv.get(cacheKey, "json");
228
+ if (cached) {
229
+ payload = cached;
230
+ }
231
+ }
232
+ if (!payload) {
233
+ const jwtSecret = c.env?.JWT_SECRET;
234
+ payload = await AuthManager.verifyToken(token, jwtSecret);
235
+ if (payload && kv) {
236
+ const cacheKey = `auth:${token.substring(0, 20)}`;
237
+ await kv.put(cacheKey, JSON.stringify(payload), { expirationTtl: 300 });
238
+ }
239
+ }
240
+ if (!payload) {
241
+ const acceptHeader = c.req.header("Accept") || "";
242
+ if (acceptHeader.includes("text/html")) {
243
+ return c.redirect("/auth/login?error=Your session has expired, please login again");
244
+ }
245
+ return c.json({ error: "Invalid or expired token" }, 401);
246
+ }
247
+ c.set("user", payload);
248
+ return await next();
249
+ } catch (error) {
250
+ console.error("Auth middleware error:", error);
251
+ const acceptHeader = c.req.header("Accept") || "";
252
+ if (acceptHeader.includes("text/html")) {
253
+ return c.redirect("/auth/login?error=Authentication failed, please login again");
254
+ }
255
+ return c.json({ error: "Authentication failed" }, 401);
256
+ }
257
+ };
258
+ };
259
+ var requireRole = (requiredRole) => {
260
+ return async (c, next) => {
261
+ const user = c.get("user");
262
+ if (!user) {
263
+ const acceptHeader = c.req.header("Accept") || "";
264
+ if (acceptHeader.includes("text/html")) {
265
+ return c.redirect("/auth/login?error=Please login to access the admin area");
266
+ }
267
+ return c.json({ error: "Authentication required" }, 401);
268
+ }
269
+ const roles = Array.isArray(requiredRole) ? requiredRole : [requiredRole];
270
+ if (!roles.includes(user.role)) {
271
+ const acceptHeader = c.req.header("Accept") || "";
272
+ if (acceptHeader.includes("text/html")) {
273
+ return c.redirect("/auth/login?error=You do not have permission to access this area");
274
+ }
275
+ return c.json({ error: "Insufficient permissions" }, 403);
276
+ }
277
+ return await next();
278
+ };
279
+ };
280
+ var optionalAuth = () => {
281
+ return async (c, next) => {
282
+ try {
283
+ let token = c.req.header("Authorization")?.replace("Bearer ", "");
284
+ if (!token) {
285
+ token = cookie.getCookie(c, "auth_token");
286
+ }
287
+ if (token) {
288
+ const jwtSecret = c.env?.JWT_SECRET;
289
+ const payload = await AuthManager.verifyToken(token, jwtSecret);
290
+ if (payload) {
291
+ c.set("user", payload);
292
+ }
293
+ }
294
+ return await next();
295
+ } catch (error) {
296
+ console.error("Optional auth error:", error);
297
+ return await next();
298
+ }
299
+ };
300
+ };
301
+
302
+ // src/middleware/metrics.ts
303
+ var metricsMiddleware = () => {
304
+ return async (c, next) => {
305
+ const path = new URL(c.req.url).pathname;
306
+ if (path !== "/admin/dashboard/api/metrics") {
307
+ chunkRCQ2HIQD_cjs.metricsTracker.recordRequest();
308
+ }
309
+ await next();
310
+ };
311
+ };
312
+ var JWT_SECRET_FALLBACK2 = "your-super-secret-jwt-key-change-in-production";
313
+ function arrayBufferToBase64Url(buffer) {
314
+ const bytes = new Uint8Array(buffer);
315
+ let binary = "";
316
+ for (let i = 0; i < bytes.length; i++) {
317
+ binary += String.fromCharCode(bytes[i]);
318
+ }
319
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
320
+ }
321
+ async function getHmacKey(secret) {
322
+ const encoder = new TextEncoder();
323
+ return crypto.subtle.importKey(
324
+ "raw",
325
+ encoder.encode(secret),
326
+ { name: "HMAC", hash: "SHA-256" },
327
+ false,
328
+ ["sign", "verify"]
329
+ );
330
+ }
331
+ async function generateCsrfToken(secret) {
332
+ const nonceBytes = new Uint8Array(32);
333
+ crypto.getRandomValues(nonceBytes);
334
+ const nonce = arrayBufferToBase64Url(nonceBytes.buffer);
335
+ const key = await getHmacKey(secret);
336
+ const encoder = new TextEncoder();
337
+ const signatureBuffer = await crypto.subtle.sign("HMAC", key, encoder.encode(nonce));
338
+ const signature = arrayBufferToBase64Url(signatureBuffer);
339
+ return `${nonce}.${signature}`;
340
+ }
341
+ async function validateCsrfToken(token, secret) {
342
+ if (!token || typeof token !== "string") return false;
343
+ const dotIndex = token.indexOf(".");
344
+ if (dotIndex === -1) return false;
345
+ const nonce = token.substring(0, dotIndex);
346
+ const signature = token.substring(dotIndex + 1);
347
+ if (!nonce || !signature) return false;
348
+ try {
349
+ const key = await getHmacKey(secret);
350
+ const encoder = new TextEncoder();
351
+ const sigPadded = signature.replace(/-/g, "+").replace(/_/g, "/");
352
+ const sigBinary = atob(sigPadded);
353
+ const sigBytes = new Uint8Array(sigBinary.length);
354
+ for (let i = 0; i < sigBinary.length; i++) {
355
+ sigBytes[i] = sigBinary.charCodeAt(i);
356
+ }
357
+ return await crypto.subtle.verify("HMAC", key, sigBytes.buffer, encoder.encode(nonce));
358
+ } catch {
359
+ return false;
360
+ }
361
+ }
362
+ var DEFAULT_EXEMPT_PATHS = [
363
+ "/auth/login",
364
+ "/auth/register",
365
+ "/auth/seed-admin",
366
+ "/auth/accept-invitation",
367
+ "/auth/reset-password",
368
+ "/auth/request-password-reset"
369
+ ];
370
+ function isExemptPath(path, extraExemptPaths = []) {
371
+ if (path.startsWith("/forms/") || path.startsWith("/api/forms/") || path === "/forms" || path === "/api/forms") {
372
+ return true;
373
+ }
374
+ if (path.startsWith("/api/search")) {
375
+ return true;
376
+ }
377
+ const allExempt = [...DEFAULT_EXEMPT_PATHS, ...extraExemptPaths];
378
+ for (const exempt of allExempt) {
379
+ if (path === exempt || path.startsWith(exempt + "/")) {
380
+ return true;
381
+ }
382
+ }
383
+ return false;
384
+ }
385
+ function csrfProtection(options = {}) {
386
+ return async (c, next) => {
387
+ const method = c.req.method.toUpperCase();
388
+ const path = new URL(c.req.url).pathname;
389
+ const secret = c.env?.JWT_SECRET || JWT_SECRET_FALLBACK2;
390
+ if (c.env?.ENVIRONMENT === "production" && !c.env?.JWT_SECRET) {
391
+ console.warn(
392
+ "[CSRF] WARNING: JWT_SECRET is not set in production. CSRF tokens are signed with the fallback key, which is insecure."
393
+ );
394
+ }
395
+ if (method === "GET" || method === "HEAD" || method === "OPTIONS") {
396
+ await ensureCsrfCookie(c, secret);
397
+ await next();
398
+ return;
399
+ }
400
+ if (isExemptPath(path, options.exemptPaths)) {
401
+ await next();
402
+ return;
403
+ }
404
+ const authCookie = cookie.getCookie(c, "auth_token");
405
+ if (!authCookie) {
406
+ await next();
407
+ return;
408
+ }
409
+ const cookieToken = cookie.getCookie(c, "csrf_token");
410
+ let headerToken = c.req.header("X-CSRF-Token");
411
+ if (!headerToken) {
412
+ const contentType = c.req.header("Content-Type") || "";
413
+ if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
414
+ try {
415
+ const body = await c.req.parseBody();
416
+ headerToken = body["_csrf"];
417
+ } catch {
418
+ }
419
+ }
420
+ }
421
+ if (!cookieToken || !headerToken) {
422
+ return csrfError(c, "CSRF token missing");
423
+ }
424
+ if (cookieToken !== headerToken) {
425
+ return csrfError(c, "CSRF token mismatch");
426
+ }
427
+ const isValid = await validateCsrfToken(cookieToken, secret);
428
+ if (!isValid) {
429
+ return csrfError(c, "CSRF token invalid");
430
+ }
431
+ await next();
432
+ };
433
+ }
434
+ async function ensureCsrfCookie(c, secret) {
435
+ const existing = cookie.getCookie(c, "csrf_token");
436
+ if (existing) {
437
+ const isValid = await validateCsrfToken(existing, secret);
438
+ if (isValid) {
439
+ c.set("csrfToken", existing);
440
+ return;
441
+ }
442
+ }
443
+ const token = await generateCsrfToken(secret);
444
+ c.set("csrfToken", token);
445
+ const isDev = c.env?.ENVIRONMENT === "development" || !c.env?.ENVIRONMENT;
446
+ cookie.setCookie(c, "csrf_token", token, {
447
+ httpOnly: false,
448
+ // JS must read this cookie
449
+ secure: !isDev,
450
+ sameSite: "Strict",
451
+ path: "/",
452
+ maxAge: 86400
453
+ // 24 hours — browser-side expiry
454
+ });
455
+ }
456
+ function csrfError(c, message) {
457
+ const accept = c.req.header("Accept") || "";
458
+ if (accept.includes("text/html")) {
459
+ return c.html(
460
+ `<!DOCTYPE html><html><head><title>403 Forbidden</title></head><body><h1>403 Forbidden</h1><p>${message}</p></body></html>`,
461
+ 403
462
+ );
463
+ }
464
+ return c.json({ error: message, status: 403 }, 403);
465
+ }
466
+
467
+ // src/middleware/rate-limit.ts
468
+ function rateLimit(options) {
469
+ const { max, windowMs, keyPrefix } = options;
470
+ return async (c, next) => {
471
+ const kv = c.env?.CACHE_KV;
472
+ if (!kv) {
473
+ return await next();
474
+ }
475
+ const ip = c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for") || "unknown";
476
+ const key = `ratelimit:${keyPrefix}:${ip}`;
477
+ try {
478
+ const now = Date.now();
479
+ const stored = await kv.get(key, "json");
480
+ let entry;
481
+ if (stored && stored.resetAt > now) {
482
+ entry = stored;
483
+ } else {
484
+ entry = { count: 0, resetAt: now + windowMs };
485
+ }
486
+ entry.count++;
487
+ const ttlSeconds = Math.ceil((entry.resetAt - now) / 1e3);
488
+ if (entry.count > max) {
489
+ await kv.put(key, JSON.stringify(entry), { expirationTtl: Math.max(ttlSeconds, 1) });
490
+ const retryAfter = Math.ceil((entry.resetAt - now) / 1e3);
491
+ c.header("Retry-After", String(retryAfter));
492
+ c.header("X-RateLimit-Limit", String(max));
493
+ c.header("X-RateLimit-Remaining", "0");
494
+ c.header("X-RateLimit-Reset", String(Math.ceil(entry.resetAt / 1e3)));
495
+ return c.json({ error: "Too many requests. Please try again later." }, 429);
496
+ }
497
+ await kv.put(key, JSON.stringify(entry), { expirationTtl: Math.max(ttlSeconds, 1) });
498
+ c.header("X-RateLimit-Limit", String(max));
499
+ c.header("X-RateLimit-Remaining", String(max - entry.count));
500
+ c.header("X-RateLimit-Reset", String(Math.ceil(entry.resetAt / 1e3)));
501
+ return await next();
502
+ } catch (error) {
503
+ console.error("Rate limiter error (non-fatal):", error);
504
+ return await next();
505
+ }
506
+ };
507
+ }
508
+
509
+ // src/middleware/security-headers.ts
510
+ var securityHeadersMiddleware = () => {
511
+ return async (c, next) => {
512
+ await next();
513
+ c.header("X-Content-Type-Options", "nosniff");
514
+ c.header("X-Frame-Options", "SAMEORIGIN");
515
+ c.header("Referrer-Policy", "strict-origin-when-cross-origin");
516
+ c.header("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
517
+ const environment = c.env?.ENVIRONMENT;
518
+ if (environment !== "development") {
519
+ c.header("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
520
+ }
521
+ };
522
+ };
523
+
524
+ // src/middleware/index.ts
525
+ var loggingMiddleware = () => async (_c, next) => await next();
526
+ var detailedLoggingMiddleware = () => async (_c, next) => await next();
527
+ var securityLoggingMiddleware = () => async (_c, next) => await next();
528
+ var performanceLoggingMiddleware = () => async (_c, next) => await next();
529
+ var cacheHeaders = () => async (_c, next) => await next();
530
+ var compressionMiddleware = async (_c, next) => await next();
531
+ var PermissionManager = {};
532
+ var requirePermission = () => async (_c, next) => await next();
533
+ var requireAnyPermission = () => async (_c, next) => await next();
534
+ var logActivity = () => {
535
+ };
536
+ var requireActivePlugin = () => async (_c, next) => await next();
537
+ var requireActivePlugins = () => async (_c, next) => await next();
538
+ var getActivePlugins = () => [];
539
+ var isPluginActive = () => false;
540
+
541
+ exports.AuthManager = AuthManager;
542
+ exports.PermissionManager = PermissionManager;
543
+ exports.bootstrapMiddleware = bootstrapMiddleware;
544
+ exports.cacheHeaders = cacheHeaders;
545
+ exports.compressionMiddleware = compressionMiddleware;
546
+ exports.csrfProtection = csrfProtection;
547
+ exports.detailedLoggingMiddleware = detailedLoggingMiddleware;
548
+ exports.generateCsrfToken = generateCsrfToken;
549
+ exports.getActivePlugins = getActivePlugins;
550
+ exports.isPluginActive = isPluginActive;
551
+ exports.logActivity = logActivity;
552
+ exports.loggingMiddleware = loggingMiddleware;
553
+ exports.metricsMiddleware = metricsMiddleware;
554
+ exports.optionalAuth = optionalAuth;
555
+ exports.performanceLoggingMiddleware = performanceLoggingMiddleware;
556
+ exports.rateLimit = rateLimit;
557
+ exports.requireActivePlugin = requireActivePlugin;
558
+ exports.requireActivePlugins = requireActivePlugins;
559
+ exports.requireAnyPermission = requireAnyPermission;
560
+ exports.requireAuth = requireAuth;
561
+ exports.requirePermission = requirePermission;
562
+ exports.requireRole = requireRole;
563
+ exports.securityHeadersMiddleware = securityHeadersMiddleware;
564
+ exports.securityLoggingMiddleware = securityLoggingMiddleware;
565
+ exports.validateCsrfToken = validateCsrfToken;
566
+ exports.verifySecurityConfig = verifySecurityConfig;
567
+ //# sourceMappingURL=chunk-K4Q4SFJJ.cjs.map
568
+ //# sourceMappingURL=chunk-K4Q4SFJJ.cjs.map