@rebasepro/server 0.14.0 → 0.14.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 (58) hide show
  1. package/dist/api/rest/query-parser.d.ts +37 -1
  2. package/dist/api/rest/write-validation.d.ts +26 -0
  3. package/dist/auth/index.d.ts +3 -1
  4. package/dist/auth/interfaces.d.ts +14 -1
  5. package/dist/auth/jwks-routes.d.ts +17 -0
  6. package/dist/auth/jwt-keys.d.ts +108 -0
  7. package/dist/auth/jwt.d.ts +62 -2
  8. package/dist/{auth-CYoPVf-E.js → auth-BobZVd0j.js} +142 -167
  9. package/dist/auth-BobZVd0j.js.map +1 -0
  10. package/dist/boot/boot.d.ts +36 -50
  11. package/dist/boot/ddl-bootstrap.d.ts +15 -0
  12. package/dist/boot/env.d.ts +20 -0
  13. package/dist/boot/provision.d.ts +182 -0
  14. package/dist/boot/role.d.ts +88 -0
  15. package/dist/{cron-store-Dvr4Y1sZ.js → cron-store-CB1x-Ken.js} +3 -3
  16. package/dist/{cron-store-Dvr4Y1sZ.js.map → cron-store-CB1x-Ken.js.map} +1 -1
  17. package/dist/{ddl-bootstrap-BhXbTnBl.js → ddl-bootstrap-Cywoj8Ta.js} +40 -2
  18. package/dist/ddl-bootstrap-Cywoj8Ta.js.map +1 -0
  19. package/dist/env.d.ts +2 -0
  20. package/dist/functions/proxy.d.ts +41 -0
  21. package/dist/functions/selection.d.ts +45 -0
  22. package/dist/index.d.ts +8 -3
  23. package/dist/index.es.js +1113 -661
  24. package/dist/index.es.js.map +1 -1
  25. package/dist/init/shutdown.d.ts +4 -0
  26. package/dist/init/surfaces.d.ts +79 -0
  27. package/dist/init.d.ts +121 -1
  28. package/dist/jobs/index.d.ts +5 -0
  29. package/dist/jobs/job-queue.d.ts +14 -0
  30. package/dist/jobs/job-store.d.ts +22 -0
  31. package/dist/jobs/types.d.ts +125 -0
  32. package/dist/jobs-DR4SjGrD.js +326 -0
  33. package/dist/jobs-DR4SjGrD.js.map +1 -0
  34. package/dist/{jwt-_IFqfTOg.js → jwt-VJyXTdQQ.js} +447 -11
  35. package/dist/jwt-VJyXTdQQ.js.map +1 -0
  36. package/dist/{openapi-generator-DPKtUC9X.js → openapi-generator-DQeQ_q2f.js} +68 -3
  37. package/dist/openapi-generator-DQeQ_q2f.js.map +1 -0
  38. package/dist/proxy-Bj5DVllb.js +139 -0
  39. package/dist/proxy-Bj5DVllb.js.map +1 -0
  40. package/dist/{request-timeout-RivJsME0.js → request-timeout-BuFoEKwT.js} +6 -3
  41. package/dist/request-timeout-BuFoEKwT.js.map +1 -0
  42. package/dist/selection-_z6TM1DB.js +64 -0
  43. package/dist/selection-_z6TM1DB.js.map +1 -0
  44. package/dist/services/webhook-service.d.ts +43 -5
  45. package/dist/{src-C7rkDGxA.js → src-8XDWyDfR.js} +84 -13
  46. package/dist/src-8XDWyDfR.js.map +1 -0
  47. package/dist/src-Cz9nMgUR.js.map +1 -1
  48. package/dist/storage/keys.d.ts +17 -0
  49. package/dist/storage/routes.d.ts +1 -1
  50. package/dist/storage/storage-registry.d.ts +46 -4
  51. package/dist/storage/tus-handler.d.ts +1 -1
  52. package/package.json +5 -5
  53. package/dist/auth-CYoPVf-E.js.map +0 -1
  54. package/dist/ddl-bootstrap-BhXbTnBl.js.map +0 -1
  55. package/dist/jwt-_IFqfTOg.js.map +0 -1
  56. package/dist/openapi-generator-DPKtUC9X.js.map +0 -1
  57. package/dist/request-timeout-RivJsME0.js.map +0 -1
  58. package/dist/src-C7rkDGxA.js.map +0 -1
