@rebasepro/server 0.13.0 → 0.13.1-canary.g249daa1
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.
- package/dist/{admin_block-CcSGdH7M.js → admin_block-CLeUMG8G.js} +3 -1
- package/dist/admin_block-CLeUMG8G.js.map +1 -0
- package/dist/api/errors.d.ts +0 -6
- package/dist/api/openapi-generator.d.ts +14 -0
- package/dist/api/rest/idempotency.d.ts +41 -4
- package/dist/api/rest/write-validation.d.ts +12 -5
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/jwt.d.ts +12 -0
- package/dist/{auth-CuC9M2x6.js → auth-BANhRhN9.js} +194 -84
- package/dist/auth-BANhRhN9.js.map +1 -0
- package/dist/{backup-CVggVhR2.js → backup-BJiY_mM-.js} +2 -2
- package/dist/{backup-CVggVhR2.js.map → backup-BJiY_mM-.js.map} +1 -1
- package/dist/boot/boot.d.ts +19 -0
- package/dist/boot/ddl-bootstrap.d.ts +70 -0
- package/dist/{contract-routes-Dj8i5AiM.js → contract-routes-FSt0Nyfk.js} +2 -2
- package/dist/{contract-routes-Dj8i5AiM.js.map → contract-routes-FSt0Nyfk.js.map} +1 -1
- package/dist/cron/cron-scheduler.d.ts +8 -3
- package/dist/cron/define-cron.d.ts +5 -3
- package/dist/{cron-routes-CrQ0tK-_.js → cron-routes-D5a9v1HY.js} +2 -2
- package/dist/{cron-routes-CrQ0tK-_.js.map → cron-routes-D5a9v1HY.js.map} +1 -1
- package/dist/{cron-scheduler-B3RFt0HS.js → cron-scheduler-CsQtYLMq.js} +28 -3
- package/dist/cron-scheduler-CsQtYLMq.js.map +1 -0
- package/dist/{cron-store-BywZsyfZ.js → cron-store-ZbjfuvMb.js} +65 -46
- package/dist/cron-store-ZbjfuvMb.js.map +1 -0
- package/dist/ddl-bootstrap-Bp3mr6Td.js +183 -0
- package/dist/ddl-bootstrap-Bp3mr6Td.js.map +1 -0
- package/dist/{errors-CgkCzoj7.js → errors-BpudWAVU.js} +2 -10
- package/dist/errors-BpudWAVU.js.map +1 -0
- package/dist/functions/define-function.d.ts +1 -1
- package/dist/history/history-routes.d.ts +6 -0
- package/dist/index.es.js +835 -276
- package/dist/index.es.js.map +1 -1
- package/dist/{jwt-DD6EtpGj.js → jwt-CzeviDcB.js} +17 -2
- package/dist/{jwt-DD6EtpGj.js.map → jwt-CzeviDcB.js.map} +1 -1
- package/dist/{openapi-generator-BEwyiaAr.js → openapi-generator-VB0MUH8h.js} +340 -128
- package/dist/openapi-generator-VB0MUH8h.js.map +1 -0
- package/dist/{schema-editor-routes-CbF20Mf1.js → schema-editor-routes-BqjzvvWU.js} +4 -4
- package/dist/{schema-editor-routes-CbF20Mf1.js.map → schema-editor-routes-BqjzvvWU.js.map} +1 -1
- package/dist/{src-_qQ3RNCK.js → src-Ca6NhxKs.js} +79 -2
- package/dist/src-Ca6NhxKs.js.map +1 -0
- package/dist/{src-Cum9kox5.js → src-DVb5a0bp.js} +153 -39
- package/dist/src-DVb5a0bp.js.map +1 -0
- package/dist/storage/keys.d.ts +65 -0
- package/dist/utils/logging.d.ts +0 -4
- package/dist/utils/sql.d.ts +11 -6
- package/package.json +6 -6
- package/dist/admin_block-CcSGdH7M.js.map +0 -1
- package/dist/auth-CuC9M2x6.js.map +0 -1
- package/dist/backend-CIxN4FVm.js +0 -15
- package/dist/backend-CIxN4FVm.js.map +0 -1
- package/dist/cron-scheduler-B3RFt0HS.js.map +0 -1
- package/dist/cron-store-BywZsyfZ.js.map +0 -1
- package/dist/errors-CgkCzoj7.js.map +0 -1
- package/dist/openapi-generator-BEwyiaAr.js.map +0 -1
- package/dist/src-Cum9kox5.js.map +0 -1
- package/dist/src-_qQ3RNCK.js.map +0 -1
|
@@ -44,6 +44,7 @@ var ADMIN_COLLECTION_KEYS = [
|
|
|
44
44
|
"defaultSize",
|
|
45
45
|
"defaultViewMode",
|
|
46
46
|
"disableDefaultActions",
|
|
47
|
+
"display",
|
|
47
48
|
"enabledViews",
|
|
48
49
|
"entityActions",
|
|
49
50
|
"entityViews",
|
|
@@ -54,6 +55,7 @@ var ADMIN_COLLECTION_KEYS = [
|
|
|
54
55
|
"formAutoSave",
|
|
55
56
|
"formView",
|
|
56
57
|
"group",
|
|
58
|
+
"hideFromEntityViews",
|
|
57
59
|
"hideFromNavigation",
|
|
58
60
|
"hideIdFromCollection",
|
|
59
61
|
"hideIdFromForm",
|
|
@@ -121,4 +123,4 @@ var ADMIN_PROPERTY_KEYS = [
|
|
|
121
123
|
//#endregion
|
|
122
124
|
export { ADMIN_PROPERTY_KEYS as n, ADMIN_COLLECTION_KEYS as t };
|
|
123
125
|
|
|
124
|
-
//# sourceMappingURL=admin_block-
|
|
126
|
+
//# sourceMappingURL=admin_block-CLeUMG8G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin_block-CLeUMG8G.js","names":[],"sources":["../../types/src/types/admin_block.ts"],"sourcesContent":["/**\n * The keys of a collection's admin block, as data.\n *\n * There is no *type* for the block in this package any more, and that is the point:\n * `admin` is not declared on `BaseCollectionConfig` or on any property here, so a\n * BaaS install cannot even write one. `@rebasepro/admin-types` adds the field back by\n * declaration merging, which is why installing it is what makes the admin surface\n * appear.\n *\n * The *list* still has to live here, because three runtime consumers need it and two\n * of them are core — see below.\n */\n\n/**\n * Every key that belongs inside a collection's `admin` block, as data.\n *\n * The type that describes these fields is `AdminCollectionOptions` in\n * `@rebasepro/admin-types`, and it is erased at build time — but three runtime\n * consumers need the list, and two of them are core:\n *\n * - `serializeCollections`, to drop the block from the contract\n * - the ts-morph schema editor in `@rebasepro/server`, which rewrites collection\n * files on disk from the admin panel and has to know where each key goes. A key\n * missing from this list gets written to the *top level* of the file, where the\n * backend ignores it and the panel never finds it again.\n * - the `collections-admin-block` codemod\n *\n * `@rebasepro/admin-types` re-exports this and asserts it names only real option\n * keys; the count is pinned by a test there.\n *\n * @group Models\n */\nexport const ADMIN_COLLECTION_KEYS = [\n \"Actions\",\n \"additionalFields\",\n \"alwaysApplyDefaultValues\",\n \"components\",\n \"defaultEntityAction\",\n \"defaultFilter\",\n \"defaultSelectedView\",\n \"defaultSize\",\n \"defaultViewMode\",\n \"disableDefaultActions\",\n \"display\",\n \"enabledViews\",\n \"entityActions\",\n \"entityViews\",\n \"exportable\",\n \"filterPresets\",\n \"fixedFilter\",\n \"form\",\n \"formAutoSave\",\n \"formView\",\n \"group\",\n \"hideFromEntityViews\",\n \"hideFromNavigation\",\n \"hideIdFromCollection\",\n \"hideIdFromForm\",\n \"icon\",\n \"includeJsonView\",\n \"inlineEditing\",\n \"kanban\",\n \"listProperties\",\n \"localChangesBackup\",\n \"openEntityMode\",\n \"orderProperty\",\n \"pagination\",\n \"previewProperties\",\n \"propertiesOrder\",\n \"selectionController\",\n \"selectionEnabled\",\n \"sideDialogWidth\",\n \"sort\",\n \"titleProperty\"\n] as const;\n\n/** A key of a collection's `admin` block. @group Models */\nexport type AdminCollectionKey = typeof ADMIN_COLLECTION_KEYS[number];\n\n/**\n * Every key that belongs inside a *property's* `admin` block, as data.\n *\n * The union of `AdminPropertyOptions` and its per-type extensions\n * (`AdminStringOptions`, `AdminArrayOptions`, …) in `@rebasepro/admin-types`.\n * It lives here for the same reason {@link ADMIN_COLLECTION_KEYS} does: the\n * runtime consumers are core packages that the BaaS guard forbids from\n * importing `@rebasepro/admin-types`. Here it is the boot-time collection\n * validator in `@rebasepro/server`, which has to tell \"you left `readOnly` at\n * the top of the property, where nothing reads it\" apart from \"you invented a\n * key we have never heard of\".\n *\n * `@rebasepro/admin-types` re-exports this and asserts it names only real\n * option keys.\n *\n * @group Models\n */\nexport const ADMIN_PROPERTY_KEYS = [\n \"canAddElements\",\n \"clearable\",\n \"columnWidth\",\n \"customProps\",\n \"disabled\",\n \"expanded\",\n \"Field\",\n \"Filter\",\n \"filterOperators\",\n \"fixedFilter\",\n \"hideFromCollection\",\n \"includeEntityLink\",\n \"includeId\",\n \"markdown\",\n \"minimalistView\",\n \"multiline\",\n \"Preview\",\n \"previewAsTag\",\n \"previewProperties\",\n \"readOnly\",\n \"sortable\",\n \"span\",\n \"spreadChildren\",\n \"urlPreview\",\n \"widget\",\n] as const;\n\n/** A key of a property's `admin` block. @group Models */\nexport type AdminPropertyKey = typeof ADMIN_PROPERTY_KEYS[number];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,wBAAwB;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;;;;;;;;;;;;;;;;;AAsBA,IAAa,sBAAsB;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ"}
|
package/dist/api/errors.d.ts
CHANGED
|
@@ -55,12 +55,6 @@ export interface RebaseApiError extends Error {
|
|
|
55
55
|
code?: string;
|
|
56
56
|
details?: unknown;
|
|
57
57
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Type guard for errors that carry optional API metadata (statusCode, code, details).
|
|
60
|
-
* Returns true for any Error instance — the optional properties are then
|
|
61
|
-
* checked via normal property access.
|
|
62
|
-
*/
|
|
63
|
-
export declare function isRebaseApiError(error: unknown): error is RebaseApiError;
|
|
64
58
|
/**
|
|
65
59
|
* Hono error-handling middleware (`app.onError`).
|
|
66
60
|
* Converts any error into the canonical `{ error: { message, code } }` shape.
|
|
@@ -12,5 +12,19 @@ export interface OpenApiGeneratorOptions {
|
|
|
12
12
|
basePath?: string;
|
|
13
13
|
/** Whether auth is enabled on data routes. Defaults to true. */
|
|
14
14
|
requireAuth?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The list-pagination bounds the REST layer applies, so the spec states the
|
|
17
|
+
* ones a request will actually meet.
|
|
18
|
+
*
|
|
19
|
+
* These were hardcoded as `default: 20, maximum: 100` — neither of which
|
|
20
|
+
* the server has ever used. The spec drives the API Explorer and is what a
|
|
21
|
+
* generated client is built from, so an understated ceiling is a request
|
|
22
|
+
* the client refuses to make, and an overstated one is a 400 nobody
|
|
23
|
+
* predicted.
|
|
24
|
+
*/
|
|
25
|
+
listLimits?: {
|
|
26
|
+
defaultLimit?: number;
|
|
27
|
+
maxLimit?: number;
|
|
28
|
+
};
|
|
15
29
|
}
|
|
16
30
|
export declare function generateOpenApiSpec(collections: CollectionConfig[], options?: OpenApiGeneratorOptions): Record<string, unknown>;
|
|
@@ -1,9 +1,46 @@
|
|
|
1
1
|
import { DataDriver } from "@rebasepro/types";
|
|
2
|
+
/**
|
|
3
|
+
* What a caller may do with a key.
|
|
4
|
+
*
|
|
5
|
+
* `claimed` is the only outcome that permits the write. Recalling and then
|
|
6
|
+
* writing was not atomic: two requests carrying one key both missed the recall,
|
|
7
|
+
* both wrote, and `ON CONFLICT DO NOTHING` protected only the second row in
|
|
8
|
+
* *this* table — the duplicate row in the caller's collection, which is the
|
|
9
|
+
* thing the mechanism exists to prevent, was inserted anyway. The offline queue
|
|
10
|
+
* is shared across tabs, so two tabs reconnecting together is the ordinary case
|
|
11
|
+
* rather than an exotic one.
|
|
12
|
+
*/
|
|
13
|
+
export type IdempotencyClaim =
|
|
14
|
+
/** This key has already been answered; serve that answer again. */
|
|
15
|
+
{
|
|
16
|
+
status: "replay";
|
|
17
|
+
response: unknown;
|
|
18
|
+
}
|
|
19
|
+
/** Another request holds this key and has not answered yet. */
|
|
20
|
+
| {
|
|
21
|
+
status: "in-flight";
|
|
22
|
+
}
|
|
23
|
+
/** The key is ours; do the write, then `complete` or `release` it. */
|
|
24
|
+
| {
|
|
25
|
+
status: "claimed";
|
|
26
|
+
};
|
|
2
27
|
export interface IdempotencyStore {
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Take the key if it is free, atomically. Never throws — a store that
|
|
30
|
+
* cannot answer reports `claimed`, which degrades to no idempotency rather
|
|
31
|
+
* than to a refused write.
|
|
32
|
+
*/
|
|
33
|
+
claim(key: string, uid: string | undefined): Promise<IdempotencyClaim>;
|
|
34
|
+
/** Record what this key answered. Never throws. */
|
|
35
|
+
complete(key: string, uid: string | undefined, response: unknown): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Give the key back after the write it was claimed for failed.
|
|
38
|
+
*
|
|
39
|
+
* Without this a transient failure strands the key: every retry would be
|
|
40
|
+
* told `in-flight` until the row aged out, turning one failed write into a
|
|
41
|
+
* day of refusals.
|
|
42
|
+
*/
|
|
43
|
+
release(key: string, uid: string | undefined): Promise<void>;
|
|
7
44
|
}
|
|
8
45
|
/**
|
|
9
46
|
* Returns `undefined` when the driver cannot run SQL, which disables the whole
|
|
@@ -33,11 +33,18 @@ export declare function assertKnownWriteFields(values: Record<string, unknown>,
|
|
|
33
33
|
* This shapes the *response*, which is what the parameter says it does; it is
|
|
34
34
|
* not a column pushdown, so it saves bandwidth rather than database work.
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
* the admin table, realtime reconciliation, the offline
|
|
38
|
-
* arrives without one is not a smaller row, it is an
|
|
39
|
-
* `fields=title` and being unable to open the record
|
|
40
|
-
* answer than one extra key.
|
|
36
|
+
* The collection's key always survives. Rows are addressed by it everywhere
|
|
37
|
+
* above this layer — the admin table, realtime reconciliation, the offline
|
|
38
|
+
* cache — and a row that arrives without one is not a smaller row, it is an
|
|
39
|
+
* unusable one. Asking for `fields=title` and being unable to open the record
|
|
40
|
+
* you clicked is a worse answer than one extra key.
|
|
41
|
+
*
|
|
42
|
+
* That is a statement about the key, not about the name `id`: a collection
|
|
43
|
+
* keyed on `slug`, or on `user_id + role_id`, was projected down to the fields
|
|
44
|
+
* asked for and nothing else, which is the very outcome the paragraph above
|
|
45
|
+
* describes. `assertKnownWriteFields` one function up already has a dedicated
|
|
46
|
+
* error for collections with no `id` column, so this layer had no excuse for
|
|
47
|
+
* assuming one.
|
|
41
48
|
*/
|
|
42
49
|
export declare function projectResponseFields<T extends Record<string, unknown>>(rows: T[], fields: readonly string[] | undefined, collection: CollectionConfig, options?: {
|
|
43
50
|
include?: readonly string[];
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from "./interfaces";
|
|
2
|
-
export { configureJwt, generateAccessToken, verifyAccessToken, generateRefreshToken, hashRefreshToken, getRefreshTokenExpiry, getAccessTokenExpiry, generateDownloadToken, verifyDownloadToken } from "./jwt";
|
|
2
|
+
export { configureJwt, isJwtConfigured, generateAccessToken, verifyAccessToken, generateRefreshToken, hashRefreshToken, getRefreshTokenExpiry, getAccessTokenExpiry, generateDownloadToken, verifyDownloadToken } from "./jwt";
|
|
3
3
|
export type { JwtConfig, AccessTokenPayload, DownloadTokenPayload } from "./jwt";
|
|
4
4
|
export { hashPassword, verifyPassword, validatePasswordStrength } from "./password";
|
|
5
5
|
export { safeCompare } from "./crypto-utils";
|
package/dist/auth/jwt.d.ts
CHANGED
|
@@ -30,6 +30,18 @@ export interface AccessTokenPayload {
|
|
|
30
30
|
* Validates the secret strength to prevent deployment with default/weak secrets.
|
|
31
31
|
*/
|
|
32
32
|
export declare function configureJwt(config: JwtConfig): void;
|
|
33
|
+
/**
|
|
34
|
+
* Has this server been given a JWT secret?
|
|
35
|
+
*
|
|
36
|
+
* False on every backend that authenticates through an adapter — Firebase,
|
|
37
|
+
* Clerk, anything with its own tokens — because nothing calls
|
|
38
|
+
* {@link configureJwt} there. Signing paths still throw when it is false, since
|
|
39
|
+
* asking for a token from a server that cannot mint one is a mistake worth
|
|
40
|
+
* hearing about. Paths whose contract is *"tolerate the absence of auth"* must
|
|
41
|
+
* ask first: `optionalAuth` crashing a request because this backend does not do
|
|
42
|
+
* JWT is a 500 on a route that had already decided anonymous was acceptable.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isJwtConfigured(): boolean;
|
|
33
45
|
/**
|
|
34
46
|
* Generate an access token (short-lived, 1 hour by default)
|
|
35
47
|
*/
|
|
@@ -2,38 +2,18 @@ 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 {
|
|
6
|
-
import "./src-
|
|
7
|
-
import {
|
|
5
|
+
import { S as isAnonymousUid, b as resolveClientListLimit, x as ANONYMOUS_USER_ID } from "./src-DVb5a0bp.js";
|
|
6
|
+
import "./src-Ca6NhxKs.js";
|
|
7
|
+
import { i as isSQLAdmin, r as revokeInternalTableSql, t as createDdlBootstrapper } from "./ddl-bootstrap-Bp3mr6Td.js";
|
|
8
8
|
import { t as logger } from "./logger-BYU66ENZ.js";
|
|
9
|
-
import { n as errorHandler, t as ApiError } from "./errors-
|
|
10
|
-
import { a as generateRefreshToken, c as getRefreshTokenTtlMs,
|
|
9
|
+
import { n as errorHandler, t as ApiError } from "./errors-BpudWAVU.js";
|
|
10
|
+
import { a as generateRefreshToken, c as getRefreshTokenTtlMs, f as verifyAccessToken, i as generateDownloadToken, l as hashRefreshToken, m as require_jsonwebtoken, n as configureJwt, o as getAccessTokenExpiry, p as verifyDownloadToken, r as generateAccessToken, s as getRefreshTokenExpiry, t as MAX_COOKIE_AGE_MS, u as isJwtConfigured } from "./jwt-CzeviDcB.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, randomBytes as randomBytes$1, randomUUID as randomUUID$1, scrypt, timingSafeEqual } from "crypto";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Resolve a client-supplied list `limit` into a safe, always-defined value.
|
|
19
|
-
*
|
|
20
|
-
* - A provided limit is coerced to an integer and clamped to `[1, maxLimit]`,
|
|
21
|
-
* so `0`, negatives, and absurd values can never bypass the cap.
|
|
22
|
-
* - An absent / blank / non-numeric limit falls back to the mode default:
|
|
23
|
-
* `vectorDefaultLimit` for a vector search, otherwise `defaultLimit`.
|
|
24
|
-
*
|
|
25
|
-
* The return is never `undefined` — no ingress that routes its client limit
|
|
26
|
-
* through this can produce an unbounded read.
|
|
27
|
-
*/
|
|
28
|
-
function resolveClientListLimit(rawLimit, opts = {}) {
|
|
29
|
-
const maxLimit = opts.maxLimit ?? 1e3;
|
|
30
|
-
if (rawLimit != null && String(rawLimit).trim() !== "") {
|
|
31
|
-
const parsed = typeof rawLimit === "number" ? rawLimit : parseInt(String(rawLimit), 10);
|
|
32
|
-
if (Number.isFinite(parsed)) return Math.min(Math.max(1, Math.floor(parsed)), maxLimit);
|
|
33
|
-
}
|
|
34
|
-
return opts.vectorSearch ? opts.vectorDefaultLimit ?? 10 : opts.defaultLimit ?? 50;
|
|
35
|
-
}
|
|
36
|
-
//#endregion
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
37
17
|
//#region ../types/src/controllers/storage.ts
|
|
38
18
|
/**
|
|
39
19
|
* Path prefix that marks an object as **public**. Files stored under this
|
|
@@ -461,6 +441,91 @@ function createApiKeyPreAuth(options) {
|
|
|
461
441
|
};
|
|
462
442
|
}
|
|
463
443
|
//#endregion
|
|
444
|
+
//#region src/storage/keys.ts
|
|
445
|
+
/**
|
|
446
|
+
* Canonical storage keys.
|
|
447
|
+
*
|
|
448
|
+
* Storage is not under RLS, so a `storageAuthorize` hook is the whole access
|
|
449
|
+
* control model — and a hook can only be correct if the key it is shown is the
|
|
450
|
+
* key that is written. That is the invariant this module exists to hold: one
|
|
451
|
+
* canonical string, computed once per request, handed to the hook, to the
|
|
452
|
+
* controller, and to the download token alike.
|
|
453
|
+
*
|
|
454
|
+
* ## Why rejecting beats stripping
|
|
455
|
+
*
|
|
456
|
+
* The previous `sanitizeStorageKey` *stripped* `../` in a single pass. Two
|
|
457
|
+
* things were wrong with that, and only one of them was the obvious one.
|
|
458
|
+
*
|
|
459
|
+
* The obvious one: a single pass is not a fixed point. `....//` contains `../`
|
|
460
|
+
* at offset 2, so removing it leaves `../` behind — the sanitizer manufactured
|
|
461
|
+
* the traversal it was there to remove. `users/alice/....//bob/x` came out as
|
|
462
|
+
* `users/alice/../bob/x`, which a prefix hook reads as alice's (it starts with
|
|
463
|
+
* `users/alice/`) and the filesystem reads as bob's. The hook approved one
|
|
464
|
+
* object and the controller wrote another.
|
|
465
|
+
*
|
|
466
|
+
* The subtler one, and the reason this is a rewrite rather than a loop: even a
|
|
467
|
+
* correct strip is a silent rewrite. A caller who asks to store at `a/../b` and
|
|
468
|
+
* gets an object at `a/b` was not protected, they were misled — and every later
|
|
469
|
+
* read, ownership row and audit line now refers to a path nobody chose. So a key
|
|
470
|
+
* that means something other than what it says is refused (400), not repaired.
|
|
471
|
+
*
|
|
472
|
+
* Note what is NOT traversal under this rule: `....` is an ordinary directory
|
|
473
|
+
* name, and `users/alice/....//bob/x` canonicalizes to
|
|
474
|
+
* `users/alice/..../bob/x` — still comfortably inside alice's prefix, which is
|
|
475
|
+
* exactly right. Only a real `..` segment is refused.
|
|
476
|
+
*/
|
|
477
|
+
/** Longest key accepted, in UTF-16 code units. Matches the previous cap. */
|
|
478
|
+
var MAX_STORAGE_KEY_LENGTH = 1024;
|
|
479
|
+
/**
|
|
480
|
+
* A key that cannot be canonicalized. Carries no path back to the caller
|
|
481
|
+
* beyond what they sent, so it is safe to surface as a 400 message.
|
|
482
|
+
*/
|
|
483
|
+
var InvalidStorageKeyError = class extends Error {
|
|
484
|
+
constructor(message) {
|
|
485
|
+
super(message);
|
|
486
|
+
this.name = "InvalidStorageKeyError";
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
/**
|
|
490
|
+
* Canonicalize a caller-supplied storage key, or throw
|
|
491
|
+
* {@link InvalidStorageKeyError}.
|
|
492
|
+
*
|
|
493
|
+
* Normalizations applied (safe, idempotent, and meaning-preserving):
|
|
494
|
+
* - leading slashes removed — `/a/b` and `a/b` name the same object
|
|
495
|
+
* - `.` segments and repeated slashes collapsed
|
|
496
|
+
*
|
|
497
|
+
* Refusals (the key means something other than what it says):
|
|
498
|
+
* - any `..` segment, on either separator, at any depth
|
|
499
|
+
* - null bytes
|
|
500
|
+
* - keys longer than {@link MAX_STORAGE_KEY_LENGTH}
|
|
501
|
+
*
|
|
502
|
+
* A trailing slash is preserved: it is how the folder route marks a prefix.
|
|
503
|
+
*/
|
|
504
|
+
function canonicalStorageKey(rawKey) {
|
|
505
|
+
if (rawKey.includes("\0")) throw new InvalidStorageKeyError("Storage key contains a null byte.");
|
|
506
|
+
if (rawKey.length > 1024) throw new InvalidStorageKeyError(`Storage key exceeds the maximum length of ${MAX_STORAGE_KEY_LENGTH} characters.`);
|
|
507
|
+
if (rawKey.split(/[\\/]/).some((segment) => segment === "..")) throw new InvalidStorageKeyError("Storage key contains a '..' path segment. Keys must name an object directly.");
|
|
508
|
+
const withoutLeadingSlashes = rawKey.replace(/^\/+/, "");
|
|
509
|
+
if (withoutLeadingSlashes === "") return "";
|
|
510
|
+
const normalized = path.posix.normalize(withoutLeadingSlashes);
|
|
511
|
+
if (normalized === "." || normalized === "./") return "";
|
|
512
|
+
return normalized.replace(/^\.\//, "").replace(/^\/+/, "");
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Canonicalize, or return `null` when the key is not canonicalizable.
|
|
516
|
+
*
|
|
517
|
+
* For callers that must fail closed without an exception — the download-token
|
|
518
|
+
* middleware compares a request path against a granted path, and a key it
|
|
519
|
+
* cannot canonicalize simply matches nothing.
|
|
520
|
+
*/
|
|
521
|
+
function tryCanonicalStorageKey(rawKey) {
|
|
522
|
+
try {
|
|
523
|
+
return canonicalStorageKey(rawKey);
|
|
524
|
+
} catch {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
//#endregion
|
|
464
529
|
//#region src/auth/middleware.ts
|
|
465
530
|
/**
|
|
466
531
|
* Hono middleware that requires a valid JWT token via Authorization header.
|
|
@@ -551,7 +616,7 @@ var requireAdmin = async (c, next) => {
|
|
|
551
616
|
var optionalAuth = async (c, next) => {
|
|
552
617
|
if (c.get("user")) return next();
|
|
553
618
|
const token = extractBearerToken(c.req.header("authorization"));
|
|
554
|
-
if (token !== void 0) {
|
|
619
|
+
if (token !== void 0 && isJwtConfigured()) {
|
|
555
620
|
const payload = verifyAccessToken(token);
|
|
556
621
|
if (payload) c.set("user", payload);
|
|
557
622
|
}
|
|
@@ -561,6 +626,7 @@ var optionalAuth = async (c, next) => {
|
|
|
561
626
|
* Extract user from token - for WebSocket authentication
|
|
562
627
|
*/
|
|
563
628
|
function extractUserFromToken(token) {
|
|
629
|
+
if (!isJwtConfigured()) return null;
|
|
564
630
|
return verifyAccessToken(token);
|
|
565
631
|
}
|
|
566
632
|
/**
|
|
@@ -711,7 +777,7 @@ function createAuthMiddleware(options) {
|
|
|
711
777
|
var queryTokenAuth = async (c, next) => {
|
|
712
778
|
if (extractBearerToken(c.req.header("authorization")) !== void 0) return next();
|
|
713
779
|
const queryToken = c.req.query("token");
|
|
714
|
-
if (queryToken) {
|
|
780
|
+
if (queryToken && isJwtConfigured()) {
|
|
715
781
|
const payload = verifyAccessToken(queryToken);
|
|
716
782
|
if (payload) c.set("user", payload);
|
|
717
783
|
}
|
|
@@ -782,15 +848,25 @@ var fileTokenAuth = async (c, next) => {
|
|
|
782
848
|
if (idx < 0) return "";
|
|
783
849
|
return fullPath.substring(idx + prefix.length + 1);
|
|
784
850
|
};
|
|
851
|
+
/**
|
|
852
|
+
* Must agree with `parseBucketAndPath` in `storage/routes.ts`, because the
|
|
853
|
+
* path a token GRANTS is minted there and the path a request ASKS FOR is
|
|
854
|
+
* derived here. `/metadata` canonicalizes before signing, so a raw
|
|
855
|
+
* comparison here would 403 an otherwise-valid request for any key whose
|
|
856
|
+
* URL form is not already canonical (`a//b.txt`, `a/./b.txt`).
|
|
857
|
+
*
|
|
858
|
+
* A key that cannot be canonicalized yields null, which matches no grant —
|
|
859
|
+
* the same fail-closed answer `isPathMatch` gives a `..` segment.
|
|
860
|
+
*/
|
|
785
861
|
const parseBucketPath = (filePath) => {
|
|
786
862
|
const parts = filePath.split("/");
|
|
787
863
|
if (parts.length > 1 && parts[0].toLowerCase() === "default") return {
|
|
788
864
|
bucket: "default",
|
|
789
|
-
resolvedPath: parts.slice(1).join("/")
|
|
865
|
+
resolvedPath: tryCanonicalStorageKey(parts.slice(1).join("/"))
|
|
790
866
|
};
|
|
791
867
|
return {
|
|
792
868
|
bucket: "default",
|
|
793
|
-
resolvedPath: filePath
|
|
869
|
+
resolvedPath: tryCanonicalStorageKey(filePath)
|
|
794
870
|
};
|
|
795
871
|
};
|
|
796
872
|
const bearerToken = extractBearerToken(authHeader);
|
|
@@ -800,7 +876,8 @@ var fileTokenAuth = async (c, next) => {
|
|
|
800
876
|
const rawPath = extractWildcard(c);
|
|
801
877
|
if (rawPath) {
|
|
802
878
|
const { bucket, resolvedPath } = parseBucketPath(decodeURIComponent(rawPath));
|
|
803
|
-
|
|
879
|
+
const requestedFullPath = resolvedPath === null ? null : `${bucket}/${resolvedPath}`;
|
|
880
|
+
if (requestedFullPath !== null && isPathMatch(requestedFullPath, payload.path)) {
|
|
804
881
|
c.set("user", {
|
|
805
882
|
uid: "download-token",
|
|
806
883
|
roles: ["reader"]
|
|
@@ -824,7 +901,8 @@ var fileTokenAuth = async (c, next) => {
|
|
|
824
901
|
const rawPath = extractWildcard(c);
|
|
825
902
|
if (rawPath) {
|
|
826
903
|
const { bucket, resolvedPath } = parseBucketPath(decodeURIComponent(rawPath));
|
|
827
|
-
|
|
904
|
+
const requestedFullPath = resolvedPath === null ? null : `${bucket}/${resolvedPath}`;
|
|
905
|
+
if (requestedFullPath !== null && isPathMatch(requestedFullPath, payload.path)) {
|
|
828
906
|
c.set("user", {
|
|
829
907
|
uid: "download-token",
|
|
830
908
|
roles: ["reader"]
|
|
@@ -1458,11 +1536,11 @@ async function finalizeAdminUserCreation(entity, clearPassword, ctx) {
|
|
|
1458
1536
|
await ctx.authRepo.createPasswordResetToken(entity.id, tokenHash, expiresAt);
|
|
1459
1537
|
const setPasswordUrl = `${ctx.emailConfig?.resetPasswordUrl || ""}/reset-password?token=${token}`;
|
|
1460
1538
|
const appName = ctx.emailConfig?.appName || "Rebase";
|
|
1461
|
-
const templateFn = ctx.emailConfig?.templates?.
|
|
1539
|
+
const templateFn = ctx.emailConfig?.templates?.userInvitation;
|
|
1462
1540
|
const emailContent = templateFn ? templateFn(setPasswordUrl, {
|
|
1463
1541
|
email: entity.values.email,
|
|
1464
1542
|
displayName: entity.values.displayName
|
|
1465
|
-
}) :
|
|
1543
|
+
}) : getUserInvitationTemplate(setPasswordUrl, {
|
|
1466
1544
|
email: entity.values.email,
|
|
1467
1545
|
displayName: entity.values.displayName
|
|
1468
1546
|
}, appName);
|
|
@@ -1491,6 +1569,11 @@ async function finalizeAdminUserCreation(entity, clearPassword, ctx) {
|
|
|
1491
1569
|
var scryptAsync = promisify(scrypt);
|
|
1492
1570
|
var SALT_LENGTH = 32;
|
|
1493
1571
|
var KEY_LENGTH = 64;
|
|
1572
|
+
var SCRYPT_PARAMS = {
|
|
1573
|
+
N: 16384,
|
|
1574
|
+
r: 8,
|
|
1575
|
+
p: 1
|
|
1576
|
+
};
|
|
1494
1577
|
/**
|
|
1495
1578
|
* Password requirements:
|
|
1496
1579
|
* - Minimum 8 characters
|
|
@@ -1515,7 +1598,7 @@ function validatePasswordStrength(password) {
|
|
|
1515
1598
|
*/
|
|
1516
1599
|
async function hashPassword(password) {
|
|
1517
1600
|
const salt = randomBytes$1(SALT_LENGTH);
|
|
1518
|
-
const derivedKey = await scryptAsync(password, salt, KEY_LENGTH);
|
|
1601
|
+
const derivedKey = await scryptAsync(password, salt, KEY_LENGTH, SCRYPT_PARAMS);
|
|
1519
1602
|
return `${salt.toString("hex")}:${derivedKey.toString("hex")}`;
|
|
1520
1603
|
}
|
|
1521
1604
|
/**
|
|
@@ -1527,7 +1610,7 @@ async function verifyPassword(password, storedHash) {
|
|
|
1527
1610
|
if (!saltHex || !hashHex) return false;
|
|
1528
1611
|
const salt = Buffer.from(saltHex, "hex");
|
|
1529
1612
|
const storedKey = Buffer.from(hashHex, "hex");
|
|
1530
|
-
return timingSafeEqual(await scryptAsync(password, salt, KEY_LENGTH), storedKey);
|
|
1613
|
+
return timingSafeEqual(await scryptAsync(password, salt, KEY_LENGTH, SCRYPT_PARAMS), storedKey);
|
|
1531
1614
|
}
|
|
1532
1615
|
//#endregion
|
|
1533
1616
|
//#region src/auth/auth-hooks.ts
|
|
@@ -1686,7 +1769,23 @@ function createRateLimiter(options = {}) {
|
|
|
1686
1769
|
};
|
|
1687
1770
|
}
|
|
1688
1771
|
/**
|
|
1689
|
-
*
|
|
1772
|
+
* The address the socket is actually connected to, or `undefined` on a runtime
|
|
1773
|
+
* that cannot say. Unforgeable, unlike every header below.
|
|
1774
|
+
*
|
|
1775
|
+
* `getConnInfo` is Node-specific and throws elsewhere, so the failure is
|
|
1776
|
+
* swallowed: a runtime without connection info falls back to the shared bucket,
|
|
1777
|
+
* which is a availability trade the caller cannot exploit.
|
|
1778
|
+
*/
|
|
1779
|
+
function socketAddress(c) {
|
|
1780
|
+
try {
|
|
1781
|
+
return getConnInfo(c).remote.address;
|
|
1782
|
+
} catch {
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Default key generator: the client's address, from the most trustworthy source
|
|
1788
|
+
* this deployment has.
|
|
1690
1789
|
*
|
|
1691
1790
|
* `X-Forwarded-For` is a client-writable header; only the entries appended by
|
|
1692
1791
|
* trusted reverse proxies can be believed. With `trustedProxyHops` proxies in
|
|
@@ -1695,8 +1794,20 @@ function createRateLimiter(options = {}) {
|
|
|
1695
1794
|
* client-supplied and must be ignored. This is what prevents a caller from
|
|
1696
1795
|
* spoofing `X-Forwarded-For` to spread its requests across many rate-limit keys.
|
|
1697
1796
|
*
|
|
1698
|
-
*
|
|
1699
|
-
*
|
|
1797
|
+
* `X-Real-IP` is the *same* kind of header and needs the same rule, which it
|
|
1798
|
+
* did not have: it was read unconditionally, including under
|
|
1799
|
+
* `trustedProxyHops === 0` — the mode whose entire meaning is "no proxy is in
|
|
1800
|
+
* front of me". With no proxy there, nothing writes `X-Real-IP` except the
|
|
1801
|
+
* caller, so the key was theirs to choose: one header per request bought an
|
|
1802
|
+
* unlimited number of buckets, and the limiters on login, registration and
|
|
1803
|
+
* password reset counted to one. The reasoning had been done carefully for one
|
|
1804
|
+
* spelling of a proxy header and not carried to its twin.
|
|
1805
|
+
*
|
|
1806
|
+
* So `X-Real-IP` is now believed only where a proxy is declared to exist. With
|
|
1807
|
+
* none, the connection's own address is used — unforgeable, and available
|
|
1808
|
+
* because the server runs on `@hono/node-server`. `"unknown"` is the last
|
|
1809
|
+
* resort only, and it is a single shared bucket by design: better that
|
|
1810
|
+
* anonymous callers throttle each other than that any of them throttles nobody.
|
|
1700
1811
|
*/
|
|
1701
1812
|
function defaultKeyGenerator(c, trustedProxyHops = resolveTrustedProxyHops()) {
|
|
1702
1813
|
if (trustedProxyHops > 0) {
|
|
@@ -1705,8 +1816,10 @@ function defaultKeyGenerator(c, trustedProxyHops = resolveTrustedProxyHops()) {
|
|
|
1705
1816
|
const ips = forwardedFor.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1706
1817
|
if (ips.length > 0) return ips[Math.max(0, ips.length - trustedProxyHops)];
|
|
1707
1818
|
}
|
|
1819
|
+
const realIp = c.req.header("x-real-ip");
|
|
1820
|
+
if (realIp) return realIp;
|
|
1708
1821
|
}
|
|
1709
|
-
return c
|
|
1822
|
+
return socketAddress(c) ?? "unknown";
|
|
1710
1823
|
}
|
|
1711
1824
|
/**
|
|
1712
1825
|
* Pre-configured rate limiter for general auth endpoints (login, register).
|
|
@@ -6168,7 +6281,7 @@ function mountSessionRoutes(opts) {
|
|
|
6168
6281
|
clearRefreshCookie(c, config.cookieAuth);
|
|
6169
6282
|
const accessToken = extractBearerToken(c.req.header("authorization"));
|
|
6170
6283
|
if (ops.afterLogout && accessToken !== void 0) {
|
|
6171
|
-
const { verifyAccessToken } = await import("./jwt-
|
|
6284
|
+
const { verifyAccessToken } = await import("./jwt-CzeviDcB.js").then((n) => n.d);
|
|
6172
6285
|
const payload = verifyAccessToken(accessToken);
|
|
6173
6286
|
if (payload) ops.afterLogout(payload.uid).catch((err) => {
|
|
6174
6287
|
logger.error("[AuthHooks] afterLogout error", { error: err instanceof Error ? err.message : err });
|
|
@@ -6549,11 +6662,6 @@ function createAuthRoutes(config) {
|
|
|
6549
6662
|
newPassword: string().min(1, "New password is required").max(128)
|
|
6550
6663
|
});
|
|
6551
6664
|
const refreshSchema = object({ refreshToken: string().min(1).optional() });
|
|
6552
|
-
object({ refreshToken: string().optional() });
|
|
6553
|
-
object({
|
|
6554
|
-
displayName: string().max(255).optional(),
|
|
6555
|
-
photoURL: string().url().max(2048).optional()
|
|
6556
|
-
});
|
|
6557
6665
|
/** Parse a Zod schema against the request body, throwing ApiError on failure */
|
|
6558
6666
|
function parseBody(schema, body) {
|
|
6559
6667
|
const result = schema.safeParse(body);
|
|
@@ -8591,44 +8699,45 @@ function createApiKeyStore(driver) {
|
|
|
8591
8699
|
return;
|
|
8592
8700
|
}
|
|
8593
8701
|
const exec = (sqlText, options) => admin.executeSql(sqlText, options?.params ? { params: options.params } : void 0);
|
|
8702
|
+
const ddl = createDdlBootstrapper(exec, "api-key-store");
|
|
8594
8703
|
return {
|
|
8595
8704
|
async ensureTable() {
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
logger.
|
|
8628
|
-
|
|
8629
|
-
logger.error("❌ Failed to create API keys table", { error: err });
|
|
8630
|
-
logger.warn("⚠️ Continuing without API keys support.");
|
|
8705
|
+
await ddl.ensureObject("Creating schema rebase", "CREATE SCHEMA IF NOT EXISTS rebase");
|
|
8706
|
+
await ddl.ensureObject(`Creating ${TABLE}`, `
|
|
8707
|
+
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
8708
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
8709
|
+
name TEXT NOT NULL,
|
|
8710
|
+
key_prefix TEXT NOT NULL,
|
|
8711
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
8712
|
+
permissions JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
8713
|
+
admin BOOLEAN NOT NULL DEFAULT FALSE,
|
|
8714
|
+
rate_limit INTEGER,
|
|
8715
|
+
created_by TEXT NOT NULL,
|
|
8716
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
8717
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
8718
|
+
last_used_at TIMESTAMPTZ,
|
|
8719
|
+
expires_at TIMESTAMPTZ,
|
|
8720
|
+
revoked_at TIMESTAMPTZ
|
|
8721
|
+
)
|
|
8722
|
+
`);
|
|
8723
|
+
await ddl.ensureObject("Creating idx_api_keys_hash", `
|
|
8724
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_hash
|
|
8725
|
+
ON ${TABLE}(key_hash)
|
|
8726
|
+
`);
|
|
8727
|
+
await ddl.ensureObject("Creating idx_api_keys_prefix", `
|
|
8728
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_prefix
|
|
8729
|
+
ON ${TABLE}(key_prefix)
|
|
8730
|
+
`);
|
|
8731
|
+
await ddl.ensureObject(`Adding ${TABLE}.admin`, `
|
|
8732
|
+
ALTER TABLE ${TABLE}
|
|
8733
|
+
ADD COLUMN IF NOT EXISTS admin BOOLEAN NOT NULL DEFAULT FALSE
|
|
8734
|
+
`);
|
|
8735
|
+
if (!await ddl.isReadable(TABLE)) {
|
|
8736
|
+
logger.error(`❌ [api-key-store] ${TABLE} is unavailable — every API-key authenticated request will be rejected on this instance, and the table could not be taken back off the end-user role.`);
|
|
8737
|
+
return;
|
|
8631
8738
|
}
|
|
8739
|
+
await ddl.step("Revoking end-user access to api_keys", () => exec(revokeInternalTableSql("rebase", "api_keys")));
|
|
8740
|
+
logger.info("✅ API keys table ready");
|
|
8632
8741
|
},
|
|
8633
8742
|
async createApiKey(request, createdBy) {
|
|
8634
8743
|
const plaintext = generateApiKey();
|
|
@@ -8939,6 +9048,7 @@ var auth_exports = /* @__PURE__ */ __exportAll({
|
|
|
8939
9048
|
hashToken: () => hashToken,
|
|
8940
9049
|
httpMethodToOperation: () => httpMethodToOperation,
|
|
8941
9050
|
isApiKeyToken: () => isApiKeyToken,
|
|
9051
|
+
isJwtConfigured: () => isJwtConfigured,
|
|
8942
9052
|
isOperationAllowed: () => isOperationAllowed,
|
|
8943
9053
|
mountMagicLinkRoutes: () => mountMagicLinkRoutes,
|
|
8944
9054
|
optionalAuth: () => optionalAuth,
|
|
@@ -8956,6 +9066,6 @@ var auth_exports = /* @__PURE__ */ __exportAll({
|
|
|
8956
9066
|
verifyPassword: () => verifyPassword
|
|
8957
9067
|
});
|
|
8958
9068
|
//#endregion
|
|
8959
|
-
export {
|
|
9069
|
+
export { safeCompare as $, getEmailVerificationTemplate as A, optionalAuth as B, createDataRateLimiter as C, validatePasswordStrength as D, hashPassword as E, createAdapterAuthMiddleware as F, InvalidStorageKeyError as G, queryTokenAuth as H, createAuthMiddleware as I, createFunctionApiKeyGuard as J, canonicalStorageKey as K, createRequireAuth as L, getPasswordResetTemplate as M, getUserInvitationTemplate as N, verifyPassword as O, getWelcomeEmailTemplate as P, extractBearerToken as Q, extractUserFromToken as R, _coercedNumber as S, resolveAuthHooks as T, requireAdmin as U, publicObjectAuth as V, requireAuth as W, isApiKeyToken as X, createStorageApiKeyGuard as Y, validateApiKey as Z, createBuiltinAuthAdapter as _, createSpotifyProvider as a, object as b, createGitLabProvider as c, createFacebookProvider as d, scopeDataDriver as et, createAppleProvider as f, createGoogleProvider as g, createLinkedinProvider as h, createApiKeyStore as i, isPublicStoragePath as it, getMagicLinkTemplate as j, generateSecurePassword as k, createDiscordProvider as l, createGitHubProvider as m, createCustomAuthAdapter as n, isOperationAllowed as nt, createSlackProvider as o, createMicrosoftProvider as p, createApiKeyPreAuth as q, createApiKeyRoutes as r, PUBLIC_STORAGE_PREFIX as rt, createBitbucketProvider as s, auth_exports as t, httpMethodToOperation as tt, createTwitterProvider as u, ZodNumber as v, MemoryRateLimitStore as w, string as x, _enum as y, fileTokenAuth as z };
|
|
8960
9070
|
|
|
8961
|
-
//# sourceMappingURL=auth-
|
|
9071
|
+
//# sourceMappingURL=auth-BANhRhN9.js.map
|