@metalabel/dfos-client 0.52.1 → 0.54.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/README.md +7 -1
- package/dist/api-auth.d.ts +57 -8
- package/dist/api-auth.js +64 -11
- package/dist/index.d.ts +6 -4
- package/dist/index.js +77 -20
- package/dist/{memory-B5WMnEUQ.d.ts → memory-aYqSas8m.d.ts} +1 -1
- package/dist/siwd.d.ts +20 -3
- package/dist/siwd.js +17 -0
- package/dist/store/index.d.ts +2 -2
- package/dist/{types-BbIEz7BN.d.ts → types-DClhQw33.d.ts} +6 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -158,7 +158,11 @@ await verifyApiRequest(client, {
|
|
|
158
158
|
});
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
It throws `ApiRequestVerifyError`, carrying `reason` (`invalid` / `unverifiable` / `config`), `phase`, and the recommended `status` — branch on those, never on message text.
|
|
161
|
+
It throws `ApiRequestVerifyError`, carrying `reason` (`invalid` / `replayed` / `uncovered` / `unverifiable` / `config`), `phase`, and the recommended `status` — branch on those, never on message text.
|
|
162
|
+
|
|
163
|
+
The two 403s say different things. `invalid` means the credential does not hold: a broken chain, a revocation, a public audience, an audience that is not the signer. `uncovered` means it holds and does not reach this route's resource and action. A route offering optional authentication serves its anonymous projection on `uncovered`, because a credential only ever adds, and refuses `invalid` outright.
|
|
164
|
+
|
|
165
|
+
**Space-scoped grants, and per-request uniqueness.** A route that serves one space passes `resource: 'api:<host>/spaces/<id>'`, resolving the id by its own routing; a grant naming the bare host covers it by ancestor coverage, and a grant naming one space covers only that space. The host half must match `host`, so a mismatch is a deployment error (500) rather than a verdict about the caller. A route that gates writes passes `requireJti: true` and records the returned `jti` under the presenter DID until the proof expires; the second presentation of the same value is a replay, which `replayedProof` classifies as a 409. On the signing side `createApiAuthFetch` attaches a fresh `jti` to every write by default — pass `jti: 'always'` or `jti: 'never'` to change that.
|
|
162
166
|
|
|
163
167
|
`verifyApiIdentityRequest` is the same verifier for the envelope's credential-less sibling, the [identity proof](https://protocol.dfos.com/integrations#the-identity-proof) — it establishes only which DID is asking, leaving what that DID may do to the resource's own policy.
|
|
164
168
|
|
|
@@ -179,6 +183,8 @@ import {
|
|
|
179
183
|
|
|
180
184
|
Sign In With DFOS. The three verbs above are the relying-party login kit, in the order a login uses them: `createSiwdLoginRequest` mints the challenge and builds the `/authorize` URL to redirect to, `readSiwdCallback` parses what comes back, and `verifySiwd` verifies it — mint → redirect, read → verify. The `expect` object `createSiwdLoginRequest` returns (nonce, domain, and the DID when the challenge is bound to one) is what `verifySiwd` checks against, so the relying party MUST persist it across the redirect: a verifier that takes its expectation from the callback has implemented the check and none of the protection. See [`examples/siwd-demo`](../../examples/siwd-demo) for the reference consumer.
|
|
181
185
|
|
|
186
|
+
`spaces` on the login request locks consent to a named set of places — `'all'`, or up to 31 distinct 31-character space ids. A host honors it only when `scope` names a space-level action, and refuses the request whole otherwise. Absent, the user chooses the places at the consent screen. Either way the returned credential's `att` is the answer: consent may narrow the set, so read it rather than assuming the ask was honored whole.
|
|
187
|
+
|
|
182
188
|
The `nonce`/`consumeNonce` pair on the expectation (supply exactly one) maps one field each to the spec's two replay disciplines — which discipline a given scope obliges, and why, is [INTEGRATIONS § Replay prevention](https://protocol.dfos.com/integrations#replay-prevention)'s argument to make:
|
|
183
189
|
|
|
184
190
|
**`expect.nonce` — flow-bound login.** For a backend granting only a browser session (`scope=identity`), source the expected nonce from state you bound to that browser at mint time — a server-side session, or the nonce sealed under your own key in an `httpOnly` cookie — and compare:
|
package/dist/api-auth.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { ApiRequestVerifyError, DEFAULT_PROOF_SKEW_SECONDS, DEFAULT_PROOF_WINDOW_SECONDS, DFOS_AUTH_SCHEME, EMPTY_BODY_SHA256, IDENTITY_PROOF_JWS_TYP, IdentityProofPayload, MAX_BODY_BYTES, MAX_PROOF_FRESHNESS_SPAN_SECONDS, MAX_REQUEST_PROOF_SIZE, ProofExtraMembers, REQUEST_PROOF_JWS_TYP, RequestProofFailurePhase, RequestProofFailureReason, RequestProofPayload, SignApiIdentityRequestInput, SignApiRequestInput, apiIdentitySigningInput, apiRequestSigningInput, assertProofVerifierConfig, buildApiAuthHeaders, buildApiIdentityHeaders, parseDfosAuthorization, sha256BodyHash, signApiIdentityRequest, signApiRequest } from '@metalabel/dfos-protocol/credentials';
|
|
2
|
-
import { a as Client } from './types-
|
|
1
|
+
export { ApiRequestVerifyError, DEFAULT_PROOF_SKEW_SECONDS, DEFAULT_PROOF_WINDOW_SECONDS, DFOS_AUTH_SCHEME, EMPTY_BODY_SHA256, IDENTITY_PROOF_JWS_TYP, IdentityProofPayload, MAX_BODY_BYTES, MAX_JTI_BYTES, MAX_PROOF_FRESHNESS_SPAN_SECONDS, MAX_REQUEST_PROOF_SIZE, ProofExtraMembers, REQUEST_PROOF_JWS_TYP, RequestProofFailurePhase, RequestProofFailureReason, RequestProofPayload, SignApiIdentityRequestInput, SignApiRequestInput, apiIdentitySigningInput, apiRequestSigningInput, apiResourceCovers, assertProofVerifierConfig, buildApiAuthHeaders, buildApiIdentityHeaders, generateJti, parseApiResource, parseDfosAuthorization, replayedProof, sha256BodyHash, signApiIdentityRequest, signApiRequest, uncoveredProof } from '@metalabel/dfos-protocol/credentials';
|
|
2
|
+
import { a as Client } from './types-DClhQw33.js';
|
|
3
3
|
import '@metalabel/dfos-protocol/chain';
|
|
4
4
|
import '@metalabel/dfos-web-relay/peer-client';
|
|
5
5
|
|
|
6
|
-
/** The
|
|
6
|
+
/** The registry's account-level default action. */
|
|
7
7
|
declare const DEFAULT_API_ACTION = "read:profile";
|
|
8
8
|
interface CreateApiAuthFetchOptions {
|
|
9
9
|
/**
|
|
@@ -20,6 +20,13 @@ interface CreateApiAuthFetchOptions {
|
|
|
20
20
|
sign: (message: Uint8Array) => Promise<Uint8Array>;
|
|
21
21
|
/** The underlying transport. Default `globalThis.fetch`. */
|
|
22
22
|
fetch?: typeof fetch;
|
|
23
|
+
/**
|
|
24
|
+
* When to attach a freshly minted `jti`. Default `'writes'` — every method
|
|
25
|
+
* except GET, HEAD, and OPTIONS — which is what a write-gating host requires.
|
|
26
|
+
* `'always'` covers a host that requires it on reads too; `'never'` is for a
|
|
27
|
+
* host that has no replay cache and would only be paying for the bytes.
|
|
28
|
+
*/
|
|
29
|
+
jti?: 'writes' | 'always' | 'never';
|
|
23
30
|
}
|
|
24
31
|
/**
|
|
25
32
|
* A signing `fetch`. Hand it to any API client with a fetch seam and every
|
|
@@ -70,8 +77,8 @@ interface VerifyApiRequestInput {
|
|
|
70
77
|
* a verifier that compared the proof's `host` against a request header would
|
|
71
78
|
* have no host binding at all. Include the port when it is not 443.
|
|
72
79
|
*
|
|
73
|
-
* It is also the
|
|
74
|
-
*
|
|
80
|
+
* It is also the host half of the `api:` resource this verifier requires, so
|
|
81
|
+
* the request binding and the grant name the same origin.
|
|
75
82
|
*/
|
|
76
83
|
host: string;
|
|
77
84
|
/** The received request's method. */
|
|
@@ -92,10 +99,26 @@ interface VerifyApiRequestInput {
|
|
|
92
99
|
maxBodyBytes?: number;
|
|
93
100
|
/** The action token this route requires. Default `read:profile`. */
|
|
94
101
|
action?: string;
|
|
102
|
+
/**
|
|
103
|
+
* The `api:` resource this route demands, which the leaf's attenuation must
|
|
104
|
+
* cover. Default `api:<host>` — the account-level form. A SPACE-ADDRESSED
|
|
105
|
+
* route passes `api:<host>/spaces/<id>`, resolving the id by its own routing;
|
|
106
|
+
* a bare-host grant satisfies it by ancestor coverage.
|
|
107
|
+
*
|
|
108
|
+
* Its host half MUST byte-equal `host`, so the binding and the grant name the
|
|
109
|
+
* same origin. Anything else is a deployment error (500), never a verdict.
|
|
110
|
+
*/
|
|
111
|
+
resource?: string;
|
|
95
112
|
/** Acceptance window `W`, seconds. Default 60. `W + S` MUST NOT exceed 300. */
|
|
96
113
|
windowSeconds?: number;
|
|
97
114
|
/** Clock-skew allowance `S`, seconds. Default 60. `W + S` MUST NOT exceed 300. */
|
|
98
115
|
skewSeconds?: number;
|
|
116
|
+
/**
|
|
117
|
+
* Require the registered `jti` member (401 when absent). A deployment gating
|
|
118
|
+
* WRITES sets it on every write-shaped route and records the returned value in
|
|
119
|
+
* its own replay cache, keyed with the presenter DID.
|
|
120
|
+
*/
|
|
121
|
+
requireJti?: boolean;
|
|
99
122
|
/**
|
|
100
123
|
* Accept a presenter resolution whose tip could not be verified (cache-only or
|
|
101
124
|
* empty-delta-against-cache). Default FALSE: key resolution is CURRENT-STATE,
|
|
@@ -110,12 +133,18 @@ interface VerifiedRequestProof {
|
|
|
110
133
|
subjectDID: string;
|
|
111
134
|
/** The authority the grant and the binding both name. */
|
|
112
135
|
host: string;
|
|
136
|
+
/** The `api:` resource the route demanded and the leaf was found to cover. */
|
|
137
|
+
resource: string;
|
|
113
138
|
/** The action token the leaf's attenuation was found to cover. */
|
|
114
139
|
action: string;
|
|
115
140
|
/** The proof's issued-at, unix seconds. */
|
|
116
141
|
iat: number;
|
|
117
142
|
/** The leaf credential's CID, re-derived and equal to the proof's member. */
|
|
118
143
|
credentialCID: string;
|
|
144
|
+
/** The registered `jti`, when the proof carried one — the replay cache's value. */
|
|
145
|
+
jti?: string;
|
|
146
|
+
/** The signing key's roles, when the presenter's state named them. */
|
|
147
|
+
keyRoles?: readonly ('auth' | 'assert' | 'controller')[];
|
|
119
148
|
}
|
|
120
149
|
/**
|
|
121
150
|
* Verify a credential-gated request — INTEGRATIONS.md, Verification algorithm's
|
|
@@ -131,7 +160,13 @@ interface VerifiedRequestProof {
|
|
|
131
160
|
*
|
|
132
161
|
* Throws `ApiRequestVerifyError`; branch on `reason`/`phase`/`status`, never on
|
|
133
162
|
* message text. `status` is the recommended HTTP code (401 proof-invalid, 403
|
|
134
|
-
* credential-invalid, 503 unverifiable, 500 config).
|
|
163
|
+
* credential-invalid or uncovered, 503 unverifiable, 500 config).
|
|
164
|
+
*
|
|
165
|
+
* The two 403s are different answers. `invalid` means the credential itself does
|
|
166
|
+
* not hold — a broken chain, a revocation, a public audience, an audience that
|
|
167
|
+
* is not the signer. `uncovered` means it holds and does not reach this route's
|
|
168
|
+
* resource and action; a route offering optional authentication serves its
|
|
169
|
+
* anonymous projection on that verdict rather than refusing.
|
|
135
170
|
*
|
|
136
171
|
* Missing issuer dependencies and an unavailable revocation source are
|
|
137
172
|
* unverifiable (503). The default checker throws when no relay answers; an
|
|
@@ -168,6 +203,12 @@ interface VerifyApiIdentityRequestInput {
|
|
|
168
203
|
windowSeconds?: number;
|
|
169
204
|
/** Clock-skew allowance `S`, seconds. Default 60. `W + S` MUST NOT exceed 300. */
|
|
170
205
|
skewSeconds?: number;
|
|
206
|
+
/**
|
|
207
|
+
* Require the registered `jti` member (401 when absent). A deployment gating
|
|
208
|
+
* WRITES with a bare identity sets it on every write-shaped route — the signer
|
|
209
|
+
* is the principal, and the replay discipline is the same one.
|
|
210
|
+
*/
|
|
211
|
+
requireJti?: boolean;
|
|
171
212
|
/**
|
|
172
213
|
* Accept a presenter resolution whose tip could not be verified (cache-only or
|
|
173
214
|
* empty-delta-against-cache). Default FALSE: key resolution is CURRENT-STATE,
|
|
@@ -193,10 +234,18 @@ interface VerifiedIdentityProof {
|
|
|
193
234
|
iat: number;
|
|
194
235
|
/**
|
|
195
236
|
* The DECODED payload, unknown members included — where a caller reads an
|
|
196
|
-
*
|
|
197
|
-
* covers it; the canonical member set stays closed.
|
|
237
|
+
* unregistered additive member the envelope verifier ignored. The signature
|
|
238
|
+
* already covers it; the canonical member set stays closed.
|
|
198
239
|
*/
|
|
199
240
|
rawPayload: Record<string, unknown>;
|
|
241
|
+
/** The registered `jti`, when the proof carried one — the replay cache's value. */
|
|
242
|
+
jti?: string;
|
|
243
|
+
/**
|
|
244
|
+
* The signing key's roles, when the presenter's state named them. A deployment
|
|
245
|
+
* gating writes with this artifact should refuse a key whose only effective
|
|
246
|
+
* role is `controller`.
|
|
247
|
+
*/
|
|
248
|
+
keyRoles?: readonly ('auth' | 'assert' | 'controller')[];
|
|
200
249
|
}
|
|
201
250
|
/**
|
|
202
251
|
* Verify an identity-proven request — INTEGRATIONS.md, Verification algorithm's
|
package/dist/api-auth.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
apiIdentitySigningInput,
|
|
5
5
|
apiRequestSigningInput,
|
|
6
6
|
ApiRequestVerifyError,
|
|
7
|
+
apiResourceCovers,
|
|
7
8
|
assertProofVerifierConfig,
|
|
8
9
|
buildApiAuthHeaders,
|
|
9
10
|
buildApiIdentityHeaders,
|
|
@@ -13,17 +14,22 @@ import {
|
|
|
13
14
|
DEFAULT_PROOF_WINDOW_SECONDS,
|
|
14
15
|
DFOS_AUTH_SCHEME,
|
|
15
16
|
EMPTY_BODY_SHA256,
|
|
17
|
+
generateJti,
|
|
16
18
|
IDENTITY_PROOF_JWS_TYP,
|
|
17
19
|
matchesResource,
|
|
18
20
|
MAX_BODY_BYTES,
|
|
19
21
|
MAX_CREDENTIAL_SIZE,
|
|
22
|
+
MAX_JTI_BYTES,
|
|
20
23
|
MAX_PROOF_FRESHNESS_SPAN_SECONDS,
|
|
21
24
|
MAX_REQUEST_PROOF_SIZE,
|
|
25
|
+
parseApiResource,
|
|
22
26
|
parseDfosAuthorization,
|
|
27
|
+
replayedProof,
|
|
23
28
|
REQUEST_PROOF_JWS_TYP,
|
|
24
29
|
sha256BodyHash,
|
|
25
30
|
signApiIdentityRequest,
|
|
26
31
|
signApiRequest,
|
|
32
|
+
uncoveredProof,
|
|
27
33
|
verifyDelegationChain,
|
|
28
34
|
verifyDFOSCredential,
|
|
29
35
|
verifyIdentityProofEnvelope,
|
|
@@ -31,6 +37,7 @@ import {
|
|
|
31
37
|
} from "@metalabel/dfos-protocol/credentials";
|
|
32
38
|
import { decodeJwsUnsafe } from "@metalabel/dfos-protocol/crypto";
|
|
33
39
|
var DEFAULT_API_ACTION = "read:profile";
|
|
40
|
+
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
34
41
|
var MAX_DELEGATION_DEPTH = 16;
|
|
35
42
|
var credentialCIDFromHeader = (credential) => {
|
|
36
43
|
const decoded = decodeJwsUnsafe(credential);
|
|
@@ -45,6 +52,7 @@ var LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::
|
|
|
45
52
|
var createApiAuthFetch = (options) => {
|
|
46
53
|
const credentialCID = credentialCIDFromHeader(options.credential);
|
|
47
54
|
const send = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
55
|
+
const jtiMode = options.jti ?? "writes";
|
|
48
56
|
return async (input, init) => {
|
|
49
57
|
const request = init === void 0 && input instanceof Request ? input : new Request(input, init);
|
|
50
58
|
const url = new URL(request.url);
|
|
@@ -53,6 +61,7 @@ var createApiAuthFetch = (options) => {
|
|
|
53
61
|
`refusing to sign a ${url.protocol}// request to ${url.host}: api: surfaces are HTTPS surfaces, and a proof sent in the clear replays for its whole freshness window (plaintext is allowed only to localhost, 127.0.0.1, and [::1])`
|
|
54
62
|
);
|
|
55
63
|
}
|
|
64
|
+
const attachJti = jtiMode === "always" || jtiMode === "writes" && !SAFE_METHODS.has(request.method);
|
|
56
65
|
const { proof } = await signApiRequest({
|
|
57
66
|
method: request.method,
|
|
58
67
|
// `host`, never `hostname`: the authority carries the port when there is
|
|
@@ -69,7 +78,8 @@ var createApiAuthFetch = (options) => {
|
|
|
69
78
|
body: new Uint8Array(await request.clone().arrayBuffer()),
|
|
70
79
|
credentialCID,
|
|
71
80
|
kid: options.kid,
|
|
72
|
-
sign: options.sign
|
|
81
|
+
sign: options.sign,
|
|
82
|
+
...attachJti ? { jti: generateJti() } : {}
|
|
73
83
|
});
|
|
74
84
|
const headers = new Headers(request.headers);
|
|
75
85
|
for (const [name, value] of Object.entries(
|
|
@@ -94,10 +104,24 @@ var clientPresenterResolver = (client, allowStale) => async (did) => {
|
|
|
94
104
|
);
|
|
95
105
|
}
|
|
96
106
|
const state = resolved.value;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
107
|
+
const byId = /* @__PURE__ */ new Map();
|
|
108
|
+
for (const [role, keys] of [
|
|
109
|
+
["auth", state.authKeys],
|
|
110
|
+
["assert", state.assertKeys],
|
|
111
|
+
["controller", state.controllerKeys]
|
|
112
|
+
]) {
|
|
113
|
+
for (const key of keys) {
|
|
114
|
+
const seen = byId.get(key.id);
|
|
115
|
+
if (seen) seen.roles.push(role);
|
|
116
|
+
else
|
|
117
|
+
byId.set(key.id, {
|
|
118
|
+
id: key.id,
|
|
119
|
+
publicKeyMultibase: key.publicKeyMultibase,
|
|
120
|
+
roles: [role]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { isDeleted: state.isDeleted, keys: [...byId.values()] };
|
|
101
125
|
};
|
|
102
126
|
var discoverChainRoot = (leafToken) => {
|
|
103
127
|
let token = leafToken;
|
|
@@ -118,12 +142,24 @@ var verifyApiRequest = async (client, input) => {
|
|
|
118
142
|
if (action.split(",").every((token) => token.trim() === "")) {
|
|
119
143
|
throw misconfigured("required action must name a non-empty token");
|
|
120
144
|
}
|
|
145
|
+
const resource = input.resource ?? `api:${input.host}`;
|
|
146
|
+
const parsedResource = parseApiResource(resource);
|
|
147
|
+
if (!parsedResource) {
|
|
148
|
+
throw misconfigured(
|
|
149
|
+
`required resource ${resource} is not a well-formed api: resource (api:<host> or api:<host>/spaces/<id>)`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (parsedResource.host !== input.host) {
|
|
153
|
+
throw misconfigured(
|
|
154
|
+
`required resource ${resource} names a different authority than the verifier's host ${input.host}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
121
157
|
if (input.credential.length > MAX_CREDENTIAL_SIZE) {
|
|
122
158
|
throw invalidProof(
|
|
123
159
|
`credential exceeds max size: ${input.credential.length} > ${MAX_CREDENTIAL_SIZE}`
|
|
124
160
|
);
|
|
125
161
|
}
|
|
126
|
-
const { payload, presenterDID, now } = await verifyRequestProofEnvelope(
|
|
162
|
+
const { payload, presenterDID, now, keyRoles } = await verifyRequestProofEnvelope(
|
|
127
163
|
input,
|
|
128
164
|
clientPresenterResolver(client, input.allowStale === true)
|
|
129
165
|
);
|
|
@@ -167,23 +203,34 @@ var verifyApiRequest = async (client, input) => {
|
|
|
167
203
|
if (leaf.aud !== presenterDID) {
|
|
168
204
|
throw invalidCredential("credential audience does not match the request proof signing key");
|
|
169
205
|
}
|
|
170
|
-
if (!await matchesResource(leaf.att,
|
|
171
|
-
throw
|
|
206
|
+
if (!await matchesResource(leaf.att, resource, action)) {
|
|
207
|
+
throw uncoveredProof(`credential does not cover ${action} on ${resource}`);
|
|
172
208
|
}
|
|
173
209
|
return {
|
|
174
210
|
subjectDID: rootDID,
|
|
175
211
|
host: input.host,
|
|
212
|
+
resource,
|
|
176
213
|
action,
|
|
177
214
|
iat: payload.iat,
|
|
178
|
-
credentialCID: leaf.credentialCID
|
|
215
|
+
credentialCID: leaf.credentialCID,
|
|
216
|
+
...payload.jti !== void 0 ? { jti: payload.jti } : {},
|
|
217
|
+
...keyRoles !== void 0 ? { keyRoles } : {}
|
|
179
218
|
};
|
|
180
219
|
};
|
|
181
220
|
var verifyApiIdentityRequest = async (client, input) => {
|
|
182
|
-
const { payload, rawPayload, presenterDID, kid } = await verifyIdentityProofEnvelope(
|
|
221
|
+
const { payload, rawPayload, presenterDID, kid, keyRoles } = await verifyIdentityProofEnvelope(
|
|
183
222
|
input,
|
|
184
223
|
clientPresenterResolver(client, input.allowStale === true)
|
|
185
224
|
);
|
|
186
|
-
return {
|
|
225
|
+
return {
|
|
226
|
+
presenterDID,
|
|
227
|
+
kid,
|
|
228
|
+
host: input.host,
|
|
229
|
+
iat: payload.iat,
|
|
230
|
+
rawPayload,
|
|
231
|
+
...payload.jti !== void 0 ? { jti: payload.jti } : {},
|
|
232
|
+
...keyRoles !== void 0 ? { keyRoles } : {}
|
|
233
|
+
};
|
|
187
234
|
};
|
|
188
235
|
export {
|
|
189
236
|
ApiRequestVerifyError,
|
|
@@ -194,19 +241,25 @@ export {
|
|
|
194
241
|
EMPTY_BODY_SHA256,
|
|
195
242
|
IDENTITY_PROOF_JWS_TYP,
|
|
196
243
|
MAX_BODY_BYTES,
|
|
244
|
+
MAX_JTI_BYTES,
|
|
197
245
|
MAX_PROOF_FRESHNESS_SPAN_SECONDS,
|
|
198
246
|
MAX_REQUEST_PROOF_SIZE,
|
|
199
247
|
REQUEST_PROOF_JWS_TYP,
|
|
200
248
|
apiIdentitySigningInput,
|
|
201
249
|
apiRequestSigningInput,
|
|
250
|
+
apiResourceCovers,
|
|
202
251
|
assertProofVerifierConfig,
|
|
203
252
|
buildApiAuthHeaders,
|
|
204
253
|
buildApiIdentityHeaders,
|
|
205
254
|
createApiAuthFetch,
|
|
255
|
+
generateJti,
|
|
256
|
+
parseApiResource,
|
|
206
257
|
parseDfosAuthorization,
|
|
258
|
+
replayedProof,
|
|
207
259
|
sha256BodyHash,
|
|
208
260
|
signApiIdentityRequest,
|
|
209
261
|
signApiRequest,
|
|
262
|
+
uncoveredProof,
|
|
210
263
|
verifyApiIdentityRequest,
|
|
211
264
|
verifyApiRequest
|
|
212
265
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { C as ClientConfig, a as Client, b as Callbacks, R as RevChecker } from './types-
|
|
2
|
-
export { c as CallOptions, D as DocumentBlob, E as EffectiveIdentity, d as EverProvedIdentity, G as GlobalLogOptions, e as GlobalLogPage, f as GlobalLogResult, I as IndexCapabilities, g as IndexContentPage, h as IndexContentRow, i as IndexCountersignatureRow, j as IndexCountersignaturesPage, k as IndexCredentialRow, l as IndexCredentialsPage, m as IndexIdentitiesPage, n as IndexIdentityProfile, o as IndexIdentityRow, p as IndexOrder, q as IndexRecencyOrder, L as LogOp, P as Provenance, r as RelayHealth, s as RelayResponse, t as Resolution, u as Resolved, v as ResolvedContent, w as ResolvedCredential, S as Store, T as Trust, U as UnverifiableAxis, V as VerifyResult } from './types-
|
|
3
|
-
export { m as memoryStore } from './memory-
|
|
1
|
+
import { C as ClientConfig, a as Client, b as Callbacks, R as RevChecker } from './types-DClhQw33.js';
|
|
2
|
+
export { c as CallOptions, D as DocumentBlob, E as EffectiveIdentity, d as EverProvedIdentity, G as GlobalLogOptions, e as GlobalLogPage, f as GlobalLogResult, I as IndexCapabilities, g as IndexContentPage, h as IndexContentRow, i as IndexCountersignatureRow, j as IndexCountersignaturesPage, k as IndexCredentialRow, l as IndexCredentialsPage, m as IndexIdentitiesPage, n as IndexIdentityProfile, o as IndexIdentityRow, p as IndexOrder, q as IndexRecencyOrder, L as LogOp, P as Provenance, r as RelayHealth, s as RelayResponse, t as Resolution, u as Resolved, v as ResolvedContent, w as ResolvedCredential, S as Store, T as Trust, U as UnverifiableAxis, V as VerifyResult } from './types-DClhQw33.js';
|
|
3
|
+
export { m as memoryStore } from './memory-aYqSas8m.js';
|
|
4
4
|
import '@metalabel/dfos-protocol/chain';
|
|
5
5
|
import '@metalabel/dfos-protocol/credentials';
|
|
6
6
|
import '@metalabel/dfos-web-relay/peer-client';
|
|
@@ -65,7 +65,9 @@ declare const divergenceErrorFrom: (err: unknown) => DivergenceError | undefined
|
|
|
65
65
|
* payload binds exactly the queried (issuerDID, credentialCID). Anything less —
|
|
66
66
|
* unreachable relay, negative answer, forged or mismatched proof — moves on to
|
|
67
67
|
* the next relay; false only after the full set has been consulted and at least
|
|
68
|
-
* one relay answered with a parseable status body. Zero answers throws
|
|
68
|
+
* one relay answered with a parseable status body. Zero answers throws, and so
|
|
69
|
+
* does a positive answer whose signing key this client could not resolve: a
|
|
70
|
+
* dependency failure is never a negative status.
|
|
69
71
|
*
|
|
70
72
|
* When the caller supplies `asOfUnix` (the protocol does, on every cold fold, with
|
|
71
73
|
* each operation's own `createdAt`), a verified revocation only counts if its own
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { dagCborCanonicalEncode, decodeJwsUnsafe as decodeJwsUnsafe2 } from "@me
|
|
|
12
12
|
import { createHttpPeerClient } from "@metalabel/dfos-web-relay/peer-client";
|
|
13
13
|
|
|
14
14
|
// src/transport.ts
|
|
15
|
+
import { isDependencyMissing, markDependencyMissing } from "@metalabel/dfos-protocol";
|
|
15
16
|
var PAGE_LIMIT = 1e3;
|
|
16
17
|
var MAX_PAGES = 1e4;
|
|
17
18
|
var normalizeRelays = (relays) => {
|
|
@@ -62,6 +63,14 @@ var fanOutLog = async (fetchPage, relays, quorum, verifyCandidate) => {
|
|
|
62
63
|
const byDigest = /* @__PURE__ */ new Map();
|
|
63
64
|
const badDigests = /* @__PURE__ */ new Set();
|
|
64
65
|
let lastVerifyError;
|
|
66
|
+
let lastDependencyError;
|
|
67
|
+
const recordFailure = (err, digest) => {
|
|
68
|
+
if (!(err instanceof StaleAnswerError)) {
|
|
69
|
+
if (isDependencyMissing(err)) lastDependencyError = err;
|
|
70
|
+
else lastVerifyError = err;
|
|
71
|
+
}
|
|
72
|
+
badDigests.add(digest);
|
|
73
|
+
};
|
|
65
74
|
for (const url of relays) {
|
|
66
75
|
let entries = null;
|
|
67
76
|
try {
|
|
@@ -89,8 +98,7 @@ var fanOutLog = async (fetchPage, relays, quorum, verifyCandidate) => {
|
|
|
89
98
|
provenance: { answeredBy: group.url, responses, agreed: true, fromCache: false }
|
|
90
99
|
};
|
|
91
100
|
} catch (err) {
|
|
92
|
-
|
|
93
|
-
badDigests.add(digest);
|
|
101
|
+
recordFailure(err, digest);
|
|
94
102
|
}
|
|
95
103
|
}
|
|
96
104
|
}
|
|
@@ -105,8 +113,7 @@ var fanOutLog = async (fetchPage, relays, quorum, verifyCandidate) => {
|
|
|
105
113
|
provenance: { answeredBy: group.url, responses, agreed: false, fromCache: false }
|
|
106
114
|
};
|
|
107
115
|
} catch (err) {
|
|
108
|
-
|
|
109
|
-
badDigests.add(digest);
|
|
116
|
+
recordFailure(err, digest);
|
|
110
117
|
}
|
|
111
118
|
}
|
|
112
119
|
if (lastVerifyError !== void 0) {
|
|
@@ -115,6 +122,14 @@ var fanOutLog = async (fetchPage, relays, quorum, verifyCandidate) => {
|
|
|
115
122
|
cause: lastVerifyError
|
|
116
123
|
});
|
|
117
124
|
}
|
|
125
|
+
if (lastDependencyError !== void 0) {
|
|
126
|
+
const message = lastDependencyError instanceof Error ? lastDependencyError.message : "unknown error";
|
|
127
|
+
throw markDependencyMissing(
|
|
128
|
+
new Error(`candidate log verification could not complete: ${message}`, {
|
|
129
|
+
cause: lastDependencyError
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
}
|
|
118
133
|
return {
|
|
119
134
|
outcome: "unreachable",
|
|
120
135
|
entries: [],
|
|
@@ -234,11 +249,13 @@ var createIndexQueries = (relays, fetchImpl) => {
|
|
|
234
249
|
// src/resolvers.ts
|
|
235
250
|
import {
|
|
236
251
|
decodeMultikey,
|
|
252
|
+
IdentityStateNoSeenKeysError,
|
|
237
253
|
verifyContentChain,
|
|
238
254
|
verifyContentExtensionFromTrustedState,
|
|
239
255
|
verifyIdentityChain,
|
|
240
256
|
verifyIdentityExtensionFromTrustedState
|
|
241
257
|
} from "@metalabel/dfos-protocol/chain";
|
|
258
|
+
import { CredentialVerificationError } from "@metalabel/dfos-protocol/credentials";
|
|
242
259
|
import { decodeJwsUnsafe } from "@metalabel/dfos-protocol/crypto";
|
|
243
260
|
var DID_PREFIX = "did:dfos";
|
|
244
261
|
var cacheKey = (kind, id) => `${kind}:${id}`;
|
|
@@ -292,6 +309,8 @@ var keyBytesFor = (state, keyId) => {
|
|
|
292
309
|
);
|
|
293
310
|
return key ? decodeMultikey(key.publicKeyMultibase).keyBytes : null;
|
|
294
311
|
};
|
|
312
|
+
var NoStateAsOfError = class extends CredentialVerificationError {
|
|
313
|
+
};
|
|
295
314
|
var createResolvers = (deps) => {
|
|
296
315
|
const relaysFor = (o) => normalizeRelays(o?.relays ?? deps.relays);
|
|
297
316
|
const getIdentityChain = async (did, options) => {
|
|
@@ -323,17 +342,28 @@ var createResolvers = (deps) => {
|
|
|
323
342
|
let headCID = cached.headCID;
|
|
324
343
|
let lastCreatedAt = cached.lastCreatedAt;
|
|
325
344
|
const log = [...cached.log];
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
345
|
+
try {
|
|
346
|
+
for (const entry of entries.slice(cached.log.length)) {
|
|
347
|
+
const r = await verifyIdentityExtensionFromTrustedState({
|
|
348
|
+
currentState: state,
|
|
349
|
+
headCID,
|
|
350
|
+
lastCreatedAt,
|
|
351
|
+
newOp: entry.jwsToken
|
|
352
|
+
});
|
|
353
|
+
state = r.state;
|
|
354
|
+
headCID = r.operationCID;
|
|
355
|
+
lastCreatedAt = r.createdAt;
|
|
356
|
+
log.push(entry.jwsToken);
|
|
357
|
+
}
|
|
358
|
+
} catch (e) {
|
|
359
|
+
if (!(e instanceof IdentityStateNoSeenKeysError)) throw e;
|
|
360
|
+
const full = entries.map((entry) => entry.jwsToken);
|
|
361
|
+
const replayed = await verifyIdentityChain({ didPrefix: DID_PREFIX, log: full });
|
|
362
|
+
if (replayed.did !== did) {
|
|
363
|
+
throw new Error(`relay served a mismatched identity: asked ${did}, got ${replayed.did}`);
|
|
364
|
+
}
|
|
365
|
+
const last = opMeta(full[full.length - 1]);
|
|
366
|
+
return { state: replayed, log: full, headCID: last.cid, lastCreatedAt: last.createdAt };
|
|
337
367
|
}
|
|
338
368
|
return { state, log, headCID, lastCreatedAt };
|
|
339
369
|
};
|
|
@@ -375,13 +405,18 @@ var createResolvers = (deps) => {
|
|
|
375
405
|
const { state, log } = resolution;
|
|
376
406
|
const last = log[log.length - 1];
|
|
377
407
|
if (basis === void 0 || last === void 0 || opMeta(last).createdAt <= basis) return state;
|
|
408
|
+
const first = log[0];
|
|
409
|
+
if (first !== void 0 && basis < opMeta(first).createdAt) {
|
|
410
|
+
throw new NoStateAsOfError(`identity has no state as of ${basis}`);
|
|
411
|
+
}
|
|
378
412
|
const asOf = await verifyIdentityChain({ didPrefix: DID_PREFIX, log, asOf: basis });
|
|
379
|
-
return { ...asOf, isDeleted: state.isDeleted };
|
|
413
|
+
return { ...asOf, isDeleted: state.isDeleted, basisDeterminate: true };
|
|
380
414
|
};
|
|
381
415
|
const resolveIdentity = async (did, basis) => {
|
|
382
416
|
try {
|
|
383
417
|
return await stateAsOf(await getIdentityChain(did), basis);
|
|
384
|
-
} catch {
|
|
418
|
+
} catch (err) {
|
|
419
|
+
if (err instanceof NoStateAsOfError) throw err;
|
|
385
420
|
return void 0;
|
|
386
421
|
}
|
|
387
422
|
};
|
|
@@ -506,12 +541,29 @@ var createResolvers = (deps) => {
|
|
|
506
541
|
};
|
|
507
542
|
|
|
508
543
|
// src/revocation.ts
|
|
544
|
+
import { isDependencyMissing as isDependencyMissing2 } from "@metalabel/dfos-protocol";
|
|
509
545
|
import { parseProtocolTimestampUnix, verifyRevocation } from "@metalabel/dfos-protocol/chain";
|
|
546
|
+
import { CredentialVerificationError as CredentialVerificationError2 } from "@metalabel/dfos-protocol/credentials";
|
|
510
547
|
import { REVOCATIONS_BASE_PATH } from "@metalabel/dfos-web-relay/peer-client";
|
|
548
|
+
var unavailable = (why) => new Error(`revocation status unavailable: ${why}`);
|
|
549
|
+
var KeyUnresolvableError = class extends Error {
|
|
550
|
+
};
|
|
551
|
+
var isResolverVerdict = (err) => err instanceof CredentialVerificationError2 && !isDependencyMissing2(err);
|
|
511
552
|
var createRevocationChecker = (relays, fetchImpl, resolveKey) => {
|
|
512
553
|
const relaySet = normalizeRelays(relays);
|
|
554
|
+
const guardedResolveKey = async (kid, basis) => {
|
|
555
|
+
try {
|
|
556
|
+
return await resolveKey(kid, basis);
|
|
557
|
+
} catch (err) {
|
|
558
|
+
if (isResolverVerdict(err)) throw err;
|
|
559
|
+
throw new KeyUnresolvableError(`could not resolve the revocation signing key ${kid}`, {
|
|
560
|
+
cause: err
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
};
|
|
513
564
|
return async (issuerDID, credentialCID, asOfUnix) => {
|
|
514
565
|
let answered = false;
|
|
566
|
+
let unresolvable;
|
|
515
567
|
for (const url of relaySet) {
|
|
516
568
|
let body = null;
|
|
517
569
|
try {
|
|
@@ -532,17 +584,22 @@ var createRevocationChecker = (relays, fetchImpl, resolveKey) => {
|
|
|
532
584
|
}
|
|
533
585
|
if (!body?.revoked || !body.revocation) continue;
|
|
534
586
|
try {
|
|
535
|
-
const verified = await verifyRevocation({
|
|
587
|
+
const verified = await verifyRevocation({
|
|
588
|
+
jwsToken: body.revocation,
|
|
589
|
+
resolveKey: guardedResolveKey
|
|
590
|
+
});
|
|
536
591
|
if (verified.did === issuerDID && verified.credentialCID === credentialCID) {
|
|
537
592
|
if (asOfUnix === void 0 || asOfUnix <= 0) return true;
|
|
538
593
|
const revokedAtUnix = parseProtocolTimestampUnix(verified.createdAt);
|
|
539
594
|
if (revokedAtUnix === null || revokedAtUnix <= asOfUnix) return true;
|
|
540
595
|
continue;
|
|
541
596
|
}
|
|
542
|
-
} catch {
|
|
597
|
+
} catch (err) {
|
|
598
|
+
if (err instanceof KeyUnresolvableError) unresolvable = err;
|
|
543
599
|
}
|
|
544
600
|
}
|
|
545
|
-
if (!answered) throw
|
|
601
|
+
if (!answered) throw unavailable("no relay answered");
|
|
602
|
+
if (unresolvable) throw unavailable(unresolvable.message);
|
|
546
603
|
return false;
|
|
547
604
|
};
|
|
548
605
|
};
|
package/dist/siwd.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Signer, VerifiedIdentity, VerifiedSignRequest } from '@metalabel/dfos-protocol/chain';
|
|
2
|
-
import { a as Client, V as VerifyResult } from './types-
|
|
2
|
+
import { a as Client, V as VerifyResult } from './types-DClhQw33.js';
|
|
3
3
|
import '@metalabel/dfos-protocol/credentials';
|
|
4
4
|
import '@metalabel/dfos-web-relay/peer-client';
|
|
5
5
|
|
|
@@ -96,6 +96,16 @@ interface SiwdLoginRequestInput {
|
|
|
96
96
|
* would describe something other than what was asked for.
|
|
97
97
|
*/
|
|
98
98
|
scope: string;
|
|
99
|
+
/**
|
|
100
|
+
* Lock consent to a named set of places: `all`, or up to 31 distinct 31-char
|
|
101
|
+
* space ids. Absent, the user chooses at the consent screen.
|
|
102
|
+
*
|
|
103
|
+
* A host honors it only when `scope` names a space-level action, and refuses
|
|
104
|
+
* the request whole otherwise. The returned credential's `att` is the answer —
|
|
105
|
+
* consent MAY narrow the set, so read it rather than assuming the ask was
|
|
106
|
+
* honored whole.
|
|
107
|
+
*/
|
|
108
|
+
spaces?: readonly string[] | 'all';
|
|
99
109
|
/** Consent-screen prose. A host MAY decline to render it; see specs/INTEGRATIONS.md. */
|
|
100
110
|
statement?: string;
|
|
101
111
|
/**
|
|
@@ -177,8 +187,8 @@ interface SiwdLoginRequest {
|
|
|
177
187
|
* client identity the tier is open and every scope is available.
|
|
178
188
|
*
|
|
179
189
|
* It also owns the WIRE PARAM NAMES (`challenge`, `redirect_uri`, `scope`,
|
|
180
|
-
* `client_did`, and — via `createSiwdLoopbackLoginRequest` —
|
|
181
|
-
* `client_chain`) as their single source in this package. They are snake_case
|
|
190
|
+
* `spaces`, `client_did`, and — via `createSiwdLoopbackLoginRequest` —
|
|
191
|
+
* `client_proof` and `client_chain`) as their single source in this package. They are snake_case
|
|
182
192
|
* on the wire and camelCase everywhere else, which is exactly the kind of seam
|
|
183
193
|
* every hand-rolled RP re-implements and eventually gets wrong.
|
|
184
194
|
*
|
|
@@ -329,6 +339,13 @@ interface SiwdLoopbackLoginRequestInput {
|
|
|
329
339
|
* a credential to.
|
|
330
340
|
*/
|
|
331
341
|
scope: string;
|
|
342
|
+
/**
|
|
343
|
+
* Lock consent to a named set of places: `all`, or up to 31 distinct 31-char
|
|
344
|
+
* space ids. Same rule as `SiwdLoginRequestInput.spaces` — the host honors it
|
|
345
|
+
* only when `scope` names a space-level action, and refuses the request whole
|
|
346
|
+
* otherwise.
|
|
347
|
+
*/
|
|
348
|
+
spaces?: readonly string[] | 'all';
|
|
332
349
|
/** Consent-screen prose. A host MAY decline to render it; see specs/INTEGRATIONS.md. */
|
|
333
350
|
statement?: string;
|
|
334
351
|
/** Bind the challenge to ONE subject DID (sign in as this DID or not at all). */
|
package/dist/siwd.js
CHANGED
|
@@ -122,6 +122,18 @@ var bareHostname = (url) => {
|
|
|
122
122
|
const host = url.hostname.toLowerCase();
|
|
123
123
|
return host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
124
124
|
};
|
|
125
|
+
var SIWD_SPACE_ID_RE = /^[2346789acdefhknrtvz]{31}$/;
|
|
126
|
+
var MAX_SIWD_SPACES = 31;
|
|
127
|
+
var siwdSpacesParam = (spaces) => {
|
|
128
|
+
if (spaces === "all") return "all";
|
|
129
|
+
const malformed = !Array.isArray(spaces) || spaces.length === 0 || spaces.length > MAX_SIWD_SPACES || spaces.some((id) => typeof id !== "string" || !SIWD_SPACE_ID_RE.test(id)) || new Set(spaces).size !== spaces.length;
|
|
130
|
+
if (malformed) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
"invalid SIWD login request: spaces must be 'all' or distinct 31-character space ids"
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return spaces.join(",");
|
|
136
|
+
};
|
|
125
137
|
var parseUrlOrThrow = (value, field) => {
|
|
126
138
|
try {
|
|
127
139
|
return new URL(value);
|
|
@@ -138,6 +150,7 @@ var createSiwdLoginRequest = (input) => {
|
|
|
138
150
|
"invalid SIWD login request: loopback redirects support scope=identity only without a client identity \u2014 a credential scope needs a client_did proven under the loopback credential tier (specs/INTEGRATIONS.md \xA7Loopback clients)"
|
|
139
151
|
);
|
|
140
152
|
}
|
|
153
|
+
const spaces = input.spaces !== void 0 ? siwdSpacesParam(input.spaces) : void 0;
|
|
141
154
|
const { challenge, encoded, nonce } = createSiwdChallenge({
|
|
142
155
|
domain: input.domain,
|
|
143
156
|
...input.statement !== void 0 ? { statement: input.statement } : {},
|
|
@@ -148,6 +161,9 @@ var createSiwdLoginRequest = (input) => {
|
|
|
148
161
|
url.searchParams.set("challenge", encoded);
|
|
149
162
|
url.searchParams.set("redirect_uri", input.redirectUri);
|
|
150
163
|
url.searchParams.set("scope", input.scope);
|
|
164
|
+
if (spaces !== void 0) {
|
|
165
|
+
url.searchParams.set("spaces", spaces);
|
|
166
|
+
}
|
|
151
167
|
if (input.clientDid !== void 0) {
|
|
152
168
|
url.searchParams.set("client_did", input.clientDid);
|
|
153
169
|
}
|
|
@@ -263,6 +279,7 @@ var createSiwdLoopbackLoginRequest = async (input) => {
|
|
|
263
279
|
redirectUri: input.redirectUri,
|
|
264
280
|
scope: input.scope,
|
|
265
281
|
clientDid: input.client.did,
|
|
282
|
+
...input.spaces !== void 0 ? { spaces: input.spaces } : {},
|
|
266
283
|
...input.statement !== void 0 ? { statement: input.statement } : {},
|
|
267
284
|
...input.did !== void 0 ? { did: input.did } : {},
|
|
268
285
|
...input.nonce !== void 0 ? { nonce: input.nonce } : {}
|
package/dist/store/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { m as memoryStore } from '../memory-
|
|
2
|
-
import { S as Store } from '../types-
|
|
1
|
+
export { m as memoryStore } from '../memory-aYqSas8m.js';
|
|
2
|
+
import { S as Store } from '../types-DClhQw33.js';
|
|
3
3
|
import '@metalabel/dfos-protocol/chain';
|
|
4
4
|
import '@metalabel/dfos-protocol/credentials';
|
|
5
5
|
import '@metalabel/dfos-web-relay/peer-client';
|
|
@@ -5,6 +5,12 @@ import { PeerClient } from '@metalabel/dfos-web-relay/peer-client';
|
|
|
5
5
|
/** Head/as-of state; excludes the credit-claim projection. */
|
|
6
6
|
type EffectiveIdentity = VerifiedIdentity & {
|
|
7
7
|
readonly resolution?: 'effective';
|
|
8
|
+
/**
|
|
9
|
+
* True when no operation dated at or before the basis can still arrive and
|
|
10
|
+
* change this key state — the protocol's `ResolvedIdentity.basisDeterminate`,
|
|
11
|
+
* which decides whether a missing key is a verdict or a retryable miss.
|
|
12
|
+
*/
|
|
13
|
+
basisDeterminate?: boolean;
|
|
8
14
|
};
|
|
9
15
|
/** Every key ever proved, including memberships absent from effective state. */
|
|
10
16
|
type EverProvedIdentity = VerifiedIdentity & {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metalabel/dfos-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DFOS Client — the client-side kit for participating in the protocol: resolve, verify, prove. Fetch, resolve, verify-orchestration and cache over untrusted relays, plus the SIWD and API-AUTH proof surfaces. Holds no keys; all crypto truth comes from @metalabel/dfos-protocol",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,16 +47,16 @@
|
|
|
47
47
|
"README.md"
|
|
48
48
|
],
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@metalabel/dfos-protocol": "^0.
|
|
51
|
-
"@metalabel/dfos-web-relay": "^0.
|
|
50
|
+
"@metalabel/dfos-protocol": "^0.54.0",
|
|
51
|
+
"@metalabel/dfos-web-relay": "^0.54.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^24.10.4",
|
|
55
55
|
"tsup": "^8.5.1",
|
|
56
56
|
"typescript": "^5.9.3",
|
|
57
57
|
"vitest": "^4.1.8",
|
|
58
|
-
"@metalabel/dfos-protocol": "0.
|
|
59
|
-
"@metalabel/dfos-web-relay": "0.
|
|
58
|
+
"@metalabel/dfos-protocol": "0.54.0",
|
|
59
|
+
"@metalabel/dfos-web-relay": "0.54.0"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "tsup",
|