feat: initial scaffold of uniweb — unibus web frontend (SPA + gateway)
Extracted from unibus v0.13.0: the chat SPA (web/, React+Mantine, per-user BIP39 wallet) and the web gateway (cmd/webgw, REST+SSE) that acts as a bus peer for the browser. Consumes unibus as a Go module via replace => ../unibus, keeping its own replace fn-registry for the cybersecurity primitives. go build/vet/test and pnpm build green in the new location.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
// High-level wallet account operations shared by the join, recover and login
|
||||
// flows. These compose the low-level primitives (derive / crypto / store) with
|
||||
// the gateway API so the page components stay thin.
|
||||
|
||||
import { api } from "../api";
|
||||
import type { MeInfo, User } from "../types";
|
||||
import { decryptJSON, encryptJSON } from "./crypto";
|
||||
import type { WalletIdentity } from "./derive";
|
||||
import { getIdentity, putIdentity, type StoredIdentity } from "./store";
|
||||
|
||||
function toUser(me: MeInfo): User {
|
||||
return { id: me.endpoint, handle: me.handle || me.endpoint.slice(0, 8) };
|
||||
}
|
||||
|
||||
// saveAndOpen encrypts the identity under `password`, stores it on this device,
|
||||
// and opens a gateway session as that user. Used by join (new identity) and
|
||||
// recover (re-derived identity): both end with a locally-encrypted key plus a
|
||||
// live per-user session. The mnemonic/seed is NOT touched here — only the derived
|
||||
// keypair is persisted (encrypted).
|
||||
export async function saveAndOpen(
|
||||
identity: WalletIdentity,
|
||||
handle: string,
|
||||
password: string,
|
||||
): Promise<User> {
|
||||
const enc = await encryptJSON(identity, password);
|
||||
await putIdentity({
|
||||
handle,
|
||||
signPub: identity.signPub,
|
||||
kexPub: identity.kexPub,
|
||||
enc,
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
const me = await api.session(identity, handle);
|
||||
return toUser(me);
|
||||
}
|
||||
|
||||
// unlockAndOpen reads this device's stored identity, decrypts the private key with
|
||||
// `password`, and opens a gateway session. Throws WrongPasswordError on a bad
|
||||
// password (GCM auth failure) and NoLocalIdentityError if the device has none.
|
||||
export async function unlockAndOpen(password: string): Promise<User> {
|
||||
const stored = await getIdentity();
|
||||
if (!stored) throw new NoLocalIdentityError();
|
||||
const identity = await decryptJSON<WalletIdentity>(stored.enc, password);
|
||||
const me = await api.session(identity, stored.handle);
|
||||
return toUser(me);
|
||||
}
|
||||
|
||||
// localIdentity returns the device's stored identity record (or null), for the
|
||||
// router to decide between the password-unlock screen and the welcome screen, and
|
||||
// to greet the user by handle before unlocking.
|
||||
export async function localIdentity(): Promise<StoredIdentity | null> {
|
||||
return getIdentity();
|
||||
}
|
||||
|
||||
export class NoLocalIdentityError extends Error {
|
||||
constructor() {
|
||||
super("no local identity on this device");
|
||||
this.name = "NoLocalIdentityError";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user