First real UI for kez-chat. Served by the chat-server as static
files; uses the same HTTP API a native client would (dogfoods the
contract).
Stack: Svelte 5 + TypeScript + Vite + Tailwind 4 + @noble/curves +
@scure/base + canonicalize + idb-keyval + svelte-spa-router.
Bundle: 113 KB JS / 14 KB CSS (gzip: 42 KB / 4 KB).
Pages (all behind hash routing):
/ Landing — sign up or restore from seed
/create Account creation flow:
1. Pick handle, set passphrase
2. Show seed for paper backup, require ack
3. Confirm
4. POST /v1/register, save passphrase-encrypted seed
to IndexedDB
/restore Stub for restore-from-seed (v0.2: needs
GET /v1/by-primary endpoint on the server)
/unlock Enter passphrase to derive the AES-GCM key,
decrypt the seed, populate session state
/dashboard Show handle, primary, registered_at, sigchain URL
/claims List locally-cached claims (with publication status)
/claims/add Add-a-claim wizard:
1. Pick channel (github/dns/web/nostr/bluesky/ap)
2. Enter identifier
3. SignedClaimEnvelope built + signed in-browser
using Ed25519 + JCS, matching the spec exactly
4. Show channel-appropriate publish instructions +
copyable markdown or JSON artifact
5. User marks it published (purely a local note —
actual verification is the verifier's job)
Crypto / KEZ helpers (src/lib/kez.ts):
- generateIdentity / identityFromSeed (32-byte Ed25519)
- canonicalBytes (RFC 8785 JCS via the `canonicalize` package — same
one our Node port uses; produces byte-identical output to Rust)
- signClaim, signRegistration (build envelopes; sign with
ed25519-sha512-jcs; same alg / key / sig shape as kez-core)
- toPrettyJson, toMarkdown (the same wire encodings the CLI emits)
Key storage (src/lib/identity-store.ts):
- IndexedDB via idb-keyval
- Seed encrypted under user passphrase: PBKDF2-SHA256
(600,000 iterations, OWASP 2024 guidance) → AES-GCM-256
- Documented limitation: browsers don't have an OS-keychain
equivalent. Native clients (future CLI/Tauri) will use the OS
keychain for better protection.
Bundle includes:
- Workaround for TS 5.6+ Uint8Array<ArrayBufferLike> vs ArrayBuffer
strictness (small asBuffer() helper that copies into a plain
ArrayBuffer for WebCrypto + Response calls).
Dockerfile updated: now multi-stage with a Node `webbuild` stage
that runs `npm run build` before the Rust binary stage. SPA dist
is copied into the runtime image at /app/web; chat-server's
KEZ_CHAT_WEB_DIR points at it so the SPA is served at /.
What works against the LIVE deployment right now (https://kez.lat):
- Open https://kez.lat → SPA loads (113 KB JS, 14 KB CSS)
- Create account → key gen happens in browser, seed shown for
backup, encrypted under passphrase, POSTed to /v1/register
- Dashboard → shows registered handle + primary + sigchain URL
- Claims wizard → sign for any of the 6 channels, get publish
instructions + the right wire format to copy
- Lock / unlock — passphrase-derived AES-GCM, no roundtrips
What's still TODO (v0.2):
- Restore-from-seed: needs GET /v1/by-primary on the server so the
SPA can discover the handle from a seed
- Actual NATS chat: needs server's auth callout (currently 501) +
nats.ws client (browser side; package is in deps but not used yet)
- Sigchain integration: append `add` event when user publishes a
claim, upload to sig-server (needs sig.kez.lat tunnel)
- Verification: in-browser channel fetches (some channels are
CORS-friendly, others need a server-side proxy)
- Compact (kez:z1:) form: the spec uses zstd, browsers don't have
native zstd CompressionStream support yet. Workaround in code
uses deflate-raw with a `kez:zd1:` prefix to make it obvious the
output isn't spec-compliant; replace with @bokuweb/zstd-wasm or
similar when we need true compact form in the SPA.
53 lines
2.0 KiB
Docker
53 lines
2.0 KiB
Docker
# Multi-stage build for kez-chat-server + bundled Svelte SPA.
|
|
#
|
|
# Stage 1: build the Svelte SPA → web/dist/
|
|
# Stage 2: build the Rust binary against kez-core (path dep)
|
|
# Stage 3: minimal runtime image with binary + SPA
|
|
#
|
|
# Build context = repo root (so we can see kez-chat/web/ and rust/).
|
|
# docker-compose.yml sets `context: ../..` accordingly.
|
|
|
|
# ─── Stage 1: build the SPA ────────────────────────────────────────────────
|
|
FROM node:22-slim AS webbuild
|
|
WORKDIR /src/web
|
|
COPY kez-chat/web/package.json kez-chat/web/package-lock.json* ./
|
|
RUN npm install --no-audit --no-fund
|
|
COPY kez-chat/web/ ./
|
|
RUN npm run build
|
|
|
|
# ─── Stage 2: build the Rust binary ────────────────────────────────────────
|
|
FROM rust:1.86-slim AS build
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
pkg-config libssl-dev ca-certificates \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
WORKDIR /src
|
|
COPY rust/ /src/rust/
|
|
COPY kez-chat/ /src/kez-chat/
|
|
WORKDIR /src/kez-chat
|
|
RUN cargo build --release --bin kez-chat-server
|
|
|
|
# ─── Stage 3: runtime ──────────────────────────────────────────────────────
|
|
FROM debian:bookworm-slim
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
ca-certificates \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& useradd -r -u 10001 -m kez
|
|
|
|
# Rust binary
|
|
COPY --from=build /src/kez-chat/target/release/kez-chat-server /usr/local/bin/kez-chat-server
|
|
# SPA static files
|
|
COPY --from=webbuild /src/web/dist/ /app/web/
|
|
|
|
USER kez
|
|
WORKDIR /data
|
|
|
|
ENV KEZ_CHAT_BIND=0.0.0.0:6969 \
|
|
KEZ_CHAT_DB=/data/kez-chat.db \
|
|
KEZ_CHAT_SERVER=kez.lat \
|
|
KEZ_CHAT_SIG_SERVER_URL=http://sig-server:7878 \
|
|
KEZ_CHAT_WEB_DIR=/app/web \
|
|
RUST_LOG=info
|
|
|
|
EXPOSE 6969
|
|
ENTRYPOINT ["/usr/local/bin/kez-chat-server"]
|