@fitzzero/quickdraw-core 5.1.1 → 5.1.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 (107) hide show
  1. package/README.md +533 -180
  2. package/dist/{admin-DKwkIh5i.d.ts → admin-DWWrT1h6.d.ts} +2 -2
  3. package/dist/{adminSchemas-2DUA_hZ8.d.ts → adminSchemas-D4bFKsrb.d.ts} +6 -4
  4. package/dist/{auth-BhWqdyb-.d.ts → auth-DXiSJZaf.d.ts} +42 -16
  5. package/dist/{caller-CXTFswBO.d.ts → caller-BT-Mv4bD.d.ts} +117 -22
  6. package/dist/{chunk-RT6OILOI.js → chunk-4D2DOGAW.js} +2 -14
  7. package/dist/chunk-4D2DOGAW.js.map +1 -0
  8. package/dist/chunk-5CGO426C.js +15 -0
  9. package/dist/chunk-5CGO426C.js.map +1 -0
  10. package/dist/{chunk-RZTP4IEW.js → chunk-6K5BY47X.js} +4594 -3481
  11. package/dist/chunk-6K5BY47X.js.map +1 -0
  12. package/dist/{chunk-VSQBHPWH.js → chunk-753VJIUP.js} +13 -15
  13. package/dist/chunk-753VJIUP.js.map +1 -0
  14. package/dist/{chunk-ZIVPHYRG.js → chunk-7QCUX4TK.js} +4 -4
  15. package/dist/chunk-7QCUX4TK.js.map +1 -0
  16. package/dist/chunk-AK23XSWD.js +16 -0
  17. package/dist/chunk-AK23XSWD.js.map +1 -0
  18. package/dist/{chunk-SHEGLZEP.js → chunk-AONAFOYU.js} +14 -5
  19. package/dist/{chunk-SHEGLZEP.js.map → chunk-AONAFOYU.js.map} +1 -1
  20. package/dist/chunk-DKE6QO26.js +136 -0
  21. package/dist/chunk-DKE6QO26.js.map +1 -0
  22. package/dist/{chunk-H23KEFBA.js → chunk-DUBU3BB3.js} +30 -1
  23. package/dist/chunk-DUBU3BB3.js.map +1 -0
  24. package/dist/{chunk-CDKG4TFA.js → chunk-GKH3LJ7N.js} +1 -1
  25. package/dist/chunk-GKH3LJ7N.js.map +1 -0
  26. package/dist/{chunk-OECZPRPW.js → chunk-GSIPUNJL.js} +49 -9
  27. package/dist/chunk-GSIPUNJL.js.map +1 -0
  28. package/dist/{chunk-DD4SKCNO.js → chunk-LCC7HNFP.js} +12 -1
  29. package/dist/chunk-LCC7HNFP.js.map +1 -0
  30. package/dist/{chunk-2PWMUVC7.js → chunk-MBH6IDSM.js} +480 -12
  31. package/dist/chunk-MBH6IDSM.js.map +1 -0
  32. package/dist/{chunk-QCHKWIRS.js → chunk-NMXYENH4.js} +10 -10
  33. package/dist/{chunk-QCHKWIRS.js.map → chunk-NMXYENH4.js.map} +1 -1
  34. package/dist/chunk-NO4M6PO5.js +101 -0
  35. package/dist/chunk-NO4M6PO5.js.map +1 -0
  36. package/dist/{chunk-TBMKJ26R.js → chunk-NW6U45HX.js} +3 -1
  37. package/dist/chunk-NW6U45HX.js.map +1 -0
  38. package/dist/{chunk-REX5YPHX.js → chunk-RJ5I2VO3.js} +74 -19
  39. package/dist/chunk-RJ5I2VO3.js.map +1 -0
  40. package/dist/{chunk-QEKVYG5F.js → chunk-RL4VKLBX.js} +1 -1
  41. package/dist/{chunk-QEKVYG5F.js.map → chunk-RL4VKLBX.js.map} +1 -1
  42. package/dist/chunk-XH2IZVLT.js +62 -0
  43. package/dist/chunk-XH2IZVLT.js.map +1 -0
  44. package/dist/{chunk-4DIQHASK.js → chunk-Z5ILULF2.js} +26 -7
  45. package/dist/chunk-Z5ILULF2.js.map +1 -0
  46. package/dist/cli/quickdraw-docs.js +5 -3
  47. package/dist/cli/quickdraw-docs.js.map +1 -1
  48. package/dist/client/index.d.ts +30 -18
  49. package/dist/client/index.js +8 -6
  50. package/dist/client/index.js.map +1 -1
  51. package/dist/{clientTypes-Bc-G8n-U.d.ts → clientTypes-hWcPUVS9.d.ts} +3 -3
  52. package/dist/{connection-Cr1BU07W.d.ts → connection-CgijdGpT.d.ts} +8 -4
  53. package/dist/{createServer-CP9eDHZZ.d.ts → createServer-Cp-Wk500.d.ts} +36 -14
  54. package/dist/{createTestApp-UKVGEmOf.d.ts → createTestApp-BB_PkqQ7.d.ts} +16 -10
  55. package/dist/{envelope-Cp3YcVxC.d.ts → envelope-DzWyIXjj.d.ts} +18 -4
  56. package/dist/{http-Djj2Inuu.d.ts → http-DotQ5bj8.d.ts} +68 -17
  57. package/dist/index.d.ts +7 -7
  58. package/dist/index.js +37 -13
  59. package/dist/index.js.map +1 -1
  60. package/dist/{infer-Teo1xpsd.d.ts → infer-BmoZsL9a.d.ts} +43 -10
  61. package/dist/logger-Yz93KzdV.d.ts +30 -0
  62. package/dist/{metrics-BdBu5o8G.d.ts → metrics-DW80Exve.d.ts} +2 -2
  63. package/dist/origins-kvpxnVcW.d.ts +8 -0
  64. package/dist/prisma/index.d.ts +5 -5
  65. package/dist/prisma/index.js +130 -67
  66. package/dist/prisma/index.js.map +1 -1
  67. package/dist/{rateLimit-ZMIZJUZF.js → rateLimit-5RKBFLUR.js} +2 -2
  68. package/dist/server/auth/index.d.ts +82 -33
  69. package/dist/server/auth/index.js +273 -268
  70. package/dist/server/auth/index.js.map +1 -1
  71. package/dist/server/express/index.d.ts +9 -2
  72. package/dist/server/express/index.js +1 -1
  73. package/dist/server/index.d.ts +147 -67
  74. package/dist/server/index.js +520 -381
  75. package/dist/server/index.js.map +1 -1
  76. package/dist/server/mcp/index.d.ts +63 -17
  77. package/dist/server/mcp/index.js +256 -54
  78. package/dist/server/mcp/index.js.map +1 -1
  79. package/dist/server/otel.d.ts +3 -3
  80. package/dist/{sharing-DVa8W-yR.d.ts → sharing-UDLn1049.d.ts} +3 -3
  81. package/dist/testing/client.d.ts +14 -14
  82. package/dist/testing/client.js +9 -7
  83. package/dist/testing/client.js.map +1 -1
  84. package/dist/testing/index.d.ts +12 -12
  85. package/dist/testing/index.js +21 -12
  86. package/dist/testing/index.js.map +1 -1
  87. package/dist/testing/mock.d.ts +5 -5
  88. package/dist/testing/mock.js +8 -6
  89. package/dist/{types-Cu0vmx7c.d.ts → types-CYliTP65.d.ts} +1 -1
  90. package/dist/utils/index.d.ts +1 -1
  91. package/package.json +1 -1
  92. package/dist/chunk-2PWMUVC7.js.map +0 -1
  93. package/dist/chunk-4DIQHASK.js.map +0 -1
  94. package/dist/chunk-CDKG4TFA.js.map +0 -1
  95. package/dist/chunk-DD4SKCNO.js.map +0 -1
  96. package/dist/chunk-H23KEFBA.js.map +0 -1
  97. package/dist/chunk-OECZPRPW.js.map +0 -1
  98. package/dist/chunk-REX5YPHX.js.map +0 -1
  99. package/dist/chunk-RT6OILOI.js.map +0 -1
  100. package/dist/chunk-RZTP4IEW.js.map +0 -1
  101. package/dist/chunk-TBMKJ26R.js.map +0 -1
  102. package/dist/chunk-VSQBHPWH.js.map +0 -1
  103. package/dist/chunk-YSWH776Y.js +0 -13
  104. package/dist/chunk-YSWH776Y.js.map +0 -1
  105. package/dist/chunk-ZIVPHYRG.js.map +0 -1
  106. package/dist/logger-BY28acW-.d.ts +0 -16
  107. /package/dist/{rateLimit-ZMIZJUZF.js.map → rateLimit-5RKBFLUR.js.map} +0 -0
