@interop/http-signature-zcap-verify 12.0.0

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/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2018-2021, Digital Bazaar, Inc.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of http-signature-zcap-verify nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # http-signature-zcap-verify _(@interop/http-signature-zcap-verify)_
2
+
3
+ A library for verifying Authorization Capability (ZCAP) invocations via HTTP
4
+ signatures.
5
+
6
+ It is the verifier counterpart to
7
+ [`@interop/http-signature-zcap-invoke`](https://github.com/interop-alliance/http-signature-zcap-invoke),
8
+ which signs the request. On the server, `verifyCapabilityInvocation` parses the
9
+ HTTP signature headers, verifies the signature, dereferences the invoked
10
+ capability, and validates the capability delegation chain.
11
+
12
+ ## Install
13
+
14
+ - Browsers, Node.js, and React Native are supported.
15
+ - Written in TypeScript; ships ESM with type declarations.
16
+
17
+ To install from NPM:
18
+
19
+ ```
20
+ pnpm add @interop/http-signature-zcap-verify
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ `verifyCapabilityInvocation` needs two functions you supply:
26
+
27
+ - **`documentLoader`** -- a JSON-LD document loader that can resolve the
28
+ controller document, the invoked root capability, and any contexts in the
29
+ delegation chain.
30
+ - **`getVerifier`** -- an async function that dereferences a key id to a
31
+ signature verifier and its verification method document.
32
+
33
+ ### Setting up the document loader and verifier
34
+
35
+ ```ts
36
+ import { securityLoader } from '@interop/security-document-loader'
37
+ import { Ed25519VerificationKey } from '@interop/ed25519-verification-key'
38
+
39
+ // Preloaded with the zcap, DID, Multikey, ed25519, and data-integrity contexts.
40
+ const documentLoader = securityLoader().build()
41
+
42
+ async function getVerifier({ keyId, documentLoader }) {
43
+ const { document } = await documentLoader(keyId)
44
+ // `fromKeyDocument` rebuilds the key from the dereferenced verification
45
+ // method and throws if the key has been revoked. It accepts a verification
46
+ // method using either the Multikey or the ed25519-2020 suite context.
47
+ const key = await Ed25519VerificationKey.fromKeyDocument({ document })
48
+ return { verifier: key.verifier(), verificationMethod: document }
49
+ }
50
+ ```
51
+
52
+ > **Note:** the controller document returned by your `documentLoader` should use
53
+ > the DID v1 context (`https://www.w3.org/ns/did/v1`) and list the invoking key
54
+ > under `capabilityInvocation`. Controllers that use a non-DID context force a
55
+ > framing step against the legacy `https://w3id.org/security/v2` context, which
56
+ > the loader above does not bundle.
57
+
58
+ ### Verifying an incoming request
59
+
60
+ ```ts
61
+ import { verifyCapabilityInvocation } from '@interop/http-signature-zcap-verify'
62
+ import { Ed25519Signature2020 } from '@interop/ed25519-signature'
63
+
64
+ // In an HTTP handler, `req.method`, `req.url`, and `req.headers` come from the
65
+ // incoming request. `expectedTarget` is the absolute URL the capability must
66
+ // apply to; `expectedRootCapability` is the root zcap ID you authorized.
67
+ const result = await verifyCapabilityInvocation({
68
+ url: req.url,
69
+ method: req.method,
70
+ headers: req.headers,
71
+ // verify-mode suite for the delegation chain; no signer needed
72
+ suite: new Ed25519Signature2020(),
73
+ getVerifier,
74
+ documentLoader,
75
+ expectedHost: 'api.example.com',
76
+ expectedAction: 'read',
77
+ expectedTarget: 'https://api.example.com/documents/123',
78
+ expectedRootCapability:
79
+ 'urn:zcap:root:' +
80
+ encodeURIComponent('https://api.example.com/documents/123')
81
+ })
82
+
83
+ if (!result.verified) {
84
+ // `result.error` describes why verification failed (bad signature,
85
+ // unexpected host, expired capability, unauthorized key, etc.)
86
+ throw result.error
87
+ }
88
+
89
+ // On success, `result` also includes the invoked `capability`,
90
+ // `capabilityAction`, the `controller`/`invoker`, the `verificationMethod`,
91
+ // and the `dereferencedChain`.
92
+ console.log('invoked by', result.controller)
93
+ ```
94
+
95
+ ### Result
96
+
97
+ `verifyCapabilityInvocation` resolves to an object and does not throw for
98
+ verification failures -- check `result.verified`:
99
+
100
+ - On failure: `{ verified: false, error }`.
101
+ - On success: `{ verified: true, capability, capabilityAction, controller,
102
+ invoker, verificationMethod, dereferencedChain }`.
103
+
104
+ (It does throw a `TypeError` for programmer errors, such as omitting
105
+ `getVerifier`.)
106
+
107
+ ### Key options
108
+
109
+ | Option | Description |
110
+ | -------------------------- | ------------------------------------------------------------------------- |
111
+ | `url`, `method`, `headers` | The incoming request. |
112
+ | `getVerifier` | Async function returning `{ verifier, verificationMethod }` for a key id. |
113
+ | `documentLoader` | JSON-LD loader for the controller, root capability, and contexts. |
114
+ | `expectedHost` | The expected `Host` header (string or array of allowed hosts). |
115
+ | `expectedAction` | The capability action the request must invoke (e.g. `'read'`). |
116
+ | `expectedTarget` | The absolute URL the capability must apply to. |
117
+ | `expectedRootCapability` | The authorized root capability ID (string or array). |
118
+ | `suite` | The signature suite(s) used to verify the delegation chain. |
119
+ | `allowTargetAttenuation` | Allow hierarchical RESTful target attenuation (default `false`). |
120
+ | `maxClockSkew` | Allowed clock skew in seconds (default `300`). |
121
+ | `now` | A UNIX timestamp (seconds) or `Date` to verify against (default: now). |
122
+
123
+ See the JSDoc on `verifyCapabilityInvocation` for the full list, including
124
+ `additionalHeaders`, `beforeValidatePurpose`, `inspectCapabilityChain`,
125
+ `maxChainLength`, and `maxDelegationTtl`.
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Standard (RFC 4648) base64 with padding. Used to decode the `signature`
3
+ * parameter from the `Authorization` header, which the signer encodes with
4
+ * padding (`Buffer.toString('base64')` in Node, `btoa`/`toBase64()` in the
5
+ * browser).
6
+ */
7
+ export declare const base64decode: import("@scure/base").BytesCoder;
8
+ /**
9
+ * RFC 4648 url-safe base64 without padding. Used to decode the gzipped
10
+ * capability from the `capability-invocation` header, which the signer encodes
11
+ * unpadded (`Buffer.toString('base64url')` in Node, `toBase64({ omitPadding:
12
+ * true })` in the browser).
13
+ */
14
+ export declare const base64url: import("@scure/base").BytesCoder;
15
+ //# sourceMappingURL=baseX.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseX.d.ts","sourceRoot":"","sources":["../src/baseX.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,eAAO,MAAM,YAAY,kCAAS,CAAA;AAElC;;;;;GAKG;AACH,eAAO,MAAM,SAAS,kCAAiB,CAAA"}
package/dist/baseX.js ADDED
@@ -0,0 +1,19 @@
1
+ /*!
2
+ * Copyright (c) 2021-2026 Digital Bazaar, Inc. and Interop Alliance. All rights reserved.
3
+ */
4
+ import { base64, base64urlnopad } from '@scure/base';
5
+ /**
6
+ * Standard (RFC 4648) base64 with padding. Used to decode the `signature`
7
+ * parameter from the `Authorization` header, which the signer encodes with
8
+ * padding (`Buffer.toString('base64')` in Node, `btoa`/`toBase64()` in the
9
+ * browser).
10
+ */
11
+ export const base64decode = base64;
12
+ /**
13
+ * RFC 4648 url-safe base64 without padding. Used to decode the gzipped
14
+ * capability from the `capability-invocation` header, which the signer encodes
15
+ * unpadded (`Buffer.toString('base64url')` in Node, `toBase64({ omitPadding:
16
+ * true })` in the browser).
17
+ */
18
+ export const base64url = base64urlnopad;
19
+ //# sourceMappingURL=baseX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseX.js","sourceRoot":"","sources":["../src/baseX.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEpD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAA;AAElC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,cAAc,CAAA"}
@@ -0,0 +1,126 @@
1
+ import type { InspectCapabilityChain } from '@interop/zcap';
2
+ /**
3
+ * Verifies an HTTP signature using public key material.
4
+ */
5
+ export interface Verifier {
6
+ verify(payload: {
7
+ data: Uint8Array;
8
+ signature: Uint8Array;
9
+ }): Promise<boolean>;
10
+ }
11
+ /**
12
+ * A verification method document, typically dereferenced from the key id.
13
+ */
14
+ export interface VerificationMethod {
15
+ id?: string;
16
+ controller?: string;
17
+ [key: string]: unknown;
18
+ }
19
+ /**
20
+ * An async function that dereferences a key id and returns a verifier and the
21
+ * verification method document for that key.
22
+ */
23
+ export type GetVerifier = (options: {
24
+ keyId: string;
25
+ documentLoader: DocumentLoader;
26
+ }) => Promise<{
27
+ verifier: Verifier;
28
+ verificationMethod: VerificationMethod;
29
+ }>;
30
+ /**
31
+ * A JSON-LD document loader.
32
+ */
33
+ export type DocumentLoader = (url: string) => Promise<{
34
+ contextUrl?: string | null;
35
+ documentUrl?: string;
36
+ document: unknown;
37
+ }>;
38
+ /**
39
+ * @param options {object} - Options to use.
40
+ * @param options.url {string} - The url of the request.
41
+ * @param options.method {string} - The HTTP request method.
42
+ * @param options.headers {object} - The headers from the request.
43
+ * @param options.getVerifier {GetVerifier} - An async function to
44
+ * call to get a verifier and verification method for the key ID.
45
+ * @param options.documentLoader {DocumentLoader} - A jsonld document loader; it
46
+ * must be able to load the root zcap and any contexts used in the zcap
47
+ * delegation chain.
48
+ * @param options.expectedHost {string|string[]} - The expected host of the
49
+ * request.
50
+ * @param options.expectedAction {string} - The expected action of the zcap.
51
+ * @param options.expectedRootCapability {string|string[]} - The expected root
52
+ * capability of the zcap.
53
+ * @param options.expectedTarget {string} - The expected target of the zcap.
54
+ * @param options.suite {object} - The jsigs signature suite(s) for verifying
55
+ * the capability delegation chain.
56
+ * @param [options.allowTargetAttenuation=false] {boolean} - Allow the
57
+ * invocationTarget of a delegation chain to be increasingly restrictive
58
+ * based on a hierarchical RESTful URL structure.
59
+ * @param [options.additionalHeaders=[]] {string[]} - Additional headers
60
+ * to verify.
61
+ * @param [options.beforeValidatePurpose] {Function} - A function that is
62
+ * called prior to validating the proof purpose and is passed the purpose
63
+ * instance, proof meta data, and capability information.
64
+ * @param [options.inspectCapabilityChain] {Function} - A function that can
65
+ * inspect a capability chain.
66
+ * @param [options.maxChainLength] {number} - The maximum length of the
67
+ * capability delegation chain.
68
+ * @param [options.maxDelegationTtl] {number} - The maximum milliseconds to
69
+ * live for a delegated zcap as measured by the time difference between
70
+ * `expires` and `created` on the delegation proof.
71
+ * @param [options.maxClockSkew=300] {number} - A maximum number of seconds
72
+ * that clocks may be skewed when checking capability expiration date-times
73
+ * against `date`, when comparing invocation proof creation time against
74
+ * delegation proof creation time, and when comparing the capability
75
+ * invocation expiration time against `now`.
76
+ * @param [options.now=now] {number|Date} - A unix timestamp or an
77
+ * instance of Date.
78
+ */
79
+ export interface VerifyCapabilityInvocationOptions {
80
+ url: string;
81
+ method: string;
82
+ headers: Record<string, string>;
83
+ getVerifier: GetVerifier;
84
+ documentLoader: DocumentLoader;
85
+ expectedHost: string | string[];
86
+ expectedAction: string;
87
+ expectedRootCapability: string | string[];
88
+ expectedTarget: string;
89
+ suite: object;
90
+ allowTargetAttenuation?: boolean;
91
+ additionalHeaders?: string[];
92
+ beforeValidatePurpose?: (params: {
93
+ purpose: unknown;
94
+ proof: unknown;
95
+ capability: unknown;
96
+ capabilityAction: unknown;
97
+ }) => Promise<void> | void;
98
+ inspectCapabilityChain?: InspectCapabilityChain;
99
+ maxChainLength?: number;
100
+ maxClockSkew?: number;
101
+ maxDelegationTtl?: number;
102
+ now?: number | Date;
103
+ }
104
+ /**
105
+ * The result of a capability invocation verification.
106
+ */
107
+ export interface VerifyCapabilityInvocationResult {
108
+ verified: boolean;
109
+ error?: Error;
110
+ capability?: unknown;
111
+ capabilityAction?: unknown;
112
+ controller?: string;
113
+ dereferencedChain?: unknown;
114
+ invoker?: string;
115
+ verificationMethod?: VerificationMethod;
116
+ }
117
+ /**
118
+ * Verifies a zcap invocation in the form of an http-signature header.
119
+ *
120
+ * @param options {VerifyCapabilityInvocationOptions} - Options to use.
121
+ *
122
+ * @returns {Promise<VerifyCapabilityInvocationResult>} The result of the
123
+ * verification.
124
+ */
125
+ export declare function verifyCapabilityInvocation({ url, method, headers, getVerifier, documentLoader, expectedHost, expectedAction, expectedRootCapability, expectedTarget, suite, additionalHeaders, allowTargetAttenuation, beforeValidatePurpose, inspectCapabilityChain, maxChainLength, maxClockSkew, maxDelegationTtl, now }: VerifyCapabilityInvocationOptions): Promise<VerifyCapabilityInvocationResult>;
126
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,196 @@
1
+ /*!
2
+ * Copyright (c) 2021-2026 Digital Bazaar, Inc. and Interop Alliance. All rights reserved.
3
+ */
4
+ import { CapabilityInvocation, constants } from '@interop/zcap';
5
+ import { parseRequest, parseSignatureHeader } from '@interop/http-signature-header';
6
+ import { base64decode, base64url } from './baseX.js';
7
+ import pako from 'pako';
8
+ /**
9
+ * Verifies a zcap invocation in the form of an http-signature header.
10
+ *
11
+ * @param options {VerifyCapabilityInvocationOptions} - Options to use.
12
+ *
13
+ * @returns {Promise<VerifyCapabilityInvocationResult>} The result of the
14
+ * verification.
15
+ */
16
+ export async function verifyCapabilityInvocation({ url, method, headers, getVerifier, documentLoader, expectedHost, expectedAction, expectedRootCapability, expectedTarget, suite, additionalHeaders = [], allowTargetAttenuation = false, beforeValidatePurpose, inspectCapabilityChain, maxChainLength, maxClockSkew = 300, maxDelegationTtl, now = Math.floor(Date.now() / 1000) }) {
17
+ if (now instanceof Date) {
18
+ now = Math.floor(now.getTime() / 1000);
19
+ }
20
+ if (!getVerifier) {
21
+ throw new TypeError('"getVerifier" must be given to dereference the key for verifying ' +
22
+ 'the capability invocation signature.');
23
+ }
24
+ if (beforeValidatePurpose && typeof beforeValidatePurpose !== 'function') {
25
+ throw new TypeError('"beforeValidatePurpose" must be a function.');
26
+ }
27
+ // parse http header for signature
28
+ const expectedHeaders = [
29
+ '(key-id)',
30
+ '(created)',
31
+ '(expires)',
32
+ '(request-target)',
33
+ 'host',
34
+ 'capability-invocation'
35
+ ];
36
+ const reqHeaders = _lowerCaseObjectKeys(headers);
37
+ if (reqHeaders['content-type']) {
38
+ additionalHeaders.push('content-type');
39
+ additionalHeaders.push('digest');
40
+ }
41
+ expectedHeaders.push(...additionalHeaders);
42
+ let parsed;
43
+ try {
44
+ // `now` will be used to check against the expiry on the signature using
45
+ // a clock skew of 5 minutes
46
+ parsed = parseRequest({ url, method, headers }, {
47
+ headers: expectedHeaders,
48
+ clockSkew: maxClockSkew,
49
+ now
50
+ });
51
+ }
52
+ catch (error) {
53
+ return { verified: false, error: error };
54
+ }
55
+ // verify that `host` matches server host
56
+ if (!Array.isArray(expectedHost)) {
57
+ expectedHost = [expectedHost];
58
+ }
59
+ const { host } = reqHeaders;
60
+ if (!expectedHost.includes(host)) {
61
+ const error = new Error('Host header contains an unexpected host name.');
62
+ error.name = 'NotAllowedError';
63
+ error.host = host;
64
+ error.expectedHost = expectedHost;
65
+ return { verified: false, error };
66
+ }
67
+ /* Note: The order in which we run these checks can introduce side channels
68
+ that leak information (e.g., timing). However, we are not presently concerned
69
+ about leaking information about existing capabilities as such leaks do not
70
+ pose any security risk -- and any privacy correlation risk is low if the
71
+ capability identifiers are infeasible to guess. */
72
+ // get parsed parameters from HTTP header and generate signing string
73
+ const { keyId, signingString, params: { created, signature: b64Signature } } = parsed;
74
+ // verify HTTP signature
75
+ const { verifier, verificationMethod } = await getVerifier({
76
+ keyId,
77
+ documentLoader
78
+ });
79
+ const encoder = new TextEncoder();
80
+ const data = encoder.encode(signingString);
81
+ const signature = base64decode.decode(b64Signature);
82
+ const verified = await verifier.verify({ data, signature });
83
+ if (!verified) {
84
+ const error = new Error('Signature not verified.');
85
+ error.name = 'DataError';
86
+ return { verified: false, error };
87
+ }
88
+ // always dereference the invoked capability to ensure that the system can
89
+ // dereference it authoritatively (which may include ensuring that it is
90
+ // saved in an authorized list, etc.)
91
+ const invocationHeader = reqHeaders['capability-invocation'];
92
+ const parsedInvocationHeader = parseSignatureHeader(invocationHeader);
93
+ if (parsedInvocationHeader.scheme !== 'zcap') {
94
+ const error = new Error('Capability invocation scheme must be "zcap".');
95
+ error.name = 'DataError';
96
+ return { verified: false, error };
97
+ }
98
+ let capability = parsedInvocationHeader.params.id;
99
+ if (!capability) {
100
+ capability = parsedInvocationHeader.params.capability;
101
+ if (capability) {
102
+ try {
103
+ capability = JSON.parse(new TextDecoder('utf-8').decode(pako.ungzip(base64url.decode(capability))));
104
+ }
105
+ catch {
106
+ const error = new Error('Capability in Capability-Invocation header is improperly encoded.');
107
+ error.name = 'DataError';
108
+ return { verified: false, error };
109
+ }
110
+ }
111
+ if (!capability.parentCapability) {
112
+ const error = new Error('A root capability must be invoked using only its ID.');
113
+ error.name = 'DataError';
114
+ return { verified: false, error };
115
+ }
116
+ }
117
+ if (!capability) {
118
+ const error = new Error('Capability not present in Capability-Invocation header.');
119
+ error.name = 'DataError';
120
+ return { verified: false, error };
121
+ }
122
+ // check capability invocation
123
+ const purpose = new CapabilityInvocation({
124
+ allowTargetAttenuation,
125
+ // `date` is in milliseconds and `now` is in seconds, so convert
126
+ date: now * 1000,
127
+ expectedAction,
128
+ expectedRootCapability,
129
+ expectedTarget,
130
+ inspectCapabilityChain,
131
+ maxChainLength,
132
+ maxClockSkew,
133
+ maxDelegationTtl,
134
+ suite
135
+ });
136
+ // invocation target must match absolute url
137
+ let invocationTarget;
138
+ // do not simply check for a colon (`:`) to find a relative URL
139
+ // because some applications allow and pass non-conformant
140
+ // relative URLs with unencoded colons (and we accept those
141
+ // because it is common to do so despite non-conformance)
142
+ if (url.startsWith('https://') || url.startsWith('http://')) {
143
+ invocationTarget = url;
144
+ }
145
+ else {
146
+ // If encountering a relative URL, assume HTTPS
147
+ invocationTarget = `https://${headers.host}${url}`;
148
+ }
149
+ const capabilityAction = parsedInvocationHeader.params.action;
150
+ const proof = {
151
+ '@context': constants.ZCAP_CONTEXT_URL,
152
+ capability,
153
+ capabilityAction,
154
+ // use second precision for created date
155
+ created: new Date(Number(created) * 1000).toISOString().slice(0, -5) + 'Z',
156
+ invocationTarget,
157
+ verificationMethod: keyId
158
+ };
159
+ if (beforeValidatePurpose) {
160
+ await beforeValidatePurpose({
161
+ purpose,
162
+ proof,
163
+ capability,
164
+ capabilityAction
165
+ });
166
+ }
167
+ const result = await purpose.validate(proof, {
168
+ verificationMethod,
169
+ documentLoader
170
+ });
171
+ const { valid, error } = result;
172
+ // `dereferencedChain` is attached internally on success but is not part of
173
+ // the public `ProofValidateResult` type.
174
+ const { dereferencedChain } = result;
175
+ if (!valid) {
176
+ return { verified: false, error };
177
+ }
178
+ const controller = verificationMethod.controller || verificationMethod.id;
179
+ return {
180
+ capability,
181
+ capabilityAction,
182
+ controller,
183
+ dereferencedChain,
184
+ invoker: controller,
185
+ verificationMethod,
186
+ verified: true
187
+ };
188
+ }
189
+ function _lowerCaseObjectKeys(obj) {
190
+ const newObject = {};
191
+ for (const [key, value] of Object.entries(obj)) {
192
+ newObject[key.toLowerCase()] = value;
193
+ }
194
+ return newObject;
195
+ }
196
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "@interop/http-signature-zcap-verify",
3
+ "description": "A library for invoking Authorization Capabilities via HTTP signatures",
4
+ "version": "12.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "pnpm run clear && tsc",
8
+ "clear": "rimraf dist/*",
9
+ "dev": "vite",
10
+ "fix": "eslint --fix src test && pnpm run format",
11
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
12
+ "lint": "eslint src test",
13
+ "prepare": "pnpm run build",
14
+ "rebuild": "pnpm run clear && pnpm run build",
15
+ "test": "pnpm run lint && pnpm run test-node && pnpm run test-browser",
16
+ "test-browser": "playwright test",
17
+ "test-node": "vitest run",
18
+ "test-coverage": "vitest run --coverage"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "react-native": "./dist/index.js",
29
+ "import": "./dist/index.js"
30
+ }
31
+ },
32
+ "module": "dist/index.js",
33
+ "browser": "dist/index.js",
34
+ "types": "dist/index.d.ts",
35
+ "sideEffects": false,
36
+ "dependencies": {
37
+ "@interop/http-signature-header": "^5.0.2",
38
+ "@interop/zcap": "^10.1.0",
39
+ "@scure/base": "^2.2.0",
40
+ "pako": "^2.0.4"
41
+ },
42
+ "devDependencies": {
43
+ "@eslint/js": "^10.0.1",
44
+ "@interop/ed25519-signature": "^7.0.0",
45
+ "@interop/ed25519-verification-key": "^7.0.1",
46
+ "@interop/http-signature-zcap-invoke": "^6.2.0",
47
+ "@interop/security-document-loader": "^9.2.0",
48
+ "@playwright/test": "^1.60.0",
49
+ "@types/node": "^25.9.1",
50
+ "@vitest/coverage-v8": "^4.1.7",
51
+ "eslint": "^10.4.0",
52
+ "eslint-config-prettier": "^10.1.8",
53
+ "globals": "^17.6.0",
54
+ "prettier": "^3.8.3",
55
+ "rimraf": "^6.1.3",
56
+ "typescript": "^5.5.0",
57
+ "typescript-eslint": "^8.59.4",
58
+ "vite": "^8.0.14",
59
+ "vitest": "^4.1.7"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
64
+ "keywords": [
65
+ "authorization",
66
+ "capability",
67
+ "authorization capability",
68
+ "object capability",
69
+ "ocap-ld",
70
+ "http signature",
71
+ "http signatures",
72
+ "zcap",
73
+ "zcaps"
74
+ ],
75
+ "packageManager": "pnpm@11.3.0",
76
+ "engines": {
77
+ "node": ">=24.0"
78
+ },
79
+ "author": {
80
+ "name": "Interop Alliance",
81
+ "url": "https://github.com/interop-alliance/"
82
+ },
83
+ "license": "BSD-3-Clause",
84
+ "repository": {
85
+ "type": "git",
86
+ "url": "git+https://github.com/interop-alliance/http-signature-zcap-verify.git"
87
+ },
88
+ "homepage": "https://github.com/interop-alliance/http-signature-zcap-verify",
89
+ "bugs": "https://github.com/interop-alliance/http-signature-zcap-verify/issues"
90
+ }