was-teaching-server 0.9.1
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/CHANGELOG.md +1432 -0
- package/LICENSE +661 -0
- package/README.md +305 -0
- package/common/css/materialize.1.0.0.min.css +13 -0
- package/dist/auth-header-hooks.d.ts +84 -0
- package/dist/auth-header-hooks.d.ts.map +1 -0
- package/dist/auth-header-hooks.js +136 -0
- package/dist/auth-header-hooks.js.map +1 -0
- package/dist/authorize.d.ts +45 -0
- package/dist/authorize.d.ts.map +1 -0
- package/dist/authorize.js +76 -0
- package/dist/authorize.js.map +1 -0
- package/dist/backends/filesystem.d.ts +1179 -0
- package/dist/backends/filesystem.d.ts.map +1 -0
- package/dist/backends/filesystem.js +2727 -0
- package/dist/backends/filesystem.js.map +1 -0
- package/dist/backends/postgres.d.ts +833 -0
- package/dist/backends/postgres.d.ts.map +1 -0
- package/dist/backends/postgres.js +2164 -0
- package/dist/backends/postgres.js.map +1 -0
- package/dist/backends/postgresSchema.d.ts +36 -0
- package/dist/backends/postgresSchema.d.ts.map +1 -0
- package/dist/backends/postgresSchema.js +203 -0
- package/dist/backends/postgresSchema.js.map +1 -0
- package/dist/config.default.d.ts +360 -0
- package/dist/config.default.d.ts.map +1 -0
- package/dist/config.default.js +493 -0
- package/dist/config.default.js.map +1 -0
- package/dist/corsProxy.d.ts +45 -0
- package/dist/corsProxy.d.ts.map +1 -0
- package/dist/corsProxy.js +278 -0
- package/dist/corsProxy.js.map +1 -0
- package/dist/digest.d.ts +45 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +174 -0
- package/dist/digest.js.map +1 -0
- package/dist/errors.d.ts +654 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +960 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/atomicFile.d.ts +61 -0
- package/dist/lib/atomicFile.d.ts.map +1 -0
- package/dist/lib/atomicFile.js +150 -0
- package/dist/lib/atomicFile.js.map +1 -0
- package/dist/lib/backendRegistry.d.ts +59 -0
- package/dist/lib/backendRegistry.d.ts.map +1 -0
- package/dist/lib/backendRegistry.js +92 -0
- package/dist/lib/backendRegistry.js.map +1 -0
- package/dist/lib/backendUsage.d.ts +28 -0
- package/dist/lib/backendUsage.d.ts.map +1 -0
- package/dist/lib/backendUsage.js +55 -0
- package/dist/lib/backendUsage.js.map +1 -0
- package/dist/lib/backends.d.ts +142 -0
- package/dist/lib/backends.d.ts.map +1 -0
- package/dist/lib/backends.js +272 -0
- package/dist/lib/backends.js.map +1 -0
- package/dist/lib/blindedIndex.d.ts +156 -0
- package/dist/lib/blindedIndex.d.ts.map +1 -0
- package/dist/lib/blindedIndex.js +338 -0
- package/dist/lib/blindedIndex.js.map +1 -0
- package/dist/lib/collectionDescription.d.ts +33 -0
- package/dist/lib/collectionDescription.d.ts.map +1 -0
- package/dist/lib/collectionDescription.js +21 -0
- package/dist/lib/collectionDescription.js.map +1 -0
- package/dist/lib/cursor.d.ts +18 -0
- package/dist/lib/cursor.d.ts.map +1 -0
- package/dist/lib/cursor.js +50 -0
- package/dist/lib/cursor.js.map +1 -0
- package/dist/lib/edvEnvelope.d.ts +50 -0
- package/dist/lib/edvEnvelope.d.ts.map +1 -0
- package/dist/lib/edvEnvelope.js +106 -0
- package/dist/lib/edvEnvelope.js.map +1 -0
- package/dist/lib/encryption.d.ts +192 -0
- package/dist/lib/encryption.d.ts.map +1 -0
- package/dist/lib/encryption.js +383 -0
- package/dist/lib/encryption.js.map +1 -0
- package/dist/lib/etag.d.ts +33 -0
- package/dist/lib/etag.d.ts.map +1 -0
- package/dist/lib/etag.js +36 -0
- package/dist/lib/etag.js.map +1 -0
- package/dist/lib/exportManifest.d.ts +29 -0
- package/dist/lib/exportManifest.d.ts.map +1 -0
- package/dist/lib/exportManifest.js +77 -0
- package/dist/lib/exportManifest.js.map +1 -0
- package/dist/lib/importTar.d.ts +81 -0
- package/dist/lib/importTar.d.ts.map +1 -0
- package/dist/lib/importTar.js +301 -0
- package/dist/lib/importTar.js.map +1 -0
- package/dist/lib/isJson.d.ts +16 -0
- package/dist/lib/isJson.d.ts.map +1 -0
- package/dist/lib/isJson.js +17 -0
- package/dist/lib/isJson.js.map +1 -0
- package/dist/lib/keyEpoch.d.ts +38 -0
- package/dist/lib/keyEpoch.d.ts.map +1 -0
- package/dist/lib/keyEpoch.js +75 -0
- package/dist/lib/keyEpoch.js.map +1 -0
- package/dist/lib/keyedMutex.d.ts +28 -0
- package/dist/lib/keyedMutex.d.ts.map +1 -0
- package/dist/lib/keyedMutex.js +45 -0
- package/dist/lib/keyedMutex.js.map +1 -0
- package/dist/lib/kmsModule.d.ts +62 -0
- package/dist/lib/kmsModule.d.ts.map +1 -0
- package/dist/lib/kmsModule.js +327 -0
- package/dist/lib/kmsModule.js.map +1 -0
- package/dist/lib/kmsRecordCipher.d.ts +82 -0
- package/dist/lib/kmsRecordCipher.d.ts.map +1 -0
- package/dist/lib/kmsRecordCipher.js +226 -0
- package/dist/lib/kmsRecordCipher.js.map +1 -0
- package/dist/lib/pagination.d.ts +19 -0
- package/dist/lib/pagination.d.ts.map +1 -0
- package/dist/lib/pagination.js +21 -0
- package/dist/lib/pagination.js.map +1 -0
- package/dist/lib/paths.d.ts +273 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +270 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/preconditions.d.ts +65 -0
- package/dist/lib/preconditions.d.ts.map +1 -0
- package/dist/lib/preconditions.js +112 -0
- package/dist/lib/preconditions.js.map +1 -0
- package/dist/lib/resourceFileName.d.ts +41 -0
- package/dist/lib/resourceFileName.d.ts.map +1 -0
- package/dist/lib/resourceFileName.js +58 -0
- package/dist/lib/resourceFileName.js.map +1 -0
- package/dist/lib/revocations.d.ts +49 -0
- package/dist/lib/revocations.d.ts.map +1 -0
- package/dist/lib/revocations.js +86 -0
- package/dist/lib/revocations.js.map +1 -0
- package/dist/lib/validateDid.d.ts +19 -0
- package/dist/lib/validateDid.d.ts.map +1 -0
- package/dist/lib/validateDid.js +35 -0
- package/dist/lib/validateDid.js.map +1 -0
- package/dist/lib/validateId.d.ts +66 -0
- package/dist/lib/validateId.d.ts.map +1 -0
- package/dist/lib/validateId.js +126 -0
- package/dist/lib/validateId.js.map +1 -0
- package/dist/plugin.d.ts +115 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +134 -0
- package/dist/plugin.js.map +1 -0
- package/dist/policy.d.ts +78 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +115 -0
- package/dist/policy.js.map +1 -0
- package/dist/provisioning.d.ts +38 -0
- package/dist/provisioning.d.ts.map +1 -0
- package/dist/provisioning.js +72 -0
- package/dist/provisioning.js.map +1 -0
- package/dist/requests/BackendRequest.d.ts +61 -0
- package/dist/requests/BackendRequest.d.ts.map +1 -0
- package/dist/requests/BackendRequest.js +152 -0
- package/dist/requests/BackendRequest.js.map +1 -0
- package/dist/requests/CollectionRequest.d.ts +234 -0
- package/dist/requests/CollectionRequest.d.ts.map +1 -0
- package/dist/requests/CollectionRequest.js +705 -0
- package/dist/requests/CollectionRequest.js.map +1 -0
- package/dist/requests/KeyRequest.d.ts +115 -0
- package/dist/requests/KeyRequest.d.ts.map +1 -0
- package/dist/requests/KeyRequest.js +468 -0
- package/dist/requests/KeyRequest.js.map +1 -0
- package/dist/requests/KeystoreRequest.d.ts +99 -0
- package/dist/requests/KeystoreRequest.d.ts.map +1 -0
- package/dist/requests/KeystoreRequest.js +247 -0
- package/dist/requests/KeystoreRequest.js.map +1 -0
- package/dist/requests/PolicyRequest.d.ts +53 -0
- package/dist/requests/PolicyRequest.d.ts.map +1 -0
- package/dist/requests/PolicyRequest.js +124 -0
- package/dist/requests/PolicyRequest.js.map +1 -0
- package/dist/requests/ResourceRequest.d.ts +134 -0
- package/dist/requests/ResourceRequest.d.ts.map +1 -0
- package/dist/requests/ResourceRequest.js +548 -0
- package/dist/requests/ResourceRequest.js.map +1 -0
- package/dist/requests/RevocationRequest.d.ts +67 -0
- package/dist/requests/RevocationRequest.d.ts.map +1 -0
- package/dist/requests/RevocationRequest.js +187 -0
- package/dist/requests/RevocationRequest.js.map +1 -0
- package/dist/requests/SpaceRequest.d.ts +204 -0
- package/dist/requests/SpaceRequest.d.ts.map +1 -0
- package/dist/requests/SpaceRequest.js +520 -0
- package/dist/requests/SpaceRequest.js.map +1 -0
- package/dist/requests/SpacesRepositoryRequest.d.ts +50 -0
- package/dist/requests/SpacesRepositoryRequest.d.ts.map +1 -0
- package/dist/requests/SpacesRepositoryRequest.js +159 -0
- package/dist/requests/SpacesRepositoryRequest.js.map +1 -0
- package/dist/requests/collectionContext.d.ts +21 -0
- package/dist/requests/collectionContext.d.ts.map +1 -0
- package/dist/requests/collectionContext.js +30 -0
- package/dist/requests/collectionContext.js.map +1 -0
- package/dist/requests/controllerConsent.d.ts +58 -0
- package/dist/requests/controllerConsent.d.ts.map +1 -0
- package/dist/requests/controllerConsent.js +73 -0
- package/dist/requests/controllerConsent.js.map +1 -0
- package/dist/requests/keystoreContext.d.ts +68 -0
- package/dist/requests/keystoreContext.d.ts.map +1 -0
- package/dist/requests/keystoreContext.js +87 -0
- package/dist/requests/keystoreContext.js.map +1 -0
- package/dist/requests/resourceInput.d.ts +19 -0
- package/dist/requests/resourceInput.d.ts.map +1 -0
- package/dist/requests/resourceInput.js +102 -0
- package/dist/requests/resourceInput.js.map +1 -0
- package/dist/requests/spaceContext.d.ts +127 -0
- package/dist/requests/spaceContext.d.ts.map +1 -0
- package/dist/requests/spaceContext.js +201 -0
- package/dist/requests/spaceContext.js.map +1 -0
- package/dist/routes.d.ts +55 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +306 -0
- package/dist/routes.js.map +1 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +60 -0
- package/dist/server.js.map +1 -0
- package/dist/start.d.ts +7 -0
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +61 -0
- package/dist/start.js.map +1 -0
- package/dist/storage.d.ts +29 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +47 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +915 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/views/home.hbs +9 -0
- package/dist/views/templates/main.hbs +12 -0
- package/dist/zcap.d.ts +267 -0
- package/dist/zcap.d.ts.map +1 -0
- package/dist/zcap.js +478 -0
- package/dist/zcap.js.map +1 -0
- package/package.json +109 -0
- package/src/auth-header-hooks.ts +163 -0
- package/src/authorize.ts +117 -0
- package/src/backends/filesystem.ts +3626 -0
- package/src/backends/postgres.ts +3073 -0
- package/src/backends/postgresSchema.ts +231 -0
- package/src/config.default.ts +657 -0
- package/src/corsProxy.ts +326 -0
- package/src/declarations.d.ts +28 -0
- package/src/digest.ts +223 -0
- package/src/errors.ts +1122 -0
- package/src/index.ts +21 -0
- package/src/lib/atomicFile.ts +176 -0
- package/src/lib/backendRegistry.ts +139 -0
- package/src/lib/backendUsage.ts +79 -0
- package/src/lib/backends.ts +351 -0
- package/src/lib/blindedIndex.ts +466 -0
- package/src/lib/collectionDescription.ts +43 -0
- package/src/lib/cursor.ts +52 -0
- package/src/lib/edvEnvelope.ts +120 -0
- package/src/lib/encryption.ts +472 -0
- package/src/lib/etag.ts +40 -0
- package/src/lib/exportManifest.ts +115 -0
- package/src/lib/importTar.ts +381 -0
- package/src/lib/isJson.ts +18 -0
- package/src/lib/keyEpoch.ts +89 -0
- package/src/lib/keyedMutex.ts +45 -0
- package/src/lib/kmsModule.ts +451 -0
- package/src/lib/kmsRecordCipher.ts +292 -0
- package/src/lib/pagination.ts +22 -0
- package/src/lib/paths.ts +382 -0
- package/src/lib/preconditions.ts +145 -0
- package/src/lib/resourceFileName.ts +69 -0
- package/src/lib/revocations.ts +116 -0
- package/src/lib/validateDid.ts +41 -0
- package/src/lib/validateId.ts +148 -0
- package/src/plugin.ts +288 -0
- package/src/policy.ts +176 -0
- package/src/provisioning.ts +98 -0
- package/src/requests/BackendRequest.ts +219 -0
- package/src/requests/CollectionRequest.ts +899 -0
- package/src/requests/KeyRequest.ts +617 -0
- package/src/requests/KeystoreRequest.ts +325 -0
- package/src/requests/PolicyRequest.ts +169 -0
- package/src/requests/ResourceRequest.ts +649 -0
- package/src/requests/RevocationRequest.ts +267 -0
- package/src/requests/SpaceRequest.ts +686 -0
- package/src/requests/SpacesRepositoryRequest.ts +194 -0
- package/src/requests/collectionContext.ts +41 -0
- package/src/requests/controllerConsent.ts +108 -0
- package/src/requests/keystoreContext.ts +124 -0
- package/src/requests/resourceInput.ts +118 -0
- package/src/requests/spaceContext.ts +311 -0
- package/src/routes.ts +420 -0
- package/src/server.ts +69 -0
- package/src/start.ts +71 -0
- package/src/storage.ts +60 -0
- package/src/types.ts +1016 -0
- package/src/views/home.hbs +9 -0
- package/src/views/templates/main.hbs +12 -0
- package/src/zcap.ts +657 -0
package/dist/policy.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Access-control policy evaluation: resolves the effective policy for a target
|
|
3
|
+
* (most-specific level wins) and decides whether a policy grants an action.
|
|
4
|
+
* Policies are the fallback authorization path -- consulted when a capability
|
|
5
|
+
* invocation is absent or does not grant access (see authorize.ts). v1
|
|
6
|
+
* recognizes only `{ "type": "PublicCanRead" }`; any other `type` grants nothing
|
|
7
|
+
* (fail-closed). Also builds the RFC9264 linkset that advertises a Space's or
|
|
8
|
+
* Collection's policy resource for discovery.
|
|
9
|
+
*/
|
|
10
|
+
import type { FastifyBaseLogger } from 'fastify';
|
|
11
|
+
import type { PolicyDocument, StorageBackend } from './types.js';
|
|
12
|
+
/** The kind of access a request needs; derived from the HTTP method. */
|
|
13
|
+
export type AccessAction = 'read' | 'write';
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the policy that governs a target, honoring the spec's
|
|
16
|
+
* most-specific-wins inheritance: a Resource policy overrides a Collection
|
|
17
|
+
* policy, which overrides a Space policy. The first level that has a policy
|
|
18
|
+
* document is the effective policy; if none do, resolves undefined.
|
|
19
|
+
*
|
|
20
|
+
* @param options {object}
|
|
21
|
+
* @param options.storage {StorageBackend} the request's storage backend
|
|
22
|
+
* @param options.spaceId {string}
|
|
23
|
+
* @param [options.collectionId] {string}
|
|
24
|
+
* @param [options.resourceId] {string}
|
|
25
|
+
* @returns {Promise<PolicyDocument | undefined>}
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveEffectivePolicy({ storage, spaceId, collectionId, resourceId }: {
|
|
28
|
+
storage: StorageBackend;
|
|
29
|
+
spaceId: string;
|
|
30
|
+
collectionId?: string;
|
|
31
|
+
resourceId?: string;
|
|
32
|
+
}): Promise<PolicyDocument | undefined>;
|
|
33
|
+
/**
|
|
34
|
+
* Decides whether a policy document grants the requested action. Dispatches on
|
|
35
|
+
* the policy `type`; an absent policy or an unrecognized `type` grants nothing
|
|
36
|
+
* (fail-closed).
|
|
37
|
+
*
|
|
38
|
+
* @param options {object}
|
|
39
|
+
* @param [options.policy] {PolicyDocument} the effective policy, if any
|
|
40
|
+
* @param options.action {AccessAction}
|
|
41
|
+
* @param [options.logger] {FastifyBaseLogger} logger for the fail-closed warn
|
|
42
|
+
* on an unrecognized policy type
|
|
43
|
+
* @returns {boolean}
|
|
44
|
+
*/
|
|
45
|
+
export declare function policyGrants({ policy, action, logger }: {
|
|
46
|
+
policy?: PolicyDocument;
|
|
47
|
+
action: AccessAction;
|
|
48
|
+
logger?: FastifyBaseLogger;
|
|
49
|
+
}): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Builds the RFC9264 linkset for a Space or Collection, advertising its
|
|
52
|
+
* auxiliary resources for discovery. Collection vs Space is selected by whether
|
|
53
|
+
* `collectionId` is present.
|
|
54
|
+
*
|
|
55
|
+
* Relations:
|
|
56
|
+
* - `policy` (`POLICY_LINK_RELATION`) -- the access-control policy resource,
|
|
57
|
+
* advertised only when a policy document is set at that exact level.
|
|
58
|
+
* - On a Collection (`collectionId` present): `backend` (`BACKEND_LINK_RELATION`)
|
|
59
|
+
* and `quota` (`QUOTA_LINK_RELATION`), advertised unconditionally (every
|
|
60
|
+
* Collection has a selected backend and a quota report endpoint).
|
|
61
|
+
* - On a Space (`collectionId` absent): `backends-available`
|
|
62
|
+
* (`BACKENDS_AVAILABLE_LINK_RELATION`) and `quotas` (`QUOTAS_LINK_RELATION`),
|
|
63
|
+
* advertised unconditionally (both endpoints always exist).
|
|
64
|
+
*
|
|
65
|
+
* @param options {object}
|
|
66
|
+
* @param options.storage {StorageBackend} the request's storage backend
|
|
67
|
+
* @param options.spaceId {string}
|
|
68
|
+
* @param [options.collectionId] {string}
|
|
69
|
+
* @returns {Promise<object>} a `{ linkset: [...] }` object
|
|
70
|
+
*/
|
|
71
|
+
export declare function buildLinkset({ storage, spaceId, collectionId }: {
|
|
72
|
+
storage: StorageBackend;
|
|
73
|
+
spaceId: string;
|
|
74
|
+
collectionId?: string;
|
|
75
|
+
}): Promise<{
|
|
76
|
+
linkset: Array<Record<string, unknown>>;
|
|
77
|
+
}>;
|
|
78
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAkBhE,wEAAwE;AACxE,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,CAAA;AAE3C;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CAAC,EAC3C,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACX,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAkBtC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,MAAM,EACN,MAAM,EACP,EAAE;IACD,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAC3B,GAAG,OAAO,CAiBV;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CAAC,EACjC,OAAO,EACP,OAAO,EACP,YAAY,EACb,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAAE,CAAC,CAiCvD"}
|
package/dist/policy.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { POLICY_LINK_RELATION, BACKEND_LINK_RELATION, QUOTA_LINK_RELATION, BACKENDS_AVAILABLE_LINK_RELATION, QUOTAS_LINK_RELATION } from './config.default.js';
|
|
2
|
+
import { collectionPath, spacePath, policyPath, backendPath, quotaPath, backendsPath, quotasPath } from './lib/paths.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the policy that governs a target, honoring the spec's
|
|
5
|
+
* most-specific-wins inheritance: a Resource policy overrides a Collection
|
|
6
|
+
* policy, which overrides a Space policy. The first level that has a policy
|
|
7
|
+
* document is the effective policy; if none do, resolves undefined.
|
|
8
|
+
*
|
|
9
|
+
* @param options {object}
|
|
10
|
+
* @param options.storage {StorageBackend} the request's storage backend
|
|
11
|
+
* @param options.spaceId {string}
|
|
12
|
+
* @param [options.collectionId] {string}
|
|
13
|
+
* @param [options.resourceId] {string}
|
|
14
|
+
* @returns {Promise<PolicyDocument | undefined>}
|
|
15
|
+
*/
|
|
16
|
+
export async function resolveEffectivePolicy({ storage, spaceId, collectionId, resourceId }) {
|
|
17
|
+
if (collectionId !== undefined && resourceId !== undefined) {
|
|
18
|
+
const resourcePolicy = await storage.getPolicy({
|
|
19
|
+
spaceId,
|
|
20
|
+
collectionId,
|
|
21
|
+
resourceId
|
|
22
|
+
});
|
|
23
|
+
if (resourcePolicy) {
|
|
24
|
+
return resourcePolicy;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (collectionId !== undefined) {
|
|
28
|
+
const collectionPolicy = await storage.getPolicy({ spaceId, collectionId });
|
|
29
|
+
if (collectionPolicy) {
|
|
30
|
+
return collectionPolicy;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return await storage.getPolicy({ spaceId });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Decides whether a policy document grants the requested action. Dispatches on
|
|
37
|
+
* the policy `type`; an absent policy or an unrecognized `type` grants nothing
|
|
38
|
+
* (fail-closed).
|
|
39
|
+
*
|
|
40
|
+
* @param options {object}
|
|
41
|
+
* @param [options.policy] {PolicyDocument} the effective policy, if any
|
|
42
|
+
* @param options.action {AccessAction}
|
|
43
|
+
* @param [options.logger] {FastifyBaseLogger} logger for the fail-closed warn
|
|
44
|
+
* on an unrecognized policy type
|
|
45
|
+
* @returns {boolean}
|
|
46
|
+
*/
|
|
47
|
+
export function policyGrants({ policy, action, logger }) {
|
|
48
|
+
if (!policy) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
switch (policy.type) {
|
|
52
|
+
case 'PublicCanRead':
|
|
53
|
+
return action === 'read';
|
|
54
|
+
default:
|
|
55
|
+
// Unknown policy type: grant nothing, fall through to zcap-only decision.
|
|
56
|
+
// Warn so a misconfigured / unsupported policy is diagnosable (the
|
|
57
|
+
// fail-closed behavior itself is correct; this is purely a signal).
|
|
58
|
+
logger?.warn({ policyType: policy.type }, 'Unrecognized access-control policy type; granting nothing (fail-closed).');
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Builds the RFC9264 linkset for a Space or Collection, advertising its
|
|
64
|
+
* auxiliary resources for discovery. Collection vs Space is selected by whether
|
|
65
|
+
* `collectionId` is present.
|
|
66
|
+
*
|
|
67
|
+
* Relations:
|
|
68
|
+
* - `policy` (`POLICY_LINK_RELATION`) -- the access-control policy resource,
|
|
69
|
+
* advertised only when a policy document is set at that exact level.
|
|
70
|
+
* - On a Collection (`collectionId` present): `backend` (`BACKEND_LINK_RELATION`)
|
|
71
|
+
* and `quota` (`QUOTA_LINK_RELATION`), advertised unconditionally (every
|
|
72
|
+
* Collection has a selected backend and a quota report endpoint).
|
|
73
|
+
* - On a Space (`collectionId` absent): `backends-available`
|
|
74
|
+
* (`BACKENDS_AVAILABLE_LINK_RELATION`) and `quotas` (`QUOTAS_LINK_RELATION`),
|
|
75
|
+
* advertised unconditionally (both endpoints always exist).
|
|
76
|
+
*
|
|
77
|
+
* @param options {object}
|
|
78
|
+
* @param options.storage {StorageBackend} the request's storage backend
|
|
79
|
+
* @param options.spaceId {string}
|
|
80
|
+
* @param [options.collectionId] {string}
|
|
81
|
+
* @returns {Promise<object>} a `{ linkset: [...] }` object
|
|
82
|
+
*/
|
|
83
|
+
export async function buildLinkset({ storage, spaceId, collectionId }) {
|
|
84
|
+
const anchor = collectionId !== undefined
|
|
85
|
+
? collectionPath({ spaceId, collectionId })
|
|
86
|
+
: spacePath({ spaceId });
|
|
87
|
+
const policyHref = policyPath({ spaceId, collectionId });
|
|
88
|
+
const policy = collectionId !== undefined
|
|
89
|
+
? await storage.getPolicy({ spaceId, collectionId })
|
|
90
|
+
: await storage.getPolicy({ spaceId });
|
|
91
|
+
const entry = { anchor };
|
|
92
|
+
if (policy) {
|
|
93
|
+
entry[POLICY_LINK_RELATION] = [
|
|
94
|
+
{ href: policyHref, type: 'application/json' }
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
if (collectionId !== undefined) {
|
|
98
|
+
entry[BACKEND_LINK_RELATION] = [
|
|
99
|
+
{ href: backendPath({ spaceId, collectionId }), type: 'application/json' }
|
|
100
|
+
];
|
|
101
|
+
entry[QUOTA_LINK_RELATION] = [
|
|
102
|
+
{ href: quotaPath({ spaceId, collectionId }), type: 'application/json' }
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
entry[BACKENDS_AVAILABLE_LINK_RELATION] = [
|
|
107
|
+
{ href: backendsPath({ spaceId }), type: 'application/json' }
|
|
108
|
+
];
|
|
109
|
+
entry[QUOTAS_LINK_RELATION] = [
|
|
110
|
+
{ href: quotasPath({ spaceId }), type: 'application/json' }
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
return { linkset: [entry] };
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provisioning gate for the two open provisioning endpoints (`POST /spaces/`
|
|
3
|
+
* and `POST /kms/keystores`). By default provisioning is allowed -- anyone may
|
|
4
|
+
* create a Space or keystore by proving control of the controller DID named in
|
|
5
|
+
* the request body (the teaching-server behavior). A deployment that wants to
|
|
6
|
+
* gate provisioning supplies either an `onboardingToken` (a shared secret,
|
|
7
|
+
* checked here by the stock `onboardingTokenAuthorizer`) or a custom
|
|
8
|
+
* `authorizeProvisioning` callback; both flow through the same seam.
|
|
9
|
+
*/
|
|
10
|
+
import type { FastifyReply, FastifyRequest } from 'fastify';
|
|
11
|
+
import type { AuthorizeProvisioning } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* onRequest hook: the provisioning gate. Runs first in the hook chain of the
|
|
14
|
+
* SpacesRepository and `/kms` route groups. For a `POST` to one of the two
|
|
15
|
+
* provisioning endpoints it consults the configured `authorizeProvisioning`
|
|
16
|
+
* callback: `verify` proceeds to normal zcap verification, `grant` marks the
|
|
17
|
+
* request as provisioning-authorized (skipping zcap verification downstream),
|
|
18
|
+
* and `deny` refuses with a 403. Every other request (and every deployment with
|
|
19
|
+
* no callback configured) passes straight through, so the default zcap path is
|
|
20
|
+
* unchanged.
|
|
21
|
+
* @param request {import('fastify').FastifyRequest}
|
|
22
|
+
* @param reply {import('fastify').FastifyReply}
|
|
23
|
+
* @returns {Promise<void>}
|
|
24
|
+
*/
|
|
25
|
+
export declare function provisioningGate(request: FastifyRequest, _reply: FastifyReply): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Builds the stock onboarding-token provisioning authorizer: a shared-secret
|
|
28
|
+
* gate that requires an `Authorization: Bearer <token>` header matching the
|
|
29
|
+
* configured token. A request signed with a zcap `Authorization: Signature ...`
|
|
30
|
+
* also lands here -- with a token configured, provisioning requires the token,
|
|
31
|
+
* full stop. Throws `MissingOnboardingTokenError` (401) when no Bearer token is
|
|
32
|
+
* present and `InvalidOnboardingTokenError` (403) when it does not match; a
|
|
33
|
+
* match returns `grant`.
|
|
34
|
+
* @param onboardingToken {string} the configured shared-secret token
|
|
35
|
+
* @returns {AuthorizeProvisioning}
|
|
36
|
+
*/
|
|
37
|
+
export declare function onboardingTokenAuthorizer(onboardingToken: string): AuthorizeProvisioning;
|
|
38
|
+
//# sourceMappingURL=provisioning.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisioning.d.ts","sourceRoot":"","sources":["../src/provisioning.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE3D,OAAO,KAAK,EAAE,qBAAqB,EAAwB,MAAM,YAAY,CAAA;AAc7E;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,eAAe,EAAE,MAAM,GACtB,qBAAqB,CAmBvB"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { InvalidOnboardingTokenError, MissingOnboardingTokenError, ProvisioningNotAuthorizedError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* The exact route URLs the provisioning gate acts on. `/spaces` (no trailing
|
|
5
|
+
* slash) is included so a token-authorized request reaches the canonical-slash
|
|
6
|
+
* 308 redirect instead of failing the auth-header check first.
|
|
7
|
+
*/
|
|
8
|
+
const PROVISIONING_ROUTES = new Set(['/spaces', '/spaces/', '/kms/keystores']);
|
|
9
|
+
/**
|
|
10
|
+
* onRequest hook: the provisioning gate. Runs first in the hook chain of the
|
|
11
|
+
* SpacesRepository and `/kms` route groups. For a `POST` to one of the two
|
|
12
|
+
* provisioning endpoints it consults the configured `authorizeProvisioning`
|
|
13
|
+
* callback: `verify` proceeds to normal zcap verification, `grant` marks the
|
|
14
|
+
* request as provisioning-authorized (skipping zcap verification downstream),
|
|
15
|
+
* and `deny` refuses with a 403. Every other request (and every deployment with
|
|
16
|
+
* no callback configured) passes straight through, so the default zcap path is
|
|
17
|
+
* unchanged.
|
|
18
|
+
* @param request {import('fastify').FastifyRequest}
|
|
19
|
+
* @param reply {import('fastify').FastifyReply}
|
|
20
|
+
* @returns {Promise<void>}
|
|
21
|
+
*/
|
|
22
|
+
export async function provisioningGate(request, _reply) {
|
|
23
|
+
// Only the two provisioning POST endpoints are gated; everything else passes.
|
|
24
|
+
if (request.method !== 'POST' ||
|
|
25
|
+
!PROVISIONING_ROUTES.has(request.routeOptions.url ?? '')) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const authorize = request.server.authorizeProvisioning;
|
|
29
|
+
// No policy configured: default allow -- the zcap path is unchanged.
|
|
30
|
+
if (!authorize) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const decision = await authorize({ request });
|
|
34
|
+
if (decision === 'grant') {
|
|
35
|
+
request.provisioningAuthorized = true;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (decision === 'deny') {
|
|
39
|
+
throw new ProvisioningNotAuthorizedError();
|
|
40
|
+
}
|
|
41
|
+
// 'verify': fall through to the normal zcap capability-invocation path.
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Builds the stock onboarding-token provisioning authorizer: a shared-secret
|
|
45
|
+
* gate that requires an `Authorization: Bearer <token>` header matching the
|
|
46
|
+
* configured token. A request signed with a zcap `Authorization: Signature ...`
|
|
47
|
+
* also lands here -- with a token configured, provisioning requires the token,
|
|
48
|
+
* full stop. Throws `MissingOnboardingTokenError` (401) when no Bearer token is
|
|
49
|
+
* present and `InvalidOnboardingTokenError` (403) when it does not match; a
|
|
50
|
+
* match returns `grant`.
|
|
51
|
+
* @param onboardingToken {string} the configured shared-secret token
|
|
52
|
+
* @returns {AuthorizeProvisioning}
|
|
53
|
+
*/
|
|
54
|
+
export function onboardingTokenAuthorizer(onboardingToken) {
|
|
55
|
+
return async function authorizeWithOnboardingToken({ request }) {
|
|
56
|
+
const header = request.headers.authorization;
|
|
57
|
+
const match = header?.match(/^Bearer\s+(.+)$/i);
|
|
58
|
+
if (!match) {
|
|
59
|
+
throw new MissingOnboardingTokenError();
|
|
60
|
+
}
|
|
61
|
+
const presented = match[1];
|
|
62
|
+
// Timing-safe comparison over fixed-length SHA-256 digests, so mismatched
|
|
63
|
+
// token lengths do not leak through `timingSafeEqual`'s length check.
|
|
64
|
+
const presentedDigest = createHash('sha256').update(presented).digest();
|
|
65
|
+
const expectedDigest = createHash('sha256').update(onboardingToken).digest();
|
|
66
|
+
if (!timingSafeEqual(presentedDigest, expectedDigest)) {
|
|
67
|
+
throw new InvalidOnboardingTokenError();
|
|
68
|
+
}
|
|
69
|
+
return 'grant';
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=provisioning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisioning.js","sourceRoot":"","sources":["../src/provisioning.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEzD,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,EAC3B,8BAA8B,EAC/B,MAAM,aAAa,CAAA;AAEpB;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAA;AAE9E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAuB,EACvB,MAAoB;IAEpB,8EAA8E;IAC9E,IACE,OAAO,CAAC,MAAM,KAAK,MAAM;QACzB,CAAC,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,EAAE,CAAC,EACxD,CAAC;QACD,OAAM;IACR,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAA;IACtD,qEAAqE;IACrE,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAM;IACR,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;IAC7C,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAA;QACrC,OAAM;IACR,CAAC;IACD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,MAAM,IAAI,8BAA8B,EAAE,CAAA;IAC5C,CAAC;IACD,wEAAwE;AAC1E,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,yBAAyB,CACvC,eAAuB;IAEvB,OAAO,KAAK,UAAU,4BAA4B,CAAC,EACjD,OAAO,EACR;QACC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAA;QAC5C,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAA;QAC/C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,2BAA2B,EAAE,CAAA;QACzC,CAAC;QACD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAW,CAAA;QACpC,0EAA0E;QAC1E,sEAAsE;QACtE,MAAM,eAAe,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,CAAA;QACvE,MAAM,cAAc,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,MAAM,EAAE,CAAA;QAC5E,IAAI,CAAC,eAAe,CAAC,eAAe,EAAE,cAAc,CAAC,EAAE,CAAC;YACtD,MAAM,IAAI,2BAA2B,EAAE,CAAA;QACzC,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request handlers for registering an `external` backend against a Space (spec
|
|
3
|
+
* "Backends"): register (`POST`), replace (`PUT`), and deregister (`DELETE`) a
|
|
4
|
+
* backend record. Authorization is the Space controller's key (capability-only,
|
|
5
|
+
* no policy fallback), exactly like Create Collection / Delete Space.
|
|
6
|
+
*
|
|
7
|
+
* The write body is secret-bearing (`BackendConnectionInput`), but every
|
|
8
|
+
* response returns the **sanitized** `BackendDescriptor` (via
|
|
9
|
+
* `sanitizeBackendRecord`), so a client response can never carry the secret. A
|
|
10
|
+
* registered backend is inert this increment: it is listed but not yet
|
|
11
|
+
* selectable as a Collection's `backend` (the live provider adapter is future
|
|
12
|
+
* work).
|
|
13
|
+
*/
|
|
14
|
+
import type { FastifyReply, FastifyRequest } from 'fastify';
|
|
15
|
+
export declare class BackendRequest {
|
|
16
|
+
/**
|
|
17
|
+
* POST /space/:spaceId/backends
|
|
18
|
+
* Registers a new `external` backend. Rejects the reserved `default` id and a
|
|
19
|
+
* duplicate id (checked after capability verification so an unauthorized
|
|
20
|
+
* caller cannot probe ids). Responds 201 with the sanitized descriptor.
|
|
21
|
+
* @param request {import('fastify').FastifyRequest}
|
|
22
|
+
* @param reply {import('fastify').FastifyReply}
|
|
23
|
+
* @returns {Promise<FastifyReply>}
|
|
24
|
+
*/
|
|
25
|
+
static post(request: FastifyRequest<{
|
|
26
|
+
Params: {
|
|
27
|
+
spaceId: string;
|
|
28
|
+
};
|
|
29
|
+
Body: unknown;
|
|
30
|
+
}>, reply: FastifyReply): Promise<FastifyReply>;
|
|
31
|
+
/**
|
|
32
|
+
* PUT /space/:spaceId/backends/:backendId
|
|
33
|
+
* Upsert (the re-consent path): create-or-replace a backend record at a chosen
|
|
34
|
+
* id. The body `id` (when present) must match the path id, and `default` is
|
|
35
|
+
* rejected. Responds 201 + Location + sanitized body on create, 204 on update.
|
|
36
|
+
* @param request {import('fastify').FastifyRequest}
|
|
37
|
+
* @param reply {import('fastify').FastifyReply}
|
|
38
|
+
* @returns {Promise<FastifyReply>}
|
|
39
|
+
*/
|
|
40
|
+
static put(request: FastifyRequest<{
|
|
41
|
+
Params: {
|
|
42
|
+
spaceId: string;
|
|
43
|
+
backendId: string;
|
|
44
|
+
};
|
|
45
|
+
Body: unknown;
|
|
46
|
+
}>, reply: FastifyReply): Promise<FastifyReply>;
|
|
47
|
+
/**
|
|
48
|
+
* DELETE /space/:spaceId/backends/:backendId
|
|
49
|
+
* Deregisters a backend. Idempotent (204 even if the record was absent).
|
|
50
|
+
* @param request {import('fastify').FastifyRequest}
|
|
51
|
+
* @param reply {import('fastify').FastifyReply}
|
|
52
|
+
* @returns {Promise<FastifyReply>}
|
|
53
|
+
*/
|
|
54
|
+
static delete(request: FastifyRequest<{
|
|
55
|
+
Params: {
|
|
56
|
+
spaceId: string;
|
|
57
|
+
backendId: string;
|
|
58
|
+
};
|
|
59
|
+
}>, reply: FastifyReply): Promise<FastifyReply>;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=BackendRequest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackendRequest.d.ts","sourceRoot":"","sources":["../../src/requests/BackendRequest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAe3D,qBAAa,cAAc;IACzB;;;;;;;;OAQG;WACU,IAAI,CACf,OAAO,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;QAC3B,IAAI,EAAE,OAAO,CAAA;KACd,CAAC,EACF,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC;IAqDxB;;;;;;;;OAQG;WACU,GAAG,CACd,OAAO,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAA;QAC9C,IAAI,EAAE,OAAO,CAAA;KACd,CAAC,EACF,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC;IAkExB;;;;;;OAMG;WACU,MAAM,CACjB,OAAO,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAA;KAC/C,CAAC,EACF,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC;CA4BzB"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { fetchSpaceAndVerify } from './spaceContext.js';
|
|
2
|
+
import { assertValidIds } from '../lib/validateId.js';
|
|
3
|
+
import { DEFAULT_BACKEND_ID, assertProviderAllowed, assertValidBackendId, buildBackendRecord, parseBackendRegistration, sanitizeBackendRecord } from '../lib/backends.js';
|
|
4
|
+
import { invalidateResolvedBackend } from '../lib/backendRegistry.js';
|
|
5
|
+
import { backendsPath, registeredBackendPath } from '../lib/paths.js';
|
|
6
|
+
import { InvalidRequestBodyError, IdConflictError } from '../errors.js';
|
|
7
|
+
export class BackendRequest {
|
|
8
|
+
/**
|
|
9
|
+
* POST /space/:spaceId/backends
|
|
10
|
+
* Registers a new `external` backend. Rejects the reserved `default` id and a
|
|
11
|
+
* duplicate id (checked after capability verification so an unauthorized
|
|
12
|
+
* caller cannot probe ids). Responds 201 with the sanitized descriptor.
|
|
13
|
+
* @param request {import('fastify').FastifyRequest}
|
|
14
|
+
* @param reply {import('fastify').FastifyReply}
|
|
15
|
+
* @returns {Promise<FastifyReply>}
|
|
16
|
+
*/
|
|
17
|
+
static async post(request, reply) {
|
|
18
|
+
const { params: { spaceId }, body } = request;
|
|
19
|
+
const { serverUrl, storage } = request.server;
|
|
20
|
+
const requestName = 'Register Backend';
|
|
21
|
+
// Reject path-traversal / non-URL-safe ids before any storage access.
|
|
22
|
+
assertValidIds({ spaceId }, { requestName });
|
|
23
|
+
const registration = parseBackendRegistration(body, { requestName });
|
|
24
|
+
// Fail fast if the server's registration allowlist excludes this provider
|
|
25
|
+
// (permissive when no allowlist is configured).
|
|
26
|
+
assertProviderAllowed({
|
|
27
|
+
provider: registration.provider,
|
|
28
|
+
enabledProviders: request.server.enabledBackendProviders
|
|
29
|
+
});
|
|
30
|
+
assertValidBackendId(registration.id, { requestName });
|
|
31
|
+
// Verify (capability-only): registering a backend requires a valid
|
|
32
|
+
// capability invocation by the Space controller; no policy fallback.
|
|
33
|
+
await fetchSpaceAndVerify({
|
|
34
|
+
request,
|
|
35
|
+
spaceId,
|
|
36
|
+
targetPath: backendsPath({ spaceId }),
|
|
37
|
+
requestName
|
|
38
|
+
});
|
|
39
|
+
// After verification (so an unauthorized caller cannot probe ids): the
|
|
40
|
+
// `default` id is reserved for the server backend, and POST must not replace
|
|
41
|
+
// an existing record (create-or-replace by id is PUT's job).
|
|
42
|
+
if (registration.id === DEFAULT_BACKEND_ID) {
|
|
43
|
+
throw new InvalidRequestBodyError({
|
|
44
|
+
requestName,
|
|
45
|
+
detail: `"${DEFAULT_BACKEND_ID}" is the reserved server backend id and cannot be registered.`,
|
|
46
|
+
pointer: '#/id'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (await storage.getBackend({ spaceId, backendId: registration.id })) {
|
|
50
|
+
throw new IdConflictError({ kind: 'Backend' });
|
|
51
|
+
}
|
|
52
|
+
const record = buildBackendRecord(registration);
|
|
53
|
+
await storage.writeBackend({ spaceId, backendId: record.id, record });
|
|
54
|
+
const createdUrl = new URL(registeredBackendPath({ spaceId, backendId: record.id }), serverUrl).toString();
|
|
55
|
+
reply.header('Location', createdUrl);
|
|
56
|
+
return reply.status(201).send(sanitizeBackendRecord(record));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* PUT /space/:spaceId/backends/:backendId
|
|
60
|
+
* Upsert (the re-consent path): create-or-replace a backend record at a chosen
|
|
61
|
+
* id. The body `id` (when present) must match the path id, and `default` is
|
|
62
|
+
* rejected. Responds 201 + Location + sanitized body on create, 204 on update.
|
|
63
|
+
* @param request {import('fastify').FastifyRequest}
|
|
64
|
+
* @param reply {import('fastify').FastifyReply}
|
|
65
|
+
* @returns {Promise<FastifyReply>}
|
|
66
|
+
*/
|
|
67
|
+
static async put(request, reply) {
|
|
68
|
+
const { params: { spaceId, backendId }, body } = request;
|
|
69
|
+
const { serverUrl, storage } = request.server;
|
|
70
|
+
const requestName = 'Register Backend';
|
|
71
|
+
// Reject path-traversal / non-URL-safe ids before any storage access.
|
|
72
|
+
assertValidIds({ spaceId }, { requestName });
|
|
73
|
+
assertValidBackendId(backendId, { requestName });
|
|
74
|
+
const registration = parseBackendRegistration(body, { requestName });
|
|
75
|
+
// Fail fast if the server's registration allowlist excludes this provider
|
|
76
|
+
// (permissive when no allowlist is configured).
|
|
77
|
+
assertProviderAllowed({
|
|
78
|
+
provider: registration.provider,
|
|
79
|
+
enabledProviders: request.server.enabledBackendProviders
|
|
80
|
+
});
|
|
81
|
+
// The backend `id` is immutable: a body `id`, when present, must match the
|
|
82
|
+
// URL id; and `default` is the reserved server backend id.
|
|
83
|
+
if (registration.id !== backendId) {
|
|
84
|
+
throw new InvalidRequestBodyError({
|
|
85
|
+
requestName,
|
|
86
|
+
detail: `Backend "id" (${registration.id}) does not match the URL backend id (${backendId}).`,
|
|
87
|
+
pointer: '#/id'
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (backendId === DEFAULT_BACKEND_ID) {
|
|
91
|
+
throw new InvalidRequestBodyError({
|
|
92
|
+
requestName,
|
|
93
|
+
detail: `"${DEFAULT_BACKEND_ID}" is the reserved server backend id and cannot be registered.`,
|
|
94
|
+
pointer: '#/id'
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// Verify (capability-only): no policy fallback for a write.
|
|
98
|
+
await fetchSpaceAndVerify({
|
|
99
|
+
request,
|
|
100
|
+
spaceId,
|
|
101
|
+
targetPath: registeredBackendPath({ spaceId, backendId }),
|
|
102
|
+
requestName
|
|
103
|
+
});
|
|
104
|
+
const existing = await storage.getBackend({ spaceId, backendId });
|
|
105
|
+
const record = buildBackendRecord(registration);
|
|
106
|
+
await storage.writeBackend({ spaceId, backendId, record });
|
|
107
|
+
// Bust any memoized adapter so the next resolve rebuilds it from the new
|
|
108
|
+
// connection material.
|
|
109
|
+
invalidateResolvedBackend({
|
|
110
|
+
providers: request.server.backendProviders,
|
|
111
|
+
spaceId,
|
|
112
|
+
backendId
|
|
113
|
+
});
|
|
114
|
+
if (existing) {
|
|
115
|
+
return reply.status(204).send();
|
|
116
|
+
}
|
|
117
|
+
const createdUrl = new URL(registeredBackendPath({ spaceId, backendId }), serverUrl).toString();
|
|
118
|
+
reply.header('Location', createdUrl);
|
|
119
|
+
return reply.status(201).send(sanitizeBackendRecord(record));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* DELETE /space/:spaceId/backends/:backendId
|
|
123
|
+
* Deregisters a backend. Idempotent (204 even if the record was absent).
|
|
124
|
+
* @param request {import('fastify').FastifyRequest}
|
|
125
|
+
* @param reply {import('fastify').FastifyReply}
|
|
126
|
+
* @returns {Promise<FastifyReply>}
|
|
127
|
+
*/
|
|
128
|
+
static async delete(request, reply) {
|
|
129
|
+
const { params: { spaceId, backendId } } = request;
|
|
130
|
+
const { storage } = request.server;
|
|
131
|
+
const requestName = 'Deregister Backend';
|
|
132
|
+
// Reject path-traversal / non-URL-safe ids before any storage access.
|
|
133
|
+
assertValidIds({ spaceId }, { requestName });
|
|
134
|
+
assertValidBackendId(backendId, { requestName });
|
|
135
|
+
// Verify (capability-only): no policy fallback for a write.
|
|
136
|
+
await fetchSpaceAndVerify({
|
|
137
|
+
request,
|
|
138
|
+
spaceId,
|
|
139
|
+
targetPath: registeredBackendPath({ spaceId, backendId }),
|
|
140
|
+
requestName
|
|
141
|
+
});
|
|
142
|
+
await storage.deleteBackend({ spaceId, backendId });
|
|
143
|
+
// Bust any memoized adapter for the now-removed record.
|
|
144
|
+
invalidateResolvedBackend({
|
|
145
|
+
providers: request.server.backendProviders,
|
|
146
|
+
spaceId,
|
|
147
|
+
backendId
|
|
148
|
+
});
|
|
149
|
+
return reply.status(204).send();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=BackendRequest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackendRequest.js","sourceRoot":"","sources":["../../src/requests/BackendRequest.ts"],"names":[],"mappings":"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"}
|