@@ -2,17 +2,16 @@ import { createRequire as __createRequire } from "module";
2
2
  import process from "process";
3
3
  __createRequire(import.meta.url);
4
4
  import { i as __toESM, n as __exportAll } from "./rolldown-runtime-DSJWtz9O.js";
5
- import { C as resolveClientListLimit, T as isAnonymousUid, a as deserializeLogicalCondition, i as deserializeFilter, r as UnknownFilterOperatorError, w as ANONYMOUS_USER_ID, x as ListLimitError } from "./src-C7rkDGxA.js";
5
+ import { C as ListLimitError, D as isAnonymousUid, E as ANONYMOUS_USER_ID, T as resolveClientListLimit, a as deserializeLogicalCondition, i as deserializeFilter, r as UnknownFilterOperatorError } from "./src-8XDWyDfR.js";
6
6
  import "./src-Cz9nMgUR.js";
7
- import { i as isSQLAdmin, r as revokeInternalTableSql, t as createDdlBootstrapper } from "./ddl-bootstrap-BhXbTnBl.js";
7
+ import { n as createDdlBootstrapper, o as revokeInternalTableSql, s as isSQLAdmin } from "./ddl-bootstrap-Cywoj8Ta.js";
8
+ import { S as canonicalStorageId, T as require_jsonwebtoken, _ as verifyMfaPendingToken, a as generateMfaPendingToken, c as getJwks, d as hasAsymmetricSigningKey, f as hashRefreshToken, g as verifyDownloadToken, h as verifyAccessToken, i as generateDownloadToken, l as getRefreshTokenExpiry, n as configureJwt, o as generateRefreshToken, p as isJwtConfigured, r as generateAccessToken, s as getAccessTokenExpiry, t as MAX_COOKIE_AGE_MS, u as getRefreshTokenTtlMs, w as tryCanonicalStorageKey } from "./jwt-VJyXTdQQ.js";
8
9
  import { t as logger } from "./logger-DfvF_8r-.js";
9
10
  import { n as errorHandler, t as ApiError } from "./errors-EBYiaJ2E.js";
10
- import { a as generateMfaPendingToken, c as getRefreshTokenExpiry, d as isJwtConfigured, g as require_jsonwebtoken, h as verifyMfaPendingToken, i as generateDownloadToken, l as getRefreshTokenTtlMs, m as verifyDownloadToken, n as configureJwt, o as generateRefreshToken, p as verifyAccessToken, r as generateAccessToken, s as getAccessTokenExpiry, t as MAX_COOKIE_AGE_MS, u as hashRefreshToken } from "./jwt-_IFqfTOg.js";
11
11
  import { createHash, randomBytes, randomInt } from "node:crypto";
12
12
  import { Hono } from "hono";
13
13
  import { promisify } from "util";
14
14
  import { createCipheriv, createDecipheriv, createHash as createHash$1, createHmac, createPublicKey, randomBytes as randomBytes$1, randomUUID as randomUUID$1, scrypt, timingSafeEqual } from "crypto";
15
- import path from "node:path";
16
15
  import { getConnInfo } from "@hono/node-server/conninfo";
17
16
  //#region ../types/src/controllers/storage.ts
18
17
  /**
@@ -172,9 +171,68 @@ function parseWhereParam(raw) {
172
171
  const filter = decodeFilter(parsed);
173
172
  return Object.keys(filter).length > 0 ? filter : void 0;
174
173
  }
174
+ /**
175
+ * The parsed entries as the driver contract spells them: `[field, direction]`
176
+ * tuples in order of significance.
177
+ *
178
+ * The REST layer used to hand the driver `orderBy[0].field` and drop the rest,
179
+ * so `?orderBy=[{"field":"roles"},{"field":"created_at","direction":"desc"}]`
180
+ * — a shape this parser has always accepted and validated in full — sorted by
181
+ * `roles` alone and returned the ties in whatever order Postgres pleased.
182
+ */
183
+ function orderByEntriesToTuples(entries) {
184
+ if (!entries || entries.length === 0) return void 0;
185
+ return entries.map(({ field, direction }) => [field, direction]);
186
+ }
175
187
  function invalidOrderBy(detail) {
176
188
  throw invalidParam(`Invalid \`orderBy\` parameter: ${detail}. Expected \`field\`, \`field:desc\`, or a JSON array like [{"field":"created_at","direction":"desc"}]`, "INVALID_ORDER_BY");
177
189
  }