@@ -1,7 +1,15 @@
1
+ import {
2
+ OAUTH_RETURN_ORIGIN_COOKIE,
3
+ originAllowlist,
4
+ rememberRouteOrigins,
5
+ routeOriginsOf,
6
+ validateRedirectOrigin
7
+ } from "../../chunk-NO4M6PO5.js";
1
8
  import {
2
9
  HOST_SESSION_COOKIE,
3
10
  SESSION_COOKIE,
4
11
  clearSessionCookie,
12
+ clearStaleHostSession,
5
13
  cookieDomainFromEnv,
6
14
  cookieToken,
7
15
  cookiesOf,
@@ -11,20 +19,22 @@ import {
11
19
  httpCredentialOf,
12
20
  isJsonRequest,
13
21
  isSecureSessionRequest,
22
+ markSocketAuth,
14
23
  readJsonInput,
24
+ readVerifiedJWT,
15
25
  recordSocketSession,
16
26
  sessionCookieNameFor,
17
27
  sessionCookieNamesFor,
18
28
  setSessionCookie,
19
- tokenOf,
20
29
  transportCookieNaming,
21
30
  verifyJWT
22
- } from "../../chunk-REX5YPHX.js";
23
- import "../../chunk-H23KEFBA.js";
31
+ } from "../../chunk-RJ5I2VO3.js";
32
+ import "../../chunk-DUBU3BB3.js";
24
33
  import "../../chunk-Z2LZ7IZ7.js";
