@muhkoo/connect 0.7.0-alpha.3 → 0.7.0-alpha.5
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 +11 -13
- package/dist/browser/index.js +51 -10
- package/dist/browser/index.js.map +1 -1
- package/dist/connect.d.ts +14 -0
- package/dist/server/index.js +51 -10
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -142,9 +142,10 @@ const client = new Client({
|
|
|
142
142
|
const user = await client.auth.zk.login("alice", "correct horse battery staple");
|
|
143
143
|
// └ { username, commitment }
|
|
144
144
|
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
// KV — per-user persistent key/value (collection + id); client-side encrypted,
|
|
146
|
+
// the server stores only ciphertext.
|
|
147
|
+
await client.kv.set("todos", "t1", { title: "Buy groceries", completed: false });
|
|
148
|
+
const todo = await client.kv.get("todos", "t1");
|
|
148
149
|
const ids = await client.storage.list("todos");
|
|
149
150
|
await client.storage.delete("todos", "t1");
|
|
150
151
|
|
|
@@ -169,9 +170,12 @@ online).
|
|
|
169
170
|
message once with a shared group key so the server can persist + replay it.
|
|
170
171
|
`createChannel(name)` mints + registers a channel (you become its first
|
|
171
172
|
key-holder); `joinChannel(name)` resolves an existing one and is admitted by the
|
|
172
|
-
app's **keeper** — a
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
app's **keeper** — a server-side process (the app's Durable Object) that holds
|
|
174
|
+
the channel's group key and re-issues it to newcomers — so a channel is joinable
|
|
175
|
+
even when no human member is online. The message **relay** stays blind (it only
|
|
176
|
+
ever sees opaque, ECIES-wrapped key blobs); the **keeper** does hold group keys,
|
|
177
|
+
so channels are *relay-blind*, not fully server-blind. (Enabling a server-side
|
|
178
|
+
agent on a channel likewise gives that agent the group key.) The space handle was
|
|
175
179
|
once `Room`; that name is still exported as an alias, and `client.message.room()`
|
|
176
180
|
returns the same handle.
|
|
177
181
|
|
|
@@ -459,12 +463,6 @@ yarn lint # eslint ./src
|
|
|
459
463
|
(true for all of the above; on CF Workers, pre-compile via wrangler's
|
|
460
464
|
`.wasm` import if you want to skip startup cost).
|
|
461
465
|
|
|
462
|
-
## Related
|
|
463
|
-
|
|
464
|
-
- [`muhkoo/accelerator`](https://github.com/muhkoo/accelerator) — the CF
|
|
465
|
-
Workers + DOs backend this SDK targets, including the personal-space DO,
|
|
466
|
-
message-bus DO, and the bn128.wasm-driven `verifyZkAuthProof`.
|
|
467
|
-
|
|
468
466
|
## License
|
|
469
467
|
|
|
470
|
-
|
|
468
|
+
MIT — see `LICENSE`.
|
package/dist/browser/index.js
CHANGED
|
@@ -1807,16 +1807,15 @@ class DoubleRatchetManager {
|
|
|
1807
1807
|
}
|
|
1808
1808
|
async registerZK(clientId, secret, salt, ecdsaPub) {
|
|
1809
1809
|
await this._authenticator.initializeZK();
|
|
1810
|
-
console.log(`Registering ZK for ${clientId}...`);
|
|
1811
|
-
console.log('ECDSA Public Key:', ecdsaPub);
|
|
1812
1810
|
const ecdsaJwk = await crypto.subtle.exportKey('jwk', ecdsaPub);
|
|
1813
1811
|
const ecdsaHex = toHex(fromBase64Url(ecdsaJwk.x)).slice(0, 64);
|
|
1814
1812
|
const ecdsaPubField = new Field(BigInt('0x' + ecdsaHex));
|
|
1815
1813
|
const ecdsaPubHash = await Poseidon.hash([ecdsaPubField]);
|
|
1816
1814
|
const commitment = await Poseidon.hash([secret, salt, ecdsaPubHash]);
|
|
1817
|
-
console.log(`Commitment for ${clientId}:`, { commitment, salt });
|
|
1818
1815
|
this.registeredUsers.set(clientId, { commitment, salt });
|
|
1819
|
-
|
|
1816
|
+
// NB: never log `secret`, `salt`, or `commitment` — `salt` is a private
|
|
1817
|
+
// witness for the ZK circuit and `commitment` is the user's stable id.
|
|
1818
|
+
appLogger.debug(`Registered ZK identity for ${clientId}`);
|
|
1820
1819
|
}
|
|
1821
1820
|
async performHandshake(senderId, recipientId, zkProof, publicInput, clientPublicKey, // ECDH pub for session
|
|
1822
1821
|
clientAuthPublicKey, // ECDSA pub for auth
|
|
@@ -4892,7 +4891,7 @@ function base64UrlDecode(s) {
|
|
|
4892
4891
|
* expect (and isn't serving a stale cached bundle). Keep this in sync with
|
|
4893
4892
|
* `package.json` `version`.
|
|
4894
4893
|
*/
|
|
4895
|
-
const VERSION = "0.7.0-alpha.
|
|
4894
|
+
const VERSION = "0.7.0-alpha.4";
|
|
4896
4895
|
|
|
4897
4896
|
/**
|
|
4898
4897
|
* `HttpClient` — the one place the unified {@link Client} talks to the
|
|
@@ -4928,6 +4927,8 @@ class HttpError extends Error {
|
|
|
4928
4927
|
}
|
|
4929
4928
|
class HttpClient {
|
|
4930
4929
|
baseUrl;
|
|
4930
|
+
/** Origin of {@link baseUrl} — credentials are only attached same-origin. */
|
|
4931
|
+
baseOrigin;
|
|
4931
4932
|
apiKey;
|
|
4932
4933
|
getSessionToken;
|
|
4933
4934
|
onUnauthorized;
|
|
@@ -4936,6 +4937,12 @@ class HttpClient {
|
|
|
4936
4937
|
if (!opts?.baseUrl)
|
|
4937
4938
|
throw new Error("HttpClient: `baseUrl` is required");
|
|
4938
4939
|
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
4940
|
+
try {
|
|
4941
|
+
this.baseOrigin = new URL(this.baseUrl).origin;
|
|
4942
|
+
}
|
|
4943
|
+
catch {
|
|
4944
|
+
this.baseOrigin = "";
|
|
4945
|
+
}
|
|
4939
4946
|
this.apiKey = opts.apiKey ?? null;
|
|
4940
4947
|
this.getSessionToken = opts.getSessionToken ?? (() => null);
|
|
4941
4948
|
this.onUnauthorized = opts.onUnauthorized ?? null;
|
|
@@ -4952,13 +4959,34 @@ class HttpClient {
|
|
|
4952
4959
|
*/
|
|
4953
4960
|
fetch = (input, init) => {
|
|
4954
4961
|
const headers = new Headers(init?.headers);
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4962
|
+
// Only attach credentials when the request stays on the accelerator's
|
|
4963
|
+
// origin. An absolute URL pointing elsewhere (e.g. a caller-supplied or
|
|
4964
|
+
// server-reflected path that escapes `baseUrl`) must NOT receive the app
|
|
4965
|
+
// key or session token, or it becomes a token-exfiltration vector.
|
|
4966
|
+
if (this.isSameOrigin(input)) {
|
|
4967
|
+
if (this.apiKey)
|
|
4968
|
+
headers.set("X-Muhkoo-Key", this.apiKey);
|
|
4969
|
+
const token = this.getSessionToken();
|
|
4970
|
+
if (token)
|
|
4971
|
+
headers.set("X-Muhkoo-Session", token);
|
|
4972
|
+
}
|
|
4960
4973
|
return this.fetchFn(input, { ...init, headers });
|
|
4961
4974
|
};
|
|
4975
|
+
/** True when `input` resolves to {@link baseOrigin} (relative URLs always do). */
|
|
4976
|
+
isSameOrigin(input) {
|
|
4977
|
+
try {
|
|
4978
|
+
const urlStr = typeof input === "string" ? input
|
|
4979
|
+
: input instanceof URL ? input.href
|
|
4980
|
+
: input.url;
|
|
4981
|
+
// Relative URLs are resolved against baseUrl downstream → same origin.
|
|
4982
|
+
if (!/^https?:\/\//i.test(urlStr))
|
|
4983
|
+
return true;
|
|
4984
|
+
return new URL(urlStr).origin === this.baseOrigin;
|
|
4985
|
+
}
|
|
4986
|
+
catch {
|
|
4987
|
+
return true;
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4962
4990
|
// -------------------------------------------------------------------------
|
|
4963
4991
|
// JSON convenience helpers (path is relative to baseUrl)
|
|
4964
4992
|
// -------------------------------------------------------------------------
|
|
@@ -5452,6 +5480,19 @@ class HostedAuth {
|
|
|
5452
5480
|
this.location().assign(url);
|
|
5453
5481
|
return url;
|
|
5454
5482
|
}
|
|
5483
|
+
/**
|
|
5484
|
+
* (App side) Send the user to centralized account & security management on
|
|
5485
|
+
* the hosted page (auth.muhkoo.dev/security) — manage passkeys, recovery
|
|
5486
|
+
* phrase, email, Google, change password, and remove login methods, all in
|
|
5487
|
+
* one place. `returnUri` (default: the current URL) gives the hosted page a
|
|
5488
|
+
* "Back to app" target. Redirects the browser; resolves to the URL.
|
|
5489
|
+
*/
|
|
5490
|
+
async manageAccount(opts = {}) {
|
|
5491
|
+
const ret = opts.returnUri ?? this.location().href;
|
|
5492
|
+
const url = `${this.deps.authBaseUrl}/security?` + new URLSearchParams({ return: ret }).toString();
|
|
5493
|
+
this.location().assign(url);
|
|
5494
|
+
return url;
|
|
5495
|
+
}
|
|
5455
5496
|
/** Whether the current URL looks like a hosted-auth callback (has code + state). */
|
|
5456
5497
|
isCallback() {
|
|
5457
5498
|
try {
|