@rebasepro/server 0.14.0 → 0.14.1-canary.g7e666eb

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 (31) hide show
  1. package/dist/api/rest/query-parser.d.ts +15 -1
  2. package/dist/api/rest/write-validation.d.ts +26 -0
  3. package/dist/auth/interfaces.d.ts +14 -1
  4. package/dist/auth/jwt.d.ts +30 -2
  5. package/dist/{auth-CYoPVf-E.js → auth-BQcdhMBL.js} +64 -165
  6. package/dist/auth-BQcdhMBL.js.map +1 -0
  7. package/dist/{cron-store-Dvr4Y1sZ.js → cron-store-D5dUNviq.js} +2 -2
  8. package/dist/{cron-store-Dvr4Y1sZ.js.map → cron-store-D5dUNviq.js.map} +1 -1
  9. package/dist/ddl-bootstrap-BhXbTnBl.js.map +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.es.js +267 -279
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/{jwt-_IFqfTOg.js → jwt-CYGFT0ih.js} +297 -7
  14. package/dist/jwt-CYGFT0ih.js.map +1 -0
  15. package/dist/{openapi-generator-DPKtUC9X.js → openapi-generator-BWL8F2La.js} +24 -3
  16. package/dist/openapi-generator-BWL8F2La.js.map +1 -0
  17. package/dist/{request-timeout-RivJsME0.js → request-timeout-BuFoEKwT.js} +6 -3
  18. package/dist/request-timeout-BuFoEKwT.js.map +1 -0
  19. package/dist/{src-C7rkDGxA.js → src-8XDWyDfR.js} +84 -13
  20. package/dist/src-8XDWyDfR.js.map +1 -0
  21. package/dist/src-Cz9nMgUR.js.map +1 -1
  22. package/dist/storage/keys.d.ts +17 -0
  23. package/dist/storage/routes.d.ts +1 -1
  24. package/dist/storage/storage-registry.d.ts +46 -4
  25. package/dist/storage/tus-handler.d.ts +1 -1
  26. package/package.json +5 -5
  27. package/dist/auth-CYoPVf-E.js.map +0 -1
  28. package/dist/jwt-_IFqfTOg.js.map +0 -1
  29. package/dist/openapi-generator-DPKtUC9X.js.map +0 -1
  30. package/dist/request-timeout-RivJsME0.js.map +0 -1
  31. package/dist/src-C7rkDGxA.js.map +0 -1
@@ -1,6 +1,20 @@
1
- import type { ListLimitBounds } from "@rebasepro/types";
1
+ import type { ListLimitBounds, OrderByTuple } from "@rebasepro/types";
2
2
  import { QueryOptions } from "../types";
3
3
  export declare const mapOperator: (op: string) => import("@rebasepro/types").WhereFilterOp | null;
4
+ type OrderByEntry = {
5
+ field: string;
6
+ direction: "asc" | "desc";
7
+ };
8
+ /**
9
+ * The parsed entries as the driver contract spells them: `[field, direction]`
10
+ * tuples in order of significance.
11
+ *
12
+ * The REST layer used to hand the driver `orderBy[0].field` and drop the rest,
13
+ * so `?orderBy=[{"field":"roles"},{"field":"created_at","direction":"desc"}]`
14
+ * — a shape this parser has always accepted and validated in full — sorted by
15
+ * `roles` alone and returned the ties in whatever order Postgres pleased.
16
+ */
17
+ export declare function orderByEntriesToTuples(entries?: OrderByEntry[]): OrderByTuple[] | undefined;
4
18
  export { DEFAULT_LIST_LIMIT, DEFAULT_VECTOR_LIST_LIMIT, MAX_LIST_LIMIT } from "@rebasepro/types";
