shokupan 0.15.0 → 0.15.2

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 (29) hide show
  1. package/dist/{index-46Z4ASUY.cjs → index-B-bQ7kZy.cjs} +232 -79
  2. package/dist/index-B-bQ7kZy.cjs.map +1 -0
  3. package/dist/{index-MDmdOQNV.js → index-BsMp6pBd.js} +229 -76
  4. package/dist/index-BsMp6pBd.js.map +1 -0
  5. package/dist/index.cjs +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/{knex-BbK40SH3.js → knex-CnHqFsjZ.js} +2 -2
  8. package/dist/{knex-BbK40SH3.js.map → knex-CnHqFsjZ.js.map} +1 -1
  9. package/dist/{knex-CIS1IT0Y.cjs → knex-DYPtgZG5.cjs} +2 -2
  10. package/dist/{knex-CIS1IT0Y.cjs.map → knex-DYPtgZG5.cjs.map} +1 -1
  11. package/dist/{level-Dhm9CiBU.js → level-BH1qmHgY.js} +2 -2
  12. package/dist/{level-Dhm9CiBU.js.map → level-BH1qmHgY.js.map} +1 -1
  13. package/dist/{level-B0zBSwWA.cjs → level-C8w1C6JX.cjs} +2 -2
  14. package/dist/{level-B0zBSwWA.cjs.map → level-C8w1C6JX.cjs.map} +1 -1
  15. package/dist/plugins/application/api-explorer/plugin.d.ts +9 -0
  16. package/dist/plugins/middleware/cors.d.ts +11 -0
  17. package/dist/{sqlite-sfDxgiji.cjs → sqlite-Bf3Y44PF.cjs} +2 -2
  18. package/dist/{sqlite-sfDxgiji.cjs.map → sqlite-Bf3Y44PF.cjs.map} +1 -1
  19. package/dist/{sqlite-CY-WyaJ5.js → sqlite-C2dVWOnw.js} +2 -2
  20. package/dist/{sqlite-CY-WyaJ5.js.map → sqlite-C2dVWOnw.js.map} +1 -1
  21. package/dist/{surreal-D1txBCZb.js → surreal-C6kHsjO6.js} +2 -2
  22. package/dist/{surreal-D1txBCZb.js.map → surreal-C6kHsjO6.js.map} +1 -1
  23. package/dist/{surreal-DiEl_VJL.cjs → surreal-Ctjsrb6S.cjs} +2 -2
  24. package/dist/{surreal-DiEl_VJL.cjs.map → surreal-Ctjsrb6S.cjs.map} +1 -1
  25. package/dist/util/body-parser.d.ts +7 -0
  26. package/dist/util/env-loader.d.ts +2 -0
  27. package/package.json +1 -1
  28. package/dist/index-46Z4ASUY.cjs.map +0 -1
  29. package/dist/index-MDmdOQNV.js.map +0 -1
@@ -116,26 +116,63 @@ class BodyParser {
116
116
  }
117
117
  /**
118
118
  * Parsing helper for FormData
119
+ * Security: Enforces maxBodySize by reading the raw body stream before
120
+ * handing it to formData(), so the limit cannot be bypassed via a spoofed
121
+ * Content-Length header.
119
122
  */
