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
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection backend selection helpers (spec "Backends" / "Collection Backend
|
|
3
|
+
* Selected"). A Collection carries an optional `backend` object whose `id` MUST
|
|
4
|
+
* name one of the Space's backends-available; this module centralizes the three
|
|
5
|
+
* operations that need that list: enumerating the available backends, validating
|
|
6
|
+
* (and default-filling) a client-supplied `backend` on create/update, and
|
|
7
|
+
* resolving a stored `{ id }` to its full descriptor for `GET .../backend`.
|
|
8
|
+
*
|
|
9
|
+
* This reference server ships a single server-configured backend (the active
|
|
10
|
+
* `StorageBackend`, registered as `default`), so the available list has one
|
|
11
|
+
* entry; the indirection leaves room for multiple registered backends later.
|
|
12
|
+
*/
|
|
13
|
+
import type {
|
|
14
|
+
BackendDescriptor,
|
|
15
|
+
BackendConnectionPublic,
|
|
16
|
+
BackendRegistration,
|
|
17
|
+
CollectionDescription,
|
|
18
|
+
StorageBackend,
|
|
19
|
+
StoredBackendRecord
|
|
20
|
+
} from '../types.js'
|
|
21
|
+
import { InvalidRequestBodyError, UnsupportedBackendError } from '../errors.js'
|
|
22
|
+
import { isUrlSafeSegment } from './validateId.js'
|
|
23
|
+
|
|
24
|
+
/** The conventional id of the server-assigned default backend (spec). */
|
|
25
|
+
export const DEFAULT_BACKEND_ID = 'default'
|
|
26
|
+
|
|
27
|
+
// `listRegisteredBackends` (defined below, with the registration helpers) is the
|
|
28
|
+
// single source of a Space's selectable backends -- the server `default` plus
|
|
29
|
+
// its registered `external` records -- used by both the selection-validation
|
|
30
|
+
// (`assertSupportedBackend`) and descriptor-resolution (`resolveBackendDescriptor`)
|
|
31
|
+
// paths below.
|
|
32
|
+
|
|
33
|
+
/** The public (secret-free) subset of a connection a sanitized read may carry. */
|
|
34
|
+
const PUBLIC_CONNECTION_FIELDS = [
|
|
35
|
+
'account',
|
|
36
|
+
'scope',
|
|
37
|
+
'connectedAt',
|
|
38
|
+
'rootFolderName'
|
|
39
|
+
] as const
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Validates a client-supplied Collection `backend` value and returns the
|
|
43
|
+
* normalized `{ id }` to persist. An absent value defaults to the server's
|
|
44
|
+
* default backend. A present-but-malformed value (not an object, or no string
|
|
45
|
+
* `id`) is `invalid-request-body` (400); a well-formed value whose `id` is not
|
|
46
|
+
* in the Space's backends-available (the server `default` plus its registered
|
|
47
|
+
* `external` backends) is `unsupported-backend` (409). A registered `external`
|
|
48
|
+
* backend is now selectable here -- routing its data plane to the resolved
|
|
49
|
+
* adapter is the resolver's job (lib/backendRegistry.ts).
|
|
50
|
+
*
|
|
51
|
+
* @param options {object}
|
|
52
|
+
* @param options.storage {StorageBackend} the request's storage backend
|
|
53
|
+
* @param options.spaceId {string} the Space whose backends-available is checked
|
|
54
|
+
* @param [options.backend] {unknown} the request body's `backend` value
|
|
55
|
+
* @param [options.requestName] {string} request name for the 400 error title
|
|
56
|
+
* @returns {Promise<{ id: string }>} the normalized backend reference to store
|
|
57
|
+
*/
|
|
58
|
+
export async function assertSupportedBackend({
|
|
59
|
+
storage,
|
|
60
|
+
spaceId,
|
|
61
|
+
backend,
|
|
62
|
+
requestName
|
|
63
|
+
}: {
|
|
64
|
+
storage: StorageBackend
|
|
65
|
+
spaceId: string
|
|
66
|
+
backend?: unknown
|
|
67
|
+
requestName?: string
|
|
68
|
+
}): Promise<{ id: string }> {
|
|
69
|
+
if (backend === undefined) {
|
|
70
|
+
return { id: DEFAULT_BACKEND_ID }
|
|
71
|
+
}
|
|
72
|
+
if (
|
|
73
|
+
typeof backend !== 'object' ||
|
|
74
|
+
backend === null ||
|
|
75
|
+
typeof (backend as { id?: unknown }).id !== 'string'
|
|
76
|
+
) {
|
|
77
|
+
throw new InvalidRequestBodyError({
|
|
78
|
+
requestName,
|
|
79
|
+
detail: 'Collection "backend" must be an object with a string "id".',
|
|
80
|
+
pointer: '#/backend'
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
const { id } = backend as { id: string }
|
|
84
|
+
const available = await listRegisteredBackends({ storage, spaceId })
|
|
85
|
+
if (!available.some(entry => entry.id === id)) {
|
|
86
|
+
throw new UnsupportedBackendError({ backendId: id })
|
|
87
|
+
}
|
|
88
|
+
return { id }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Resolves a Collection's selected backend to its full descriptor, for the
|
|
93
|
+
* `GET .../backend` ("Collection Backend Selected") response, consulting the
|
|
94
|
+
* Space's backends-available (server `default` plus registered `external`
|
|
95
|
+
* records). A stored description without a `backend` (created before the property
|
|
96
|
+
* existed) defaults to the server's default backend.
|
|
97
|
+
*
|
|
98
|
+
* @param options {object}
|
|
99
|
+
* @param options.storage {StorageBackend} the request's storage backend
|
|
100
|
+
* @param options.spaceId {string} the Space whose backends-available is checked
|
|
101
|
+
* @param options.collectionDescription {CollectionDescription}
|
|
102
|
+
* @returns {Promise<BackendDescriptor>}
|
|
103
|
+
*/
|
|
104
|
+
export async function resolveBackendDescriptor({
|
|
105
|
+
storage,
|
|
106
|
+
spaceId,
|
|
107
|
+
collectionDescription
|
|
108
|
+
}: {
|
|
109
|
+
storage: StorageBackend
|
|
110
|
+
spaceId: string
|
|
111
|
+
collectionDescription: CollectionDescription
|
|
112
|
+
}): Promise<BackendDescriptor> {
|
|
113
|
+
const id = collectionDescription.backend?.id ?? DEFAULT_BACKEND_ID
|
|
114
|
+
const available = await listRegisteredBackends({ storage, spaceId })
|
|
115
|
+
const descriptor = available.find(entry => entry.id === id)
|
|
116
|
+
// A stored backend id should always resolve (it was validated on write); fall
|
|
117
|
+
// back to the default descriptor defensively rather than returning undefined.
|
|
118
|
+
return descriptor ?? storage.describe()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- External backend registration (spec "Backends") ---
|
|
122
|
+
//
|
|
123
|
+
// A registered `external` backend is both listed (below) and now *selectable* as
|
|
124
|
+
// a Collection's `backend` (`assertSupportedBackend` / `resolveBackendDescriptor`
|
|
125
|
+
// above consult `listRegisteredBackends`). Routing the selected Collection's data
|
|
126
|
+
// plane to the live provider adapter is the resolver's job
|
|
127
|
+
// (lib/backendRegistry.ts); a selected backend with no registered provider
|
|
128
|
+
// adapter fails closed there.
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Projects a full (secret-bearing) `StoredBackendRecord` down to the sanitized
|
|
132
|
+
* `BackendDescriptor` that every client-facing read path returns: the public
|
|
133
|
+
* descriptor fields plus a `connection` reduced to its public subset (no
|
|
134
|
+
* `authorizationCode` / `refreshToken` / etc.). The single projection shared by
|
|
135
|
+
* `FileSystemBackend.listBackends` and the register (`POST`/`PUT`) responses, so
|
|
136
|
+
* the secret material has exactly one place it could leak -- and does not.
|
|
137
|
+
* @param record {StoredBackendRecord}
|
|
138
|
+
* @returns {BackendDescriptor}
|
|
139
|
+
*/
|
|
140
|
+
export function sanitizeBackendRecord(
|
|
141
|
+
record: StoredBackendRecord
|
|
142
|
+
): BackendDescriptor {
|
|
143
|
+
const { connection } = record
|
|
144
|
+
const publicConnection: BackendConnectionPublic = {
|
|
145
|
+
kind: connection.kind,
|
|
146
|
+
status:
|
|
147
|
+
(connection.status as BackendConnectionPublic['status']) ?? 'registered'
|
|
148
|
+
}
|
|
149
|
+
for (const field of PUBLIC_CONNECTION_FIELDS) {
|
|
150
|
+
const value = connection[field]
|
|
151
|
+
if (typeof value === 'string') {
|
|
152
|
+
publicConnection[field] = value
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
id: record.id,
|
|
157
|
+
...(record.name !== undefined && { name: record.name }),
|
|
158
|
+
managedBy: record.managedBy,
|
|
159
|
+
...(record.storageMode !== undefined && {
|
|
160
|
+
storageMode: record.storageMode
|
|
161
|
+
}),
|
|
162
|
+
...(record.persistence !== undefined && {
|
|
163
|
+
persistence: record.persistence
|
|
164
|
+
}),
|
|
165
|
+
...(record.features !== undefined && { features: record.features }),
|
|
166
|
+
provider: record.provider,
|
|
167
|
+
connection: publicConnection
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The backends advertised at `GET /space/:spaceId/backends`: the server's
|
|
173
|
+
* `default` backend first, followed by the Space's sanitized registered
|
|
174
|
+
* `external` backends. Also the single source of a Space's selectable backends
|
|
175
|
+
* for `assertSupportedBackend` / `resolveBackendDescriptor`.
|
|
176
|
+
* @param options {object}
|
|
177
|
+
* @param options.storage {StorageBackend}
|
|
178
|
+
* @param options.spaceId {string}
|
|
179
|
+
* @returns {Promise<BackendDescriptor[]>}
|
|
180
|
+
*/
|
|
181
|
+
export async function listRegisteredBackends({
|
|
182
|
+
storage,
|
|
183
|
+
spaceId
|
|
184
|
+
}: {
|
|
185
|
+
storage: StorageBackend
|
|
186
|
+
spaceId: string
|
|
187
|
+
}): Promise<BackendDescriptor[]> {
|
|
188
|
+
return [storage.describe(), ...(await storage.listBackends({ spaceId }))]
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Validates a backend-registration request body and returns the typed
|
|
193
|
+
* `BackendRegistration`. Enforces: an object body; a string `id`; a non-empty
|
|
194
|
+
* string `provider`; `managedBy` absent or `'external'` (a client may not
|
|
195
|
+
* register a `server` backend); and a `connection` object with a string `kind`.
|
|
196
|
+
* Throws `InvalidRequestBodyError` (400) with the offending `#/...` pointer.
|
|
197
|
+
* Does not check `id` URL-safety -- callers run `assertValidBackendId`.
|
|
198
|
+
* @param body {unknown} the parsed request body
|
|
199
|
+
* @param options {object}
|
|
200
|
+
* @param [options.requestName] {string} request name for the 400 error title
|
|
201
|
+
* @returns {BackendRegistration}
|
|
202
|
+
*/
|
|
203
|
+
export function parseBackendRegistration(
|
|
204
|
+
body: unknown,
|
|
205
|
+
{ requestName }: { requestName?: string } = {}
|
|
206
|
+
): BackendRegistration {
|
|
207
|
+
if (typeof body !== 'object' || body === null) {
|
|
208
|
+
throw new InvalidRequestBodyError({
|
|
209
|
+
requestName,
|
|
210
|
+
detail: 'Backend registration body must be a JSON object.',
|
|
211
|
+
pointer: '#'
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
const candidate = body as Record<string, unknown>
|
|
215
|
+
if (typeof candidate.id !== 'string') {
|
|
216
|
+
throw new InvalidRequestBodyError({
|
|
217
|
+
requestName,
|
|
218
|
+
detail: 'Backend registration requires a string "id".',
|
|
219
|
+
pointer: '#/id'
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
if (
|
|
223
|
+
typeof candidate.provider !== 'string' ||
|
|
224
|
+
candidate.provider.length === 0
|
|
225
|
+
) {
|
|
226
|
+
throw new InvalidRequestBodyError({
|
|
227
|
+
requestName,
|
|
228
|
+
detail: 'Backend registration requires a non-empty string "provider".',
|
|
229
|
+
pointer: '#/provider'
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
if (candidate.managedBy !== undefined && candidate.managedBy !== 'external') {
|
|
233
|
+
throw new InvalidRequestBodyError({
|
|
234
|
+
requestName,
|
|
235
|
+
detail:
|
|
236
|
+
'Backend "managedBy" must be "external"; server backends are not client-registered.',
|
|
237
|
+
pointer: '#/managedBy'
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
const connection = candidate.connection
|
|
241
|
+
if (
|
|
242
|
+
typeof connection !== 'object' ||
|
|
243
|
+
connection === null ||
|
|
244
|
+
typeof (connection as Record<string, unknown>).kind !== 'string'
|
|
245
|
+
) {
|
|
246
|
+
throw new InvalidRequestBodyError({
|
|
247
|
+
requestName,
|
|
248
|
+
detail: 'Backend "connection" must be an object with a string "kind".',
|
|
249
|
+
pointer: '#/connection'
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
id: candidate.id,
|
|
254
|
+
...(typeof candidate.name === 'string' && { name: candidate.name }),
|
|
255
|
+
managedBy: 'external',
|
|
256
|
+
provider: candidate.provider,
|
|
257
|
+
...(Array.isArray(candidate.storageMode) && {
|
|
258
|
+
storageMode: candidate.storageMode as Array<'document' | 'blob'>
|
|
259
|
+
}),
|
|
260
|
+
...(Array.isArray(candidate.features) && {
|
|
261
|
+
features: candidate.features as string[]
|
|
262
|
+
}),
|
|
263
|
+
connection: connection as BackendRegistration['connection']
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Enforces the optional server-wide registration allowlist (config
|
|
269
|
+
* `WAS_ENABLED_BACKENDS`): the set of backend `provider` names a client may
|
|
270
|
+
* register. An `undefined` allowlist is **permissive** -- any provider passes,
|
|
271
|
+
* preserving the prior behavior where any provider could be registered. When the
|
|
272
|
+
* allowlist is configured, a `provider` outside it is rejected with
|
|
273
|
+
* `unsupported-backend` (409) pointing at `#/provider`. This is the fail-fast
|
|
274
|
+
* registration gate; the resolver's data-plane factory check
|
|
275
|
+
* (lib/backendRegistry.ts) remains the backstop for a provider with no live
|
|
276
|
+
* adapter.
|
|
277
|
+
* @param options {object}
|
|
278
|
+
* @param options.provider {string} the registration's `provider`
|
|
279
|
+
* @param [options.enabledProviders] {string[]} the configured allowlist, or
|
|
280
|
+
* `undefined` for permissive
|
|
281
|
+
* @returns {void}
|
|
282
|
+
*/
|
|
283
|
+
export function assertProviderAllowed({
|
|
284
|
+
provider,
|
|
285
|
+
enabledProviders
|
|
286
|
+
}: {
|
|
287
|
+
provider: string
|
|
288
|
+
enabledProviders?: string[]
|
|
289
|
+
}): void {
|
|
290
|
+
if (enabledProviders === undefined) {
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
if (!enabledProviders.includes(provider)) {
|
|
294
|
+
throw new UnsupportedBackendError({
|
|
295
|
+
backendId: provider,
|
|
296
|
+
detail: `Backend provider '${provider}' is not enabled on this server.`,
|
|
297
|
+
pointer: '#/provider'
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Asserts a registered-backend id is a single URL-safe path segment (it becomes
|
|
304
|
+
* both a URL segment and a `.backend.<id>.json` filename). A backend id is a
|
|
305
|
+
* body field, so a failure is `invalid-request-body` (400) pointing at `#/id`
|
|
306
|
+
* rather than a URL-param id error.
|
|
307
|
+
* @param id {string}
|
|
308
|
+
* @param options {object}
|
|
309
|
+
* @param [options.requestName] {string} request name for the 400 error title
|
|
310
|
+
* @returns {void}
|
|
311
|
+
*/
|
|
312
|
+
export function assertValidBackendId(
|
|
313
|
+
id: string,
|
|
314
|
+
{ requestName }: { requestName?: string } = {}
|
|
315
|
+
): void {
|
|
316
|
+
if (!isUrlSafeSegment(id)) {
|
|
317
|
+
throw new InvalidRequestBodyError({
|
|
318
|
+
requestName,
|
|
319
|
+
detail: 'Backend "id" must be a single, URL-safe path segment.',
|
|
320
|
+
pointer: '#/id'
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Assembles the full `StoredBackendRecord` to persist from a validated
|
|
327
|
+
* `BackendRegistration`: the descriptor fields, `managedBy: 'external'`, default
|
|
328
|
+
* `storageMode` / `features`, and the full (secret-bearing) connection stamped
|
|
329
|
+
* with `status: 'registered'` and the registration timestamp. The record is
|
|
330
|
+
* inert until the live provider adapter (future work) connects it.
|
|
331
|
+
* @param registration {BackendRegistration}
|
|
332
|
+
* @returns {StoredBackendRecord}
|
|
333
|
+
*/
|
|
334
|
+
export function buildBackendRecord(
|
|
335
|
+
registration: BackendRegistration
|
|
336
|
+
): StoredBackendRecord {
|
|
337
|
+
const { id, name, provider, storageMode, features, connection } = registration
|
|
338
|
+
return {
|
|
339
|
+
id,
|
|
340
|
+
...(name !== undefined && { name }),
|
|
341
|
+
managedBy: 'external',
|
|
342
|
+
provider,
|
|
343
|
+
storageMode: storageMode ?? ['document', 'blob'],
|
|
344
|
+
features: features ?? [],
|
|
345
|
+
connection: {
|
|
346
|
+
...connection,
|
|
347
|
+
status: 'registered',
|
|
348
|
+
connectedAt: new Date().toISOString()
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|