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/README.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# WAS Teaching Server (_was-teaching-server_)
|
|
2
|
+
|
|
3
|
+
[](https://github.com/interop-alliance/was-teaching-server/actions?query=workflow%3A%22CI%22)
|
|
4
|
+
|
|
5
|
+
> A basic
|
|
6
|
+
> [Wallet Attached Storage](https://digitalcredentials.github.io/wallet-attached-storage-spec/)
|
|
7
|
+
> Server used to demonstrate the specification
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Background](#background)
|
|
12
|
+
- [Install](#install)
|
|
13
|
+
- [Usage](#usage)
|
|
14
|
+
- [Security](#security)
|
|
15
|
+
- [License](#license)
|
|
16
|
+
|
|
17
|
+
## Background
|
|
18
|
+
|
|
19
|
+
See:
|
|
20
|
+
|
|
21
|
+
- [Wallet Attached Storage Specification](https://digitalcredentials.github.io/wallet-attached-storage-spec/)
|
|
22
|
+
|
|
23
|
+
### Supported Features
|
|
24
|
+
|
|
25
|
+
Authorization: Uses `ZcapClient` from `@interop/ezcap`, see the
|
|
26
|
+
[zCap Developer Guide](https://github.com/interop-alliance/zcap-developer-guide)
|
|
27
|
+
for more details.
|
|
28
|
+
|
|
29
|
+
#### Spaces API
|
|
30
|
+
|
|
31
|
+
- [Create Space](https://digitalcredentials.github.io/wallet-attached-storage-spec/#http-api-post-spaces)
|
|
32
|
+
(`POST /spaces/`)
|
|
33
|
+
- [Get Space by Id](https://digitalcredentials.github.io/wallet-attached-storage-spec/#read-space-operation)
|
|
34
|
+
(`GET /space/:spaceId`)
|
|
35
|
+
- [Update Space (or Create Space by Id)](https://digitalcredentials.github.io/wallet-attached-storage-spec/#update-space-operation)
|
|
36
|
+
(`PUT /space/:spaceId`)
|
|
37
|
+
- [Delete Space by Id](https://digitalcredentials.github.io/wallet-attached-storage-spec/#delete-space-operation)
|
|
38
|
+
(`DELETE /space/:spaceId`)
|
|
39
|
+
|
|
40
|
+
#### Collections API
|
|
41
|
+
|
|
42
|
+
- [Create Collection](https://digitalcredentials.github.io/wallet-attached-storage-spec/#create-collection-add-collection-to-a-space-operation)
|
|
43
|
+
(`POST /space/:spaceId/`)
|
|
44
|
+
- [Update Collection (or Create Collection by Id)](https://digitalcredentials.github.io/wallet-attached-storage-spec/#update-or-create-by-id-collection-operation)
|
|
45
|
+
(`PUT /space/:spaceId/:collectionId`)
|
|
46
|
+
- [Get a Collection Description object](https://digitalcredentials.github.io/wallet-attached-storage-spec/#get-collection-description-operation)
|
|
47
|
+
(`GET /space/:spaceId/:collectionId` - no trailing slash)
|
|
48
|
+
- [List Resources in a Collection](https://digitalcredentials.github.io/wallet-attached-storage-spec/#get-collection-operation)
|
|
49
|
+
(`GET /space/:spaceId/:collectionId/` - with trailing slash)
|
|
50
|
+
- [Delete Collection by Id](https://digitalcredentials.github.io/wallet-attached-storage-spec/#delete-collection-operation)
|
|
51
|
+
(`DELETE /space/:spaceId/:collectionId`)
|
|
52
|
+
|
|
53
|
+
#### Resources API
|
|
54
|
+
|
|
55
|
+
- [Create Resource](https://digitalcredentials.github.io/wallet-attached-storage-spec/#create-resource-add-resource-to-collection-operation)
|
|
56
|
+
(`POST /space/:spaceId/:collectionId/`)
|
|
57
|
+
- both JSON objects and binary blobs
|
|
58
|
+
- [Get Resource by Id](https://digitalcredentials.github.io/wallet-attached-storage-spec/#read-resource-operation)
|
|
59
|
+
(`GET /space/:spaceId/:collectionId/:resourceId`)
|
|
60
|
+
- [Head Resource by Id](https://digitalcredentials.github.io/wallet-attached-storage-spec/#content-types-and-representations)
|
|
61
|
+
(`HEAD /space/:spaceId/:collectionId/:resourceId`)
|
|
62
|
+
- same headers as `GET` with no body; `Content-Type`/`Content-Length` from the
|
|
63
|
+
Resource's Metadata
|
|
64
|
+
- [Update Resource (or Create Resource by Id)](https://digitalcredentials.github.io/wallet-attached-storage-spec/#update-or-create-by-id-resource-operation)
|
|
65
|
+
(`PUT /space/:spaceId/:collectionId/:resourceId`)
|
|
66
|
+
- [Delete Resource by Id](https://digitalcredentials.github.io/wallet-attached-storage-spec/#delete-resource-operation)
|
|
67
|
+
(`DELETE /space/:spaceId/:collectionId/:resourceId`)
|
|
68
|
+
|
|
69
|
+
**Uploading binary resources (and large files).** Send the file as the **raw
|
|
70
|
+
request body** with its own `Content-Type` (e.g. `image/png`) -- not wrapped in
|
|
71
|
+
a form. The server streams a raw-body upload straight to storage (enforcing the
|
|
72
|
+
upload cap as it goes), so this is the path for large files. The write must
|
|
73
|
+
still be zCap-signed, and because the signature covers the `Digest` header, the
|
|
74
|
+
client hashes the whole body before sending (a plain browser `<form>` cannot do
|
|
75
|
+
this -- uploads are made programmatically by a signing client). The OPTIONAL
|
|
76
|
+
`multipart/form-data` upload is supported as a convenience for the HTML-form
|
|
77
|
+
workflow: it MUST carry exactly one file part, and the server buffers that part
|
|
78
|
+
in memory bounded by `MAX_UPLOAD_BYTES`, so it is best suited to smaller
|
|
79
|
+
uploads.
|
|
80
|
+
|
|
81
|
+
#### Access Control Policy (public read)
|
|
82
|
+
|
|
83
|
+
By default every operation requires a capability invocation. A **policy**
|
|
84
|
+
auxiliary resource can override this to make a Space, Collection, or Resource
|
|
85
|
+
world-readable — the common "create public link" use case (e.g. a wallet sharing
|
|
86
|
+
a Verifiable Credential).
|
|
87
|
+
|
|
88
|
+
- CRUD the policy at any level (controller-only):
|
|
89
|
+
- `GET|PUT|DELETE /space/:spaceId/policy`
|
|
90
|
+
- `GET|PUT|DELETE /space/:spaceId/:collectionId/policy`
|
|
91
|
+
- `GET|PUT|DELETE /space/:spaceId/:collectionId/:resourceId/policy`
|
|
92
|
+
- Discover a policy via the `linkset` property on the Space/Collection
|
|
93
|
+
Description, or `GET /space/:spaceId[/:collectionId]/linkset`
|
|
94
|
+
(`application/linkset+json`, RFC9264).
|
|
95
|
+
|
|
96
|
+
How a read is authorized: the server tries the capability invocation first; if
|
|
97
|
+
none is presented, or it does not grant access, it falls back to the **effective
|
|
98
|
+
policy** (resolved most-specific-first: Resource > Collection > Space). Policies
|
|
99
|
+
are **permissive-only** — they can only broaden access beyond capabilities,
|
|
100
|
+
never deny a valid capability holder. A denied read returns `404` (so existence
|
|
101
|
+
is not leaked); writes always require a capability.
|
|
102
|
+
|
|
103
|
+
The policy document is a small, `type`-discriminated, extensible shape. v1
|
|
104
|
+
recognizes one type (any other `type` is treated as "grants nothing", so it
|
|
105
|
+
falls through to the capability-only decision):
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{ "type": "PublicCanRead" }
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Set it, for example, on a `public-credentials` Collection so anyone can
|
|
112
|
+
`GET /space/:spaceId/public-credentials/:resourceId` without authorization. This
|
|
113
|
+
minimal canned-policy approach (cf. S3 `public-read`, `chmod o+r`) covers the
|
|
114
|
+
dominant public-read case; a richer policy language (e.g. Cedar) is left as a
|
|
115
|
+
future, separate tier.
|
|
116
|
+
|
|
117
|
+
## Install
|
|
118
|
+
|
|
119
|
+
Requires Node.js 24.x and [`pnpm`](https://pnpm.io/).
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
pnpm install
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This server is written in TypeScript. In development it runs directly via
|
|
126
|
+
[`tsx`](https://github.com/privatenumber/tsx) (no build step); for production it
|
|
127
|
+
is compiled with `tsc` to `dist/`.
|
|
128
|
+
|
|
129
|
+
## Usage
|
|
130
|
+
|
|
131
|
+
### Running in Development
|
|
132
|
+
|
|
133
|
+
`tsx` runs the TypeScript sources directly and watches for changes (views are
|
|
134
|
+
served from `src/views`, so no asset copy is needed):
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
SERVER_URL='http://localhost:3002' PORT=3002 pnpm dev
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Building and Starting the Server
|
|
141
|
+
|
|
142
|
+
`pnpm build` compiles `src/` to `dist/` and copies `src/views` > `dist/views`,
|
|
143
|
+
then `pnpm start` runs the compiled output:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
pnpm build
|
|
147
|
+
SERVER_URL='http://localhost:3002' PORT=3002 pnpm start
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Using as a Library
|
|
151
|
+
|
|
152
|
+
The WAS protocol surface is also exported as a registerable Fastify plugin,
|
|
153
|
+
`fastifyWas`, so a downstream server can compose it with its own persistence
|
|
154
|
+
backend and security plugins. See
|
|
155
|
+
[Consuming the Server as a Library](docs/consuming-server-as-library.md).
|
|
156
|
+
|
|
157
|
+
### Environment Variables
|
|
158
|
+
|
|
159
|
+
The whole env surface below is read and validated once at startup
|
|
160
|
+
(`loadConfigFromEnv` in `src/config.default.ts`): a missing `SERVER_URL` or any
|
|
161
|
+
malformed value fails startup immediately with the offending variable named,
|
|
162
|
+
rather than surfacing later as broken ZCap matching.
|
|
163
|
+
|
|
164
|
+
| Variable | Default | Description |
|
|
165
|
+
| --------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
166
|
+
| `SERVER_URL` | (none) | **Required.** This server's base URL; used to build and match ZCap `invocationTarget` URLs (host and port must match the client's exactly). Must be an absolute `http:`/`https:` URL with no path, query, or fragment -- deploying under a sub-path is not supported. |
|
|
167
|
+
| `PORT` | `3002` | TCP port to listen on (an integer between 1 and 65535). |
|
|
168
|
+
| `DATABASE_URL` | (unset = filesystem) | Selects the **PostgreSQL storage backend**: a `postgres://` connection string (e.g. `postgres://was:was@localhost:5433/was`). When set, all WAS + WebKMS data is stored in Postgres (schema migrations are applied automatically at startup); unset, the server uses the default filesystem backend rooted at `data/`. See [Storage Backends](#storage-backends). |
|
|
169
|
+
| `STORAGE_LIMIT_PER_SPACE` | (unset = unlimited, warned) | Per-Space storage quota in **bytes** (spec "Quotas"). When set, writes that would push a Space over this limit are rejected with `quota-exceeded` (507). Unset means each Space is unlimited, and startup logs a warning; set `unlimited` to acknowledge the choice explicitly and silence it. |
|
|
170
|
+
| `MAX_UPLOAD_BYTES` | `67108864` (64 MiB) | Per-upload size cap in **bytes** (spec "Quotas", the backend's `maxUploadBytes` constraint), default-on. A single upload exceeding it is rejected with `payload-too-large` (413) -- distinct from the cumulative `STORAGE_LIMIT_PER_SPACE`. Set `unlimited` to remove the cap (filesystem backend only: the Postgres backend buffers each upload in memory as a single `bytea`, so it rejects `unlimited` at startup). |
|
|
171
|
+
| `MAX_SPACES_PER_CONTROLLER` | `100` | Max number of Spaces a single controller DID may create (a default-on count quota). A create beyond the limit is rejected with `quota-exceeded` (507); overwriting an existing Space never trips it. Set `unlimited` for no cap. Hard (transactional) on the Postgres backend, soft under concurrency on the filesystem backend -- like the byte quota. |
|
|
172
|
+
| `MAX_COLLECTIONS_PER_SPACE` | `100` | Max number of Collections one Space may hold (a default-on count quota, also enforced during a tar import). A create beyond the limit is rejected with `quota-exceeded` (507); overwriting an existing Collection's description never trips it. Set `unlimited` for no cap. |
|
|
173
|
+
| `MAX_RESOURCES_PER_SPACE` | `10000` | Max number of live Resources one Space may hold across all its Collections (a default-on count quota, also enforced during a tar import; tombstoned Resources do not count). A create beyond the limit is rejected with `quota-exceeded` (507); overwriting an existing Resource never trips it, and a delete frees a slot. Set `unlimited` for no cap. |
|
|
174
|
+
| `KMS_RECORD_KEK` | (unset = disabled) | At-rest encryption key for WebKMS key records: a single AES-256 key-encryption key (KEK) in base58btc Multikey form (`secretKeyMultibase`, header `0xa2 0x01`) -- the single-KEK alias. When set, the secret fields of newly generated `/kms` key records are envelope-encrypted under it before they reach storage; existing plaintext records stay readable. Unset means key records are stored **plaintext** (the teaching default). Mutually exclusive with `KMS_RECORD_KEKS`. |
|
|
175
|
+
| `KMS_RECORD_KEKS` | (unset = disabled) | A comma-separated list of AES-256 KEKs (same Multikey form as `KMS_RECORD_KEK`), for registering more than one KEK so a rotation can add a new KEK while keeping the old one available to decrypt records already written under it. Each KEK is registered by an id derived from its material; surrounding whitespace is trimmed and empty entries ignored; a duplicate entry is a startup error. The **first entry is the current KEK** (the one that wraps new records) by default -- so a rotation is "prepend the new KEK, keep the old behind it". Use `KMS_RECORD_KEK` for the single-KEK case; setting both is a startup error. |
|
|
176
|
+
| `KMS_RECORD_CURRENT_KEK` | (unset = first entry) | Optionally overrides which registered KEK wraps **new** records: a `urn:kek:sha256:<hex>` id, a multibase KEK value (its id is derived), or the literal `none` (case-insensitive). It must name a KEK registered via `KMS_RECORD_KEK`/`KMS_RECORD_KEKS`. `none` is the **decrypt-only** posture -- existing encrypted records still read, but new key records are written **plaintext** (the wind-down path). Setting it with no KEK configured is a startup error. |
|
|
177
|
+
| `WAS_ONBOARDING_TOKEN` | (unset = disabled) | Shared-secret onboarding token gating the two open provisioning endpoints (`POST /spaces/` and `POST /kms/keystores`). When set, those two endpoints require an `Authorization: Bearer <token>` header, which then substitutes for ZCap verification on that request; every other operation still uses the normal capability-invocation path. Unset means provisioning is open -- anyone may create a Space or keystore by proving control of the `controller` DID in the request body (the teaching default). See [Provisioning gate](#provisioning-gate). |
|
|
178
|
+
|
|
179
|
+
### Provisioning gate
|
|
180
|
+
|
|
181
|
+
By default, anyone may create a Space (`POST /spaces/`) or a WebKMS keystore
|
|
182
|
+
(`POST /kms/keystores`) by proving control of the `controller` DID named in the
|
|
183
|
+
request body -- the open, teaching-server behavior. A deployment can gate those
|
|
184
|
+
two endpoints in one of two mutually-exclusive ways:
|
|
185
|
+
|
|
186
|
+
- **Onboarding token** -- set `WAS_ONBOARDING_TOKEN` (or pass `onboardingToken`
|
|
187
|
+
to the `fastifyWas` plugin / `createApp`). Provisioning then requires an
|
|
188
|
+
`Authorization: Bearer <token>` header matching the configured secret; a valid
|
|
189
|
+
token substitutes for ZCap verification on that request.
|
|
190
|
+
- **Custom policy** -- pass an `authorizeProvisioning` callback to the plugin.
|
|
191
|
+
It receives `{ request }` and returns `'verify'` (run the normal ZCap path),
|
|
192
|
+
`'grant'` (authorize the request itself, skipping ZCap verification), or
|
|
193
|
+
`'deny'` (403); it may also throw a `ProblemError` for a custom response.
|
|
194
|
+
|
|
195
|
+
Both configure the same seam; setting both at once is rejected at startup.
|
|
196
|
+
|
|
197
|
+
### Storage Backends
|
|
198
|
+
|
|
199
|
+
Two interchangeable first-party backends implement the same `StorageBackend`
|
|
200
|
+
contract (`src/types.ts`):
|
|
201
|
+
|
|
202
|
+
- **Filesystem** (default): stores everything under `data/`. Zero setup.
|
|
203
|
+
- **PostgreSQL**: selected by setting `DATABASE_URL`. Quota accounting is
|
|
204
|
+
transactional (a _hard_ per-Space limit, unlike the filesystem's documented
|
|
205
|
+
soft limit), conditional writes use row locks instead of an in-process mutex
|
|
206
|
+
(so multiple server processes can share one database), and blob uploads are
|
|
207
|
+
buffered `bytea` writes bounded by `MAX_UPLOAD_BYTES` (default 64 MiB;
|
|
208
|
+
`MAX_UPLOAD_BYTES=unlimited` is rejected at startup, since the in-memory
|
|
209
|
+
buffering makes an unbounded upload unsafe).
|
|
210
|
+
|
|
211
|
+
To run a disposable local Postgres with Podman (substitute `docker` if you
|
|
212
|
+
prefer; the commands are identical):
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# One-time: pull and start Postgres 17 with a named volume for data.
|
|
216
|
+
# Host port 5433 avoids colliding with any system Postgres on 5432.
|
|
217
|
+
podman run -d --name was-postgres \
|
|
218
|
+
-e POSTGRES_USER=was \
|
|
219
|
+
-e POSTGRES_PASSWORD=was \
|
|
220
|
+
-e POSTGRES_DB=was \
|
|
221
|
+
-p 5433:5432 \
|
|
222
|
+
-v was-pgdata:/var/lib/postgresql/data \
|
|
223
|
+
docker.io/library/postgres:17
|
|
224
|
+
|
|
225
|
+
# Verify it is accepting connections:
|
|
226
|
+
podman exec was-postgres pg_isready -U was
|
|
227
|
+
|
|
228
|
+
# Day-to-day lifecycle:
|
|
229
|
+
podman stop was-postgres
|
|
230
|
+
podman start was-postgres
|
|
231
|
+
|
|
232
|
+
# Full teardown (drops all stored data):
|
|
233
|
+
podman rm -f was-postgres && podman volume rm was-pgdata
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Then start the server against it:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
DATABASE_URL=postgres://was:was@localhost:5433/was pnpm dev
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Schema migrations are embedded (`src/backends/postgresSchema.ts`) and applied
|
|
243
|
+
idempotently at startup. To migrate existing data between backends, export each
|
|
244
|
+
Space from one server and import it into the other — both backends speak the
|
|
245
|
+
same tar archive dialect (`POST /space/:id/export` / `.../import`).
|
|
246
|
+
|
|
247
|
+
### Running Tests
|
|
248
|
+
|
|
249
|
+
The full gate (lint + build + Vitest integration tests):
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
pnpm test
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Just the Vitest integration suite under `test/`:
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
pnpm test-node
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The Postgres backend tests are **opt-in**: they are skipped unless
|
|
262
|
+
`WAS_TEST_DATABASE_URL` points at a running (disposable) Postgres. With the
|
|
263
|
+
Podman container above running:
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
pnpm test:pg
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Each suite isolates itself in a throwaway `was_test_<hex>` schema, dropped on
|
|
270
|
+
teardown, so parallel test workers never collide and the container stays
|
|
271
|
+
reusable.
|
|
272
|
+
|
|
273
|
+
### Conformance Tests
|
|
274
|
+
|
|
275
|
+
The `conformance/` suite runs against any external WAS server. The server's
|
|
276
|
+
`SERVER_URL` and the test's `TEST_SERVER_URL` **must be byte-for-byte
|
|
277
|
+
identical** (including host and port) — ZCap `invocationTarget` URLs embed the
|
|
278
|
+
full host:port, so even `localhost` vs `127.0.0.1` or a port mismatch will make
|
|
279
|
+
delegated-access tests 404.
|
|
280
|
+
|
|
281
|
+
For a local run, `pnpm conformance:local` does the whole dance for you: it spins
|
|
282
|
+
up the server on a fixed local URL, waits until it answers, runs the suite with
|
|
283
|
+
a matching `TEST_SERVER_URL`, and tears the server down afterward (even if the
|
|
284
|
+
suite fails) — so the two URLs can't drift out of sync.
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# One-shot local run (recommended). Override the port with PORT=... if 3002 is
|
|
288
|
+
# taken; the server and test URLs are both derived from it, so they stay in sync.
|
|
289
|
+
pnpm conformance:local
|
|
290
|
+
|
|
291
|
+
# Against an already-running or external server. Start it with a matching
|
|
292
|
+
# SERVER_URL, then in another shell:
|
|
293
|
+
TEST_SERVER_URL=https://was.example.com pnpm conformance
|
|
294
|
+
|
|
295
|
+
# With an onboarding token, for servers that require one for POST /spaces/:
|
|
296
|
+
TEST_SERVER_URL=https://was.example.com TEST_ONBOARDING_TOKEN=abc123 pnpm conformance
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Security
|
|
300
|
+
|
|
301
|
+
This is an experimental research server.
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
[GNU AFFERO GENERAL PUBLIC LICENSE v3](LICENSE)
|