shokupan 0.15.0 → 0.15.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.
Files changed (28) hide show
  1. package/dist/{index-46Z4ASUY.cjs → index-5vGQaaL5.cjs} +126 -60
  2. package/dist/index-5vGQaaL5.cjs.map +1 -0
  3. package/dist/{index-MDmdOQNV.js → index-Da50R4Vm.js} +123 -57
  4. package/dist/index-Da50R4Vm.js.map +1 -0
  5. package/dist/index.cjs +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/{knex-CIS1IT0Y.cjs → knex-CDjnrxdn.cjs} +2 -2
  8. package/dist/{knex-CIS1IT0Y.cjs.map → knex-CDjnrxdn.cjs.map} +1 -1
  9. package/dist/{knex-BbK40SH3.js → knex-Cy3JHwDL.js} +2 -2
  10. package/dist/{knex-BbK40SH3.js.map → knex-Cy3JHwDL.js.map} +1 -1
  11. package/dist/{level-Dhm9CiBU.js → level--l_261kz.js} +2 -2
  12. package/dist/{level-Dhm9CiBU.js.map → level--l_261kz.js.map} +1 -1
  13. package/dist/{level-B0zBSwWA.cjs → level-dUQVvWkh.cjs} +2 -2
  14. package/dist/{level-B0zBSwWA.cjs.map → level-dUQVvWkh.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-CY-WyaJ5.js → sqlite-DOyRAxVq.js} +2 -2
  18. package/dist/{sqlite-CY-WyaJ5.js.map → sqlite-DOyRAxVq.js.map} +1 -1
  19. package/dist/{sqlite-sfDxgiji.cjs → sqlite-DdGPsbUB.cjs} +2 -2
  20. package/dist/{sqlite-sfDxgiji.cjs.map → sqlite-DdGPsbUB.cjs.map} +1 -1
  21. package/dist/{surreal-D1txBCZb.js → surreal-BHXu9CVo.js} +2 -2
  22. package/dist/{surreal-D1txBCZb.js.map → surreal-BHXu9CVo.js.map} +1 -1
  23. package/dist/{surreal-DiEl_VJL.cjs → surreal-p1skSyYf.cjs} +2 -2
  24. package/dist/{surreal-DiEl_VJL.cjs.map → surreal-p1skSyYf.cjs.map} +1 -1
  25. package/dist/util/body-parser.d.ts +7 -0
  26. package/package.json +1 -1
  27. package/dist/index-46Z4ASUY.cjs.map +0 -1
  28. package/dist/index-MDmdOQNV.js.map +0 -1