5
19
  /**
6
20
  * Overridable list-pagination bounds for {@link parseQueryOptions}. Without
@@ -85,3 +85,29 @@ export declare function assertWriteValuesValid(values: Record<string, unknown>,
85
85
  export declare function projectResponseFields<T extends Record<string, unknown>>(rows: T[], fields: readonly string[] | undefined, collection: CollectionConfig, options?: {
86
86
  include?: readonly string[];
87
87
  }): T[];
88
+ /**
89
+ * Both write checks, as one call, for a transport that is not the REST router.
90
+ *
91
+ * The REST routes run `assertKnownWriteFields` and `assertWriteValuesValid`
92
+ * back to back on the caller's body, seven times over — and they were the only
93
+ * place either ran. The WebSocket `SAVE` handler took a client payload straight
94
+ * to `driver.save`, so the same write arrived validated through one door and
95
+ * unvalidated through the other: `PATCH /api/data/users/1 { age: 999 }` was a
96
+ * 400, and the socket wrote it.
97
+ *
98
+ * Exported for the sockets in `@rebasepro/server-postgres` and
99
+ * `@rebasepro/server-mongo`, which are the other request boundaries. Not the
100
+ * route builder — `index.ts` keeps that internal, and this is a rule rather
101
+ * than wiring.
102
+ *
103
+ * It stays at the boundary rather than moving into the driver deliberately: it
104
+ * validates what a *caller sent*, before `beforeSave` gets a chance to fill in
105
+ * or rewrite anything. In-process writes through `rebase.data` are trusted
106
+ * server code and are not run through it, which is the placement the REST layer
107
+ * already chose.
108
+ *
109
+ * @param values the caller's payload, exactly as it arrived
110
+ * @param collection resolved from the registry by path — never the copy the
111
+ * client sent, or the rules would be the caller's to pick
112
+ */
113
+ export declare function assertWriteRequestValid(values: Record<string, unknown>, collection: CollectionConfig): void;
@@ -227,7 +227,20 @@ export interface PaginatedUsersResult {
227
227
  */
228
228
  export interface UserRepository {
229
229
  /**
230
- * Create a new user
230
+ * Create a new user.
231
+ *
232
+ * **An email already in use is a 409 `EMAIL_EXISTS`, not a 500.** Every
233
+ * caller checks first — `POST /auth/register` reads `getUserByEmail` and
234
+ * answers 409 — and every engine backs that check with a unique index,
235
+ * because a check cannot hold its answer still. What is left is the window
236
+ * between the two, and a double-clicked signup button is wide enough: both
237
+ * requests read "no such user", both insert, one wins.
238
+ *
239
+ * The loser's insert violates the index either way; the only question is
240
+ * what it is turned into. An unmapped driver error is a 500 "Internal
241
+ * Server Error", which tells the person who clicked twice that the server
242
+ * is broken rather than that they already have an account — and tells the
243
+ * operator to go looking for a fault that is not there.
231
244
  */
232
245
  createUser(data: CreateUserData): Promise<UserData>;
233
246
  /**
@@ -141,12 +141,40 @@ export declare function verifyMfaPendingToken(token: string): {
141
141
  export interface DownloadTokenPayload {
142
142
  purpose: "file-read";
143
143
  path: string;
144
+ /**
145
+ * The storage source the grant is good for, canonicalized by
146
+ * {@link canonicalStorageId}. Always present on a decoded payload; see
147
+ * {@link verifyDownloadToken} for how tokens minted before this claim
148
+ * existed are read.
149
+ */
150
+ storageId: string;
144
151
  }
145
152
  /**
146
153
  * Generate a short-lived download token scoped to a specific file path or prefix
154
+ * *within one storage source*.
155
+ *
156
+ * Both halves of that scope are load-bearing. A key is only unique inside its
157
+ * own bucket, and a project with more than one source routinely holds the same
158
+ * key in several of them — `avatars/u1.png` in `(default)` and in `media` are
159
+ * different objects, quite possibly with different owners. A token that names
160
+ * only the path is therefore a grant on every source at once: authorize a read
161
+ * on the source whose `storageAuthorize` hook says yes, then spend the token
162
+ * against `?storageId=` pointing somewhere else.
163
+ *
164
+ * `storageId` is optional here only because omitting it *is* the default
165
+ * source, which is what the overwhelming majority of deployments have. A mint
166
+ * site that forgets to pass a named source produces a default-scoped token,
167
+ * which fails closed at `/file/*` rather than over-granting.
147
168
  */
148
- export declare function generateDownloadToken(path: string, expiresInSeconds?: number): string;
169
+ export declare function generateDownloadToken(path: string, expiresInSeconds?: number, storageId?: string | null): string;
149
170
  /**
150
- * Verify and decode a download token
171
+ * Verify and decode a download token.
172
+ *
173
+ * A token minted before `storageId` existed carries no such claim. It is read
174
+ * as a grant on the **default source** rather than on all of them: that is the
175
+ * fail-closed reading, and it is what such a token almost always was, since a
176
+ * named source has to be asked for explicitly. The cost is bounded by the
177
+ * five-minute TTL — for at most that long after a deploy, an in-flight token
178
+ * for a *named* source is refused and the client re-fetches `/metadata`.
151
179
  */
152
180
  export declare function verifyDownloadToken(token: string): DownloadTokenPayload | null;
@@ -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
7
  import { i as isSQLAdmin, r as revokeInternalTableSql, t as createDdlBootstrapper } from "./ddl-bootstrap-BhXbTnBl.js";
8
+ import { S as require_jsonwebtoken, a as generateMfaPendingToken, c as getRefreshTokenExpiry, d as isJwtConfigured, 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, x as tryCanonicalStorageKey, y as canonicalStorageId } from "./jwt-CYGFT0ih.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,6 +171,19 @@ 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
  }
