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,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth onRequest hooks: `requireAuthHeaders` (401 if Authorization /
|
|
3
|
+
* Capability-Invocation are missing), `requireAuthHeadersOrPublicRead` (same,
|
|
4
|
+
* but safe methods pass through unauthenticated), and `parseAuthHeaders`
|
|
5
|
+
* (parses the auth headers into `request.zcap`). Every route group in
|
|
6
|
+
* routes.js installs `requireAuthHeadersOrPublicRead` then `parseAuthHeaders`.
|
|
7
|
+
*/
|
|
8
|
+
import type { FastifyReply, FastifyRequest } from 'fastify'
|
|
9
|
+
import {
|
|
10
|
+
AuthHeaderParseError,
|
|
11
|
+
MissingAuthError,
|
|
12
|
+
MissingKeyIdError
|
|
13
|
+
} from './errors.js'
|
|
14
|
+
import { parseSignatureHeader } from '@interop/http-signature-header'
|
|
15
|
+
import { isValidController } from './lib/validateDid.js'
|
|
16
|
+
import type { IDID, ParsedZcap } from './types.js'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Adds a request.zcap property, which contains the three parsed auth-related
|
|
20
|
+
* request headers (`Authorization:`, `Capability-Invocation:`, and `Digest:`)
|
|
21
|
+
*
|
|
22
|
+
* Example Authorization header:
|
|
23
|
+
* Signature keyId="did:key:z6Mkud27oH7SyTr495b67UgZ6tFmA72egaxyte23ygpUfEvD#z6Mkud27oH7SyTr495b67UgZ6tFmA72egaxyte23ygpUfEvD",
|
|
24
|
+
* headers="(key-id) (created) (expires) (request-target) host capability-invocation content-type digest",
|
|
25
|
+
* signature="6GoRQ+rW69wBhNyERkafAXEZXZArezHvGRNUWC0HNI4Ss1xAiiMHdayS5aA2R6hLuYRNw6h9J9eCmQVMuHE1Bw==",
|
|
26
|
+
* created="1758150502",expires="1758151102"
|
|
27
|
+
*
|
|
28
|
+
* Example Capability-Invocation header:
|
|
29
|
+
* zcap id="urn:zcap:root:http%3A%2F%2Flocalhost%3A42957%2Fspaces%2F",action="POST"
|
|
30
|
+
*
|
|
31
|
+
* Example Digest header:
|
|
32
|
+
* mh=uEiCPO-qYr-z0GYV5F75-N1l8Rhjv4xIkKZsnbTZeZ7emSA
|
|
33
|
+
*
|
|
34
|
+
* Example request.zcap object value from above headers:
|
|
35
|
+
* {
|
|
36
|
+
* keyId: 'did:key:z6Mkud27oH7SyTr495b67UgZ6tFmA72egaxyte23ygpUfEvD#z6Mkud27oH7SyTr495b67UgZ6tFmA72egaxyte23ygpUfEvD',
|
|
37
|
+
* headers: '(key-id) (created) (expires) (request-target) host capability-invocation content-type digest',
|
|
38
|
+
* signature: '0SXR+S2EiipdjzS9ahNyM9Zdfe2r3fZHJFTaYwK6HGq4dbWkJkiZkvg8DcvXUGxolF9AVc6LEtmbOfAGDAAVBg==',
|
|
39
|
+
* created: '1758151067',
|
|
40
|
+
* expires: '1758151667',
|
|
41
|
+
* invocation: 'zcap id="urn:zcap:root:http%3A%2F%2Flocalhost%3A40767%2Fspaces%2F",action="POST"',
|
|
42
|
+
* digest: 'mh=uEiCPO-qYr-z0GYV5F75-N1l8Rhjv4xIkKZsnbTZeZ7emSA'
|
|
43
|
+
* }
|
|
44
|
+
*
|
|
45
|
+
* @param request {import('fastify').FastifyRequest}
|
|
46
|
+
* @param reply {import('fastify').FastifyReply}
|
|
47
|
+
* @returns {Promise<void>}
|
|
48
|
+
*/
|
|
49
|
+
export async function parseAuthHeaders(
|
|
50
|
+
request: FastifyRequest,
|
|
51
|
+
_reply: FastifyReply
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
const { headers } = request
|
|
54
|
+
|
|
55
|
+
// A provisioning-authorized request (e.g. a valid onboarding token) carries a
|
|
56
|
+
// Bearer token, not an HTTP Signature -- there is no zcap to parse.
|
|
57
|
+
if (request.provisioningAuthorized) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// No Authorization header presented (e.g. an anonymous read that a fallback
|
|
62
|
+
// policy may authorize). Leave `request.zcap` unset; the handler decides.
|
|
63
|
+
if (!headers.authorization) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let keyId: string | undefined
|
|
68
|
+
try {
|
|
69
|
+
// { keyId, headers, signature, created, expires }
|
|
70
|
+
const { params } = parseSignatureHeader(headers.authorization ?? '')
|
|
71
|
+
keyId = params.keyId
|
|
72
|
+
request.zcap = {
|
|
73
|
+
...params,
|
|
74
|
+
invocation: headers['capability-invocation'],
|
|
75
|
+
digest: headers.digest
|
|
76
|
+
} as ParsedZcap
|
|
77
|
+
} catch (err) {
|
|
78
|
+
throw new AuthHeaderParseError({ cause: err as Error })
|
|
79
|
+
}
|
|
80
|
+
// Ensure keyId was parsed from the Authorization header
|
|
81
|
+
if (!keyId) {
|
|
82
|
+
throw new MissingKeyIdError()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The DID of the party that signed this request's capability invocation, taken
|
|
88
|
+
* from the parsed `keyId` with its verification-method fragment stripped
|
|
89
|
+
* (`did:key:z6Mk...#z6Mk...` to `did:key:z6Mk...`). Under a delegated
|
|
90
|
+
* capability this is the delegatee -- whoever actually invoked -- not the Space
|
|
91
|
+
* controller.
|
|
92
|
+
*
|
|
93
|
+
* Resolves to `undefined` when the request carries no parsed invocation (an
|
|
94
|
+
* anonymous read of a public Resource, or a provisioning-token request), or
|
|
95
|
+
* when the stripped `keyId` is not a syntactically valid `did:key`. Callers
|
|
96
|
+
* recording it as server-managed provenance therefore treat it as optional,
|
|
97
|
+
* and never persist a value that failed to narrow to a DID.
|
|
98
|
+
*
|
|
99
|
+
* @param request {import('fastify').FastifyRequest}
|
|
100
|
+
* @returns {IDID | undefined}
|
|
101
|
+
*/
|
|
102
|
+
export function invokerDid(request: FastifyRequest): IDID | undefined {
|
|
103
|
+
const did = request.zcap?.keyId.split('#')[0]
|
|
104
|
+
return isValidController(did) ? did : undefined
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* onRequest hook that enforces presence of the auth-related headers. Throws
|
|
109
|
+
* MissingAuthError (401) when `Authorization` or `Capability-Invocation` is
|
|
110
|
+
* absent. For route groups where every operation, reads included, is
|
|
111
|
+
* privileged: the WebKMS `/kms` group uses it (every webkms route is
|
|
112
|
+
* zcap-invoked; the protocol has no public reads). No WAS group qualifies --
|
|
113
|
+
* even the SpacesRepository group lets anonymous List Spaces reads through,
|
|
114
|
+
* answered with the spec's empty-items 200.
|
|
115
|
+
* @param request {import('fastify').FastifyRequest}
|
|
116
|
+
* @param reply {import('fastify').FastifyReply}
|
|
117
|
+
* @returns {Promise<void>}
|
|
118
|
+
*/
|
|
119
|
+
export async function requireAuthHeaders(
|
|
120
|
+
request: FastifyRequest,
|
|
121
|
+
_reply: FastifyReply
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
// A provisioning-authorized request (onboarding token) carries no capability
|
|
124
|
+
// invocation and a Bearer -- not Signature -- Authorization header.
|
|
125
|
+
if (request.provisioningAuthorized) {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
const { headers } = request
|
|
129
|
+
if (!(headers.authorization && headers['capability-invocation'])) {
|
|
130
|
+
throw new MissingAuthError()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Like `requireAuthHeaders`, but lets safe (read) methods through without auth
|
|
136
|
+
* so the handler can decide what an anonymous read sees: a fallback
|
|
137
|
+
* access-control policy (e.g. a world-readable Resource), or for List Spaces
|
|
138
|
+
* the empty-items 200. The handler still denies anonymous reads that no policy
|
|
139
|
+
* authorizes. Unsafe methods (writes) still require auth. Used by every route
|
|
140
|
+
* group.
|
|
141
|
+
* @param request {import('fastify').FastifyRequest}
|
|
142
|
+
* @param reply {import('fastify').FastifyReply}
|
|
143
|
+
* @returns {Promise<void>}
|
|
144
|
+
*/
|
|
145
|
+
export async function requireAuthHeadersOrPublicRead(
|
|
146
|
+
request: FastifyRequest,
|
|
147
|
+
_reply: FastifyReply
|
|
148
|
+
): Promise<void> {
|
|
149
|
+
// A provisioning-authorized request (onboarding token) carries no capability
|
|
150
|
+
// invocation and a Bearer -- not Signature -- Authorization header.
|
|
151
|
+
if (request.provisioningAuthorized) {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
const { headers, method } = request
|
|
155
|
+
if (headers.authorization && headers['capability-invocation']) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
// Safe methods may proceed unauthenticated; authorize() decides via policy.
|
|
159
|
+
if (method === 'GET' || method === 'HEAD') {
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
throw new MissingAuthError()
|
|
163
|
+
}
|
package/src/authorize.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-request authorization decision. Replaces the inline `handleZcapVerify`
|
|
3
|
+
* call in read handlers with a two-step decision: first try the capability
|
|
4
|
+
* invocation (if auth headers are present); if that does not grant access, fall
|
|
5
|
+
* back to the target's effective access-control policy (see policy.ts). Throws
|
|
6
|
+
* the same errors as `handleZcapVerify` when neither path authorizes the
|
|
7
|
+
* request. The decision is permissive: a policy can only broaden access, never
|
|
8
|
+
* deny a valid capability holder.
|
|
9
|
+
*/
|
|
10
|
+
import type { FastifyRequest } from 'fastify'
|
|
11
|
+
import { handleZcapVerify } from './zcap.js'
|
|
12
|
+
import {
|
|
13
|
+
resolveEffectivePolicy,
|
|
14
|
+
policyGrants,
|
|
15
|
+
type AccessAction
|
|
16
|
+
} from './policy.js'
|
|
17
|
+
import { UnauthorizedError } from './errors.js'
|
|
18
|
+
import type { IDID } from './types.js'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Authorizes a request against a target, by capability invocation first and the
|
|
22
|
+
* effective access-control policy second.
|
|
23
|
+
*
|
|
24
|
+
* @param options {object}
|
|
25
|
+
* @param options.request {FastifyRequest} the request (supplies url, method,
|
|
26
|
+
* headers, logger, and `request.server` for serverUrl + storage)
|
|
27
|
+
* @param options.allowedTarget {string} the capability's expected
|
|
28
|
+
* invocationTarget (full URL, including host and port)
|
|
29
|
+
* @param options.spaceId {string}
|
|
30
|
+
* @param [options.collectionId] {string}
|
|
31
|
+
* @param [options.resourceId] {string}
|
|
32
|
+
* @param options.spaceController {IDID} the did:key that controls the Space
|
|
33
|
+
* @param [options.requestName] {string} human-readable request name, used in
|
|
34
|
+
* error titles
|
|
35
|
+
* @param [options.allowTargetQuery] {boolean} tolerate query parameters that
|
|
36
|
+
* extend `allowedTarget` on the capability-invocation path (see `verifyZcap`)
|
|
37
|
+
* @param [options.attenuatedRootTarget] {string} ancestor target (the Space
|
|
38
|
+
* URL) whose root capability is also accepted as the root of a delegated
|
|
39
|
+
* chain attenuating down to the request URL (see `verifyZcap`)
|
|
40
|
+
* @returns {Promise<void>} resolves when authorized; throws otherwise
|
|
41
|
+
*/
|
|
42
|
+
export async function authorize({
|
|
43
|
+
request,
|
|
44
|
+
allowedTarget,
|
|
45
|
+
spaceId,
|
|
46
|
+
collectionId,
|
|
47
|
+
resourceId,
|
|
48
|
+
spaceController,
|
|
49
|
+
requestName = '',
|
|
50
|
+
allowTargetQuery = false,
|
|
51
|
+
attenuatedRootTarget
|
|
52
|
+
}: {
|
|
53
|
+
request: FastifyRequest
|
|
54
|
+
allowedTarget: string
|
|
55
|
+
spaceId: string
|
|
56
|
+
collectionId?: string
|
|
57
|
+
resourceId?: string
|
|
58
|
+
spaceController: IDID
|
|
59
|
+
requestName?: string
|
|
60
|
+
allowTargetQuery?: boolean
|
|
61
|
+
attenuatedRootTarget?: string
|
|
62
|
+
}): Promise<void> {
|
|
63
|
+
const { url, method, headers } = request
|
|
64
|
+
const { serverUrl, storage } = request.server
|
|
65
|
+
const action: AccessAction =
|
|
66
|
+
method === 'GET' || method === 'HEAD' ? 'read' : 'write'
|
|
67
|
+
|
|
68
|
+
// 1. If the caller presented a capability invocation, try it first. Success
|
|
69
|
+
// authorizes the request; failure -- a revoked capability included -- falls
|
|
70
|
+
// through to the policy check (we hold the error to re-throw if the policy
|
|
71
|
+
// does not grant access either). Revoking a capability therefore withdraws
|
|
72
|
+
// only what the capability granted: access a policy already grants everyone
|
|
73
|
+
// (a public-readable Space) survives, which is the permissive-policy model.
|
|
74
|
+
let zcapError: Error | undefined
|
|
75
|
+
if (headers.authorization && headers['capability-invocation']) {
|
|
76
|
+
try {
|
|
77
|
+
await handleZcapVerify({
|
|
78
|
+
url,
|
|
79
|
+
allowedTarget,
|
|
80
|
+
allowedAction: method,
|
|
81
|
+
method,
|
|
82
|
+
headers,
|
|
83
|
+
serverUrl,
|
|
84
|
+
spaceController,
|
|
85
|
+
requestName,
|
|
86
|
+
logger: request.log,
|
|
87
|
+
allowTargetQuery,
|
|
88
|
+
attenuatedRootTarget,
|
|
89
|
+
revocation: { storage, scope: { spaceId } }
|
|
90
|
+
})
|
|
91
|
+
return
|
|
92
|
+
} catch (err) {
|
|
93
|
+
zcapError = err as Error
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 2. Fall back to the target's effective access-control policy.
|
|
98
|
+
const policy = await resolveEffectivePolicy({
|
|
99
|
+
storage,
|
|
100
|
+
spaceId,
|
|
101
|
+
collectionId,
|
|
102
|
+
resourceId
|
|
103
|
+
})
|
|
104
|
+
if (policyGrants({ policy, action, logger: request.log })) {
|
|
105
|
+
// Log granted public-access decisions so they are auditable (an anonymous
|
|
106
|
+
// or otherwise-unauthorized read allowed purely by a policy).
|
|
107
|
+
request.log.info(
|
|
108
|
+
{ spaceId, collectionId, resourceId, action, policyType: policy?.type },
|
|
109
|
+
'Access granted by access-control policy.'
|
|
110
|
+
)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Neither a capability nor a policy authorizes this request. Re-throw the
|
|
115
|
+
// capability error when there was one, else a generic 404 (no leak).
|
|
116
|
+
throw zcapError ?? new UnauthorizedError({ requestName })
|
|
117
|
+
}
|