@@ -116,26 +116,58 @@ 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) {
123
- const err = new Error("Length Required");
124
- err.status = 411;
125
- throw err;
124
+ const rawBuffer = await BodyParser.readRawBufferBody(req, maxBodySize);
125
+ const syntheticReq = new Request("http://localhost", {
126
+ method: "POST",
127
+ headers: req.headers,
128
+ body: rawBuffer.buffer
129
+ });
130
+ return syntheticReq.formData();
131
+ }
132
+ /**
133
+ * Reads raw body as Uint8Array with size enforcement (used by parseFormData).
134
+ */
135
+ static async readRawBufferBody(req, maxBodySize) {
136
+ if (typeof req.body === "string") {
137
+ const enc = new TextEncoder().encode(req.body);
138
+ if (enc.byteLength > maxBodySize) {
139
+ const err = new Error("Payload Too Large");
140
+ err.status = 413;
141
+ throw err;
142
+ }
143
+ return enc;
126
144
  }
127
- const cl = parseInt(clHeader, 10);
128
- if (isNaN(cl)) {
129
- const err = new Error("Bad Request");
130
- err.status = 400;
131
- throw err;
145
+ const reader = req.body?.getReader();
146
+ if (!reader) return new Uint8Array(0);
147
+ const chunks = [];
148
+ let totalSize = 0;
149
+ try {
150
+ while (true) {
151
+ const { done, value } = await reader.read();
152
+ if (done) break;
153
+ totalSize += value.length;
154
+ if (totalSize > maxBodySize) {
155
+ const err = new Error("Payload Too Large");
156
+ err.status = 413;
157
+ throw err;
158
+ }
159
+ chunks.push(value);
160
+ }
161
+ } finally {
162
+ reader.releaseLock();
132
163
  }
133
- if (cl > maxBodySize) {
134
- const err = new Error("Payload Too Large");
135
- err.status = 413;
136
- throw err;
164
+ const result = new Uint8Array(totalSize);
165
+ let offset = 0;
166
+ for (const chunk of chunks) {
167
+ result.set(chunk, offset);
168
+ offset += chunk.length;
137
169
  }
138
- return req.formData();
170
+ return result;
139
171
  }
140
172
  /**
141
173
  * Reads raw body as string with size enforcement
@@ -1383,7 +1415,7 @@ class ShokupanContext {
1383
1415
  }
1384
1416
  function RateLimitMiddleware(options = {}) {
1385
1417
  const windowMs = options.windowMs || 60 * 1e3;
1386
- const max = options.limit || options.max || 5;
1418
+ const max = options.limit || options.max || 100;
1387
1419
  const message = options.message || "Too many requests, please try again later.";
1388
1420
  const statusCode = options.statusCode || 429;
1389
1421
  const headers = options.headers !== false;
@@ -1412,9 +1444,7 @@ function RateLimitMiddleware(options = {}) {
1412
1444
  const hits = /* @__PURE__ */ new Map();
1413
1445
  const interval = setInterval(() => {
1414
1446
  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];
1447
+ for (const [key, record] of hits) {
1418
1448
  if (record.resetTime <= now) {
1419
1449
  hits.delete(key);
1420
1450
  }
@@ -5920,29 +5950,29 @@ class Shokupan extends ShokupanRouter {
5920
5950
  try {
5921
5951
  switch (adapterName) {
5922
5952
  case "sqlite": {
5923
- const { SqliteAdapter } = await Promise.resolve().then(() => require("./sqlite-sfDxgiji.cjs"));
5953
+ const { SqliteAdapter } = await Promise.resolve().then(() => require("./sqlite-DdGPsbUB.cjs"));
5924
5954
  this.datastore = new SqliteAdapter(options);
5925
5955
  break;
5926
5956
  }
5927
5957
  case "level": {
5928
- const { LevelAdapter } = await Promise.resolve().then(() => require("./level-B0zBSwWA.cjs"));
5958
+ const { LevelAdapter } = await Promise.resolve().then(() => require("./level-dUQVvWkh.cjs"));
5929
5959
  this.datastore = new LevelAdapter(options);
5930
5960
  break;
5931
5961
  }
5932
5962
  case "surrealdb": {
5933
- const { SurrealAdapter } = await Promise.resolve().then(() => require("./surreal-DiEl_VJL.cjs"));
5963
+ const { SurrealAdapter } = await Promise.resolve().then(() => require("./surreal-p1skSyYf.cjs"));
5934
5964
  const legacyConfig = this.applicationConfig.surreal || {};
5935
5965
  const effectiveOptions = { ...legacyConfig, ...options };
5936
5966
  this.datastore = new SurrealAdapter(effectiveOptions);
5937
5967
  break;
5938
5968
  }
5939
5969
  case "knex": {
5940
- const { KnexAdapter } = await Promise.resolve().then(() => require("./knex-CIS1IT0Y.cjs"));
5970
+ const { KnexAdapter } = await Promise.resolve().then(() => require("./knex-CDjnrxdn.cjs"));
5941
5971
  this.datastore = new KnexAdapter(options || {});
5942
5972
  break;
5943
5973
  }
5944
5974
  default: {
5945
- const { SurrealAdapter } = await Promise.resolve().then(() => require("./surreal-DiEl_VJL.cjs"));
5975
+ const { SurrealAdapter } = await Promise.resolve().then(() => require("./surreal-p1skSyYf.cjs"));
5946
5976
  const legacy = this.applicationConfig.surreal;
5947
5977
  this.datastore = new SurrealAdapter(options || legacy || {});
5948
5978
  }
@@ -6897,7 +6927,7 @@ class ApiExplorerPlugin extends ShokupanRouter {
6897
6927
  }
6898
6928
  }
6899
6929
  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));
6930
+ 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-5vGQaaL5.cjs", document.baseURI).href));
6901
6931
  if (dir.endsWith("dist")) {
6902
6932
  return dir + "/plugins/application/api-explorer";
6903
6933
  }
@@ -6926,22 +6956,26 @@ class ApiExplorerPlugin extends ShokupanRouter {
6926
6956
  this.get("/style.css", (ctx) => serveFile(ctx, "style.css", "text/css"));
6927
6957
  this.get("/theme.css", (ctx) => serveFile(ctx, "theme.css", "text/css"));
6928
6958
  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
- });
6959
+ const isProduction = process.env.NODE_ENV === "production";
6960
+ const sourceViewEnabled = this.pluginOptions.enableSourceView ?? !isProduction;
6961
+ if (sourceViewEnabled) {
6962
+ this.get("/_source", async (ctx) => {
6963
+ const file = ctx.query["file"];
6964
+ if (!file) return ctx.text("Missing file parameter", 400);
6965
+ const { resolve } = await import("node:path");
6966
+ const cwd = process.cwd();
6967
+ const resolvedPath = resolve(cwd, file);
6968
+ if (!resolvedPath.startsWith(cwd + "/") && resolvedPath !== cwd) {
6969
+ return ctx.text("Forbidden: File must be within project root", 403);
6970
+ }
6971
+ try {
6972
+ const content = await promises$1.readFile(resolvedPath, "utf-8");
6973
+ return ctx.text(content);
6974
+ } catch (err) {
6975
+ return ctx.text("File not found", 404);
6976
+ }
6977
+ });
6978
+ }
6945
6979
  this.get("/openapi.json", async (ctx) => {
6946
6980
  const spec = this.root.openApiSpec ? structuredClone(this.root.openApiSpec) : await (this.root || this).generateApiSpec();
6947
6981
  return ctx.json(stripSourceCode(spec));
@@ -7566,7 +7600,7 @@ class AsyncApiPlugin extends ShokupanRouter {
7566
7600
  this.init();
7567
7601
  }
7568
7602
  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));
7603
+ 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-5vGQaaL5.cjs", document.baseURI).href));
7570
7604
  if (dir.endsWith("dist")) {
7571
7605
  return dir + "/plugins/application/asyncapi";
7572
7606
  }
@@ -7822,7 +7856,16 @@ class AuthPlugin extends ShokupanRouter {
7822
7856
  };
7823
7857
  } else if (provider === "apple") {
7824
7858
  if (idToken) {
7825
- const payload = this.jose.decodeJwt(idToken);
7859
+ const { createRemoteJWKSet, jwtVerify } = this.jose;
7860
+ if (!this._appleJwks) {
7861
+ this._appleJwks = createRemoteJWKSet(
7862
+ new URL("https://appleid.apple.com/auth/keys")
7863
+ );
7864
+ }
7865
+ const { payload } = await jwtVerify(idToken, this._appleJwks, {
7866
+ issuer: "https://appleid.apple.com",
7867
+ audience: this.authConfig.providers.apple?.clientId
7868
+ });
7826
7869
  user = {
7827
7870
  id: payload.sub,
7828
7871
  email: payload["email"],
@@ -8199,7 +8242,7 @@ function Card({ title, contentId }) {
8199
8242
  /* @__PURE__ */ jsxRuntime.jsx("div", { id: contentId })
8200
8243
  ] });
8201
8244
  }
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);
8245
+ const require$1 = node_module.createRequire(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index-5vGQaaL5.cjs", document.baseURI).href);
8203
8246
  const http = require$1("node:http");
8204
8247
  const https = require$1("node:https");
8205
8248
  class FetchInterceptor {
@@ -8803,7 +8846,7 @@ class Dashboard {
8803
8846
  }
8804
8847
  // Get base path for dashboard files - works in both dev (src/) and production (dist/)
8805
8848
  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));
8849
+ 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-5vGQaaL5.cjs", document.baseURI).href));
8807
8850
  if (dir.endsWith("dist")) {
8808
8851
  return dir + "/plugins/application/dashboard";
8809
8852
  }
@@ -11306,6 +11349,11 @@ function Cors(options = {}) {
11306
11349
  optionsSuccessStatus: 204
11307
11350
  };
11308
11351
  const opts = { ...defaults2, ...options };
11352
+ if (opts.credentials && opts.origin === "*") {
11353
+ throw new Error(
11354
+ 'CORS misconfiguration: `credentials: true` is incompatible with `origin: "*"`. Specify an explicit origin or array of origins instead.'
11355
+ );
11356
+ }
11309
11357
  const corsMiddleware = async function CorsMiddleware(ctx, next) {
11310
11358
  const headers = {};
11311
11359
  const origin = ctx.headers.get("origin");
@@ -11852,6 +11900,9 @@ function Proxy$1(options) {
11852
11900
  if (!["http:", "https:"].includes(url.protocol)) {
11853
11901
  return ctx.text("Invalid protocol in proxied URL", 400);
11854
11902
  }
11903
+ if (!options.allowedHosts?.includes(url.hostname)) {
11904
+ return ctx.text("Proxied hostname not in allowlist", 403);
11905
+ }
11855
11906
  const headers = new Headers(req.headers);
11856
11907
  if (options.changeOrigin) {
11857
11908
  headers.set("host", targetUrl.host);
@@ -11945,7 +11996,9 @@ function handleWSDrain(ws) {
11945
11996
  }
11946
11997
  function SecurityHeaders(options = {}) {
11947
11998
  const securityHeadersMiddleware = async function SecurityHeadersMiddleware(ctx, next) {
11948
- const set = (k, v) => ctx.response.set(k, v);
11999
+ const response = await next();
12000
+ if (!(response instanceof Response)) return response;
12001
+ const set = (k, v) => response.headers.set(k, v);
11949
12002
  if (options.dnsPrefetchControl !== false) {
11950
12003
  const allow = options.dnsPrefetchControl?.allow;
11951
12004
  set("X-DNS-Prefetch-Control", allow ? "on" : "off");
@@ -11956,7 +12009,7 @@ function SecurityHeaders(options = {}) {
11956
12009
  if (action === "sameorigin") set("X-Frame-Options", "SAMEORIGIN");
11957
12010
  else if (action === "deny") set("X-Frame-Options", "DENY");
11958
12011
  }
11959
- if (options.hsts !== false) {
12012
+ if (options.hsts !== false && ctx.secure === true) {
11960
12013
  const opt = options.hsts || {};
11961
12014
  const maxAge = opt.maxAge || 15552e3;
11962
12015
  let header = `max-age=${maxAge}`;
@@ -11983,14 +12036,25 @@ function SecurityHeaders(options = {}) {
11983
12036
  if (opt === void 0 || opt === true) {
11984
12037
  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
12038
  } 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];
12039
+ const parts = [];
12040
+ for (const [key, val] of Object.entries(opt)) {
12041
+ if (val === false) continue;
12042
+ const directive = key.replace(/([A-Z])/g, "-$1").toLowerCase();
12043
+ if (val === true) {
12044
+ parts.push(directive);
12045
+ } else {
12046
+ const sources = Array.isArray(val) ? val.join(" ") : String(val);
12047
+ parts.push(`${directive} ${sources}`);
12048
+ }
12049
+ }
12050
+ if (parts.length > 0) {
12051
+ set("Content-Security-Policy", parts.join(";"));
11989
12052
  }
11990
12053
  }
11991
12054
  }
11992
- if (options.hidePoweredBy !== false) ;
11993
- const response = await next();
12055
+ if (options.hidePoweredBy !== false) {
12056
+ response.headers.delete("X-Powered-By");
12057
+ }
11994
12058
  return response;
11995
12059
  };
11996
12060
  securityHeadersMiddleware.isBuiltin = true;
@@ -12103,7 +12167,7 @@ function unsign(input, secret) {
12103
12167
  Buffer.from(expectedInput).copy(paddedExpected);
12104
12168
  Buffer.from(input).copy(paddedInput);
12105
12169
  try {
12106
- const valid = require("crypto").timingSafeEqual(paddedExpected, paddedInput);
12170
+ const valid = crypto.timingSafeEqual(paddedExpected, paddedInput);
12107
12171
  return valid ? tentValue : false;
12108
12172
  } catch {
12109
12173
  return false;
@@ -12122,10 +12186,14 @@ function Session(options) {
12122
12186
  const cookies = ctx.cookies;
12123
12187
  const rawCookie = cookies[name];
12124
12188
  if (rawCookie) {
12125
- if (rawCookie.substr(0, 2) === "s:") {
12126
- const val = unsign(rawCookie.slice(2), secrets[0]);
12127
- if (val) {
12128
- reqSessionId = val;
12189
+ if (rawCookie.slice(0, 2) === "s:") {
12190
+ const signed = rawCookie.slice(2);
12191
+ for (let i = 0; i < secrets.length; i++) {
12192
+ const val = unsign(signed, secrets[i]);
12193
+ if (val !== false) {
12194
+ reqSessionId = val;
12195
+ break;
12196
+ }
12129
12197
  }
12130
12198
  } else {
12131
12199
  reqSessionId = rawCookie;
@@ -12181,8 +12249,6 @@ function Session(options) {
12181
12249
  });
12182
12250
  });
12183
12251
  };
12184
- sessObj.undefined = () => {
12185
- };
12186
12252
  sessObj.reload = () => {
12187
12253
  return new Promise((resolve, reject) => {
12188
12254
  store.get(sessObj.id, (err, sess2) => {
@@ -12425,4 +12491,4 @@ exports.traceMiddleware = traceMiddleware;
12425
12491
  exports.useExpress = useExpress;
12426
12492
  exports.valibot = valibot;
12427
12493
  exports.validate = validate;
12428
- //# sourceMappingURL=index-46Z4ASUY.cjs.map
12494
+ //# sourceMappingURL=index-5vGQaaL5.cjs.map