@calimero-network/mero-js 26.11.0 → 27.0.1

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.
Files changed (220) hide show
  1. package/README.md +73 -288
  2. package/dist/cloud-v2/attestation.d.ts +91 -0
  3. package/dist/cloud-v2/attestation.js +135 -0
  4. package/dist/cloud-v2/capabilities.d.ts +30 -0
  5. package/dist/cloud-v2/capabilities.js +31 -0
  6. package/dist/cloud-v2/client.d.ts +426 -0
  7. package/dist/cloud-v2/client.js +646 -0
  8. package/dist/cloud-v2/errors.d.ts +98 -0
  9. package/dist/cloud-v2/errors.js +165 -0
  10. package/dist/cloud-v2/index.d.ts +10 -0
  11. package/dist/cloud-v2/index.js +5 -0
  12. package/dist/cloud-v2/passkey.d.ts +27 -0
  13. package/dist/cloud-v2/passkey.js +73 -0
  14. package/dist/crypto/internal.d.ts +3 -37
  15. package/dist/crypto/internal.js +3 -72
  16. package/dist/index.browser.mjs +4 -2
  17. package/dist/index.browser.mjs.map +4 -4
  18. package/dist/index.cjs +1750 -12772
  19. package/dist/index.cjs.map +4 -4
  20. package/dist/index.d.ts +3 -56
  21. package/dist/index.js +14 -75
  22. package/dist/index.mjs +1750 -12772
  23. package/dist/index.mjs.map +4 -4
  24. package/dist/sealed/index.d.ts +4 -4
  25. package/dist/sealed/index.js +2 -2
  26. package/dist/sealed/release.d.ts +21 -76
  27. package/dist/sealed/release.js +24 -108
  28. package/dist/sealed/sealed.d.ts +8 -56
  29. package/dist/sealed/sealed.js +11 -89
  30. package/dist/sealed/verify.d.ts +7 -13
  31. package/dist/sealed/verify.js +3 -7
  32. package/package.json +8 -13
  33. package/dist/account/account-admin.d.ts +0 -80
  34. package/dist/account/account-admin.js +0 -346
  35. package/dist/account/account.d.ts +0 -196
  36. package/dist/account/account.js +0 -269
  37. package/dist/account/application-id.d.ts +0 -50
  38. package/dist/account/application-id.js +0 -83
  39. package/dist/account/bootstrap-from-invitation.d.ts +0 -110
  40. package/dist/account/bootstrap-from-invitation.js +0 -80
  41. package/dist/account/create-context.d.ts +0 -103
  42. package/dist/account/create-context.js +0 -267
  43. package/dist/account/govern.d.ts +0 -24
  44. package/dist/account/govern.js +0 -50
  45. package/dist/account/index.d.ts +0 -26
  46. package/dist/account/index.js +0 -15
  47. package/dist/account/join-as-account.d.ts +0 -23
  48. package/dist/account/join-as-account.js +0 -34
  49. package/dist/account/join-with-node.d.ts +0 -115
  50. package/dist/account/join-with-node.js +0 -161
  51. package/dist/account/node-admin.d.ts +0 -20
  52. package/dist/account/node-admin.js +0 -33
  53. package/dist/account/relay-from-invitation.d.ts +0 -137
  54. package/dist/account/relay-from-invitation.js +0 -180
  55. package/dist/account/relay-login-error.d.ts +0 -32
  56. package/dist/account/relay-login-error.js +0 -34
  57. package/dist/account/session.d.ts +0 -266
  58. package/dist/account/session.js +0 -915
  59. package/dist/account/wordlist.d.ts +0 -16
  60. package/dist/account/wordlist.js +0 -273
  61. package/dist/admin-api/admin-client.d.ts +0 -688
  62. package/dist/admin-api/admin-client.js +0 -1230
  63. package/dist/admin-api/admin-factory.d.ts +0 -14
  64. package/dist/admin-api/admin-factory.js +0 -22
  65. package/dist/admin-api/admin-types.d.ts +0 -2046
  66. package/dist/admin-api/admin-types.js +0 -12
  67. package/dist/admin-api/index.d.ts +0 -4
  68. package/dist/admin-api/index.js +0 -5
  69. package/dist/admin-api/redeem-invitation.d.ts +0 -101
  70. package/dist/admin-api/redeem-invitation.js +0 -200
  71. package/dist/auth/index.d.ts +0 -34
  72. package/dist/auth/index.js +0 -67
  73. package/dist/auth-api/auth-client.d.ts +0 -31
  74. package/dist/auth-api/auth-client.js +0 -117
  75. package/dist/auth-api/auth-factory.d.ts +0 -7
  76. package/dist/auth-api/auth-factory.js +0 -41
  77. package/dist/auth-api/auth-types.d.ts +0 -130
  78. package/dist/auth-api/auth-types.js +0 -2
  79. package/dist/auth-api/index.d.ts +0 -3
  80. package/dist/auth-api/index.js +0 -4
  81. package/dist/capabilities.d.ts +0 -84
  82. package/dist/capabilities.js +0 -117
  83. package/dist/cloud/account-ownership.d.ts +0 -63
  84. package/dist/cloud/account-ownership.js +0 -87
  85. package/dist/cloud/cloud-client.d.ts +0 -949
  86. package/dist/cloud/cloud-client.js +0 -1068
  87. package/dist/cloud/connect.d.ts +0 -213
  88. package/dist/cloud/connect.js +0 -260
  89. package/dist/cloud/enrol-redirect.d.ts +0 -181
  90. package/dist/cloud/enrol-redirect.js +0 -244
  91. package/dist/cloud/index.d.ts +0 -12
  92. package/dist/cloud/index.js +0 -10
  93. package/dist/cloud/link-redirect.d.ts +0 -95
  94. package/dist/cloud/link-redirect.js +0 -121
  95. package/dist/cloud/link-state.d.ts +0 -36
  96. package/dist/cloud/link-state.js +0 -104
  97. package/dist/cloud/routing-proof.d.ts +0 -113
  98. package/dist/cloud/routing-proof.js +0 -96
  99. package/dist/device-cert/device-cert.d.ts +0 -189
  100. package/dist/device-cert/device-cert.js +0 -239
  101. package/dist/device-cert/device-scope.d.ts +0 -58
  102. package/dist/device-cert/device-scope.js +0 -76
  103. package/dist/device-cert/index.d.ts +0 -4
  104. package/dist/device-cert/index.js +0 -3
  105. package/dist/ephemeral/codec.d.ts +0 -4
  106. package/dist/ephemeral/codec.js +0 -12
  107. package/dist/ephemeral/index.d.ts +0 -57
  108. package/dist/ephemeral/index.js +0 -101
  109. package/dist/ephemeral/types.d.ts +0 -27
  110. package/dist/ephemeral/types.js +0 -1
  111. package/dist/errors/classify.d.ts +0 -65
  112. package/dist/errors/classify.js +0 -117
  113. package/dist/errors/index.d.ts +0 -2
  114. package/dist/errors/index.js +0 -1
  115. package/dist/events/group.d.ts +0 -95
  116. package/dist/events/group.js +0 -10
  117. package/dist/events/index.d.ts +0 -5
  118. package/dist/events/index.js +0 -2
  119. package/dist/events/sse.d.ts +0 -127
  120. package/dist/events/sse.js +0 -469
  121. package/dist/events/ws.d.ts +0 -57
  122. package/dist/events/ws.js +0 -212
  123. package/dist/http-client/api-response.d.ts +0 -15
  124. package/dist/http-client/api-response.js +0 -1
  125. package/dist/http-client/http-factory.d.ts +0 -71
  126. package/dist/http-client/http-factory.js +0 -68
  127. package/dist/http-client/http-types.d.ts +0 -87
  128. package/dist/http-client/http-types.js +0 -1
  129. package/dist/http-client/index.d.ts +0 -7
  130. package/dist/http-client/index.js +0 -11
  131. package/dist/http-client/path-segment.d.ts +0 -1
  132. package/dist/http-client/path-segment.js +0 -8
  133. package/dist/http-client/retry.d.ts +0 -5
  134. package/dist/http-client/retry.js +0 -82
  135. package/dist/http-client/signal-utils.d.ts +0 -2
  136. package/dist/http-client/signal-utils.js +0 -41
  137. package/dist/http-client/web-client.d.ts +0 -80
  138. package/dist/http-client/web-client.js +0 -557
  139. package/dist/invitation/index.d.ts +0 -2
  140. package/dist/invitation/index.js +0 -2
  141. package/dist/invitation/invitation.d.ts +0 -106
  142. package/dist/invitation/invitation.js +0 -104
  143. package/dist/login/index.d.ts +0 -4
  144. package/dist/login/index.js +0 -3
  145. package/dist/login/login.d.ts +0 -111
  146. package/dist/login/login.js +0 -110
  147. package/dist/login/session.d.ts +0 -124
  148. package/dist/login/session.js +0 -141
  149. package/dist/mero-js.d.ts +0 -231
  150. package/dist/mero-js.js +0 -469
  151. package/dist/namespace-op/index.d.ts +0 -2
  152. package/dist/namespace-op/index.js +0 -3
  153. package/dist/namespace-op/namespace-op.d.ts +0 -150
  154. package/dist/namespace-op/namespace-op.js +0 -191
  155. package/dist/nodeDiscovery.d.ts +0 -47
  156. package/dist/nodeDiscovery.js +0 -101
  157. package/dist/owner-op/index.d.ts +0 -2
  158. package/dist/owner-op/index.js +0 -3
  159. package/dist/owner-op/owner-op.d.ts +0 -119
  160. package/dist/owner-op/owner-op.js +0 -214
  161. package/dist/presence/relay-presence.d.ts +0 -56
  162. package/dist/presence/relay-presence.js +0 -107
  163. package/dist/presence/statement.d.ts +0 -12
  164. package/dist/presence/statement.js +0 -17
  165. package/dist/relay/index.d.ts +0 -4
  166. package/dist/relay/index.js +0 -2
  167. package/dist/relay/nonce-source.d.ts +0 -72
  168. package/dist/relay/nonce-source.js +0 -111
  169. package/dist/relay/relay-client.d.ts +0 -472
  170. package/dist/relay/relay-client.js +0 -518
  171. package/dist/relay-attestation/attest-node-key.d.ts +0 -66
  172. package/dist/relay-attestation/attest-node-key.js +0 -110
  173. package/dist/relay-attestation/index.d.ts +0 -1
  174. package/dist/relay-attestation/index.js +0 -1
  175. package/dist/request/caller-proof.d.ts +0 -43
  176. package/dist/request/caller-proof.js +0 -36
  177. package/dist/request/index.d.ts +0 -3
  178. package/dist/request/index.js +0 -3
  179. package/dist/request/proof-signer.d.ts +0 -49
  180. package/dist/request/proof-signer.js +0 -59
  181. package/dist/request/request-sig.d.ts +0 -99
  182. package/dist/request/request-sig.js +0 -95
  183. package/dist/request-proof/authorizer.d.ts +0 -41
  184. package/dist/request-proof/authorizer.js +0 -60
  185. package/dist/request-proof/index.d.ts +0 -4
  186. package/dist/request-proof/index.js +0 -2
  187. package/dist/request-proof/request-proof.d.ts +0 -53
  188. package/dist/request-proof/request-proof.js +0 -95
  189. package/dist/rpc/index.d.ts +0 -64
  190. package/dist/rpc/index.js +0 -78
  191. package/dist/signer/index.d.ts +0 -2
  192. package/dist/signer/index.js +0 -3
  193. package/dist/signer/signer.d.ts +0 -92
  194. package/dist/signer/signer.js +0 -147
  195. package/dist/token-store/index.d.ts +0 -27
  196. package/dist/token-store/index.js +0 -62
  197. package/dist/transport/index.d.ts +0 -6
  198. package/dist/transport/index.js +0 -3
  199. package/dist/transport/mero-client.d.ts +0 -294
  200. package/dist/transport/mero-client.js +0 -386
  201. package/dist/transport/relay-observer.d.ts +0 -152
  202. package/dist/transport/relay-observer.js +0 -173
  203. package/dist/transport/relay-transport.d.ts +0 -73
  204. package/dist/transport/relay-transport.js +0 -94
  205. package/dist/transport/types.d.ts +0 -100
  206. package/dist/transport/types.js +0 -32
  207. package/dist/types/api-response.d.ts +0 -15
  208. package/dist/types/api-response.js +0 -1
  209. package/dist/types/index.d.ts +0 -1
  210. package/dist/types/index.js +0 -1
  211. package/dist/warrant/creation-warrant.d.ts +0 -163
  212. package/dist/warrant/creation-warrant.js +0 -194
  213. package/dist/warrant/governance-op.d.ts +0 -202
  214. package/dist/warrant/governance-op.js +0 -355
  215. package/dist/warrant/governance-warrant.d.ts +0 -124
  216. package/dist/warrant/governance-warrant.js +0 -164
  217. package/dist/warrant/index.d.ts +0 -8
  218. package/dist/warrant/index.js +0 -5
  219. package/dist/warrant/warrant.d.ts +0 -188
  220. package/dist/warrant/warrant.js +0 -202
