@calimero-network/mero-js 26.11.0 → 27.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/README.md +73 -288
- package/dist/cloud-v2/attestation.d.ts +91 -0
- package/dist/cloud-v2/attestation.js +135 -0
- package/dist/cloud-v2/capabilities.d.ts +30 -0
- package/dist/cloud-v2/capabilities.js +31 -0
- package/dist/cloud-v2/client.d.ts +426 -0
- package/dist/cloud-v2/client.js +646 -0
- package/dist/cloud-v2/errors.d.ts +98 -0
- package/dist/cloud-v2/errors.js +165 -0
- package/dist/cloud-v2/index.d.ts +10 -0
- package/dist/cloud-v2/index.js +5 -0
- package/dist/cloud-v2/passkey.d.ts +27 -0
- package/dist/cloud-v2/passkey.js +73 -0
- package/dist/crypto/internal.d.ts +3 -37
- package/dist/crypto/internal.js +3 -72
- package/dist/index.browser.mjs +4 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +1750 -12772
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -56
- package/dist/index.js +14 -75
- package/dist/index.mjs +1750 -12772
- package/dist/index.mjs.map +4 -4
- package/dist/sealed/index.d.ts +4 -4
- package/dist/sealed/index.js +2 -2
- package/dist/sealed/release.d.ts +21 -76
- package/dist/sealed/release.js +24 -108
- package/dist/sealed/sealed.d.ts +8 -56
- package/dist/sealed/sealed.js +11 -89
- package/dist/sealed/verify.d.ts +7 -13
- package/dist/sealed/verify.js +3 -7
- package/package.json +8 -13
- package/dist/account/account-admin.d.ts +0 -80
- package/dist/account/account-admin.js +0 -346
- package/dist/account/account.d.ts +0 -196
- package/dist/account/account.js +0 -269
- package/dist/account/application-id.d.ts +0 -50
- package/dist/account/application-id.js +0 -83
- package/dist/account/bootstrap-from-invitation.d.ts +0 -110
- package/dist/account/bootstrap-from-invitation.js +0 -80
- package/dist/account/create-context.d.ts +0 -103
- package/dist/account/create-context.js +0 -267
- package/dist/account/govern.d.ts +0 -24
- package/dist/account/govern.js +0 -50
- package/dist/account/index.d.ts +0 -26
- package/dist/account/index.js +0 -15
- package/dist/account/join-as-account.d.ts +0 -23
- package/dist/account/join-as-account.js +0 -34
- package/dist/account/join-with-node.d.ts +0 -115
- package/dist/account/join-with-node.js +0 -161
- package/dist/account/node-admin.d.ts +0 -20
- package/dist/account/node-admin.js +0 -33
- package/dist/account/relay-from-invitation.d.ts +0 -137
- package/dist/account/relay-from-invitation.js +0 -180
- package/dist/account/relay-login-error.d.ts +0 -32
- package/dist/account/relay-login-error.js +0 -34
- package/dist/account/session.d.ts +0 -266
- package/dist/account/session.js +0 -915
- package/dist/account/wordlist.d.ts +0 -16
- package/dist/account/wordlist.js +0 -273
- package/dist/admin-api/admin-client.d.ts +0 -688
- package/dist/admin-api/admin-client.js +0 -1230
- package/dist/admin-api/admin-factory.d.ts +0 -14
- package/dist/admin-api/admin-factory.js +0 -22
- package/dist/admin-api/admin-types.d.ts +0 -2046
- package/dist/admin-api/admin-types.js +0 -12
- package/dist/admin-api/index.d.ts +0 -4
- package/dist/admin-api/index.js +0 -5
- package/dist/admin-api/redeem-invitation.d.ts +0 -101
- package/dist/admin-api/redeem-invitation.js +0 -200
- package/dist/auth/index.d.ts +0 -34
- package/dist/auth/index.js +0 -67
- package/dist/auth-api/auth-client.d.ts +0 -31
- package/dist/auth-api/auth-client.js +0 -117
- package/dist/auth-api/auth-factory.d.ts +0 -7
- package/dist/auth-api/auth-factory.js +0 -41
- package/dist/auth-api/auth-types.d.ts +0 -130
- package/dist/auth-api/auth-types.js +0 -2
- package/dist/auth-api/index.d.ts +0 -3
- package/dist/auth-api/index.js +0 -4
- package/dist/capabilities.d.ts +0 -84
- package/dist/capabilities.js +0 -117
- package/dist/cloud/account-ownership.d.ts +0 -63
- package/dist/cloud/account-ownership.js +0 -87
- package/dist/cloud/cloud-client.d.ts +0 -949
- package/dist/cloud/cloud-client.js +0 -1068
- package/dist/cloud/connect.d.ts +0 -213
- package/dist/cloud/connect.js +0 -260
- package/dist/cloud/enrol-redirect.d.ts +0 -181
- package/dist/cloud/enrol-redirect.js +0 -244
- package/dist/cloud/index.d.ts +0 -12
- package/dist/cloud/index.js +0 -10
- package/dist/cloud/link-redirect.d.ts +0 -95
- package/dist/cloud/link-redirect.js +0 -121
- package/dist/cloud/link-state.d.ts +0 -36
- package/dist/cloud/link-state.js +0 -104
- package/dist/cloud/routing-proof.d.ts +0 -113
- package/dist/cloud/routing-proof.js +0 -96
- package/dist/device-cert/device-cert.d.ts +0 -189
- package/dist/device-cert/device-cert.js +0 -239
- package/dist/device-cert/device-scope.d.ts +0 -58
- package/dist/device-cert/device-scope.js +0 -76
- package/dist/device-cert/index.d.ts +0 -4
- package/dist/device-cert/index.js +0 -3
- package/dist/ephemeral/codec.d.ts +0 -4
- package/dist/ephemeral/codec.js +0 -12
- package/dist/ephemeral/index.d.ts +0 -57
- package/dist/ephemeral/index.js +0 -101
- package/dist/ephemeral/types.d.ts +0 -27
- package/dist/ephemeral/types.js +0 -1
- package/dist/errors/classify.d.ts +0 -65
- package/dist/errors/classify.js +0 -117
- package/dist/errors/index.d.ts +0 -2
- package/dist/errors/index.js +0 -1
- package/dist/events/group.d.ts +0 -95
- package/dist/events/group.js +0 -10
- package/dist/events/index.d.ts +0 -5
- package/dist/events/index.js +0 -2
- package/dist/events/sse.d.ts +0 -127
- package/dist/events/sse.js +0 -469
- package/dist/events/ws.d.ts +0 -57
- package/dist/events/ws.js +0 -212
- package/dist/http-client/api-response.d.ts +0 -15
- package/dist/http-client/api-response.js +0 -1
- package/dist/http-client/http-factory.d.ts +0 -71
- package/dist/http-client/http-factory.js +0 -68
- package/dist/http-client/http-types.d.ts +0 -87
- package/dist/http-client/http-types.js +0 -1
- package/dist/http-client/index.d.ts +0 -7
- package/dist/http-client/index.js +0 -11
- package/dist/http-client/path-segment.d.ts +0 -1
- package/dist/http-client/path-segment.js +0 -8
- package/dist/http-client/retry.d.ts +0 -5
- package/dist/http-client/retry.js +0 -82
- package/dist/http-client/signal-utils.d.ts +0 -2
- package/dist/http-client/signal-utils.js +0 -41
- package/dist/http-client/web-client.d.ts +0 -80
- package/dist/http-client/web-client.js +0 -557
- package/dist/invitation/index.d.ts +0 -2
- package/dist/invitation/index.js +0 -2
- package/dist/invitation/invitation.d.ts +0 -106
- package/dist/invitation/invitation.js +0 -104
- package/dist/login/index.d.ts +0 -4
- package/dist/login/index.js +0 -3
- package/dist/login/login.d.ts +0 -111
- package/dist/login/login.js +0 -110
- package/dist/login/session.d.ts +0 -124
- package/dist/login/session.js +0 -141
- package/dist/mero-js.d.ts +0 -231
- package/dist/mero-js.js +0 -469
- package/dist/namespace-op/index.d.ts +0 -2
- package/dist/namespace-op/index.js +0 -3
- package/dist/namespace-op/namespace-op.d.ts +0 -150
- package/dist/namespace-op/namespace-op.js +0 -191
- package/dist/nodeDiscovery.d.ts +0 -47
- package/dist/nodeDiscovery.js +0 -101
- package/dist/owner-op/index.d.ts +0 -2
- package/dist/owner-op/index.js +0 -3
- package/dist/owner-op/owner-op.d.ts +0 -119
- package/dist/owner-op/owner-op.js +0 -214
- package/dist/presence/relay-presence.d.ts +0 -56
- package/dist/presence/relay-presence.js +0 -107
- package/dist/presence/statement.d.ts +0 -12
- package/dist/presence/statement.js +0 -17
- package/dist/relay/index.d.ts +0 -4
- package/dist/relay/index.js +0 -2
- package/dist/relay/nonce-source.d.ts +0 -72
- package/dist/relay/nonce-source.js +0 -111
- package/dist/relay/relay-client.d.ts +0 -472
- package/dist/relay/relay-client.js +0 -518
- package/dist/relay-attestation/attest-node-key.d.ts +0 -66
- package/dist/relay-attestation/attest-node-key.js +0 -110
- package/dist/relay-attestation/index.d.ts +0 -1
- package/dist/relay-attestation/index.js +0 -1
- package/dist/request/caller-proof.d.ts +0 -43
- package/dist/request/caller-proof.js +0 -36
- package/dist/request/index.d.ts +0 -3
- package/dist/request/index.js +0 -3
- package/dist/request/proof-signer.d.ts +0 -49
- package/dist/request/proof-signer.js +0 -59
- package/dist/request/request-sig.d.ts +0 -99
- package/dist/request/request-sig.js +0 -95
- package/dist/request-proof/authorizer.d.ts +0 -41
- package/dist/request-proof/authorizer.js +0 -60
- package/dist/request-proof/index.d.ts +0 -4
- package/dist/request-proof/index.js +0 -2
- package/dist/request-proof/request-proof.d.ts +0 -53
- package/dist/request-proof/request-proof.js +0 -95
- package/dist/rpc/index.d.ts +0 -64
- package/dist/rpc/index.js +0 -78
- package/dist/signer/index.d.ts +0 -2
- package/dist/signer/index.js +0 -3
- package/dist/signer/signer.d.ts +0 -92
- package/dist/signer/signer.js +0 -147
- package/dist/token-store/index.d.ts +0 -27
- package/dist/token-store/index.js +0 -62
- package/dist/transport/index.d.ts +0 -6
- package/dist/transport/index.js +0 -3
- package/dist/transport/mero-client.d.ts +0 -294
- package/dist/transport/mero-client.js +0 -386
- package/dist/transport/relay-observer.d.ts +0 -152
- package/dist/transport/relay-observer.js +0 -173
- package/dist/transport/relay-transport.d.ts +0 -73
- package/dist/transport/relay-transport.js +0 -94
- package/dist/transport/types.d.ts +0 -100
- package/dist/transport/types.js +0 -32
- package/dist/types/api-response.d.ts +0 -15
- package/dist/types/api-response.js +0 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/warrant/creation-warrant.d.ts +0 -163
- package/dist/warrant/creation-warrant.js +0 -194
- package/dist/warrant/governance-op.d.ts +0 -202
- package/dist/warrant/governance-op.js +0 -355
- package/dist/warrant/governance-warrant.d.ts +0 -124
- package/dist/warrant/governance-warrant.js +0 -164
- package/dist/warrant/index.d.ts +0 -8
- package/dist/warrant/index.js +0 -5
- package/dist/warrant/warrant.d.ts +0 -188
- package/dist/warrant/warrant.js +0 -202
package/dist/sealed/verify.js
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
* signatures against Intel's root, the TCB status of the platform, the
|
|
6
6
|
* measurements of the image, and that its report data is the nonce and the
|
|
7
7
|
* binding this client asked for. {@link createQuoteVerifier} does all of that
|
|
8
|
-
* here
|
|
9
|
-
*
|
|
10
|
-
* collateral, only choose which valid collateral to serve.
|
|
8
|
+
* here. A Calimero 1.0 node sends no collateral: the verifier fetches Intel's
|
|
9
|
+
* (`fetchCollateral`), which the node can neither forge nor choose.
|
|
11
10
|
*
|
|
12
11
|
* The DCAP verification itself — parsing the quote, checking its signature
|
|
13
12
|
* chain and appraising its TCB level against the collateral — comes from a
|
|
@@ -41,10 +40,7 @@ const REGISTERS = ['mrtd', 'rtmr0', 'rtmr1', 'rtmr2', 'rtmr3'];
|
|
|
41
40
|
* dcapVerify,
|
|
42
41
|
* ...trustedMeasurementsFromReleases([publishedMrtds], { profile: 'locked-read-only' }),
|
|
43
42
|
* });
|
|
44
|
-
* const
|
|
45
|
-
* baseUrl,
|
|
46
|
-
* transportPublicKey: () => fetchAttestedTransportKey(admin, verifier),
|
|
47
|
-
* });
|
|
43
|
+
* const client = new CalimeroClient({ baseUrl, policy: { verifyQuote: verifier } });
|
|
48
44
|
* ```
|
|
49
45
|
*/
|
|
50
46
|
export function createQuoteVerifier(options) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calimero-network/mero-js",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "27.0.0",
|
|
4
|
+
"description": "JavaScript client for Calimero 1.0: attested TEE nodes, a sealed channel, passkeys and API keys",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -41,24 +41,18 @@
|
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "tsc && esbuild src/index.ts --bundle --platform=neutral --external:@phala/dcap-qvl --target=es2020 --format=esm --outfile=dist/index.mjs --sourcemap && esbuild src/index.ts --bundle --platform=neutral --external:@phala/dcap-qvl --target=es2020 --format=cjs --outfile=dist/index.cjs --sourcemap && esbuild src/index.ts --bundle --platform=browser --external:@phala/dcap-qvl --target=es2020 --format=esm --outfile=dist/index.browser.mjs --sourcemap --minify",
|
|
44
|
+
"prepare": "npm run build",
|
|
44
45
|
"test": "vitest run --passWithNoTests",
|
|
45
46
|
"test:unit": "vitest run --config vitest.config.ts",
|
|
46
|
-
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
47
|
-
"test:e2e:watch": "vitest --config vitest.e2e.config.ts",
|
|
48
|
-
"test:e2e:auth": "vitest run --config vitest.e2e.config.ts tests/e2e/auth-api.test.ts",
|
|
49
|
-
"test:e2e:admin": "vitest run --config vitest.e2e.config.ts tests/e2e/admin-api.test.ts",
|
|
50
|
-
"test:e2e:examples": "vitest run --config vitest.e2e.config.ts tests/e2e/examples.test.ts",
|
|
51
47
|
"lint": "eslint .",
|
|
52
48
|
"lint:fix": "eslint . --fix",
|
|
53
49
|
"eslint": "eslint .",
|
|
54
50
|
"typecheck": "tsc --noEmit",
|
|
55
|
-
"typecheck:contract": "tsc --noEmit -p tsconfig.contract.json",
|
|
56
51
|
"typecheck:consumer": "tsc --noEmit -p tsconfig.consumer.json",
|
|
57
|
-
"typecheck:e2e": "tsc --noEmit -p tsconfig.e2e.json",
|
|
58
|
-
"test:contract": "vitest run --config vitest.config.ts src/__contract__",
|
|
59
52
|
"tsc": "tsc -b",
|
|
60
53
|
"clean": "pnpm rimraf dist",
|
|
61
|
-
"prepublishOnly": "pnpm clean && pnpm build && pnpm test:unit && pnpm lint && pnpm typecheck && pnpm typecheck:consumer
|
|
54
|
+
"prepublishOnly": "pnpm clean && pnpm build && pnpm test:unit && pnpm lint && pnpm typecheck && pnpm typecheck:consumer",
|
|
55
|
+
"test:cloud-v2": "vitest run --config vitest.cloud-v2.config.ts"
|
|
62
56
|
},
|
|
63
57
|
"files": [
|
|
64
58
|
"dist"
|
|
@@ -67,8 +61,9 @@
|
|
|
67
61
|
"calimero",
|
|
68
62
|
"mero",
|
|
69
63
|
"javascript",
|
|
70
|
-
"
|
|
71
|
-
"
|
|
64
|
+
"tee",
|
|
65
|
+
"confidential-computing",
|
|
66
|
+
"passkeys",
|
|
72
67
|
"sdk"
|
|
73
68
|
],
|
|
74
69
|
"license": "MIT",
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* An account's admin API: the same method names and types as mero-js's
|
|
3
|
-
* `AdminApiClient`, so an app written against a node's admin needs no second
|
|
4
|
-
* code path. Reads go to the relay's caller-scoped admin client; writes go
|
|
5
|
-
* through the relay as governance ops, delegated context and namespace
|
|
6
|
-
* creation, or an invitation the account signs itself. A write with no
|
|
7
|
-
* account form (upgrades, installs) is refused by name.
|
|
8
|
-
*/
|
|
9
|
-
import { type AdminApiClient, type SignedGroupOpenInvitation } from '../admin-api/index.js';
|
|
10
|
-
import { signGroupInvitation } from '../invitation/index.js';
|
|
11
|
-
import type { DelegatedSession } from './session.js';
|
|
12
|
-
import { governGroup, governRoot } from './govern.js';
|
|
13
|
-
import { createDelegatedContext, foundDelegatedNamespace } from './create-context.js';
|
|
14
|
-
export declare class NotForAccountError extends Error {
|
|
15
|
-
readonly method: string;
|
|
16
|
-
constructor(method: string);
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* An account that has enrolled but joined nothing has no relay: nothing serves
|
|
20
|
-
* it yet. Its listings are empty, which is true; anything else names this.
|
|
21
|
-
*/
|
|
22
|
-
export declare class NoRelayError extends Error {
|
|
23
|
-
readonly method: string;
|
|
24
|
-
constructor(method: string);
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Why an account's invitation was not minted: nobody its claimant could reach
|
|
28
|
-
* would admit them. A claimant with no node finds its admitter through the
|
|
29
|
-
* cloud's routing, intersected with the invitation's signed admitters, so an
|
|
30
|
-
* invitation the cloud routes to none of them can only fail in their hands.
|
|
31
|
-
*
|
|
32
|
-
* - `not-hosted` - the cloud lists no node holding the namespace. A relay it
|
|
33
|
-
* does not list cannot stand in: an address on the invitation is unsigned.
|
|
34
|
-
* - `no-named-node` — the cloud's nodes are not among the admitters this
|
|
35
|
-
* invitation would name.
|
|
36
|
-
*/
|
|
37
|
-
export declare class InvitationNotClaimableError extends Error {
|
|
38
|
-
readonly namespaceId: string;
|
|
39
|
-
readonly reason: 'not-hosted' | 'no-named-node';
|
|
40
|
-
constructor(namespaceId: string, reason: 'not-hosted' | 'no-named-node');
|
|
41
|
-
}
|
|
42
|
-
export interface AccountAdminDeps {
|
|
43
|
-
govern?: {
|
|
44
|
-
group: typeof governGroup;
|
|
45
|
-
root: typeof governRoot;
|
|
46
|
-
};
|
|
47
|
-
createContext?: typeof createDelegatedContext;
|
|
48
|
-
found?: typeof foundDelegatedNamespace;
|
|
49
|
-
latestVersion?: (registryUrl: string, pkg: string) => Promise<string>;
|
|
50
|
-
signInvitation?: typeof signGroupInvitation;
|
|
51
|
-
/**
|
|
52
|
-
* Redeem an invitation for this account: resolve the relay the invitation
|
|
53
|
-
* names, have it admit the account, and switch the session onto that relay.
|
|
54
|
-
* The provider supplies it (it owns the session); without it a join is
|
|
55
|
-
* refused by name. Rejects with the refusal's reason and HTTP `status`.
|
|
56
|
-
*/
|
|
57
|
-
join?: (namespaceId: string, invitation: SignedGroupOpenInvitation) => Promise<{
|
|
58
|
-
namespaceId: string;
|
|
59
|
-
}>;
|
|
60
|
-
/**
|
|
61
|
-
* The cloud's routing for a namespace (`CloudClient.getNamespaceRouting`):
|
|
62
|
-
* the nodes a node-less invitee can be admitted through. Given, an
|
|
63
|
-
* invitation is minted only if one of them is an admitter it names; a lookup
|
|
64
|
-
* that fails does not block minting. The provider supplies it.
|
|
65
|
-
*/
|
|
66
|
-
routing?: (namespaceId: string) => Promise<{
|
|
67
|
-
nodes: ReadonlyArray<{
|
|
68
|
-
account: string | null;
|
|
69
|
-
}>;
|
|
70
|
-
}>;
|
|
71
|
-
}
|
|
72
|
-
export declare function createAccountAdmin(input: {
|
|
73
|
-
session: DelegatedSession;
|
|
74
|
-
read: AdminApiClient | null;
|
|
75
|
-
app: {
|
|
76
|
-
packageName?: string;
|
|
77
|
-
packageVersion?: string;
|
|
78
|
-
registryUrl?: string;
|
|
79
|
-
};
|
|
80
|
-
}, deps?: AccountAdminDeps): AdminApiClient;
|
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
import { groupDeletedOp, groupMetadataSetOp, groupReparentedOp, memberAddedOp, memberCapabilitySetOp, memberJoinedOpenOp, memberLeftOp, memberMetadataSetOp, memberRemovedOp, contextMetadataSetOp, defaultCapabilitiesSetOp, subgroupCreation, subgroupVisibilitySetOp, memberRoleSetOp, contextDetachedOp, } from '../warrant/index.js';
|
|
2
|
-
import { signGroupInvitation, } from '../invitation/index.js';
|
|
3
|
-
import { governGroup, governRoot, namespaceOfGroup, rememberGroupNamespace } from './govern.js';
|
|
4
|
-
import { createDelegatedContext, foundDelegatedNamespace, latestPublishedVersion } from './create-context.js';
|
|
5
|
-
export class NotForAccountError extends Error {
|
|
6
|
-
constructor(method) {
|
|
7
|
-
super(`${method} is not available for an account: it needs a node`);
|
|
8
|
-
this.method = method;
|
|
9
|
-
this.name = 'NotForAccountError';
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* An account that has enrolled but joined nothing has no relay: nothing serves
|
|
14
|
-
* it yet. Its listings are empty, which is true; anything else names this.
|
|
15
|
-
*/
|
|
16
|
-
export class NoRelayError extends Error {
|
|
17
|
-
constructor(method) {
|
|
18
|
-
super(`${method} needs a relay, and this account has none yet: join from an invitation first`);
|
|
19
|
-
this.method = method;
|
|
20
|
-
this.name = 'NoRelayError';
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Why an account's invitation was not minted: nobody its claimant could reach
|
|
25
|
-
* would admit them. A claimant with no node finds its admitter through the
|
|
26
|
-
* cloud's routing, intersected with the invitation's signed admitters, so an
|
|
27
|
-
* invitation the cloud routes to none of them can only fail in their hands.
|
|
28
|
-
*
|
|
29
|
-
* - `not-hosted` - the cloud lists no node holding the namespace. A relay it
|
|
30
|
-
* does not list cannot stand in: an address on the invitation is unsigned.
|
|
31
|
-
* - `no-named-node` — the cloud's nodes are not among the admitters this
|
|
32
|
-
* invitation would name.
|
|
33
|
-
*/
|
|
34
|
-
export class InvitationNotClaimableError extends Error {
|
|
35
|
-
constructor(namespaceId, reason) {
|
|
36
|
-
super(reason === 'not-hosted'
|
|
37
|
-
? `nobody could claim an invitation to ${namespaceId} yet: the cloud lists no node holding the namespace, so an invitee with no node of their own has no relay to be admitted through.`
|
|
38
|
-
: `nobody could claim an invitation to ${namespaceId}: the cloud routes to none of the relays or admins it would name as admitters`);
|
|
39
|
-
this.namespaceId = namespaceId;
|
|
40
|
-
this.reason = reason;
|
|
41
|
-
this.name = 'InvitationNotClaimableError';
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/** What an account with no relay answers: it is a member of nothing. */
|
|
45
|
-
const EMPTY_READS = {
|
|
46
|
-
listNamespaces: async () => [],
|
|
47
|
-
listNamespacesForApplication: async () => [],
|
|
48
|
-
getContexts: async () => ({ contexts: [] }),
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Calls with no account form: refused by name, so an app sees why rather than
|
|
52
|
-
* the bare 403 a relay gives a call its session may not make. Everything not
|
|
53
|
-
* listed and not overridden below is a read, answered by the relay.
|
|
54
|
-
*
|
|
55
|
-
* - Upgrades, installs, a node's own context identity: a node's to do.
|
|
56
|
-
* - `deleteContext`: a node drops its own copy and the group keeps the context.
|
|
57
|
-
* An account's copy is the relay's, shared with every account it serves;
|
|
58
|
-
* removing a context for the group is `detachContextFromGroup` or
|
|
59
|
-
* `deleteGroup`.
|
|
60
|
-
* - Aliases, read or written: they are a node's own names, and a relay's are the
|
|
61
|
-
* relay's. Core gives an account session no alias permission.
|
|
62
|
-
* - Deleting a namespace, legacy group invitations and joins, TEE policy, blob
|
|
63
|
-
* deletion: core has no delegated form for them.
|
|
64
|
-
* - Account devices: the wallet manages them, not an app.
|
|
65
|
-
*
|
|
66
|
-
* Upgrade, cascade and migration status are NOT here: core serves an account
|
|
67
|
-
* its own groups' status (#4392, rc.76), and migration status to the account
|
|
68
|
-
* that administers the namespace (#4400, rc.77), so they go to the relay.
|
|
69
|
-
*/
|
|
70
|
-
const NODE_ONLY = new Set([
|
|
71
|
-
'upgradeGroup', 'retryGroupUpgrade', 'activateGroupUpgrade', 'abortMigration', 'installApplication', 'installDevApplication',
|
|
72
|
-
'uninstallApplication', 'generateContextIdentity', 'deleteContext',
|
|
73
|
-
// A node's leaveContext is a local opt-out (a tombstone, its own identity rows)
|
|
74
|
-
// and publishes nothing; an account has nothing local to opt out of, and
|
|
75
|
-
// leaving the context's group instead is leaveGroup.
|
|
76
|
-
'leaveContext',
|
|
77
|
-
'createContextAlias', 'createApplicationAlias', 'createDeviceAlias',
|
|
78
|
-
'deleteContextAlias', 'deleteApplicationAlias', 'deleteDeviceAlias',
|
|
79
|
-
'lookupContextAlias', 'lookupApplicationAlias', 'lookupDeviceAlias',
|
|
80
|
-
'listContextAliases', 'listApplicationAliases', 'listDeviceAliases',
|
|
81
|
-
'deleteNamespace', 'createGroupInvitation', 'joinGroup',
|
|
82
|
-
'setTeeAdmissionPolicy', 'getTeeAdmissionPolicy', 'deleteBlob',
|
|
83
|
-
'listAccountDevices', 'revokeAccountDevice',
|
|
84
|
-
]);
|
|
85
|
-
/**
|
|
86
|
-
* Core's refusal of a MemberJoinedOpen from someone already a direct member
|
|
87
|
-
* (`AlreadyDirectMember`, answered 409): "signer … is a direct member; use
|
|
88
|
-
* MemberJoined or add_group_members instead". Not to be confused with "is not a
|
|
89
|
-
* direct member", which a leave by an inherited member gets.
|
|
90
|
-
*/
|
|
91
|
-
function isAlreadyDirectMember(e) {
|
|
92
|
-
if (!(e instanceof Error))
|
|
93
|
-
return false;
|
|
94
|
-
const status = e.status;
|
|
95
|
-
return (status === undefined || status === 409) && /signer [0-9a-f]+ is a direct member/i.test(e.message);
|
|
96
|
-
}
|
|
97
|
-
export function createAccountAdmin(input, deps = {}) {
|
|
98
|
-
const { session: s, read, app } = input;
|
|
99
|
-
const governGroupOp = deps.govern?.group ?? governGroup;
|
|
100
|
-
const governRootOp = deps.govern?.root ?? governRoot;
|
|
101
|
-
const createContext = deps.createContext ?? createDelegatedContext;
|
|
102
|
-
const found = deps.found ?? foundDelegatedNamespace;
|
|
103
|
-
const latestVersion = deps.latestVersion ?? latestPublishedVersion;
|
|
104
|
-
const signInvitation = deps.signInvitation ?? signGroupInvitation;
|
|
105
|
-
const join = deps.join;
|
|
106
|
-
const routing = deps.routing;
|
|
107
|
-
// Reachable with or without a relay: the join is how an account gets one.
|
|
108
|
-
async function joinNamespace(namespaceId, req) {
|
|
109
|
-
if (!join)
|
|
110
|
-
throw new NotForAccountError('joinNamespace without a join handler');
|
|
111
|
-
return join(namespaceId, req.invitation);
|
|
112
|
-
}
|
|
113
|
-
// Only reached through the relay proxy below, so a null here is unreachable;
|
|
114
|
-
// the guard keeps the types honest rather than asserting it away.
|
|
115
|
-
const relay = () => {
|
|
116
|
-
if (read === null)
|
|
117
|
-
throw new NoRelayError('this call');
|
|
118
|
-
return read;
|
|
119
|
-
};
|
|
120
|
-
// A group or root op is posted to the group's namespace. A subgroup this
|
|
121
|
-
// account neither created nor joined (an open channel someone else made) is
|
|
122
|
-
// one the relay can name: ask it once, as `createContext` does, and remember.
|
|
123
|
-
async function knowNamespaceOf(groupId) {
|
|
124
|
-
if (namespaceOfGroup(s, groupId))
|
|
125
|
-
return;
|
|
126
|
-
const info = await relay().getGroupInfo(groupId);
|
|
127
|
-
rememberGroupNamespace(s.account, groupId, info.namespaceId ?? groupId);
|
|
128
|
-
}
|
|
129
|
-
const group = async (session, groupId, op) => {
|
|
130
|
-
await knowNamespaceOf(groupId);
|
|
131
|
-
return governGroupOp(session, groupId, op);
|
|
132
|
-
};
|
|
133
|
-
const root = async (session, groupId, op) => {
|
|
134
|
-
await knowNamespaceOf(groupId);
|
|
135
|
-
return governRootOp(session, groupId, op);
|
|
136
|
-
};
|
|
137
|
-
// MemberJoinedOpen on `groupId`, as a node's join sends it: a member who only
|
|
138
|
-
// inherits becomes a direct one, and a direct member's join is a no-op. The
|
|
139
|
-
// member list cannot tell those two apart (it lists inherited members too), so
|
|
140
|
-
// the join is sent and core's refusal of a direct member is the no-op.
|
|
141
|
-
// Returns whether this join made the account a member.
|
|
142
|
-
async function joinOpen(groupId) {
|
|
143
|
-
try {
|
|
144
|
-
await root(s, groupId, memberJoinedOpenOp({ member: s.account, groupId, credential: s.credential }));
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
catch (e) {
|
|
148
|
-
if (isAlreadyDirectMember(e))
|
|
149
|
-
return false;
|
|
150
|
-
throw e;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
async function isMemberOfContextGroup(contextId, knownGroupId) {
|
|
154
|
-
const groupId = knownGroupId ?? String(await relay().getContextGroup(contextId));
|
|
155
|
-
const { members } = await relay().listGroupMembers(groupId);
|
|
156
|
-
return members.some((m) => m.identity.toLowerCase() === s.account.toLowerCase());
|
|
157
|
-
}
|
|
158
|
-
const writes = {
|
|
159
|
-
// The node-wide `for-application` listing is not caller-scoped, so a relay
|
|
160
|
-
// refuses it to an account. Its own scoped list, filtered, is the answer.
|
|
161
|
-
async listNamespacesForApplication(applicationId) {
|
|
162
|
-
return (await relay().listNamespaces()).filter((ns) => ns.targetApplicationId === applicationId);
|
|
163
|
-
},
|
|
164
|
-
async getNodeIdentity() {
|
|
165
|
-
return { accountId: s.account, deviceId: null, publicKey: '', deviceCertified: true };
|
|
166
|
-
},
|
|
167
|
-
async createNamespace(req) {
|
|
168
|
-
if (!app.packageName)
|
|
169
|
-
throw new NotForAccountError('createNamespace without a packageName');
|
|
170
|
-
const version = app.packageVersion ?? (await latestVersion(app.registryUrl ?? 'https://apps.calimero.network', app.packageName));
|
|
171
|
-
const { namespaceId, haEnabled, haError } = await found(s, {
|
|
172
|
-
defaultCapabilities: 231,
|
|
173
|
-
application: { applicationId: req.applicationId, package: app.packageName, version },
|
|
174
|
-
});
|
|
175
|
-
if (req.name)
|
|
176
|
-
await group(s, namespaceId, groupMetadataSetOp({ name: req.name }));
|
|
177
|
-
// A node's result is `{ namespaceId }`; HA's outcome rides along as extra
|
|
178
|
-
// fields, so code typed against the node call is unaffected.
|
|
179
|
-
return { namespaceId, haEnabled, ...(haError !== undefined ? { haError } : {}) };
|
|
180
|
-
},
|
|
181
|
-
async createGroupInNamespace(namespaceId, req = {}) {
|
|
182
|
-
// Absent ⇒ Open, as on a node: a subgroup created Restricted and flipped
|
|
183
|
-
// Open strands every namespace member outside it.
|
|
184
|
-
const { op } = await subgroupCreation({ parentId: namespaceId, restricted: req.visibility === 'restricted', admin: s.account });
|
|
185
|
-
const { groupId } = await root(s, namespaceId, op);
|
|
186
|
-
rememberGroupNamespace(s.account, groupId, namespaceId);
|
|
187
|
-
if (req.groupName)
|
|
188
|
-
await group(s, groupId, groupMetadataSetOp({ name: req.groupName }));
|
|
189
|
-
return { groupId };
|
|
190
|
-
},
|
|
191
|
-
async addGroupMembers(groupId, req) {
|
|
192
|
-
for (const m of req.members)
|
|
193
|
-
await group(s, groupId, memberAddedOp(m.identity, (m.role ?? 'Member')));
|
|
194
|
-
},
|
|
195
|
-
async removeGroupMembers(groupId, req) {
|
|
196
|
-
for (const m of req.members)
|
|
197
|
-
await group(s, groupId, memberRemovedOp(m));
|
|
198
|
-
},
|
|
199
|
-
async setGroupMetadata(groupId, req) {
|
|
200
|
-
await group(s, groupId, groupMetadataSetOp(req));
|
|
201
|
-
},
|
|
202
|
-
async setMemberMetadata(groupId, identity, req) {
|
|
203
|
-
await group(s, groupId, memberMetadataSetOp(identity, req));
|
|
204
|
-
},
|
|
205
|
-
async setContextMetadata(groupId, contextId, req) {
|
|
206
|
-
await group(s, groupId, contextMetadataSetOp(contextId, req));
|
|
207
|
-
},
|
|
208
|
-
async setDefaultCapabilities(groupId, req) {
|
|
209
|
-
await group(s, groupId, defaultCapabilitiesSetOp(req.defaultCapabilities));
|
|
210
|
-
},
|
|
211
|
-
async setSubgroupVisibility(groupId, req) {
|
|
212
|
-
await group(s, groupId, subgroupVisibilitySetOp(req.subgroupVisibility === 'open' ? 'open' : 'restricted'));
|
|
213
|
-
},
|
|
214
|
-
async setMemberCapabilities(groupId, identity, req) {
|
|
215
|
-
await group(s, groupId, memberCapabilitySetOp(identity, req.capabilities));
|
|
216
|
-
},
|
|
217
|
-
async deleteGroup(groupId) {
|
|
218
|
-
await root(s, groupId, groupDeletedOp(groupId));
|
|
219
|
-
return { isDeleted: true };
|
|
220
|
-
},
|
|
221
|
-
async reparentGroup(childGroupId, req) {
|
|
222
|
-
await root(s, childGroupId, groupReparentedOp(childGroupId, req.newParentId));
|
|
223
|
-
return { reparented: true };
|
|
224
|
-
},
|
|
225
|
-
async leaveGroup(groupId) {
|
|
226
|
-
await group(s, groupId, memberLeftOp(s.account));
|
|
227
|
-
},
|
|
228
|
-
async leaveNamespace(namespaceId) {
|
|
229
|
-
await group(s, namespaceId, memberLeftOp(s.account));
|
|
230
|
-
},
|
|
231
|
-
// An account runs as itself in every context it can reach (the relay
|
|
232
|
-
// executes as the account), so its identity there is the account. Owned
|
|
233
|
-
// when its account is a member of the context's group, as on a node a
|
|
234
|
-
// context identity is owned once the node has joined.
|
|
235
|
-
async getContextIdentitiesOwned(contextId) {
|
|
236
|
-
return { identities: (await isMemberOfContextGroup(contextId)) ? [s.account] : [] };
|
|
237
|
-
},
|
|
238
|
-
async joinContext(contextId) {
|
|
239
|
-
await joinOpen(String(await relay().getContextGroup(contextId)));
|
|
240
|
-
return { contextId, memberPublicKey: s.account };
|
|
241
|
-
},
|
|
242
|
-
async createContext(req) {
|
|
243
|
-
const info = await relay().getGroupInfo(req.groupId);
|
|
244
|
-
const namespaceId = info.namespaceId ?? req.groupId;
|
|
245
|
-
// Every field with a place in the creation warrant; `identitySecret` has
|
|
246
|
-
// none for an account (see CreateDelegatedContextRequest).
|
|
247
|
-
const { contextId } = await createContext(s, {
|
|
248
|
-
namespaceId, groupId: req.groupId, applicationId: req.applicationId,
|
|
249
|
-
serviceName: req.serviceName, contextSeed: req.contextSeed,
|
|
250
|
-
name: req.name, initializationParams: req.initializationParams,
|
|
251
|
-
});
|
|
252
|
-
// The relay executes as the account, so the account is its member key
|
|
253
|
-
// here as it is in `joinContext` and `getContextIdentitiesOwned`. Apps
|
|
254
|
-
// record this key as the context's owner; an empty one was being stored.
|
|
255
|
-
return { contextId, memberPublicKey: s.account, groupId: req.groupId };
|
|
256
|
-
},
|
|
257
|
-
async createNamespaceInvitation(namespaceId) {
|
|
258
|
-
const [{ members }, info] = await Promise.all([relay().listGroupMembers(namespaceId), relay().getGroupInfo(namespaceId)]);
|
|
259
|
-
// The relays admit: whoever claims an account's invitation may have no
|
|
260
|
-
// node, and only a relay can admit a joiner with none. With no relay in
|
|
261
|
-
// the namespace, leave it to the admins (signGroupInvitation's default).
|
|
262
|
-
const relays = members
|
|
263
|
-
.filter((m) => m.role === 'RelayTee')
|
|
264
|
-
.map((m) => m.identity.toLowerCase());
|
|
265
|
-
if (routing) {
|
|
266
|
-
// Who the claimant's client will look for: the admitters this
|
|
267
|
-
// invitation names (the relays, else signGroupInvitation's admins).
|
|
268
|
-
const named = new Set(relays.length > 0
|
|
269
|
-
? relays
|
|
270
|
-
: members.filter((m) => m.role === 'Admin').map((m) => m.identity.toLowerCase()));
|
|
271
|
-
let nodes = null;
|
|
272
|
-
try {
|
|
273
|
-
nodes = (await routing(namespaceId)).nodes;
|
|
274
|
-
}
|
|
275
|
-
catch {
|
|
276
|
-
// Nothing learned: the claimant's own lookup will say what is wrong.
|
|
277
|
-
}
|
|
278
|
-
if (nodes !== null) {
|
|
279
|
-
if (nodes.length === 0)
|
|
280
|
-
throw new InvitationNotClaimableError(namespaceId, 'not-hosted');
|
|
281
|
-
if (!nodes.some((n) => n.account !== null && named.has(n.account.toLowerCase()))) {
|
|
282
|
-
throw new InvitationNotClaimableError(namespaceId, 'no-named-node');
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
const invitation = await signInvitation({
|
|
287
|
-
groupId: namespaceId,
|
|
288
|
-
inviterAccount: s.account,
|
|
289
|
-
deviceSecret: s.deviceSecret,
|
|
290
|
-
...(relays.length > 0 ? { admitters: relays } : {}),
|
|
291
|
-
members: members,
|
|
292
|
-
applicationId: info.targetApplicationId,
|
|
293
|
-
appKey: info.appKey,
|
|
294
|
-
});
|
|
295
|
-
return { invitation };
|
|
296
|
-
},
|
|
297
|
-
// The relay syncs its own view; an account has nothing to pull.
|
|
298
|
-
async syncGroup(groupId) {
|
|
299
|
-
return { groupId, synced: true };
|
|
300
|
-
},
|
|
301
|
-
async syncContext() {
|
|
302
|
-
// Same: nothing for an account to pull.
|
|
303
|
-
},
|
|
304
|
-
joinNamespace,
|
|
305
|
-
async updateMemberRole(groupId, identity, req) {
|
|
306
|
-
await group(s, groupId, memberRoleSetOp(identity, req.role));
|
|
307
|
-
},
|
|
308
|
-
// An open subgroup is joined the way `joinContext` joins one: MemberJoinedOpen
|
|
309
|
-
// on the subgroup, and nothing to do for a member already in it.
|
|
310
|
-
async joinSubgroupInheritance(groupId) {
|
|
311
|
-
return { groupId, memberPublicKey: s.account, wasInherited: await joinOpen(groupId) };
|
|
312
|
-
},
|
|
313
|
-
async detachContextFromGroup(groupId, contextId) {
|
|
314
|
-
await group(s, groupId, contextDetachedOp(contextId));
|
|
315
|
-
},
|
|
316
|
-
};
|
|
317
|
-
if (read === null) {
|
|
318
|
-
return new Proxy({}, {
|
|
319
|
-
get(_target, prop) {
|
|
320
|
-
if (typeof prop !== 'string')
|
|
321
|
-
return undefined;
|
|
322
|
-
if (prop === 'getNodeIdentity')
|
|
323
|
-
return writes.getNodeIdentity;
|
|
324
|
-
if (prop === 'joinNamespace')
|
|
325
|
-
return joinNamespace;
|
|
326
|
-
if (prop in EMPTY_READS)
|
|
327
|
-
return EMPTY_READS[prop];
|
|
328
|
-
return async () => {
|
|
329
|
-
throw new NoRelayError(prop);
|
|
330
|
-
};
|
|
331
|
-
},
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
return new Proxy(read, {
|
|
335
|
-
get(target, prop, receiver) {
|
|
336
|
-
if (typeof prop === 'string' && prop in writes)
|
|
337
|
-
return writes[prop];
|
|
338
|
-
if (typeof prop === 'string' && NODE_ONLY.has(prop)) {
|
|
339
|
-
return async () => {
|
|
340
|
-
throw new NotForAccountError(prop);
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
return Reflect.get(target, prop, receiver);
|
|
344
|
-
},
|
|
345
|
-
});
|
|
346
|
-
}
|