120
123
  static async parseFormData(req, maxBodySize) {
121
- const clHeader = req.headers.get("content-length");
122
- if (!clHeader) {
124
+ if (!req.headers.has("content-length") && req.headers.get("transfer-encoding") !== "chunked") {
123
125
  const err = new Error("Length Required");
124
126
  err.status = 411;
125
127
  throw err;
126
128
  }
127
- const cl = parseInt(clHeader, 10);
128
- if (isNaN(cl)) {
129
- const err = new Error("Bad Request");
130
- err.status = 400;
131
- throw err;
129
+ const rawBuffer = await BodyParser.readRawBufferBody(req, maxBodySize);
130
+ const syntheticReq = new Request("http://localhost", {
131
+ method: "POST",
132
+ headers: req.headers,
133
+ body: rawBuffer.buffer
134
+ });
135
+ return syntheticReq.formData();
136
+ }
137
+ /**
138
+ * Reads raw body as Uint8Array with size enforcement (used by parseFormData).
139
+ */
140
+ static async readRawBufferBody(req, maxBodySize) {
141
+ if (typeof req.body === "string") {
142
+ const enc = new TextEncoder().encode(req.body);
143
+ if (enc.byteLength > maxBodySize) {
144
+ const err = new Error("Payload Too Large");
145
+ err.status = 413;
146
+ throw err;
147
+ }
148
+ return enc;
132
149
  }
133
- if (cl > maxBodySize) {
134
- const err = new Error("Payload Too Large");
135
- err.status = 413;
136
- throw err;
150
+ const reader = req.body?.getReader();
151
+ if (!reader) return new Uint8Array(0);
152
+ const chunks = [];
153
+ let totalSize = 0;
154
+ try {
155
+ while (true) {
156
+ const { done, value } = await reader.read();
157
+ if (done) break;
158
+ totalSize += value.length;
159
+ if (totalSize > maxBodySize) {
160
+ const err = new Error("Payload Too Large");
161
+ err.status = 413;
162
+ throw err;
163
+ }
164
+ chunks.push(value);
165
+ }
166
+ } finally {
167
+ reader.releaseLock();
137
168
  }
138
- return req.formData();
169
+ const result = new Uint8Array(totalSize);
170
+ let offset = 0;
171
+ for (const chunk of chunks) {
172
+ result.set(chunk, offset);
173
+ offset += chunk.length;
174
+ }
175
+ return result;
139
176
  }
140
177
  /**
141
178
  * Reads raw body as string with size enforcement
@@ -1383,7 +1420,7 @@ class ShokupanContext {
1383
1420
  }
1384
1421
  function RateLimitMiddleware(options = {}) {
1385
1422
  const windowMs = options.windowMs || 60 * 1e3;
1386
- const max = options.limit || options.max || 5;
1423
+ const max = options.limit || options.max || 100;
1387
1424
  const message = options.message || "Too many requests, please try again later.";
1388
1425
  const statusCode = options.statusCode || 429;
1389
1426
  const headers = options.headers !== false;
@@ -1412,9 +1449,7 @@ function RateLimitMiddleware(options = {}) {
1412
1449
  const hits = /* @__PURE__ */ new Map();
1413
1450
  const interval = setInterval(() => {
1414
1451
  const now = Date.now();
1415
- const entries = Array.from(hits.entries());
1416
- for (let i = 0; i < entries.length; i++) {
1417
- const [key, record] = entries[i];
1452
+ for (const [key, record] of hits) {
1418
1453
  if (record.resetTime <= now) {
1419
1454
  hits.delete(key);
1420
1455
  }
@@ -2797,7 +2832,12 @@ function serveStatic(config, prefix) {
2797
2832
  const normalizedPrefix = prefix.endsWith("/") && prefix !== "/" ? prefix.slice(0, -1) : prefix;
2798
2833
  const isEtag = !!config.etag;
2799
2834
  const extensions = config.extensions || ["html", "htm", "htmx"];
2835
+ const cacheEnabled = config.useCache !== false;
2800
2836
  const FILES = {};
2837
+ let _resolveReady;
2838
+ const _ready = new Promise((resolve2) => {
2839
+ _resolveReady = resolve2;
2840
+ });
2801
2841
  function toHeaders(name, stats, isEtag2) {
2802
2842
  const ctype = mrmime.lookup(name) || "application/octet-stream";
2803
2843
  const headers = {
@@ -2813,7 +2853,7 @@ function serveStatic(config, prefix) {
2813
2853
  }
2814
2854
  return headers;
2815
2855
  }
2816
- if (!config.useCache) {
2856
+ if (cacheEnabled) {
2817
2857
  async function walk(dir) {
2818
2858
  const entries = await promises$1.readdir(dir, { withFileTypes: true });
2819
2859
  for (const entry of entries) {
@@ -2828,10 +2868,15 @@ function serveStatic(config, prefix) {
2828
2868
  }
2829
2869
  }
2830
2870
  }
2831
- walk(rootPath).catch(console.error);
2871
+ walk(rootPath).then(_resolveReady).catch((err) => {
2872
+ console.error("[serveStatic] Cache population error:", err);
2873
+ _resolveReady();
2874
+ });
2875
+ } else {
2876
+ _resolveReady();
2832
2877
  }
2833
2878
  const serveStaticMiddleware = async (ctx) => {
2834
- let reqPath = ctx.path.slice(normalizedPrefix.length);
2879
+ let reqPath = ctx.params?.["*"] ?? ctx.path.slice(normalizedPrefix.length);
2835
2880
  if (!reqPath.startsWith("/")) reqPath = "/" + reqPath;
2836
2881
  try {
2837
2882
  reqPath = decodeURIComponent(reqPath);
@@ -2842,7 +2887,7 @@ function serveStatic(config, prefix) {
2842
2887
  return ctx.json({ error: "Forbidden" }, 403);
2843
2888
  }
2844
2889
  let file;
2845
- if (!config.useCache) {
2890
+ if (cacheEnabled) {
2846
2891
  file = FILES[reqPath];
2847
2892
  if (!file) {
2848
2893
  for (const ext of extensions) {
@@ -2919,7 +2964,7 @@ function serveStatic(config, prefix) {
2919
2964
  <li><a href="../">../</a></li>
2920
2965
  <% } %>
2921
2966
  <% it.files.forEach(function(f) { %>
2922
- <li><a href="<%= f %>"><%= f %></a></li>
2967
+ <li><a href="<%= encodeURIComponent(f) %>"><%= f %></a></li>
2923
2968
  <% }) %>
2924
2969
  </ul>
2925
2970
  </body>
@@ -2977,6 +3022,7 @@ function serveStatic(config, prefix) {
2977
3022
  };
2978
3023
  serveStaticMiddleware.isBuiltin = true;
2979
3024
  serveStaticMiddleware.pluginName = "ServeStatic";
3025
+ serveStaticMiddleware.ready = _ready;
2980
3026
  return serveStaticMiddleware;
2981
3027
  }
2982
3028
  class OpenTelemetryPlugin {
@@ -3691,14 +3737,21 @@ class RouterTrie {
3691
3737
  delete params[node.paramChild.paramName];
3692
3738
  }
3693
3739
  if (node.wildcardChild) {
3740
+ params["*"] = segment;
3694
3741
  const result = this.findNode(node.wildcardChild, segments, index + 1, params);
3695
3742
  if (result) return result;
3743
+ delete params["*"];
3696
3744
  }
3697
3745
  if (node.recursiveChild) {
3698
3746
  const remaining = segments.length - index;
3699
3747
  for (let k = 0; k <= remaining; k++) {
3748
+ const matchedSegments = segments.slice(index, index + k).join("/");
3749
+ params["*"] = matchedSegments;
3750
+ params["**"] = matchedSegments;
3700
3751
  const result = this.findNode(node.recursiveChild, segments, index + k, params);
3701
3752
  if (result) return result;
3753
+ delete params["*"];
3754
+ delete params["**"];
3702
3755
  }
3703
3756
  }
3704
3757
  return null;
@@ -4173,13 +4226,13 @@ class ShokupanRouter {
4173
4226
  }
4174
4227
  return ctx.upgrade({
4175
4228
  open: async (ctx2, ws) => {
4229
+ ctx2[$ws] = ws;
4176
4230
  if (handlers.onOpen) {
4177
4231
  const sessionData = await handlers.onOpen(ctx2, ws);
4178
4232
  if (sessionData !== void 0) {
4179
4233
  ws.data = sessionData;
4180
4234
  ctx2.state = sessionData;
4181
4235
  }
4182
- ctx2[$ws] = ws;
4183
4236
  }
4184
4237
  if (!ctx2[$wsMessages]) ctx2[$wsMessages] = [];
4185
4238
  const originalSend = ws.send.bind(ws);
@@ -4287,6 +4340,7 @@ class ShokupanRouter {
4287
4340
  }
4288
4341
  return ctx.upgrade({
4289
4342
  open: async (ctx2, ws) => {
4343
+ ctx2[$ws] = ws;
4290
4344
  if (openMethodName) {
4291
4345
  const openMethod = instance[openMethodName];
4292
4346
  if (openMethod) {
@@ -4615,13 +4669,17 @@ class ShokupanRouter {
4615
4669
  find(method, path2) {
4616
4670
  let result = this.trie.search(method, path2);
4617
4671
  if (result) {
4618
- result.handler = this.wrapHandlerWithMiddleware(result.handler);
4672
+ if (!this[$isApplication]) {
4673
+ result.handler = this.wrapHandlerWithMiddleware(result.handler);
4674
+ }
4619
4675
  return result;
4620
4676
  }
4621
4677
  if (method === "HEAD") {
4622
4678
  result = this.trie.search("GET", path2);
4623
4679
  if (result) {
4624
- result.handler = this.wrapHandlerWithMiddleware(result.handler);
4680
+ if (!this[$isApplication]) {
4681
+ result.handler = this.wrapHandlerWithMiddleware(result.handler);
4682
+ }
4625
4683
  return result;
4626
4684
  }
4627
4685
  }
@@ -5920,29 +5978,29 @@ class Shokupan extends ShokupanRouter {
5920
5978
  try {
5921
5979
  switch (adapterName) {
5922
5980
  case "sqlite": {
5923
- const { SqliteAdapter } = await Promise.resolve().then(() => require("./sqlite-sfDxgiji.cjs"));
5981
+ const { SqliteAdapter } = await Promise.resolve().then(() => require("./sqlite-Bf3Y44PF.cjs"));
5924
5982
  this.datastore = new SqliteAdapter(options);
5925
5983
  break;
5926
5984
  }
5927
5985
  case "level": {
5928
- const { LevelAdapter } = await Promise.resolve().then(() => require("./level-B0zBSwWA.cjs"));
5986
+ const { LevelAdapter } = await Promise.resolve().then(() => require("./level-C8w1C6JX.cjs"));
5929
5987
  this.datastore = new LevelAdapter(options);
5930
5988
  break;
5931
5989
  }
5932
5990
  case "surrealdb": {
5933
- const { SurrealAdapter } = await Promise.resolve().then(() => require("./surreal-DiEl_VJL.cjs"));
5991
+ const { SurrealAdapter } = await Promise.resolve().then(() => require("./surreal-Ctjsrb6S.cjs"));
5934
5992
  const legacyConfig = this.applicationConfig.surreal || {};
5935
5993
  const effectiveOptions = { ...legacyConfig, ...options };
5936
5994
  this.datastore = new SurrealAdapter(effectiveOptions);
5937
5995
  break;
5938
5996
  }
5939
5997
  case "knex": {
5940
- const { KnexAdapter } = await Promise.resolve().then(() => require("./knex-CIS1IT0Y.cjs"));
5998
+ const { KnexAdapter } = await Promise.resolve().then(() => require("./knex-DYPtgZG5.cjs"));
5941
5999
  this.datastore = new KnexAdapter(options || {});
5942
6000
  break;
5943
6001
  }
5944
6002
  default: {
5945
- const { SurrealAdapter } = await Promise.resolve().then(() => require("./surreal-DiEl_VJL.cjs"));
6003
+ const { SurrealAdapter } = await Promise.resolve().then(() => require("./surreal-Ctjsrb6S.cjs"));
5946
6004
  const legacy = this.applicationConfig.surreal;
5947
6005
  this.datastore = new SurrealAdapter(options || legacy || {});
5948
6006
  }
@@ -6897,7 +6955,7 @@ class ApiExplorerPlugin extends ShokupanRouter {
6897
6955
  }
6898
6956
  }
6899
6957
  static getBasePath() {
6900
- const dir = path$1.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index-46Z4ASUY.cjs", document.baseURI).href));
6958
+ const dir = path$1.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index-B-bQ7kZy.cjs", document.baseURI).href));
6901
6959
  if (dir.endsWith("dist")) {
6902
6960
  return dir + "/plugins/application/api-explorer";
6903
6961
  }
@@ -6926,22 +6984,26 @@ class ApiExplorerPlugin extends ShokupanRouter {
6926
6984
  this.get("/style.css", (ctx) => serveFile(ctx, "style.css", "text/css"));
6927
6985
  this.get("/theme.css", (ctx) => serveFile(ctx, "theme.css", "text/css"));
6928
6986
  this.get("/explorer-client.mjs", (ctx) => serveFile(ctx, "explorer-client.mjs", "application/javascript"));
6929
- this.get("/_source", async (ctx) => {
6930
- const file = ctx.query["file"];
6931
- if (!file) return ctx.text("Missing file parameter", 400);
6932
- const { resolve, normalize, isAbsolute } = await import("node:path");
6933
- const cwd = process.cwd();
6934
- const resolvedPath = resolve(cwd, file);
6935
- if (!resolvedPath.startsWith(cwd)) {
6936
- return ctx.text("Forbidden: File must be within project root", 403);
6937
- }
6938
- try {
6939
- const content = await promises$1.readFile(resolvedPath, "utf-8");
6940
- return ctx.text(content);
6941
- } catch (err) {
6942
- return ctx.text("File not found", 404);
6943
- }
6944
- });
6987
+ const isProduction = process.env.NODE_ENV === "production";
6988
+ const sourceViewEnabled = this.pluginOptions.enableSourceView ?? !isProduction;
6989
+ if (sourceViewEnabled) {
6990
+ this.get("/_source", async (ctx) => {
6991
+ const file = ctx.query["file"];
6992
+ if (!file) return ctx.text("Missing file parameter", 400);
6993
+ const { resolve } = await import("node:path");
6994
+ const cwd = process.cwd();
6995
+ const resolvedPath = resolve(cwd, file);
6996
+ if (!resolvedPath.startsWith(cwd + "/") && resolvedPath !== cwd) {
6997
+ return ctx.text("Forbidden: File must be within project root", 403);
6998
+ }
6999
+ try {
7000
+ const content = await promises$1.readFile(resolvedPath, "utf-8");
7001
+ return ctx.text(content);
7002
+ } catch (err) {
7003
+ return ctx.text("File not found", 404);
7004
+ }
7005
+ });
7006
+ }
6945
7007
  this.get("/openapi.json", async (ctx) => {
6946
7008
  const spec = this.root.openApiSpec ? structuredClone(this.root.openApiSpec) : await (this.root || this).generateApiSpec();
6947
7009
  return ctx.json(stripSourceCode(spec));
@@ -7566,7 +7628,7 @@ class AsyncApiPlugin extends ShokupanRouter {
7566
7628
  this.init();
7567
7629
  }
7568
7630
  static getBasePath() {
7569
- const dir = path$1.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index-46Z4ASUY.cjs", document.baseURI).href));
7631
+ const dir = path$1.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index-B-bQ7kZy.cjs", document.baseURI).href));
7570
7632
  if (dir.endsWith("dist")) {
7571
7633
  return dir + "/plugins/application/asyncapi";
7572
7634
  }
@@ -7822,7 +7884,16 @@ class AuthPlugin extends ShokupanRouter {
7822
7884
  };
7823
7885
  } else if (provider === "apple") {
7824
7886
  if (idToken) {
7825
- const payload = this.jose.decodeJwt(idToken);
7887
+ const { createRemoteJWKSet, jwtVerify } = this.jose;
7888
+ if (!this._appleJwks) {
7889
+ this._appleJwks = createRemoteJWKSet(
7890
+ new URL("https://appleid.apple.com/auth/keys")
7891
+ );
7892
+ }
7893
+ const { payload } = await jwtVerify(idToken, this._appleJwks, {
7894
+ issuer: "https://appleid.apple.com",
7895
+ audience: this.authConfig.providers.apple?.clientId
7896
+ });
7826
7897
  user = {
7827
7898
  id: payload.sub,
7828
7899
  email: payload["email"],
@@ -8199,7 +8270,7 @@ function Card({ title, contentId }) {
8199
8270
  /* @__PURE__ */ jsxRuntime.jsx("div", { id: contentId })
8200
8271
  ] });
8201
8272
  }
8202
- const require$1 = node_module.createRequire(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index-46Z4ASUY.cjs", document.baseURI).href);
8273
+ const require$1 = node_module.createRequire(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index-B-bQ7kZy.cjs", document.baseURI).href);
8203
8274
  const http = require$1("node:http");
8204
8275
  const https = require$1("node:https");
8205
8276
  class FetchInterceptor {
@@ -8803,7 +8874,7 @@ class Dashboard {
8803
8874
  }
8804
8875
  // Get base path for dashboard files - works in both dev (src/) and production (dist/)
8805
8876
  static getBasePath() {
8806
- const dir = path$1.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index-46Z4ASUY.cjs", document.baseURI).href));
8877
+ const dir = path$1.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index-B-bQ7kZy.cjs", document.baseURI).href));
8807
8878
  if (dir.endsWith("dist")) {
8808
8879
  return dir + "/plugins/application/dashboard";
8809
8880
  }
@@ -11212,14 +11283,29 @@ function Compression(options = {}) {
11212
11283
  }
11213
11284
  const acceptEncoding = ctx.headers.get("accept-encoding") || "";
11214
11285
  let method = null;
11215
- if (acceptEncoding.includes("br")) method = "br";
11216
- else if (acceptEncoding.includes("zstd")) {
11217
- if (typeof Bun === "undefined") {
11218
- throw new Error("zstd compression is only available in Bun runtime. Client requested zstd but server is running on Node.js.");
11219
- }
11220
- method = "zstd";
11221
- } else if (acceptEncoding.includes("gzip")) method = "gzip";
11222
- else if (acceptEncoding.includes("deflate")) method = "deflate";
11286
+ const encodings = acceptEncoding.split(",");
11287
+ for (let i = 0; i < encodings.length; i++) {
11288
+ const enc = encodings[i].split(";")[0].trim().toLowerCase();
11289
+ if (enc === "br" && allowedAlgorithms.has("br")) {
11290
+ method = "br";
11291
+ break;
11292
+ }
11293
+ if (enc === "zstd" && allowedAlgorithms.has("zstd")) {
11294
+ if (typeof Bun !== "undefined") {
11295
+ method = "zstd";
11296
+ break;
11297
+ }
11298
+ continue;
11299
+ }
11300
+ if (enc === "gzip" && allowedAlgorithms.has("gzip")) {
11301
+ method = "gzip";
11302
+ break;
11303
+ }
11304
+ if (enc === "deflate" && allowedAlgorithms.has("deflate")) {
11305
+ method = "deflate";
11306
+ break;
11307
+ }
11308
+ }
11223
11309
  if (!method) return next();
11224
11310
  if (!allowedAlgorithms.has(method)) {
11225
11311
  return next();
@@ -11306,6 +11392,11 @@ function Cors(options = {}) {
11306
11392
  optionsSuccessStatus: 204
11307
11393
  };
11308
11394
  const opts = { ...defaults2, ...options };
11395
+ if (opts.credentials && opts.origin === "*") {
11396
+ throw new Error(
11397
+ 'CORS misconfiguration: `credentials: true` is incompatible with `origin: "*"`. Specify an explicit origin or array of origins instead.'
11398
+ );
11399
+ }
11309
11400
  const corsMiddleware = async function CorsMiddleware(ctx, next) {
11310
11401
  const headers = {};
11311
11402
  const origin = ctx.headers.get("origin");
@@ -11591,10 +11682,21 @@ function validate(config) {
11591
11682
  let validQuery;
11592
11683
  if (validators.query && queryObj) {
11593
11684
  validQuery = await validators.query(queryObj);
11685
+ Object.defineProperty(ctx, "query", {
11686
+ value: validQuery,
11687
+ writable: true,
11688
+ configurable: true
11689
+ });
11594
11690
  }
11691
+ let validHeaders;
11595
11692
  if (validators.headers) {
11596
11693
  const headersObj = Object.fromEntries(ctx.req.headers.entries());
11597
- await validators.headers(headersObj);
11694
+ validHeaders = await validators.headers(headersObj);
11695
+ Object.defineProperty(ctx, "headers", {
11696
+ value: new Headers(validHeaders),
11697
+ writable: true,
11698
+ configurable: true
11699
+ });
11598
11700
  }
11599
11701
  let validBody;
11600
11702
  if (validators.body) {
@@ -11612,6 +11714,7 @@ function validate(config) {
11612
11714
  const validatedData = { ...dataToValidate };
11613
11715
  if (config.params) validatedData.params = ctx.params;
11614
11716
  if (config.query) validatedData.query = validQuery;
11717
+ if (config.headers) validatedData.headers = validHeaders;
11615
11718
  if (config.body) validatedData.body = validBody;
11616
11719
  await ctx.app.runHooks("afterValidate", ctx, validatedData);
11617
11720
  return next();
@@ -11852,6 +11955,9 @@ function Proxy$1(options) {
11852
11955
  if (!["http:", "https:"].includes(url.protocol)) {
11853
11956
  return ctx.text("Invalid protocol in proxied URL", 400);
11854
11957
  }
11958
+ if (options.allowedHosts && !options.allowedHosts.includes(url.hostname)) {
11959
+ return ctx.text("Proxied hostname not in allowlist", 403);
11960
+ }
11855
11961
  const headers = new Headers(req.headers);
11856
11962
  if (options.changeOrigin) {
11857
11963
  headers.set("host", targetUrl.host);
@@ -11945,7 +12051,16 @@ function handleWSDrain(ws) {
11945
12051
  }
11946
12052
  function SecurityHeaders(options = {}) {
11947
12053
  const securityHeadersMiddleware = async function SecurityHeadersMiddleware(ctx, next) {
11948
- const set = (k, v) => ctx.response.set(k, v);
12054
+ const response = await next();
12055
+ const set = (k, v) => {
12056
+ if (response instanceof Response) {
12057
+ try {
12058
+ response.headers.set(k, v);
12059
+ } catch (e) {
12060
+ }
12061
+ }
12062
+ ctx.response.headers.set(k, v);
12063
+ };
11949
12064
  if (options.dnsPrefetchControl !== false) {
11950
12065
  const allow = options.dnsPrefetchControl?.allow;
11951
12066
  set("X-DNS-Prefetch-Control", allow ? "on" : "off");
@@ -11956,7 +12071,7 @@ function SecurityHeaders(options = {}) {
11956
12071
  if (action === "sameorigin") set("X-Frame-Options", "SAMEORIGIN");
11957
12072
  else if (action === "deny") set("X-Frame-Options", "DENY");
11958
12073
  }
11959
- if (options.hsts !== false) {
12074
+ if (options.hsts !== false && ctx.secure === true) {
11960
12075
  const opt = options.hsts || {};
11961
12076
  const maxAge = opt.maxAge || 15552e3;
11962
12077
  let header = `max-age=${maxAge}`;
@@ -11983,20 +12098,39 @@ function SecurityHeaders(options = {}) {
11983
12098
  if (opt === void 0 || opt === true) {
11984
12099
  set("Content-Security-Policy", "default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests");
11985
12100
  } else if (typeof opt === "object") {
11986
- const optEntries = Object.entries(opt);
11987
- for (let i = 0; i < optEntries.length; i++) {
11988
- const [key, val] = optEntries[i];
12101
+ const parts = [];
12102
+ for (const [key, val] of Object.entries(opt)) {
12103
+ if (val === false) continue;
12104
+ const directive = key.replace(/([A-Z])/g, "-$1").toLowerCase();
12105
+ if (val === true) {
12106
+ parts.push(directive);
12107
+ } else {
12108
+ const sources = Array.isArray(val) ? val.join(" ") : String(val);
12109
+ parts.push(`${directive} ${sources}`);
12110
+ }
12111
+ }
12112
+ if (parts.length > 0) {
12113
+ set("Content-Security-Policy", parts.join(";"));
11989
12114
  }
11990
12115
  }
11991
12116
  }
11992
- if (options.hidePoweredBy !== false) ;
11993
- const response = await next();
12117
+ if (options.hidePoweredBy !== false) {
12118
+ if (response instanceof Response) {
12119
+ try {
12120
+ response.headers.delete("X-Powered-By");
12121
+ } catch (e) {
12122
+ }
12123
+ }
12124
+ ctx.response.headers.delete("X-Powered-By");
12125
+ }
11994
12126
  return response;
11995
12127
  };
11996
12128
  securityHeadersMiddleware.isBuiltin = true;
11997
12129
  securityHeadersMiddleware.pluginName = "SecurityHeaders";
11998
12130
  return securityHeadersMiddleware;
11999
12131
  }
12132
+ const $isDirty = /* @__PURE__ */ Symbol("isDirty");
12133
+ const $resetDirty = /* @__PURE__ */ Symbol("resetDirty");
12000
12134
  class Cookie {
12001
12135
  maxAge;
12002
12136
  signed;
@@ -12103,7 +12237,7 @@ function unsign(input, secret) {
12103
12237
  Buffer.from(expectedInput).copy(paddedExpected);
12104
12238
  Buffer.from(input).copy(paddedInput);
12105
12239
  try {
12106
- const valid = require("crypto").timingSafeEqual(paddedExpected, paddedInput);
12240
+ const valid = crypto.timingSafeEqual(paddedExpected, paddedInput);
12107
12241
  return valid ? tentValue : false;
12108
12242
  } catch {
12109
12243
  return false;
@@ -12122,10 +12256,14 @@ function Session(options) {
12122
12256
  const cookies = ctx.cookies;
12123
12257
  const rawCookie = cookies[name];
12124
12258
  if (rawCookie) {
12125
- if (rawCookie.substr(0, 2) === "s:") {
12126
- const val = unsign(rawCookie.slice(2), secrets[0]);
12127
- if (val) {
12128
- reqSessionId = val;
12259
+ if (rawCookie.slice(0, 2) === "s:") {
12260
+ const signed = rawCookie.slice(2);
12261
+ for (let i = 0; i < secrets.length; i++) {
12262
+ const val = unsign(signed, secrets[i]);
12263
+ if (val !== false) {
12264
+ reqSessionId = val;
12265
+ break;
12266
+ }
12129
12267
  }
12130
12268
  } else {
12131
12269
  reqSessionId = rawCookie;
@@ -12181,8 +12319,6 @@ function Session(options) {
12181
12319
  });
12182
12320
  });
12183
12321
  };
12184
- sessObj.undefined = () => {
12185
- };
12186
12322
  sessObj.reload = () => {
12187
12323
  return new Promise((resolve, reject) => {
12188
12324
  store.get(sessObj.id, (err, sess2) => {
@@ -12204,7 +12340,25 @@ function Session(options) {
12204
12340
  sessObj.cookie.expires = new Date(Date.now() + (sessObj.cookie.maxAge || 0));
12205
12341
  if (store.touch) store.touch(sessObj.id, sessObj);
12206
12342
  };
12207
- return sessObj;
12343
+ let _dirty = false;
12344
+ const skippedKeys = /* @__PURE__ */ new Set(["id", "save", "destroy", "regenerate", "reload", "touch"]);
12345
+ const proxy = new Proxy(sessObj, {
12346
+ set(target, prop, value, receiver) {
12347
+ if (typeof prop !== "symbol" && !skippedKeys.has(prop)) {
12348
+ _dirty = true;
12349
+ }
12350
+ return Reflect.set(target, prop, value, receiver);
12351
+ },
12352
+ deleteProperty(target, prop) {
12353
+ if (typeof prop !== "symbol" && !skippedKeys.has(prop)) _dirty = true;
12354
+ return Reflect.deleteProperty(target, prop);
12355
+ }
12356
+ });
12357
+ proxy[$isDirty] = () => _dirty;
12358
+ proxy[$resetDirty] = () => {
12359
+ _dirty = false;
12360
+ };
12361
+ return proxy;
12208
12362
  };
12209
12363
  let sessionData = null;
12210
12364
  if (!isNew && sessionID) {
@@ -12227,17 +12381,15 @@ function Session(options) {
12227
12381
  ctx.session = sess;
12228
12382
  ctx.sessionID = sessionID;
12229
12383
  ctx.sessionStore = store;
12230
- const originalHash = JSON.stringify(sess);
12231
12384
  const result = await next();
12232
- const currentHash = JSON.stringify(sess);
12233
- const isModified = originalHash !== currentHash;
12385
+ const isModified = sess[$isDirty]?.() ?? false;
12234
12386
  if (!sessionID) return result;
12235
12387
  let shouldSave = false;
12236
12388
  if (isModified) {
12237
12389
  shouldSave = true;
12238
12390
  } else if (isNew && saveUninitialized) {
12239
12391
  shouldSave = true;
12240
- } else if (!isNew && resave) {
12392
+ } else if (!isNew && resave && isModified) {
12241
12393
  shouldSave = true;
12242
12394
  }
12243
12395
  if (shouldSave) {
@@ -12274,6 +12426,7 @@ function Static(options, prefix = "/") {
12274
12426
  try {
12275
12427
  const result = await staticMiddleware(ctx, next);
12276
12428
  if (result instanceof Response && result.status === 404) {
12429
+ ctx.response.status = 200;
12277
12430
  return next();
12278
12431
  }
12279
12432
  return result;
@@ -12425,4 +12578,4 @@ exports.traceMiddleware = traceMiddleware;
12425
12578
  exports.useExpress = useExpress;
12426
12579
  exports.valibot = valibot;
12427
12580
  exports.validate = validate;
12428
- //# sourceMappingURL=index-46Z4ASUY.cjs.map
12581
+ //# sourceMappingURL=index-B-bQ7kZy.cjs.map