@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
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hex-encode a string's UTF-8 bytes, for {@link AccountSignWithRootRequest.payload}.
|
|
3
|
-
*
|
|
4
|
-
* Exists so each caller does not write the same loop. Every verifier this is
|
|
5
|
-
* used with issues its challenge as text, and the node takes bytes — getting
|
|
6
|
-
* that conversion subtly wrong (UTF-16 code units, a stray newline from a shell)
|
|
7
|
-
* produces a signature over the wrong message, which fails at the verifier with
|
|
8
|
-
* nothing local to point at.
|
|
9
|
-
*/
|
|
10
|
-
export function hexEncodeUtf8(text) {
|
|
11
|
-
return Array.from(new TextEncoder().encode(text), (b) => b.toString(16).padStart(2, '0')).join('');
|
|
12
|
-
}
|
package/dist/admin-api/index.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/** The two calls a redeem needs, so any client (or any app's join) can drive it. */
|
|
2
|
-
export interface InviteRedeemer {
|
|
3
|
-
/**
|
|
4
|
-
* Send the join. Resolve on success, throw on failure.
|
|
5
|
-
*
|
|
6
|
-
* It does not have to be reliable, and it does not have to be idempotent on
|
|
7
|
-
* the client — the node's join is idempotent, and `memberships()` settles any
|
|
8
|
-
* ambiguity this throws.
|
|
9
|
-
*/
|
|
10
|
-
join(namespaceId: string, invitation: unknown): Promise<void>;
|
|
11
|
-
/**
|
|
12
|
-
* Namespace ids this node is a member of.
|
|
13
|
-
*
|
|
14
|
-
* Throwing is a legitimate answer ("could not tell") and is treated as such:
|
|
15
|
-
* it never turns a join that succeeded into a failure.
|
|
16
|
-
*/
|
|
17
|
-
memberships(): Promise<readonly string[]>;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Why a join failed, as far as it tells a person what to do next.
|
|
21
|
-
*
|
|
22
|
-
* Read off the HTTP status where the node gives one (core rc.56+ answers a
|
|
23
|
-
* refused join with a 4xx, not a 500), and off the message otherwise.
|
|
24
|
-
*/
|
|
25
|
-
export type InviteFailureReason =
|
|
26
|
-
/** The invitation's time ran out. Only a new link helps. */
|
|
27
|
-
'expired'
|
|
28
|
-
/** The link is malformed or names nothing joinable. Only a new link helps. */
|
|
29
|
-
| 'invalid'
|
|
30
|
-
/**
|
|
31
|
-
* This person already joined with this link once, and has since left. A link
|
|
32
|
-
* admits each person once: only a new one helps.
|
|
33
|
-
*/
|
|
34
|
-
| 'already-used'
|
|
35
|
-
/** An admin removed this person. No link readmits them; an admin must re-add them. */
|
|
36
|
-
| 'removed'
|
|
37
|
-
/** The node refused this person for another reason (blocked, not allowed). */
|
|
38
|
-
| 'refused'
|
|
39
|
-
/** This client's session with the node lapsed; signing in finishes it. */
|
|
40
|
-
| 'signed-out'
|
|
41
|
-
/** No member was reachable to let them in yet. Trying later can work. */
|
|
42
|
-
| 'no-one-online'
|
|
43
|
-
/** This client could not reach its own node at all. */
|
|
44
|
-
| 'node-unreachable'
|
|
45
|
-
/** Nothing more specific is known; the node's message is all there is. */
|
|
46
|
-
| 'unknown';
|
|
47
|
-
/**
|
|
48
|
-
* What happened, in the terms a UI needs.
|
|
49
|
-
*
|
|
50
|
-
* `already-member` is deliberately a success and deliberately distinct: it is
|
|
51
|
-
* the outcome of following a link twice, or of the timeout above, and telling
|
|
52
|
-
* someone "you are already in this one" is neither an error nor the same
|
|
53
|
-
* message as having just joined.
|
|
54
|
-
*/
|
|
55
|
-
export type RedeemOutcome = {
|
|
56
|
-
status: 'joined';
|
|
57
|
-
namespaceId: string;
|
|
58
|
-
teamName?: string;
|
|
59
|
-
} | {
|
|
60
|
-
status: 'already-member';
|
|
61
|
-
namespaceId: string;
|
|
62
|
-
teamName?: string;
|
|
63
|
-
} | {
|
|
64
|
-
status: 'failed';
|
|
65
|
-
namespaceId: string | null;
|
|
66
|
-
/** The node's own words, for logs and for a UI that shows detail. */
|
|
67
|
-
message: string;
|
|
68
|
-
/** Why, in terms a person can act on. */
|
|
69
|
-
reason: InviteFailureReason;
|
|
70
|
-
/**
|
|
71
|
-
* Whether trying again later could plausibly work. A malformed token
|
|
72
|
-
* cannot; an unreachable peer can. A caller holding the invitation uses
|
|
73
|
-
* this to decide whether to keep it — see {@link shouldRetain}.
|
|
74
|
-
*/
|
|
75
|
-
retryable: boolean;
|
|
76
|
-
};
|
|
77
|
-
/** Whether an outcome means the invitation is finished with. */
|
|
78
|
-
export declare function isSettled(outcome: RedeemOutcome): boolean;
|
|
79
|
-
/**
|
|
80
|
-
* Whether the invitation should be kept for another attempt later.
|
|
81
|
-
*
|
|
82
|
-
* The inverse of {@link isSettled}, named for the decision it drives.
|
|
83
|
-
*/
|
|
84
|
-
export declare function shouldRetain(outcome: RedeemOutcome): boolean;
|
|
85
|
-
/**
|
|
86
|
-
* Join the namespace an invitation names, and report what happened.
|
|
87
|
-
*
|
|
88
|
-
* Sends the join once and never retries it: a failed request whose namespace
|
|
89
|
-
* is then listed is `already-member`, not a failure. Never throws for an
|
|
90
|
-
* ordinary failure — the outcome carries it, because every caller has to branch
|
|
91
|
-
* on "already a member" anyway.
|
|
92
|
-
*
|
|
93
|
-
* Most callers want {@link AdminApiClient.redeemInvitation}, which supplies the
|
|
94
|
-
* redeemer from the admin API. This form is for a join that is more than one
|
|
95
|
-
* call — a namespace followed by the context inside it, say.
|
|
96
|
-
*/
|
|
97
|
-
export declare function redeemInvitation(parsed: {
|
|
98
|
-
namespaceId: string;
|
|
99
|
-
invitation: unknown;
|
|
100
|
-
teamName?: string;
|
|
101
|
-
}, redeemer: InviteRedeemer): Promise<RedeemOutcome>;
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
// ── Redeeming an invitation: joining once, and knowing whether it worked ──────
|
|
2
|
-
//
|
|
3
|
-
// Deciding "did we join?" by whether the join request resolved is the wrong
|
|
4
|
-
// question, and on a real node (core rc.41, 2026-09-22) it failed two ways:
|
|
5
|
-
//
|
|
6
|
-
// 1. The desktop proxy aborts every admin request at 30s. A namespace join
|
|
7
|
-
// whose members are all offline waits for one to appear — measured at 95s.
|
|
8
|
-
// So the request failed, the join landed anyway, and the app reported
|
|
9
|
-
// "could not join" for a namespace it was already in. It kept the
|
|
10
|
-
// invitation, and replayed it on every load.
|
|
11
|
-
//
|
|
12
|
-
// 2. A refresh after the join shared its `try`, so a refresh that threw
|
|
13
|
-
// turned a real join into a reported failure.
|
|
14
|
-
//
|
|
15
|
-
// So membership is the question, and there are two independent proofs of it:
|
|
16
|
-
// the request resolving, and the namespace being listed. Either one is enough,
|
|
17
|
-
// which matters because either one can be missing.
|
|
18
|
-
import { classifyError, messageOf } from '../errors/classify.js';
|
|
19
|
-
import { RelayLoginError } from '../account/relay-login-error.js';
|
|
20
|
-
/** Whether an outcome means the invitation is finished with. */
|
|
21
|
-
export function isSettled(outcome) {
|
|
22
|
-
return outcome.status !== 'failed' || !outcome.retryable;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Whether the invitation should be kept for another attempt later.
|
|
26
|
-
*
|
|
27
|
-
* The inverse of {@link isSettled}, named for the decision it drives.
|
|
28
|
-
*/
|
|
29
|
-
export function shouldRetain(outcome) {
|
|
30
|
-
return !isSettled(outcome);
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* The refusal codes a join route answers with beside `error` (core's
|
|
34
|
-
* `join_refusal_code`), and what each one tells a person.
|
|
35
|
-
*
|
|
36
|
-
* `join_refused_by_member` is absent on purpose: a member that refused may only
|
|
37
|
-
* be behind, so it says nothing final on its own, and the member's own words
|
|
38
|
-
* (in the message) are read by {@link reasonFromMessage} instead.
|
|
39
|
-
*/
|
|
40
|
-
const REASON_FOR_CODE = {
|
|
41
|
-
invitation_expired: 'expired',
|
|
42
|
-
invitation_invalid: 'invalid',
|
|
43
|
-
invitation_already_used: 'already-used',
|
|
44
|
-
removed_from_group: 'removed',
|
|
45
|
-
reentry_blocked: 'refused',
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* The machine-readable `code` a node put beside its `error`, if any: on
|
|
49
|
-
* mero-js's `HTTPError` it is in the body text, on an axios-style error in
|
|
50
|
-
* `response.data`.
|
|
51
|
-
*/
|
|
52
|
-
function refusalCodeOf(err) {
|
|
53
|
-
if (!err || typeof err !== 'object')
|
|
54
|
-
return undefined;
|
|
55
|
-
const shaped = err;
|
|
56
|
-
if (typeof shaped.response?.data?.code === 'string')
|
|
57
|
-
return shaped.response.data.code;
|
|
58
|
-
if (typeof shaped.bodyText !== 'string')
|
|
59
|
-
return undefined;
|
|
60
|
-
try {
|
|
61
|
-
const body = JSON.parse(shaped.bodyText);
|
|
62
|
-
return typeof body?.code === 'string' ? body.code : undefined;
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return undefined;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Which reason the message alone names, for a node that answers without a
|
|
70
|
-
* useful status — older than rc.56, where every refusal was a bare 500 — or
|
|
71
|
-
* without a `code`, or for a member's refusal quoted inside a retryable answer.
|
|
72
|
-
*
|
|
73
|
-
* Kept narrow on purpose. Anything not recognised here stays retryable, because
|
|
74
|
-
* the cost of retrying a dead link is one more attempt, while the cost of
|
|
75
|
-
* discarding a live one is someone who cannot join at all.
|
|
76
|
-
*/
|
|
77
|
-
function reasonFromMessage(message) {
|
|
78
|
-
const m = message.toLowerCase();
|
|
79
|
-
if (m.includes('invitation has expired') ||
|
|
80
|
-
m.includes('invitation expired') ||
|
|
81
|
-
// Core rc.56+ names the group between the words: "invitation for group
|
|
82
|
-
// <id> expired at <secs>".
|
|
83
|
-
/\binvitation for group .+? expired at \d+/.test(m)) {
|
|
84
|
-
return 'expired';
|
|
85
|
-
}
|
|
86
|
-
if (m.includes('malformed') ||
|
|
87
|
-
m.includes('does not match namespace_id') ||
|
|
88
|
-
m.includes('invalid invitation') ||
|
|
89
|
-
/\binvitation for group .+? is invalid:/.test(m)) {
|
|
90
|
-
return 'invalid';
|
|
91
|
-
}
|
|
92
|
-
// Core's own words for a spent link and a removal (`MembershipError`), which a
|
|
93
|
-
// refusing member also quotes back to the joiner.
|
|
94
|
-
if (m.includes('has already used this invitation'))
|
|
95
|
-
return 'already-used';
|
|
96
|
-
if (/\bwas removed from group\b/.test(m))
|
|
97
|
-
return 'removed';
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* The reasons that are final whatever status carried them: no later attempt
|
|
102
|
-
* with this link can succeed for this person.
|
|
103
|
-
*/
|
|
104
|
-
const FINAL_FROM_TEXT = new Set(['already-used', 'removed']);
|
|
105
|
-
/**
|
|
106
|
-
* Why a join failed, and whether a later attempt could work.
|
|
107
|
-
*
|
|
108
|
-
* The status decides where there is one. A 4xx the node chose is a definitive
|
|
109
|
-
* answer about this invitation or this person, so it is not retried; a 5xx or
|
|
110
|
-
* no answer at all is about the moment, so it is. Two exceptions keep an
|
|
111
|
-
* invitation that is still good: a 401 is the client's session, not the
|
|
112
|
-
* invitation, and a 404 is not specific enough to throw a link away on.
|
|
113
|
-
*/
|
|
114
|
-
function classifyJoinFailure(err, message) {
|
|
115
|
-
// The join never left: the relay session it needed could not be minted. Says
|
|
116
|
-
// nothing about the invitation, which is kept for after a sign-in or a retry.
|
|
117
|
-
if (err instanceof RelayLoginError) {
|
|
118
|
-
return { reason: err.kind === 'signed-out' ? 'signed-out' : 'node-unreachable', retryable: true };
|
|
119
|
-
}
|
|
120
|
-
const { kind, status } = classifyError(err);
|
|
121
|
-
const fromText = reasonFromMessage(message);
|
|
122
|
-
// A code is the node naming the refusal itself: nothing below can know better.
|
|
123
|
-
const code = refusalCodeOf(err);
|
|
124
|
-
const fromCode = code ? REASON_FOR_CODE[code] : undefined;
|
|
125
|
-
if (fromCode)
|
|
126
|
-
return { reason: fromCode, retryable: false };
|
|
127
|
-
// A spent link or a removal is final even when a member said it inside a
|
|
128
|
-
// retryable answer (a 503 for a join every peer refused): asking again only
|
|
129
|
-
// asks the same members, who will say the same.
|
|
130
|
-
if (fromText && FINAL_FROM_TEXT.has(fromText))
|
|
131
|
-
return { reason: fromText, retryable: false };
|
|
132
|
-
// Any other member refusal is not "no one is online": someone answered, and
|
|
133
|
-
// their words are the message. Kept, since a member behind on the team's
|
|
134
|
-
// history refuses what it has not seen yet.
|
|
135
|
-
if (code === 'join_refused_by_member')
|
|
136
|
-
return { reason: 'unknown', retryable: true };
|
|
137
|
-
if (status === 401)
|
|
138
|
-
return { reason: 'signed-out', retryable: true };
|
|
139
|
-
if (status === 400)
|
|
140
|
-
return { reason: fromText ?? 'invalid', retryable: false };
|
|
141
|
-
if (status === 410)
|
|
142
|
-
return { reason: 'expired', retryable: false };
|
|
143
|
-
if (status === 403)
|
|
144
|
-
return { reason: 'refused', retryable: false };
|
|
145
|
-
// An expired invitation, a spent one, a member who was removed or blocked:
|
|
146
|
-
// every 409 a join can answer is final.
|
|
147
|
-
if (status === 409)
|
|
148
|
-
return { reason: fromText ?? 'refused', retryable: false };
|
|
149
|
-
if (kind === 'unavailable')
|
|
150
|
-
return { reason: 'no-one-online', retryable: true };
|
|
151
|
-
if (kind === 'unreachable')
|
|
152
|
-
return { reason: 'node-unreachable', retryable: true };
|
|
153
|
-
// A 500 from a node older than rc.56 may still be a refusal in disguise;
|
|
154
|
-
// anything else has only its message to go on.
|
|
155
|
-
if (fromText)
|
|
156
|
-
return { reason: fromText, retryable: false };
|
|
157
|
-
return { reason: 'unknown', retryable: true };
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Join the namespace an invitation names, and report what happened.
|
|
161
|
-
*
|
|
162
|
-
* Sends the join once and never retries it: a failed request whose namespace
|
|
163
|
-
* is then listed is `already-member`, not a failure. Never throws for an
|
|
164
|
-
* ordinary failure — the outcome carries it, because every caller has to branch
|
|
165
|
-
* on "already a member" anyway.
|
|
166
|
-
*
|
|
167
|
-
* Most callers want {@link AdminApiClient.redeemInvitation}, which supplies the
|
|
168
|
-
* redeemer from the admin API. This form is for a join that is more than one
|
|
169
|
-
* call — a namespace followed by the context inside it, say.
|
|
170
|
-
*/
|
|
171
|
-
export async function redeemInvitation(parsed, redeemer) {
|
|
172
|
-
const { namespaceId, invitation, teamName } = parsed;
|
|
173
|
-
let joinError = null;
|
|
174
|
-
try {
|
|
175
|
-
await redeemer.join(namespaceId, invitation);
|
|
176
|
-
}
|
|
177
|
-
catch (err) {
|
|
178
|
-
joinError = err;
|
|
179
|
-
}
|
|
180
|
-
// The request resolving is proof on its own, so this must not be able to
|
|
181
|
-
// demote a real join: a `memberships()` that throws is "could not tell".
|
|
182
|
-
let listed = null;
|
|
183
|
-
try {
|
|
184
|
-
listed = (await redeemer.memberships()).includes(namespaceId);
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
listed = null;
|
|
188
|
-
}
|
|
189
|
-
if (joinError === null) {
|
|
190
|
-
return { status: 'joined', namespaceId, teamName };
|
|
191
|
-
}
|
|
192
|
-
if (listed === true) {
|
|
193
|
-
// The join landed despite the error — an aborted request, or a link
|
|
194
|
-
// followed twice.
|
|
195
|
-
return { status: 'already-member', namespaceId, teamName };
|
|
196
|
-
}
|
|
197
|
-
const message = messageOf(joinError, 'Could not join. Check the invitation.');
|
|
198
|
-
const { reason, retryable } = classifyJoinFailure(joinError, message);
|
|
199
|
-
return { status: 'failed', namespaceId, message, reason, retryable };
|
|
200
|
-
}
|
package/dist/auth/index.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export interface AuthCallbackResult {
|
|
2
|
-
accessToken: string;
|
|
3
|
-
refreshToken: string;
|
|
4
|
-
applicationId: string;
|
|
5
|
-
contextId: string;
|
|
6
|
-
contextIdentity: string;
|
|
7
|
-
nodeUrl: string;
|
|
8
|
-
}
|
|
9
|
-
export interface AuthLoginOptions {
|
|
10
|
-
callbackUrl: string;
|
|
11
|
-
packageName?: string;
|
|
12
|
-
mode: string;
|
|
13
|
-
permissions?: string[];
|
|
14
|
-
registryUrl?: string;
|
|
15
|
-
packageVersion?: string;
|
|
16
|
-
}
|
|
17
|
-
export interface ParseAuthCallbackOptions {
|
|
18
|
-
/**
|
|
19
|
-
* Accept tokens this browser did not ask for, for a host that injects them itself
|
|
20
|
-
* (Calimero Desktop). Anyone who can open the page can then choose the session.
|
|
21
|
-
*/
|
|
22
|
-
allowUnsolicited?: boolean;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Parse an auth callback URL hash fragment.
|
|
26
|
-
* Returns null if access_token is missing, or if the callback does not answer a
|
|
27
|
-
* login begun here with {@link buildAuthLoginUrl} (single use, expires).
|
|
28
|
-
*/
|
|
29
|
-
export declare function parseAuthCallback(url: string, options?: ParseAuthCallbackOptions): AuthCallbackResult | null;
|
|
30
|
-
/**
|
|
31
|
-
* Build the auth login URL for redirecting users to the node's auth page.
|
|
32
|
-
* Remembers the login so {@link parseAuthCallback} can match the callback to it.
|
|
33
|
-
*/
|
|
34
|
-
export declare function buildAuthLoginUrl(nodeUrl: string, opts: AuthLoginOptions): string;
|
package/dist/auth/index.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { beginState, takeState } from '../cloud/link-state.js';
|
|
2
|
-
const PENDING_KEY = 'calimero.auth.login.pending';
|
|
3
|
-
const STATE_PARAM = 'auth_state';
|
|
4
|
-
/**
|
|
5
|
-
* Parse an auth callback URL hash fragment.
|
|
6
|
-
* Returns null if access_token is missing, or if the callback does not answer a
|
|
7
|
-
* login begun here with {@link buildAuthLoginUrl} (single use, expires).
|
|
8
|
-
*/
|
|
9
|
-
export function parseAuthCallback(url, options = {}) {
|
|
10
|
-
try {
|
|
11
|
-
const hashIndex = url.indexOf('#');
|
|
12
|
-
if (hashIndex === -1)
|
|
13
|
-
return null;
|
|
14
|
-
const hash = url.substring(hashIndex + 1);
|
|
15
|
-
const params = new URLSearchParams(hash);
|
|
16
|
-
const accessToken = params.get('access_token');
|
|
17
|
-
if (!accessToken)
|
|
18
|
-
return null;
|
|
19
|
-
if (!options.allowUnsolicited && !takeState(PENDING_KEY, stateOf(url, hashIndex))) {
|
|
20
|
-
console.warn('[mero-js] ignoring auth callback tokens: they answer no login begun in this browser');
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
return {
|
|
24
|
-
accessToken,
|
|
25
|
-
refreshToken: params.get('refresh_token') ?? '',
|
|
26
|
-
applicationId: params.get('application_id') ?? '',
|
|
27
|
-
contextId: params.get('context_id') ?? '',
|
|
28
|
-
contextIdentity: params.get('context_identity') ?? '',
|
|
29
|
-
nodeUrl: params.get('node_url') ?? '',
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function stateOf(url, hashIndex) {
|
|
37
|
-
const queryIndex = url.indexOf('?');
|
|
38
|
-
if (queryIndex === -1 || queryIndex > hashIndex)
|
|
39
|
-
return null;
|
|
40
|
-
return new URLSearchParams(url.substring(queryIndex + 1, hashIndex)).get(STATE_PARAM);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Build the auth login URL for redirecting users to the node's auth page.
|
|
44
|
-
* Remembers the login so {@link parseAuthCallback} can match the callback to it.
|
|
45
|
-
*/
|
|
46
|
-
export function buildAuthLoginUrl(nodeUrl, opts) {
|
|
47
|
-
const callback = new URL(opts.callbackUrl);
|
|
48
|
-
callback.searchParams.set(STATE_PARAM, beginState(PENDING_KEY, {}));
|
|
49
|
-
const params = new URLSearchParams();
|
|
50
|
-
params.set('callback-url', callback.toString());
|
|
51
|
-
if (opts.permissions && opts.permissions.length > 0) {
|
|
52
|
-
params.set('permissions', opts.permissions.join(','));
|
|
53
|
-
}
|
|
54
|
-
params.set('mode', opts.mode);
|
|
55
|
-
if (opts.packageName) {
|
|
56
|
-
params.set('package-name', opts.packageName);
|
|
57
|
-
if (opts.packageVersion) {
|
|
58
|
-
params.set('package-version', opts.packageVersion);
|
|
59
|
-
}
|
|
60
|
-
if (opts.registryUrl) {
|
|
61
|
-
params.set('registry-url', opts.registryUrl);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
// Trim trailing slash from nodeUrl
|
|
65
|
-
const base = nodeUrl.replace(/\/+$/, '');
|
|
66
|
-
return `${base}/auth/login?${params.toString()}`;
|
|
67
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { HttpClient } from '../http-client/index.js';
|
|
2
|
-
import { HealthResponse, IdentityResponse, ProvidersResponse, TokenRequest, TokenResponse, RefreshTokenRequest, MockTokenRequest, RevokeTokenRequest, RevokeTokenResponse, CreateKeyRequest, CreateKeyResponse, DeleteKeyResponse, RootKey, ClientKey, GenerateClientKeyRequest, DeleteClientResponse, PermissionResponse, UpdateKeyPermissionsRequest } from './auth-types.js';
|
|
3
|
-
export declare class AuthApiClient {
|
|
4
|
-
private httpClient;
|
|
5
|
-
constructor(httpClient: HttpClient);
|
|
6
|
-
getHealth(): Promise<HealthResponse>;
|
|
7
|
-
getIdentity(): Promise<IdentityResponse>;
|
|
8
|
-
getProviders(): Promise<ProvidersResponse>;
|
|
9
|
-
getLoginPage(): Promise<string>;
|
|
10
|
-
generateTokens(request: TokenRequest): Promise<TokenResponse>;
|
|
11
|
-
refreshToken(request: RefreshTokenRequest): Promise<TokenResponse>;
|
|
12
|
-
generateMockTokens(request: MockTokenRequest): Promise<TokenResponse>;
|
|
13
|
-
validateToken(token: string): Promise<{
|
|
14
|
-
valid: boolean;
|
|
15
|
-
headers: Record<string, string>;
|
|
16
|
-
status: number;
|
|
17
|
-
}>;
|
|
18
|
-
validateTokenGet(token: string): Promise<{
|
|
19
|
-
status: number;
|
|
20
|
-
headers: Record<string, string>;
|
|
21
|
-
}>;
|
|
22
|
-
revokeTokens(request: RevokeTokenRequest): Promise<RevokeTokenResponse>;
|
|
23
|
-
listRootKeys(): Promise<RootKey[]>;
|
|
24
|
-
createRootKey(request: CreateKeyRequest): Promise<CreateKeyResponse>;
|
|
25
|
-
deleteRootKey(keyId: string): Promise<DeleteKeyResponse>;
|
|
26
|
-
listClientKeys(): Promise<ClientKey[]>;
|
|
27
|
-
generateClientKey(request: GenerateClientKeyRequest): Promise<TokenResponse>;
|
|
28
|
-
deleteClientKey(keyId: string, clientId: string): Promise<DeleteClientResponse>;
|
|
29
|
-
getKeyPermissions(keyId: string): Promise<PermissionResponse>;
|
|
30
|
-
updateKeyPermissions(keyId: string, changes: UpdateKeyPermissionsRequest): Promise<PermissionResponse>;
|
|
31
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { pathSegment } from '../http-client/path-segment.js';
|
|
2
|
-
export class AuthApiClient {
|
|
3
|
-
constructor(httpClient) {
|
|
4
|
-
this.httpClient = httpClient;
|
|
5
|
-
}
|
|
6
|
-
// Health and Status Endpoints
|
|
7
|
-
async getHealth() {
|
|
8
|
-
const response = await this.httpClient.get('/auth/health');
|
|
9
|
-
if (!response.data) {
|
|
10
|
-
throw new Error('Health response data is null');
|
|
11
|
-
}
|
|
12
|
-
return response.data;
|
|
13
|
-
}
|
|
14
|
-
async getIdentity() {
|
|
15
|
-
const response = await this.httpClient.get('/admin/identity');
|
|
16
|
-
if (!response.data) {
|
|
17
|
-
throw new Error('Identity response data is null');
|
|
18
|
-
}
|
|
19
|
-
return response.data;
|
|
20
|
-
}
|
|
21
|
-
async getProviders() {
|
|
22
|
-
const response = await this.httpClient.get('/auth/providers');
|
|
23
|
-
if (!response.data) {
|
|
24
|
-
throw new Error('Providers response data is null');
|
|
25
|
-
}
|
|
26
|
-
return response.data;
|
|
27
|
-
}
|
|
28
|
-
// Authentication Endpoints
|
|
29
|
-
async getLoginPage() {
|
|
30
|
-
return this.httpClient.get('/auth/login', { parse: 'text' });
|
|
31
|
-
}
|
|
32
|
-
async generateTokens(request) {
|
|
33
|
-
return this.httpClient.post('/auth/token', request);
|
|
34
|
-
}
|
|
35
|
-
async refreshToken(request) {
|
|
36
|
-
return this.httpClient.post('/auth/refresh', request);
|
|
37
|
-
}
|
|
38
|
-
async generateMockTokens(request) {
|
|
39
|
-
return this.httpClient.post('/auth/mock-token', request);
|
|
40
|
-
}
|
|
41
|
-
async validateToken(token) {
|
|
42
|
-
try {
|
|
43
|
-
const response = await this.validateTokenGet(token);
|
|
44
|
-
return {
|
|
45
|
-
valid: response.status === 200,
|
|
46
|
-
headers: response.headers,
|
|
47
|
-
status: response.status,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
return {
|
|
52
|
-
valid: false,
|
|
53
|
-
headers: {},
|
|
54
|
-
status: 401,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
async validateTokenGet(token) {
|
|
59
|
-
const response = await this.httpClient.head('/auth/validate', {
|
|
60
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
61
|
-
});
|
|
62
|
-
return {
|
|
63
|
-
status: response.status,
|
|
64
|
-
headers: response.headers,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
// Token Management Endpoints
|
|
68
|
-
// NOTE: node auth status lives on AdminApiClient.isAuthed() (/admin-api/is-authed);
|
|
69
|
-
// there is no /auth/is-authed on the auth service.
|
|
70
|
-
async revokeTokens(request) {
|
|
71
|
-
const response = await this.httpClient.post('/admin/revoke', request);
|
|
72
|
-
if (!response.data) {
|
|
73
|
-
throw new Error('Revoke tokens response data is null');
|
|
74
|
-
}
|
|
75
|
-
return response.data;
|
|
76
|
-
}
|
|
77
|
-
// Key Management Endpoints
|
|
78
|
-
async listRootKeys() {
|
|
79
|
-
const response = await this.httpClient.get('/admin/keys');
|
|
80
|
-
if (!response.data) {
|
|
81
|
-
throw new Error('Root keys response data is null');
|
|
82
|
-
}
|
|
83
|
-
return response.data;
|
|
84
|
-
}
|
|
85
|
-
async createRootKey(request) {
|
|
86
|
-
const response = await this.httpClient.post('/admin/keys', request);
|
|
87
|
-
if (!response.data) {
|
|
88
|
-
throw new Error('Create root key response data is null');
|
|
89
|
-
}
|
|
90
|
-
return response.data;
|
|
91
|
-
}
|
|
92
|
-
async deleteRootKey(keyId) {
|
|
93
|
-
return this.httpClient.delete(`/admin/keys/${pathSegment(keyId)}`);
|
|
94
|
-
}
|
|
95
|
-
// Client Management Endpoints
|
|
96
|
-
async listClientKeys() {
|
|
97
|
-
const response = await this.httpClient.get('/admin/keys/clients');
|
|
98
|
-
if (!response.data) {
|
|
99
|
-
throw new Error('Client keys response data is null');
|
|
100
|
-
}
|
|
101
|
-
return response.data;
|
|
102
|
-
}
|
|
103
|
-
async generateClientKey(request) {
|
|
104
|
-
return this.httpClient.post('/admin/client-key', request);
|
|
105
|
-
}
|
|
106
|
-
async deleteClientKey(keyId, clientId) {
|
|
107
|
-
return this.httpClient.delete(`/admin/keys/${pathSegment(keyId)}/clients/${pathSegment(clientId)}`);
|
|
108
|
-
}
|
|
109
|
-
// Permission Management Endpoints
|
|
110
|
-
async getKeyPermissions(keyId) {
|
|
111
|
-
return this.httpClient.get(`/admin/keys/${pathSegment(keyId)}/permissions`);
|
|
112
|
-
}
|
|
113
|
-
async updateKeyPermissions(keyId, changes) {
|
|
114
|
-
// Core expects an { add, remove } delta, not a { permissions } replacement.
|
|
115
|
-
return this.httpClient.put(`/admin/keys/${pathSegment(keyId)}/permissions`, { add: changes.add, remove: changes.remove });
|
|
116
|
-
}
|
|
117
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { AuthApiClient } from './auth-client.js';
|
|
2
|
-
import { AuthApiClientConfig } from './auth-types.js';
|
|
3
|
-
import { HttpClient } from '../http-client/index.js';
|
|
4
|
-
export declare function createBrowserAuthApiClient(_config: AuthApiClientConfig): AuthApiClient;
|
|
5
|
-
export declare function createNodeAuthApiClient(_config: AuthApiClientConfig): AuthApiClient;
|
|
6
|
-
export declare function createAuthApiClient(_config: AuthApiClientConfig): AuthApiClient;
|
|
7
|
-
export declare function createAuthApiClientFromHttpClient(httpClient: HttpClient, _config: AuthApiClientConfig): AuthApiClient;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { AuthApiClient } from './auth-client.js';
|
|
2
|
-
// Mock HTTP client for testing
|
|
3
|
-
class MockHttpClient {
|
|
4
|
-
async get() {
|
|
5
|
-
throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
|
|
6
|
-
}
|
|
7
|
-
async post() {
|
|
8
|
-
throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
|
|
9
|
-
}
|
|
10
|
-
async put() {
|
|
11
|
-
throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
|
|
12
|
-
}
|
|
13
|
-
async delete() {
|
|
14
|
-
throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
|
|
15
|
-
}
|
|
16
|
-
async patch() {
|
|
17
|
-
throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
|
|
18
|
-
}
|
|
19
|
-
async head() {
|
|
20
|
-
throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
|
|
21
|
-
}
|
|
22
|
-
async request() {
|
|
23
|
-
throw new Error('HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client');
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// Factory functions for creating Auth API clients
|
|
27
|
-
export function createBrowserAuthApiClient(_config) {
|
|
28
|
-
const httpClient = new MockHttpClient();
|
|
29
|
-
return new AuthApiClient(httpClient);
|
|
30
|
-
}
|
|
31
|
-
export function createNodeAuthApiClient(_config) {
|
|
32
|
-
const httpClient = new MockHttpClient();
|
|
33
|
-
return new AuthApiClient(httpClient);
|
|
34
|
-
}
|
|
35
|
-
export function createAuthApiClient(_config) {
|
|
36
|
-
const httpClient = new MockHttpClient();
|
|
37
|
-
return new AuthApiClient(httpClient);
|
|
38
|
-
}
|
|
39
|
-
export function createAuthApiClientFromHttpClient(httpClient, _config) {
|
|
40
|
-
return new AuthApiClient(httpClient);
|
|
41
|
-
}
|