@rebasepro/server 0.22.0 → 0.24.0
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 +1 -1
- package/bin/rebase-server.js +4 -2
- package/dist/{GCSStorageController-CLIJXwGS.js → GCSStorageController-BSiP1c-f.js} +57 -29
- package/dist/GCSStorageController-BSiP1c-f.js.map +1 -0
- package/dist/{S3StorageController-Dcuf8lMA.js → S3StorageController-CAwFRgjV.js} +19 -7
- package/dist/S3StorageController-CAwFRgjV.js.map +1 -0
- package/dist/api/ast-schema-editor.d.ts +127 -1
- package/dist/api/errors.d.ts +9 -0
- package/dist/api/live-schema-routes.d.ts +52 -8
- package/dist/api/logs-routes.d.ts +39 -1
- package/dist/api/openapi-generator.d.ts +17 -0
- package/dist/api/rest/api-generator.d.ts +158 -42
- package/dist/api/rest/auth-collection-writes.d.ts +85 -0
- package/dist/api/rest/field-access-query.d.ts +6 -2
- package/dist/api/rest/idempotency.d.ts +7 -1
- package/dist/api/rest/nested-write-access.d.ts +46 -0
- package/dist/api/rest/write-validation.d.ts +34 -2
- package/dist/api/types.d.ts +17 -1
- package/dist/{ast-schema-editor-CslO8Oje.js → ast-schema-editor-CWqS_sLJ.js} +411 -13
- package/dist/ast-schema-editor-CWqS_sLJ.js.map +1 -0
- package/dist/auth/access.d.ts +105 -0
- package/dist/auth/adapter-middleware.d.ts +2 -1
- package/dist/auth/address-ownership.d.ts +68 -0
- package/dist/auth/admin-roles-route.d.ts +4 -2
- package/dist/auth/admin-roles.d.ts +17 -20
- package/dist/auth/admin-user-ops.d.ts +35 -2
- package/dist/auth/admin-users-route.d.ts +1 -0
- package/dist/auth/api-keys/api-key-middleware.d.ts +56 -55
- package/dist/auth/api-keys/api-key-routes.d.ts +41 -11
- package/dist/auth/api-keys/api-key-store.d.ts +31 -8
- package/dist/auth/api-keys/api-key-types.d.ts +14 -16
- package/dist/auth/api-keys/http-operation.d.ts +19 -0
- package/dist/auth/api-keys/index.d.ts +11 -11
- package/dist/auth/api-keys/key-grant.d.ts +41 -0
- package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
- package/dist/auth/auth-hooks.d.ts +50 -7
- package/dist/auth/builtin-auth-adapter.d.ts +8 -0
- package/dist/auth/captcha.d.ts +5 -0
- package/dist/auth/cookie-utils.d.ts +7 -0
- package/dist/auth/deliverable-address.d.ts +6 -0
- package/dist/auth/email-change-routes.d.ts +41 -0
- package/dist/auth/expired-token-sweep.d.ts +67 -0
- package/dist/auth/impersonation.d.ts +110 -0
- package/dist/auth/index.d.ts +4 -2
- package/dist/auth/interfaces.d.ts +146 -65
- package/dist/auth/jwt.d.ts +66 -3
- package/dist/auth/magic-link-routes.d.ts +2 -6
- package/dist/auth/mfa-routes.d.ts +2 -9
- package/dist/auth/middleware.d.ts +17 -5
- package/dist/auth/oauth-signin-policy.d.ts +25 -8
- package/dist/auth/otp-routes.d.ts +2 -6
- package/dist/auth/passwordless-signup.d.ts +27 -0
- package/dist/auth/platform-token.d.ts +122 -0
- package/dist/auth/rate-limiter.d.ts +72 -1
- package/dist/auth/routes.d.ts +45 -0
- package/dist/auth/scope-routes.d.ts +22 -0
- package/dist/auth/session-routes.d.ts +18 -6
- package/dist/auth/token-revocation.d.ts +53 -1
- package/dist/auth/verify-credential.d.ts +28 -0
- package/dist/{auth-CCDpk2rn.js → auth-DMLngxn_.js} +2712 -711
- package/dist/auth-DMLngxn_.js.map +1 -0
- package/dist/backend-DTAOsLQc.js +30 -0
- package/dist/backend-DTAOsLQc.js.map +1 -0
- package/dist/backup/backup-common.d.ts +29 -0
- package/dist/backup/backup-routes.d.ts +24 -4
- package/dist/backup/backup-schedule.d.ts +33 -0
- package/dist/backup/backup-storage.d.ts +14 -0
- package/dist/backup/index.d.ts +2 -0
- package/dist/backup-CN0s50D2.js +444 -0
- package/dist/backup-CN0s50D2.js.map +1 -0
- package/dist/boot/bundle.d.ts +19 -0
- package/dist/boot/driver.d.ts +10 -0
- package/dist/boot/env.d.ts +51 -6
- package/dist/boot/fetch-bundle.d.ts +18 -1
- package/dist/boot/rls-audit-option.d.ts +26 -0
- package/dist/boot/security-headers.d.ts +26 -0
- package/dist/boot/sources.d.ts +1 -0
- package/dist/boot/static-routing.d.ts +56 -0
- package/dist/collection_patch-BRu-BvDv.js +472 -0
- package/dist/collection_patch-BRu-BvDv.js.map +1 -0
- package/dist/{contract-routes-eLxV0le1.js → contract-routes-fz8i4pxs.js} +17 -4
- package/dist/contract-routes-fz8i4pxs.js.map +1 -0
- package/dist/cron/cron-routes.d.ts +7 -2
- package/dist/cron/cron-scheduler.d.ts +146 -21
- package/dist/cron/cron-store.d.ts +76 -8
- package/dist/cron/index.d.ts +1 -1
- package/dist/{cron-loader-CQjvjpEw.js → cron-loader-CwaANlOG.js} +4 -4
- package/dist/cron-loader-CwaANlOG.js.map +1 -0
- package/dist/cron-routes-Bc-SB0Se.js +96 -0
- package/dist/cron-routes-Bc-SB0Se.js.map +1 -0
- package/dist/{cron-scheduler-COPQxlEq.js → cron-scheduler-CYQgco86.js} +427 -83
- package/dist/cron-scheduler-CYQgco86.js.map +1 -0
- package/dist/{cron-store-BYGZFNWk.js → cron-store-D2Q9-Aco.js} +139 -23
- package/dist/cron-store-D2Q9-Aco.js.map +1 -0
- package/dist/{ddl-bootstrap-CfNvxMuK.js → ddl-bootstrap-BaqMSa4Y.js} +3 -26
- package/dist/ddl-bootstrap-BaqMSa4Y.js.map +1 -0
- package/dist/email/index.d.ts +2 -2
- package/dist/email/link-base.d.ts +5 -4
- package/dist/email/smtp-email-service.d.ts +13 -1
- package/dist/email/templates.d.ts +31 -0
- package/dist/email/types.d.ts +29 -2
- package/dist/env.d.ts +25 -7
- package/dist/{errors-DWsX4yTd.js → errors-D6_y86c5.js} +102 -8
- package/dist/errors-D6_y86c5.js.map +1 -0
- package/dist/{function-loader-xnbDAPfa.js → function-loader-D7o5Epjj.js} +2 -2
- package/dist/{function-loader-xnbDAPfa.js.map → function-loader-D7o5Epjj.js.map} +1 -1
- package/dist/{function-routes-Chet4-lB.js → function-routes-CaNG4waN.js} +24 -12
- package/dist/function-routes-CaNG4waN.js.map +1 -0
- package/dist/functions/context.d.ts +17 -6
- package/dist/functions/guards.d.ts +22 -5
- package/dist/functions/index.d.ts +2 -2
- package/dist/functions/index.js +90 -36
- package/dist/functions/index.js.map +1 -1
- package/dist/{history-recorder-BQmB0P_j.js → history-recorder-Nr8zLvoU.js} +9 -7
- package/dist/history-recorder-Nr8zLvoU.js.map +1 -0
- package/dist/{history-store-CetkrBBD.js → history-store-rcAm_xFR.js} +2 -2
- package/dist/{history-store-CetkrBBD.js.map → history-store-rcAm_xFR.js.map} +1 -1
- package/dist/index.d.ts +14 -4
- package/dist/index.es.js +5733 -1551
- package/dist/index.es.js.map +1 -1
- package/dist/init/docs.d.ts +5 -2
- package/dist/init/health.d.ts +17 -2
- package/dist/init/shutdown.d.ts +18 -3
- package/dist/init.d.ts +54 -0
- package/dist/jobs/index.d.ts +2 -2
- package/dist/jobs/job-queue.d.ts +23 -2
- package/dist/jobs/job-store.d.ts +37 -5
- package/dist/jobs/types.d.ts +8 -6
- package/dist/{jobs-Bjr8DZAi.js → jobs-DqYNfquG.js} +306 -167
- package/dist/jobs-DqYNfquG.js.map +1 -0
- package/dist/{jwt-C4OW-DNq.js → jwt-R6bSPMjk.js} +114 -38
- package/dist/{jwt-C4OW-DNq.js.map → jwt-R6bSPMjk.js.map} +1 -1
- package/dist/{keys-Qfc4XieN.js → keys-GAVZqbqx.js} +18 -17
- package/dist/{keys-Qfc4XieN.js.map → keys-GAVZqbqx.js.map} +1 -1
- package/dist/{logger-DO2PZc4i.js → logger-D-S-hO5e.js} +26 -3
- package/dist/logger-D-S-hO5e.js.map +1 -0
- package/dist/{logs-routes-3EEzPjhl.js → logs-routes-DAdv37GI.js} +54 -11
- package/dist/logs-routes-DAdv37GI.js.map +1 -0
- package/dist/mcp/consent-page.d.ts +1 -1
- package/dist/mcp/mcp-routes.d.ts +45 -2
- package/dist/mcp/mcp-tools.d.ts +22 -10
- package/dist/mcp/oauth-metadata.d.ts +21 -16
- package/dist/mcp/oauth-routes.d.ts +34 -1
- package/dist/mcp/oauth-store.d.ts +29 -13
- package/dist/metrics/history-recorder.d.ts +1 -1
- package/dist/{openapi-generator-D8uFz-LW.js → openapi-generator-DAq_XVDu.js} +135 -22
- package/dist/openapi-generator-DAq_XVDu.js.map +1 -0
- package/dist/{proxy-Czngl3p9.js → proxy-qRlqeUmO.js} +2 -2
- package/dist/{proxy-Czngl3p9.js.map → proxy-qRlqeUmO.js.map} +1 -1
- package/dist/{query-parser-BleZmY18.js → query-parser-BgiKJKvc.js} +41 -82
- package/dist/query-parser-BgiKJKvc.js.map +1 -0
- package/dist/{request-timeout-C_4C2BeR.js → request-timeout-DgH7j8qO.js} +3 -3
- package/dist/{request-timeout-C_4C2BeR.js.map → request-timeout-DgH7j8qO.js.map} +1 -1
- package/dist/rls-audit/index.d.ts +4 -0
- package/dist/schema-edit/apply-schema-change.d.ts +63 -3
- package/dist/schema-edit/project-root.d.ts +3 -2
- package/dist/schema-edit/remote-source.d.ts +9 -4
- package/dist/{schema-editor-routes-DdLihzp0.js → schema-editor-routes-oIyuWl3L.js} +12 -7
- package/dist/schema-editor-routes-oIyuWl3L.js.map +1 -0
- package/dist/serve-spa.d.ts +58 -0
- package/dist/services/routed-realtime-service.d.ts +11 -0
- package/dist/soft-delete-params-BWPilMPF.js +59 -0
- package/dist/soft-delete-params-BWPilMPF.js.map +1 -0
- package/dist/{src-Br6ARbs6.js → src-CatHFUym.js} +439 -45
- package/dist/src-CatHFUym.js.map +1 -0
- package/dist/{src-1vL-I1Po.js → src-I3aG1PcY.js} +371 -81
- package/dist/src-I3aG1PcY.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +13 -1
- package/dist/storage/LocalStorageController.d.ts +2 -0
- package/dist/storage/S3StorageController.d.ts +2 -0
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/keys.d.ts +12 -0
- package/dist/storage/property-limits.d.ts +41 -6
- package/dist/storage/rendition-cache.d.ts +11 -1
- package/dist/storage/request-keys.d.ts +82 -0
- package/dist/storage/requested-object.d.ts +74 -0
- package/dist/storage/routes.d.ts +36 -18
- package/dist/storage/tus-handler.d.ts +30 -5
- package/dist/storage/types.d.ts +36 -1
- package/dist/types-BfKcm9do.js.map +1 -1
- package/dist/utils/logger.d.ts +12 -0
- package/package.json +9 -9
- package/dist/GCSStorageController-CLIJXwGS.js.map +0 -1
- package/dist/S3StorageController-Dcuf8lMA.js.map +0 -1
- package/dist/admin-roles-vYdp_Pil.js +0 -36
- package/dist/admin-roles-vYdp_Pil.js.map +0 -1
- package/dist/admin_block-DxKLmdiv.js +0 -206
- package/dist/admin_block-DxKLmdiv.js.map +0 -1
- package/dist/ast-schema-editor-CslO8Oje.js.map +0 -1
- package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
- package/dist/auth-CCDpk2rn.js.map +0 -1
- package/dist/backup-DzI9jLwc.js +0 -192
- package/dist/backup-DzI9jLwc.js.map +0 -1
- package/dist/contract-routes-eLxV0le1.js.map +0 -1
- package/dist/cron-loader-CQjvjpEw.js.map +0 -1
- package/dist/cron-routes-B7CRGfiq.js +0 -72
- package/dist/cron-routes-B7CRGfiq.js.map +0 -1
- package/dist/cron-scheduler-COPQxlEq.js.map +0 -1
- package/dist/cron-store-BYGZFNWk.js.map +0 -1
- package/dist/ddl-bootstrap-CfNvxMuK.js.map +0 -1
- package/dist/errors-DWsX4yTd.js.map +0 -1
- package/dist/function-routes-Chet4-lB.js.map +0 -1
- package/dist/history-recorder-BQmB0P_j.js.map +0 -1
- package/dist/jobs-Bjr8DZAi.js.map +0 -1
- package/dist/logger-DO2PZc4i.js.map +0 -1
- package/dist/logs-routes-3EEzPjhl.js.map +0 -1
- package/dist/openapi-generator-D8uFz-LW.js.map +0 -1
- package/dist/query-parser-BleZmY18.js.map +0 -1
- package/dist/schema-editor-routes-DdLihzp0.js.map +0 -1
- package/dist/src-1vL-I1Po.js.map +0 -1
- package/dist/src-Br6ARbs6.js.map +0 -1
package/dist/auth/jwt.d.ts
CHANGED
|
@@ -61,6 +61,23 @@ export interface AccessTokenPayload {
|
|
|
61
61
|
* comparison and the watermark was read on exactly one path — refresh.
|
|
62
62
|
*/
|
|
63
63
|
iat?: number;
|
|
64
|
+
/**
|
|
65
|
+
* When the token expires, in seconds since the epoch — the standard `exp`
|
|
66
|
+
* claim. Carried through because a socket authenticated with this token
|
|
67
|
+
* has to stop honouring it at this instant, and has no request to refuse.
|
|
68
|
+
*/
|
|
69
|
+
exp?: number;
|
|
70
|
+
/**
|
|
71
|
+
* The sign-in this token was minted for: the session id its refresh
|
|
72
|
+
* tokens share, carried across every rotation.
|
|
73
|
+
*
|
|
74
|
+
* What lets one device be signed out — `POST /auth/logout`,
|
|
75
|
+
* `DELETE /auth/sessions/:id` — reach that device's access token rather
|
|
76
|
+
* than only its refresh token, and what lets `GET /auth/sessions` mark the
|
|
77
|
+
* caller's own session. Absent on tokens minted before it existed; those
|
|
78
|
+
* keep working until they expire, judged by the watermark alone.
|
|
79
|
+
*/
|
|
80
|
+
sid?: string;
|
|
64
81
|
/** Email claim from the JWT, if present */
|
|
65
82
|
email?: string;
|
|
66
83
|
/** Display name claim from the JWT, if present */
|
|
@@ -89,6 +106,14 @@ export interface AccessTokenPayload {
|
|
|
89
106
|
*/
|
|
90
107
|
claims?: Record<string, unknown>;
|
|
91
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* The custom half of a verified token, or nothing when there is none.
|
|
111
|
+
*
|
|
112
|
+
* Exported for the one other place that builds a request identity's claims
|
|
113
|
+
* without a token to verify — `resolveUser`, which reads what a token minted
|
|
114
|
+
* now would carry — so both drop the same identity claims.
|
|
115
|
+
*/
|
|
116
|
+
export declare function customClaimsOf(decoded: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
92
117
|
/**
|
|
93
118
|
* Configure JWT settings - call this during initialization.
|
|
94
119
|
* Validates the secret strength to prevent deployment with default/weak secrets.
|
|
@@ -105,6 +130,12 @@ export declare function configureJwt(config: JwtConfig): void;
|
|
|
105
130
|
export declare function getJwks(): {
|
|
106
131
|
keys: PublicJwk[];
|
|
107
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`.
|
|
137
|
+
*/
|
|
138
|
+
export declare function configuredJwtSecret(): string | undefined;
|
|
108
139
|
/** Is this backend signing access tokens asymmetrically? */
|
|
109
140
|
export declare function hasAsymmetricSigningKey(): boolean;
|
|
110
141
|
/**
|
|
@@ -128,11 +159,28 @@ export declare function generateAccessToken(uid: string, roles: string[], aal?:
|
|
|
128
159
|
* account. Written into the token so the RLS identity and the WebSocket
|
|
129
160
|
* path can tell the two apart without a database lookup.
|
|
130
161
|
*/
|
|
131
|
-
isAnonymous?: boolean
|
|
162
|
+
isAnonymous?: boolean,
|
|
163
|
+
/**
|
|
164
|
+
* The sign-in this token belongs to — see {@link AccessTokenPayload.sid}.
|
|
165
|
+
* Every token the auth routes mint carries one.
|
|
166
|
+
*/
|
|
167
|
+
sessionId?: string): Promise<string>;
|
|
132
168
|
/**
|
|
133
169
|
* Get the expiration time of an access token in milliseconds from now
|
|
134
170
|
*/
|
|
135
171
|
export declare function getAccessTokenExpiryMs(): number;
|
|
172
|
+
/**
|
|
173
|
+
* A lifetime setting in milliseconds, or null when it is not one.
|
|
174
|
+
*
|
|
175
|
+
* The grammar is vercel/ms, because that is what `jsonwebtoken` reads
|
|
176
|
+
* `expiresIn` with: the token's `exp` and the expiry this module reports
|
|
177
|
+
* have to come from one reading of the same string, and they used to come
|
|
178
|
+
* from two — ours understood `<integer><d|h|m|s>` and nothing else.
|
|
179
|
+
*
|
|
180
|
+
* Stricter than ms in two ways, both about values nobody means: a bare number
|
|
181
|
+
* (ms reads "3600" as 3.6 seconds) and a lifetime of zero or less.
|
|
182
|
+
*/
|
|
183
|
+
export declare function parseDurationMs(value: string): number | null;
|
|
136
184
|
/**
|
|
137
185
|
* Get the expiration timestamp for an access token
|
|
138
186
|
*/
|
|
@@ -153,7 +201,8 @@ export declare function getAccessTokenExpiry(): number;
|
|
|
153
201
|
*/
|
|
154
202
|
export declare function verifyAccessToken(token: string): Promise<AccessTokenPayload | null>;
|
|
155
203
|
/**
|
|
156
|
-
* Generate a random refresh token
|
|
204
|
+
* Generate a random refresh token. Long-lived: 400 days by default, sliding —
|
|
205
|
+
* see {@link getRefreshTokenTtlMs}.
|
|
157
206
|
*/
|
|
158
207
|
export declare function generateRefreshToken(): string;
|
|
159
208
|
/**
|
|
@@ -205,9 +254,14 @@ export declare function generateMfaPendingToken(uid: string, expiresInSeconds?:
|
|
|
205
254
|
*
|
|
206
255
|
* Returns `null` for anything else — including a perfectly valid *access*
|
|
207
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`.
|
|
208
261
|
*/
|
|
209
262
|
export declare function verifyMfaPendingToken(token: string): Promise<{
|
|
210
263
|
uid: string;
|
|
264
|
+
iat?: number;
|
|
211
265
|
} | null>;
|
|
212
266
|
export interface DownloadTokenPayload {
|
|
213
267
|
purpose: "file-read";
|
|
@@ -219,6 +273,13 @@ export interface DownloadTokenPayload {
|
|
|
219
273
|
* existed are read.
|
|
220
274
|
*/
|
|
221
275
|
storageId: string;
|
|
276
|
+
/**
|
|
277
|
+
* An opaque mark of the user who minted the token, for rate limiting only:
|
|
278
|
+
* a read that spends the token is charged to that user's allowance rather
|
|
279
|
+
* than to the reader's address. A hash, because the token travels in a URL.
|
|
280
|
+
* Absent for a token minted for nobody.
|
|
281
|
+
*/
|
|
282
|
+
rl?: string;
|
|
222
283
|
}
|
|
223
284
|
/**
|
|
224
285
|
* Generate a short-lived download token scoped to a specific file path or prefix
|
|
@@ -237,7 +298,9 @@ export interface DownloadTokenPayload {
|
|
|
237
298
|
* site that forgets to pass a named source produces a default-scoped token,
|
|
238
299
|
* which fails closed at `/file/*` rather than over-granting.
|
|
239
300
|
*/
|
|
240
|
-
export declare function generateDownloadToken(path: string, expiresInSeconds?: number, storageId?: string | null
|
|
301
|
+
export declare function generateDownloadToken(path: string, expiresInSeconds?: number, storageId?: string | null,
|
|
302
|
+
/** The user minting it, so the reads it buys are charged to them. */
|
|
303
|
+
principal?: string | null): Promise<string>;
|
|
241
304
|
/**
|
|
242
305
|
* Verify and decode a download token.
|
|
243
306
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import type { MiddlewareHandler } from "hono";
|
|
3
|
-
import type { AuthModuleConfig } from "./routes.js";
|
|
3
|
+
import type { AuthModuleConfig, CreateSessionAndTokens } from "./routes.js";
|
|
4
4
|
import type { ResolvedAuthHooks } from "./auth-hooks.js";
|
|
5
5
|
import type { HonoEnv } from "../api/types.js";
|
|
6
6
|
import { z } from "zod";
|
|
@@ -24,11 +24,7 @@ export declare function mountMagicLinkRoutes(deps: {
|
|
|
24
24
|
isAnonymous?: boolean;
|
|
25
25
|
metadata?: Record<string, unknown> | null;
|
|
26
26
|
}, roleIds: string[], accessToken: string, refreshToken: string, providerId: string) => unknown;
|
|
27
|
-
createSessionAndTokens:
|
|
28
|
-
roleIds: string[];
|
|
29
|
-
accessToken: string;
|
|
30
|
-
refreshToken: string;
|
|
31
|
-
}>;
|
|
27
|
+
createSessionAndTokens: CreateSessionAndTokens;
|
|
32
28
|
applyTransformHook: (response: AuthResponsePayload, method: TransformAuthResponseContext["method"], request: Request, uid: string) => Promise<AuthResponsePayload>;
|
|
33
29
|
/**
|
|
34
30
|
* Built by the caller so a misconfiguration fails the boot once, rather
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Hono, type MiddlewareHandler } from "hono";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { HonoEnv } from "../api/types.js";
|
|
4
|
-
import type { AuthModuleConfig } from "./routes.js";
|
|
4
|
+
import type { AuthModuleConfig, CreateSessionAndTokens } from "./routes.js";
|
|
5
5
|
import { resolveAuthHooks } from "./auth-hooks.js";
|
|
6
6
|
import type { AuthResponsePayload, TransformAuthResponseContext } from "@rebasepro/types";
|
|
7
7
|
interface MfaRoutesConfig {
|
|
@@ -27,14 +27,7 @@ interface MfaRoutesConfig {
|
|
|
27
27
|
isAnonymous?: boolean;
|
|
28
28
|
metadata?: Record<string, unknown> | null;
|
|
29
29
|
}, roleIds: string[], accessToken: string, refreshToken: string, providerId: string) => unknown;
|
|
30
|
-
createSessionAndTokens:
|
|
31
|
-
skipMfaGate?: boolean;
|
|
32
|
-
aal?: "aal1" | "aal2";
|
|
33
|
-
}) => Promise<{
|
|
34
|
-
roleIds: string[];
|
|
35
|
-
accessToken: string;
|
|
36
|
-
refreshToken: string;
|
|
37
|
-
}>;
|
|
30
|
+
createSessionAndTokens: CreateSessionAndTokens;
|
|
38
31
|
applyTransformHook?: (response: AuthResponsePayload, method: TransformAuthResponseContext["method"], request: Request, uid: string) => Promise<AuthResponsePayload>;
|
|
39
32
|
}
|
|
40
33
|
export declare function mountMfaRoutes(opts: MfaRoutesConfig): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MiddlewareHandler, Context } from "hono";
|
|
2
|
-
import type
|
|
2
|
+
import { type AccessJudgeRepository } from "./token-revocation.js";
|
|
3
3
|
import { DataDriver } from "@rebasepro/types";
|
|
4
4
|
import { AccessTokenPayload } from "./jwt.js";
|
|
5
5
|
import type { HonoEnv } from "../api/types.js";
|
|
@@ -131,11 +131,15 @@ export declare function createRequireAuth(options?: {
|
|
|
131
131
|
* invalidated by `logout` or a password reset stops working on admin routes
|
|
132
132
|
* too, not just on the data plane.
|
|
133
133
|
*/
|
|
134
|
-
revocationRepo?:
|
|
134
|
+
revocationRepo?: AccessJudgeRepository;
|
|
135
135
|
}): MiddlewareHandler<HonoEnv>;
|
|
136
136
|
/**
|
|
137
|
-
* Middleware that requires the user to
|
|
138
|
-
* Must be used AFTER requireAuth or on a route where user is
|
|
137
|
+
* Middleware that requires the user to hold the `admin` role, which holds
|
|
138
|
+
* every scope. Must be used AFTER requireAuth or on a route where user is
|
|
139
|
+
* guaranteed.
|
|
140
|
+
*
|
|
141
|
+
* Prefer `requireScope` from `./access` for anything a scope names: a scope
|
|
142
|
+
* can be granted to a narrower role and to a key, and `admin` cannot.
|
|
139
143
|
*/
|
|
140
144
|
export declare const requireAdmin: MiddlewareHandler<HonoEnv>;
|
|
141
145
|
/**
|
|
@@ -197,6 +201,14 @@ export declare const queryTokenAuth: MiddlewareHandler<HonoEnv>;
|
|
|
197
201
|
* requested object path is public, it sets a minimal "public" principal so the
|
|
198
202
|
* downstream `requireAuth` gate lets the read through. Private paths are left
|
|
199
203
|
* untouched, so they still require a valid token.
|
|
204
|
+
*
|
|
205
|
+
* Public means the object the route will serve is public, so the decision is
|
|
206
|
+
* made on that object's canonical key, derived by the route's own function
|
|
207
|
+
* (`requestedStorageObject`). It used to be made on the raw path, through a
|
|
208
|
+
* check that strips a `scheme://`: `notes://public/secret.txt` read as
|
|
209
|
+
* `public/secret.txt` here while the route served the private
|
|
210
|
+
* `notes:/public/secret.txt`, and the authorize hook — not asked about this
|
|
211
|
+
* principal — never saw it.
|
|
200
212
|
*/
|
|
201
213
|
export declare const publicObjectAuth: MiddlewareHandler<HonoEnv>;
|
|
202
214
|
/**
|
|
@@ -217,7 +229,7 @@ export declare const publicObjectAuth: MiddlewareHandler<HonoEnv>;
|
|
|
217
229
|
* Hono routes the request, so this comparison already runs on a resolved path.
|
|
218
230
|
* But that is a guarantee of the runtime rather than of this code, and it is
|
|
219
231
|
* one line to not depend on it. The same rule already guards the public-object
|
|
220
|
-
* path — see `
|
|
232
|
+
* path — see `isPublicStorageKey`.
|
|
221
233
|
*/
|
|
222
234
|
export declare function isPathMatch(requested: string, allowed: string): boolean;
|
|
223
235
|
/**
|
|
@@ -13,7 +13,16 @@ export type AutoLinkRefusal =
|
|
|
13
13
|
* address. Attaching a provider identity to it would hand the session to
|
|
14
14
|
* whoever registered the address first — the classic pre-hijack.
|
|
15
15
|
*/
|
|
16
|
-
| "local-account-unverified"
|
|
16
|
+
| "local-account-unverified"
|
|
17
|
+
/**
|
|
18
|
+
* The local account has no password, and its address was never proven
|
|
19
|
+
* either: it was made by a sign-in through a provider that did not vouch
|
|
20
|
+
* for the address, or by someone who never had to. Its sign-in methods are
|
|
21
|
+
* whatever that someone attached, so this is the same pre-hijack as a
|
|
22
|
+
* password would be. Reported apart from `local-account-unverified` only
|
|
23
|
+
* so a login screen does not tell its owner to use a password.
|
|
24
|
+
*/
|
|
25
|
+
| "local-account-unverified-passwordless";
|
|
17
26
|
export type AutoLinkDecision = {
|
|
18
27
|
allowed: true;
|
|
19
28
|
} | {
|
|
@@ -28,17 +37,25 @@ export interface AutoLinkExistingUser {
|
|
|
28
37
|
/**
|
|
29
38
|
* May an OAuth identity be attached to a pre-existing account found *by email*?
|
|
30
39
|
*
|
|
31
|
-
* Both sides have to be trustworthy
|
|
40
|
+
* Both sides have to be trustworthy:
|
|
32
41
|
*
|
|
33
42
|
* - the **provider** must have verified the address, or the caller has not
|
|
34
43
|
* shown they control it;
|
|
35
|
-
* - the **local account** must
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
44
|
+
* - the **local account**'s address must have been verified too. Whoever
|
|
45
|
+
* made an unverified account never proved the address, and every way in
|
|
46
|
+
* they left on it — a password, an identity from a provider that did not
|
|
47
|
+
* vouch for the address — would go on opening the account the owner is
|
|
48
|
+
* about to be signed into.
|
|
49
|
+
*
|
|
50
|
+
* The second rule used to exempt an account with no password, on the theory
|
|
51
|
+
* that one made by an OAuth sign-in holds no credential an attacker could have
|
|
52
|
+
* planted. It holds exactly one: the identity it was made with, which is an
|
|
53
|
+
* attacker's whenever the provider did not vouch for the address.
|
|
39
54
|
*
|
|
40
|
-
* A refusal is not a dead end
|
|
41
|
-
* identity once the caller proves ownership by holding a session
|
|
55
|
+
* A refusal is not a dead end. `POST /auth/link/<provider>` attaches the
|
|
56
|
+
* identity once the caller proves ownership by holding a session, and proving
|
|
57
|
+
* the address (a magic link, an email code, a password reset) verifies the
|
|
58
|
+
* account — removing what nobody proved — after which this answers yes.
|
|
42
59
|
*/
|
|
43
60
|
export declare function decideOAuthAutoLink(args: {
|
|
44
61
|
providerEmailVerified: boolean | undefined;
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
import { Hono } from "hono";
|
|
43
43
|
import type { MiddlewareHandler } from "hono";
|
|
44
44
|
import { z } from "zod";
|
|
45
|
-
import type { AuthModuleConfig } from "./routes.js";
|
|
45
|
+
import type { AuthModuleConfig, CreateSessionAndTokens } from "./routes.js";
|
|
46
46
|
import type { ResolvedAuthHooks } from "./auth-hooks.js";
|
|
47
47
|
import type { HonoEnv } from "../api/types.js";
|
|
48
48
|
import type { AuthResponsePayload, TransformAuthResponseContext } from "@rebasepro/types";
|
|
@@ -78,11 +78,7 @@ export declare function mountOtpRoutes(deps: {
|
|
|
78
78
|
isAnonymous?: boolean;
|
|
79
79
|
metadata?: Record<string, unknown> | null;
|
|
80
80
|
}, roleIds: string[], accessToken: string, refreshToken: string, providerId: string) => unknown;
|
|
81
|
-
createSessionAndTokens:
|
|
82
|
-
roleIds: string[];
|
|
83
|
-
accessToken: string;
|
|
84
|
-
refreshToken: string;
|
|
85
|
-
}>;
|
|
81
|
+
createSessionAndTokens: CreateSessionAndTokens;
|
|
86
82
|
applyTransformHook: (response: AuthResponsePayload, method: TransformAuthResponseContext["method"], request: Request, uid: string) => Promise<AuthResponsePayload>;
|
|
87
83
|
/** Built by the caller, as for magic link. Absent when captcha is off. */
|
|
88
84
|
captchaMiddleware?: MiddlewareHandler<HonoEnv>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sign-up by magic link or email code (`auth.magicLinkCreatesUsers`).
|
|
3
|
+
*
|
|
4
|
+
* Both passwordless doors refuse an address with no account — silently, so
|
|
5
|
+
* they do not say which addresses have one. A passwordless-only app then had
|
|
6
|
+
* no way to create accounts at all. With the option on, and registration open,
|
|
7
|
+
* an unknown address gets an account at request time — no password, unverified
|
|
8
|
+
* — and the link or code mailed to it is what proves the address and signs it
|
|
9
|
+
* in, exactly as for an existing account. Supabase's `shouldCreateUser`.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import type { AuthModuleConfig } from "./routes.js";
|
|
14
|
+
import type { ResolvedAuthHooks } from "./auth-hooks.js";
|
|
15
|
+
import type { UserData } from "./interfaces.js";
|
|
16
|
+
/**
|
|
17
|
+
* The account a passwordless request for `email` should mail, creating it
|
|
18
|
+
* when the deployment lets passwordless requests sign people up. `null` when
|
|
19
|
+
* there is none and none may be made — the caller answers as for any unknown
|
|
20
|
+
* address.
|
|
21
|
+
*
|
|
22
|
+
* Registration's controls hold: the kill switch, `allowRegistration` (with no
|
|
23
|
+
* first-user exception: the first admin is made by registering or by the
|
|
24
|
+
* operator, never by whoever asks for a link first), `beforeUserCreate`,
|
|
25
|
+
* `afterUserCreate` and the default role.
|
|
26
|
+
*/
|
|
27
|
+
export declare function accountForPasswordlessRequest(config: AuthModuleConfig, ops: ResolvedAuthHooks, email: string): Promise<UserData | null>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform tokens: a short-lived credential the platform hosting this server
|
|
3
|
+
* mints for one person, for one project, for a few read-only scopes.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists
|
|
6
|
+
*
|
|
7
|
+
* On Rebase Cloud the owner of a project is signed in to the *control plane*,
|
|
8
|
+
* not to their own app. Their app's admin surfaces — the cron job list and its
|
|
9
|
+
* run history first — accept an admin user of the app, an `rk_` key, or the
|
|
10
|
+
* service key, and an owner may hold none of them: a fresh deploy has no admin
|
|
11
|
+
* account, and the service key is admin on the whole API, which is the wrong
|
|
12
|
+
* thing to hand someone who wants to read why last night's job failed.
|
|
13
|
+
*
|
|
14
|
+
* So the control plane signs a token instead. It checks the caller's project
|
|
15
|
+
* membership, then signs `{ aud: <project>, sub: <who>, scope: "cron:read" }`
|
|
16
|
+
* with a key only it holds, valid for minutes. This server verifies it against
|
|
17
|
+
* the public half — `REBASE_PLATFORM_TOKEN_KEY` — which the platform sets at
|
|
18
|
+
* deploy. Nothing secret lives in the tenant: the env holds a public key, and
|
|
19
|
+
* leaking it lets nobody mint anything.
|
|
20
|
+
*
|
|
21
|
+
* ## What a platform token can never do
|
|
22
|
+
*
|
|
23
|
+
* - **Exceed {@link PLATFORM_TOKEN_SCOPES}.** The token names scopes; this
|
|
24
|
+
* server grants only the ones on that list, whatever the platform signed. The
|
|
25
|
+
* ceiling lives here, in the tenant's code, so a compromised or buggy control
|
|
26
|
+
* plane cannot widen it.
|
|
27
|
+
* - **Outlive {@link PLATFORM_TOKEN_MAX_LIFETIME_SECONDS}.** A token whose
|
|
28
|
+
* `exp - iat` is longer is refused, not clamped: a long-lived platform token
|
|
29
|
+
* is a static secret by another name.
|
|
30
|
+
* - **Reach another project.** One platform key signs for every tenant, so the
|
|
31
|
+
* audience is what binds a token to this one: `aud` must equal
|
|
32
|
+
* `REBASE_PLATFORM_TOKEN_AUDIENCE`.
|
|
33
|
+
* - **Act as a person of this app.** The caller is `platform:<sub>` with no
|
|
34
|
+
* roles: row-level security sees nobody it knows, and every surface that
|
|
35
|
+
* checks a scope other than the granted ones refuses it.
|
|
36
|
+
*
|
|
37
|
+
* ## Wire format
|
|
38
|
+
*
|
|
39
|
+
* `rpt_` followed by a compact ES256 JWS. The prefix keeps the credential
|
|
40
|
+
* classes apart the way `rk_` does: a platform token is never tried as a user
|
|
41
|
+
* session, and a user session is never tried as a platform token.
|
|
42
|
+
*
|
|
43
|
+
* @module
|
|
44
|
+
*/
|
|
45
|
+
import type { MiddlewareHandler } from "hono";
|
|
46
|
+
import type { AdminScope } from "@rebasepro/types";
|
|
47
|
+
import type { HonoEnv } from "../api/types.js";
|
|
48
|
+
/** Marks a bearer token as a platform token. */
|
|
49
|
+
export declare const PLATFORM_TOKEN_PREFIX = "rpt_";
|
|
50
|
+
/** The `iss` every platform token carries. */
|
|
51
|
+
export declare const PLATFORM_TOKEN_ISSUER = "rebase-cloud";
|
|
52
|
+
/**
|
|
53
|
+
* Everything a platform token can be granted on this server.
|
|
54
|
+
*
|
|
55
|
+
* Read-only on purpose. Adding a scope here is a decision about what the
|
|
56
|
+
* platform may do inside a customer's app on a person's behalf, and belongs in
|
|
57
|
+
* its own change.
|
|
58
|
+
*/
|
|
59
|
+
export declare const PLATFORM_TOKEN_SCOPES: readonly AdminScope[];
|
|
60
|
+
/** The longest `exp - iat` a platform token may declare. */
|
|
61
|
+
export declare const PLATFORM_TOKEN_MAX_LIFETIME_SECONDS = 600;
|
|
62
|
+
/** The environment variables a platform sets to turn platform tokens on. */
|
|
63
|
+
export declare const PLATFORM_TOKEN_KEY_ENV = "REBASE_PLATFORM_TOKEN_KEY";
|
|
64
|
+
export declare const PLATFORM_TOKEN_AUDIENCE_ENV = "REBASE_PLATFORM_TOKEN_AUDIENCE";
|
|
65
|
+
/** What this server verifies platform tokens against. */
|
|
66
|
+
export interface PlatformTokenConfig {
|
|
67
|
+
/**
|
|
68
|
+
* PEM-encoded SPKI public keys, EC P-256. More than one during a key
|
|
69
|
+
* rotation: a token verifies against any of them.
|
|
70
|
+
*/
|
|
71
|
+
publicKeys: string[];
|
|
72
|
+
/** This project, as the platform names it in `aud`. */
|
|
73
|
+
audience: string;
|
|
74
|
+
}
|
|
75
|
+
/** A verified platform token, as the request will act. */
|
|
76
|
+
export interface PlatformCaller {
|
|
77
|
+
/** Who the platform minted it for — a control-plane account. */
|
|
78
|
+
subject: string;
|
|
79
|
+
/** The granted scopes: what the token asked for, within the ceiling. */
|
|
80
|
+
scopes: string[];
|
|
81
|
+
/** `jti`, when the platform set one — what an audit line quotes. */
|
|
82
|
+
tokenId?: string;
|
|
83
|
+
}
|
|
84
|
+
/** Why a presented platform token does not authenticate. */
|
|
85
|
+
export interface PlatformTokenRefusal {
|
|
86
|
+
refusal: string;
|
|
87
|
+
}
|
|
88
|
+
export declare function isPlatformToken(token: string): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* The PEM blocks in an env value: real newlines, `\n`-escaped ones (what a
|
|
91
|
+
* one-line `.env` holds), or the whole thing base64-encoded.
|
|
92
|
+
*/
|
|
93
|
+
export declare function parsePublicKeys(value: string): string[];
|
|
94
|
+
/**
|
|
95
|
+
* Read the platform-token configuration from the environment.
|
|
96
|
+
*
|
|
97
|
+
* `undefined` when platform tokens are off — neither variable set, which is
|
|
98
|
+
* every self-hosted server — or misconfigured. A misconfiguration is logged and
|
|
99
|
+
* leaves them off rather than failing the boot: the platform set these values,
|
|
100
|
+
* the app's owner cannot fix them, and the app serving traffic matters more
|
|
101
|
+
* than its cron history being readable from the CLI.
|
|
102
|
+
*/
|
|
103
|
+
export declare function platformTokensFromEnv(env: Record<string, string | undefined>): Promise<PlatformTokenConfig | undefined>;
|
|
104
|
+
/**
|
|
105
|
+
* Verify a presented platform token: signature, issuer, audience, lifetime, and
|
|
106
|
+
* the scopes it may be granted here.
|
|
107
|
+
*/
|
|
108
|
+
export declare function verifyPlatformToken(token: string, config: PlatformTokenConfig, nowSeconds?: number): Promise<PlatformCaller | PlatformTokenRefusal>;
|
|
109
|
+
/**
|
|
110
|
+
* Authenticate an `rpt_` bearer token ahead of an admin gate.
|
|
111
|
+
*
|
|
112
|
+
* Mounted where the `rk_` pre-auth is, and shaped like it: a request it does not
|
|
113
|
+
* recognise passes through untouched, a recognised one either becomes a caller
|
|
114
|
+
* with narrowed `scopes` or is refused here. The gate's scope check then decides
|
|
115
|
+
* — so a token holding `cron:read` reads cron, and is a 403 on every other
|
|
116
|
+
* surface.
|
|
117
|
+
*
|
|
118
|
+
* With `config` undefined, platform tokens are off on this server; an `rpt_`
|
|
119
|
+
* token is still recognised, so its holder learns that rather than the generic
|
|
120
|
+
* "invalid token" a JWT parser would answer.
|
|
121
|
+
*/
|
|
122
|
+
export declare function createPlatformTokenPreAuth(config: PlatformTokenConfig | undefined): MiddlewareHandler<HonoEnv>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MiddlewareHandler } from "hono";
|
|
2
|
+
import { type DataRateLimitCaller } from "@rebasepro/types";
|
|
2
3
|
import { HonoEnv } from "../api/types.js";
|
|
3
|
-
import { RateLimitStore } from "./rate-limit-store.js";
|
|
4
|
+
import { RateLimitDecision, RateLimitStore } from "./rate-limit-store.js";
|
|
4
5
|
/**
|
|
5
6
|
* Sliding-window rate limiting for Hono.
|
|
6
7
|
*
|
|
@@ -28,6 +29,15 @@ interface RateLimiterOptions {
|
|
|
28
29
|
* shared one to have several limiters (or several processes) agree.
|
|
29
30
|
*/
|
|
30
31
|
store?: RateLimitStore;
|
|
32
|
+
/**
|
|
33
|
+
* Count in the deployment's shared store once one is configured, under
|
|
34
|
+
* this name. For a limiter declared at module load, which runs before any
|
|
35
|
+
* configuration exists and so cannot be handed a `store` — see
|
|
36
|
+
* {@link setSharedRateLimitStore}. The name namespaces the limiter's keys
|
|
37
|
+
* there: two limiters keyed by the same IP must not share one count.
|
|
38
|
+
* Ignored when `store` is given.
|
|
39
|
+
*/
|
|
40
|
+
name?: string;
|
|
31
41
|
/**
|
|
32
42
|
* Per-request limit override, for buckets whose allowance is data rather
|
|
33
43
|
* than config (an API key's own `rate_limit`). Returning `undefined` uses
|
|
@@ -59,12 +69,55 @@ interface RateLimiterOptions {
|
|
|
59
69
|
*/
|
|
60
70
|
trustedProxyHops?: number;
|
|
61
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Point every named limiter at the deployment's shared store — or, with
|
|
74
|
+
* `undefined`, back at its own memory. Called once per boot with the store
|
|
75
|
+
* the data limiters use, and only when that store is shared (SQL, or one the
|
|
76
|
+
* operator supplied): the default memory store is swept on the data window,
|
|
77
|
+
* which is not every limiter's window.
|
|
78
|
+
*/
|
|
79
|
+
export declare function setSharedRateLimitStore(store: RateLimitStore | undefined): void;
|
|
62
80
|
/**
|
|
63
81
|
* Create a rate-limiting middleware.
|
|
64
82
|
*
|
|
65
83
|
* Uses a sliding window: only hits within the last `windowMs` are counted.
|
|
66
84
|
*/
|
|
67
85
|
export declare function createRateLimiter(options?: RateLimiterOptions): MiddlewareHandler<HonoEnv>;
|
|
86
|
+
/**
|
|
87
|
+
* Default key generator: the client's address, from the most trustworthy source
|
|
88
|
+
* this deployment has.
|
|
89
|
+
*
|
|
90
|
+
* `X-Forwarded-For` is a client-writable header; only the entries appended by
|
|
91
|
+
* trusted reverse proxies can be believed. With `trustedProxyHops` proxies in
|
|
92
|
+
* front, each appends the address it saw, so the real client IP is the
|
|
93
|
+
* `trustedProxyHops`-th entry from the right — everything further left is
|
|
94
|
+
* client-supplied and must be ignored. This is what prevents a caller from
|
|
95
|
+
* spoofing `X-Forwarded-For` to spread its requests across many rate-limit keys.
|
|
96
|
+
*
|
|
97
|
+
* `X-Real-IP` is the *same* kind of header and needs the same rule, which it
|
|
98
|
+
* did not have: it was read unconditionally, including under
|
|
99
|
+
* `trustedProxyHops === 0` — the mode whose entire meaning is "no proxy is in
|
|
100
|
+
* front of me". With no proxy there, nothing writes `X-Real-IP` except the
|
|
101
|
+
* caller, so the key was theirs to choose: one header per request bought an
|
|
102
|
+
* unlimited number of buckets, and the limiters on login, registration and
|
|
103
|
+
* password reset counted to one. The reasoning had been done carefully for one
|
|
104
|
+
* spelling of a proxy header and not carried to its twin.
|
|
105
|
+
*
|
|
106
|
+
* So `X-Real-IP` is now believed only where a proxy is declared to exist. With
|
|
107
|
+
* none, the connection's own address is used — unforgeable, and available
|
|
108
|
+
* because the server runs on `@hono/node-server`. `"unknown"` is the last
|
|
109
|
+
* resort only, and it is a single shared bucket by design: better that
|
|
110
|
+
* anonymous callers throttle each other than that any of them throttles nobody.
|
|
111
|
+
*/
|
|
112
|
+
/**
|
|
113
|
+
* Where a request comes from, as the rate limiters judge it: the socket's own
|
|
114
|
+
* address, or — only behind as many proxies as `TRUSTED_PROXY_HOPS` declares —
|
|
115
|
+
* the address they report. What a session row records as its IP, so the
|
|
116
|
+
* sessions list shows the same address the limiter counted, rather than the
|
|
117
|
+
* raw `X-Forwarded-For` (whose leftmost entry the caller chooses) or
|
|
118
|
+
* `"unknown"` for every direct connection.
|
|
119
|
+
*/
|
|
120
|
+
export declare function requestClientAddress(c: Parameters<MiddlewareHandler<HonoEnv>>[0]): string;
|
|
68
121
|
/**
|
|
69
122
|
* Pre-configured rate limiter for general auth endpoints (login, register).
|
|
70
123
|
* 200 requests per 15 minutes per IP.
|
|
@@ -191,6 +244,12 @@ export interface DataRateLimitConfig {
|
|
|
191
244
|
anonymousFunctions?: number | null;
|
|
192
245
|
/** Share counts across replicas. Defaults to this process's memory. */
|
|
193
246
|
store?: RateLimitStore;
|
|
247
|
+
/**
|
|
248
|
+
* The deployment's service key, recognised as a Bearer before any auth
|
|
249
|
+
* middleware has run — the storage router's limiter runs ahead of its
|
|
250
|
+
* routes' auth — and never limited. See {@link dataRateLimitBuckets}.
|
|
251
|
+
*/
|
|
252
|
+
serviceKey?: string;
|
|
194
253
|
}
|
|
195
254
|
/** @see DataRateLimitConfig.anonymousFunctions */
|
|
196
255
|
export declare const DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT = 3000;
|
|
@@ -212,4 +271,16 @@ export declare const DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT = 3000;
|
|
|
212
271
|
* `enabled: false` rather than pay for it twice.
|
|
213
272
|
*/
|
|
214
273
|
export declare function createDataRateLimiter(config?: DataRateLimitConfig): MiddlewareHandler<HonoEnv>;
|
|
274
|
+
/**
|
|
275
|
+
* The data API's per-caller limit, as a check rather than a middleware — for
|
|
276
|
+
* the door into the same data that is not an HTTP request.
|
|
277
|
+
*
|
|
278
|
+
* The realtime socket serves the rows the data API serves, one frame per
|
|
279
|
+
* request. It counted them per connection only, so a caller bought more budget
|
|
280
|
+
* by opening more sockets, and a frame never met the per-person limit its HTTP
|
|
281
|
+
* twin does. Built from the same config and store as
|
|
282
|
+
* {@link createDataRateLimiter}, it puts a caller in the same bucket under the
|
|
283
|
+
* same key: one person's allowance, spent through either door.
|
|
284
|
+
*/
|
|
285
|
+
export declare function createDataRateLimitCheck(config?: DataRateLimitConfig): (caller: DataRateLimitCaller) => Promise<RateLimitDecision | null>;
|
|
215
286
|
export {};
|
package/dist/auth/routes.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { AuthHooks } from "./auth-hooks.js";
|
|
|
4
4
|
import { EmailService, EmailConfig } from "../email/index.js";
|
|
5
5
|
import { HonoEnv } from "../api/types.js";
|
|
6
6
|
import { type CaptchaConfig } from "./captcha.js";
|
|
7
|
+
import { isDeliverableAddress } from "./deliverable-address.js";
|
|
8
|
+
export { isDeliverableAddress };
|
|
7
9
|
/**
|
|
8
10
|
* Shared configuration for auth and admin route factories.
|
|
9
11
|
*/
|
|
@@ -86,7 +88,30 @@ export interface AuthModuleConfig {
|
|
|
86
88
|
* how long a captured token stays useful to someone who copied it.
|
|
87
89
|
*/
|
|
88
90
|
refreshTokenReuseIntervalSeconds?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Let a magic-link or email-code request for an address with no account
|
|
93
|
+
* create one (no password, unverified until the link or code is used),
|
|
94
|
+
* while registration is open. Off by default. See `passwordless-signup.ts`.
|
|
95
|
+
*/
|
|
96
|
+
magicLinkCreatesUsers?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* What a refresh token presented after its reuse window does to its
|
|
99
|
+
* session. See `RebaseAuthConfig.refreshTokenReuse`. Default `"reject"`.
|
|
100
|
+
*/
|
|
101
|
+
refreshTokenReuse?: RefreshTokenReusePolicy;
|
|
102
|
+
/**
|
|
103
|
+
* Refuse password sign-in until the account's address is verified, and
|
|
104
|
+
* register confirm-first: `POST /auth/register` answers the same "check
|
|
105
|
+
* your inbox" whether or not the address already has an account, and
|
|
106
|
+
* signs nobody in. Off by default. Needs email; the boot refuses it
|
|
107
|
+
* without. See `RebaseAuthConfig.requireEmailVerification`.
|
|
108
|
+
*/
|
|
109
|
+
requireEmailVerification?: boolean;
|
|
89
110
|
}
|
|
111
|
+
/** What a refresh token replayed after its reuse window does to its session. */
|
|
112
|
+
export type RefreshTokenReusePolicy = "reject" | "revoke-session";
|
|
113
|
+
/** How long an email-verification link stays usable. */
|
|
114
|
+
export declare const EMAIL_VERIFICATION_TTL_MS: number;
|
|
90
115
|
/**
|
|
91
116
|
* Configuration for httpOnly refresh-token cookies.
|
|
92
117
|
*/
|
|
@@ -109,4 +134,24 @@ export interface CookieAuthConfig {
|
|
|
109
134
|
*/
|
|
110
135
|
secure?: boolean;
|
|
111
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* What {@link CreateSessionAndTokens} is told about the sign-in it opens.
|
|
139
|
+
*
|
|
140
|
+
* `method` is what `providerId` says for the session from now on — stored on
|
|
141
|
+
* its refresh token and carried across every rotation — so each door names
|
|
142
|
+
* its own rather than leaving it to a default.
|
|
143
|
+
*/
|
|
144
|
+
export interface SessionOptions {
|
|
145
|
+
/** `"password"`, `"anonymous"`, `"magic-link"`, `"otp"`, `"mfa"` or a provider id. */
|
|
146
|
+
method: string;
|
|
147
|
+
/** Only for the route that has just seen the second factor. */
|
|
148
|
+
skipMfaGate?: boolean;
|
|
149
|
+
aal?: "aal1" | "aal2";
|
|
150
|
+
}
|
|
151
|
+
/** Mint a session, as every sign-in door does. See `createSessionAndTokens`. */
|
|
152
|
+
export type CreateSessionAndTokens = (uid: string, userAgent: string, ipAddress: string, options: SessionOptions) => Promise<{
|
|
153
|
+
roleIds: string[];
|
|
154
|
+
accessToken: string;
|
|
155
|
+
refreshToken: string;
|
|
156
|
+
}>;
|
|
112
157
|
export declare function createAuthRoutes(config: AuthModuleConfig): Hono<HonoEnv>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /auth/scopes` — every scope this backend knows, described, and the
|
|
3
|
+
* ones the caller holds.
|
|
4
|
+
*
|
|
5
|
+
* What a screen needs to offer scopes for a key or a role: the built-in ones,
|
|
6
|
+
* the app's own `auth.scopes`, their wording and what their targets name. Any
|
|
7
|
+
* authenticated caller may read it; it describes the backend's vocabulary,
|
|
8
|
+
* not anybody's data.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import { Hono, type MiddlewareHandler } from "hono";
|
|
13
|
+
import type { AccessJudgeRepository } from "./token-revocation.js";
|
|
14
|
+
import type { HonoEnv } from "../api/types.js";
|
|
15
|
+
export interface ScopeRouteOptions {
|
|
16
|
+
serviceKey?: string;
|
|
17
|
+
resolveRoles?: (uid: string) => Promise<string[]>;
|
|
18
|
+
revocationRepo?: AccessJudgeRepository;
|
|
19
|
+
/** Authenticates `rk_` keys first, so a key can read what it holds. */
|
|
20
|
+
apiKeyPreAuth?: MiddlewareHandler<HonoEnv>;
|
|
21
|
+
}
|
|
22
|
+
export declare function createScopeRoutes(options: ScopeRouteOptions): Hono<HonoEnv>;
|