25
34
  import {
26
- consoleLogger
27
- } from "../../chunk-YSWH776Y.js";
35
+ consoleLogger,
36
+ errorText
37
+ } from "../../chunk-DKE6QO26.js";
28
38
  import {
29
39
  INTERNAL_MESSAGE,
30
40
  QuickdrawError,
@@ -128,38 +138,15 @@ var googleProvider = {
128
138
  // src/server/auth/mock.ts
129
139
  import { randomBytes } from "crypto";
130
140
 
131
- // src/server/auth/validateOrigin.ts
132
- var OAUTH_RETURN_ORIGIN_COOKIE = "oauth_return_origin";
133
- var LOCALHOST_REGEX = /^http:\/\/localhost:\d+$/;
134
- function envExtraOrigins() {
135
- const raw = process.env.EXTRA_ALLOWED_ORIGINS;
136
- if (!raw) return [];
137
- return raw.split(",").map((s) => s.trim()).filter(Boolean);
138
- }
139
- function validateRedirectOrigin(origin, options = {}) {
140
- if (!origin) return null;
141
- const clientUrl = options.clientUrl ?? process.env.CLIENT_URL;
142
- let parsedOrigin;
143
- try {
144
- parsedOrigin = new URL(origin);
145
- } catch {
146
- return null;
147
- }
148
- const cleanOrigin = parsedOrigin.origin;
149
- if (clientUrl && cleanOrigin === clientUrl) {
150
- return cleanOrigin;
151
- }
152
- const extra = options.extraAllowedOrigins ?? envExtraOrigins();
153
- if (extra.includes(cleanOrigin)) {
154
- return cleanOrigin;
155
- }
156
- if (options.allowedPatterns?.some((pattern) => pattern.test(cleanOrigin))) {
157
- return cleanOrigin;
158
- }
159
- if ((options.allowLocalhostInDev ?? true) && process.env.NODE_ENV !== "production" && LOCALHOST_REGEX.test(cleanOrigin)) {
160
- return cleanOrigin;
161
- }
162
- return null;
141
+ // src/server/auth/devSignIns.ts
142
+ var DEVELOPMENT_ENVS = /* @__PURE__ */ new Set(["development", "test"]);
143
+ function developmentSignInsAllowed() {
144
+ const env = process.env.NODE_ENV;
145
+ return env !== void 0 && DEVELOPMENT_ENVS.has(env);
146
+ }
147
+ function nodeEnvLabel() {
148
+ const env = process.env.NODE_ENV;
149
+ return env === void 0 ? "unset" : JSON.stringify(env);
163
150
  }
164
151
 
165
152
  // src/server/auth/mock.ts
@@ -168,7 +155,7 @@ var CODE_TTL_MS = 5 * 60 * 1e3;
168
155
  var TOKEN_TTL_MS = 60 * 60 * 1e3;
169
156
  var DEV_PRIVATE_ORIGIN_REGEX = /^http:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}):\d+$/;
170
157
  function isMockOAuthEnabled() {
171
- return process.env.ENABLE_MOCK_OAUTH === "true" && process.env.NODE_ENV !== "production";
158
+ return process.env.ENABLE_MOCK_OAUTH === "true" && developmentSignInsAllowed();
172
159
  }
173
160
  function createMockOAuthProvider(apiBaseUrl, options = {}) {
174
161
  const prefix = options.pathPrefix ?? DEFAULT_MOCK_PATH_PREFIX;
@@ -269,7 +256,7 @@ function sweep(store) {
269
256
  }
270
257
  }
271
258
  function guard(res) {
272
- if (process.env.NODE_ENV === "production") {
259
+ if (!developmentSignInsAllowed()) {
273
260
  res.status(404).json({ error: "Not found" });
274
261
  return false;
275
262
  }
@@ -356,7 +343,9 @@ function registerMockOAuthProvider(router, options) {
356
343
  const logger = options.logger ?? consoleLogger;
357
344
  if (!isMockOAuthEnabled()) {
358
345
  if (process.env.ENABLE_MOCK_OAUTH === "true") {
359
- logger.error("Mock OAuth is enabled but NODE_ENV is production \u2014 refusing to mount routes");
346
+ logger.error(
347
+ `Mock OAuth is enabled but NODE_ENV is ${nodeEnvLabel()}: the mock runs only when NODE_ENV is development or test, so its routes are not mounted; set NODE_ENV=development to use it`
348
+ );
360
349
  }
361
350
  return false;
362
351
  }
@@ -370,7 +359,9 @@ function registerMockOAuthProvider(router, options) {
370
359
  router.get(`${endpoints.prefix}/authorize`, authorizeHandler(endpoints));
371
360
  router.post(`${endpoints.prefix}/token`, tokenHandler(endpoints));
372
361
  router.get(`${endpoints.prefix}/userinfo`, userinfoHandler(endpoints));
373
- logger.info(`Mock OAuth provider mounted at ${endpoints.prefix} (development only)`);
362
+ logger.warn(
363
+ `Mock OAuth provider mounted at ${endpoints.prefix}: anyone who reaches it signs in as any user it lists (NODE_ENV=${process.env.NODE_ENV ?? ""}; development and tests only)`
364
+ );
374
365
  return true;
375
366
  }
376
367
 
@@ -401,6 +392,14 @@ function sendJson(res, status, body) {
401
392
  function refuse(res, code, message) {
402
393
  sendJson(res, httpStatus(code), { error: code, message });
403
394
  }
395
+ function failRoute(logger, req, res, error) {
396
+ logger.error("An auth route failed", {
397
+ category: "quickdraw.auth",
398
+ path: pathOf(req),
399
+ error: errorText(error)
400
+ });
401
+ refuse(res, "INTERNAL", INTERNAL_MESSAGE);
402
+ }
404
403
  function redirect(res, location) {
405
404
  noStore(res, 302);
406
405
  res.setHeader("location", location);
@@ -411,67 +410,8 @@ function noContent(res) {
411
410
  res.end();
412
411
  }
413
412
 
414
- // src/server/auth/routes/origins.ts
415
- function exactOrigin(entry, owner) {
416
- let url;
417
- try {
418
- url = typeof entry === "string" ? new URL(entry) : void 0;
419
- } catch {
420
- url = void 0;
421
- }
422
- const bare = url !== void 0 && (url.protocol === "https:" || url.protocol === "http:") && url.username === "" && url.password === "" && url.pathname === "/" && url.search === "" && url.hash === "";
423
- if (url === void 0 || !bare) {
424
- throw new TypeError(
425
- `${owner}: allowedOrigins entries are origins such as "https://app.example.com" or anchored patterns; got ${String(entry)}`
426
- );
427
- }
428
- return url.origin;
429
- }
430
- function anchoredPattern(pattern, owner) {
431
- const { source } = pattern;
432
- const anchored = source.startsWith("^") && source.endsWith("$") && !source.endsWith("\\$");
433
- if (!anchored || pattern.global || pattern.sticky) {
434
- throw new TypeError(
435
- `${owner}: an allowedOrigins pattern must match whole origins, anchored with ^ and $ and without the g or y flag; got ${String(pattern)}`
436
- );
437
- }
438
- return new RegExp(`^(?:${source})$`, pattern.flags);
439
- }
440
- function originAllowlist(allowedOrigins, owner, allowEmpty = false) {
441
- if (!Array.isArray(allowedOrigins) || allowedOrigins.length === 0 && !allowEmpty) {
442
- throw new TypeError(`${owner}: allowedOrigins must list the web app's origins`);
443
- }
444
- const exact = [];
445
- const patterns = [];
446
- for (const entry of allowedOrigins) {
447
- if (entry instanceof RegExp) {
448
- patterns.push(anchoredPattern(entry, owner));
449
- } else {
450
- exact.push(exactOrigin(entry, owner));
451
- }
452
- }
453
- const options = {
454
- // An empty client URL, not undefined: undefined falls back to process.env.CLIENT_URL.
455
- clientUrl: "",
456
- extraAllowedOrigins: exact,
457
- allowedPatterns: patterns,
458
- allowLocalhostInDev: false
459
- };
460
- return Object.freeze({
461
- allowed: (value) => typeof value === "string" && value !== "" ? validateRedirectOrigin(value, options) : null,
462
- fallback: exact[0]
463
- });
464
- }
465
- var ROUTE_ORIGINS = /* @__PURE__ */ new WeakMap();
466
- function rememberRouteOrigins(sessions, origins) {
467
- ROUTE_ORIGINS.set(sessions, origins);
468
- }
469
- function routeOriginsOf(sessions) {
470
- return ROUTE_ORIGINS.get(sessions);
471
- }
472
-
473
413
  // src/server/auth/routes/state.ts
474
- import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
414
+ import { createHmac, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
475
415
  var OAUTH_STATE_COOKIE = "qd_oauth";
476
416
  var HOST_OAUTH_STATE_COOKIE = "__Host-qd_oauth";
477
417
  var OAUTH_STATE_TTL_MS = 10 * 60 * 1e3;
@@ -479,16 +419,30 @@ var MAX_REDEEMED = 1e4;
479
419
  function newState() {
480
420
  return randomBytes2(32).toString("base64url");
481
421
  }
482
- function encodePending(pending) {
483
- return Buffer.from(JSON.stringify(pending), "utf8").toString("base64url");
422
+ function oauthStateKey(jwtSecret) {
423
+ return createHmac("sha256", jwtSecret).update("quickdraw:oauth-state").digest();
424
+ }
425
+ function macOf(payload, key) {
426
+ return createHmac("sha256", key).update(payload).digest();
427
+ }
428
+ function encodePending(pending, key) {
429
+ const payload = Buffer.from(JSON.stringify(pending), "utf8").toString("base64url");
430
+ return `${payload}.${macOf(payload, key).toString("base64url")}`;
484
431
  }
485
- function decodePending(raw) {
486
- if (raw === void 0 || raw === "") {
432
+ function decodePending(raw, key) {
433
+ const dot = raw === void 0 ? -1 : raw.lastIndexOf(".");
434
+ if (raw === void 0 || dot <= 0) {
435
+ return null;
436
+ }
437
+ const payload = raw.slice(0, dot);
438
+ const mac = Buffer.from(raw.slice(dot + 1), "base64url");
439
+ const expected = macOf(payload, key);
440
+ if (mac.length !== expected.length || !timingSafeEqual(mac, expected)) {
487
441
  return null;
488
442
  }
489
443
  let value;
490
444
  try {
491
- value = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
445
+ value = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
492
446
  } catch {
493
447
  return null;
494
448
  }
@@ -564,7 +518,7 @@ async function issueSession(keys, userId, meta, ttlMs = DEFAULT_SESSION_TTL_MS)
564
518
  }
565
519
  async function liveSession(keys, token) {
566
520
  checkSessionKeys(keys, "liveSession");
567
- const payload = await verifyJWT(token, keys.jwtSecret);
521
+ const payload = await readVerifiedJWT(token, keys.jwtSecret);
568
522
  if (payload === null || typeof payload.userId !== "string" || payload.userId === "" || payload.sid === void 0) {
569
523
  return null;
570
524
  }
@@ -641,9 +595,9 @@ function warnOnCookieDomain(cookie, logger) {
641
595
  if (cookie.name !== void 0 || cookie.domain !== void 0 === transportsSeeDomain) {
642
596
  return;
643
597
  }
644
- const why = cookie.domain === void 0 ? 'cookie.domain is "" while COOKIE_DOMAIN is set, so over HTTPS the routes set __Host-session where socketAuth and the HTTP transport read "session" first' : 'cookie.domain is set and COOKIE_DOMAIN is not, so the routes set "session" on every request, which socketAuth and the HTTP transport read over HTTPS only under that name';
598
+ const why = cookie.domain === void 0 ? 'cookie.domain is "" while COOKIE_DOMAIN is set, so over HTTPS the routes set __Host-session, but socketAuth and the HTTP transport also take a "session" cookie there, which any site under COOKIE_DOMAIN can set for a browser that holds no __Host-session. Unset COOKIE_DOMAIN' : `cookie.domain is set and COOKIE_DOMAIN is not, so the routes set "session" on every request, which socketAuth and the HTTP transport read over HTTPS only under that name. Set COOKIE_DOMAIN to the cookie's domain instead`;
645
599
  logger.warn(
646
- `${OWNER}: ${why}. Set COOKIE_DOMAIN to the cookie's domain instead, or name the cookie in all three places: cookie.name, socketAuth's cookieName and createServer's http.cookieName.`,
600
+ `${OWNER}: ${why}, or name the cookie in all three places: cookie.name, socketAuth's cookieName and createServer's http.cookieName.`,
647
601
  { category: "quickdraw.auth" }
648
602
  );
649
603
  }
@@ -704,8 +658,9 @@ function routeSettings(options) {
704
658
  const cookie = cookieOf(options.cookie);
705
659
  const logger = options.logger ?? consoleLogger;
706
660
  const publicUrl = publicUrlOf(options.publicUrl);
661
+ const keys = checkSessionKeys(options, OWNER);
707
662
  const settings = Object.freeze({
708
- keys: checkSessionKeys(options, OWNER),
663
+ keys,
709
664
  origins: originAllowlist(options.allowedOrigins, OWNER),
710
665
  publicUrl,
711
666
  basePath: basePathOf(options.basePath),
@@ -716,6 +671,7 @@ function routeSettings(options) {
716
671
  onRevoke: options.onRevoke,
717
672
  logger,
718
673
  redeemed: redeemedStates(),
674
+ stateKey: oauthStateKey(keys.jwtSecret),
719
675
  watchHost: hostWatcher(publicUrl, logger)
720
676
  });
721
677
  warnOnCookieDomain(cookie, logger);
@@ -772,16 +728,25 @@ function setSession(res, settings, req, token, pageOrigin) {
772
728
  const from = fromPage(req, pageOrigin);
773
729
  const name = sessionCookieName(settings, from);
774
730
  res.cookie(name, token, sessionCookieSettings(settings, from, name, true));
731
+ if (settings.cookie.name === void 0) {
732
+ clearStaleHostSession(res, req, name, settings.cookie.sameSite);
733
+ }
775
734
  }
776
735
  function clearSession(res, settings, req) {
777
736
  const name = sessionCookieName(settings, req);
778
737
  res.clearCookie(name, sessionCookieSettings(settings, req, name, false));
738
+ if (settings.cookie.name === void 0) {
739
+ clearStaleHostSession(res, req, name, settings.cookie.sameSite);
740
+ }
741
+ }
742
+ function stateSecure(settings, req) {
743
+ return secureFor(settings, req) || settings.publicUrl.startsWith("https:");
779
744
  }
780
745
  function stateCookieName(settings, req) {
781
- return secureFor(settings, req) ? HOST_OAUTH_STATE_COOKIE : OAUTH_STATE_COOKIE;
746
+ return stateSecure(settings, req) ? HOST_OAUTH_STATE_COOKIE : OAUTH_STATE_COOKIE;
782
747
  }
783
748
  function stateCookieOf(settings, req, live = false) {
784
- const secure = secureFor(settings, req);
749
+ const secure = stateSecure(settings, req);
785
750
  return {
786
751
  httpOnly: true,
787
752
  secure,
@@ -800,6 +765,151 @@ function issueFor(settings, userId, provider, req) {
800
765
  return issueSession(settings.keys, userId, meta, settings.cookie.ttlMs);
801
766
  }
802
767
 
768
+ // src/server/auth/routes/socketAuth.ts
769
+ function handshakeOf(request) {
770
+ const { handshake } = request.socket;
771
+ return { headers: request.headers, secure: handshake?.secure === true };
772
+ }
773
+ function credentialOf(request, naming) {
774
+ if (request.transport === "http") {
775
+ const { sessionCookie } = request;
776
+ if (typeof sessionCookie === "string" && sessionCookie !== "") {
777
+ return { token: sessionCookie, checkOrigin: true };
778
+ }
779
+ }
780
+ const { token } = request.auth;
781
+ if (typeof token === "string" && token !== "") {
782
+ return { token, checkOrigin: request.transport === "http" && request.credential === "cookie" };
783
+ }
784
+ if (request.transport === "http") {
785
+ return null;
786
+ }
787
+ const names = sessionCookieNamesFor(handshakeOf(request), naming);
788
+ const cookie = cookieToken(cookiesOf({ headers: request.headers }), names);
789
+ return cookie === null ? null : { token: cookie, checkOrigin: true };
790
+ }
791
+ function originAccepted(headers, origins, allowMissing) {
792
+ const { origin } = headers;
793
+ if (origin !== void 0) {
794
+ return origins.allowed(origin) !== null;
795
+ }
796
+ return allowMissing || headers["sec-fetch-site"] === "same-origin";
797
+ }
798
+ function httpOriginAccepted(headers, origins) {
799
+ const { origin } = headers;
800
+ if (origin !== void 0) {
801
+ return origins.allowed(origin) !== null;
802
+ }
803
+ const site = headers["sec-fetch-site"];
804
+ return site === void 0 || site === "same-origin";
805
+ }
806
+ var compiled = /* @__PURE__ */ new WeakMap();
807
+ function cookieOriginAllowed(request, allowedOrigins, options = {}) {
808
+ let origins = compiled.get(allowedOrigins);
809
+ if (origins === void 0) {
810
+ origins = originAllowlist(allowedOrigins, "cookieOriginAllowed", true);
811
+ compiled.set(allowedOrigins, origins);
812
+ }
813
+ return request.transport === "socket" ? originAccepted(request.headers, origins, options.allowMissingOrigin === true) : httpOriginAccepted(request.headers, origins);
814
+ }
815
+ function httpCookieOriginAllowed(headers, origins) {
816
+ return httpOriginAccepted(headers, origins);
817
+ }
818
+ var COOKIE_REFUSED = "This page's origin may not use the session cookie; send it from an allowed origin or use a bearer token";
819
+ function checkCookieOrigin(request, origins, allowMissing) {
820
+ if (request.transport === "http") {
821
+ if (!httpOriginAccepted(request.headers, origins)) {
822
+ throw new QuickdrawError("FORBIDDEN", COOKIE_REFUSED);
823
+ }
824
+ return;
825
+ }
826
+ if (!originAccepted(request.headers, origins, allowMissing)) {
827
+ throw refused("This page's origin may not use the session cookie on a socket");
828
+ }
829
+ }
830
+ function refused(message) {
831
+ return new QuickdrawError("UNAUTHENTICATED", message);
832
+ }
833
+ function checkDevCredentials(devCredentials) {
834
+ if (devCredentials === void 0) {
835
+ return void 0;
836
+ }
837
+ if (typeof devCredentials !== "function") {
838
+ throw new TypeError("socketAuth: devCredentials must be a function of the user id");
839
+ }
840
+ if (!developmentSignInsAllowed()) {
841
+ throw new TypeError(
842
+ `socketAuth: devCredentials signs sockets in by a user id alone, so it runs only when NODE_ENV is development or test (NODE_ENV is ${nodeEnvLabel()}); set NODE_ENV=development to use it`
843
+ );
844
+ }
845
+ return devCredentials;
846
+ }
847
+ function devUserOf(request) {
848
+ const { token, userId } = request.auth;
849
+ const tokenless = token === void 0 || token === null || token === "";
850
+ return request.transport === "socket" && tokenless && typeof userId === "string" && userId !== "" ? userId : void 0;
851
+ }
852
+ async function devPrincipal(devCredentials, userId) {
853
+ if (!developmentSignInsAllowed()) {
854
+ throw refused("Development credentials are refused unless NODE_ENV is development or test");
855
+ }
856
+ const principal = await devCredentials(userId);
857
+ if (principal === null || principal === void 0) {
858
+ throw refused("No development user has that id");
859
+ }
860
+ if (principal.userId !== userId) {
861
+ throw new TypeError("socketAuth: devCredentials must return the principal of the user named");
862
+ }
863
+ return principal;
864
+ }
865
+ async function principalOfSession(session, loadPrincipal, owner) {
866
+ if (loadPrincipal === void 0) {
867
+ return { userId: session.userId, kind: "user" };
868
+ }
869
+ const principal = await loadPrincipal(session.userId, session);
870
+ if (principal === null || principal === void 0) {
871
+ throw refused("The session's user has no principal");
872
+ }
873
+ if (principal.userId !== session.userId) {
874
+ throw new TypeError(`${owner}: loadPrincipal must return the principal of the session's user`);
875
+ }
876
+ return principal;
877
+ }
878
+ function socketAuth(options) {
879
+ const keys = checkSessionKeys(options, "socketAuth");
880
+ const origins = originAllowlist(options.allowedOrigins, "socketAuth", true);
881
+ const naming = transportCookieNaming(options.cookieName);
882
+ const allowMissing = options.allowMissingOrigin === true;
883
+ const { loadPrincipal } = options;
884
+ if (loadPrincipal !== void 0 && typeof loadPrincipal !== "function") {
885
+ throw new TypeError("socketAuth: loadPrincipal must be a function");
886
+ }
887
+ const devCredentials = checkDevCredentials(options.devCredentials);
888
+ const authenticate = async (request) => {
889
+ const devUser = devCredentials === void 0 ? void 0 : devUserOf(request);
890
+ if (devCredentials !== void 0 && devUser !== void 0) {
891
+ return await devPrincipal(devCredentials, devUser);
892
+ }
893
+ const credential = credentialOf(request, naming);
894
+ if (credential === null) {
895
+ return null;
896
+ }
897
+ if (credential.checkOrigin) {
898
+ checkCookieOrigin(request, origins, allowMissing);
899
+ }
900
+ const session = await liveSession(keys, credential.token);
901
+ if (session === null) {
902
+ throw refused("The session is not valid");
903
+ }
904
+ if (request.transport === "socket") {
905
+ recordSocketSession(request.socket, session.id, session.expiresAt);
906
+ }
907
+ return await principalOfSession(session, loadPrincipal, "socketAuth");
908
+ };
909
+ markSocketAuth(authenticate, { devCredentials: devCredentials !== void 0 });
910
+ return authenticate;
911
+ }
912
+
803
913
  // src/server/auth/routes/guest.ts
804
914
  var GUEST_MAX_BODY_BYTES = 16384;
805
915
  function guest(options) {
@@ -832,7 +942,7 @@ async function createGuest(settings, provider, req, res) {
832
942
  }
833
943
  settings.logger.error("Creating a guest user failed", {
834
944
  category: "quickdraw.auth",
835
- error: error instanceof Error ? error.message : String(error)
945
+ error: errorText(error)
836
946
  });
837
947
  refuse(res, "INTERNAL", INTERNAL_MESSAGE);
838
948
  return null;
@@ -840,6 +950,10 @@ async function createGuest(settings, provider, req, res) {
840
950
  }
841
951
  function guestRoute(settings, provider) {
842
952
  return async (req, res) => {
953
+ if (!httpCookieOriginAllowed(req.headers, settings.origins)) {
954
+ refuse(res, "FORBIDDEN", COOKIE_REFUSED);
955
+ return;
956
+ }
843
957
  const user = await createGuest(settings, provider, req, res);
844
958
  if (user === null) {
845
959
  return;
@@ -880,7 +994,7 @@ async function loadLimiters(option) {
880
994
  if (option.signIn !== void 0 && option.session !== void 0 && option.providers !== void 0) {
881
995
  return option;
882
996
  }
883
- const presets = await import("../../rateLimit-ZMIZJUZF.js");
997
+ const presets = await import("../../rateLimit-5RKBFLUR.js");
884
998
  return {
885
999
  signIn: option.signIn ?? presets.createAuthLimiter({ max: SIGN_IN_LIMIT }),
886
1000
  session: option.session ?? presets.createAuthStatusLimiter(),
@@ -944,9 +1058,6 @@ function mockServes() {
944
1058
 
945
1059
  // src/server/auth/routes/oauth.ts
946
1060
  var CATEGORY = "quickdraw.auth";
947
- function messageOf(error) {
948
- return error instanceof Error ? error.message : String(error);
949
- }
950
1061
  function startRoute(settings, flow) {
951
1062
  return (req, res) => {
952
1063
  if (flow.enabled?.() === false) {
@@ -961,7 +1072,10 @@ function startRoute(settings, flow) {
961
1072
  }
962
1073
  const state = newState();
963
1074
  const location = flow.authorizeUrl(state, redirectUriOf(settings, flow.id));
964
- const pending = encodePending({ state, provider: flow.id, origin, issuedAt: Date.now() });
1075
+ const pending = encodePending(
1076
+ { state, provider: flow.id, origin, issuedAt: Date.now() },
1077
+ settings.stateKey
1078
+ );
965
1079
  res.cookie(stateCookieName(settings, req), pending, stateCookieOf(settings, req, true));
966
1080
  redirect(res, location);
967
1081
  };
@@ -974,7 +1088,7 @@ async function userFor(settings, flow, code) {
974
1088
  settings.logger.warn("A sign-in's code exchange failed", {
975
1089
  category: CATEGORY,
976
1090
  provider: flow.id,
977
- error: messageOf(error)
1091
+ error: errorText(error)
978
1092
  });
979
1093
  return { error: "failed" };
980
1094
  }
@@ -989,7 +1103,7 @@ async function userFor(settings, flow, code) {
989
1103
  settings.logger.error("onLogin failed", {
990
1104
  category: CATEGORY,
991
1105
  provider: flow.id,
992
- error: messageOf(error)
1106
+ error: errorText(error)
993
1107
  });
994
1108
  return { error: "failed" };
995
1109
  }
@@ -1012,7 +1126,7 @@ async function complete(settings, flow, req, res, origin) {
1012
1126
  } catch (error) {
1013
1127
  settings.logger.error("Creating a session failed", {
1014
1128
  category: CATEGORY,
1015
- error: messageOf(error)
1129
+ error: errorText(error)
1016
1130
  });
1017
1131
  redirect(res, landingOf(settings, origin, "failed"));
1018
1132
  return;
@@ -1028,7 +1142,7 @@ function callbackRoute(settings, flow) {
1028
1142
  return;
1029
1143
  }
1030
1144
  const name = stateCookieName(settings, req);
1031
- const pending = decodePending(cookiesOf(req)[name]);
1145
+ const pending = decodePending(cookiesOf(req)[name], settings.stateKey);
1032
1146
  res.clearCookie(name, stateCookieOf(settings, req));
1033
1147
  if (pending === null) {
1034
1148
  const { fallback } = settings.origins;
@@ -1060,13 +1174,24 @@ function callbackRoute(settings, flow) {
1060
1174
 
1061
1175
  // src/server/auth/routes/session.ts
1062
1176
  var NOT_SIGNED_IN = "Not signed in";
1063
- function sessionOf(settings, req) {
1064
- const token = tokenOf(req, sessionCookieNaming(settings));
1177
+ function tokenFor(settings, req, res) {
1178
+ const credential = httpCredentialOf(req, sessionCookieNaming(settings));
1179
+ if (credential?.from === "cookie" && !httpCookieOriginAllowed(req.headers, settings.origins)) {
1180
+ refuse(res, "FORBIDDEN", COOKIE_REFUSED);
1181
+ return void 0;
1182
+ }
1183
+ return credential?.token ?? null;
1184
+ }
1185
+ function sessionOf(settings, token) {
1065
1186
  return token === null ? Promise.resolve(null) : liveSession(settings.keys, token);
1066
1187
  }
1067
1188
  function meRoute(settings) {
1068
1189
  return async (req, res) => {
1069
- const session = await sessionOf(settings, req);
1190
+ const token = tokenFor(settings, req, res);
1191
+ if (token === void 0) {
1192
+ return;
1193
+ }
1194
+ const session = await sessionOf(settings, token);
1070
1195
  if (session === null) {
1071
1196
  refuse(res, "UNAUTHENTICATED", NOT_SIGNED_IN);
1072
1197
  return;
@@ -1081,14 +1206,17 @@ async function revoked(settings, userId, sessionId) {
1081
1206
  settings.logger.error("onRevoke failed", {
1082
1207
  category: "quickdraw.auth",
1083
1208
  userId,
1084
- error: error instanceof Error ? error.message : String(error)
1209
+ error: errorText(error)
1085
1210
  });
1086
1211
  }
1087
1212
  }
1088
1213
  function logoutRoute(settings) {
1089
1214
  return async (req, res) => {
1090
- const token = tokenOf(req, sessionCookieNaming(settings));
1091
- const payload = token === null ? null : await verifyJWT(token, settings.keys.jwtSecret);
1215
+ const token = tokenFor(settings, req, res);
1216
+ if (token === void 0) {
1217
+ return;
1218
+ }
1219
+ const payload = token === null ? null : await readVerifiedJWT(token, settings.keys.jwtSecret);
1092
1220
  if (payload?.sid !== void 0) {
1093
1221
  await settings.keys.sessions.revoke(payload.sid);
1094
1222
  settings.logger.info("Signed out", { category: "quickdraw.auth", userId: payload.userId });
@@ -1100,7 +1228,11 @@ function logoutRoute(settings) {
1100
1228
  }
1101
1229
  function logoutAllRoute(settings) {
1102
1230
  return async (req, res) => {
1103
- const session = await sessionOf(settings, req);
1231
+ const token = tokenFor(settings, req, res);
1232
+ if (token === void 0) {
1233
+ return;
1234
+ }
1235
+ const session = await sessionOf(settings, token);
1104
1236
  clearSession(res, settings, req);
1105
1237
  if (session === null) {
1106
1238
  refuse(res, "UNAUTHENTICATED", NOT_SIGNED_IN);
@@ -1187,7 +1319,7 @@ function routeTable(settings, listed) {
1187
1319
  const mounted = providers.filter((provider) => addProvider(table, settings, provider));
1188
1320
  if (mounted.length === 0) {
1189
1321
  settings.logger.warn(
1190
- "createAuthRoutes: no provider can sign anyone in (the mock is mounted only while ENABLE_MOCK_OAUTH is set outside production): every sign-in route answers 404",
1322
+ "createAuthRoutes: no provider can sign anyone in (the mock is mounted only while ENABLE_MOCK_OAUTH is set and NODE_ENV is development or test): every sign-in route answers 404",
1191
1323
  { category: "quickdraw.auth" }
1192
1324
  );
1193
1325
  }
@@ -1216,12 +1348,7 @@ async function serve(route, req, res) {
1216
1348
  }
1217
1349
  function dispatch(settings, limiters, route, req, res) {
1218
1350
  const fail = (error) => {
1219
- settings.logger.error("An auth route failed", {
1220
- category: "quickdraw.auth",
1221
- path: pathOf(req),
1222
- error: error instanceof Error ? error.message : String(error)
1223
- });
1224
- refuse(res, "INTERNAL", INTERNAL_MESSAGE);
1351
+ failRoute(settings.logger, req, res, error);
1225
1352
  };
1226
1353
  if (limiters === null) {
1227
1354
  refuse(res, "INTERNAL", "The auth routes' rate limiters are unavailable");
@@ -1270,151 +1397,28 @@ function createAuthRoutes(options) {
1270
1397
  return Object.assign(routes, { providers: () => servedProviders(mounted) });
1271
1398
  }
1272
1399
 
1273
- // src/server/auth/routes/socketAuth.ts
1274
- function handshakeOf(request) {
1275
- const { handshake } = request.socket;
1276
- return { headers: request.headers, secure: handshake?.secure === true };
1277
- }
1278
- function credentialOf(request, naming) {
1279
- const { token } = request.auth;
1280
- if (typeof token === "string" && token !== "") {
1281
- return { token, checkOrigin: request.transport === "http" && request.credential === "cookie" };
1282
- }
1283
- if (request.transport === "http") {
1284
- return null;
1285
- }
1286
- const names = sessionCookieNamesFor(handshakeOf(request), naming);
1287
- const cookie = cookieToken(cookiesOf({ headers: request.headers }), names);
1288
- return cookie === null ? null : { token: cookie, checkOrigin: true };
1289
- }
1290
- function originAccepted(headers, origins, allowMissing) {
1291
- const { origin } = headers;
1292
- if (origin !== void 0) {
1293
- return origins.allowed(origin) !== null;
1294
- }
1295
- return allowMissing || headers["sec-fetch-site"] === "same-origin";
1296
- }
1297
- function httpOriginAccepted(headers, origins) {
1298
- const { origin } = headers;
1299
- if (origin !== void 0) {
1300
- return origins.allowed(origin) !== null;
1301
- }
1302
- const site = headers["sec-fetch-site"];
1303
- return site === void 0 || site === "same-origin";
1304
- }
1305
- var compiled = /* @__PURE__ */ new WeakMap();
1306
- function cookieOriginAllowed(request, allowedOrigins, options = {}) {
1307
- let origins = compiled.get(allowedOrigins);
1308
- if (origins === void 0) {
1309
- origins = originAllowlist(allowedOrigins, "cookieOriginAllowed", true);
1310
- compiled.set(allowedOrigins, origins);
1311
- }
1312
- return request.transport === "socket" ? originAccepted(request.headers, origins, options.allowMissingOrigin === true) : httpOriginAccepted(request.headers, origins);
1313
- }
1314
- function httpCookieOriginAllowed(headers, origins) {
1315
- return httpOriginAccepted(headers, origins);
1316
- }
1317
- function checkCookieOrigin(request, origins, allowMissing) {
1318
- if (request.transport === "http") {
1319
- if (!httpOriginAccepted(request.headers, origins)) {
1320
- throw new QuickdrawError(
1321
- "FORBIDDEN",
1322
- "This page's origin may not use the session cookie; send it from an allowed origin or use a bearer token"
1323
- );
1324
- }
1325
- return;
1326
- }
1327
- if (!originAccepted(request.headers, origins, allowMissing)) {
1328
- throw refused("This page's origin may not use the session cookie on a socket");
1329
- }
1330
- }
1331
- function refused(message) {
1332
- return new QuickdrawError("UNAUTHENTICATED", message);
1333
- }
1334
- function production() {
1335
- return process.env.NODE_ENV === "production";
1336
- }
1337
- function checkDevCredentials(devCredentials) {
1338
- if (devCredentials === void 0) {
1339
- return void 0;
1340
- }
1341
- if (typeof devCredentials !== "function") {
1342
- throw new TypeError("socketAuth: devCredentials must be a function of the user id");
1343
- }
1344
- if (production()) {
1345
- throw new TypeError(
1346
- "socketAuth: devCredentials signs sockets in by a user id alone, so it cannot be used when NODE_ENV is production"
1347
- );
1348
- }
1349
- return devCredentials;
1350
- }
1351
- function devUserOf(request) {
1352
- const { token, userId } = request.auth;
1353
- const tokenless = token === void 0 || token === null || token === "";
1354
- return request.transport === "socket" && tokenless && typeof userId === "string" && userId !== "" ? userId : void 0;
1355
- }
1356
- async function devPrincipal(devCredentials, userId) {
1357
- if (production()) {
1358
- throw refused("Development credentials are refused in production");
1359
- }
1360
- const principal = await devCredentials(userId);
1361
- if (principal === null || principal === void 0) {
1362
- throw refused("No development user has that id");
1363
- }
1364
- if (principal.userId !== userId) {
1365
- throw new TypeError("socketAuth: devCredentials must return the principal of the user named");
1366
- }
1367
- return principal;
1368
- }
1369
- function socketAuth(options) {
1370
- const keys = checkSessionKeys(options, "socketAuth");
1371
- const origins = originAllowlist(options.allowedOrigins, "socketAuth", true);
1372
- const naming = transportCookieNaming(options.cookieName);
1373
- const allowMissing = options.allowMissingOrigin === true;
1400
+ // src/server/auth/routes/bearerAuth.ts
1401
+ function bearerAuth(options) {
1402
+ const keys = checkSessionKeys(options, "bearerAuth");
1374
1403
  const { loadPrincipal } = options;
1375
1404
  if (loadPrincipal !== void 0 && typeof loadPrincipal !== "function") {
1376
- throw new TypeError("socketAuth: loadPrincipal must be a function");
1405
+ throw new TypeError("bearerAuth: loadPrincipal must be a function");
1377
1406
  }
1378
- const devCredentials = checkDevCredentials(options.devCredentials);
1379
- return async (request) => {
1380
- const devUser = devCredentials === void 0 ? void 0 : devUserOf(request);
1381
- if (devCredentials !== void 0 && devUser !== void 0) {
1382
- return await devPrincipal(devCredentials, devUser);
1383
- }
1384
- const credential = credentialOf(request, naming);
1385
- if (credential === null) {
1407
+ return async (token) => {
1408
+ if (typeof token !== "string" || token === "") {
1386
1409
  return null;
1387
1410
  }
1388
- if (credential.checkOrigin) {
1389
- checkCookieOrigin(request, origins, allowMissing);
1390
- }
1391
- const session = await liveSession(keys, credential.token);
1411
+ const session = await liveSession(keys, token);
1392
1412
  if (session === null) {
1393
- throw refused("The session is not valid");
1394
- }
1395
- if (request.transport === "socket") {
1396
- recordSocketSession(request.socket, session.id);
1397
- }
1398
- if (loadPrincipal === void 0) {
1399
- return { userId: session.userId, kind: "user" };
1413
+ throw new QuickdrawError("UNAUTHENTICATED", "The session is not valid");
1400
1414
  }
1401
- const principal = await loadPrincipal(session.userId, session);
1402
- if (principal === null || principal === void 0) {
1403
- throw refused("The session's user has no principal");
1404
- }
1405
- if (principal.userId !== session.userId) {
1406
- throw new TypeError(
1407
- "socketAuth: loadPrincipal must return the principal of the session's user"
1408
- );
1409
- }
1410
- return principal;
1415
+ return await principalOfSession(session, loadPrincipal, "bearerAuth");
1411
1416
  };
1412
1417
  }
1413
1418
 
1414
1419
  // src/server/auth/routes/requireSession.ts
1415
1420
  var SESSIONS = /* @__PURE__ */ new WeakMap();
1416
1421
  var NO_ORIGINS = originAllowlist([], "requireSession", true);
1417
- var COOKIE_REFUSED = "This page's origin may not use the session cookie; send it from an allowed origin or use a bearer token";
1418
1422
  var NO_LIST = "requireSession has no origin list, so no page may use the session cookie: pass it the sessions object createAuthRoutes was given (the same store object: a second store over the same table is another), or pass allowedOrigins";
1419
1423
  function requireSession(keys, options = {}) {
1420
1424
  checkSessionKeys(keys, "requireSession");
@@ -1625,6 +1629,7 @@ export {
1625
1629
  OAUTH_STATE_COOKIE,
1626
1630
  OAUTH_STATE_TTL_MS,
1627
1631
  SESSION_COOKIE,
1632
+ bearerAuth,
1628
1633
  clearSessionCookie,
1629
1634
  cookieOriginAllowed,
1630
1635
  createAuthRoutes,