@@ -832,137 +844,6 @@ function createApiKeyPreAuth(options) {
832
844
  };
833
845
  }
834
846
  //#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
847
  //#region src/auth/middleware.ts
967
848
  /**
968
849
  * Hono middleware that requires a valid JWT token via Authorization header.
@@ -1326,25 +1207,49 @@ var fileTokenAuth = async (c, next) => {
1326
1207
  resolvedPath: tryCanonicalStorageKey(filePath)
1327
1208
  };
1328
1209
  };
1210
+ /**
1211
+ * Decide what a valid download token entitles this request to.
1212
+ *
1213
+ * One function rather than a copy per token location: a Bearer token and a
1214
+ * `?token=` are the same grant presented two ways, and the whole failure
1215
+ * this guards against is two places disagreeing about what was granted.
1216
+ *
1217
+ * `"no-path"` is distinct from the two denials because the caller treats it
1218
+ * differently — a request with no object path at all is not a denied read,
1219
+ * it is a request this middleware has nothing to say about, and on
1220
+ * `/metadata/*` it must still reach the downstream auth gate.
1221
+ */
1222
+ const evaluateGrant = (payload) => {
1223
+ if (canonicalStorageId(c.req.query("storageId")) !== payload.storageId) return "deny-storage";
1224
+ const rawPath = extractWildcard(c);
1225
+ if (!rawPath) return "no-path";
1226
+ const { bucket, resolvedPath } = parseBucketPath(decodeURIComponent(rawPath));
1227
+ if (resolvedPath === null) return "deny-path";
1228
+ return isPathMatch(`${bucket}/${resolvedPath}`, payload.path) ? "grant" : "deny-path";
1229
+ };
1230
+ /**
1231
+ * Which half of the scope failed is named in the message. The holder of the
1232
+ * token already knows both values, so this reveals nothing to an attacker —
1233
+ * and without it, a client that forgets to forward `?storageId=` on the file
1234
+ * URL reports a "path mismatch" for a path that matches perfectly.
1235
+ */
1236
+ const denyMismatch = (outcome) => c.json({ error: {
1237
+ message: outcome === "deny-storage" ? "Forbidden: Scoped token storage mismatch" : "Forbidden: Scoped token path mismatch",
1238
+ code: "FORBIDDEN"
1239
+ } }, 403);
1329
1240
  const bearerToken = extractBearerToken(authHeader);
1330
1241
  if (bearerToken !== void 0) {
1331
1242
  const payload = verifyDownloadToken(bearerToken);
1332
1243
  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);
1244
+ const outcome = evaluateGrant(payload);
1245
+ if (outcome === "grant") {
1246
+ c.set("user", {
1247
+ uid: "download-token",
1248
+ roles: ["reader"]
1249
+ });
1250
+ return next();
1347
1251
  }