190
+ /** The aggregate functions `?select=` accepts. */
191
+ var AGGREGATE_FUNCTIONS = /* @__PURE__ */ new Set([
192
+ "count",
193
+ "sum",
194
+ "avg",
195
+ "min",
196
+ "max"
197
+ ]);
198
+ /**
199
+ * Parse `?select=count(),sum(total),avg(total)`.
200
+ *
201
+ * The spelling is SQL's, because whoever writes it is thinking in SQL and
202
+ * because any other spelling has to be learned first. `count()` with no field
203
+ * counts rows; every other function names a column.
204
+ *
205
+ * Aliases are derived rather than accepted: `sum(total)` returns as
206
+ * `sum_total`, `count()` as `count`. Letting a caller choose would mean
207
+ * checking their alias is not also a `groupBy` field — a rule nobody would
208
+ * guess, and a silently overwritten value if it went unchecked.
209
+ */
210
+ function parseAggregateSelect(raw) {
211
+ const value = getLastValue(raw);
212
+ if (!value) return void 0;
213
+ const entries = String(value).split(",").map((s) => s.trim()).filter(Boolean);
214
+ if (entries.length === 0) return void 0;
215
+ return entries.map((entry) => {
216
+ const match = /^([a-z]+)\(\s*([A-Za-z0-9_]*)\s*\)$/i.exec(entry);
217
+ if (!match) throw invalidParam(`Invalid \`select\` entry "${entry}". Expected \`fn(field)\`, e.g. \`sum(total)\` or \`count()\`.`, "INVALID_AGGREGATE_SELECT");
218
+ const fn = match[1].toLowerCase();
219
+ const field = match[2] || void 0;
220
+ if (!AGGREGATE_FUNCTIONS.has(fn)) throw invalidParam(`Unknown aggregate function "${fn}". Expected: ${[...AGGREGATE_FUNCTIONS].join(", ")}.`, "INVALID_AGGREGATE_FUNCTION");
221
+ if (fn !== "count" && !field) throw invalidParam(`\`${fn}()\` needs a field, e.g. \`${fn}(total)\`. Only \`count()\` may be empty.`, "INVALID_AGGREGATE_SELECT");
222
+ return {
223
+ fn,
224
+ field,
225
+ alias: field ? `${fn}_${field}` : fn
226
+ };
227
+ });
228
+ }
229
+ /** Parse `?groupBy=status,country`. */
230
+ function parseGroupBy(raw) {
231
+ const value = getLastValue(raw);
232
+ if (!value) return void 0;
233
+ const fields = String(value).split(",").map((s) => s.trim()).filter(Boolean);
234
+ return fields.length > 0 ? fields : void 0;
235
+ }
178
236
  /** `asc`/`desc`, in any case. Anything else is a request to sort in a way that does not exist. */