@@ -1,244 +0,0 @@
1
- /**
2
- * Getting an app's device key certified **by a wallet on another origin**.
3
- *
4
- * An app holds a device keypair and no account. It cannot certify that key
5
- * itself: certifying is a signature by an account root, and the root is the one
6
- * key in the system with nothing above it to revoke it — so a page that can
7
- * reach it can take the account for good. The whole design is that the app
8
- * never can.
9
- *
10
- * What separates them is the **same-origin policy**, and that is why this is a
11
- * redirect rather than a message, a popup API or a shared library. The wallet
12
- * keeps the root as a non-extractable key in *its* origin's IndexedDB; script
13
- * running on the app's origin cannot read another origin's storage, cannot read
14
- * its keys, and cannot draw its consent screen. Nothing weaker enforces that —
15
- * an iframe with a `postMessage` bridge would put the approval dialog inside a
16
- * document the app can style, overlay and click for you.
17
- *
18
- * So the shape is: send the person to the wallet naming the key you want
19
- * certified, they approve there, and the wallet redirects back with an
20
- * `AccountProof<DeviceCert>` over that key.
21
- *
22
- * ## The credential comes back in the FRAGMENT
23
- *
24
- * Same reason the cloud's link grant does — see `link-redirect.ts`. A fragment
25
- * is not sent to the server, so it lands in nobody's access log, no referrer
26
- * header and no proxy trace on the way. A query string would put a credential
27
- * into every log between here and the app's host.
28
- *
29
- * ## What the app must check before storing anything
30
- *
31
- * A fragment is written by whoever sent the person to this URL, which is not
32
- * necessarily the wallet. Three things therefore have to be checked locally:
33
- *
34
- * - the credential certifies **the key this app asked about** — otherwise the
35
- * app stores a credential it cannot sign for, and every warrant it mints is
36
- * refused somewhere far from here;
37
- * - the credential is **internally consistent** — the root signed it, the
38
- * account it names is the one that root derives, and its device id was
39
- * minted for that account ({@link verifyDeviceCredential});
40
- * - the account reported beside it is **the account the credential names** —
41
- * the app shows that id to a person and keys state by it, so a mismatch is a
42
- * credential filed under someone else's name.
43
- *
44
- * {@link completeDeviceEnrolment} does all three and throws on any of them,
45
- * before the caller has anything to store. What none of them establish is that
46
- * the account is the *person's* — anyone can mint a root offline and certify
47
- * any public key with it, and a device key is public once enrolled. That is
48
- * what the state narrows: {@link deviceEnrolmentUrl} remembers each request, and
49
- * {@link completeDeviceEnrolment} refuses an answer to a request this browser did
50
- * not start, rather than to a link someone sent.
51
- */
52
- import { verifyDeviceCredential, } from '../device-cert/index.js';
53
- import { beginState, takeState } from './link-state.js';
54
- /** Query parameters the wallet reads. Mirrors the cloud's `link-account` shape. */
55
- const DEVICE_PARAM = 'enrol-device';
56
- const KEM_PARAM = 'enrol-kem';
57
- const CALLBACK_PARAM = 'callback-url';
58
- const STATE_PARAM = 'state';
59
- const PENDING_KEY = 'calimero.device.enrol.pending';
60
- /**
61
- * The URL to send a person to.
62
- *
63
- * Built rather than documented as a string so the parameter names live in one
64
- * place: a wallet and an app that disagree about them fail as a blank consent
65
- * screen, which looks like an outage rather than a contract mismatch.
66
- *
67
- * Remembers the request under a fresh state the wallet echoes back, so
68
- * {@link completeDeviceEnrolment} can match the answer to it.
69
- */
70
- export function deviceEnrolmentUrl(options) {
71
- assertHex(options.devicePublicKey, 'devicePublicKey');
72
- assertHex(options.kemPublicKey, 'kemPublicKey');
73
- assertSafeReturnTo(options.returnTo);
74
- warnOnForeignCallback(options.returnTo);
75
- const url = new URL(options.walletUrl);
76
- url.searchParams.set(DEVICE_PARAM, options.devicePublicKey);
77
- url.searchParams.set(KEM_PARAM, options.kemPublicKey);
78
- url.searchParams.set(CALLBACK_PARAM, options.returnTo);
79
- url.searchParams.set(STATE_PARAM, beginState(PENDING_KEY, {}));
80
- return url.toString();
81
- }
82
- /** Send the person to the wallet to approve this device. Does not return. */
83
- export function beginDeviceEnrolment(options) {
84
- window.location.assign(deviceEnrolmentUrl(options));
85
- // `assign` does not stop execution, and a caller that kept going would act on
86
- // a page already being replaced.
87
- throw new Error('redirecting to the wallet to approve this device');
88
- }
89
- /**
90
- * Read an enrolment out of the current URL fragment, and strip it.
91
- *
92
- * Returns `null` on an ordinary page load, so a caller can run it
93
- * unconditionally at startup. Throws when the person declined, because that is
94
- * an outcome the app has to show rather than a state to wait in.
95
- *
96
- * Nothing is verified here — the shape is separated from the checking so a
97
- * caller can see what arrived (to log it, or to name the account in a refusal)
98
- * even when it goes on to be refused. Nothing read out of this belongs in
99
- * storage until {@link completeDeviceEnrolment} has passed.
100
- */
101
- export function readEnrolmentCallback(location = window.location) {
102
- const params = new URLSearchParams(location.hash.replace(/^#/, ''));
103
- const error = params.get('error');
104
- if (error) {
105
- takeState(PENDING_KEY, params.get(STATE_PARAM));
106
- stripFragment();
107
- throw new Error(error === 'denied' || error === 'cancelled'
108
- ? 'the device was not approved at the wallet'
109
- : `the wallet refused this enrolment: ${error}`);
110
- }
111
- const credential = params.get('credential');
112
- const account = params.get('account');
113
- const device = params.get('device');
114
- if (!credential || !account || !device)
115
- return null;
116
- // Consumed once. A credential left in the address bar is replayed by a
117
- // reload, ends up in the browser's history and session restore, and is the
118
- // one value here worth keeping out of both.
119
- stripFragment();
120
- const state = params.get('state');
121
- return { credential, account, device, ...(state ? { state } : {}) };
122
- }
123
- function stripFragment() {
124
- if (typeof history === 'undefined' || !history.replaceState)
125
- return;
126
- history.replaceState(null, '', location.pathname + location.search);
127
- }
128
- /**
129
- * Verify what came back, and refuse loudly rather than returning a half-checked
130
- * credential.
131
- *
132
- * Every throw here is a credential that must not reach storage. The order is
133
- * deliberate: the cheap local comparisons first, so a fragment aimed at the
134
- * wrong app is rejected without a signature verification, and the message names
135
- * what disagreed rather than reporting "invalid credential".
136
- *
137
- * The state is spent on the first call, so an answer is accepted at most once.
138
- */
139
- export async function completeDeviceEnrolment(options) {
140
- if (!takeState(PENDING_KEY, options.state)) {
141
- throw new Error('this enrolment answers a request this app did not make - start the ' +
142
- 'enrolment again from here');
143
- }
144
- const certificate = await verifyDeviceCredential(options.credential);
145
- if (certificate.signPublicKey !== normaliseHex(options.devicePublicKey)) {
146
- throw new Error(`this credential certifies device key ${certificate.signPublicKey}, but ` +
147
- `this app holds ${normaliseHex(options.devicePublicKey)} — it would ` +
148
- 'sign with a key the certificate does not cover');
149
- }
150
- if (options.kemPublicKey !== undefined &&
151
- certificate.kemPublicKey !== normaliseHex(options.kemPublicKey)) {
152
- throw new Error(`this credential names delivery key ${certificate.kemPublicKey}, not the ` +
153
- 'one this app asked about — group keys would be sealed to somebody else');
154
- }
155
- if (certificate.account !== normaliseHex(options.account)) {
156
- throw new Error(`the wallet reported account ${normaliseHex(options.account)}, but the ` +
157
- `credential names ${certificate.account}`);
158
- }
159
- if (certificate.device !== normaliseHex(options.device)) {
160
- throw new Error(`the wallet reported device ${normaliseHex(options.device)}, but the ` +
161
- `credential names ${certificate.device}`);
162
- }
163
- return {
164
- credential: options.credential,
165
- account: certificate.account,
166
- device: certificate.device,
167
- certificate,
168
- };
169
- }
170
- function assertHex(value, label) {
171
- if (!/^[0-9a-f]{64}$/.test(value)) {
172
- throw new Error(`${label} must be 64 lowercase hex characters`);
173
- }
174
- }
175
- function normaliseHex(value) {
176
- return value.trim().toLowerCase();
177
- }
178
- /**
179
- * Where a credential may be delivered.
180
- *
181
- * `https:` anywhere, `http:` only on loopback. Plain HTTP elsewhere would put
182
- * the fragment on a page any network position can rewrite, and the fragment is
183
- * the credential; loopback is exempt because it never leaves the machine and is
184
- * the only way to develop against a wallet at all.
185
- *
186
- * Everything else — `javascript:`, `data:`, a custom app scheme — is refused.
187
- * The first two are script the wallet would be asked to run; a custom scheme is
188
- * a legitimate thing to want for a native app, but which installed handler
189
- * claims it is not something this SDK can check, so it is refused here rather
190
- * than accepted on trust.
191
- */
192
- /**
193
- * Is the credential coming back to somewhere other than the page that asked?
194
- *
195
- * Normally an app enrols its own device key and gets the answer back on its own
196
- * origin, so the two match. They can legitimately differ — a callback page on a
197
- * sibling host, an app mid-migration — which is why this is not refused.
198
- *
199
- * But it is the shape an abuse would also take: send someone to the real wallet
200
- * and have the credential delivered elsewhere. Nothing here can tell the two
201
- * apart, and a client-side check constrains nobody who did not call this
202
- * function anyway. What it is good for is the honest case — surfacing the
203
- * mismatch to whoever is building the app, and giving a wallet the same fact to
204
- * put in front of the person approving.
205
- *
206
- * Exported so a wallet can render it, rather than each caller re-deriving it.
207
- */
208
- export function callbackIsForeign(returnTo, pageOrigin) {
209
- const here = pageOrigin ?? (typeof window === 'undefined' ? undefined : window.location.origin);
210
- if (!here)
211
- return false;
212
- try {
213
- return new URL(returnTo).origin !== here;
214
- }
215
- catch {
216
- return false;
217
- }
218
- }
219
- function warnOnForeignCallback(returnTo) {
220
- if (!callbackIsForeign(returnTo))
221
- return;
222
- // A warning, not a throw: see `callbackIsForeign` for why this is a smell
223
- // rather than a verdict.
224
- console.warn(`[calimero] enrolling a device but sending the credential to ${new URL(returnTo).origin}, ` +
225
- `which is not this page's origin. Legitimate when deliberate; worth a second look otherwise.`);
226
- }
227
- function assertSafeReturnTo(returnTo) {
228
- let url;
229
- try {
230
- url = new URL(returnTo);
231
- }
232
- catch {
233
- throw new Error(`returnTo must be an absolute URL, got ${returnTo}`);
234
- }
235
- const loopback = url.hostname === 'localhost' ||
236
- url.hostname === '127.0.0.1' ||
237
- url.hostname === '[::1]' ||
238
- url.hostname === '::1';
239
- if (url.protocol === 'https:')
240
- return;
241
- if (url.protocol === 'http:' && loopback)
242
- return;
243
- throw new Error(`returnTo must be https:, or http: on localhost — got ${url.protocol}//${url.hostname}`);
244
- }
@@ -1,12 +0,0 @@
1
- export { CloudClient, admitterAddrsFromNetworkStatus, AccountHaRefusedError, AccountNotLinkedError, AccountLinkedToSeveralUsersError, HaRequestPendingError, UnknownRelayError, RelayNotDialableError, } from './cloud-client.js';
2
- export type { CloudClientConfig, CloudSession, CloudNamespace, CloudRelay, CloudRelayJoin, CloudAccountRelay, CloudNamespaceNode, CloudNamespaceRouting, CloudMachine, CloudMachineNamespace, CloudLinkedAccount, CloudLinkedAccounts, CloudAccountLink, AccountLinkChallenge, AccountLinkProof, AccountLinkHandoff, AccountLinkCallback, AccountLoginChallenge, AccountLoginProof, EnableHAOptions, DisableHAOptions, EnableHaAsAccountOptions, } from './cloud-client.js';
3
- export { connectCloud, connectCloudWithAccount } from './connect.js';
4
- export type { ConnectCloudOptions, ConnectCloudWithAccountOptions, CloudConnection, } from './connect.js';
5
- export { signRoutingChallenge, routingProofHeaders } from './routing-proof.js';
6
- export type { RoutingCredential, DiscoveryChallenge, RoutingChallenge, RoutingProofHeaders, } from './routing-proof.js';
7
- export { cloudLinkUrl, beginCloudLink, readCloudLinkCallback, completeCloudLink, } from './link-redirect.js';
8
- export type { BeginCloudLinkOptions, CloudLinkCallback, CompleteCloudLinkOptions, } from './link-redirect.js';
9
- export { deviceEnrolmentUrl, beginDeviceEnrolment, readEnrolmentCallback, completeDeviceEnrolment, callbackIsForeign, } from './enrol-redirect.js';
10
- export type { DeviceEnrolmentOptions, DeviceEnrolmentCallback, CompleteDeviceEnrolmentOptions, EnrolledDevice, } from './enrol-redirect.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';
@@ -1,10 +0,0 @@
1
- export { CloudClient, admitterAddrsFromNetworkStatus, AccountHaRefusedError, AccountNotLinkedError, AccountLinkedToSeveralUsersError, HaRequestPendingError, UnknownRelayError, RelayNotDialableError, } from './cloud-client.js';
2
- export { connectCloud, connectCloudWithAccount } from './connect.js';
3
- export { signRoutingChallenge, routingProofHeaders } from './routing-proof.js';
4
- // Linking an account to a cloud login from a browser — the redirect half the
5
- // cloud's consent screen expects an app to implement.
6
- export { cloudLinkUrl, beginCloudLink, readCloudLinkCallback, completeCloudLink, } from './link-redirect.js';
7
- // Getting this app's device key certified by a wallet on another origin — the
8
- // redirect that keeps the account root out of the app's reach.
9
- export { deviceEnrolmentUrl, beginDeviceEnrolment, readEnrolmentCallback, completeDeviceEnrolment, callbackIsForeign, } from './enrol-redirect.js';
10
- export { signAccountOwnershipClaim, signAccountHaClaim, ACCOUNT_OWNERSHIP_AUDIENCE, ACCOUNT_HA_AUDIENCE, } from './account-ownership.js';
@@ -1,95 +0,0 @@
1
- /**
2
- * Linking an account to a cloud login **from a browser**.
3
- *
4
- * The cloud's own accounts screen says it plainly: *"The cloud cannot link an
5
- * account on your behalf: proving you own it is a signature by the account root
6
- * key, which the cloud never holds."* So the link needs two things that live in
7
- * different places — a cloud session, which only Google can mint, and a root
8
- * signature, which only the holder can make. Neither side can do it alone.
9
- *
10
- * The cloud already implements its half: send a person to
11
- * `?link-account=<id>&challenge=<sha256(verifier)>&callback-url=<url>`, it
12
- * shows a consent screen and redirects back with a **grant** — that login's
13
- * sealed consent to link one named account. This module is the missing other
14
- * half, and it is deliberately small: send them there, then spend what comes
15
- * back.
16
- *
17
- * ## Why a grant rather than a session
18
- *
19
- * A grant is spendable only with the account's root and the verifier this
20
- * browser kept when it began the link. That is what lets the page complete the
21
- * link while holding **no cloud session at all** - and it is why a wallet
22
- * should never be handed one. A session token is a credential for everything
23
- * that login can do; a grant is a credential for exactly one link, to exactly
24
- * one account, sealed at the moment the person read the id on screen and
25
- * agreed to it.
26
- *
27
- * ## The account id is chosen by us and read by them
28
- *
29
- * The cloud seals the account into the grant at consent time, and its own note
30
- * says why: the server can check that the key which later signs derives that
31
- * same account, but *"it cannot know whether the person meant this account"*.
32
- * The consent screen is that check. So show the same id the person will see,
33
- * before sending them — a wallet that hides it has removed the only safeguard.
34
- */
35
- import type { CloudAccountLink } from './cloud-client.js';
36
- import { type RootSource } from '../account/account.js';
37
- export interface BeginCloudLinkOptions {
38
- /** The account to link — 64 hex. Show this to the person before redirecting. */
39
- accountId: string;
40
- /**
41
- * Where the cloud sends them back. Must be `https:`, or `http:` on localhost.
42
- *
43
- * The grant returns in the URL **fragment**, which is never sent to a server,
44
- * so it does not land in anyone's access log on the way.
45
- */
46
- returnTo: string;
47
- /** The cloud's web origin. Defaults to the hosted one. */
48
- cloudWebUrl?: string;
49
- }
50
- /**
51
- * The consent URL to send a person to. Remembers the link, and the verifier its
52
- * grant will be bound to, so only this browser can match the callback and spend it.
53
- */
54
- export declare function cloudLinkUrl(options: BeginCloudLinkOptions): Promise<string>;
55
- /** Send the person to the cloud to consent. Does not return. */
56
- export declare function beginCloudLink(options: BeginCloudLinkOptions): Promise<never>;
57
- /** What came back in the fragment, if anything. */
58
- export interface CloudLinkCallback {
59
- grant: string;
60
- /** The account the cloud sealed into the grant. */
61
- account: string;
62
- /** The cloud login that consented. Show it before spending: anyone holding the consent URL could have answered it. */
63
- login: string;
64
- /** This browser's secret for the grant, kept from the redirect. Spend it with the grant. */
65
- verifier: string;
66
- }
67
- /**
68
- * Read a grant out of the current URL fragment, and strip it.
69
- *
70
- * Returns `null` when this is an ordinary page load, so a caller can run it
71
- * unconditionally at startup. Throws when declined or when this browser did not
72
- * begin the link.
73
- */
74
- export declare function readCloudLinkCallback(location?: {
75
- hash: string;
76
- search?: string;
77
- }): CloudLinkCallback | null;
78
- export interface CompleteCloudLinkOptions extends CloudLinkCallback {
79
- /** The account root — a 64-hex secret, or a Signer over a key you cannot read. */
80
- root: RootSource;
81
- /** The **manager** API base, e.g. `https://manager.cloud.calimero.network`. */
82
- cloudBaseUrl?: string;
83
- fetch?: typeof fetch;
84
- }
85
- /**
86
- * Spend the grant: sign it with the root and hand it back to the cloud.
87
- *
88
- * Anonymous by construction — the client is built with no session, because the
89
- * whole point is that the page never needed one.
90
- *
91
- * @throws if the grant names a different account than the root derives, before
92
- * anything is sent. The cloud checks this too, but failing here says which two
93
- * accounts disagreed rather than returning a bare 403.
94
- */
95
- export declare function completeCloudLink(options: CompleteCloudLinkOptions): Promise<CloudAccountLink>;
@@ -1,121 +0,0 @@
1
- /**
2
- * Linking an account to a cloud login **from a browser**.
3
- *
4
- * The cloud's own accounts screen says it plainly: *"The cloud cannot link an
5
- * account on your behalf: proving you own it is a signature by the account root
6
- * key, which the cloud never holds."* So the link needs two things that live in
7
- * different places — a cloud session, which only Google can mint, and a root
8
- * signature, which only the holder can make. Neither side can do it alone.
9
- *
10
- * The cloud already implements its half: send a person to
11
- * `?link-account=<id>&challenge=<sha256(verifier)>&callback-url=<url>`, it
12
- * shows a consent screen and redirects back with a **grant** — that login's
13
- * sealed consent to link one named account. This module is the missing other
14
- * half, and it is deliberately small: send them there, then spend what comes
15
- * back.
16
- *
17
- * ## Why a grant rather than a session
18
- *
19
- * A grant is spendable only with the account's root and the verifier this
20
- * browser kept when it began the link. That is what lets the page complete the
21
- * link while holding **no cloud session at all** - and it is why a wallet
22
- * should never be handed one. A session token is a credential for everything
23
- * that login can do; a grant is a credential for exactly one link, to exactly
24
- * one account, sealed at the moment the person read the id on screen and
25
- * agreed to it.
26
- *
27
- * ## The account id is chosen by us and read by them
28
- *
29
- * The cloud seals the account into the grant at consent time, and its own note
30
- * says why: the server can check that the key which later signs derives that
31
- * same account, but *"it cannot know whether the person meant this account"*.
32
- * The consent screen is that check. So show the same id the person will see,
33
- * before sending them — a wallet that hides it has removed the only safeguard.
34
- */
35
- import { CloudClient } from './cloud-client.js';
36
- import { resolveRoot } from '../account/account.js';
37
- import { beginLinkState, linkGrantClaims, takeLinkVerifier, withoutLinkState } from './link-state.js';
38
- /** Where the consent screen lives. The web app, not the manager API. */
39
- const DEFAULT_CLOUD_WEB_URL = 'https://cloud.calimero.network';
40
- /**
41
- * The consent URL to send a person to. Remembers the link, and the verifier its
42
- * grant will be bound to, so only this browser can match the callback and spend it.
43
- */
44
- export async function cloudLinkUrl(options) {
45
- const url = new URL(options.cloudWebUrl ?? DEFAULT_CLOUD_WEB_URL);
46
- const { callbackUrl, challenge } = await beginLinkState(options.accountId, options.returnTo);
47
- url.searchParams.set('link-account', options.accountId);
48
- url.searchParams.set('challenge', challenge);
49
- url.searchParams.set('callback-url', callbackUrl);
50
- return url.toString();
51
- }
52
- /** Send the person to the cloud to consent. Does not return. */
53
- export async function beginCloudLink(options) {
54
- window.location.assign(await cloudLinkUrl(options));
55
- // `assign` does not stop execution, and a caller that kept going would act on
56
- // a page already being replaced.
57
- throw new Error('redirecting to the cloud to authorise this link');
58
- }
59
- /**
60
- * Read a grant out of the current URL fragment, and strip it.
61
- *
62
- * Returns `null` when this is an ordinary page load, so a caller can run it
63
- * unconditionally at startup. Throws when declined or when this browser did not
64
- * begin the link.
65
- */
66
- export function readCloudLinkCallback(location = window.location) {
67
- const params = new URLSearchParams(location.hash.replace(/^#/, ''));
68
- const error = params.get('error');
69
- if (error) {
70
- takeLinkVerifier(location.search ?? '', null);
71
- stripFragment();
72
- throw new Error(error === 'denied'
73
- ? 'the account link was declined at the cloud'
74
- : `the cloud refused this link: ${error}`);
75
- }
76
- const grant = params.get('grant');
77
- const account = params.get('account');
78
- if (!grant || !account)
79
- return null;
80
- const verifier = takeLinkVerifier(location.search ?? '', account);
81
- const claims = linkGrantClaims(grant);
82
- if (!verifier || claims?.account !== account) {
83
- stripFragment();
84
- throw new Error('this account link answers a request this browser did not make - start the link again from here');
85
- }
86
- // Consumed once. Leaving it in the bar means a reload replays a spent grant
87
- // and reports a confusing failure instead of the link that already worked.
88
- stripFragment();
89
- return { grant, account, login: claims.login, verifier };
90
- }
91
- function stripFragment() {
92
- if (typeof history === 'undefined' || !history.replaceState)
93
- return;
94
- history.replaceState(null, '', location.pathname + withoutLinkState(location.search));
95
- }
96
- /**
97
- * Spend the grant: sign it with the root and hand it back to the cloud.
98
- *
99
- * Anonymous by construction — the client is built with no session, because the
100
- * whole point is that the page never needed one.
101
- *
102
- * @throws if the grant names a different account than the root derives, before
103
- * anything is sent. The cloud checks this too, but failing here says which two
104
- * accounts disagreed rather than returning a bare 403.
105
- */
106
- export async function completeCloudLink(options) {
107
- const root = await resolveRoot(options.root);
108
- if (root.accountId !== options.account) {
109
- throw new Error(`this grant is for account ${options.account}, but the root here derives ` +
110
- `${root.accountId} — it was minted for a different account`);
111
- }
112
- const cloud = new CloudClient({
113
- cloudBaseUrl: options.cloudBaseUrl,
114
- fetch: options.fetch,
115
- });
116
- return cloud.linkAccountWithGrant({
117
- grant: options.grant,
118
- verifier: options.verifier,
119
- signer: root.signer,
120
- });
121
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * Redirects this browser began, so a callback can be matched to one. `localStorage`,
3
- * not `sessionStorage`: consent opens in a `noopener` tab with empty session storage.
4
- */
5
- export declare const ACCOUNT_HEX: RegExp;
6
- export declare const VERIFIER_HEX: RegExp;
7
- /** Remember `payload` in the `key` store under a fresh random state, and return the state. */
8
- export declare function beginState<T extends object>(key: string, payload: T): string;
9
- /** Removes and returns what `state` was begun with, or null when this browser did not begin it. */
10
- export declare function takeState<T extends object>(key: string, state: string | null | undefined): T | null;
11
- /** What a link grant claims, read without its seal, which only the cloud can check. */
12
- export interface LinkGrantClaims {
13
- /** The cloud login that consented. */
14
- login: string;
15
- account: string;
16
- challenge: string;
17
- }
18
- /** The challenge a verifier commits to: sha256 of its bytes, hex. */
19
- export declare function linkChallenge(verifier: string): Promise<string>;
20
- /**
21
- * Remember a link for `account` under a fresh verifier. Returns `returnTo`
22
- * carrying its state, and the challenge to send the cloud in place of the verifier.
23
- */
24
- export declare function beginLinkState(account: string, returnTo: string): Promise<{
25
- callbackUrl: string;
26
- challenge: string;
27
- }>;
28
- /**
29
- * Removes the link a callback answers and returns its verifier, or null when
30
- * this browser did not begin a link for `account` with this state.
31
- */
32
- export declare function takeLinkVerifier(search: string, account: string | null): string | null;
33
- /** The claims of a link grant, or null when `grant` is not one. */
34
- export declare function linkGrantClaims(grant: string): LinkGrantClaims | null;
35
- /** The query with the link state removed, for rewriting the address bar. */
36
- export declare function withoutLinkState(search: string): string;
@@ -1,104 +0,0 @@
1
- /**
2
- * Redirects this browser began, so a callback can be matched to one. `localStorage`,
3
- * not `sessionStorage`: consent opens in a `noopener` tab with empty session storage.
4
- */
5
- import { hex } from "../crypto/internal.js";
6
- const KEY = "calimero.cloud.link.pending";
7
- const STATE_PARAM = "link_state";
8
- const LIFETIME_MS = 10 * 60 * 1000; // a grant lives 5 minutes, plus time to consent
9
- export const ACCOUNT_HEX = /^[0-9a-f]{64}$/;
10
- const MAX_PENDING = 20; // bounds storage when a page builds a consent URL on every render
11
- const VERIFIER_BYTES = 32; // the cloud refuses any other length
12
- export const VERIFIER_HEX = /^[0-9a-f]{64}$/;
13
- const LINK_GRANT_USE = "account-link-grant";
14
- function readPending(key, now) {
15
- try {
16
- const raw = localStorage.getItem(key);
17
- const pending = raw ? JSON.parse(raw) : {};
18
- return Object.fromEntries(Object.entries(pending).filter(([, p]) => p && typeof p.expiresAt === "number" && p.expiresAt > now));
19
- }
20
- catch {
21
- return {};
22
- }
23
- }
24
- function writePending(key, pending) {
25
- try {
26
- localStorage.setItem(key, JSON.stringify(pending));
27
- }
28
- catch {
29
- // Storage blocked: the redirect cannot be matched later and fails closed.
30
- }
31
- }
32
- /** Remember `payload` in the `key` store under a fresh random state, and return the state. */
33
- export function beginState(key, payload) {
34
- const now = Date.now();
35
- const pending = readPending(key, now);
36
- const state = hex(crypto.getRandomValues(new Uint8Array(16)));
37
- pending[state] = { ...payload, expiresAt: now + LIFETIME_MS };
38
- writePending(key, Object.fromEntries(Object.entries(pending).slice(-MAX_PENDING)));
39
- return state;
40
- }
41
- /** Removes and returns what `state` was begun with, or null when this browser did not begin it. */
42
- export function takeState(key, state) {
43
- const pending = readPending(key, Date.now());
44
- if (!state || !Object.prototype.hasOwnProperty.call(pending, state))
45
- return null;
46
- const entry = pending[state];
47
- delete pending[state];
48
- writePending(key, pending);
49
- return entry;
50
- }
51
- /** The challenge a verifier commits to: sha256 of its bytes, hex. */
52
- export async function linkChallenge(verifier) {
53
- const bytes = Uint8Array.from(verifier.match(/../g) ?? [], (b) => parseInt(b, 16));
54
- return hex(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)));
55
- }
56
- /**
57
- * Remember a link for `account` under a fresh verifier. Returns `returnTo`
58
- * carrying its state, and the challenge to send the cloud in place of the verifier.
59
- */
60
- export async function beginLinkState(account, returnTo) {
61
- if (!ACCOUNT_HEX.test(account)) {
62
- throw new Error("accountId must be 64 lowercase hex characters");
63
- }
64
- const url = new URL(returnTo);
65
- const verifier = hex(crypto.getRandomValues(new Uint8Array(VERIFIER_BYTES)));
66
- const challenge = await linkChallenge(verifier);
67
- url.searchParams.set(STATE_PARAM, beginState(KEY, { account, verifier }));
68
- return { callbackUrl: url.toString(), challenge };
69
- }
70
- /**
71
- * Removes the link a callback answers and returns its verifier, or null when
72
- * this browser did not begin a link for `account` with this state.
73
- */
74
- export function takeLinkVerifier(search, account) {
75
- const pending = takeState(KEY, new URLSearchParams(search).get(STATE_PARAM));
76
- if (!pending || pending.account !== account)
77
- return null;
78
- return typeof pending.verifier === "string" && VERIFIER_HEX.test(pending.verifier)
79
- ? pending.verifier
80
- : null;
81
- }
82
- /** The claims of a link grant, or null when `grant` is not one. */
83
- export function linkGrantClaims(grant) {
84
- try {
85
- const body = grant.split(".")[0].replace(/-/g, "+").replace(/_/g, "/");
86
- const claims = JSON.parse(new TextDecoder().decode(Uint8Array.from(atob(body), (c) => c.charCodeAt(0))));
87
- const { use, sub, acc, chal } = claims;
88
- if (use !== LINK_GRANT_USE)
89
- return null;
90
- if (typeof sub !== "string" || typeof acc !== "string" || typeof chal !== "string")
91
- return null;
92
- return { login: sub, account: acc, challenge: chal };
93
- }
94
- catch {
95
- return null;
96
- }
97
- }
98
- /** The query with the link state removed, for rewriting the address bar. */
99
- export function withoutLinkState(search) {
100
- const params = new URLSearchParams(search);
101
- params.delete(STATE_PARAM);
102
- const rest = params.toString();
103
- return rest ? `?${rest}` : "";
104
- }