1252
+ if (outcome !== "no-path") return denyMismatch(outcome);
1348
1253
  }
1349
1254
  if (isFileRoute) return c.json({ error: {
1350
1255
  message: "Unauthorized: Access JWT not allowed on file routes",
@@ -1355,21 +1260,15 @@ var fileTokenAuth = async (c, next) => {
1355
1260
  if (queryToken) {
1356
1261
  const payload = verifyDownloadToken(queryToken);
1357
1262
  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);
1263
+ const outcome = evaluateGrant(payload);
1264
+ if (outcome === "grant") {
1265
+ c.set("user", {
1266
+ uid: "download-token",
1267
+ roles: ["reader"]
1268
+ });
1269
+ return next();
1372
1270
  }
1271
+ if (outcome !== "no-path") return denyMismatch(outcome);
1373
1272
  }
1374
1273
  return c.json({ error: {
1375
1274
  message: "Unauthorized: Invalid or unauthorized token",
@@ -7419,7 +7318,7 @@ function mountSessionRoutes(opts) {
7419
7318
  clearRefreshCookie(c, config.cookieAuth);
7420
7319
  const accessToken = extractBearerToken(c.req.header("authorization"));
7421
7320
  if (ops.afterLogout && accessToken !== void 0) {
7422
- const { verifyAccessToken } = await import("./jwt-_IFqfTOg.js").then((n) => n.f);
7321
+ const { verifyAccessToken } = await import("./jwt-CYGFT0ih.js").then((n) => n.f);
7423
7322
  const payload = verifyAccessToken(accessToken);
7424
7323
  if (payload) ops.afterLogout(payload.uid).catch((err) => {
7425
7324
  logger.error("[AuthHooks] afterLogout error", { error: err instanceof Error ? err.message : err });
@@ -10526,6 +10425,6 @@ var auth_exports = /* @__PURE__ */ __exportAll({
10526
10425
  verifyPassword: () => verifyPassword
10527
10426
  });
10528
10427
  //#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 };
10428
+ export { fileTokenAuth as $, MemoryRateLimitStore as A, getEmailVerificationTemplate as B, ZodNumber as C, _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, object as T, getUserInvitationTemplate as U, getMagicLinkTemplate as V, getWelcomeEmailTemplate as W, createAuthMiddleware as X, createAdapterAuthMiddleware as Y, createRequireAuth as Z, createLinkedinProvider as _, parseQueryOptions as _t, createSpotifyProvider as a, createApiKeyPreAuth as at, pkceTokenParams as b, isPublicStoragePath as bt, createGitLabProvider as c, isApiKeyToken as ct, createFacebookProvider as d, safeCompare as dt, optionalAuth as et, createAppleProvider as f, SERVICE_IDENTITY as ft, createGitHubProvider as g, orderByEntriesToTuples as gt, verifyOidcIdToken as h, isOperationAllowed as ht, createApiKeyStore as i, requireAuth as it, SMTPEmailService as j, createDataRateLimiter as k, createDiscordProvider as l, validateApiKey as lt, tryVerifyOidcIdToken as m, httpMethodToOperation as mt, createCustomAuthAdapter as n, queryTokenAuth as nt, createSlackProvider as o, createFunctionApiKeyGuard as ot, createMicrosoftProvider as p, scopeDataDriver as pt, html as q, createApiKeyRoutes as r, requireAdmin as rt, createBitbucketProvider as s, createStorageApiKeyGuard as st, auth_exports as t, publicObjectAuth as tt, createTwitterProvider as u, extractBearerToken as ut, createGoogleProvider as v, resolveListLimitParam as vt, _enum as w, providerVerifiedEmail as x, oauthCodeFlowSchema as y, PUBLIC_STORAGE_PREFIX as yt, generateSecurePassword as z };
10530
10429
 
10531
- //# sourceMappingURL=auth-CYoPVf-E.js.map
10430
+ //# sourceMappingURL=auth-BQcdhMBL.js.map