@calimero-network/mero-js 22.6.0 → 22.8.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/dist/account/account.d.ts +99 -4
- package/dist/account/account.js +0 -0
- package/dist/account/index.d.ts +6 -2
- package/dist/account/index.js +3 -1
- package/dist/admin-api/admin-client.d.ts +138 -2
- package/dist/admin-api/admin-client.js +237 -1
- package/dist/admin-api/admin-types.d.ts +159 -5
- package/dist/capabilities.d.ts +42 -0
- package/dist/capabilities.js +64 -0
- package/dist/cloud/cloud-client.d.ts +38 -8
- package/dist/cloud/cloud-client.js +38 -21
- package/dist/cloud/connect.d.ts +101 -2
- package/dist/cloud/connect.js +84 -8
- package/dist/cloud/enrol-redirect.d.ts +195 -0
- package/dist/cloud/enrol-redirect.js +236 -0
- package/dist/cloud/index.d.ts +6 -2
- package/dist/cloud/index.js +7 -1
- package/dist/cloud/link-redirect.d.ts +85 -0
- package/dist/cloud/link-redirect.js +105 -0
- package/dist/crypto/internal.d.ts +15 -0
- package/dist/crypto/internal.js +18 -0
- package/dist/device-cert/device-cert.d.ts +85 -2
- package/dist/device-cert/device-cert.js +100 -9
- package/dist/device-cert/index.d.ts +2 -2
- package/dist/device-cert/index.js +1 -1
- package/dist/events/sse.d.ts +22 -2
- package/dist/events/sse.js +20 -11
- package/dist/http-client/http-types.d.ts +18 -0
- package/dist/http-client/web-client.js +21 -1
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +1999 -536
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +11 -6
- package/dist/index.js +17 -3
- package/dist/index.mjs +1999 -536
- package/dist/index.mjs.map +4 -4
- package/dist/login/login.d.ts +11 -1
- package/dist/login/login.js +5 -4
- package/dist/login/session.d.ts +21 -5
- package/dist/login/session.js +7 -15
- package/dist/namespace-op/namespace-op.d.ts +14 -2
- package/dist/namespace-op/namespace-op.js +9 -5
- package/dist/relay/index.d.ts +3 -3
- package/dist/relay/index.js +1 -1
- package/dist/relay/nonce-source.d.ts +135 -0
- package/dist/relay/nonce-source.js +187 -19
- package/dist/relay/relay-client.d.ts +148 -5
- package/dist/relay/relay-client.js +187 -7
- package/dist/relay-attestation/attest-node-key.d.ts +66 -0
- package/dist/relay-attestation/attest-node-key.js +110 -0
- package/dist/relay-attestation/index.d.ts +1 -0
- package/dist/relay-attestation/index.js +1 -0
- package/dist/request-proof/authorizer.d.ts +48 -0
- package/dist/request-proof/authorizer.js +65 -0
- package/dist/request-proof/index.d.ts +4 -0
- package/dist/request-proof/index.js +2 -0
- package/dist/request-proof/request-proof.d.ts +56 -0
- package/dist/request-proof/request-proof.js +111 -0
- package/dist/rpc/index.d.ts +21 -0
- package/dist/rpc/index.js +20 -0
- package/dist/transport/index.d.ts +6 -0
- package/dist/transport/index.js +3 -0
- package/dist/transport/mero-client.d.ts +286 -0
- package/dist/transport/mero-client.js +379 -0
- package/dist/transport/relay-observer.d.ts +152 -0
- package/dist/transport/relay-observer.js +173 -0
- package/dist/transport/relay-transport.d.ts +73 -0
- package/dist/transport/relay-transport.js +94 -0
- package/dist/transport/types.d.ts +100 -0
- package/dist/transport/types.js +32 -0
- package/dist/warrant/governance-op.d.ts +47 -1
- package/dist/warrant/governance-op.js +62 -2
- package/dist/warrant/index.d.ts +2 -2
- package/dist/warrant/index.js +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Signer } from '../signer/signer.js';
|
|
1
2
|
/** An account root: the secret, the key it derives, and the account it names. */
|
|
2
3
|
export interface AccountRoot {
|
|
3
4
|
/**
|
|
@@ -51,10 +52,98 @@ export declare function generateAccountRoot(): Promise<RecoverableAccountRoot>;
|
|
|
51
52
|
export declare function accountRootFromPhrase(phrase: string): Promise<RecoverableAccountRoot>;
|
|
52
53
|
/** The public half of a root you already hold, without re-deriving it by hand. */
|
|
53
54
|
export declare function accountRootFromSecret(secret: string): Promise<AccountRoot>;
|
|
55
|
+
/** An account root held as a key this page may use and cannot read. */
|
|
56
|
+
export interface AccountRootSigner {
|
|
57
|
+
/** The root's ed25519 public key, 64 hex. Safe to publish. */
|
|
58
|
+
readonly publicKey: string;
|
|
59
|
+
/** The account this root names, 64 hex. */
|
|
60
|
+
readonly accountId: string;
|
|
61
|
+
/**
|
|
62
|
+
* Signs for the root. Pass it to `signDeviceCert({ signer })`.
|
|
63
|
+
*
|
|
64
|
+
* There is deliberately no `secret` beside this: the point of the shape is
|
|
65
|
+
* that nothing downstream of here can be handed the account.
|
|
66
|
+
*/
|
|
67
|
+
readonly signer: Signer;
|
|
68
|
+
}
|
|
69
|
+
/** A freshly minted root, with the phrase that is its only backup. */
|
|
70
|
+
export interface NewAccountRootSigner extends AccountRootSigner {
|
|
71
|
+
/**
|
|
72
|
+
* The 24-word phrase. Show it **once**, and store it nowhere.
|
|
73
|
+
*
|
|
74
|
+
* Unlike {@link RecoverableAccountRoot.phrase} this is the only copy that will
|
|
75
|
+
* ever exist: the signer beside it cannot be exported, so a holder who does
|
|
76
|
+
* not write these words down has no path back to the account from another
|
|
77
|
+
* browser — and neither does anyone else, which is the trade.
|
|
78
|
+
*/
|
|
79
|
+
readonly phrase: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Mint a root that stays in this page and hand back its recovery phrase.
|
|
83
|
+
*
|
|
84
|
+
* The enrolment-time counterpart to {@link generateAccountRoot}: same BIP39
|
|
85
|
+
* entropy, same account derivation, but the secret never leaves as a string a
|
|
86
|
+
* caller could persist. What survives a reload is the `CryptoKey` inside the
|
|
87
|
+
* signer — structured-clone it into IndexedDB — and what survives losing the
|
|
88
|
+
* browser is the phrase.
|
|
89
|
+
*
|
|
90
|
+
* **On scrubbing.** The entropy is zeroed and the seed is imported
|
|
91
|
+
* unexportable, but deriving the public half needs the seed as hex for an
|
|
92
|
+
* instant, and a JS string cannot be wiped. So this narrows the window from
|
|
93
|
+
* "for as long as the account exists" to "during this call" — a real
|
|
94
|
+
* improvement, and not the same claim as "the secret never exists".
|
|
95
|
+
*/
|
|
96
|
+
export declare function createAccountRootSigner(): Promise<NewAccountRootSigner>;
|
|
97
|
+
/**
|
|
98
|
+
* Restore the same root in another browser from its phrase.
|
|
99
|
+
*
|
|
100
|
+
* This is what makes the design portable: the phrase names the entropy, the
|
|
101
|
+
* entropy names the root, and the root names the account — so the same 24 words
|
|
102
|
+
* reach the same account id on any device, with no server involved and nothing
|
|
103
|
+
* to migrate.
|
|
104
|
+
*
|
|
105
|
+
* Throws on a bad word count, an unknown word, or a failed checksum rather than
|
|
106
|
+
* returning some other account — see {@link accountRootFromPhrase}.
|
|
107
|
+
*/
|
|
108
|
+
export declare function accountRootSignerFromPhrase(phrase: string): Promise<AccountRootSigner>;
|
|
109
|
+
/**
|
|
110
|
+
* The account root, in either form a caller may hold it.
|
|
111
|
+
*
|
|
112
|
+
* Exists because the cloud entry points take the root **positionally** — the
|
|
113
|
+
* whole argument is "the root" — so the two forms cannot be separate optional
|
|
114
|
+
* fields the way {@link AccountLinkInput} has them.
|
|
115
|
+
*
|
|
116
|
+
* A `string` is the 64-hex secret. A {@link Signer} is a root this process may
|
|
117
|
+
* use and cannot read: pass the `signer` from
|
|
118
|
+
* {@link accountRootSignerFromPhrase}.
|
|
119
|
+
*/
|
|
120
|
+
export type RootSource = string | Signer;
|
|
121
|
+
/** A root's identity and how it signs, however the caller supplied it. */
|
|
122
|
+
export interface ResolvedRoot {
|
|
123
|
+
signer: Signer;
|
|
124
|
+
/** The root's ed25519 public key, 64 hex. */
|
|
125
|
+
publicKey: string;
|
|
126
|
+
/** The account it names, 64 hex. */
|
|
127
|
+
accountId: string;
|
|
128
|
+
}
|
|
129
|
+
/** What a root is, however it was supplied: who it is, and how it signs. */
|
|
130
|
+
export declare function resolveRoot(source: RootSource): Promise<ResolvedRoot>;
|
|
131
|
+
/**
|
|
132
|
+
* The same, for entry points that take the two forms as separate optional
|
|
133
|
+
* fields. Exactly one must be present — see {@link resolveSigner}.
|
|
134
|
+
*/
|
|
135
|
+
export declare function resolveRootPair(rootSecret: string | undefined, signer: Signer | undefined): Promise<ResolvedRoot>;
|
|
54
136
|
/** What {@link signAccountLink} needs. */
|
|
55
137
|
export interface AccountLinkInput {
|
|
56
|
-
/**
|
|
57
|
-
|
|
138
|
+
/**
|
|
139
|
+
* The account root's signing secret, 64 hex.
|
|
140
|
+
*
|
|
141
|
+
* Mutually exclusive with {@link AccountLinkInput.signer}. Prefer the signer
|
|
142
|
+
* in a browser: this is the account, and nothing above it can revoke it.
|
|
143
|
+
*/
|
|
144
|
+
rootSecret?: string;
|
|
145
|
+
/** The root's signer — use instead of {@link AccountLinkInput.rootSecret}. */
|
|
146
|
+
signer?: Signer;
|
|
58
147
|
/**
|
|
59
148
|
* The challenge from `GET /api/cloud/me/accounts/challenge`, verbatim.
|
|
60
149
|
*
|
|
@@ -76,8 +165,14 @@ export interface AccountLinkInput {
|
|
|
76
165
|
export declare function signAccountLink(input: AccountLinkInput): Promise<string>;
|
|
77
166
|
/** What {@link signAccountLogin} needs. */
|
|
78
167
|
export interface AccountLoginInput {
|
|
79
|
-
/**
|
|
80
|
-
|
|
168
|
+
/**
|
|
169
|
+
* The account root's signing secret, 64 hex.
|
|
170
|
+
*
|
|
171
|
+
* Mutually exclusive with {@link AccountLoginInput.signer}.
|
|
172
|
+
*/
|
|
173
|
+
rootSecret?: string;
|
|
174
|
+
/** The root's signer — use instead of {@link AccountLoginInput.rootSecret}. */
|
|
175
|
+
signer?: Signer;
|
|
81
176
|
/** The challenge from `GET /api/auth/account/challenge`, verbatim UTF-8. */
|
|
82
177
|
nonce: string;
|
|
83
178
|
}
|
package/dist/account/account.js
CHANGED
|
Binary file
|
package/dist/account/index.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
export { generateAccountRoot, accountRootFromPhrase, accountRootFromSecret, signAccountLink, signAccountLogin, } from './account.js';
|
|
2
|
-
export type { AccountRoot, RecoverableAccountRoot, AccountLinkInput, AccountLoginInput, } from './account.js';
|
|
1
|
+
export { generateAccountRoot, accountRootFromPhrase, accountRootFromSecret, createAccountRootSigner, accountRootSignerFromPhrase, signAccountLink, signAccountLogin, } from './account.js';
|
|
2
|
+
export type { AccountRoot, RecoverableAccountRoot, AccountRootSigner, NewAccountRootSigner, AccountLinkInput, AccountLoginInput, } from './account.js';
|
|
3
|
+
export { resolveRoot } from './account.js';
|
|
4
|
+
export type { RootSource } from './account.js';
|
|
5
|
+
export { resolveRootPair } from './account.js';
|
|
6
|
+
export type { ResolvedRoot } from './account.js';
|
package/dist/account/index.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
// Account roots — minting one, restoring it from a phrase, and proving you hold it.
|
|
2
|
-
export { generateAccountRoot, accountRootFromPhrase, accountRootFromSecret, signAccountLink, signAccountLogin, } from './account.js';
|
|
2
|
+
export { generateAccountRoot, accountRootFromPhrase, accountRootFromSecret, createAccountRootSigner, accountRootSignerFromPhrase, signAccountLink, signAccountLogin, } from './account.js';
|
|
3
|
+
export { resolveRoot } from './account.js';
|
|
4
|
+
export { resolveRootPair } from './account.js';
|
|
@@ -1,6 +1,37 @@
|
|
|
1
|
-
import { HttpClient } from '../http-client/index.js';
|
|
2
|
-
import type { HealthStatus, ReadinessStatus, AdminAuthStatus, InstallApplicationRequest, InstallApplicationResponseData, InstallDevApplicationRequest, UninstallApplicationResponseData, ListApplicationsResponseData, GetApplicationResponseData, ApplicationVersionEntry, GetApplicationAbiResponseData, GetLatestVersionResponseData, ListPackagesResponseData, ListVersionsResponseData, CreateContextRequest, CreateContextResponseData, DeleteContextResponseData, GetContextsResponseData, Context, GenerateContextIdentityResponseData, GetContextIdentitiesResponseData, JoinContextResponseData, JoinSubgroupInheritanceResponseData, ContextGroupResponseData, ContextStorageResponseData, InviteSpecializedNodeRequest, InviteSpecializedNodeResponseData, UpdateContextApplicationRequest, ResyncContextRequest, ResyncContextResponseData, ContextsWithExecutorsResponseData, UploadBlobRequest, UploadBlobResponseData, DeleteBlobResponseData, ListBlobsResponseData, GetBlobInfoResponseData, BlobReadOptions, CreateContextAliasRequest, CreateApplicationAliasRequest, CreateDeviceAliasRequest, CreateAliasResponseData, LookupAliasResponseData, DeleteAliasResponseData, ListAliasesResponseData, ListNamespacesResponseData, CreateNamespaceRequest, CreateNamespaceResponseData, DeleteNamespaceResponseData, CreateNamespaceInvitationRequest, CreateNamespaceInvitationResponseData, CreateRecursiveInvitationResponseData, JoinNamespaceRequest, JoinNamespaceResponseData, CreateGroupInNamespaceRequest, CreateGroupInNamespaceResponseData, SubgroupEntry, Namespace, NamespaceIdentity, NodeIdentity, AccountPairInitRequest, AccountPairInitResponseData, AccountPairCompleteRequest, AccountPairCompleteResponseData, AccountSignWithRootRequest, AccountSignWithRootResponseData, RelinkDeviceRequest, RelinkDeviceResponseData, RescopeDeviceRequest, RescopeDeviceResponseData, LabelDeviceRequest, LabelDeviceResponseData, AccountDeviceEntry, AccountApplicationEntry, RevokeAccountDeviceRequest, RevokeAccountDeviceResponseData, GroupInfoResponseData, CreateGroupRequest, CreateGroupResponseData, DeleteGroupResponseData, ListGroupMembersResponseData, ListGroupContextsResponseData, AddGroupMembersRequest, RemoveGroupMembersRequest, UpdateMemberRoleRequest, MemberCapabilities, SetMemberCapabilitiesRequest, SetDefaultCapabilitiesRequest, SetMemberAutoFollowRequest, SetSubgroupVisibilityRequest, SetTeeAdmissionPolicyRequest, GetTeeAdmissionPolicyResponseData, SetTeeAuthoringPolicyRequest, SetGroupMetadataRequest, SetMemberMetadataRequest, SetContextMetadataRequest, MetadataRecord, SyncGroupResponseData, UpgradeGroupRequest, UpgradeGroupResponseData, GroupUpgradeStatusResponseData, MigrationStatus, CascadeStatusEntry, RetryGroupUpgradeResponseData, ReparentGroupRequest, ReparentGroupResponseData, IssueOwnershipProofRequest, IssueNamespaceOwnershipProofRequest, IssueOwnershipProofResponseData, CreateGroupInvitationRequest, CreateGroupInvitationResponseData, CreateRecursiveGroupInvitationResponseData, JoinGroupRequest, JoinGroupResponseData, TeeInfoResponseData, TeeAttestRequest, TeeAttestResponseData, TeeVerifyQuoteRequest, TeeVerifyQuoteResponseData, PerformIntentRequest, PerformIntentResponseData, IntentRelayInfo, QueryContextRequest, QueryContextResponseData,
|
|
1
|
+
import { HttpClient, HTTPError } from '../http-client/index.js';
|
|
2
|
+
import type { HealthStatus, ReadinessStatus, AdminAuthStatus, InstallApplicationRequest, InstallApplicationResponseData, InstallDevApplicationRequest, UninstallApplicationResponseData, ListApplicationsResponseData, GetApplicationResponseData, ApplicationVersionEntry, GetApplicationAbiResponseData, GetLatestVersionResponseData, ListPackagesResponseData, ListVersionsResponseData, CreateContextRequest, CreateContextResponseData, DeleteContextResponseData, GetContextsResponseData, Context, GenerateContextIdentityResponseData, GetContextIdentitiesResponseData, JoinContextResponseData, JoinSubgroupInheritanceResponseData, ContextGroupResponseData, ContextStorageResponseData, InviteSpecializedNodeRequest, InviteSpecializedNodeResponseData, UpdateContextApplicationRequest, ResyncContextRequest, ResyncContextResponseData, ContextsWithExecutorsResponseData, UploadBlobRequest, UploadBlobResponseData, DeleteBlobResponseData, ListBlobsResponseData, GetBlobInfoResponseData, BlobReadOptions, CreateContextAliasRequest, CreateApplicationAliasRequest, CreateDeviceAliasRequest, CreateAliasResponseData, LookupAliasResponseData, DeleteAliasResponseData, ListAliasesResponseData, ListNamespacesResponseData, CreateNamespaceRequest, CreateNamespaceResponseData, DeleteNamespaceResponseData, CreateNamespaceInvitationRequest, CreateNamespaceInvitationResponseData, CreateRecursiveInvitationResponseData, JoinNamespaceRequest, JoinNamespaceResponseData, CreateGroupInNamespaceRequest, CreateGroupInNamespaceResponseData, SubgroupEntry, Namespace, NamespaceIdentity, NodeIdentity, AccountPairInitRequest, AccountPairInitResponseData, AccountPairCompleteRequest, AccountPairCompleteResponseData, AccountSignWithRootRequest, AccountSignWithRootResponseData, RelinkDeviceRequest, RelinkDeviceResponseData, RescopeDeviceRequest, RescopeDeviceResponseData, LabelDeviceRequest, LabelDeviceResponseData, AccountDeviceEntry, AccountApplicationEntry, RevokeAccountDeviceRequest, RevokeAccountDeviceResponseData, MemberDevicesEntry, ListMemberDevicesOptions, GroupInfoResponseData, CreateGroupRequest, CreateGroupResponseData, DeleteGroupResponseData, ListGroupMembersResponseData, ListGroupContextsResponseData, AddGroupMembersRequest, RemoveGroupMembersRequest, UpdateMemberRoleRequest, MemberCapabilities, SetMemberCapabilitiesRequest, SetDefaultCapabilitiesRequest, SetMemberAutoFollowRequest, SetSubgroupVisibilityRequest, SetTeeAdmissionPolicyRequest, GetTeeAdmissionPolicyResponseData, SetTeeAuthoringPolicyRequest, SetGroupMetadataRequest, SetMemberMetadataRequest, SetContextMetadataRequest, MetadataRecord, SyncGroupResponseData, UpgradeGroupRequest, UpgradeGroupResponseData, GroupUpgradeStatusResponseData, MigrationStatus, CascadeStatusEntry, RetryGroupUpgradeResponseData, ReparentGroupRequest, ReparentGroupResponseData, IssueOwnershipProofRequest, IssueNamespaceOwnershipProofRequest, IssueOwnershipProofResponseData, CreateGroupInvitationRequest, CreateGroupInvitationResponseData, CreateRecursiveGroupInvitationResponseData, JoinGroupRequest, JoinGroupResponseData, TeeInfoResponseData, TeeAttestRequest, TeeAttestResponseData, TeeVerifyQuoteRequest, TeeVerifyQuoteResponseData, PerformIntentRequest, PerformIntentResponseData, IntentRelayInfo, QueryContextRequest, QueryContextResponseData, ListMemberDevicesResponseData, SealToAccountRequest, SealedEnvelope, AdmitJoinRequest, AdmitJoinResponseData, WarrantNonceState } from './admin-types.js';
|
|
3
3
|
import { type RedeemOutcome } from './redeem-invitation.js';
|
|
4
|
+
/**
|
|
5
|
+
* Thrown when a node answers the warrant-nonce route with a 404.
|
|
6
|
+
*
|
|
7
|
+
* Its own type because the meaning is unambiguous and not "not found": the
|
|
8
|
+
* handler never 404s — an unknown device is a `200` with `seen: false`, a
|
|
9
|
+
* malformed id is a `400` — so a 404 says the route is not mounted on this
|
|
10
|
+
* node. It is new on core and absent from `master` builds. A caller that sees
|
|
11
|
+
* this should fall back to its own counter (or refuse to write) rather than
|
|
12
|
+
* retry, because no amount of retrying adds a route to a running node.
|
|
13
|
+
*/
|
|
14
|
+
export declare class WarrantNonceRouteUnavailableError extends Error {
|
|
15
|
+
/** The path that answered 404. */
|
|
16
|
+
readonly path: string;
|
|
17
|
+
/** The underlying HTTP failure, kept for logs. */
|
|
18
|
+
readonly cause: HTTPError;
|
|
19
|
+
name: string;
|
|
20
|
+
constructor(
|
|
21
|
+
/** The path that answered 404. */
|
|
22
|
+
path: string,
|
|
23
|
+
/** The underlying HTTP failure, kept for logs. */
|
|
24
|
+
cause: HTTPError);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Shape the node's answer into the union, which is where the exhausted case
|
|
28
|
+
* stops being a missing field and starts being something the compiler asks
|
|
29
|
+
* about.
|
|
30
|
+
*
|
|
31
|
+
* Exported for tests: it is the whole client-side contract of the route, and
|
|
32
|
+
* testing it needs no transport.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseWarrantNonce(body: string): WarrantNonceState;
|
|
4
35
|
/**
|
|
5
36
|
* Compare two version strings by SemVer 2.0 precedence, ascending: negative if
|
|
6
37
|
* `a < b`, positive if `a > b`, `0` if equal. Build metadata (`+...`) is stripped
|
|
@@ -397,6 +428,13 @@ export declare class AdminApiClient {
|
|
|
397
428
|
* `namespaceId` names where to publish from, not the extent of the revocation:
|
|
398
429
|
* a device belongs to the account, so every namespace holding a binding for it
|
|
399
430
|
* is withdrawn from and reported back.
|
|
431
|
+
*
|
|
432
|
+
* **Revocation is forward-only.** It withdraws the device's authority from
|
|
433
|
+
* here on and does NOT retract past authorship: ops the device already signed
|
|
434
|
+
* keep verifying and stay in the DAG, because the certificate that authorised
|
|
435
|
+
* them was valid when the signature was made. Nothing in this system can
|
|
436
|
+
* un-author what a device wrote, so a UI built on this call must not offer
|
|
437
|
+
* that - "this device can no longer act" is the only claim it supports.
|
|
400
438
|
*/
|
|
401
439
|
revokeAccountDevice(namespaceId: string, request: RevokeAccountDeviceRequest): Promise<RevokeAccountDeviceResponseData>;
|
|
402
440
|
getGroupInfo(groupId: string): Promise<GroupInfoResponseData>;
|
|
@@ -406,6 +444,104 @@ export declare class AdminApiClient {
|
|
|
406
444
|
getSubgroupVisibility(groupId: string): Promise<string>;
|
|
407
445
|
deleteGroup(groupId: string): Promise<DeleteGroupResponseData>;
|
|
408
446
|
listGroupMembers(groupId: string): Promise<ListGroupMembersResponseData>;
|
|
447
|
+
/**
|
|
448
|
+
* Account -> devices for the members of a group, as its live bindings record
|
|
449
|
+
* them.
|
|
450
|
+
*
|
|
451
|
+
* The join between a group member listing, which names accounts, and a
|
|
452
|
+
* context's identities, which name bare signing keys - both 64 hex, and
|
|
453
|
+
* nothing but the source tells them apart. Batch rather than per-account
|
|
454
|
+
* because the key -> account direction gets asked about arbitrary authors,
|
|
455
|
+
* which a per-account route would answer in N calls.
|
|
456
|
+
*
|
|
457
|
+
* What comes back is **caller-scoped by the node**: an admin of the group sees
|
|
458
|
+
* every member's devices, a plain member only its own entry. A single-entry
|
|
459
|
+
* answer is therefore a statement about this caller's role, not about the
|
|
460
|
+
* group's size.
|
|
461
|
+
*
|
|
462
|
+
* Only live bindings appear, so a revoked device is absent here - use
|
|
463
|
+
* {@link listAccountDevices} for the account's own devices, which keeps a
|
|
464
|
+
* revoked one with `revoked: true`. Absence here never means the device never
|
|
465
|
+
* acted: revocation is forward-only and its past ops remain in the DAG.
|
|
466
|
+
*
|
|
467
|
+
* Wrapped in `members` on the wire rather than `data`, unwrapped here for the
|
|
468
|
+
* same reason {@link listAccountDevices} unwraps `devices`.
|
|
469
|
+
*/
|
|
470
|
+
listGroupMemberDevices(groupId: string, options?: ListMemberDevicesOptions): Promise<MemberDevicesEntry[]>;
|
|
471
|
+
/**
|
|
472
|
+
* Where `authorDeviceKey` stands in its warrant-nonce sequence in `contextId`.
|
|
473
|
+
*
|
|
474
|
+
* # What this is for
|
|
475
|
+
*
|
|
476
|
+
* The nonce is the one input to a warrant that is stateful on the *client*.
|
|
477
|
+
* Everything else an author needs it either holds (its keys, the method, the
|
|
478
|
+
* args) or can read from the relay ({@link RelayClient.describe} gives the
|
|
479
|
+
* executor account and the grant). So an author that has lost its counter —
|
|
480
|
+
* a browser keyholder in partitioned or periodically cleared storage, which
|
|
481
|
+
* is its normal lifecycle rather than a fault — otherwise has exactly one
|
|
482
|
+
* strategy: guess upward and burn a refused round trip per wrong guess, with
|
|
483
|
+
* each refusal indistinguishable at the API from a genuinely spent nonce.
|
|
484
|
+
*
|
|
485
|
+
* Read {@link WarrantNonceOpen.nextNonce} and mint there. If you still hold
|
|
486
|
+
* your own counter, mint at `max(ownNext, nextNonce)`: nonce state folds per
|
|
487
|
+
* peer, so a node can only be BEHIND a client whose warrants it has not
|
|
488
|
+
* applied — never ahead of the truth. An author spread across several relays
|
|
489
|
+
* asks each and takes the highest answer. {@link createRecoveringNonceSource}
|
|
490
|
+
* does all of that.
|
|
491
|
+
*
|
|
492
|
+
* Purely a read. It spends nothing and advances nothing, so it may be called
|
|
493
|
+
* as often as you like.
|
|
494
|
+
*
|
|
495
|
+
* # Forward compatibility — this route is NEW
|
|
496
|
+
*
|
|
497
|
+
* It is not on core `master` yet, so a current node answers **404**, which
|
|
498
|
+
* this method surfaces as {@link WarrantNonceRouteUnavailableError} rather
|
|
499
|
+
* than as an opaque {@link HTTPError}. That distinction matters because the
|
|
500
|
+
* handler itself never 404s: a device with no row is a `200` with
|
|
501
|
+
* `seen: false`, and a malformed id is a `400`. So a 404 here means the node
|
|
502
|
+
* does not serve the route at all — an older build, or a deployment that has
|
|
503
|
+
* not re-mounted it — and the answer is to fall back to a local counter, not
|
|
504
|
+
* to retry.
|
|
505
|
+
*
|
|
506
|
+
* # Authentication
|
|
507
|
+
*
|
|
508
|
+
* On the PROTECTED admin router, unlike the two delegated-execution routes it
|
|
509
|
+
* sits beside: it takes another principal's device key as input and reports
|
|
510
|
+
* that principal's activity. A `--public-intents` deployment does not serve
|
|
511
|
+
* this anonymously, so an admin credential is required.
|
|
512
|
+
*
|
|
513
|
+
* @param authorDeviceKey the device's signing key, base58 (43-44 chars) or
|
|
514
|
+
* hex (exactly 64). The node accepts either; the two never collide.
|
|
515
|
+
*/
|
|
516
|
+
getWarrantNonce(contextId: string, authorDeviceKey: string): Promise<WarrantNonceState>;
|
|
517
|
+
/**
|
|
518
|
+
* The same answer, asked for the way a delegated client has to ask.
|
|
519
|
+
*
|
|
520
|
+
* {@link getWarrantNonce} is on the protected admin router, so a keyholder
|
|
521
|
+
* writing through a relay — the client this whole facility exists for — is
|
|
522
|
+
* refused it: measured `403` for a session carrying exactly
|
|
523
|
+
* `context:intent`, `context:query`, `context:subscribe`. That client has no
|
|
524
|
+
* admin credential and cannot be given one without defeating the point of the
|
|
525
|
+
* delegated surface.
|
|
526
|
+
*
|
|
527
|
+
* What it does have is the credential proving the device is its own, which it
|
|
528
|
+
* already sends on every write. Presenting it here makes the request
|
|
529
|
+
* self-scoped: you learn a device's position only by proving, at call time,
|
|
530
|
+
* that the device is yours. So there is no oracle to protect against, and no
|
|
531
|
+
* reason for an admin gate.
|
|
532
|
+
*
|
|
533
|
+
* The device key is NOT a parameter. It is read from the verified
|
|
534
|
+
* certificate server-side, which is the only reading that cannot disagree
|
|
535
|
+
* with the signature — a key supplied alongside would be a second spelling of
|
|
536
|
+
* the same fact, and two spellings can differ.
|
|
537
|
+
*
|
|
538
|
+
* Returns the identical shape {@link getWarrantNonce} returns, so a caller can
|
|
539
|
+
* swap one for the other without touching what it does with the answer.
|
|
540
|
+
*
|
|
541
|
+
* @param authorProof hex-encoded borsh `AccountProof<DeviceCert>` — the same
|
|
542
|
+
* value passed as `authorProof` to the relay client.
|
|
543
|
+
*/
|
|
544
|
+
getWarrantNonceAsAuthor(contextId: string, authorProof: string): Promise<WarrantNonceState>;
|
|
409
545
|
/**
|
|
410
546
|
* Member accounts in the group and the devices bound to each. An admin node
|
|
411
547
|
* sees every account; a plain member only its own. `403` if this node is not a
|
|
@@ -1,6 +1,91 @@
|
|
|
1
|
-
import { withRetry } from '../http-client/index.js';
|
|
1
|
+
import { HTTPError, withRetry } from '../http-client/index.js';
|
|
2
2
|
import { CAPABILITIES, hasCap, withCap } from '../capabilities.js';
|
|
3
3
|
import { redeemInvitation } from './redeem-invitation.js';
|
|
4
|
+
/**
|
|
5
|
+
* Thrown when a node answers the warrant-nonce route with a 404.
|
|
6
|
+
*
|
|
7
|
+
* Its own type because the meaning is unambiguous and not "not found": the
|
|
8
|
+
* handler never 404s — an unknown device is a `200` with `seen: false`, a
|
|
9
|
+
* malformed id is a `400` — so a 404 says the route is not mounted on this
|
|
10
|
+
* node. It is new on core and absent from `master` builds. A caller that sees
|
|
11
|
+
* this should fall back to its own counter (or refuse to write) rather than
|
|
12
|
+
* retry, because no amount of retrying adds a route to a running node.
|
|
13
|
+
*/
|
|
14
|
+
export class WarrantNonceRouteUnavailableError extends Error {
|
|
15
|
+
constructor(
|
|
16
|
+
/** The path that answered 404. */
|
|
17
|
+
path,
|
|
18
|
+
/** The underlying HTTP failure, kept for logs. */
|
|
19
|
+
cause) {
|
|
20
|
+
super(`this node does not serve warrant-nonce discovery (404 on ${path}); it predates the route, so a client must fall back to its own nonce counter`);
|
|
21
|
+
this.path = path;
|
|
22
|
+
this.cause = cause;
|
|
23
|
+
this.name = 'WarrantNonceRouteUnavailableError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Recover a `u64` from the response bytes rather than from a parsed `number`.
|
|
28
|
+
*
|
|
29
|
+
* `JSON.parse` has already rounded anything past 2^53 by the time a value is
|
|
30
|
+
* handed over, and it rounds without complaint — so the digits are read back
|
|
31
|
+
* out of the source text and turned straight into a `bigint`.
|
|
32
|
+
*
|
|
33
|
+
* Matching on the quoted key is safe for exactly this response: its only two
|
|
34
|
+
* string fields are a context id and a device key, both of which are hex or
|
|
35
|
+
* base58 and so cannot contain a quote, a colon, or these field names. The
|
|
36
|
+
* parsed object still decides whether a field is *present*; the text is
|
|
37
|
+
* consulted only for its digits.
|
|
38
|
+
*/
|
|
39
|
+
function u64FromBody(body, parsed, field) {
|
|
40
|
+
const value = parsed[field];
|
|
41
|
+
if (value === undefined || value === null)
|
|
42
|
+
return undefined;
|
|
43
|
+
const digits = new RegExp(`"${field}"\\s*:\\s*(\\d+)`).exec(body);
|
|
44
|
+
if (digits)
|
|
45
|
+
return BigInt(digits[1]);
|
|
46
|
+
// The field is present but not a bare integer literal in the body — a node
|
|
47
|
+
// that started sending it as a string, say. Accept a decimal string, and
|
|
48
|
+
// refuse anything else loudly instead of coercing a nonce into existence.
|
|
49
|
+
if (typeof value === 'string' && /^\d+$/.test(value))
|
|
50
|
+
return BigInt(value);
|
|
51
|
+
throw new Error(`warrant-nonce response field \`${field}\` is not a u64: ${JSON.stringify(value)}`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Shape the node's answer into the union, which is where the exhausted case
|
|
55
|
+
* stops being a missing field and starts being something the compiler asks
|
|
56
|
+
* about.
|
|
57
|
+
*
|
|
58
|
+
* Exported for tests: it is the whole client-side contract of the route, and
|
|
59
|
+
* testing it needs no transport.
|
|
60
|
+
*/
|
|
61
|
+
export function parseWarrantNonce(body) {
|
|
62
|
+
const parsed = JSON.parse(body);
|
|
63
|
+
const data = parsed?.data;
|
|
64
|
+
if (!data || typeof data !== 'object') {
|
|
65
|
+
throw new Error(`warrant-nonce response had no \`data\`: ${body.slice(0, 200)}`);
|
|
66
|
+
}
|
|
67
|
+
const common = {
|
|
68
|
+
contextId: String(data.contextId ?? ''),
|
|
69
|
+
authorDeviceKey: String(data.authorDeviceKey ?? ''),
|
|
70
|
+
seen: data.seen === true,
|
|
71
|
+
highWaterNonce: u64FromBody(body, data, 'highWaterNonce'),
|
|
72
|
+
// Not defaulted: a node that stopped sending it is a node whose window this
|
|
73
|
+
// client would be guessing at, and the window is not a nonce input anyway.
|
|
74
|
+
windowWidth: u64FromBody(body, data, 'windowWidth') ?? 0n,
|
|
75
|
+
};
|
|
76
|
+
const nextNonce = u64FromBody(body, data, 'nextNonce');
|
|
77
|
+
if (nextNonce === undefined) {
|
|
78
|
+
// The one absence with a meaning: `u64::MAX` is spent. The node omits the
|
|
79
|
+
// field rather than wrapping to `0`, and a client must not fill the gap.
|
|
80
|
+
return {
|
|
81
|
+
...common,
|
|
82
|
+
kind: 'exhausted',
|
|
83
|
+
seen: true,
|
|
84
|
+
highWaterNonce: common.highWaterNonce ?? 0xffffffffffffffffn,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return { ...common, kind: 'open', nextNonce };
|
|
88
|
+
}
|
|
4
89
|
/**
|
|
5
90
|
* Helper: server wraps most responses in `{ data: T }`.
|
|
6
91
|
* This extracts `.data` so callers get the inner payload directly. Null-safe so an
|
|
@@ -733,6 +818,13 @@ export class AdminApiClient {
|
|
|
733
818
|
* `namespaceId` names where to publish from, not the extent of the revocation:
|
|
734
819
|
* a device belongs to the account, so every namespace holding a binding for it
|
|
735
820
|
* is withdrawn from and reported back.
|
|
821
|
+
*
|
|
822
|
+
* **Revocation is forward-only.** It withdraws the device's authority from
|
|
823
|
+
* here on and does NOT retract past authorship: ops the device already signed
|
|
824
|
+
* keep verifying and stay in the DAG, because the certificate that authorised
|
|
825
|
+
* them was valid when the signature was made. Nothing in this system can
|
|
826
|
+
* un-author what a device wrote, so a UI built on this call must not offer
|
|
827
|
+
* that - "this device can no longer act" is the only claim it supports.
|
|
736
828
|
*/
|
|
737
829
|
async revokeAccountDevice(namespaceId, request) {
|
|
738
830
|
return unwrap(await this.httpClient.post(`/admin-api/namespaces/${namespaceId}/account/revoke`, request));
|
|
@@ -774,6 +866,150 @@ export class AdminApiClient {
|
|
|
774
866
|
}
|
|
775
867
|
return response;
|
|
776
868
|
}
|
|
869
|
+
/**
|
|
870
|
+
* Account -> devices for the members of a group, as its live bindings record
|
|
871
|
+
* them.
|
|
872
|
+
*
|
|
873
|
+
* The join between a group member listing, which names accounts, and a
|
|
874
|
+
* context's identities, which name bare signing keys - both 64 hex, and
|
|
875
|
+
* nothing but the source tells them apart. Batch rather than per-account
|
|
876
|
+
* because the key -> account direction gets asked about arbitrary authors,
|
|
877
|
+
* which a per-account route would answer in N calls.
|
|
878
|
+
*
|
|
879
|
+
* What comes back is **caller-scoped by the node**: an admin of the group sees
|
|
880
|
+
* every member's devices, a plain member only its own entry. A single-entry
|
|
881
|
+
* answer is therefore a statement about this caller's role, not about the
|
|
882
|
+
* group's size.
|
|
883
|
+
*
|
|
884
|
+
* Only live bindings appear, so a revoked device is absent here - use
|
|
885
|
+
* {@link listAccountDevices} for the account's own devices, which keeps a
|
|
886
|
+
* revoked one with `revoked: true`. Absence here never means the device never
|
|
887
|
+
* acted: revocation is forward-only and its past ops remain in the DAG.
|
|
888
|
+
*
|
|
889
|
+
* Wrapped in `members` on the wire rather than `data`, unwrapped here for the
|
|
890
|
+
* same reason {@link listAccountDevices} unwraps `devices`.
|
|
891
|
+
*/
|
|
892
|
+
async listGroupMemberDevices(groupId, options) {
|
|
893
|
+
// Both params are optional on the node, which clamps `limit` to its own
|
|
894
|
+
// maximum; sending them only when asked keeps the node's defaults authoritative
|
|
895
|
+
// rather than freezing today's values into this client.
|
|
896
|
+
const params = new URLSearchParams();
|
|
897
|
+
if (options?.offset !== undefined)
|
|
898
|
+
params.set('offset', String(options.offset));
|
|
899
|
+
if (options?.limit !== undefined)
|
|
900
|
+
params.set('limit', String(options.limit));
|
|
901
|
+
const query = params.toString();
|
|
902
|
+
const base = `/admin-api/groups/${encodeURIComponent(groupId)}/member-devices`;
|
|
903
|
+
const response = await this.httpClient.get(query ? `${base}?${query}` : base);
|
|
904
|
+
return response.members;
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Where `authorDeviceKey` stands in its warrant-nonce sequence in `contextId`.
|
|
908
|
+
*
|
|
909
|
+
* # What this is for
|
|
910
|
+
*
|
|
911
|
+
* The nonce is the one input to a warrant that is stateful on the *client*.
|
|
912
|
+
* Everything else an author needs it either holds (its keys, the method, the
|
|
913
|
+
* args) or can read from the relay ({@link RelayClient.describe} gives the
|
|
914
|
+
* executor account and the grant). So an author that has lost its counter —
|
|
915
|
+
* a browser keyholder in partitioned or periodically cleared storage, which
|
|
916
|
+
* is its normal lifecycle rather than a fault — otherwise has exactly one
|
|
917
|
+
* strategy: guess upward and burn a refused round trip per wrong guess, with
|
|
918
|
+
* each refusal indistinguishable at the API from a genuinely spent nonce.
|
|
919
|
+
*
|
|
920
|
+
* Read {@link WarrantNonceOpen.nextNonce} and mint there. If you still hold
|
|
921
|
+
* your own counter, mint at `max(ownNext, nextNonce)`: nonce state folds per
|
|
922
|
+
* peer, so a node can only be BEHIND a client whose warrants it has not
|
|
923
|
+
* applied — never ahead of the truth. An author spread across several relays
|
|
924
|
+
* asks each and takes the highest answer. {@link createRecoveringNonceSource}
|
|
925
|
+
* does all of that.
|
|
926
|
+
*
|
|
927
|
+
* Purely a read. It spends nothing and advances nothing, so it may be called
|
|
928
|
+
* as often as you like.
|
|
929
|
+
*
|
|
930
|
+
* # Forward compatibility — this route is NEW
|
|
931
|
+
*
|
|
932
|
+
* It is not on core `master` yet, so a current node answers **404**, which
|
|
933
|
+
* this method surfaces as {@link WarrantNonceRouteUnavailableError} rather
|
|
934
|
+
* than as an opaque {@link HTTPError}. That distinction matters because the
|
|
935
|
+
* handler itself never 404s: a device with no row is a `200` with
|
|
936
|
+
* `seen: false`, and a malformed id is a `400`. So a 404 here means the node
|
|
937
|
+
* does not serve the route at all — an older build, or a deployment that has
|
|
938
|
+
* not re-mounted it — and the answer is to fall back to a local counter, not
|
|
939
|
+
* to retry.
|
|
940
|
+
*
|
|
941
|
+
* # Authentication
|
|
942
|
+
*
|
|
943
|
+
* On the PROTECTED admin router, unlike the two delegated-execution routes it
|
|
944
|
+
* sits beside: it takes another principal's device key as input and reports
|
|
945
|
+
* that principal's activity. A `--public-intents` deployment does not serve
|
|
946
|
+
* this anonymously, so an admin credential is required.
|
|
947
|
+
*
|
|
948
|
+
* @param authorDeviceKey the device's signing key, base58 (43-44 chars) or
|
|
949
|
+
* hex (exactly 64). The node accepts either; the two never collide.
|
|
950
|
+
*/
|
|
951
|
+
async getWarrantNonce(contextId, authorDeviceKey) {
|
|
952
|
+
const path = `/admin-api/contexts/${encodeURIComponent(contextId)}/warrant-nonce/${encodeURIComponent(authorDeviceKey)}`;
|
|
953
|
+
// Read as TEXT, not JSON. `JSON.parse` would turn the `u64` nonces into
|
|
954
|
+
// doubles before this code ever sees them, rounding anything past 2^53 into
|
|
955
|
+
// a value that looks ordinary and is refused forever. The digits have to be
|
|
956
|
+
// taken from the bytes.
|
|
957
|
+
let raw;
|
|
958
|
+
try {
|
|
959
|
+
raw = await this.httpClient.get(path, { parse: 'text' });
|
|
960
|
+
}
|
|
961
|
+
catch (error) {
|
|
962
|
+
if (error instanceof HTTPError && error.status === 404) {
|
|
963
|
+
throw new WarrantNonceRouteUnavailableError(path, error);
|
|
964
|
+
}
|
|
965
|
+
throw error;
|
|
966
|
+
}
|
|
967
|
+
return parseWarrantNonce(raw);
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* The same answer, asked for the way a delegated client has to ask.
|
|
971
|
+
*
|
|
972
|
+
* {@link getWarrantNonce} is on the protected admin router, so a keyholder
|
|
973
|
+
* writing through a relay — the client this whole facility exists for — is
|
|
974
|
+
* refused it: measured `403` for a session carrying exactly
|
|
975
|
+
* `context:intent`, `context:query`, `context:subscribe`. That client has no
|
|
976
|
+
* admin credential and cannot be given one without defeating the point of the
|
|
977
|
+
* delegated surface.
|
|
978
|
+
*
|
|
979
|
+
* What it does have is the credential proving the device is its own, which it
|
|
980
|
+
* already sends on every write. Presenting it here makes the request
|
|
981
|
+
* self-scoped: you learn a device's position only by proving, at call time,
|
|
982
|
+
* that the device is yours. So there is no oracle to protect against, and no
|
|
983
|
+
* reason for an admin gate.
|
|
984
|
+
*
|
|
985
|
+
* The device key is NOT a parameter. It is read from the verified
|
|
986
|
+
* certificate server-side, which is the only reading that cannot disagree
|
|
987
|
+
* with the signature — a key supplied alongside would be a second spelling of
|
|
988
|
+
* the same fact, and two spellings can differ.
|
|
989
|
+
*
|
|
990
|
+
* Returns the identical shape {@link getWarrantNonce} returns, so a caller can
|
|
991
|
+
* swap one for the other without touching what it does with the answer.
|
|
992
|
+
*
|
|
993
|
+
* @param authorProof hex-encoded borsh `AccountProof<DeviceCert>` — the same
|
|
994
|
+
* value passed as `authorProof` to the relay client.
|
|
995
|
+
*/
|
|
996
|
+
async getWarrantNonceAsAuthor(contextId, authorProof) {
|
|
997
|
+
const path = `/admin-api/contexts/${encodeURIComponent(contextId)}/warrant-nonce`;
|
|
998
|
+
// Text, not JSON, for the reason given in `getWarrantNonce`: `JSON.parse`
|
|
999
|
+
// rounds a u64 past 2^53 into a nonce that looks ordinary and is refused
|
|
1000
|
+
// for ever.
|
|
1001
|
+
let raw;
|
|
1002
|
+
try {
|
|
1003
|
+
raw = await this.httpClient.post(path, { authorProof }, { parse: 'text' });
|
|
1004
|
+
}
|
|
1005
|
+
catch (error) {
|
|
1006
|
+
if (error instanceof HTTPError && error.status === 404) {
|
|
1007
|
+
throw new WarrantNonceRouteUnavailableError(path, error);
|
|
1008
|
+
}
|
|
1009
|
+
throw error;
|
|
1010
|
+
}
|
|
1011
|
+
return parseWarrantNonce(raw);
|
|
1012
|
+
}
|
|
777
1013
|
/**
|
|
778
1014
|
* Member accounts in the group and the devices bound to each. An admin node
|
|
779
1015
|
* sees every account; a plain member only its own. `403` if this node is not a
|