@calimero-network/mero-js 23.4.3 → 23.6.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/cloud/account-ownership.d.ts +37 -4
- package/dist/cloud/account-ownership.js +40 -8
- package/dist/cloud/cloud-client.d.ts +100 -1
- package/dist/cloud/cloud-client.js +148 -0
- package/dist/cloud/index.d.ts +4 -4
- package/dist/cloud/index.js +2 -2
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +172 -42
- package/dist/index.cjs.map +4 -4
- package/dist/index.mjs +172 -42
- package/dist/index.mjs.map +4 -4
- package/package.json +1 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type Signer } from '../signer/signer.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* The `ownership_proof` body for `POST /api/cloud/me/namespaces/{ns}/enable-ha`
|
|
4
|
+
* and for `POST /api/cloud/accounts/{account}/namespaces/{ns}/enable-ha`.
|
|
5
|
+
*/
|
|
3
6
|
export interface AccountOwnershipProof {
|
|
4
7
|
kind: 'account';
|
|
5
8
|
/** hex `AccountProof<DeviceCert>`, the account's credential. */
|
|
@@ -9,15 +12,14 @@ export interface AccountOwnershipProof {
|
|
|
9
12
|
/** base64 ed25519 by the certified device key. */
|
|
10
13
|
signature: string;
|
|
11
14
|
}
|
|
12
|
-
|
|
15
|
+
/** What every account claim needs; the audience and `subject` differ per route. */
|
|
16
|
+
interface AccountClaimBase {
|
|
13
17
|
/** The namespace, hex: the id `foundNamespace` returned. */
|
|
14
18
|
namespaceId: string;
|
|
15
19
|
/** The founding account, hex. */
|
|
16
20
|
accountId: string;
|
|
17
21
|
/** The salt `foundNamespace` returned, hex (32 bytes). */
|
|
18
22
|
salt: string;
|
|
19
|
-
/** The cloud login's email: the claim is for this login only. */
|
|
20
|
-
subject: string;
|
|
21
23
|
/** The account's credential, hex `AccountProof<DeviceCert>`. */
|
|
22
24
|
credential: string;
|
|
23
25
|
deviceSecret?: string;
|
|
@@ -27,4 +29,35 @@ export interface AccountOwnershipClaimInput {
|
|
|
27
29
|
/** For tests. */
|
|
28
30
|
now?: () => number;
|
|
29
31
|
}
|
|
32
|
+
export interface AccountOwnershipClaimInput extends AccountClaimBase {
|
|
33
|
+
/** The cloud login's email: the claim is for this login only. */
|
|
34
|
+
subject: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Input for {@link signAccountHaClaim}: the same as the session claim, minus
|
|
38
|
+
* `subject` — there is no login to bind it to.
|
|
39
|
+
*/
|
|
40
|
+
export interface AccountHaClaimInput extends AccountClaimBase {
|
|
41
|
+
/**
|
|
42
|
+
* The relay that founded the namespace (the delegated session's `relayUrl`).
|
|
43
|
+
* Signed into the claim so nobody forwarding it can swap it: the cloud
|
|
44
|
+
* resolves it to one of its own fleet relays and hands that relay's
|
|
45
|
+
* addresses to the fleet node as its admitter. Leave it out when unknown.
|
|
46
|
+
*/
|
|
47
|
+
relayUrl?: string;
|
|
48
|
+
}
|
|
49
|
+
/** The audience of the session route's claim (`/api/cloud/me/...`). */
|
|
50
|
+
export declare const ACCOUNT_OWNERSHIP_AUDIENCE = "mdma:enable-ha-namespace";
|
|
51
|
+
/** The audience of the anonymous account route's claim (`/api/cloud/accounts/...`). */
|
|
52
|
+
export declare const ACCOUNT_HA_AUDIENCE = "mdma:enable-ha-namespace-as-account";
|
|
53
|
+
/** The founder's claim for the SESSION route, bound to one cloud login (`subject`). */
|
|
30
54
|
export declare function signAccountOwnershipClaim(input: AccountOwnershipClaimInput): Promise<AccountOwnershipProof>;
|
|
55
|
+
/**
|
|
56
|
+
* The founder's claim for the ANONYMOUS account route
|
|
57
|
+
* (`POST /api/cloud/accounts/{account}/namespaces/{ns}/enable-ha`): no cloud
|
|
58
|
+
* session, so no `subject`, and its own audience so a claim minted for one
|
|
59
|
+
* route can never be replayed on the other. The cloud finds the user to bill
|
|
60
|
+
* through the account's wallet link instead.
|
|
61
|
+
*/
|
|
62
|
+
export declare function signAccountHaClaim(input: AccountHaClaimInput): Promise<AccountOwnershipProof>;
|
|
63
|
+
export {};
|
|
@@ -19,6 +19,12 @@ const DOMAIN = new TextEncoder().encode('calimero.mdma.account-ownership-claim.v
|
|
|
19
19
|
/** What the cloud accepts; it caps a claim's lifetime at five minutes. */
|
|
20
20
|
const DEFAULT_TTL_MS = 60000;
|
|
21
21
|
const MAX_TTL_MS = 5 * 60000;
|
|
22
|
+
/** The cloud's limit on `relay_url`. */
|
|
23
|
+
const MAX_RELAY_URL = 1024;
|
|
24
|
+
/** The audience of the session route's claim (`/api/cloud/me/...`). */
|
|
25
|
+
export const ACCOUNT_OWNERSHIP_AUDIENCE = 'mdma:enable-ha-namespace';
|
|
26
|
+
/** The audience of the anonymous account route's claim (`/api/cloud/accounts/...`). */
|
|
27
|
+
export const ACCOUNT_HA_AUDIENCE = 'mdma:enable-ha-namespace-as-account';
|
|
22
28
|
function base64(bytes) {
|
|
23
29
|
let binary = '';
|
|
24
30
|
for (const b of bytes)
|
|
@@ -30,26 +36,52 @@ const hex32 = (value, what) => {
|
|
|
30
36
|
throw new Error(`${what} must be 64 hex characters`);
|
|
31
37
|
return value.toLowerCase();
|
|
32
38
|
};
|
|
33
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Sign one claim. `subject` is spliced in after `salt` only when given, so the
|
|
41
|
+
* session claim keeps its exact field order and the account claim has no
|
|
42
|
+
* `subject` key at all (not a null one: the cloud tells the two apart by it).
|
|
43
|
+
*/
|
|
44
|
+
async function signClaim(input, audience, subject, relayUrl) {
|
|
45
|
+
if (relayUrl !== undefined && !(relayUrl.length > 0 && relayUrl.length <= MAX_RELAY_URL)) {
|
|
46
|
+
throw new Error(`relayUrl must be a non-empty string of at most ${MAX_RELAY_URL} characters`);
|
|
47
|
+
}
|
|
34
48
|
const ttl = input.ttlMs ?? DEFAULT_TTL_MS;
|
|
35
49
|
if (!(ttl > 0 && ttl <= MAX_TTL_MS))
|
|
36
50
|
throw new Error(`ttlMs must be in (0, ${MAX_TTL_MS}]`);
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
const groupId = hex32(input.namespaceId, 'namespaceId');
|
|
52
|
+
const accountId = hex32(input.accountId, 'accountId');
|
|
53
|
+
const salt = hex32(input.salt, 'salt');
|
|
39
54
|
const signer = await resolveSigner(input.deviceSecret, input.signer, 'deviceSecret');
|
|
40
55
|
const issued = (input.now ?? Date.now)();
|
|
41
56
|
const nonce = Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => b.toString(16).padStart(2, '0')).join('');
|
|
42
57
|
const payload = new TextEncoder().encode(JSON.stringify({
|
|
43
58
|
v: 1,
|
|
44
|
-
audience
|
|
45
|
-
group_id:
|
|
46
|
-
account_id:
|
|
47
|
-
salt
|
|
48
|
-
subject:
|
|
59
|
+
audience,
|
|
60
|
+
group_id: groupId,
|
|
61
|
+
account_id: accountId,
|
|
62
|
+
salt,
|
|
63
|
+
...(subject === undefined ? {} : { subject }),
|
|
49
64
|
nonce,
|
|
50
65
|
issued_at_ms: issued,
|
|
51
66
|
expires_at_ms: issued + ttl,
|
|
67
|
+
...(relayUrl === undefined ? {} : { relay_url: relayUrl }),
|
|
52
68
|
}));
|
|
53
69
|
const signature = await signer.sign(concat(DOMAIN, payload));
|
|
54
70
|
return { kind: 'account', credential: input.credential, signed_payload: base64(payload), signature: base64(signature) };
|
|
55
71
|
}
|
|
72
|
+
/** The founder's claim for the SESSION route, bound to one cloud login (`subject`). */
|
|
73
|
+
export async function signAccountOwnershipClaim(input) {
|
|
74
|
+
if (!input.subject)
|
|
75
|
+
throw new Error('subject must be the cloud login email');
|
|
76
|
+
return signClaim(input, ACCOUNT_OWNERSHIP_AUDIENCE, input.subject);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The founder's claim for the ANONYMOUS account route
|
|
80
|
+
* (`POST /api/cloud/accounts/{account}/namespaces/{ns}/enable-ha`): no cloud
|
|
81
|
+
* session, so no `subject`, and its own audience so a claim minted for one
|
|
82
|
+
* route can never be replayed on the other. The cloud finds the user to bill
|
|
83
|
+
* through the account's wallet link instead.
|
|
84
|
+
*/
|
|
85
|
+
export async function signAccountHaClaim(input) {
|
|
86
|
+
return signClaim(input, ACCOUNT_HA_AUDIENCE, undefined, input.relayUrl);
|
|
87
|
+
}
|
|
@@ -24,8 +24,9 @@
|
|
|
24
24
|
* ever sent, and would be a throughput bottleneck on a path that has no reason
|
|
25
25
|
* to involve it.
|
|
26
26
|
*/
|
|
27
|
-
import type
|
|
27
|
+
import { type AccountOwnershipProof } from './account-ownership.js';
|
|
28
28
|
import { type RootSource } from '../account/index.js';
|
|
29
|
+
import { HTTPError } from '../http-client/web-client.js';
|
|
29
30
|
import type { Signer } from '../signer/signer.js';
|
|
30
31
|
import { type DiscoveryChallenge, type RoutingChallenge, type RoutingCredential } from './routing-proof.js';
|
|
31
32
|
/**
|
|
@@ -365,6 +366,86 @@ export interface DisableHAOptions {
|
|
|
365
366
|
contextId: string;
|
|
366
367
|
redirectUrl?: string;
|
|
367
368
|
}
|
|
369
|
+
/** Options for {@link CloudClient.enableHaAsAccount}. */
|
|
370
|
+
export interface EnableHaAsAccountOptions {
|
|
371
|
+
/** The namespace, hex: the id `foundNamespace` returned. */
|
|
372
|
+
namespaceId: string;
|
|
373
|
+
/** The salt `foundNamespace` returned, hex (32 bytes). */
|
|
374
|
+
salt: string;
|
|
375
|
+
/** The founding account, hex. */
|
|
376
|
+
accountId: string;
|
|
377
|
+
/** The account's credential, hex `AccountProof<DeviceCert>`. */
|
|
378
|
+
credential: string;
|
|
379
|
+
/** The certified device's secret, hex. Give this or `signer`. */
|
|
380
|
+
deviceSecret?: string;
|
|
381
|
+
signer?: Signer;
|
|
382
|
+
/** The claim's lifetime in ms; default one minute, at most five. */
|
|
383
|
+
ttlMs?: number;
|
|
384
|
+
/**
|
|
385
|
+
* The relay that founded the namespace (the delegated session's `relayUrl`).
|
|
386
|
+
* The cloud gives that relay to the fleet node as its admitter; without it
|
|
387
|
+
* the node has to find the relay by discovery.
|
|
388
|
+
*/
|
|
389
|
+
relayUrl?: string;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* The cloud's refusals of {@link CloudClient.enableHaAsAccount} that a
|
|
393
|
+
* person can do something about. Both are `409`s with `{"error": code}`.
|
|
394
|
+
*
|
|
395
|
+
* Extends {@link HTTPError}, so `instanceof HTTPError` handling still catches it.
|
|
396
|
+
*/
|
|
397
|
+
export declare class AccountHaRefusedError extends HTTPError {
|
|
398
|
+
/** The cloud's `error` code, verbatim. */
|
|
399
|
+
readonly code: string;
|
|
400
|
+
name: string;
|
|
401
|
+
constructor(
|
|
402
|
+
/** The cloud's `error` code, verbatim. */
|
|
403
|
+
code: string, status: number, statusText: string, url: string, headers: Headers, bodyText?: string);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* `account_not_linked`: the account was never linked to a cloud user through
|
|
407
|
+
* the wallet, so the cloud has nobody to host the namespace for. Linking it
|
|
408
|
+
* fixes this; retrying does not.
|
|
409
|
+
*/
|
|
410
|
+
export declare class AccountNotLinkedError extends AccountHaRefusedError {
|
|
411
|
+
name: string;
|
|
412
|
+
static readonly CODE = "account_not_linked";
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* `account_linked_to_several_users`: more than one cloud user linked this
|
|
416
|
+
* account, so the cloud cannot tell whose quota hosts the namespace. Unlink it
|
|
417
|
+
* from all but one.
|
|
418
|
+
*/
|
|
419
|
+
export declare class AccountLinkedToSeveralUsersError extends AccountHaRefusedError {
|
|
420
|
+
name: string;
|
|
421
|
+
static readonly CODE = "account_linked_to_several_users";
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* `ha_request_pending` (409): another namespace of this account is waiting for
|
|
425
|
+
* its first fleet node to be admitted. The route allows one at a time; it
|
|
426
|
+
* clears once that node is admitted, or by disabling HA on it from a cloud
|
|
427
|
+
* session. The claim was not spent.
|
|
428
|
+
*/
|
|
429
|
+
export declare class HaRequestPendingError extends AccountHaRefusedError {
|
|
430
|
+
name: string;
|
|
431
|
+
static readonly CODE = "ha_request_pending";
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* `unknown_relay` (422): the cloud runs no relay at `relayUrl`. The claim was
|
|
435
|
+
* not spent.
|
|
436
|
+
*/
|
|
437
|
+
export declare class UnknownRelayError extends AccountHaRefusedError {
|
|
438
|
+
name: string;
|
|
439
|
+
static readonly CODE = "unknown_relay";
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* `relay_not_dialable` (422): the relay is the cloud's but has not reported
|
|
443
|
+
* its addresses yet. The claim was not spent; retrying later can succeed.
|
|
444
|
+
*/
|
|
445
|
+
export declare class RelayNotDialableError extends AccountHaRefusedError {
|
|
446
|
+
name: string;
|
|
447
|
+
static readonly CODE = "relay_not_dialable";
|
|
448
|
+
}
|
|
368
449
|
export declare class CloudClient {
|
|
369
450
|
private baseUrl;
|
|
370
451
|
private session;
|
|
@@ -610,6 +691,24 @@ export declare class CloudClient {
|
|
|
610
691
|
* The login must be linked to that account.
|
|
611
692
|
*/
|
|
612
693
|
enableFoundedNamespaceHa(namespaceId: string, ownershipProof: AccountOwnershipProof): Promise<unknown>;
|
|
694
|
+
/**
|
|
695
|
+
* Enable HA for a namespace this account founded, with **no cloud session**.
|
|
696
|
+
*
|
|
697
|
+
* The anonymous sibling of {@link enableFoundedNamespaceHa}: it signs the
|
|
698
|
+
* founder's claim itself ({@link signAccountHaClaim} — no `subject`, its own
|
|
699
|
+
* audience) and posts it to
|
|
700
|
+
* `/api/cloud/accounts/{accountId}/namespaces/{namespaceId}/enable-ha`. The
|
|
701
|
+
* cloud bills the user the account is linked to through the wallet, so the
|
|
702
|
+
* account must be linked to exactly one:
|
|
703
|
+
*
|
|
704
|
+
* - not linked at all → {@link AccountNotLinkedError};
|
|
705
|
+
* - linked to several → {@link AccountLinkedToSeveralUsersError}.
|
|
706
|
+
*
|
|
707
|
+
* A claim that does not verify is a `403` `HTTPError`; quota and the rest
|
|
708
|
+
* answer as the session route does. Returns the cloud's response body, the
|
|
709
|
+
* same shape the session route's enable-ha returns.
|
|
710
|
+
*/
|
|
711
|
+
enableHaAsAccount(options: EnableHaAsAccountOptions): Promise<unknown>;
|
|
613
712
|
/**
|
|
614
713
|
* Stop hosting a claimed namespace.
|
|
615
714
|
*
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* ever sent, and would be a throughput bottleneck on a path that has no reason
|
|
25
25
|
* to involve it.
|
|
26
26
|
*/
|
|
27
|
+
import { signAccountHaClaim } from './account-ownership.js';
|
|
27
28
|
import { resolveRoot, resolveRootPair, signAccountLink, signAccountLogin, } from '../account/index.js';
|
|
28
29
|
import { HTTPError } from '../http-client/web-client.js';
|
|
29
30
|
import { routingProofHeaders, } from './routing-proof.js';
|
|
@@ -65,6 +66,107 @@ function teeRoleOf(row) {
|
|
|
65
66
|
export function pickExecutingRelay(relays) {
|
|
66
67
|
return (relays.find((r) => r.canExecute && r.relayUrl !== null && r.executorAccount !== null) ?? null);
|
|
67
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* The cloud's refusals of {@link CloudClient.enableHaAsAccount} that a
|
|
71
|
+
* person can do something about. Both are `409`s with `{"error": code}`.
|
|
72
|
+
*
|
|
73
|
+
* Extends {@link HTTPError}, so `instanceof HTTPError` handling still catches it.
|
|
74
|
+
*/
|
|
75
|
+
export class AccountHaRefusedError extends HTTPError {
|
|
76
|
+
constructor(
|
|
77
|
+
/** The cloud's `error` code, verbatim. */
|
|
78
|
+
code, status, statusText, url, headers, bodyText) {
|
|
79
|
+
super(status, statusText, url, headers, bodyText);
|
|
80
|
+
this.code = code;
|
|
81
|
+
this.name = 'AccountHaRefusedError';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `account_not_linked`: the account was never linked to a cloud user through
|
|
86
|
+
* the wallet, so the cloud has nobody to host the namespace for. Linking it
|
|
87
|
+
* fixes this; retrying does not.
|
|
88
|
+
*/
|
|
89
|
+
export class AccountNotLinkedError extends AccountHaRefusedError {
|
|
90
|
+
constructor() {
|
|
91
|
+
super(...arguments);
|
|
92
|
+
this.name = 'AccountNotLinkedError';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
AccountNotLinkedError.CODE = 'account_not_linked';
|
|
96
|
+
/**
|
|
97
|
+
* `account_linked_to_several_users`: more than one cloud user linked this
|
|
98
|
+
* account, so the cloud cannot tell whose quota hosts the namespace. Unlink it
|
|
99
|
+
* from all but one.
|
|
100
|
+
*/
|
|
101
|
+
export class AccountLinkedToSeveralUsersError extends AccountHaRefusedError {
|
|
102
|
+
constructor() {
|
|
103
|
+
super(...arguments);
|
|
104
|
+
this.name = 'AccountLinkedToSeveralUsersError';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
AccountLinkedToSeveralUsersError.CODE = 'account_linked_to_several_users';
|
|
108
|
+
/**
|
|
109
|
+
* `ha_request_pending` (409): another namespace of this account is waiting for
|
|
110
|
+
* its first fleet node to be admitted. The route allows one at a time; it
|
|
111
|
+
* clears once that node is admitted, or by disabling HA on it from a cloud
|
|
112
|
+
* session. The claim was not spent.
|
|
113
|
+
*/
|
|
114
|
+
export class HaRequestPendingError extends AccountHaRefusedError {
|
|
115
|
+
constructor() {
|
|
116
|
+
super(...arguments);
|
|
117
|
+
this.name = 'HaRequestPendingError';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
HaRequestPendingError.CODE = 'ha_request_pending';
|
|
121
|
+
/**
|
|
122
|
+
* `unknown_relay` (422): the cloud runs no relay at `relayUrl`. The claim was
|
|
123
|
+
* not spent.
|
|
124
|
+
*/
|
|
125
|
+
export class UnknownRelayError extends AccountHaRefusedError {
|
|
126
|
+
constructor() {
|
|
127
|
+
super(...arguments);
|
|
128
|
+
this.name = 'UnknownRelayError';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
UnknownRelayError.CODE = 'unknown_relay';
|
|
132
|
+
/**
|
|
133
|
+
* `relay_not_dialable` (422): the relay is the cloud's but has not reported
|
|
134
|
+
* its addresses yet. The claim was not spent; retrying later can succeed.
|
|
135
|
+
*/
|
|
136
|
+
export class RelayNotDialableError extends AccountHaRefusedError {
|
|
137
|
+
constructor() {
|
|
138
|
+
super(...arguments);
|
|
139
|
+
this.name = 'RelayNotDialableError';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
RelayNotDialableError.CODE = 'relay_not_dialable';
|
|
143
|
+
const ACCOUNT_HA_REFUSALS = {
|
|
144
|
+
409: [AccountNotLinkedError, AccountLinkedToSeveralUsersError, HaRequestPendingError],
|
|
145
|
+
422: [UnknownRelayError, RelayNotDialableError],
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* The `error` code of a 409 body. Accepts `{"error": code}` (the contract) and
|
|
149
|
+
* FastAPI's `{"detail": code}` / `{"detail": {"error": code}}`, so how the
|
|
150
|
+
* route raises it does not change what a caller catches.
|
|
151
|
+
*/
|
|
152
|
+
function conflictCode(bodyText) {
|
|
153
|
+
if (!bodyText)
|
|
154
|
+
return undefined;
|
|
155
|
+
try {
|
|
156
|
+
const body = JSON.parse(bodyText);
|
|
157
|
+
if (typeof body?.error === 'string')
|
|
158
|
+
return body.error;
|
|
159
|
+
if (typeof body?.detail === 'string')
|
|
160
|
+
return body.detail;
|
|
161
|
+
const nested = body?.detail?.error;
|
|
162
|
+
if (typeof nested === 'string')
|
|
163
|
+
return nested;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Not JSON: not one of ours.
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
68
170
|
/** Response headers MDMA uses to hand back a rolling-refreshed session. */
|
|
69
171
|
const REFRESH_HEADER = 'x-mdma-session-refresh';
|
|
70
172
|
const REFRESH_EXPIRES_HEADER = 'x-mdma-session-expires';
|
|
@@ -509,6 +611,52 @@ export class CloudClient {
|
|
|
509
611
|
async enableFoundedNamespaceHa(namespaceId, ownershipProof) {
|
|
510
612
|
return this.request('POST', `/api/cloud/me/namespaces/${encodeURIComponent(namespaceId)}/enable-ha`, { body: { groups: [], ownership_proof: ownershipProof } });
|
|
511
613
|
}
|
|
614
|
+
/**
|
|
615
|
+
* Enable HA for a namespace this account founded, with **no cloud session**.
|
|
616
|
+
*
|
|
617
|
+
* The anonymous sibling of {@link enableFoundedNamespaceHa}: it signs the
|
|
618
|
+
* founder's claim itself ({@link signAccountHaClaim} — no `subject`, its own
|
|
619
|
+
* audience) and posts it to
|
|
620
|
+
* `/api/cloud/accounts/{accountId}/namespaces/{namespaceId}/enable-ha`. The
|
|
621
|
+
* cloud bills the user the account is linked to through the wallet, so the
|
|
622
|
+
* account must be linked to exactly one:
|
|
623
|
+
*
|
|
624
|
+
* - not linked at all → {@link AccountNotLinkedError};
|
|
625
|
+
* - linked to several → {@link AccountLinkedToSeveralUsersError}.
|
|
626
|
+
*
|
|
627
|
+
* A claim that does not verify is a `403` `HTTPError`; quota and the rest
|
|
628
|
+
* answer as the session route does. Returns the cloud's response body, the
|
|
629
|
+
* same shape the session route's enable-ha returns.
|
|
630
|
+
*/
|
|
631
|
+
async enableHaAsAccount(options) {
|
|
632
|
+
const proof = await signAccountHaClaim({
|
|
633
|
+
namespaceId: options.namespaceId,
|
|
634
|
+
accountId: options.accountId,
|
|
635
|
+
salt: options.salt,
|
|
636
|
+
credential: options.credential,
|
|
637
|
+
deviceSecret: options.deviceSecret,
|
|
638
|
+
signer: options.signer,
|
|
639
|
+
ttlMs: options.ttlMs,
|
|
640
|
+
relayUrl: options.relayUrl,
|
|
641
|
+
});
|
|
642
|
+
// The path carries the ids as the claim does — lowercase hex — so a caller
|
|
643
|
+
// that kept them uppercase does not address a different resource.
|
|
644
|
+
const account = options.accountId.toLowerCase();
|
|
645
|
+
const namespace = options.namespaceId.toLowerCase();
|
|
646
|
+
try {
|
|
647
|
+
return await this.request('POST', `/api/cloud/accounts/${encodeURIComponent(account)}/namespaces/${encodeURIComponent(namespace)}/enable-ha`, { body: { ownership_proof: proof }, anonymous: true });
|
|
648
|
+
}
|
|
649
|
+
catch (err) {
|
|
650
|
+
if (err instanceof HTTPError && ACCOUNT_HA_REFUSALS[err.status]) {
|
|
651
|
+
const code = conflictCode(err.bodyText);
|
|
652
|
+
const Typed = ACCOUNT_HA_REFUSALS[err.status].find((t) => t.CODE === code);
|
|
653
|
+
if (Typed && code) {
|
|
654
|
+
throw new Typed(code, err.status, err.statusText, err.url, err.headers, err.bodyText);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
throw err;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
512
660
|
/**
|
|
513
661
|
* Stop hosting a claimed namespace.
|
|
514
662
|
*
|
package/dist/cloud/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { CloudClient, admitterAddrsFromNetworkStatus } from './cloud-client.js';
|
|
2
|
-
export type { CloudClientConfig, CloudSession, CloudNamespace, CloudRelay, CloudAccountRelay, CloudNamespaceNode, CloudNamespaceRouting, CloudMachine, CloudMachineNamespace, CloudLinkedAccount, CloudLinkedAccounts, CloudAccountLink, AccountLinkChallenge, AccountLinkProof, AccountLinkHandoff, AccountLinkCallback, AccountLoginChallenge, AccountLoginProof, EnableHAOptions, DisableHAOptions, } from './cloud-client.js';
|
|
1
|
+
export { CloudClient, admitterAddrsFromNetworkStatus, AccountHaRefusedError, AccountNotLinkedError, AccountLinkedToSeveralUsersError, HaRequestPendingError, UnknownRelayError, RelayNotDialableError, } from './cloud-client.js';
|
|
2
|
+
export type { CloudClientConfig, CloudSession, CloudNamespace, CloudRelay, CloudAccountRelay, CloudNamespaceNode, CloudNamespaceRouting, CloudMachine, CloudMachineNamespace, CloudLinkedAccount, CloudLinkedAccounts, CloudAccountLink, AccountLinkChallenge, AccountLinkProof, AccountLinkHandoff, AccountLinkCallback, AccountLoginChallenge, AccountLoginProof, EnableHAOptions, DisableHAOptions, EnableHaAsAccountOptions, } from './cloud-client.js';
|
|
3
3
|
export { connectCloud, connectCloudWithAccount } from './connect.js';
|
|
4
4
|
export type { ConnectCloudOptions, ConnectCloudWithAccountOptions, CloudConnection, } from './connect.js';
|
|
5
5
|
export { signRoutingChallenge, routingProofHeaders } from './routing-proof.js';
|
|
@@ -8,5 +8,5 @@ export { cloudLinkUrl, beginCloudLink, readCloudLinkCallback, completeCloudLink,
|
|
|
8
8
|
export type { BeginCloudLinkOptions, CloudLinkCallback, CompleteCloudLinkOptions, } from './link-redirect.js';
|
|
9
9
|
export { deviceEnrolmentUrl, beginDeviceEnrolment, readEnrolmentCallback, completeDeviceEnrolment, callbackIsForeign, } from './enrol-redirect.js';
|
|
10
10
|
export type { DeviceEnrolmentOptions, DeviceEnrolmentCallback, CompleteDeviceEnrolmentOptions, EnrolledDevice, } from './enrol-redirect.js';
|
|
11
|
-
export { signAccountOwnershipClaim } from './account-ownership.js';
|
|
12
|
-
export type { AccountOwnershipProof, AccountOwnershipClaimInput } from './account-ownership.js';
|
|
11
|
+
export { signAccountOwnershipClaim, signAccountHaClaim, ACCOUNT_OWNERSHIP_AUDIENCE, ACCOUNT_HA_AUDIENCE, } from './account-ownership.js';
|
|
12
|
+
export type { AccountOwnershipProof, AccountOwnershipClaimInput, AccountHaClaimInput, } from './account-ownership.js';
|
package/dist/cloud/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { CloudClient, admitterAddrsFromNetworkStatus } from './cloud-client.js';
|
|
1
|
+
export { CloudClient, admitterAddrsFromNetworkStatus, AccountHaRefusedError, AccountNotLinkedError, AccountLinkedToSeveralUsersError, HaRequestPendingError, UnknownRelayError, RelayNotDialableError, } from './cloud-client.js';
|
|
2
2
|
export { connectCloud, connectCloudWithAccount } from './connect.js';
|
|
3
3
|
export { signRoutingChallenge, routingProofHeaders } from './routing-proof.js';
|
|
4
4
|
// Linking an account to a cloud login from a browser — the redirect half the
|
|
@@ -7,4 +7,4 @@ export { cloudLinkUrl, beginCloudLink, readCloudLinkCallback, completeCloudLink,
|
|
|
7
7
|
// Getting this app's device key certified by a wallet on another origin — the
|
|
8
8
|
// redirect that keeps the account root out of the app's reach.
|
|
9
9
|
export { deviceEnrolmentUrl, beginDeviceEnrolment, readEnrolmentCallback, completeDeviceEnrolment, callbackIsForeign, } from './enrol-redirect.js';
|
|
10
|
-
export { signAccountOwnershipClaim } from './account-ownership.js';
|
|
10
|
+
export { signAccountOwnershipClaim, signAccountHaClaim, ACCOUNT_OWNERSHIP_AUDIENCE, ACCOUNT_HA_AUDIENCE, } from './account-ownership.js';
|