@selvajs/platform 0.21.0-beta.3 → 1.0.0-beta.4
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 +10 -7
- package/dist/access/index.d.ts +0 -1
- package/dist/access/index.js +0 -1
- package/dist/access/rules.d.ts +7 -14
- package/dist/access/rules.js +22 -26
- package/dist/apiTokens/index.d.ts +1 -2
- package/dist/apiTokens/index.js +1 -2
- package/dist/apiTokens/interface.d.ts +0 -1
- package/dist/apiTokens/interface.js +0 -1
- package/dist/apiTokens/scope.d.ts +0 -1
- package/dist/apiTokens/scope.js +0 -1
- package/dist/apiTokens/types.d.ts +0 -1
- package/dist/apiTokens/types.js +0 -1
- package/dist/auth/index.d.ts +0 -1
- package/dist/auth/index.js +0 -1
- package/dist/auth/interface.d.ts +4 -23
- package/dist/auth/interface.js +0 -1
- package/dist/auth/types.d.ts +4 -2
- package/dist/auth/types.js +0 -1
- package/dist/bindings/index.d.ts +0 -1
- package/dist/bindings/index.js +0 -1
- package/dist/bindings/interface.d.ts +0 -1
- package/dist/bindings/interface.js +0 -1
- package/dist/computeServer/index.d.ts +1 -7
- package/dist/computeServer/index.js +2 -8
- package/dist/computeServer/interface.d.ts +9 -6
- package/dist/computeServer/interface.js +0 -1
- package/dist/computeServer/public.d.ts +6 -0
- package/dist/computeServer/public.js +2 -0
- package/dist/computeServer/secrets.d.ts +12 -6
- package/dist/computeServer/secrets.js +35 -15
- package/dist/computeServer/types.d.ts +5 -2
- package/dist/computeServer/types.js +4 -1
- package/dist/computeServer/utils.d.ts +17 -6
- package/dist/computeServer/utils.js +31 -21
- package/dist/config.d.ts +1 -2
- package/dist/config.js +0 -1
- package/dist/context.d.ts +11 -1
- package/dist/context.js +51 -2
- package/dist/data/index.d.ts +0 -1
- package/dist/data/index.js +0 -1
- package/dist/data/interface.d.ts +0 -1
- package/dist/data/interface.js +0 -1
- package/dist/definitions/index.d.ts +2 -3
- package/dist/definitions/index.js +2 -3
- package/dist/definitions/interface.d.ts +0 -1
- package/dist/definitions/interface.js +0 -1
- package/dist/definitions/paths.d.ts +0 -1
- package/dist/definitions/paths.js +1 -12
- package/dist/definitions/schemas.d.ts +0 -21
- package/dist/definitions/schemas.js +6 -19
- package/dist/definitions/types.d.ts +3 -4
- package/dist/definitions/types.js +0 -3
- package/dist/errors/interface.d.ts +1 -2
- package/dist/errors/interface.js +0 -1
- package/dist/errors.d.ts +8 -1
- package/dist/errors.js +11 -1
- package/dist/events/audit.d.ts +1 -2
- package/dist/events/audit.js +0 -1
- package/dist/events/index.d.ts +0 -1
- package/dist/events/index.js +0 -1
- package/dist/events/interface.d.ts +0 -1
- package/dist/events/interface.js +0 -1
- package/dist/index.d.ts +18 -61
- package/dist/index.js +18 -24
- package/dist/invites/index.d.ts +0 -1
- package/dist/invites/index.js +0 -1
- package/dist/invites/interface.d.ts +5 -6
- package/dist/invites/interface.js +0 -1
- package/dist/invites/types.d.ts +0 -1
- package/dist/invites/types.js +0 -1
- package/dist/logging/interface.d.ts +0 -1
- package/dist/logging/interface.js +0 -1
- package/dist/metrics/interface.d.ts +0 -1
- package/dist/metrics/interface.js +0 -1
- package/dist/notifications/index.d.ts +0 -1
- package/dist/notifications/index.js +0 -1
- package/dist/notifications/interface.d.ts +1 -3
- package/dist/notifications/interface.js +1 -3
- package/dist/notifications/types.d.ts +0 -1
- package/dist/notifications/types.js +0 -1
- package/dist/organizations/index.d.ts +2 -3
- package/dist/organizations/index.js +1 -2
- package/dist/organizations/interface.d.ts +3 -5
- package/dist/organizations/interface.js +0 -1
- package/dist/organizations/paths.d.ts +1 -6
- package/dist/organizations/paths.js +2 -20
- package/dist/organizations/schemas.d.ts +5 -7
- package/dist/organizations/schemas.js +20 -13
- package/dist/organizations/types.d.ts +0 -1
- package/dist/organizations/types.js +0 -1
- package/dist/pagination.d.ts +9 -3
- package/dist/pagination.js +15 -1
- package/dist/permissions/index.d.ts +2 -2
- package/dist/permissions/index.js +2 -2
- package/dist/permissions/interface.d.ts +4 -4
- package/dist/permissions/interface.js +0 -1
- package/dist/permissions/types.d.ts +0 -1
- package/dist/permissions/types.js +0 -1
- package/dist/platformProjects/index.d.ts +1 -3
- package/dist/platformProjects/index.js +1 -2
- package/dist/platformProjects/interface.d.ts +0 -1
- package/dist/platformProjects/interface.js +0 -1
- package/dist/platformProjects/schemas.d.ts +0 -1
- package/dist/platformProjects/schemas.js +0 -1
- package/dist/platformProjects/types.d.ts +3 -2
- package/dist/platformProjects/types.js +0 -1
- package/dist/projects/index.d.ts +0 -1
- package/dist/projects/index.js +0 -1
- package/dist/projects/interface.d.ts +0 -1
- package/dist/projects/interface.js +0 -1
- package/dist/projects/schemas.d.ts +0 -1
- package/dist/projects/schemas.js +0 -1
- package/dist/projects/types.d.ts +0 -1
- package/dist/projects/types.js +0 -1
- package/dist/shareLinks/index.d.ts +0 -1
- package/dist/shareLinks/index.js +0 -1
- package/dist/shareLinks/interface.d.ts +6 -3
- package/dist/shareLinks/interface.js +0 -1
- package/dist/shareLinks/schemas.d.ts +0 -1
- package/dist/shareLinks/schemas.js +4 -2
- package/dist/shareLinks/types.d.ts +0 -1
- package/dist/shareLinks/types.js +0 -1
- package/dist/solveCache/interface.d.ts +2 -3
- package/dist/solveCache/interface.js +0 -1
- package/dist/storage/assetClasses.d.ts +4 -8
- package/dist/storage/assetClasses.js +6 -21
- package/dist/storage/cacheBust.d.ts +0 -1
- package/dist/storage/cacheBust.js +5 -3
- package/dist/storage/image.d.ts +8 -5
- package/dist/storage/image.js +74 -12
- package/dist/storage/index.d.ts +2 -4
- package/dist/storage/index.js +2 -3
- package/dist/storage/interface.d.ts +11 -3
- package/dist/storage/interface.js +0 -1
- package/dist/storage/safeKey.d.ts +7 -0
- package/dist/storage/safeKey.js +12 -0
- package/dist/testing/index.d.ts +5 -2
- package/dist/testing/index.js +3 -2
- package/dist/testing/suites/apiTokenStoreSuite.d.ts +0 -1
- package/dist/testing/suites/apiTokenStoreSuite.js +0 -1
- package/dist/testing/suites/auditQuerySuite.d.ts +18 -0
- package/dist/testing/suites/auditQuerySuite.js +73 -0
- package/dist/testing/suites/authProviderSuite.d.ts +14 -3
- package/dist/testing/suites/authProviderSuite.js +57 -32
- package/dist/testing/suites/computeServerStoreSuite.d.ts +1 -2
- package/dist/testing/suites/computeServerStoreSuite.js +68 -4
- package/dist/testing/suites/definitionStoreSuite.d.ts +2 -1
- package/dist/testing/suites/definitionStoreSuite.js +19 -16
- package/dist/testing/suites/emailLinkAuthSuite.d.ts +2 -5
- package/dist/testing/suites/emailLinkAuthSuite.js +1 -9
- package/dist/testing/suites/eventSinkSuite.d.ts +0 -1
- package/dist/testing/suites/eventSinkSuite.js +9 -1
- package/dist/testing/suites/helpers.d.ts +5 -1
- package/dist/testing/suites/helpers.js +7 -1
- package/dist/testing/suites/inviteStoreSuite.d.ts +0 -1
- package/dist/testing/suites/inviteStoreSuite.js +17 -7
- package/dist/testing/suites/oauthAuthSuite.d.ts +18 -0
- package/dist/testing/suites/oauthAuthSuite.js +26 -0
- package/dist/testing/suites/orgStoreSuite.d.ts +5 -1
- package/dist/testing/suites/orgStoreSuite.js +26 -7
- package/dist/testing/suites/platformPermissionStoreSuite.d.ts +0 -5
- package/dist/testing/suites/platformPermissionStoreSuite.js +12 -63
- package/dist/testing/suites/platformProjectGrantStoreSuite.d.ts +0 -1
- package/dist/testing/suites/platformProjectGrantStoreSuite.js +0 -5
- package/dist/testing/suites/projectStoreSuite.d.ts +5 -1
- package/dist/testing/suites/projectStoreSuite.js +6 -6
- package/dist/testing/suites/shareLinkStoreSuite.d.ts +2 -1
- package/dist/testing/suites/shareLinkStoreSuite.js +22 -25
- package/dist/testing/suites/solveMetricSinkSuite.d.ts +0 -1
- package/dist/testing/suites/solveMetricSinkSuite.js +2 -2
- package/dist/testing/suites/storageProviderSuite.d.ts +0 -1
- package/dist/testing/suites/storageProviderSuite.js +15 -10
- package/dist/testing/suites/userProfileStoreSuite.d.ts +1 -2
- package/dist/testing/suites/userProfileStoreSuite.js +42 -7
- package/dist/userProfile/index.d.ts +2 -2
- package/dist/userProfile/index.js +2 -2
- package/dist/userProfile/interface.d.ts +5 -6
- package/dist/userProfile/interface.js +0 -1
- package/dist/userProfile/types.d.ts +2 -1
- package/dist/userProfile/types.js +2 -1
- package/dist/utils/audit.d.ts +0 -1
- package/dist/utils/audit.js +0 -1
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.js +0 -1
- package/package.json +7 -6
- package/dist/access/index.d.ts.map +0 -1
- package/dist/access/index.js.map +0 -1
- package/dist/access/rules.d.ts.map +0 -1
- package/dist/access/rules.js.map +0 -1
- package/dist/apiTokens/index.d.ts.map +0 -1
- package/dist/apiTokens/index.js.map +0 -1
- package/dist/apiTokens/interface.d.ts.map +0 -1
- package/dist/apiTokens/interface.js.map +0 -1
- package/dist/apiTokens/scope.d.ts.map +0 -1
- package/dist/apiTokens/scope.js.map +0 -1
- package/dist/apiTokens/types.d.ts.map +0 -1
- package/dist/apiTokens/types.js.map +0 -1
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/interface.d.ts.map +0 -1
- package/dist/auth/interface.js.map +0 -1
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/auth/types.js.map +0 -1
- package/dist/bindings/index.d.ts.map +0 -1
- package/dist/bindings/index.js.map +0 -1
- package/dist/bindings/interface.d.ts.map +0 -1
- package/dist/bindings/interface.js.map +0 -1
- package/dist/computeServer/index.d.ts.map +0 -1
- package/dist/computeServer/index.js.map +0 -1
- package/dist/computeServer/interface.d.ts.map +0 -1
- package/dist/computeServer/interface.js.map +0 -1
- package/dist/computeServer/secrets.d.ts.map +0 -1
- package/dist/computeServer/secrets.js.map +0 -1
- package/dist/computeServer/types.d.ts.map +0 -1
- package/dist/computeServer/types.js.map +0 -1
- package/dist/computeServer/utils.d.ts.map +0 -1
- package/dist/computeServer/utils.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js.map +0 -1
- package/dist/data/index.d.ts.map +0 -1
- package/dist/data/index.js.map +0 -1
- package/dist/data/interface.d.ts.map +0 -1
- package/dist/data/interface.js.map +0 -1
- package/dist/definitions/index.d.ts.map +0 -1
- package/dist/definitions/index.js.map +0 -1
- package/dist/definitions/interface.d.ts.map +0 -1
- package/dist/definitions/interface.js.map +0 -1
- package/dist/definitions/paths.d.ts.map +0 -1
- package/dist/definitions/paths.js.map +0 -1
- package/dist/definitions/schemas.d.ts.map +0 -1
- package/dist/definitions/schemas.js.map +0 -1
- package/dist/definitions/types.d.ts.map +0 -1
- package/dist/definitions/types.js.map +0 -1
- package/dist/errors/interface.d.ts.map +0 -1
- package/dist/errors/interface.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/events/audit.d.ts.map +0 -1
- package/dist/events/audit.js.map +0 -1
- package/dist/events/index.d.ts.map +0 -1
- package/dist/events/index.js.map +0 -1
- package/dist/events/interface.d.ts.map +0 -1
- package/dist/events/interface.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invites/index.d.ts.map +0 -1
- package/dist/invites/index.js.map +0 -1
- package/dist/invites/interface.d.ts.map +0 -1
- package/dist/invites/interface.js.map +0 -1
- package/dist/invites/types.d.ts.map +0 -1
- package/dist/invites/types.js.map +0 -1
- package/dist/logging/interface.d.ts.map +0 -1
- package/dist/logging/interface.js.map +0 -1
- package/dist/metrics/interface.d.ts.map +0 -1
- package/dist/metrics/interface.js.map +0 -1
- package/dist/notifications/index.d.ts.map +0 -1
- package/dist/notifications/index.js.map +0 -1
- package/dist/notifications/interface.d.ts.map +0 -1
- package/dist/notifications/interface.js.map +0 -1
- package/dist/notifications/types.d.ts.map +0 -1
- package/dist/notifications/types.js.map +0 -1
- package/dist/organizations/index.d.ts.map +0 -1
- package/dist/organizations/index.js.map +0 -1
- package/dist/organizations/interface.d.ts.map +0 -1
- package/dist/organizations/interface.js.map +0 -1
- package/dist/organizations/paths.d.ts.map +0 -1
- package/dist/organizations/paths.js.map +0 -1
- package/dist/organizations/schemas.d.ts.map +0 -1
- package/dist/organizations/schemas.js.map +0 -1
- package/dist/organizations/types.d.ts.map +0 -1
- package/dist/organizations/types.js.map +0 -1
- package/dist/pagination.d.ts.map +0 -1
- package/dist/pagination.js.map +0 -1
- package/dist/permissions/index.d.ts.map +0 -1
- package/dist/permissions/index.js.map +0 -1
- package/dist/permissions/interface.d.ts.map +0 -1
- package/dist/permissions/interface.js.map +0 -1
- package/dist/permissions/types.d.ts.map +0 -1
- package/dist/permissions/types.js.map +0 -1
- package/dist/platformProjects/index.d.ts.map +0 -1
- package/dist/platformProjects/index.js.map +0 -1
- package/dist/platformProjects/interface.d.ts.map +0 -1
- package/dist/platformProjects/interface.js.map +0 -1
- package/dist/platformProjects/schemas.d.ts.map +0 -1
- package/dist/platformProjects/schemas.js.map +0 -1
- package/dist/platformProjects/types.d.ts.map +0 -1
- package/dist/platformProjects/types.js.map +0 -1
- package/dist/projects/index.d.ts.map +0 -1
- package/dist/projects/index.js.map +0 -1
- package/dist/projects/interface.d.ts.map +0 -1
- package/dist/projects/interface.js.map +0 -1
- package/dist/projects/schemas.d.ts.map +0 -1
- package/dist/projects/schemas.js.map +0 -1
- package/dist/projects/types.d.ts.map +0 -1
- package/dist/projects/types.js.map +0 -1
- package/dist/shareLinks/index.d.ts.map +0 -1
- package/dist/shareLinks/index.js.map +0 -1
- package/dist/shareLinks/interface.d.ts.map +0 -1
- package/dist/shareLinks/interface.js.map +0 -1
- package/dist/shareLinks/schemas.d.ts.map +0 -1
- package/dist/shareLinks/schemas.js.map +0 -1
- package/dist/shareLinks/types.d.ts.map +0 -1
- package/dist/shareLinks/types.js.map +0 -1
- package/dist/solveCache/interface.d.ts.map +0 -1
- package/dist/solveCache/interface.js.map +0 -1
- package/dist/storage/assetClasses.d.ts.map +0 -1
- package/dist/storage/assetClasses.js.map +0 -1
- package/dist/storage/cacheBust.d.ts.map +0 -1
- package/dist/storage/cacheBust.js.map +0 -1
- package/dist/storage/image.d.ts.map +0 -1
- package/dist/storage/image.js.map +0 -1
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/interface.d.ts.map +0 -1
- package/dist/storage/interface.js.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js.map +0 -1
- package/dist/testing/suites/apiTokenStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/apiTokenStoreSuite.js.map +0 -1
- package/dist/testing/suites/authProviderSuite.d.ts.map +0 -1
- package/dist/testing/suites/authProviderSuite.js.map +0 -1
- package/dist/testing/suites/computeServerStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/computeServerStoreSuite.js.map +0 -1
- package/dist/testing/suites/definitionStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/definitionStoreSuite.js.map +0 -1
- package/dist/testing/suites/emailLinkAuthSuite.d.ts.map +0 -1
- package/dist/testing/suites/emailLinkAuthSuite.js.map +0 -1
- package/dist/testing/suites/eventSinkSuite.d.ts.map +0 -1
- package/dist/testing/suites/eventSinkSuite.js.map +0 -1
- package/dist/testing/suites/helpers.d.ts.map +0 -1
- package/dist/testing/suites/helpers.js.map +0 -1
- package/dist/testing/suites/inviteStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/inviteStoreSuite.js.map +0 -1
- package/dist/testing/suites/orgStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/orgStoreSuite.js.map +0 -1
- package/dist/testing/suites/platformPermissionStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/platformPermissionStoreSuite.js.map +0 -1
- package/dist/testing/suites/platformProjectGrantStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/platformProjectGrantStoreSuite.js.map +0 -1
- package/dist/testing/suites/projectStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/projectStoreSuite.js.map +0 -1
- package/dist/testing/suites/shareLinkStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/shareLinkStoreSuite.js.map +0 -1
- package/dist/testing/suites/solveMetricSinkSuite.d.ts.map +0 -1
- package/dist/testing/suites/solveMetricSinkSuite.js.map +0 -1
- package/dist/testing/suites/storageProviderSuite.d.ts.map +0 -1
- package/dist/testing/suites/storageProviderSuite.js.map +0 -1
- package/dist/testing/suites/userProfileStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/userProfileStoreSuite.js.map +0 -1
- package/dist/userProfile/index.d.ts.map +0 -1
- package/dist/userProfile/index.js.map +0 -1
- package/dist/userProfile/interface.d.ts.map +0 -1
- package/dist/userProfile/interface.js.map +0 -1
- package/dist/userProfile/types.d.ts.map +0 -1
- package/dist/userProfile/types.js.map +0 -1
- package/dist/utils/audit.d.ts.map +0 -1
- package/dist/utils/audit.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @selvajs/platform
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Selva's contract with its backends: the interfaces, plus the logic every adapter shares (access rules, API-token scopes, at-rest secret encryption, Zod request schemas, slugs, the image transcoder) and vitest conformance suites. No dependency on a specific database, storage or auth service. Concrete adapters live in their own packages: `@selvajs/local-provider`, `@selvajs/supabase-provider`, `@selvajs/header-auth-provider`.
|
|
4
4
|
|
|
5
5
|
This README is the contract. Read it before writing an adapter.
|
|
6
6
|
|
|
@@ -20,6 +20,7 @@ This README is the contract. Read it before writing an adapter.
|
|
|
20
20
|
| `IShareLinkStore` | Per-definition anonymous-access tokens. | Yes — every method. |
|
|
21
21
|
| `IInviteStore` | Pending org-membership invitations. | Yes — every method. |
|
|
22
22
|
| `IComputeServerStore` | Global + per-org compute-server config. | No — not tenant-scoped. |
|
|
23
|
+
| `IApiTokenStore` | API tokens and their revocation. | Yes — every method. |
|
|
23
24
|
| `IStorageProvider` | Path-based blob storage. Authorization is the caller's responsibility. | No — callers pass already-authorized paths. |
|
|
24
25
|
|
|
25
26
|
`IDataProvider` composes every store, plus optional `auditQuery` / `events` hooks, an optional `verifySchemaVersion` handshake (for adapters whose schema migrates out-of-band), and the `ensureUser` / `onUserDeleted` lifecycle methods. An adapter typically implements one class per store and aggregates them.
|
|
@@ -49,12 +50,12 @@ Providers are two-phase: a metadata store (`IDataProvider`) and a blob store (`I
|
|
|
49
50
|
|
|
50
51
|
List queries filter `'pending'` and `'archived'` by default (`DefinitionListOptions.includePending` / `includeArchived` opt in). If step 2 fails, the record stays `'pending'` and is invisible to consumers.
|
|
51
52
|
|
|
52
|
-
**Delete —
|
|
53
|
+
**Delete — metadata-first:**
|
|
53
54
|
|
|
54
|
-
1. `
|
|
55
|
-
2. `
|
|
55
|
+
1. `data.delete(guid)`, which hides the record at once.
|
|
56
|
+
2. `storage.deletePrefix(prefix)`.
|
|
56
57
|
|
|
57
|
-
If step 2 fails,
|
|
58
|
+
If step 2 fails, the blobs are orphans no reader can reach. The reverse order would leave a live record with its blobs gone.
|
|
58
59
|
|
|
59
60
|
---
|
|
60
61
|
|
|
@@ -84,7 +85,7 @@ The Selva app normally picks bundled providers from `SELVA_AUTH_PROVIDER` / `SEL
|
|
|
84
85
|
|
|
85
86
|
## Testing your adapter
|
|
86
87
|
|
|
87
|
-
`@selvajs/platform/testing` ships a conformance suite per store. Adapters import it and run it against their own instance — passing means the adapter
|
|
88
|
+
`@selvajs/platform/testing` ships a conformance suite per store. Adapters import it and run it against their own instance — passing means the adapter honours the contract the bundled providers do.
|
|
88
89
|
|
|
89
90
|
```ts
|
|
90
91
|
import { runDefinitionStoreConformance } from '@selvajs/platform/testing';
|
|
@@ -96,7 +97,9 @@ runDefinitionStoreConformance({
|
|
|
96
97
|
});
|
|
97
98
|
```
|
|
98
99
|
|
|
99
|
-
A suite exists per store, plus auth, email-link auth, storage, event sink
|
|
100
|
+
A suite exists per store, plus auth, email-link auth, storage, event sink and solve-metric sink. `runAuthProviderConformance` wraps `runAuthCoreConformance` and `runPasswordAuthConformance`, so a provider without passwords can run the core alone or pass `passwordless`. New cases go behind an option, so an upgrade doesn't turn a third-party adapter red. The suites cover `ctx` scoping, `pending` filtering, `includePending` opt-in, `ProviderError` shapes, and the races a store exists to prevent, such as the share-link solve cap. They don't cover performance.
|
|
101
|
+
|
|
102
|
+
`sharp` is an optional peer dependency. An adapter that calls `transcodeImageIfNeeded` has to install it.
|
|
100
103
|
|
|
101
104
|
---
|
|
102
105
|
|
package/dist/access/index.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
export type { ProjectAccessInput, DefinitionAccessInput, VisibilityChangeInput, ReclaimAccessInput, CreateProjectAccessInput, OrgOwnerAuthorityInput, OwnerRemovalInput, OwnerRemovalCheck } from './rules.js';
|
|
2
2
|
export { canView, canSolve, canEdit, canCreateDefinition, canManage, canEditProjectSettings, canChangeVisibilityToPublic, canEditDefinition, canReclaim, canCreateProject, canChangeOrgRole, checkOwnerRemoval, withAdminBypass } from './rules.js';
|
|
3
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/access/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
1
|
export { canView, canSolve, canEdit, canCreateDefinition, canManage, canEditProjectSettings, canChangeVisibilityToPublic, canEditDefinition, canReclaim, canCreateProject, canChangeOrgRole, checkOwnerRemoval, withAdminBypass } from './rules.js';
|
|
2
|
-
//# sourceMappingURL=index.js.map
|
package/dist/access/rules.d.ts
CHANGED
|
@@ -52,8 +52,8 @@ export declare function canSolve(input: ProjectAccessInput): boolean;
|
|
|
52
52
|
export declare function canEdit(input: ProjectAccessInput): boolean;
|
|
53
53
|
/**
|
|
54
54
|
* Adding a new definition. Commons projects (`autoJoinOnUpload`) take uploads
|
|
55
|
-
* from anyone who
|
|
56
|
-
* everywhere else it needs {@link canEdit}.
|
|
55
|
+
* from anyone who can {@link canView} them, and the uploader owns what they
|
|
56
|
+
* upload; everywhere else it needs {@link canEdit}.
|
|
57
57
|
*/
|
|
58
58
|
export declare function canCreateDefinition(input: ProjectAccessInput): boolean;
|
|
59
59
|
/** Platform projects: `instance_admin` only. All other visibilities: project owner. */
|
|
@@ -131,18 +131,12 @@ export interface OrgOwnerAuthorityInput {
|
|
|
131
131
|
role: OrgRole;
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
|
-
* Whether the actor may hand out or take away org `owner`/`admin` standing
|
|
134
|
+
* Whether the actor may hand out or take away org `owner`/`admin` standing.
|
|
135
|
+
* Inviting someone as owner/admin, changing a member's role and removing an owner
|
|
136
|
+
* are one rule, so all three read it here.
|
|
135
137
|
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* out longhand. Two of them had drifted by the time the access audit found
|
|
139
|
-
* them: the invite route let an admin mint an `owner` invite for themselves,
|
|
140
|
-
* and DELETE let an admin remove an owner that PATCH would not let them demote.
|
|
141
|
-
* They are one rule, so they read it from one place.
|
|
142
|
-
*
|
|
143
|
-
* Reads the actor's **membership row**. `Organization.ownerId` is a separate
|
|
144
|
-
* field that can disagree with it — see the sole-`instance_admin` finding — and
|
|
145
|
-
* is not authority for this decision.
|
|
138
|
+
* Reads the actor's **membership row**. `Organization.ownerId` is a separate field
|
|
139
|
+
* that can disagree with it and is not authority for this decision.
|
|
146
140
|
*/
|
|
147
141
|
export declare function canChangeOrgRole(input: OrgOwnerAuthorityInput): boolean;
|
|
148
142
|
/**
|
|
@@ -163,4 +157,3 @@ export interface OwnerRemovalInput {
|
|
|
163
157
|
confirmed: boolean;
|
|
164
158
|
}
|
|
165
159
|
export declare function checkOwnerRemoval(input: OwnerRemovalInput): OwnerRemovalCheck;
|
|
166
|
-
//# sourceMappingURL=rules.d.ts.map
|
package/dist/access/rules.js
CHANGED
|
@@ -33,10 +33,12 @@ export function canView(input) {
|
|
|
33
33
|
return platformGrants.some((g) => (g.granteeType === 'user' && g.granteeId === userId) ||
|
|
34
34
|
(g.granteeType === 'org' && g.granteeId === actingOrgId));
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
// A project member sees the project at any visibility. Leaving the org ends the
|
|
37
|
+
// membership, so this only widens access for someone who reclaimed from outside.
|
|
38
|
+
if (member !== null)
|
|
39
|
+
return true;
|
|
38
40
|
if (project.visibility === 'org')
|
|
39
|
-
return orgMember !== null;
|
|
41
|
+
return orgMember !== null && actingOrgId === project.orgId;
|
|
40
42
|
if (project.visibility === 'public')
|
|
41
43
|
return allowCrossOrgPublic ? true : orgMember !== null;
|
|
42
44
|
return false;
|
|
@@ -69,7 +71,9 @@ export function canSolve(input) {
|
|
|
69
71
|
*/
|
|
70
72
|
export function canEdit(input) {
|
|
71
73
|
const { member, project, enablePlatformProjects, platformPermissions } = input;
|
|
72
|
-
if (project
|
|
74
|
+
if (!project)
|
|
75
|
+
return false;
|
|
76
|
+
if (project.visibility === 'platform') {
|
|
73
77
|
if (!enablePlatformProjects)
|
|
74
78
|
return false;
|
|
75
79
|
return isInstanceAdmin(platformPermissions);
|
|
@@ -78,15 +82,17 @@ export function canEdit(input) {
|
|
|
78
82
|
}
|
|
79
83
|
/**
|
|
80
84
|
* Adding a new definition. Commons projects (`autoJoinOnUpload`) take uploads
|
|
81
|
-
* from anyone who
|
|
82
|
-
* everywhere else it needs {@link canEdit}.
|
|
85
|
+
* from anyone who can {@link canView} them, and the uploader owns what they
|
|
86
|
+
* upload; everywhere else it needs {@link canEdit}.
|
|
83
87
|
*/
|
|
84
88
|
export function canCreateDefinition(input) {
|
|
85
|
-
return input.project?.autoJoinOnUpload === true || canEdit(input);
|
|
89
|
+
return (input.project?.autoJoinOnUpload === true && canView(input)) || canEdit(input);
|
|
86
90
|
}
|
|
87
91
|
/** Platform projects: `instance_admin` only. All other visibilities: project owner. */
|
|
88
92
|
export function canManage(input) {
|
|
89
|
-
if (input.project
|
|
93
|
+
if (!input.project)
|
|
94
|
+
return false;
|
|
95
|
+
if (input.project.visibility === 'platform') {
|
|
90
96
|
if (!input.enablePlatformProjects)
|
|
91
97
|
return false;
|
|
92
98
|
return isInstanceAdmin(input.platformPermissions);
|
|
@@ -95,12 +101,7 @@ export function canManage(input) {
|
|
|
95
101
|
}
|
|
96
102
|
/** Platform projects: `instance_admin` only. All other visibilities: project owner. */
|
|
97
103
|
export function canEditProjectSettings(input) {
|
|
98
|
-
|
|
99
|
-
if (!input.enablePlatformProjects)
|
|
100
|
-
return false;
|
|
101
|
-
return isInstanceAdmin(input.platformPermissions);
|
|
102
|
-
}
|
|
103
|
-
return input.member?.role === 'owner';
|
|
104
|
+
return canManage(input);
|
|
104
105
|
}
|
|
105
106
|
/**
|
|
106
107
|
* Authorization to flip a project to `public`. The `ALLOW_CROSS_ORG_PUBLIC`
|
|
@@ -121,6 +122,8 @@ export function canEditDefinition(input) {
|
|
|
121
122
|
const { project, definition, member, orgMember, userId, platformPermissions, enablePlatformProjects } = input;
|
|
122
123
|
if (!project || !definition)
|
|
123
124
|
return false;
|
|
125
|
+
if (definition.projectId !== project.id)
|
|
126
|
+
return false;
|
|
124
127
|
if (project.visibility === 'platform') {
|
|
125
128
|
if (!enablePlatformProjects)
|
|
126
129
|
return false;
|
|
@@ -174,18 +177,12 @@ export function canCreateProject(input) {
|
|
|
174
177
|
return false;
|
|
175
178
|
}
|
|
176
179
|
/**
|
|
177
|
-
* Whether the actor may hand out or take away org `owner`/`admin` standing
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* existing member's role, and removing an owner — and all three had written it
|
|
181
|
-
* out longhand. Two of them had drifted by the time the access audit found
|
|
182
|
-
* them: the invite route let an admin mint an `owner` invite for themselves,
|
|
183
|
-
* and DELETE let an admin remove an owner that PATCH would not let them demote.
|
|
184
|
-
* They are one rule, so they read it from one place.
|
|
180
|
+
* Whether the actor may hand out or take away org `owner`/`admin` standing.
|
|
181
|
+
* Inviting someone as owner/admin, changing a member's role and removing an owner
|
|
182
|
+
* are one rule, so all three read it here.
|
|
185
183
|
*
|
|
186
|
-
* Reads the actor's **membership row**. `Organization.ownerId` is a separate
|
|
187
|
-
*
|
|
188
|
-
* is not authority for this decision.
|
|
184
|
+
* Reads the actor's **membership row**. `Organization.ownerId` is a separate field
|
|
185
|
+
* that can disagree with it and is not authority for this decision.
|
|
189
186
|
*/
|
|
190
187
|
export function canChangeOrgRole(input) {
|
|
191
188
|
if (input.role === 'member')
|
|
@@ -202,4 +199,3 @@ export function checkOwnerRemoval(input) {
|
|
|
202
199
|
return 'needs_confirm';
|
|
203
200
|
return 'ok';
|
|
204
201
|
}
|
|
205
|
-
//# sourceMappingURL=rules.js.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export type { ApiScope, ApiScopeAction, ApiScopeResource, ApiToken, ApiTokenSummary, ApiTokenRevokeReason, ApiTokenLifetimeDays } from './types.js';
|
|
2
|
-
export { ApiScopeActionSchema, ApiScopeStringSchema,
|
|
2
|
+
export { ApiScopeActionSchema, ApiScopeStringSchema, API_TOKEN_LIFETIME_DAYS, parseApiScope, formatApiScope, describeApiScope } from './types.js';
|
|
3
3
|
export type { IApiTokenStore } from './interface.js';
|
|
4
4
|
export type { ScopeTarget } from './scope.js';
|
|
5
5
|
export { scopeAllows, apiScopeActionCovers, narrowApiTokenContext, outsideTokenOrg } from './scope.js';
|
|
6
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/apiTokens/index.js
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
export { ApiScopeActionSchema, ApiScopeStringSchema,
|
|
1
|
+
export { ApiScopeActionSchema, ApiScopeStringSchema, API_TOKEN_LIFETIME_DAYS, parseApiScope, formatApiScope, describeApiScope } from './types.js';
|
|
2
2
|
export { scopeAllows, apiScopeActionCovers, narrowApiTokenContext, outsideTokenOrg } from './scope.js';
|
|
3
|
-
//# sourceMappingURL=index.js.map
|
package/dist/apiTokens/scope.js
CHANGED
|
@@ -82,4 +82,3 @@ export declare const API_TOKEN_LIFETIME_DAYS: readonly [30, 90, 180];
|
|
|
82
82
|
export type ApiTokenLifetimeDays = (typeof API_TOKEN_LIFETIME_DAYS)[number];
|
|
83
83
|
/** "Read and change everything in the org". Names no ids, so it suits an email. */
|
|
84
84
|
export declare function describeApiScope(value: string): string;
|
|
85
|
-
//# sourceMappingURL=types.d.ts.map
|
package/dist/apiTokens/types.js
CHANGED
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
export type { AuthUser, UserManagementResult, LoginResult } from './types.js';
|
|
2
2
|
export type { IAuthProvider, IOAuthAuth, ISessionRefresh, IPasswordAuth, IEmailLinkAuth, IProxyAuth, IDelegatedSession, DelegatedSessionStatus } from './interface.js';
|
|
3
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/auth/index.js
CHANGED
package/dist/auth/interface.d.ts
CHANGED
|
@@ -30,9 +30,8 @@ export interface IPasswordAuth {
|
|
|
30
30
|
* `getOAuthAuthorizationUrl` and 303s to the result.
|
|
31
31
|
* 2. IdP redirects back to `/auth/{provider}/callback?code=...` → server
|
|
32
32
|
* calls `exchangeOAuthCode` to mint the session pair.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* without bouncing the user.
|
|
33
|
+
*
|
|
34
|
+
* Keeping the session alive afterwards is `ISessionRefresh`'s job.
|
|
36
35
|
*
|
|
37
36
|
* Each method returns `null` rather than throwing on irrecoverable
|
|
38
37
|
* credential failures, so the route layer can pick the right HTTP shape
|
|
@@ -64,23 +63,6 @@ export interface IOAuthAuth {
|
|
|
64
63
|
sessionToken: string;
|
|
65
64
|
refreshToken: string;
|
|
66
65
|
} | null>;
|
|
67
|
-
/**
|
|
68
|
-
* Swap a refresh token for a fresh access/refresh pair. Returns null on
|
|
69
|
-
* irrecoverable failure (revoked, expired, signed by a different secret);
|
|
70
|
-
* the session-refresh middleware treats null as "force re-login" by
|
|
71
|
-
* clearing the refresh cookie.
|
|
72
|
-
*
|
|
73
|
-
* @deprecated Session refresh is session lifecycle, not OAuth — a consumer
|
|
74
|
-
* that brokers no OAuth still needs it (refreshing a password session, or
|
|
75
|
-
* revoking on logout). Moved to `IAuthProvider.sessionRefresh`
|
|
76
|
-
* (`ISessionRefresh`). This member delegates for one release and will be
|
|
77
|
-
* removed in the next minor; migrate callers to
|
|
78
|
-
* `auth.sessionRefresh?.refreshSession(...)`.
|
|
79
|
-
*/
|
|
80
|
-
refreshSession(refreshToken: string): Promise<{
|
|
81
|
-
sessionToken: string;
|
|
82
|
-
refreshToken: string;
|
|
83
|
-
} | null>;
|
|
84
66
|
}
|
|
85
67
|
/**
|
|
86
68
|
* Optional session-lifecycle surface: refresh an expiring session, revoke one
|
|
@@ -245,8 +227,8 @@ export interface IDelegatedSession {
|
|
|
245
227
|
*/
|
|
246
228
|
status(): Promise<DelegatedSessionStatus>;
|
|
247
229
|
/**
|
|
248
|
-
* A session token acting as `userId` inside `orgId`, valid for
|
|
249
|
-
*
|
|
230
|
+
* A session token acting as `userId` inside `orgId`, valid for `ttlSeconds`
|
|
231
|
+
* (default 60 s). It carries no instance-admin rights, whatever the user holds: those
|
|
250
232
|
* never ride on a bearer key. Throws if `status` isn't ok.
|
|
251
233
|
*/
|
|
252
234
|
mint(userId: string, opts: {
|
|
@@ -327,4 +309,3 @@ export interface IAuthProvider {
|
|
|
327
309
|
*/
|
|
328
310
|
touchLastLogin?(id: string): Promise<void>;
|
|
329
311
|
}
|
|
330
|
-
//# sourceMappingURL=interface.d.ts.map
|
package/dist/auth/interface.js
CHANGED
package/dist/auth/types.d.ts
CHANGED
|
@@ -21,7 +21,10 @@ export interface AuthUser {
|
|
|
21
21
|
* - `last_admin` — refused; would leave zero `instance_admin` users
|
|
22
22
|
*/
|
|
23
23
|
export type UserManagementResult = 'ok' | 'not_found' | 'not_supported' | 'last_admin';
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* `success` includes the session token the provider minted. `rate_limited` is for
|
|
26
|
+
* providers that throttle logins; no bundled provider returns it.
|
|
27
|
+
*/
|
|
25
28
|
export type LoginResult = {
|
|
26
29
|
kind: 'success';
|
|
27
30
|
user: AuthUser;
|
|
@@ -30,4 +33,3 @@ export type LoginResult = {
|
|
|
30
33
|
kind: 'failed';
|
|
31
34
|
reason?: 'invalid_credentials' | 'disabled' | 'rate_limited';
|
|
32
35
|
};
|
|
33
|
-
//# sourceMappingURL=types.d.ts.map
|
package/dist/auth/types.js
CHANGED
package/dist/bindings/index.d.ts
CHANGED
package/dist/bindings/index.js
CHANGED
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export { isPlatformServer, isOrgServer } from './types.js';
|
|
3
|
-
export type { IComputeServerStore, GetConfigOptions } from './interface.js';
|
|
1
|
+
export * from './public.js';
|
|
4
2
|
export { isEncryptedSecret, encryptSecret, decryptSecret, decodeSecretKey } from './secrets.js';
|
|
5
|
-
export type { SecretVerificationReport, SecretVerificationFailure, SecretVerificationFailureReason } from './secrets.js';
|
|
6
|
-
export type { ResolveOptions } from './utils.js';
|
|
7
|
-
export { serversVisibleTo, defaultServerIdFor, resolveServerForOrg, findServerById, platformServers, orgServersFor, scopeConfigToOrg } from './utils.js';
|
|
8
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
//
|
|
3
|
-
// re-exported from the root barrel, which client `.svelte` code imports and
|
|
4
|
-
// where a browser bundle can't resolve `node:crypto`. Import these from
|
|
5
|
-
// `@selvajs/platform/computeServer` in server code. The report TYPES are erased
|
|
6
|
-
// at build, so they also stay re-exported from the root barrel for convenience.
|
|
1
|
+
export * from './public.js';
|
|
2
|
+
// Server-only: import these from `@selvajs/platform/computeServer`.
|
|
7
3
|
export { isEncryptedSecret, encryptSecret, decryptSecret, decodeSecretKey } from './secrets.js';
|
|
8
|
-
export { serversVisibleTo, defaultServerIdFor, resolveServerForOrg, findServerById, platformServers, orgServersFor, scopeConfigToOrg } from './utils.js';
|
|
9
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -28,10 +28,9 @@ export interface GetConfigOptions {
|
|
|
28
28
|
*
|
|
29
29
|
* Org-facing surfaces must pass this. Without it the store hands back every
|
|
30
30
|
* platform server and every *other* org's private servers, leaving each
|
|
31
|
-
* caller to re-apply a filter it can silently forget
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* deliberately; they act instance-wide.
|
|
31
|
+
* caller to re-apply a filter it can silently forget. Admin surfaces, boot
|
|
32
|
+
* health and the save-diff handlers omit it deliberately; they act
|
|
33
|
+
* instance-wide.
|
|
35
34
|
*/
|
|
36
35
|
scopeToOrgId?: string;
|
|
37
36
|
}
|
|
@@ -53,6 +52,10 @@ export interface IComputeServerStore {
|
|
|
53
52
|
* authenticated under the current `SELVA_AT_REST_KEY` (the solve fails
|
|
54
53
|
* later at Rhino.Compute rather than here).
|
|
55
54
|
*
|
|
55
|
+
* A plaintext key at rest is adapter-defined: local throws, because it never
|
|
56
|
+
* writes one, and Supabase warns and returns it, because rows can predate
|
|
57
|
+
* encryption. Both report it through `verifySecrets`.
|
|
58
|
+
*
|
|
56
59
|
* The narrow path onto the solve hot path: resolve a server from the
|
|
57
60
|
* key-free config, then fetch that one key.
|
|
58
61
|
*/
|
|
@@ -61,7 +64,8 @@ export interface IComputeServerStore {
|
|
|
61
64
|
savePlatformServers(ctx: RequestContext, servers: ComputeServerConfig[], defaultServerId: string | undefined): Promise<void>;
|
|
62
65
|
/**
|
|
63
66
|
* Replaces the org-private server set for `orgId` and (optionally) updates
|
|
64
|
-
* `orgDefaults[orgId]`. Platform rows are left untouched
|
|
67
|
+
* `orgDefaults[orgId]`. Platform rows are left untouched, and any non-org entry
|
|
68
|
+
* in `servers` is ignored.
|
|
65
69
|
*
|
|
66
70
|
* `defaultServerId: null` clears the org's override; `undefined` leaves
|
|
67
71
|
* the existing value alone.
|
|
@@ -90,4 +94,3 @@ export interface IComputeServerStore {
|
|
|
90
94
|
*/
|
|
91
95
|
verifySecrets?(): Promise<SecretVerificationReport>;
|
|
92
96
|
}
|
|
93
|
-
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { ComputeServerConfig, ComputeConfig, PlatformComputeServer, OrgComputeServer } from './types.js';
|
|
2
|
+
export { COMPUTE_RETRY_MAX, COMPUTE_TIMEOUT_DEFAULT_MS, COMPUTE_TIMEOUT_MAX_MS, COMPUTE_TIMEOUT_MIN_MS, isPlatformServer, isOrgServer } from './types.js';
|
|
3
|
+
export type { IComputeServerStore, GetConfigOptions } from './interface.js';
|
|
4
|
+
export type { ResolveOptions } from './utils.js';
|
|
5
|
+
export { serversVisibleTo, defaultServerIdFor, clampRetryCount, clampTimeoutMs, pickServer, resolveServerForOrg, findServerById, platformServers, scopeConfigToOrg } from './utils.js';
|
|
6
|
+
export type { SecretVerificationReport, SecretVerificationFailure, SecretVerificationFailureReason } from './secrets.js';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { COMPUTE_RETRY_MAX, COMPUTE_TIMEOUT_DEFAULT_MS, COMPUTE_TIMEOUT_MAX_MS, COMPUTE_TIMEOUT_MIN_MS, isPlatformServer, isOrgServer } from './types.js';
|
|
2
|
+
export { serversVisibleTo, defaultServerIdFor, clampRetryCount, clampTimeoutMs, pickServer, resolveServerForOrg, findServerById, platformServers, scopeConfigToOrg } from './utils.js';
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
export declare function isEncryptedSecret(value: string): boolean;
|
|
1
2
|
/**
|
|
2
3
|
* AES-256-GCM envelope for at-rest secrets (e.g. compute API keys).
|
|
3
4
|
*
|
|
4
|
-
* Wire format: `enc:
|
|
5
|
-
*
|
|
5
|
+
* Wire format: `enc:v2:<base64(iv|tag|ciphertext)>`. `v2` authenticates the
|
|
6
|
+
* record the secret belongs to as AAD, so ciphertext copied onto another record
|
|
7
|
+
* fails to decrypt instead of handing the plaintext to that record's URL.
|
|
8
|
+
* `enc:v1:` envelopes have no AAD and still read: a store rewrites them as `v2`
|
|
9
|
+
* on its next save.
|
|
6
10
|
*
|
|
7
11
|
* Defends against backup leaks, accidental file/DB sharing, and read-only
|
|
8
12
|
* storage access — not against an attacker who also has the master key
|
|
@@ -11,10 +15,13 @@
|
|
|
11
15
|
*
|
|
12
16
|
* Pure `node:crypto`, no fs or DB coupling, so the local and Supabase
|
|
13
17
|
* compute-server stores share this one implementation.
|
|
18
|
+
*
|
|
19
|
+
* Pass `aad` to bind the secret to its record. {@link decryptSecret} then needs the
|
|
20
|
+
* same value.
|
|
14
21
|
*/
|
|
15
|
-
export declare function
|
|
16
|
-
|
|
17
|
-
export declare function decryptSecret(envelope: string, key: Buffer): string;
|
|
22
|
+
export declare function encryptSecret(plaintext: string, key: Buffer, aad?: string): string;
|
|
23
|
+
/** `aad` is ignored for an `enc:v1:` envelope and required for an `enc:v2:` one. */
|
|
24
|
+
export declare function decryptSecret(envelope: string, key: Buffer, aad?: string): string;
|
|
18
25
|
/**
|
|
19
26
|
* Decodes a `SELVA_AT_REST_KEY` env var into a 32-byte buffer (64-char hex or
|
|
20
27
|
* base64, with or without padding). Throws on anything else so a
|
|
@@ -46,4 +53,3 @@ export interface SecretVerificationReport {
|
|
|
46
53
|
/** True if at least one row holds an unencrypted apiKey at rest. */
|
|
47
54
|
plaintextFound: boolean;
|
|
48
55
|
}
|
|
49
|
-
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -2,12 +2,19 @@ import { randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';
|
|
|
2
2
|
const ALGO = 'aes-256-gcm';
|
|
3
3
|
const IV_BYTES = 12;
|
|
4
4
|
const TAG_BYTES = 16;
|
|
5
|
-
const
|
|
5
|
+
const PREFIX_V1 = 'enc:v1:';
|
|
6
|
+
const PREFIX_V2 = 'enc:v2:';
|
|
7
|
+
export function isEncryptedSecret(value) {
|
|
8
|
+
return value.startsWith(PREFIX_V1) || value.startsWith(PREFIX_V2);
|
|
9
|
+
}
|
|
6
10
|
/**
|
|
7
11
|
* AES-256-GCM envelope for at-rest secrets (e.g. compute API keys).
|
|
8
12
|
*
|
|
9
|
-
* Wire format: `enc:
|
|
10
|
-
*
|
|
13
|
+
* Wire format: `enc:v2:<base64(iv|tag|ciphertext)>`. `v2` authenticates the
|
|
14
|
+
* record the secret belongs to as AAD, so ciphertext copied onto another record
|
|
15
|
+
* fails to decrypt instead of handing the plaintext to that record's URL.
|
|
16
|
+
* `enc:v1:` envelopes have no AAD and still read: a store rewrites them as `v2`
|
|
17
|
+
* on its next save.
|
|
11
18
|
*
|
|
12
19
|
* Defends against backup leaks, accidental file/DB sharing, and read-only
|
|
13
20
|
* storage access — not against an attacker who also has the master key
|
|
@@ -16,11 +23,11 @@ const PREFIX = 'enc:v1:';
|
|
|
16
23
|
*
|
|
17
24
|
* Pure `node:crypto`, no fs or DB coupling, so the local and Supabase
|
|
18
25
|
* compute-server stores share this one implementation.
|
|
26
|
+
*
|
|
27
|
+
* Pass `aad` to bind the secret to its record. {@link decryptSecret} then needs the
|
|
28
|
+
* same value.
|
|
19
29
|
*/
|
|
20
|
-
export function
|
|
21
|
-
return value.startsWith(PREFIX);
|
|
22
|
-
}
|
|
23
|
-
export function encryptSecret(plaintext, key) {
|
|
30
|
+
export function encryptSecret(plaintext, key, aad) {
|
|
24
31
|
if (key.length !== 32) {
|
|
25
32
|
throw new Error(`Secret key must be 32 bytes; got ${key.length}`);
|
|
26
33
|
}
|
|
@@ -29,23 +36,33 @@ export function encryptSecret(plaintext, key) {
|
|
|
29
36
|
}
|
|
30
37
|
const iv = randomBytes(IV_BYTES);
|
|
31
38
|
const cipher = createCipheriv(ALGO, key, iv);
|
|
39
|
+
if (aad !== undefined)
|
|
40
|
+
cipher.setAAD(Buffer.from(aad, 'utf8'));
|
|
32
41
|
const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
33
42
|
const tag = cipher.getAuthTag();
|
|
34
|
-
|
|
43
|
+
const prefix = aad === undefined ? PREFIX_V1 : PREFIX_V2;
|
|
44
|
+
return prefix + Buffer.concat([iv, tag, ciphertext]).toString('base64');
|
|
35
45
|
}
|
|
36
|
-
|
|
46
|
+
/** `aad` is ignored for an `enc:v1:` envelope and required for an `enc:v2:` one. */
|
|
47
|
+
export function decryptSecret(envelope, key, aad) {
|
|
37
48
|
if (key.length !== 32) {
|
|
38
49
|
throw new Error(`Secret key must be 32 bytes; got ${key.length}`);
|
|
39
50
|
}
|
|
40
51
|
if (!isEncryptedSecret(envelope)) {
|
|
41
52
|
throw new Error('Value is not an encrypted secret envelope');
|
|
42
53
|
}
|
|
43
|
-
const
|
|
54
|
+
const bound = envelope.startsWith(PREFIX_V2);
|
|
55
|
+
if (bound && aad === undefined) {
|
|
56
|
+
throw new Error('This secret is bound to a record: pass its id');
|
|
57
|
+
}
|
|
58
|
+
const buf = Buffer.from(envelope.slice(PREFIX_V2.length), 'base64');
|
|
44
59
|
const iv = buf.subarray(0, IV_BYTES);
|
|
45
60
|
const tag = buf.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);
|
|
46
61
|
const ciphertext = buf.subarray(IV_BYTES + TAG_BYTES);
|
|
47
62
|
const decipher = createDecipheriv(ALGO, key, iv);
|
|
48
63
|
decipher.setAuthTag(tag);
|
|
64
|
+
if (bound)
|
|
65
|
+
decipher.setAAD(Buffer.from(aad, 'utf8'));
|
|
49
66
|
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
|
|
50
67
|
}
|
|
51
68
|
/**
|
|
@@ -55,12 +72,15 @@ export function decryptSecret(envelope, key) {
|
|
|
55
72
|
* ciphertext nothing can decrypt.
|
|
56
73
|
*/
|
|
57
74
|
export function decodeSecretKey(raw) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
const key = raw.trim();
|
|
76
|
+
if (/^[0-9a-fA-F]{64}$/.test(key))
|
|
77
|
+
return Buffer.from(key, 'hex');
|
|
78
|
+
// `Buffer.from` skips what it can't decode, so only a round trip proves the
|
|
79
|
+
// whole string was base64 rather than 32 bytes of it.
|
|
80
|
+
const buf = Buffer.from(key, 'base64');
|
|
81
|
+
if (buf.length === 32 && buf.toString('base64').replace(/=+$/, '') === key.replace(/=+$/, '')) {
|
|
62
82
|
return buf;
|
|
83
|
+
}
|
|
63
84
|
throw new Error('SELVA_AT_REST_KEY must be 32 bytes encoded as 64-char hex or base64. ' +
|
|
64
85
|
"Generate one with: node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"");
|
|
65
86
|
}
|
|
66
|
-
//# sourceMappingURL=secrets.js.map
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export declare const COMPUTE_TIMEOUT_DEFAULT_MS = 30000;
|
|
2
|
+
export declare const COMPUTE_TIMEOUT_MIN_MS = 1000;
|
|
3
|
+
export declare const COMPUTE_TIMEOUT_MAX_MS = 300000;
|
|
4
|
+
export declare const COMPUTE_RETRY_MAX = 5;
|
|
1
5
|
/**
|
|
2
6
|
* Compute servers belong to one of two scopes — never both.
|
|
3
7
|
*
|
|
@@ -29,7 +33,7 @@ interface ComputeServerCommon {
|
|
|
29
33
|
* they'd only throw away.
|
|
30
34
|
*/
|
|
31
35
|
hasApiKey?: boolean;
|
|
32
|
-
/** Default:
|
|
36
|
+
/** Default: {@link COMPUTE_TIMEOUT_DEFAULT_MS}. */
|
|
33
37
|
timeoutMs?: number;
|
|
34
38
|
/** Default: 0. */
|
|
35
39
|
retryCount?: number;
|
|
@@ -68,4 +72,3 @@ export interface ComputeConfig {
|
|
|
68
72
|
export declare function isPlatformServer(s: ComputeServerConfig): s is PlatformComputeServer;
|
|
69
73
|
export declare function isOrgServer(s: ComputeServerConfig): s is OrgComputeServer;
|
|
70
74
|
export {};
|
|
71
|
-
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export const COMPUTE_TIMEOUT_DEFAULT_MS = 30000;
|
|
2
|
+
export const COMPUTE_TIMEOUT_MIN_MS = 1000;
|
|
3
|
+
export const COMPUTE_TIMEOUT_MAX_MS = 300000;
|
|
4
|
+
export const COMPUTE_RETRY_MAX = 5;
|
|
1
5
|
// ============================================================================
|
|
2
6
|
// Type guards
|
|
3
7
|
// ============================================================================
|
|
@@ -7,4 +11,3 @@ export function isPlatformServer(s) {
|
|
|
7
11
|
export function isOrgServer(s) {
|
|
8
12
|
return s.scope === 'org';
|
|
9
13
|
}
|
|
10
|
-
//# sourceMappingURL=types.js.map
|