179
237
  function toDirection(raw, context) {
180
238
  if (raw === void 0 || raw === null) return "asc";
@@ -299,7 +357,9 @@ function parseQueryOptions(query, limits = {}) {
299
357
  "vector_threshold",
300
358
  "or",
301
359
  "and",
302
- "where"
360
+ "where",
361
+ "select",
362
+ "groupBy"
303
363
  ];
304
364
  const filterDict = {};
305
365
  for (const [key, rawValue] of Object.entries(query)) {
@@ -832,137 +892,6 @@ function createApiKeyPreAuth(options) {
832
892
  };
833
893
  }
834
894
  //#endregion
835
- //#region src/storage/keys.ts
836
- /**
837
- * Canonical storage keys and bucket names.
838
- *
839
- * Storage is not under RLS, so a `storageAuthorize` hook is the whole access
840
- * control model — and a hook can only be correct if the key it is shown is the
841
- * key that is written. That is the invariant this module exists to hold: one
842
- * canonical string, computed once per request, handed to the hook, to the
843
- * controller, and to the download token alike.
844
- *
845
- * ## Why rejecting beats stripping
846
- *
847
- * The previous `sanitizeStorageKey` *stripped* `../` in a single pass. Two
848
- * things were wrong with that, and only one of them was the obvious one.
849
- *
850
- * The obvious one: a single pass is not a fixed point. `....//` contains `../`
851
- * at offset 2, so removing it leaves `../` behind — the sanitizer manufactured
852
- * the traversal it was there to remove. `users/alice/....//bob/x` came out as
853
- * `users/alice/../bob/x`, which a prefix hook reads as alice's (it starts with
854
- * `users/alice/`) and the filesystem reads as bob's. The hook approved one
855
- * object and the controller wrote another.
856
- *
857
- * The subtler one, and the reason this is a rewrite rather than a loop: even a
858
- * correct strip is a silent rewrite. A caller who asks to store at `a/../b` and
859
- * gets an object at `a/b` was not protected, they were misled — and every later
860
- * read, ownership row and audit line now refers to a path nobody chose. So a key
861
- * that means something other than what it says is refused (400), not repaired.
862
- *
863
- * Note what is NOT traversal under this rule: `....` is an ordinary directory
864
- * name, and `users/alice/....//bob/x` canonicalizes to
865
- * `users/alice/..../bob/x` — still comfortably inside alice's prefix, which is
866
- * exactly right. Only a real `..` segment is refused.
867
- */
868
- /** Longest key accepted, in UTF-16 code units. Matches the previous cap. */
869
- var MAX_STORAGE_KEY_LENGTH = 1024;
870
- /**
871
- * A key that cannot be canonicalized. Carries no path back to the caller
872
- * beyond what they sent, so it is safe to surface as a 400 message.
873
- */
874
- var InvalidStorageKeyError = class extends Error {
875
- constructor(message) {
876
- super(message);
877
- this.name = "InvalidStorageKeyError";
878
- }
879
- };
880
- /**
881
- * Canonicalize a caller-supplied storage key, or throw
882
- * {@link InvalidStorageKeyError}.
883
- *
884
- * Normalizations applied (safe, idempotent, and meaning-preserving):
885
- * - leading slashes removed — `/a/b` and `a/b` name the same object
886
- * - `.` segments and repeated slashes collapsed
887
- *
888
- * Refusals (the key means something other than what it says):
889
- * - any `..` segment, on either separator, at any depth
890
- * - null bytes
891
- * - keys longer than {@link MAX_STORAGE_KEY_LENGTH}
892
- *
893
- * A trailing slash is preserved: it is how the folder route marks a prefix.
894
- */
895
- function canonicalStorageKey(rawKey) {
896
- if (rawKey.includes("\0")) throw new InvalidStorageKeyError("Storage key contains a null byte.");
897
- if (rawKey.length > 1024) throw new InvalidStorageKeyError(`Storage key exceeds the maximum length of ${MAX_STORAGE_KEY_LENGTH} characters.`);
898
- if (rawKey.split(/[\\/]/).some((segment) => segment === "..")) throw new InvalidStorageKeyError("Storage key contains a '..' path segment. Keys must name an object directly.");
899
- const withoutLeadingSlashes = rawKey.replace(/^\/+/, "");
900
- if (withoutLeadingSlashes === "") return "";
901
- const denotesDirectory = /(?:^|\/)\.?$/.test(withoutLeadingSlashes);
902
- const normalized = path.posix.normalize(withoutLeadingSlashes);
903
- if (normalized === "." || normalized === "./") return "";
904
- const key = normalized.replace(/^\.\//, "").replace(/^\/+/, "");
905
- if (key === "") return "";
906
- return denotesDirectory && !key.endsWith("/") ? `${key}/` : key;
907
- }
908
- /**
909
- * Canonicalize, or return `null` when the key is not canonicalizable.
910
- *
911
- * For callers that must fail closed without an exception — the download-token
912
- * middleware compares a request path against a granted path, and a key it
913
- * cannot canonicalize simply matches nothing.
914
- */
915
- function tryCanonicalStorageKey(rawKey) {
916
- try {
917
- return canonicalStorageKey(rawKey);
918
- } catch {
919
- return null;
920
- }
921
- }
922
- /** A bucket name that does not name a bucket. See {@link canonicalStorageBucket}. */
923
- var InvalidStorageBucketError = class extends Error {
924
- constructor(message) {
925
- super(message);
926
- this.name = "InvalidStorageBucketError";
927
- }
928
- };
929
- /**
930
- * One path segment: letters, digits, `.`, `_`, `-`, first character
931
- * alphanumeric. Deliberately narrow — it is the intersection of what S3, GCS
932
- * and a filesystem directory all accept, and it makes `..`, `.tus-uploads`,
933
- * absolute paths and anything containing a separator unrepresentable.
934
- */
935
- var STORAGE_BUCKET_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
936
- /**
937
- * Canonicalize a caller-supplied bucket name, or throw
938
- * {@link InvalidStorageBucketError}.
939
- *
940
- * The bucket is the *other* caller-controlled routing value in an upload
941
- * request, and it was the one nobody validated. `LocalStorageController`
942
- * builds `join(basePath, bucket)` and then checks containment against that
943
- * result, so a bucket of `../../etc` moved the boundary rather than crossing
944
- * it — the guard passed because the guard's reference point was the attacker's.
945
- * The containment check now resolves against the storage root, and this is the
946
- * check at the route boundary that stops the value before it gets there.
947
- *
948
- * A bucket is configuration, not user data: there is no legitimate caller that
949
- * needs a separator, a leading dot, or a `..` in one. So this refuses rather
950
- * than repairs, exactly as {@link canonicalStorageKey} does — a rewritten
951
- * bucket would silently store the object somewhere the caller did not ask for.
952
- *
953
- * Returns `undefined` when the caller named no bucket (absent, or an empty
954
- * form field), which is how every controller spells "use my default". An empty
955
- * string used to reach `getFullPath` and resolve to the storage *root* rather
956
- * than the `default` bucket, which is a third place a bare key did not
957
- * round-trip.
958
- */
959
- function canonicalStorageBucket(rawBucket) {
960
- if (rawBucket === void 0 || rawBucket === null || rawBucket === "") return void 0;
961
- if (rawBucket.length > 63) throw new InvalidStorageBucketError(`Storage bucket exceeds the maximum length of 63 characters.`);
962
- if (!STORAGE_BUCKET_PATTERN.test(rawBucket)) throw new InvalidStorageBucketError("Storage bucket must be a single name of letters, digits, '.', '_' or '-', starting with a letter or digit.");
963
- return rawBucket;
964
- }
965
- //#endregion
966
895
  //#region src/auth/middleware.ts
967
896
  /**
968
897
  * Hono middleware that requires a valid JWT token via Authorization header.
@@ -1326,25 +1255,49 @@ var fileTokenAuth = async (c, next) => {
1326
1255
  resolvedPath: tryCanonicalStorageKey(filePath)
1327
1256
  };
1328
1257
  };
1258
+ /**
1259
+ * Decide what a valid download token entitles this request to.
1260
+ *
1261
+ * One function rather than a copy per token location: a Bearer token and a
1262
+ * `?token=` are the same grant presented two ways, and the whole failure
1263
+ * this guards against is two places disagreeing about what was granted.
1264
+ *
1265
+ * `"no-path"` is distinct from the two denials because the caller treats it
1266
+ * differently — a request with no object path at all is not a denied read,
1267
+ * it is a request this middleware has nothing to say about, and on
1268
+ * `/metadata/*` it must still reach the downstream auth gate.
1269
+ */
1270
+ const evaluateGrant = (payload) => {
1271
+ if (canonicalStorageId(c.req.query("storageId")) !== payload.storageId) return "deny-storage";
1272
+ const rawPath = extractWildcard(c);
1273
+ if (!rawPath) return "no-path";
1274
+ const { bucket, resolvedPath } = parseBucketPath(decodeURIComponent(rawPath));
1275
+ if (resolvedPath === null) return "deny-path";
1276
+ return isPathMatch(`${bucket}/${resolvedPath}`, payload.path) ? "grant" : "deny-path";
1277
+ };
1278
+ /**
1279
+ * Which half of the scope failed is named in the message. The holder of the
1280
+ * token already knows both values, so this reveals nothing to an attacker —
1281
+ * and without it, a client that forgets to forward `?storageId=` on the file
1282
+ * URL reports a "path mismatch" for a path that matches perfectly.
1283
+ */
1284
+ const denyMismatch = (outcome) => c.json({ error: {
1285
+ message: outcome === "deny-storage" ? "Forbidden: Scoped token storage mismatch" : "Forbidden: Scoped token path mismatch",
1286
+ code: "FORBIDDEN"
1287
+ } }, 403);
1329
1288
  const bearerToken = extractBearerToken(authHeader);
1330
1289
  if (bearerToken !== void 0) {
1331
1290
  const payload = verifyDownloadToken(bearerToken);
1332
1291
  if (payload) {
1333
- const rawPath = extractWildcard(c);
1334
- if (rawPath) {
1335
- const { bucket, resolvedPath } = parseBucketPath(decodeURIComponent(rawPath));
1336
- const requestedFullPath = resolvedPath === null ? null : `${bucket}/${resolvedPath}`;
1337
- if (requestedFullPath !== null && isPathMatch(requestedFullPath, payload.path)) {
1338
- c.set("user", {
1339
- uid: "download-token",
1340
- roles: ["reader"]
1341
- });
1342
- return next();
1343
- } else return c.json({ error: {
1344
- message: "Forbidden: Scoped token path mismatch",
1345
- code: "FORBIDDEN"
1346
- } }, 403);
1292
+ const outcome = evaluateGrant(payload);
1293
+ if (outcome === "grant") {
1294
+ c.set("user", {
1295
+ uid: "download-token",
1296
+ roles: ["reader"]
1297
+ });
1298
+ return next();
1347
1299
  }
1300
+ if (outcome !== "no-path") return denyMismatch(outcome);
1348
1301
  }
1349
1302
  if (isFileRoute) return c.json({ error: {
1350
1303
  message: "Unauthorized: Access JWT not allowed on file routes",
@@ -1355,21 +1308,15 @@ var fileTokenAuth = async (c, next) => {
1355
1308
  if (queryToken) {
1356
1309
  const payload = verifyDownloadToken(queryToken);
1357
1310
  if (payload) {
1358
- const rawPath = extractWildcard(c);
1359
- if (rawPath) {
1360
- const { bucket, resolvedPath } = parseBucketPath(decodeURIComponent(rawPath));
1361
- const requestedFullPath = resolvedPath === null ? null : `${bucket}/${resolvedPath}`;
1362
- if (requestedFullPath !== null && isPathMatch(requestedFullPath, payload.path)) {
1363
- c.set("user", {
1364
- uid: "download-token",
1365
- roles: ["reader"]
1366
- });
1367
- return next();
1368
- } else return c.json({ error: {
1369
- message: "Forbidden: Scoped token path mismatch",
1370
- code: "FORBIDDEN"
1371
- } }, 403);
1311
+ const outcome = evaluateGrant(payload);
1312
+ if (outcome === "grant") {
1313
+ c.set("user", {
1314
+ uid: "download-token",
1315
+ roles: ["reader"]
1316
+ });
1317
+ return next();
1372
1318
  }
1319
+ if (outcome !== "no-path") return denyMismatch(outcome);
1373
1320
  }
1374
1321
  return c.json({ error: {
1375
1322
  message: "Unauthorized: Invalid or unauthorized token",
@@ -7419,7 +7366,7 @@ function mountSessionRoutes(opts) {
7419
7366
  clearRefreshCookie(c, config.cookieAuth);
7420
7367
  const accessToken = extractBearerToken(c.req.header("authorization"));
7421
7368
  if (ops.afterLogout && accessToken !== void 0) {
7422
- const { verifyAccessToken } = await import("./jwt-_IFqfTOg.js").then((n) => n.f);
7369
+ const { verifyAccessToken } = await import("./jwt-VJyXTdQQ.js").then((n) => n.m);
7423
7370
  const payload = verifyAccessToken(accessToken);
7424
7371
  if (payload) ops.afterLogout(payload.uid).catch((err) => {
7425
7372
  logger.error("[AuthHooks] afterLogout error", { error: err instanceof Error ? err.message : err });
@@ -9049,6 +8996,31 @@ function toAuthUserData(user) {
9049
8996
  };
9050
8997
  }
9051
8998
  //#endregion
8999
+ //#region src/auth/jwks-routes.ts
9000
+ /**
9001
+ * `GET /.well-known/jwks.json` — the public keys that verify this issuer's
9002
+ * access tokens.
9003
+ *
9004
+ * Deliberately unauthenticated and world-readable: the whole point is that a
9005
+ * gateway, an edge function or a neighbouring service can check a Rebase token
9006
+ * without being trusted with anything. Public keys are not a secret, and
9007
+ * `jwt-keys.ts` derives what is served here from the public half of each pair.
9008
+ *
9009
+ * Mounted at the root rather than under `basePath`, because `/.well-known/` is
9010
+ * where every verifier looks — an issuer of `https://api.example.com` implies
9011
+ * `https://api.example.com/.well-known/jwks.json`, whatever the API happens to
9012
+ * be prefixed with.
9013
+ */
9014
+ function createJwksRoutes() {
9015
+ const router = new Hono();
9016
+ router.get("/jwks.json", (c) => {
9017
+ const jwks = getJwks();
9018
+ c.header("Cache-Control", "public, max-age=300");
9019
+ return c.json(jwks);
9020
+ });
9021
+ return router;
9022
+ }
9023
+ //#endregion
9052
9024
  //#region src/auth/oauth-code-flow.ts
9053
9025
  /**
9054
9026
  * Build the request schema for an authorization-code provider.
@@ -10478,6 +10450,7 @@ var auth_exports = /* @__PURE__ */ __exportAll({
10478
10450
  createGitHubProvider: () => createGitHubProvider,
10479
10451
  createGitLabProvider: () => createGitLabProvider,
10480
10452
  createGoogleProvider: () => createGoogleProvider,
10453
+ createJwksRoutes: () => createJwksRoutes,
10481
10454
  createLinkedinProvider: () => createLinkedinProvider,
10482
10455
  createMicrosoftProvider: () => createMicrosoftProvider,
10483
10456
  createRateLimiter: () => createRateLimiter,
@@ -10496,7 +10469,9 @@ var auth_exports = /* @__PURE__ */ __exportAll({
10496
10469
  generateSecurePassword: () => generateSecurePassword,
10497
10470
  generateSecureToken: () => generateSecureToken,
10498
10471
  getAccessTokenExpiry: () => getAccessTokenExpiry,
10472
+ getJwks: () => getJwks,
10499
10473
  getRefreshTokenExpiry: () => getRefreshTokenExpiry,
10474
+ hasAsymmetricSigningKey: () => hasAsymmetricSigningKey,
10500
10475
  hashPassword: () => hashPassword,
10501
10476
  hashRefreshToken: () => hashRefreshToken,
10502
10477
  hashToken: () => hashToken,
@@ -10526,6 +10501,6 @@ var auth_exports = /* @__PURE__ */ __exportAll({
10526
10501
  verifyPassword: () => verifyPassword
10527
10502
  });
10528
10503
  //#endregion
10529
- export { fileTokenAuth as $, MemoryRateLimitStore as A, getEmailVerificationTemplate as B, ZodNumber as C, isPublicStoragePath as Ct, _coercedNumber as D, string as E, resolveAuthHooks as F, RawHtml as G, getPasswordResetTemplate as H, hashPassword as I, raw as J, escapeHtml as K, validatePasswordStrength as L, createEmailService as M, assertEmailLinkBases as N, DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT as O, resolveEmailLinkBase as P, extractUserFromToken as Q, verifyPassword as R, createBuiltinAuthAdapter as S, PUBLIC_STORAGE_PREFIX as St, object as T, getUserInvitationTemplate as U, getMagicLinkTemplate as V, getWelcomeEmailTemplate as W, createAuthMiddleware as X, createAdapterAuthMiddleware as Y, createRequireAuth as Z, createLinkedinProvider as _, scopeDataDriver as _t, createSpotifyProvider as a, InvalidStorageBucketError as at, pkceTokenParams as b, parseQueryOptions as bt, createGitLabProvider as c, canonicalStorageKey as ct, createFacebookProvider as d, createStorageApiKeyGuard as dt, optionalAuth as et, createAppleProvider as f, isApiKeyToken as ft, createGitHubProvider as g, SERVICE_IDENTITY as gt, verifyOidcIdToken as h, safeCompare as ht, createApiKeyStore as i, requireAuth as it, SMTPEmailService as j, createDataRateLimiter as k, createDiscordProvider as l, createApiKeyPreAuth as lt, tryVerifyOidcIdToken as m, extractBearerToken as mt, createCustomAuthAdapter as n, queryTokenAuth as nt, createSlackProvider as o, InvalidStorageKeyError as ot, createMicrosoftProvider as p, validateApiKey as pt, html as q, createApiKeyRoutes as r, requireAdmin as rt, createBitbucketProvider as s, canonicalStorageBucket as st, auth_exports as t, publicObjectAuth as tt, createTwitterProvider as u, createFunctionApiKeyGuard as ut, createGoogleProvider as v, httpMethodToOperation as vt, _enum as w, providerVerifiedEmail as x, resolveListLimitParam as xt, oauthCodeFlowSchema as y, isOperationAllowed as yt, generateSecurePassword as z };
10504
+ export { extractUserFromToken as $, createDataRateLimiter as A, generateSecurePassword as B, createBuiltinAuthAdapter as C, isPublicStoragePath as Ct, string as D, object as E, resolveEmailLinkBase as F, getWelcomeEmailTemplate as G, getMagicLinkTemplate as H, resolveAuthHooks as I, html as J, RawHtml as K, hashPassword as L, SMTPEmailService as M, createEmailService as N, _coercedNumber as O, assertEmailLinkBases as P, createRequireAuth as Q, validatePasswordStrength as R, createJwksRoutes as S, PUBLIC_STORAGE_PREFIX as St, _enum as T, getPasswordResetTemplate as U, getEmailVerificationTemplate as V, getUserInvitationTemplate as W, createAdapterAuthMiddleware as X, raw as Y, createAuthMiddleware as Z, createLinkedinProvider as _, orderByEntriesToTuples as _t, createSpotifyProvider as a, requireAuth as at, pkceTokenParams as b, parseQueryOptions as bt, createGitLabProvider as c, createStorageApiKeyGuard as ct, createFacebookProvider as d, extractBearerToken as dt, fileTokenAuth as et, createAppleProvider as f, safeCompare as ft, createGitHubProvider as g, isOperationAllowed as gt, verifyOidcIdToken as h, httpMethodToOperation as ht, createApiKeyStore as i, requireAdmin as it, MemoryRateLimitStore as j, DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT as k, createDiscordProvider as l, isApiKeyToken as lt, tryVerifyOidcIdToken as m, scopeDataDriver as mt, createCustomAuthAdapter as n, publicObjectAuth as nt, createSlackProvider as o, createApiKeyPreAuth as ot, createMicrosoftProvider as p, SERVICE_IDENTITY as pt, escapeHtml as q, createApiKeyRoutes as r, queryTokenAuth as rt, createBitbucketProvider as s, createFunctionApiKeyGuard as st, auth_exports as t, optionalAuth as tt, createTwitterProvider as u, validateApiKey as ut, createGoogleProvider as v, parseAggregateSelect as vt, ZodNumber as w, providerVerifiedEmail as x, resolveListLimitParam as xt, oauthCodeFlowSchema as y, parseGroupBy as yt, verifyPassword as z };
10530
10505
 
10531
- //# sourceMappingURL=auth-CYoPVf-E.js.map
10506
+ //# sourceMappingURL=auth-BobZVd0j.js.map