@rebasepro/server 0.24.0 → 0.24.1-canary.gcb918fc
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/README.md +27 -2
- package/dist/GCSStorageController-fv6JAZ87.js +402 -0
- package/dist/GCSStorageController-fv6JAZ87.js.map +1 -0
- package/dist/S3StorageController-BNg-pXiY.js +480 -0
- package/dist/S3StorageController-BNg-pXiY.js.map +1 -0
- package/dist/api/mount-path.d.ts +30 -0
- package/dist/api/mount.d.ts +6 -13
- package/dist/api/rest/api-generator.d.ts +53 -1
- package/dist/api/rest/auth-collection-writes.d.ts +28 -21
- package/dist/api/rest/field-access-query.d.ts +28 -8
- package/dist/api/rest/field-ops.d.ts +19 -7
- package/dist/api/rest/query-body.d.ts +10 -0
- package/dist/api/rest/write-validation.d.ts +7 -5
- package/dist/api/schema-editor-routes.d.ts +10 -1
- package/dist/api/sql-export-routes.d.ts +8 -0
- package/dist/api/types.d.ts +35 -0
- package/dist/{ast-schema-editor-CWqS_sLJ.js → ast-schema-editor-mCeSC03x.js} +3 -3
- package/dist/{ast-schema-editor-CWqS_sLJ.js.map → ast-schema-editor-mCeSC03x.js.map} +1 -1
- package/dist/auth/access.d.ts +57 -8
- package/dist/auth/admin-user-ops.d.ts +24 -2
- package/dist/auth/admin-users-route.d.ts +1 -0
- package/dist/auth/api-keys/api-key-middleware.d.ts +10 -3
- package/dist/auth/api-keys/api-key-routes.d.ts +4 -1
- package/dist/auth/builtin-auth-adapter.d.ts +13 -0
- package/dist/auth/email-change-routes.d.ts +3 -0
- package/dist/auth/expired-token-sweep.d.ts +5 -1
- package/dist/auth/interfaces.d.ts +10 -0
- package/dist/auth/jwks-routes.d.ts +5 -1
- package/dist/auth/jwt-crypto.d.ts +7 -0
- package/dist/auth/jwt-keys.d.ts +4 -0
- package/dist/auth/jwt.d.ts +282 -166
- package/dist/auth/magic-link-routes.d.ts +3 -0
- package/dist/auth/mfa-crypto.d.ts +9 -3
- package/dist/auth/mfa-gate.d.ts +4 -2
- package/dist/auth/mfa-routes.d.ts +6 -0
- package/dist/auth/middleware.d.ts +9 -2
- package/dist/auth/otp-routes.d.ts +3 -0
- package/dist/auth/rate-limiter.d.ts +61 -34
- package/dist/auth/registration-policy.d.ts +17 -0
- package/dist/auth/require-auth.d.ts +1 -1
- package/dist/auth/routes.d.ts +16 -0
- package/dist/auth/session-routes.d.ts +6 -0
- package/dist/auth/user-picking.d.ts +37 -0
- package/dist/auth/verify-credential.d.ts +25 -4
- package/dist/{auth-DMLngxn_.js → auth-GnONzteu.js} +2869 -1871
- package/dist/auth-GnONzteu.js.map +1 -0
- package/dist/auth_adapter-CIItCTc8.js +21 -0
- package/dist/auth_adapter-CIItCTc8.js.map +1 -0
- package/dist/backend/create-backend.d.ts +281 -0
- package/dist/backend/default-backend.d.ts +35 -0
- package/dist/backend/options.d.ts +912 -0
- package/dist/backend/scope.d.ts +23 -0
- package/dist/backend/services.d.ts +54 -0
- package/dist/backend-DTAOsLQc.js.map +1 -1
- package/dist/backup/backup-common.d.ts +14 -3
- package/dist/backup/backup-routes.d.ts +23 -2
- package/dist/backup/backup-storage.d.ts +3 -1
- package/dist/backup/index.d.ts +3 -1
- package/dist/{backup-CN0s50D2.js → backup-BnvkSLHi.js} +176 -35
- package/dist/backup-BnvkSLHi.js.map +1 -0
- package/dist/boot/boot.d.ts +2 -2
- package/dist/boot/driver.d.ts +3 -0
- package/dist/boot/env.d.ts +9 -2
- package/dist/boot/http-server.d.ts +22 -0
- package/dist/boot/options.d.ts +16 -1
- package/dist/boot/provision.d.ts +4 -2
- package/dist/boot/runtime-cors.d.ts +17 -0
- package/dist/boot/sources.d.ts +2 -0
- package/dist/boot/static-routing.d.ts +9 -0
- package/dist/{contract-routes-fz8i4pxs.js → contract-routes-Ey6ftnKH.js} +4 -20
- package/dist/contract-routes-Ey6ftnKH.js.map +1 -0
- package/dist/cron/cron-loader.d.ts +77 -18
- package/dist/cron/cron-routes.d.ts +1 -1
- package/dist/cron/cron-scheduler.d.ts +69 -3
- package/dist/cron/cron-store.d.ts +33 -5
- package/dist/cron/define-cron.d.ts +6 -2
- package/dist/cron/index.d.ts +3 -3
- package/dist/cron-loader-B-u0snqq.js +202 -0
- package/dist/cron-loader-B-u0snqq.js.map +1 -0
- package/dist/{cron-routes-Bc-SB0Se.js → cron-routes-OD6BstUj.js} +27 -4
- package/dist/cron-routes-OD6BstUj.js.map +1 -0
- package/dist/{cron-scheduler-CYQgco86.js → cron-scheduler-idtLPoN-.js} +299 -33
- package/dist/cron-scheduler-idtLPoN-.js.map +1 -0
- package/dist/cron-store-CZ30ZBi-.js +577 -0
- package/dist/cron-store-CZ30ZBi-.js.map +1 -0
- package/dist/email/smtp-email-service.d.ts +23 -0
- package/dist/email/types.d.ts +18 -0
- package/dist/env.d.ts +0 -37
- package/dist/filter-dialect-O1HK6Nvr.js +1628 -0
- package/dist/filter-dialect-O1HK6Nvr.js.map +1 -0
- package/dist/{function-loader-D7o5Epjj.js → function-loader-iKhOZatS.js} +68 -38
- package/dist/function-loader-iKhOZatS.js.map +1 -0
- package/dist/{function-routes-CaNG4waN.js → function-routes-j9TwpvQe.js} +5 -4
- package/dist/function-routes-j9TwpvQe.js.map +1 -0
- package/dist/functions/context.d.ts +9 -0
- package/dist/functions/define-function.d.ts +10 -6
- package/dist/functions/function-loader.d.ts +75 -18
- package/dist/functions/function-routes.d.ts +2 -1
- package/dist/functions/index.d.ts +5 -4
- package/dist/functions/index.js +42 -13
- package/dist/functions/index.js.map +1 -1
- package/dist/functions/internal.d.ts +2 -2
- package/dist/functions/proxy.d.ts +6 -2
- package/dist/functions/request-timeout.d.ts +7 -5
- package/dist/history/history-routes.d.ts +9 -0
- package/dist/history/retention-sweep.d.ts +81 -0
- package/dist/{history-recorder-Nr8zLvoU.js → history-recorder-BfWRwmPy.js} +3 -3
- package/dist/{history-recorder-Nr8zLvoU.js.map → history-recorder-BfWRwmPy.js.map} +1 -1
- package/dist/{history-store-rcAm_xFR.js → history-store-DPofGVDp.js} +2 -2
- package/dist/{history-store-rcAm_xFR.js.map → history-store-DPofGVDp.js.map} +1 -1
- package/dist/index.d.ts +23 -10
- package/dist/index.es.js +24423 -20038
- package/dist/index.es.js.map +1 -1
- package/dist/init/docs.d.ts +27 -10
- package/dist/init/process-safety.d.ts +7 -2
- package/dist/init/shutdown.d.ts +18 -3
- package/dist/init.d.ts +49 -865
- package/dist/internal-tables-L2t39RdT.js +136 -0
- package/dist/internal-tables-L2t39RdT.js.map +1 -0
- package/dist/jobs/job-queue.d.ts +8 -0
- package/dist/jobs/job-store.d.ts +24 -0
- package/dist/jobs/types.d.ts +25 -4
- package/dist/{jobs-DqYNfquG.js → jobs-BCbflZj2.js} +234 -24
- package/dist/jobs-BCbflZj2.js.map +1 -0
- package/dist/{jwt-R6bSPMjk.js → jwt-BlvuKoR6.js} +529 -454
- package/dist/jwt-BlvuKoR6.js.map +1 -0
- package/dist/{keys-GAVZqbqx.js → keys-COzQ2aE8.js} +2 -2
- package/dist/{keys-GAVZqbqx.js.map → keys-COzQ2aE8.js.map} +1 -1
- package/dist/load-failure-L5hvfDoG.js +80 -0
- package/dist/load-failure-L5hvfDoG.js.map +1 -0
- package/dist/mcp/mcp-routes.d.ts +6 -0
- package/dist/mcp/mcp-tools.d.ts +8 -0
- package/dist/mcp/oauth-routes.d.ts +7 -0
- package/dist/metrics/index.d.ts +18 -0
- package/dist/metrics/pool-wait.d.ts +50 -0
- package/dist/offline-QNQgsPOq-B2yGeXkn.js +3305 -0
- package/dist/offline-QNQgsPOq-B2yGeXkn.js.map +1 -0
- package/dist/offline-store-Cm5sAX81-CALiO4Gn.js +1915 -0
- package/dist/offline-store-Cm5sAX81-CALiO4Gn.js.map +1 -0
- package/dist/{openapi-generator-DAq_XVDu.js → openapi-generator-D9zUQmEp.js} +366 -82
- package/dist/openapi-generator-D9zUQmEp.js.map +1 -0
- package/dist/policy-PdzX-Opd.js +121 -0
- package/dist/policy-PdzX-Opd.js.map +1 -0
- package/dist/project_manifest-R6y_KHms.js +59 -0
- package/dist/project_manifest-R6y_KHms.js.map +1 -0
- package/dist/{proxy-qRlqeUmO.js → proxy-CkTlMGmh.js} +4 -3
- package/dist/proxy-CkTlMGmh.js.map +1 -0
- package/dist/{query-parser-BgiKJKvc.js → query-parser-4WkMoA_K.js} +510 -15
- package/dist/query-parser-4WkMoA_K.js.map +1 -0
- package/dist/{request-timeout-DgH7j8qO.js → request-timeout-Dnl2_OBc.js} +15 -8
- package/dist/request-timeout-Dnl2_OBc.js.map +1 -0
- package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
- package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
- package/dist/schema-edit/apply-schema-change.d.ts +10 -0
- package/dist/schema-edit/local-source.d.ts +29 -0
- package/dist/schema-edit/remote-source.d.ts +13 -3
- package/dist/schema-edit/source-editor.d.ts +28 -0
- package/dist/{schema-editor-routes-oIyuWl3L.js → schema-editor-routes-C11Tumyd.js} +54 -10
- package/dist/schema-editor-routes-C11Tumyd.js.map +1 -0
- package/dist/services/outbound-post.d.ts +50 -0
- package/dist/services/outbound-url-guard.d.ts +34 -5
- package/dist/services/routed-realtime-service.d.ts +5 -0
- package/dist/services/webhook-service.d.ts +66 -14
- package/dist/singleton.d.ts +9 -5
- package/dist/soft-delete-params-DfV3vVN5.js +668 -0
- package/dist/soft-delete-params-DfV3vVN5.js.map +1 -0
- package/dist/sql-export-routes-ClRwzIsC.js +240 -0
- package/dist/sql-export-routes-ClRwzIsC.js.map +1 -0
- package/dist/{src-CatHFUym.js → src-D5Y3N-S4.js} +2 -1
- package/dist/{src-CatHFUym.js.map → src-D5Y3N-S4.js.map} +1 -1
- package/dist/storage/GCSStorageController.d.ts +46 -2
- package/dist/storage/LocalStorageController.d.ts +67 -2
- package/dist/storage/S3StorageController.d.ts +37 -3
- package/dist/storage/image-transform.d.ts +5 -0
- package/dist/storage/maintenance-routes.d.ts +23 -0
- package/dist/storage/maintenance.d.ts +126 -0
- package/dist/storage/rendition-cache.d.ts +9 -11
- package/dist/storage/request-principals.d.ts +30 -0
- package/dist/storage/routes.d.ts +20 -0
- package/dist/storage/source-variables.d.ts +10 -0
- package/dist/storage/streams.d.ts +42 -0
- package/dist/storage/tus-handler.d.ts +46 -2
- package/dist/storage/types.d.ts +82 -7
- package/dist/storage/upload-validation.d.ts +13 -0
- package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
- package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
- package/dist/strings.es-BeZWAodn.js +256 -0
- package/dist/strings.es-BeZWAodn.js.map +1 -0
- package/dist/upload-validation-Dd-fnsbA.js +172 -0
- package/dist/upload-validation-Dd-fnsbA.js.map +1 -0
- package/dist/utils/byte-size.d.ts +10 -0
- package/dist/utils/compression.d.ts +6 -0
- package/dist/utils/load-failure.d.ts +14 -0
- package/package.json +5 -5
- package/dist/GCSStorageController-BSiP1c-f.js +0 -256
- package/dist/GCSStorageController-BSiP1c-f.js.map +0 -1
- package/dist/S3StorageController-CAwFRgjV.js +0 -276
- package/dist/S3StorageController-CAwFRgjV.js.map +0 -1
- package/dist/auth-DMLngxn_.js.map +0 -1
- package/dist/backup-CN0s50D2.js.map +0 -1
- package/dist/contract-routes-fz8i4pxs.js.map +0 -1
- package/dist/cron-loader-CwaANlOG.js +0 -118
- package/dist/cron-loader-CwaANlOG.js.map +0 -1
- package/dist/cron-routes-Bc-SB0Se.js.map +0 -1
- package/dist/cron-scheduler-CYQgco86.js.map +0 -1
- package/dist/cron-store-D2Q9-Aco.js +0 -300
- package/dist/cron-store-D2Q9-Aco.js.map +0 -1
- package/dist/dynamic-import-X40pTZUQ.js +0 -23
- package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
- package/dist/function-loader-D7o5Epjj.js.map +0 -1
- package/dist/function-routes-CaNG4waN.js.map +0 -1
- package/dist/jobs-DqYNfquG.js.map +0 -1
- package/dist/jwt-R6bSPMjk.js.map +0 -1
- package/dist/openapi-generator-DAq_XVDu.js.map +0 -1
- package/dist/proxy-qRlqeUmO.js.map +0 -1
- package/dist/query-parser-BgiKJKvc.js.map +0 -1
- package/dist/request-timeout-DgH7j8qO.js.map +0 -1
- package/dist/schema-editor-routes-oIyuWl3L.js.map +0 -1
- package/dist/soft-delete-params-BWPilMPF.js +0 -59
- package/dist/soft-delete-params-BWPilMPF.js.map +0 -1
- package/dist/src-I3aG1PcY.js +0 -6870
- package/dist/src-I3aG1PcY.js.map +0 -1
- package/dist/types-BfKcm9do.js +0 -39
- package/dist/types-BfKcm9do.js.map +0 -1
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* present the same functionality through the pluggable `AuthAdapter` contract.
|
|
11
11
|
*/
|
|
12
12
|
import type { AuthAdapter } from "@rebasepro/types";
|
|
13
|
+
import { type JwtService } from "./jwt.js";
|
|
14
|
+
import type { AuthLimiters } from "./rate-limiter.js";
|
|
15
|
+
import type { AccessControl } from "./access.js";
|
|
13
16
|
import type { CaptchaConfig } from "./captcha.js";
|
|
14
17
|
import type { AuthRepository, OAuthProvider } from "./interfaces.js";
|
|
15
18
|
import type { AuthHooks } from "./auth-hooks.js";
|
|
@@ -23,6 +26,16 @@ import type { EmailService, EmailConfig } from "../email/index.js";
|
|
|
23
26
|
export interface BuiltinAuthAdapterConfig {
|
|
24
27
|
/** The bootstrapper-provided auth repository (users, roles, tokens). */
|
|
25
28
|
authRepository: AuthRepository;
|
|
29
|
+
/**
|
|
30
|
+
* The backend this adapter signs and verifies tokens for —
|
|
31
|
+
* `backend.services.jwt`, or a service of your own from `createJwtService`.
|
|
32
|
+
* The process default (what `configureJwt` sets) when omitted.
|
|
33
|
+
*/
|
|
34
|
+
jwt?: JwtService;
|
|
35
|
+
/** The backend's auth limiters, counting in its own store. The process default's set when omitted. */
|
|
36
|
+
limiters?: AuthLimiters;
|
|
37
|
+
/** The backend's access model, which the default role is checked against. The process default when omitted. */
|
|
38
|
+
access?: AccessControl;
|
|
26
39
|
/** Email service for password resets, verification, etc. */
|
|
27
40
|
emailService?: EmailService;
|
|
28
41
|
/** Email configuration. */
|
|
@@ -26,6 +26,7 @@ import { z } from "zod";
|
|
|
26
26
|
import type { HonoEnv } from "../api/types.js";
|
|
27
27
|
import type { MiddlewareHandler } from "hono";
|
|
28
28
|
import type { resolveAuthHooks } from "./auth-hooks.js";
|
|
29
|
+
import type { AuthLimiters } from "./rate-limiter.js";
|
|
29
30
|
import type { AuthModuleConfig } from "./routes.js";
|
|
30
31
|
/** How long an address-change link stays usable. */
|
|
31
32
|
export declare const EMAIL_CHANGE_TTL_MS: number;
|
|
@@ -36,6 +37,8 @@ interface EmailChangeRoutesConfig {
|
|
|
36
37
|
parseBody: <T>(schema: z.ZodSchema<T>, body: unknown) => T;
|
|
37
38
|
/** A signed-in user, with the revocation watermark consulted. */
|
|
38
39
|
requireLiveSession: MiddlewareHandler<HonoEnv>;
|
|
40
|
+
/** The backend's auth limiters. */
|
|
41
|
+
limiters: AuthLimiters;
|
|
39
42
|
}
|
|
40
43
|
export declare function mountEmailChangeRoutes(opts: EmailChangeRoutesConfig): void;
|
|
41
44
|
export {};
|
|
@@ -40,7 +40,11 @@ export type ExpiredTokenSweepOutcome = "swept" | "claimed-elsewhere" | "unclaime
|
|
|
40
40
|
export interface ExpiredTokenSweep {
|
|
41
41
|
/** Sweep now, then once per interval. */
|
|
42
42
|
start(): void;
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* No more ticks. Resolves once a tick already running has finished, so a
|
|
45
|
+
* caller about to close the pool can wait for the tick still using it.
|
|
46
|
+
*/
|
|
47
|
+
stop(): Promise<void>;
|
|
44
48
|
/** One tick: claim this interval's slot and, if it is ours, sweep. */
|
|
45
49
|
runSlot(): Promise<ExpiredTokenSweepOutcome>;
|
|
46
50
|
}
|
|
@@ -324,6 +324,16 @@ export interface UserRepository {
|
|
|
324
324
|
* List users with server-side pagination, search, and sorting.
|
|
325
325
|
*/
|
|
326
326
|
listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult>;
|
|
327
|
+
/**
|
|
328
|
+
* The ids of at most `limit` users, in no particular order. Answers "is
|
|
329
|
+
* there any user?" (`limit` 1) and "is this the only one?" (`limit` 2).
|
|
330
|
+
*
|
|
331
|
+
* `listUsersPaginated` answered those by counting the whole table and
|
|
332
|
+
* sorting it, on every anonymous `GET /auth/config` and every sign-up.
|
|
333
|
+
* Optional: a repository without it is asked through `listUsersPaginated`
|
|
334
|
+
* instead (`isUserTableEmpty` / `isOnlyUser` in `registration-policy.ts`).
|
|
335
|
+
*/
|
|
336
|
+
firstUserIds?(limit: number): Promise<string[]>;
|
|
327
337
|
/**
|
|
328
338
|
* Update user's password hash. `null` removes the password: the account
|
|
329
339
|
* then signs in only by link, code or provider.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import type { HonoEnv } from "../api/types.js";
|
|
3
|
+
import { type JwtService } from "./jwt.js";
|
|
3
4
|
/**
|
|
4
5
|
* `GET /.well-known/jwks.json` — the public keys that verify this issuer's
|
|
5
6
|
* access tokens.
|
|
@@ -13,5 +14,8 @@ import type { HonoEnv } from "../api/types.js";
|
|
|
13
14
|
* where every verifier looks — an issuer of `https://api.example.com` implies
|
|
14
15
|
* `https://api.example.com/.well-known/jwks.json`, whatever the API happens to
|
|
15
16
|
* be prefixed with.
|
|
17
|
+
*
|
|
18
|
+
* `jwt` is the issuer whose keys these are — a backend's own
|
|
19
|
+
* (`backend.services.jwt`), or the process default when omitted.
|
|
16
20
|
*/
|
|
17
|
-
export declare function createJwksRoutes(): Hono<HonoEnv>;
|
|
21
|
+
export declare function createJwksRoutes(jwt?: JwtService): Hono<HonoEnv>;
|
|
@@ -74,6 +74,13 @@ export declare function signJwt(payload: JwtClaims, key: JwtSigningKey, options:
|
|
|
74
74
|
export declare function verifyJwt(token: string, key: JwtVerificationKey, options: {
|
|
75
75
|
algorithms: string[];
|
|
76
76
|
}): Promise<JwtClaims>;
|
|
77
|
+
/**
|
|
78
|
+
* Whether a {@link verifyJwt} rejection says only that the token's `exp` has
|
|
79
|
+
* passed — the routine refusal that sends a client to refresh — rather than
|
|
80
|
+
* that the token is not one we signed. Here because the library decides how
|
|
81
|
+
* that is spelled.
|
|
82
|
+
*/
|
|
83
|
+
export declare function isExpiredJwtError(error: unknown): boolean;
|
|
77
84
|
/**
|
|
78
85
|
* The token's unverified header — `kid` and `alg` — or an empty object.
|
|
79
86
|
*
|
package/dist/auth/jwt-keys.d.ts
CHANGED
|
@@ -106,3 +106,7 @@ export declare function normalizePemFromEnv(value: string): string;
|
|
|
106
106
|
export declare function toJwks(keys: ResolvedJwtKey[]): {
|
|
107
107
|
keys: PublicJwk[];
|
|
108
108
|
};
|
|
109
|
+
/** A verification key, as `jsonwebtoken.verify` would import it, once per string. */
|
|
110
|
+
export declare function importedVerificationKey<K>(key: K): K | KeyObject;
|
|
111
|
+
/** A signing key, as `jsonwebtoken.sign` would import it, once per string. */
|
|
112
|
+
export declare function importedSigningKey<K>(key: K): K | KeyObject;
|
package/dist/auth/jwt.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import type { HonoEnv } from "../api/types.js";
|
|
1
3
|
import { type JwtSigningKeyConfig, type PublicJwk } from "./jwt-keys.js";
|
|
2
4
|
export interface JwtConfig {
|
|
3
5
|
secret: string;
|
|
@@ -115,59 +117,276 @@ export interface AccessTokenPayload {
|
|
|
115
117
|
*/
|
|
116
118
|
export declare function customClaimsOf(decoded: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
117
119
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
+
* How long a refresh token survives inactivity when no lifetime is configured.
|
|
121
|
+
* Sliding: every rotation re-ups it, so an active user stays signed in.
|
|
120
122
|
*/
|
|
121
|
-
export declare
|
|
123
|
+
export declare const DEFAULT_REFRESH_EXPIRES_IN = "30d";
|
|
122
124
|
/**
|
|
123
|
-
*
|
|
125
|
+
* One backend's tokens: what it signs with, what it verifies against, and how
|
|
126
|
+
* long what it mints lives.
|
|
124
127
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* The secret `configureJwt` was given, or undefined before it is called. For
|
|
135
|
-
* `mfa-crypto.ts`, which falls back to it when no MFA key is set — the secret
|
|
136
|
-
* passed in code, not only the one in `JWT_SECRET`.
|
|
128
|
+
* Every backend built by `createRebaseBackend` holds its own
|
|
129
|
+
* (`backend.services.jwt`), so two backends in one process sign and verify
|
|
130
|
+
* with their own secrets and keys and a token one minted means nothing to the
|
|
131
|
+
* other. {@link createJwtService} builds one by hand.
|
|
132
|
+
*
|
|
133
|
+
* The module-level functions of the same names — `generateAccessToken`,
|
|
134
|
+
* `verifyAccessToken`, … — answer for the process default instead: what
|
|
135
|
+
* {@link configureJwt} set, or the backend `initializeRebaseBackend` installed.
|
|
137
136
|
*/
|
|
138
|
-
export
|
|
139
|
-
/**
|
|
140
|
-
|
|
137
|
+
export interface JwtService {
|
|
138
|
+
/** The access-token lifetime, in the grammar {@link parseDurationMs} reads. */
|
|
139
|
+
readonly accessExpiresIn: string;
|
|
140
|
+
/** The refresh-token lifetime, in the grammar {@link parseDurationMs} reads. */
|
|
141
|
+
readonly refreshExpiresIn: string;
|
|
142
|
+
/**
|
|
143
|
+
* Has this service been given a JWT secret?
|
|
144
|
+
*
|
|
145
|
+
* False on every backend that authenticates through an adapter — Firebase,
|
|
146
|
+
* Clerk, anything with its own tokens — because nothing gives it one there.
|
|
147
|
+
* Signing paths still throw when it is false, since asking for a token from
|
|
148
|
+
* a server that cannot mint one is a mistake worth hearing about. Paths
|
|
149
|
+
* whose contract is *"tolerate the absence of auth"* must ask first:
|
|
150
|
+
* `optionalAuth` crashing a request because this backend does not do JWT is
|
|
151
|
+
* a 500 on a route that had already decided anonymous was acceptable.
|
|
152
|
+
*/
|
|
153
|
+
isConfigured(): boolean;
|
|
154
|
+
/** Is this service signing access tokens asymmetrically? */
|
|
155
|
+
hasAsymmetricSigningKey(): boolean;
|
|
156
|
+
/**
|
|
157
|
+
* The public keys, in JWKS form, for `/.well-known/jwks.json`.
|
|
158
|
+
*
|
|
159
|
+
* An empty `keys` array on a backend with no asymmetric keys configured is
|
|
160
|
+
* the correct answer rather than a 404: it says "this issuer publishes
|
|
161
|
+
* none", which a verifier can act on, where a 404 is indistinguishable from
|
|
162
|
+
* a misconfigured URL.
|
|
163
|
+
*/
|
|
164
|
+
getJwks(): {
|
|
165
|
+
keys: PublicJwk[];
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* The symmetric secret this service signs purpose-scoped tokens with, or
|
|
169
|
+
* undefined when it has none — for deriving a key of the backend's own.
|
|
170
|
+
* MFA's encryption falls back to it when no `MFA_ENCRYPTION_KEY` is set.
|
|
171
|
+
* A method rather than a field, so serialising the service never prints it.
|
|
172
|
+
*/
|
|
173
|
+
hmacSecret(): string | undefined;
|
|
174
|
+
/**
|
|
175
|
+
* Generate an access token (short-lived, 1 hour by default).
|
|
176
|
+
*
|
|
177
|
+
* @param isAnonymous Whether this session is a guest — anonymous sign-in
|
|
178
|
+
* rather than an account. Written into the token so the RLS identity and
|
|
179
|
+
* the WebSocket path can tell the two apart without a database lookup.
|
|
180
|
+
* @param sessionId The sign-in this token belongs to — see
|
|
181
|
+
* {@link AccessTokenPayload.sid}. Every token the auth routes mint
|
|
182
|
+
* carries one.
|
|
183
|
+
*/
|
|
184
|
+
generateAccessToken(uid: string, roles: string[], aal?: "aal1" | "aal2", customClaims?: Record<string, unknown>, isAnonymous?: boolean, sessionId?: string): Promise<string>;
|
|
185
|
+
/**
|
|
186
|
+
* Verify and decode an access token.
|
|
187
|
+
*
|
|
188
|
+
* Every token this service issues is signed with the same secret, so what a
|
|
189
|
+
* token *is* comes from its claims, not from its signature. A download
|
|
190
|
+
* token ({@link JwtService.generateDownloadToken}) is therefore a
|
|
191
|
+
* validly-signed string that must never authenticate anybody: it is scoped
|
|
192
|
+
* to one file path and handed out in URLs, which is a far weaker thing to
|
|
193
|
+
* hold than a session.
|
|
194
|
+
*
|
|
195
|
+
* Today it is rejected below for want of an id — but only by luck, since
|
|
196
|
+
* nothing stops a future download token from carrying one. So the purpose
|
|
197
|
+
* is checked explicitly: a token minted for reading a file is not a token
|
|
198
|
+
* for being a user.
|
|
199
|
+
*/
|
|
200
|
+
verifyAccessToken(token: string): Promise<AccessTokenPayload | null>;
|
|
201
|
+
/** The access-token lifetime in milliseconds. */
|
|
202
|
+
getAccessTokenExpiryMs(): number;
|
|
203
|
+
/** When an access token minted now expires, in milliseconds since the epoch. */
|
|
204
|
+
getAccessTokenExpiry(): number;
|
|
205
|
+
/**
|
|
206
|
+
* How long a refresh token is valid for, in milliseconds.
|
|
207
|
+
*
|
|
208
|
+
* Every rotation issues a token with a fresh TTL, so this is a sliding
|
|
209
|
+
* window: a user who visits at all keeps their session indefinitely, and
|
|
210
|
+
* one who disappears loses it this long after their last visit. That is
|
|
211
|
+
* what both Firebase and Supabase do by default, and it is the behaviour
|
|
212
|
+
* people mean when they say they expect to still be signed in.
|
|
213
|
+
*/
|
|
214
|
+
getRefreshTokenTtlMs(): number;
|
|
215
|
+
/** When a refresh token minted now expires. */
|
|
216
|
+
getRefreshTokenExpiry(): Date;
|
|
217
|
+
/**
|
|
218
|
+
* Mint the short-lived credential handed back with an `MFA_REQUIRED`
|
|
219
|
+
* response.
|
|
220
|
+
*
|
|
221
|
+
* Short-lived on purpose: it is the window in which a caller who has
|
|
222
|
+
* proven the first factor may present the second, not a session to be
|
|
223
|
+
* carried around. Five minutes matches the challenge TTL.
|
|
224
|
+
*/
|
|
225
|
+
generateMfaPendingToken(uid: string, expiresInSeconds?: number): Promise<string>;
|
|
226
|
+
/**
|
|
227
|
+
* Verify a pre-auth token and return the user it was minted for.
|
|
228
|
+
*
|
|
229
|
+
* Returns `null` for anything else — including a perfectly valid *access*
|
|
230
|
+
* token, which must not be interchangeable with this one in either
|
|
231
|
+
* direction.
|
|
232
|
+
*
|
|
233
|
+
* `iat` comes back with the uid because this token is a first factor that
|
|
234
|
+
* was accepted at that instant, and a sign-out everywhere after it has to
|
|
235
|
+
* void it the way it voids an access token — see `isAccessTokenRevoked`.
|
|
236
|
+
*/
|
|
237
|
+
verifyMfaPendingToken(token: string): Promise<{
|
|
238
|
+
uid: string;
|
|
239
|
+
iat?: number;
|
|
240
|
+
} | null>;
|
|
241
|
+
/**
|
|
242
|
+
* Generate a short-lived download token scoped to a specific file path or
|
|
243
|
+
* prefix *within one storage source*.
|
|
244
|
+
*
|
|
245
|
+
* Both halves of that scope are load-bearing. A key is only unique inside
|
|
246
|
+
* its own bucket, and a project with more than one source routinely holds
|
|
247
|
+
* the same key in several of them — `avatars/u1.png` in `(default)` and in
|
|
248
|
+
* `media` are different objects, quite possibly with different owners. A
|
|
249
|
+
* token that names only the path is therefore a grant on every source at
|
|
250
|
+
* once: authorize a read on the source whose `storageAuthorize` hook says
|
|
251
|
+
* yes, then spend the token against `?storageId=` pointing somewhere else.
|
|
252
|
+
*
|
|
253
|
+
* `storageId` is optional here only because omitting it *is* the default
|
|
254
|
+
* source, which is what the overwhelming majority of deployments have. A
|
|
255
|
+
* mint site that forgets to pass a named source produces a default-scoped
|
|
256
|
+
* token, which fails closed at `/file/*` rather than over-granting.
|
|
257
|
+
*
|
|
258
|
+
* @param principal The user minting it, so the reads it buys are charged
|
|
259
|
+
* to them.
|
|
260
|
+
*/
|
|
261
|
+
generateDownloadToken(path: string, expiresInSeconds?: number, storageId?: string | null, principal?: string | null): Promise<string>;
|
|
262
|
+
/**
|
|
263
|
+
* Verify and decode a download token.
|
|
264
|
+
*
|
|
265
|
+
* A token minted before `storageId` existed carries no such claim. It is
|
|
266
|
+
* read as a grant on the **default source** rather than on all of them:
|
|
267
|
+
* that is the fail-closed reading, and it is what such a token almost
|
|
268
|
+
* always was, since a named source has to be asked for explicitly. The
|
|
269
|
+
* cost is bounded by the five-minute TTL — for at most that long after a
|
|
270
|
+
* deploy, an in-flight token for a *named* source is refused and the
|
|
271
|
+
* client re-fetches `/metadata`.
|
|
272
|
+
*/
|
|
273
|
+
verifyDownloadToken(token: string): Promise<DownloadTokenPayload | null>;
|
|
274
|
+
/**
|
|
275
|
+
* Sign a short-lived token that carries state across a redirect.
|
|
276
|
+
*
|
|
277
|
+
* The generalisation of the MFA-pending token: a signed blob the server
|
|
278
|
+
* hands out and expects back, where the point is that the holder cannot
|
|
279
|
+
* edit it. The OAuth authorize flow needs one to carry the authorization
|
|
280
|
+
* request across the sign-in hop — without it, the endpoint the consent
|
|
281
|
+
* page posts to would mint a code for whatever `client_id` and
|
|
282
|
+
* `redirect_uri` its caller typed, skipping every check the authorize
|
|
283
|
+
* endpoint performs.
|
|
284
|
+
*
|
|
285
|
+
* The `purpose` is not decoration. {@link JwtService.verifyAccessToken}
|
|
286
|
+
* refuses any token that has one, so nothing minted here can be replayed
|
|
287
|
+
* as a session, and {@link JwtService.verifyPurposeToken} refuses a token
|
|
288
|
+
* whose purpose is not the one the caller named — so a token minted for
|
|
289
|
+
* one hop cannot be spent on another.
|
|
290
|
+
*/
|
|
291
|
+
signPurposeToken(purpose: string, claims: Record<string, unknown>, expiresInSeconds: number): Promise<string>;
|
|
292
|
+
/** Verify a token minted by {@link JwtService.signPurposeToken}, for one named purpose. */
|
|
293
|
+
verifyPurposeToken<T>(purpose: string, token: string): Promise<T | null>;
|
|
294
|
+
/**
|
|
295
|
+
* Mint an access token for the MCP endpoint.
|
|
296
|
+
*
|
|
297
|
+
* Deliberately **not** an ordinary session token, and the `purpose` claim
|
|
298
|
+
* is what enforces that: {@link JwtService.verifyAccessToken} refuses any
|
|
299
|
+
* token carrying one, so a credential issued to a third-party MCP client —
|
|
300
|
+
* Claude, an IDE, anything that completed the OAuth flow — cannot be
|
|
301
|
+
* replayed against `/api/data`, `/api/admin` or the websocket. It buys
|
|
302
|
+
* exactly the one resource it names.
|
|
303
|
+
*
|
|
304
|
+
* That asymmetry matters more than it first looks. These tokens are handed
|
|
305
|
+
* to software the user authorized but the operator never vetted, they
|
|
306
|
+
* live in that software's storage, and they are the only credential in
|
|
307
|
+
* this system minted for a third party. Without the purpose separation,
|
|
308
|
+
* "connect Claude to your data" would silently mean "give Claude a full
|
|
309
|
+
* session".
|
|
310
|
+
*
|
|
311
|
+
* The audience is the canonical resource URI, and
|
|
312
|
+
* {@link JwtService.verifyMcpAccessToken} requires the caller to say which
|
|
313
|
+
* resource it is checking for. A token minted for another Rebase project —
|
|
314
|
+
* or another path on this one — fails there.
|
|
315
|
+
*
|
|
316
|
+
* HS256 with the shared secret, like the download token: the authorization
|
|
317
|
+
* server and the resource server are the same process, so nothing outside
|
|
318
|
+
* it ever needs to verify one, and an asymmetric key here would be a
|
|
319
|
+
* second key surface bought for nobody.
|
|
320
|
+
*/
|
|
321
|
+
generateMcpAccessToken(payload: Omit<McpAccessTokenPayload, "purpose">, expiresInSeconds: number): Promise<string>;
|
|
322
|
+
/**
|
|
323
|
+
* Verify an MCP access token **for a specific resource**.
|
|
324
|
+
*
|
|
325
|
+
* `expectedAudience` has no default. The MCP specification requires a
|
|
326
|
+
* resource server to validate that a token was issued for it
|
|
327
|
+
* specifically, and a default would be the thing that quietly turns that
|
|
328
|
+
* check off — the caller has to say what it is, every time, so a new call
|
|
329
|
+
* site cannot forget by omission.
|
|
330
|
+
*/
|
|
331
|
+
verifyMcpAccessToken(token: string, expectedAudience: string): Promise<McpAccessTokenPayload | null>;
|
|
332
|
+
}
|
|
141
333
|
/**
|
|
142
|
-
*
|
|
334
|
+
* Build a {@link JwtService} from a configuration, refusing one it would sign
|
|
335
|
+
* badly with: a short or well-known secret, an `activeKid` no key carries, a
|
|
336
|
+
* lifetime that is not one.
|
|
143
337
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
338
|
+
* Unset lifetimes take the defaults — an hour for access tokens,
|
|
339
|
+
* {@link DEFAULT_REFRESH_EXPIRES_IN} for refresh tokens.
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```ts
|
|
343
|
+
* const jwt = createJwtService({ secret: process.env.JWT_SECRET! });
|
|
344
|
+
* const token = await jwt.generateAccessToken("u1", ["editor"]);
|
|
345
|
+
* const payload = await jwt.verifyAccessToken(token); // { uid: "u1", roles: ["editor"], … }
|
|
346
|
+
* ```
|
|
151
347
|
*/
|
|
152
|
-
export declare function
|
|
348
|
+
export declare function createJwtService(config: JwtConfig): JwtService;
|
|
153
349
|
/**
|
|
154
|
-
*
|
|
350
|
+
* Configure the process default's JWT settings — what `verifyAccessToken`,
|
|
351
|
+
* `generateAccessToken` and the other module-level functions sign and verify
|
|
352
|
+
* with.
|
|
353
|
+
*
|
|
354
|
+
* Validates the secret strength to prevent deployment with default/weak
|
|
355
|
+
* secrets. A lifetime left unset keeps the one already configured; a
|
|
356
|
+
* configuration that fails validation leaves the previous one standing rather
|
|
357
|
+
* than half-applying a new one.
|
|
155
358
|
*/
|
|
156
|
-
export declare function
|
|
359
|
+
export declare function configureJwt(config: JwtConfig): void;
|
|
157
360
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
361
|
+
* @internal The process default itself, to install another or to restore it.
|
|
362
|
+
* `setDefaultBackend` and tests use these; an application calls
|
|
363
|
+
* {@link configureJwt} or `setDefaultBackend`.
|
|
161
364
|
*/
|
|
162
|
-
|
|
365
|
+
export declare function defaultJwtService(): JwtService;
|
|
366
|
+
/** @internal See {@link defaultJwtService}. */
|
|
367
|
+
export declare function setDefaultJwtService(service: JwtService): void;
|
|
163
368
|
/**
|
|
164
|
-
* The
|
|
165
|
-
*
|
|
369
|
+
* The JWT service serving this request: the backend's whose route group is
|
|
370
|
+
* answering it, or the process default for a route mounted outside any
|
|
371
|
+
* backend.
|
|
166
372
|
*/
|
|
167
|
-
|
|
373
|
+
export declare function jwtOf(c: Context<HonoEnv>): JwtService;
|
|
374
|
+
/** See {@link JwtService.getJwks}. Answers for the process default. */
|
|
375
|
+
export declare function getJwks(): {
|
|
376
|
+
keys: PublicJwk[];
|
|
377
|
+
};
|
|
168
378
|
/**
|
|
169
|
-
*
|
|
379
|
+
* The secret {@link configureJwt} was given, or undefined before it is called.
|
|
380
|
+
* Answers for the process default — see {@link JwtService.hmacSecret}.
|
|
170
381
|
*/
|
|
382
|
+
export declare function configuredJwtSecret(): string | undefined;
|
|
383
|
+
/** See {@link JwtService.hasAsymmetricSigningKey}. Answers for the process default. */
|
|
384
|
+
export declare function hasAsymmetricSigningKey(): boolean;
|
|
385
|
+
/** See {@link JwtService.isConfigured}. Answers for the process default. */
|
|
386
|
+
export declare function isJwtConfigured(): boolean;
|
|
387
|
+
/** See {@link JwtService.generateAccessToken}. Answers for the process default. */
|
|
388
|
+
export declare function generateAccessToken(uid: string, roles: string[], aal?: "aal1" | "aal2", customClaims?: Record<string, unknown>, isAnonymous?: boolean, sessionId?: string): Promise<string>;
|
|
389
|
+
/** See {@link JwtService.getAccessTokenExpiryMs}. Answers for the process default. */
|
|
171
390
|
export declare function getAccessTokenExpiryMs(): number;
|
|
172
391
|
/**
|
|
173
392
|
* A lifetime setting in milliseconds, or null when it is not one.
|
|
@@ -181,28 +400,13 @@ export declare function getAccessTokenExpiryMs(): number;
|
|
|
181
400
|
* (ms reads "3600" as 3.6 seconds) and a lifetime of zero or less.
|
|
182
401
|
*/
|
|
183
402
|
export declare function parseDurationMs(value: string): number | null;
|
|
184
|
-
/**
|
|
185
|
-
* Get the expiration timestamp for an access token
|
|
186
|
-
*/
|
|
403
|
+
/** See {@link JwtService.getAccessTokenExpiry}. Answers for the process default. */
|
|
187
404
|
export declare function getAccessTokenExpiry(): number;
|
|
188
|
-
/**
|
|
189
|
-
* Verify and decode an access token.
|
|
190
|
-
*
|
|
191
|
-
* Every token this server issues is signed with the same secret, so what a
|
|
192
|
-
* token *is* comes from its claims, not from its signature. A download token
|
|
193
|
-
* ({@link generateDownloadToken}) is therefore a validly-signed string that
|
|
194
|
-
* must never authenticate anybody: it is scoped to one file path and handed out
|
|
195
|
-
* in URLs, which is a far weaker thing to hold than a session.
|
|
196
|
-
*
|
|
197
|
-
* Today it is rejected below for want of an id — but only by luck, since
|
|
198
|
-
* nothing stops a future download token from carrying one. So the purpose is
|
|
199
|
-
* checked explicitly: a token minted for reading a file is not a token for
|
|
200
|
-
* being a user.
|
|
201
|
-
*/
|
|
405
|
+
/** See {@link JwtService.verifyAccessToken}. Answers for the process default. */
|
|
202
406
|
export declare function verifyAccessToken(token: string): Promise<AccessTokenPayload | null>;
|
|
203
407
|
/**
|
|
204
|
-
* Generate a random refresh token. Long-lived:
|
|
205
|
-
* see {@link getRefreshTokenTtlMs}.
|
|
408
|
+
* Generate a random refresh token. Long-lived: 30 days by default, sliding —
|
|
409
|
+
* see {@link JwtService.getRefreshTokenTtlMs}.
|
|
206
410
|
*/
|
|
207
411
|
export declare function generateRefreshToken(): string;
|
|
208
412
|
/**
|
|
@@ -216,49 +420,24 @@ export declare function hashRefreshToken(token: string): Promise<string>;
|
|
|
216
420
|
* what is actually enforced, which is worse than knowing the ceiling.
|
|
217
421
|
*/
|
|
218
422
|
export declare const MAX_COOKIE_AGE_MS: number;
|
|
219
|
-
/**
|
|
220
|
-
* How long a refresh token is valid for, in milliseconds.
|
|
221
|
-
*
|
|
222
|
-
* Every rotation issues a token with a fresh TTL, so this is a sliding window:
|
|
223
|
-
* a user who visits at all keeps their session indefinitely, and one who
|
|
224
|
-
* disappears loses it this long after their last visit. That is what both
|
|
225
|
-
* Firebase and Supabase do by default, and it is the behaviour people mean
|
|
226
|
-
* when they say they expect to still be signed in.
|
|
227
|
-
*/
|
|
423
|
+
/** See {@link JwtService.getRefreshTokenTtlMs}. Answers for the process default. */
|
|
228
424
|
export declare function getRefreshTokenTtlMs(): number;
|
|
229
|
-
/**
|
|
230
|
-
* Calculate refresh token expiration date
|
|
231
|
-
*/
|
|
425
|
+
/** See {@link JwtService.getRefreshTokenExpiry}. Answers for the process default. */
|
|
232
426
|
export declare function getRefreshTokenExpiry(): Date;
|
|
233
427
|
/**
|
|
234
428
|
* The `purpose` claim carried by a credential that stands between "first factor
|
|
235
429
|
* accepted" and "session issued".
|
|
236
430
|
*
|
|
237
431
|
* A pre-auth token is NOT a session and must never be usable as one:
|
|
238
|
-
* {@link verifyAccessToken} refuses any token carrying a `purpose`,
|
|
239
|
-
* value cannot authenticate a request no matter which route it is
|
|
240
|
-
* The only thing that reads it is the MFA challenge pair, which
|
|
241
|
-
* plus a second factor — for a real session.
|
|
432
|
+
* {@link JwtService.verifyAccessToken} refuses any token carrying a `purpose`,
|
|
433
|
+
* so this value cannot authenticate a request no matter which route it is
|
|
434
|
+
* presented to. The only thing that reads it is the MFA challenge pair, which
|
|
435
|
+
* exchanges it — plus a second factor — for a real session.
|
|
242
436
|
*/
|
|
243
437
|
export declare const MFA_PENDING_PURPOSE = "mfa-pending";
|
|
244
|
-
/**
|
|
245
|
-
* Mint the short-lived credential handed back with an `MFA_REQUIRED` response.
|
|
246
|
-
*
|
|
247
|
-
* Short-lived on purpose: it is the window in which a caller who has proven the
|
|
248
|
-
* first factor may present the second, not a session to be carried around. Five
|
|
249
|
-
* minutes matches the challenge TTL.
|
|
250
|
-
*/
|
|
438
|
+
/** See {@link JwtService.generateMfaPendingToken}. Answers for the process default. */
|
|
251
439
|
export declare function generateMfaPendingToken(uid: string, expiresInSeconds?: number): Promise<string>;
|
|
252
|
-
/**
|
|
253
|
-
* Verify a pre-auth token and return the user it was minted for.
|
|
254
|
-
*
|
|
255
|
-
* Returns `null` for anything else — including a perfectly valid *access*
|
|
256
|
-
* token, which must not be interchangeable with this one in either direction.
|
|
257
|
-
*
|
|
258
|
-
* `iat` comes back with the uid because this token is a first factor that was
|
|
259
|
-
* accepted at that instant, and a sign-out everywhere after it has to void it
|
|
260
|
-
* the way it voids an access token — see `isAccessTokenRevoked`.
|
|
261
|
-
*/
|
|
440
|
+
/** See {@link JwtService.verifyMfaPendingToken}. Answers for the process default. */
|
|
262
441
|
export declare function verifyMfaPendingToken(token: string): Promise<{
|
|
263
442
|
uid: string;
|
|
264
443
|
iat?: number;
|
|
@@ -269,8 +448,8 @@ export interface DownloadTokenPayload {
|
|
|
269
448
|
/**
|
|
270
449
|
* The storage source the grant is good for, canonicalized by
|
|
271
450
|
* {@link canonicalStorageId}. Always present on a decoded payload; see
|
|
272
|
-
* {@link verifyDownloadToken} for how tokens minted before this
|
|
273
|
-
* existed are read.
|
|
451
|
+
* {@link JwtService.verifyDownloadToken} for how tokens minted before this
|
|
452
|
+
* claim existed are read.
|
|
274
453
|
*/
|
|
275
454
|
storageId: string;
|
|
276
455
|
/**
|
|
@@ -281,54 +460,15 @@ export interface DownloadTokenPayload {
|
|
|
281
460
|
*/
|
|
282
461
|
rl?: string;
|
|
283
462
|
}
|
|
284
|
-
/**
|
|
285
|
-
* Generate a short-lived download token scoped to a specific file path or prefix
|
|
286
|
-
* *within one storage source*.
|
|
287
|
-
*
|
|
288
|
-
* Both halves of that scope are load-bearing. A key is only unique inside its
|
|
289
|
-
* own bucket, and a project with more than one source routinely holds the same
|
|
290
|
-
* key in several of them — `avatars/u1.png` in `(default)` and in `media` are
|
|
291
|
-
* different objects, quite possibly with different owners. A token that names
|
|
292
|
-
* only the path is therefore a grant on every source at once: authorize a read
|
|
293
|
-
* on the source whose `storageAuthorize` hook says yes, then spend the token
|
|
294
|
-
* against `?storageId=` pointing somewhere else.
|
|
295
|
-
*
|
|
296
|
-
* `storageId` is optional here only because omitting it *is* the default
|
|
297
|
-
* source, which is what the overwhelming majority of deployments have. A mint
|
|
298
|
-
* site that forgets to pass a named source produces a default-scoped token,
|
|
299
|
-
* which fails closed at `/file/*` rather than over-granting.
|
|
300
|
-
*/
|
|
463
|
+
/** See {@link JwtService.generateDownloadToken}. Answers for the process default. */
|
|
301
464
|
export declare function generateDownloadToken(path: string, expiresInSeconds?: number, storageId?: string | null,
|
|
302
465
|
/** The user minting it, so the reads it buys are charged to them. */
|
|
303
466
|
principal?: string | null): Promise<string>;
|
|
304
|
-
/**
|
|
305
|
-
* Verify and decode a download token.
|
|
306
|
-
*
|
|
307
|
-
* A token minted before `storageId` existed carries no such claim. It is read
|
|
308
|
-
* as a grant on the **default source** rather than on all of them: that is the
|
|
309
|
-
* fail-closed reading, and it is what such a token almost always was, since a
|
|
310
|
-
* named source has to be asked for explicitly. The cost is bounded by the
|
|
311
|
-
* five-minute TTL — for at most that long after a deploy, an in-flight token
|
|
312
|
-
* for a *named* source is refused and the client re-fetches `/metadata`.
|
|
313
|
-
*/
|
|
467
|
+
/** See {@link JwtService.verifyDownloadToken}. Answers for the process default. */
|
|
314
468
|
export declare function verifyDownloadToken(token: string): Promise<DownloadTokenPayload | null>;
|
|
315
|
-
/**
|
|
316
|
-
* Sign a short-lived token that carries state across a redirect.
|
|
317
|
-
*
|
|
318
|
-
* The generalisation of the MFA-pending token: a signed blob the server hands
|
|
319
|
-
* out and expects back, where the point is that the holder cannot edit it. The
|
|
320
|
-
* OAuth authorize flow needs one to carry the authorization request across the
|
|
321
|
-
* sign-in hop — without it, the endpoint the consent page posts to would mint a
|
|
322
|
-
* code for whatever `client_id` and `redirect_uri` its caller typed, skipping
|
|
323
|
-
* every check the authorize endpoint performs.
|
|
324
|
-
*
|
|
325
|
-
* The `purpose` is not decoration. {@link verifyAccessToken} refuses any token
|
|
326
|
-
* that has one, so nothing minted here can be replayed as a session, and
|
|
327
|
-
* {@link verifyPurposeToken} refuses a token whose purpose is not the one the
|
|
328
|
-
* caller named — so a token minted for one hop cannot be spent on another.
|
|
329
|
-
*/
|
|
469
|
+
/** See {@link JwtService.signPurposeToken}. Answers for the process default. */
|
|
330
470
|
export declare function signPurposeToken(purpose: string, claims: Record<string, unknown>, expiresInSeconds: number): Promise<string>;
|
|
331
|
-
/**
|
|
471
|
+
/** See {@link JwtService.verifyPurposeToken}. Answers for the process default. */
|
|
332
472
|
export declare function verifyPurposeToken<T>(purpose: string, token: string): Promise<T | null>;
|
|
333
473
|
/** The `purpose` that marks a token as belonging to the MCP resource. */
|
|
334
474
|
export declare const MCP_ACCESS_PURPOSE = "mcp-access";
|
|
@@ -345,37 +485,13 @@ export interface McpAccessTokenPayload {
|
|
|
345
485
|
aud: string;
|
|
346
486
|
iss: string;
|
|
347
487
|
}
|
|
348
|
-
/**
|
|
349
|
-
* Mint an access token for the MCP endpoint.
|
|
350
|
-
*
|
|
351
|
-
* Deliberately **not** an ordinary session token, and the `purpose` claim is
|
|
352
|
-
* what enforces that: {@link verifyAccessToken} refuses any token carrying one,
|
|
353
|
-
* so a credential issued to a third-party MCP client — Claude, an IDE, anything
|
|
354
|
-
* that completed the OAuth flow — cannot be replayed against `/api/data`,
|
|
355
|
-
* `/api/admin` or the websocket. It buys exactly the one resource it names.
|
|
356
|
-
*
|
|
357
|
-
* That asymmetry matters more than it first looks. These tokens are handed to
|
|
358
|
-
* software the user authorized but the operator never vetted, they live in that
|
|
359
|
-
* software's storage, and they are the only credential in this system minted
|
|
360
|
-
* for a third party. Without the purpose separation, "connect Claude to your
|
|
361
|
-
* data" would silently mean "give Claude a full session".
|
|
362
|
-
*
|
|
363
|
-
* The audience is the canonical resource URI, and {@link verifyMcpAccessToken}
|
|
364
|
-
* requires the caller to say which resource it is checking for. A token minted
|
|
365
|
-
* for another Rebase project — or another path on this one — fails there.
|
|
366
|
-
*
|
|
367
|
-
* HS256 with the shared secret, like the download token: the authorization
|
|
368
|
-
* server and the resource server are the same process, so nothing outside it
|
|
369
|
-
* ever needs to verify one, and an asymmetric key here would be a second key
|
|
370
|
-
* surface bought for nobody.
|
|
371
|
-
*/
|
|
488
|
+
/** See {@link JwtService.generateMcpAccessToken}. Answers for the process default. */
|
|
372
489
|
export declare function generateMcpAccessToken(payload: Omit<McpAccessTokenPayload, "purpose">, expiresInSeconds: number): Promise<string>;
|
|
490
|
+
/** See {@link JwtService.verifyMcpAccessToken}. Answers for the process default. */
|
|
491
|
+
export declare function verifyMcpAccessToken(token: string, expectedAudience: string): Promise<McpAccessTokenPayload | null>;
|
|
373
492
|
/**
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
* server to validate that a token was issued for it specifically, and a default
|
|
378
|
-
* would be the thing that quietly turns that check off — the caller has to say
|
|
379
|
-
* what it is, every time, so a new call site cannot forget by omission.
|
|
493
|
+
* The process default as a service object that follows it — every call reads
|
|
494
|
+
* whatever {@link configureJwt} or `setDefaultBackend` installed last. What a
|
|
495
|
+
* route built outside any backend falls back to.
|
|
380
496
|
*/
|
|
381
|
-
export declare
|
|
497
|
+
export declare const processJwtService: JwtService;
|