@rebasepro/server 0.23.0 → 0.24.1-canary.g56af4a4
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-DivE9bAx.js +402 -0
- package/dist/GCSStorageController-DivE9bAx.js.map +1 -0
- package/dist/S3StorageController-D_vKs4kP.js +480 -0
- package/dist/S3StorageController-D_vKs4kP.js.map +1 -0
- package/dist/api/ast-schema-editor.d.ts +92 -1
- package/dist/api/errors.d.ts +9 -0
- package/dist/api/live-schema-routes.d.ts +38 -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 +97 -11
- 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 +9 -7
- 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 +28 -1
- package/dist/{ast-schema-editor-Mvr50v_S.js → ast-schema-editor-CSdL0iCk.js} +310 -12
- package/dist/ast-schema-editor-CSdL0iCk.js.map +1 -0
- package/dist/auth/access.d.ts +121 -0
- package/dist/auth/adapter-middleware.d.ts +2 -1
- package/dist/auth/address-ownership.d.ts +16 -1
- 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 +24 -2
- package/dist/auth/admin-users-route.d.ts +2 -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 +46 -7
- package/dist/auth/builtin-auth-adapter.d.ts +8 -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 +71 -0
- package/dist/auth/impersonation.d.ts +110 -0
- package/dist/auth/index.d.ts +4 -2
- package/dist/auth/interfaces.d.ts +120 -59
- package/dist/auth/jwt-crypto.d.ts +7 -0
- package/dist/auth/jwt-keys.d.ts +4 -0
- package/dist/auth/jwt.d.ts +54 -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/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 +54 -1
- package/dist/auth/registration-policy.d.ts +17 -0
- package/dist/auth/routes.d.ts +45 -0
- package/dist/auth/scope-routes.d.ts +22 -0
- package/dist/auth/session-routes.d.ts +11 -6
- package/dist/auth/token-revocation.d.ts +50 -1
- package/dist/auth/user-picking.d.ts +35 -0
- package/dist/auth/verify-credential.d.ts +28 -0
- package/dist/{auth-B-GIMpDG.js → auth-OPq_FeMs.js} +3115 -638
- package/dist/auth-OPq_FeMs.js.map +1 -0
- package/dist/auth_adapter-CIItCTc8.js +21 -0
- package/dist/auth_adapter-CIItCTc8.js.map +1 -0
- package/dist/backend-DTAOsLQc.js.map +1 -1
- package/dist/backup/backup-common.d.ts +24 -3
- package/dist/backup/backup-routes.d.ts +46 -5
- package/dist/backup/backup-schedule.d.ts +33 -0
- package/dist/backup/backup-storage.d.ts +16 -0
- package/dist/backup/index.d.ts +5 -1
- package/dist/backup-5d7XhLX3.js +581 -0
- package/dist/backup-5d7XhLX3.js.map +1 -0
- package/dist/boot/bundle.d.ts +19 -0
- package/dist/boot/driver.d.ts +3 -0
- package/dist/boot/env.d.ts +56 -4
- package/dist/boot/http-server.d.ts +22 -0
- package/dist/boot/options.d.ts +16 -1
- package/dist/boot/runtime-cors.d.ts +17 -0
- package/dist/boot/security-headers.d.ts +26 -0
- package/dist/boot/static-routing.d.ts +65 -0
- package/dist/collection_patch-BRu-BvDv.js +472 -0
- package/dist/collection_patch-BRu-BvDv.js.map +1 -0
- package/dist/{contract-routes-CbFjuBwa.js → contract-routes-02lASyJI.js} +5 -8
- package/dist/contract-routes-02lASyJI.js.map +1 -0
- package/dist/cron/cron-routes.d.ts +1 -1
- package/dist/cron/cron-scheduler.d.ts +94 -23
- package/dist/cron/cron-store.d.ts +31 -4
- package/dist/cron/define-cron.d.ts +6 -2
- package/dist/cron/index.d.ts +1 -1
- package/dist/{cron-loader-DfTj2Hbi.js → cron-loader-D7OmleAb.js} +11 -7
- package/dist/cron-loader-D7OmleAb.js.map +1 -0
- package/dist/{cron-routes-eE8nif_b.js → cron-routes-CBfiaqEL.js} +34 -8
- package/dist/cron-routes-CBfiaqEL.js.map +1 -0
- package/dist/{cron-scheduler-B0pLfAix.js → cron-scheduler-C2vbETMo.js} +349 -65
- package/dist/cron-scheduler-C2vbETMo.js.map +1 -0
- package/dist/cron-store-B2Ih0gCN.js +540 -0
- package/dist/cron-store-B2Ih0gCN.js.map +1 -0
- package/dist/{ddl-bootstrap-C6mo0Kmz.js → ddl-bootstrap-BaqMSa4Y.js} +2 -2
- package/dist/{ddl-bootstrap-C6mo0Kmz.js.map → ddl-bootstrap-BaqMSa4Y.js.map} +1 -1
- package/dist/email/index.d.ts +2 -2
- package/dist/email/smtp-email-service.d.ts +23 -0
- package/dist/email/templates.d.ts +22 -0
- package/dist/email/types.d.ts +44 -0
- package/dist/env.d.ts +1 -39
- package/dist/{errors-DWsX4yTd.js → errors-D6_y86c5.js} +102 -8
- package/dist/errors-D6_y86c5.js.map +1 -0
- package/dist/filter-dialect-REl-TjZr.js +1628 -0
- package/dist/filter-dialect-REl-TjZr.js.map +1 -0
- package/dist/{function-loader-xnbDAPfa.js → function-loader-BDKKiBmy.js} +5 -4
- package/dist/function-loader-BDKKiBmy.js.map +1 -0
- package/dist/{function-routes-Chet4-lB.js → function-routes-DWB7BBsi.js} +25 -13
- package/dist/function-routes-DWB7BBsi.js.map +1 -0
- package/dist/functions/context.d.ts +26 -6
- package/dist/functions/define-function.d.ts +7 -4
- package/dist/functions/guards.d.ts +22 -5
- package/dist/functions/index.d.ts +2 -2
- package/dist/functions/index.js +114 -37
- package/dist/functions/index.js.map +1 -1
- 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-B4MpJfJK.js → history-recorder-DHenr3lm.js} +4 -4
- package/dist/{history-recorder-B4MpJfJK.js.map → history-recorder-DHenr3lm.js.map} +1 -1
- package/dist/{history-store-BhxWOuz9.js → history-store-DPofGVDp.js} +2 -2
- package/dist/{history-store-BhxWOuz9.js.map → history-store-DPofGVDp.js.map} +1 -1
- package/dist/index.d.ts +14 -7
- package/dist/index.es.js +23363 -17552
- package/dist/index.es.js.map +1 -1
- package/dist/init/health.d.ts +17 -2
- package/dist/init/shutdown.d.ts +20 -0
- package/dist/init.d.ts +70 -6
- 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-CazMYhyy.js → jobs-BpD4Q0qR.js} +235 -25
- package/dist/jobs-BpD4Q0qR.js.map +1 -0
- package/dist/{jwt-DnQHNFCl.js → jwt-Bqcu7cGt.js} +207 -77
- package/dist/jwt-Bqcu7cGt.js.map +1 -0
- package/dist/{keys-CogCQpxG.js → keys-GAVZqbqx.js} +3 -17
- package/dist/{keys-CogCQpxG.js.map → keys-GAVZqbqx.js.map} +1 -1
- package/dist/load-failure-UvzZgGXT.js +48 -0
- package/dist/load-failure-UvzZgGXT.js.map +1 -0
- 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-Bj4TYYUl.js → logs-routes-DAdv37GI.js} +48 -8
- 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 +7 -0
- package/dist/mcp/mcp-tools.d.ts +23 -9
- package/dist/mcp/oauth-metadata.d.ts +21 -16
- package/dist/mcp/oauth-routes.d.ts +7 -1
- package/dist/metrics/index.d.ts +18 -0
- package/dist/metrics/pool-wait.d.ts +50 -0
- package/dist/offline-8Ks3yp9n-CadBKm2D.js +3305 -0
- package/dist/offline-8Ks3yp9n-CadBKm2D.js.map +1 -0
- package/dist/offline-store-D9ytNNaa-BCr2vsFB.js +1903 -0
- package/dist/offline-store-D9ytNNaa-BCr2vsFB.js.map +1 -0
- package/dist/{openapi-generator-O_O24MAT.js → openapi-generator-BqoUh4Ij.js} +455 -80
- package/dist/openapi-generator-BqoUh4Ij.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-Czngl3p9.js → proxy-qRlqeUmO.js} +2 -2
- package/dist/{proxy-Czngl3p9.js.map → proxy-qRlqeUmO.js.map} +1 -1
- package/dist/{query-parser-DGRVFNM3.js → query-parser-DK9eMkb9.js} +497 -52
- package/dist/query-parser-DK9eMkb9.js.map +1 -0
- package/dist/{request-timeout-C_4C2BeR.js → request-timeout-Dnl2_OBc.js} +17 -10
- 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 +73 -3
- package/dist/schema-edit/local-source.d.ts +29 -0
- package/dist/schema-edit/project-root.d.ts +3 -2
- package/dist/schema-edit/remote-source.d.ts +20 -5
- package/dist/schema-edit/source-editor.d.ts +28 -0
- package/dist/schema-editor-routes-BFf3i8jm.js +136 -0
- package/dist/schema-editor-routes-BFf3i8jm.js.map +1 -0
- package/dist/serve-spa.d.ts +58 -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 +16 -0
- package/dist/services/webhook-service.d.ts +66 -14
- package/dist/soft-delete-params-47yBGs3_.js +668 -0
- package/dist/soft-delete-params-47yBGs3_.js.map +1 -0
- package/dist/sql-export-routes-BNNuGGLP.js +240 -0
- package/dist/sql-export-routes-BNNuGGLP.js.map +1 -0
- package/dist/{src-vkcwKXbT.js → src-CatHFUym.js} +439 -20
- package/dist/src-CatHFUym.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +48 -2
- package/dist/storage/LocalStorageController.d.ts +69 -2
- package/dist/storage/S3StorageController.d.ts +39 -3
- package/dist/storage/image-transform.d.ts +5 -0
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/maintenance-routes.d.ts +23 -0
- package/dist/storage/maintenance.d.ts +126 -0
- package/dist/storage/property-limits.d.ts +41 -6
- package/dist/storage/rendition-cache.d.ts +9 -11
- package/dist/storage/request-keys.d.ts +15 -0
- package/dist/storage/request-principals.d.ts +30 -0
- package/dist/storage/requested-object.d.ts +74 -0
- package/dist/storage/routes.d.ts +52 -14
- package/dist/storage/source-variables.d.ts +10 -0
- package/dist/storage/streams.d.ts +42 -0
- package/dist/storage/tus-handler.d.ts +76 -7
- package/dist/storage/types.d.ts +101 -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/dist/utils/logger.d.ts +12 -0
- package/package.json +5 -5
- package/dist/GCSStorageController-CjrA4PMo.js +0 -242
- package/dist/GCSStorageController-CjrA4PMo.js.map +0 -1
- package/dist/S3StorageController-B6pKDNVj.js +0 -264
- package/dist/S3StorageController-B6pKDNVj.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-Mvr50v_S.js.map +0 -1
- package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
- package/dist/auth-B-GIMpDG.js.map +0 -1
- package/dist/backup-D7YR94N3.js +0 -253
- package/dist/backup-D7YR94N3.js.map +0 -1
- package/dist/contract-routes-CbFjuBwa.js.map +0 -1
- package/dist/cron-loader-DfTj2Hbi.js.map +0 -1
- package/dist/cron-routes-eE8nif_b.js.map +0 -1
- package/dist/cron-scheduler-B0pLfAix.js.map +0 -1
- package/dist/cron-store-TcoGz-xS.js +0 -305
- package/dist/cron-store-TcoGz-xS.js.map +0 -1
- package/dist/dynamic-import-X40pTZUQ.js +0 -23
- package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
- package/dist/errors-DWsX4yTd.js.map +0 -1
- package/dist/function-loader-xnbDAPfa.js.map +0 -1
- package/dist/function-routes-Chet4-lB.js.map +0 -1
- package/dist/jobs-CazMYhyy.js.map +0 -1
- package/dist/jwt-DnQHNFCl.js.map +0 -1
- package/dist/logger-DO2PZc4i.js.map +0 -1
- package/dist/logs-routes-Bj4TYYUl.js.map +0 -1
- package/dist/openapi-generator-O_O24MAT.js.map +0 -1
- package/dist/query-parser-DGRVFNM3.js.map +0 -1
- package/dist/request-timeout-C_4C2BeR.js.map +0 -1
- package/dist/schema-editor-routes-C5-lh_jO.js +0 -87
- package/dist/schema-editor-routes-C5-lh_jO.js.map +0 -1
- package/dist/src-pmvW7BFx.js +0 -6688
- package/dist/src-pmvW7BFx.js.map +0 -1
- package/dist/src-vkcwKXbT.js.map +0 -1
- package/dist/types-BfKcm9do.js +0 -39
- package/dist/types-BfKcm9do.js.map +0 -1
|
@@ -19,6 +19,11 @@ export interface UserData {
|
|
|
19
19
|
emailVerificationToken?: string | null;
|
|
20
20
|
emailVerificationSentAt?: Date | null;
|
|
21
21
|
isAnonymous?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* An administrator switched the account off: no sign-in, no refresh, and
|
|
24
|
+
* no token it already holds is honoured. See `setUserDisabled`.
|
|
25
|
+
*/
|
|
26
|
+
disabled?: boolean;
|
|
22
27
|
metadata?: Record<string, unknown>;
|
|
23
28
|
createdAt: Date;
|
|
24
29
|
updatedAt: Date;
|
|
@@ -35,6 +40,16 @@ export interface CreateUserData {
|
|
|
35
40
|
isAnonymous?: boolean;
|
|
36
41
|
metadata?: Record<string, unknown>;
|
|
37
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* An address change waiting for its confirmation link. See
|
|
45
|
+
* {@link UserRepository.setPendingEmailChange}.
|
|
46
|
+
*/
|
|
47
|
+
export interface PendingEmailChange {
|
|
48
|
+
/** The address the account is moving to, normalized. */
|
|
49
|
+
email: string;
|
|
50
|
+
/** When the link was mailed; it is refused 24 hours after this. */
|
|
51
|
+
sentAt: Date;
|
|
52
|
+
}
|
|
38
53
|
/**
|
|
39
54
|
* User Identity Data (OAuth accounts linked to user)
|
|
40
55
|
*/
|
|
@@ -92,36 +107,6 @@ export interface OAuthProvider<T = unknown> {
|
|
|
92
107
|
*/
|
|
93
108
|
verify(payload: T): Promise<OAuthProviderProfile | null>;
|
|
94
109
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Role data structure
|
|
97
|
-
*/
|
|
98
|
-
export interface RoleData {
|
|
99
|
-
id: string;
|
|
100
|
-
name: string;
|
|
101
|
-
isAdmin: boolean;
|
|
102
|
-
defaultPermissions: {
|
|
103
|
-
read?: boolean;
|
|
104
|
-
create?: boolean;
|
|
105
|
-
edit?: boolean;
|
|
106
|
-
delete?: boolean;
|
|
107
|
-
} | null;
|
|
108
|
-
collectionPermissions: Record<string, {
|
|
109
|
-
read?: boolean;
|
|
110
|
-
create?: boolean;
|
|
111
|
-
edit?: boolean;
|
|
112
|
-
delete?: boolean;
|
|
113
|
-
}> | null;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Data for creating a new role
|
|
117
|
-
*/
|
|
118
|
-
export interface CreateRoleData {
|
|
119
|
-
id: string;
|
|
120
|
-
name: string;
|
|
121
|
-
isAdmin?: boolean;
|
|
122
|
-
defaultPermissions?: RoleData["defaultPermissions"];
|
|
123
|
-
collectionPermissions?: RoleData["collectionPermissions"];
|
|
124
|
-
}
|
|
125
110
|
/**
|
|
126
111
|
* Refresh token info
|
|
127
112
|
*/
|
|
@@ -161,6 +146,31 @@ export interface RefreshTokenInfo {
|
|
|
161
146
|
* that do not store it; both read as `aal1`, the restrictive value.
|
|
162
147
|
*/
|
|
163
148
|
aal?: "aal1" | "aal2";
|
|
149
|
+
/**
|
|
150
|
+
* How the session was signed in — `"password"`, `"anonymous"`,
|
|
151
|
+
* `"magic-link"`, `"otp"`, `"mfa"` or a provider id such as `"google"`.
|
|
152
|
+
* See {@link RefreshTokenSession.method}. Absent on rows written before
|
|
153
|
+
* the column existed, which read as `"password"`.
|
|
154
|
+
*/
|
|
155
|
+
method?: string;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* What {@link TokenRepository.getAccountAccessState} reads about an account
|
|
159
|
+
* that exists.
|
|
160
|
+
*/
|
|
161
|
+
export interface AccountAccessState {
|
|
162
|
+
/** The account's roles as the database has them now. */
|
|
163
|
+
roles: string[];
|
|
164
|
+
/** The revocation watermark: sessions that began before it are void. */
|
|
165
|
+
tokensValidAfter: Date | null;
|
|
166
|
+
/**
|
|
167
|
+
* Whether the session asked about has a refresh token that is not
|
|
168
|
+
* revoked. `undefined` when no session was asked about, or the store
|
|
169
|
+
* cannot tell (a refresh-token table without session grouping).
|
|
170
|
+
*/
|
|
171
|
+
sessionActive?: boolean;
|
|
172
|
+
/** The account is switched off (`UserData.disabled`). */
|
|
173
|
+
disabled?: boolean;
|
|
164
174
|
}
|
|
165
175
|
/**
|
|
166
176
|
* Identity of the sign-in a refresh token belongs to, threaded through
|
|
@@ -179,6 +189,14 @@ export interface RefreshTokenSession {
|
|
|
179
189
|
* assurance level is a property of the *sign-in*, not of the account.
|
|
180
190
|
*/
|
|
181
191
|
aal?: "aal1" | "aal2";
|
|
192
|
+
/**
|
|
193
|
+
* How the session was signed in: what `providerId` says in every auth
|
|
194
|
+
* response for it. Written at sign-in and carried across rotations like
|
|
195
|
+
* {@link aal}, because a refresh is not a sign-in and has nothing else to
|
|
196
|
+
* read it from — answering `"password"` there turned a Google session into
|
|
197
|
+
* a password one an access-token lifetime after it began.
|
|
198
|
+
*/
|
|
199
|
+
method?: string;
|
|
182
200
|
/**
|
|
183
201
|
* The hash of the token this one replaces, when it is minted by rotating
|
|
184
202
|
* one. A repository that honours it writes the new token only while that
|
|
@@ -216,7 +234,11 @@ export interface ListUsersOptions {
|
|
|
216
234
|
limit?: number;
|
|
217
235
|
/** Number of results to skip (default 0) */
|
|
218
236
|
offset?: number;
|
|
219
|
-
/**
|
|
237
|
+
/**
|
|
238
|
+
* Search term — a case-insensitive substring of the email, the display
|
|
239
|
+
* name, any role the user holds, or the uid. (The Postgres store matches
|
|
240
|
+
* all four; the Mongo store, email and display name.)
|
|
241
|
+
*/
|
|
220
242
|
search?: string;
|
|
221
243
|
/** Field to sort by (default "createdAt") */
|
|
222
244
|
orderBy?: string;
|
|
@@ -302,6 +324,16 @@ export interface UserRepository {
|
|
|
302
324
|
* List users with server-side pagination, search, and sorting.
|
|
303
325
|
*/
|
|
304
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[]>;
|
|
305
337
|
/**
|
|
306
338
|
* Update user's password hash. `null` removes the password: the account
|
|
307
339
|
* then signs in only by link, code or provider.
|
|
@@ -319,10 +351,6 @@ export interface UserRepository {
|
|
|
319
351
|
* Find user by email verification token
|
|
320
352
|
*/
|
|
321
353
|
getUserByVerificationToken(token: string): Promise<UserData | null>;
|
|
322
|
-
/**
|
|
323
|
-
* Get roles for a user
|
|
324
|
-
*/
|
|
325
|
-
getUserRoles(uid: string): Promise<RoleData[]>;
|
|
326
354
|
/**
|
|
327
355
|
* Get role IDs for a user
|
|
328
356
|
*/
|
|
@@ -340,34 +368,45 @@ export interface UserRepository {
|
|
|
340
368
|
*/
|
|
341
369
|
getUserWithRoles(uid: string): Promise<{
|
|
342
370
|
user: UserData;
|
|
343
|
-
roles:
|
|
371
|
+
roles: string[];
|
|
344
372
|
} | null>;
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Abstract role repository interface.
|
|
348
|
-
* Handles all role-related database operations.
|
|
349
|
-
*/
|
|
350
|
-
export interface RoleRepository {
|
|
351
|
-
/**
|
|
352
|
-
* Get a role by ID
|
|
353
|
-
*/
|
|
354
|
-
getRoleById(id: string): Promise<RoleData | null>;
|
|
355
373
|
/**
|
|
356
|
-
*
|
|
374
|
+
* Switch an account off, or back on. Off, it cannot sign in or refresh,
|
|
375
|
+
* and the tokens it holds are refused (`judgeAccessToken`). Optional: a
|
|
376
|
+
* repository without it cannot disable accounts, and the admin route says
|
|
377
|
+
* so rather than pretending.
|
|
357
378
|
*/
|
|
358
|
-
|
|
379
|
+
setUserDisabled?(uid: string, disabled: boolean): Promise<void>;
|
|
359
380
|
/**
|
|
360
|
-
*
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
*
|
|
365
|
-
|
|
366
|
-
|
|
381
|
+
* Record the address an account is moving to and the hash of the token
|
|
382
|
+
* mailed there, stamped now — or with `null`, drop it. One per account: a
|
|
383
|
+
* new request replaces the last, and its link stops working.
|
|
384
|
+
*
|
|
385
|
+
* Nothing is reserved by it. The address stays free for anyone to
|
|
386
|
+
* register until the link is followed, because a pending change that held
|
|
387
|
+
* it would let any account lock a stranger out of signing up with their
|
|
388
|
+
* own address.
|
|
389
|
+
*/
|
|
390
|
+
setPendingEmailChange?(uid: string, change: {
|
|
391
|
+
email: string;
|
|
392
|
+
tokenHash: string;
|
|
393
|
+
} | null): Promise<void>;
|
|
394
|
+
/** The account's pending change, if it has one. */
|
|
395
|
+
getPendingEmailChange?(uid: string): Promise<PendingEmailChange | null>;
|
|
396
|
+
/** The account whose pending change `tokenHash` confirms, with the change. */
|
|
397
|
+
findPendingEmailChange?(tokenHash: string): Promise<{
|
|
398
|
+
user: UserData;
|
|
399
|
+
change: PendingEmailChange;
|
|
400
|
+
} | null>;
|
|
367
401
|
/**
|
|
368
|
-
*
|
|
402
|
+
* Move the account onto its pending address, in one write that holds only
|
|
403
|
+
* while `tokenHash` is still that change's token: the address becomes the
|
|
404
|
+
* account's, verified, and the pending change and any outstanding
|
|
405
|
+
* verification token are cleared. `null` when the token no longer names
|
|
406
|
+
* the change (confirmed already, replaced, cancelled). An address another
|
|
407
|
+
* account holds by then is a 409 `EMAIL_EXISTS`, as `updateUser` answers.
|
|
369
408
|
*/
|
|
370
|
-
|
|
409
|
+
applyPendingEmailChange?(uid: string, tokenHash: string): Promise<UserData | null>;
|
|
371
410
|
}
|
|
372
411
|
/**
|
|
373
412
|
* Abstract token repository interface.
|
|
@@ -418,6 +457,24 @@ export interface TokenRepository {
|
|
|
418
457
|
* user's tokens so a rotation racing the delete cannot survive it.
|
|
419
458
|
*/
|
|
420
459
|
setTokensValidAfter?(uid: string, at: Date): Promise<void>;
|
|
460
|
+
/**
|
|
461
|
+
* Everything an access token is judged against, in one read — or `null`
|
|
462
|
+
* when there is no such account.
|
|
463
|
+
*
|
|
464
|
+
* An access token is a bearer credential minted up to an hour ago, and the
|
|
465
|
+
* account it names may since have been deleted, revoked or demoted. Every
|
|
466
|
+
* door that honours one asks this before it does: the data plane, the admin
|
|
467
|
+
* gates, the realtime socket on every frame. See `judgeAccessToken`.
|
|
468
|
+
*
|
|
469
|
+
* `sessionId` is the token's `sid`: when given, the state also says
|
|
470
|
+
* whether that sign-in is still live, which is how signing one device out
|
|
471
|
+
* reaches the access token that device holds.
|
|
472
|
+
*
|
|
473
|
+
* Optional. Without it the judge composes the answer from
|
|
474
|
+
* `getUserWithRoles` and `getTokensValidAfter`, two reads instead of one,
|
|
475
|
+
* and cannot see one revoked session — only every session at once.
|
|
476
|
+
*/
|
|
477
|
+
getAccountAccessState?(uid: string, sessionId?: string): Promise<AccountAccessState | null>;
|
|
421
478
|
/**
|
|
422
479
|
* Find a refresh token by hash
|
|
423
480
|
*/
|
|
@@ -455,7 +512,11 @@ export interface TokenRepository {
|
|
|
455
512
|
*/
|
|
456
513
|
deleteAllPasswordResetTokensForUser(uid: string): Promise<void>;
|
|
457
514
|
/**
|
|
458
|
-
*
|
|
515
|
+
* Delete every token past its expiry: reset links, magic links and email
|
|
516
|
+
* codes, refresh tokens, and whatever else the repository keeps that
|
|
517
|
+
* expires. A token is refused when presented whether or not this has run,
|
|
518
|
+
* so it is housekeeping, not revocation. The server calls it once an hour
|
|
519
|
+
* on one instance of the fleet (`startExpiredTokenSweep`).
|
|
459
520
|
*/
|
|
460
521
|
deleteExpiredTokens(): Promise<void>;
|
|
461
522
|
/**
|
|
@@ -613,5 +674,5 @@ export interface MfaRepository {
|
|
|
613
674
|
/**
|
|
614
675
|
* Combined auth repository interface for convenience
|
|
615
676
|
*/
|
|
616
|
-
export interface AuthRepository extends UserRepository,
|
|
677
|
+
export interface AuthRepository extends UserRepository, TokenRepository, MfaRepository {
|
|
617
678
|
}
|
|
@@ -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
|
@@ -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,19 @@ 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;
|
|
117
|
+
/**
|
|
118
|
+
* How long a refresh token survives inactivity when no lifetime is configured.
|
|
119
|
+
* Sliding: every rotation re-ups it, so an active user stays signed in.
|
|
120
|
+
*/
|
|
121
|
+
export declare const DEFAULT_REFRESH_EXPIRES_IN = "30d";
|
|
92
122
|
/**
|
|
93
123
|
* Configure JWT settings - call this during initialization.
|
|
94
124
|
* Validates the secret strength to prevent deployment with default/weak secrets.
|
|
@@ -105,6 +135,12 @@ export declare function configureJwt(config: JwtConfig): void;
|
|
|
105
135
|
export declare function getJwks(): {
|
|
106
136
|
keys: PublicJwk[];
|
|
107
137
|
};
|
|
138
|
+
/**
|
|
139
|
+
* The secret `configureJwt` was given, or undefined before it is called. For
|
|
140
|
+
* `mfa-crypto.ts`, which falls back to it when no MFA key is set — the secret
|
|
141
|
+
* passed in code, not only the one in `JWT_SECRET`.
|
|
142
|
+
*/
|
|
143
|
+
export declare function configuredJwtSecret(): string | undefined;
|
|
108
144
|
/** Is this backend signing access tokens asymmetrically? */
|
|
109
145
|
export declare function hasAsymmetricSigningKey(): boolean;
|
|
110
146
|
/**
|
|
@@ -128,7 +164,12 @@ export declare function generateAccessToken(uid: string, roles: string[], aal?:
|
|
|
128
164
|
* account. Written into the token so the RLS identity and the WebSocket
|
|
129
165
|
* path can tell the two apart without a database lookup.
|
|
130
166
|
*/
|
|
131
|
-
isAnonymous?: boolean
|
|
167
|
+
isAnonymous?: boolean,
|
|
168
|
+
/**
|
|
169
|
+
* The sign-in this token belongs to — see {@link AccessTokenPayload.sid}.
|
|
170
|
+
* Every token the auth routes mint carries one.
|
|
171
|
+
*/
|
|
172
|
+
sessionId?: string): Promise<string>;
|
|
132
173
|
/**
|
|
133
174
|
* Get the expiration time of an access token in milliseconds from now
|
|
134
175
|
*/
|
|
@@ -165,7 +206,8 @@ export declare function getAccessTokenExpiry(): number;
|
|
|
165
206
|
*/
|
|
166
207
|
export declare function verifyAccessToken(token: string): Promise<AccessTokenPayload | null>;
|
|
167
208
|
/**
|
|
168
|
-
* Generate a random refresh token
|
|
209
|
+
* Generate a random refresh token. Long-lived: 30 days by default, sliding —
|
|
210
|
+
* see {@link getRefreshTokenTtlMs}.
|
|
169
211
|
*/
|
|
170
212
|
export declare function generateRefreshToken(): string;
|
|
171
213
|
/**
|
|
@@ -236,6 +278,13 @@ export interface DownloadTokenPayload {
|
|
|
236
278
|
* existed are read.
|
|
237
279
|
*/
|
|
238
280
|
storageId: string;
|
|
281
|
+
/**
|
|
282
|
+
* An opaque mark of the user who minted the token, for rate limiting only:
|
|
283
|
+
* a read that spends the token is charged to that user's allowance rather
|
|
284
|
+
* than to the reader's address. A hash, because the token travels in a URL.
|
|
285
|
+
* Absent for a token minted for nobody.
|
|
286
|
+
*/
|
|
287
|
+
rl?: string;
|
|
239
288
|
}
|
|
240
289
|
/**
|
|
241
290
|
* Generate a short-lived download token scoped to a specific file path or prefix
|
|
@@ -254,7 +303,9 @@ export interface DownloadTokenPayload {
|
|
|
254
303
|
* site that forgets to pass a named source produces a default-scoped token,
|
|
255
304
|
* which fails closed at `/file/*` rather than over-granting.
|
|
256
305
|
*/
|
|
257
|
-
export declare function generateDownloadToken(path: string, expiresInSeconds?: number, storageId?: string | null
|
|
306
|
+
export declare function generateDownloadToken(path: string, expiresInSeconds?: number, storageId?: string | null,
|
|
307
|
+
/** The user minting it, so the reads it buys are charged to them. */
|
|
308
|
+
principal?: string | null): Promise<string>;
|
|
258
309
|
/**
|
|
259
310
|
* Verify and decode a download token.
|
|
260
311
|
*
|
|
@@ -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
|
/**
|
|
@@ -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>;
|