Miakapp Server is the in-memory Go relay for the Miakapp 3.5 protocol. It connects home coordinators, authenticated users, and future CLI sessions without persisting Firebase credentials, Home Keys, push credentials, or product business logic.
The standalone binary validates short-lived coordinator, CLI, and browser-user access tokens against the configured control-plane JWKS. Firebase Auth and App Check credentials remain on the browser-to-control-plane HTTPS boundary and are never relay credentials. The relay holds no verification secret and never calls the platform with authenticated authority. It remains an implementation preview until the staging relay integration gate has passed; do not deploy it as a production replacement yet.
The wire contract is owned by Miakapp-V3
RFC 0001,
and this module pins the canonical Go codec at that immutable commit. Relay
credential profiles are owned by
RFC 0004,
with the independent Go verifier pinned at that second commit. This repository
does not copy either contract.
The implemented vertical slice includes:
/wswith themiakappsubprotocol, exact browser-origin allowlisting, binary-only messages, bounded HELLO, and RFC 6455 Ping/Pong liveness;- injected authentication and immutable principal binding, including reauthentication on the existing session;
- per-home process epochs, coordinator generations, presence, and disconnect grace;
- atomic five-slice coordinator declarations with locked final revalidation, ownership collision rollback, and no partially visible authorization state;
- filtered state dictionaries and snapshots, revisioned patches, atomic state mutations, and explicit resynchronization;
- event declarations, ACLs, subscriptions, routing, and relay-created principal metadata;
- call routing with connection-local ID rewriting, acceptance, stream credit,
cancellation, deadlines, terminal results, and conservative
OUTCOME_UNKNOWNhandling; - byte-bounded outbound queues and stable protocol errors; and
- finite process admission for active connections, attempts and tracked source addresses, total Homes, and aggregate queued payload bytes.
The legacy Node protocol, Firestore listeners, FCM sender, and custom miakode
encoding are intentionally absent. Miakapp 3.5 has no permanent v3 compatibility
path in the relay.
Requirements:
- Go 1.26.6
- Docker for image checks
- Bun 1.2.23, Node.js 22.22 or newer, and the exact Playwright Chromium runtime from MiakAPI for the optional SDK/browser integration check
- Java 21 and OpenSSL for the optional full control-plane integration check
Run the Go checks:
go test -race ./...
go vet ./...Run the real MiakAPI coordinator and browser integration after installing its dependencies, browser runtime, and building a local MiakAPI checkout:
cd /absolute/path/to/MiakAPI
bun install --frozen-lockfile
bunx playwright install chromium
bun run build
cd /absolute/path/to/Miakapp-Server
./scripts/check-miakapi-integration.sh /absolute/path/to/MiakAPIThis smaller gate serves the MiakAPI browser fixture from loopback HTTPS, allows exactly that page Origin, and executes it in headless Chromium against the real relay. It proves enrollment, initial state, one patch, one call/result and scheduled reauthentication on one WebSocket. A second call succeeds after the original four-second lease expires, proving the correlated renewal completed without a reconnect. The fixture uses only synthetic tokens and emits a closed semantic JSON result; browser traces and WebSocket frame inspection stay disabled. It does not exercise the control-plane credential exchange or memory isolation from a malicious relay.
Run the production authentication adapter against the canonical control-plane vectors:
./scripts/check-control-plane-integration.sh /absolute/path/to/Miakapp-V3Run the synthetic Home Key and browser source credentials through the real emulator control plane, MiakAPI providers, scheduled SDK reauthentication, and production relay verifier after building both external checkouts:
./scripts/check-platform-integration.sh /absolute/path/to/Miakapp-V3 /absolute/path/to/MiakAPIThis gate uses only loopback HTTPS, the demo-miakapp-v4 Auth and Firestore
emulators, ephemeral certificates, Home Key and control-secret files, and two
independent instances of the production verifier cache. It creates an unenrolled
synthetic Auth user and a signed synthetic App Check token in one private
temporary file. Because the Auth emulator emits deliberately unsigned JWTs, the
browser fixture applies a local shape adapter before the strict public provider
and restores the exact emulator token at the HTTPS boundary; production code is
not relaxed. The real control plane then issues the audience-bound user token.
A fake-clock probe cache proves one shared refresh for 32 concurrent future-key
tokens, the ten-second random-kid abuse bound, conditional expiry revalidation,
fail-closed outage handling, and bounded recovery. The real-time relay cache
survives signing-key rotation while a coordinator and a real Chromium browser
each complete same-socket REAUTH. The fixture then changes the authoritative
Home relay, and the browser carries that single new credential to a second real
relay without another exchange or overlapping sockets. It recovers state and
executes calls on both relays. Integration-only constructors and authenticated
loopback controls are excluded from normal relay builds; transient frame checks
emit only bounded counters and a source-credential-presence boolean, never
tokens, claims, or frame contents. This local gate does not claim live Cloud KMS
rotation or public-ingress behavior.
CI checks out both repositories at immutable commits so relay changes cannot silently drift against moving protocol, authentication, or SDK dependencies.
Build the binary and image:
go build ./cmd/miakapp-server
docker build -t miakapp-server:development .The executable reads:
| Variable | Default | Purpose |
|---|---|---|
MIAKAPP_LISTEN_ADDRESS |
:3000 |
HTTP listen address |
MIAKAPP_ALLOWED_ORIGINS |
empty | Comma-separated exact http(s)://host[:port] browser origins |
MIAKAPP_CONTROL_PLANE_ISSUER |
required | Exact HTTPS control-plane issuer origin |
MIAKAPP_CONTROL_PLANE_JWKS_URL |
required | Same-origin /.well-known/jwks.json endpoint |
MIAKAPP_RELAY_AUDIENCE |
required | Exact public wss://.../ws URL for this relay |
MIAKAPP_HANDSHAKE_TIMEOUT |
5s |
Maximum wait for HELLO and authentication |
MIAKAPP_WRITE_TIMEOUT |
5s |
One WebSocket write deadline |
MIAKAPP_PING_INTERVAL |
30s |
RFC 6455 liveness interval |
MIAKAPP_PONG_TIMEOUT |
10s |
Pong deadline |
MIAKAPP_DECLARATION_TIMEOUT |
30s |
Incomplete declaration transaction lifetime |
MIAKAPP_DISCONNECT_GRACE |
30s |
Retained coordinator generation lifetime |
MIAKAPP_SHUTDOWN_TIMEOUT |
10s |
HTTP shutdown deadline |
MIAKAPP_MAX_QUEUED_BYTES |
1048576 |
Per-connection outbound byte ceiling |
MIAKAPP_MAX_CONNECTIONS |
256 |
Process-wide active WebSocket ceiling |
MIAKAPP_MAX_CONNECTIONS_PER_IP |
32 |
Active WebSocket ceiling for one immediate TCP peer |
MIAKAPP_CONNECTION_ATTEMPTS_PER_MINUTE |
120 |
Fixed-window attempt ceiling for one immediate TCP peer |
MIAKAPP_MAX_TRACKED_IPS |
4096 |
Process-wide ceiling for in-memory source admission buckets |
MIAKAPP_MAX_HOMES |
1024 |
Process-wide live and grace-retained Home ceiling |
MIAKAPP_MAX_AGGREGATE_QUEUED_BYTES |
67108864 |
Shared outbound queue byte ceiling across all connections |
An absent HTTP Origin is accepted for non-browser clients. A supplied browser
origin must match one configured value exactly; wildcards, substring matching,
paths, queries, and fragments are rejected.
internal/auth.Verifier is the sole credential boundary. It returns a bounded
identity lease; the relay independently binds that lease to the requested role,
home, coordinator name, principal ID, Home Key client ID, and expiry.
Reauthentication cannot change the established principal or switch Home Keys.
Coordinator, CLI, and browser-user tokens use distinct canonical Ed25519
profiles from RFC 0004. The browser profile contains exactly one relay audience,
Home ID, Firebase UID, miakapp_role=user, scope=relay:user, a maximum
five-minute lease, and an optional verified email; it contains no Home Key client
or coordinator claim. HELLO may only bind the already verified user to the same
Home. Firebase ID and App Check tokens are rejected as relay credentials.
The relay pins one issuer, one audience, and one same-origin JWKS URL. The JWKS client accepts no redirects, bounds responses to 64 KiB and 16 exact Ed25519 keys, requires the specified ETag and 60-second cache policy, coalesces concurrent refreshes, and permits at most one unknown-key refresh per ten seconds. Expired caches fail closed if they cannot be refreshed.
The selected relay still observes plaintext Home traffic and must therefore be
an official instance, self-hosted by the user, or operated by somebody the user
explicitly trusts. Self-hosting requires configuring the relay's exact public
WSS URL in the authoritative Home record and as MIAKAPP_RELAY_AUDIENCE; a token
issued for another relay cannot be replayed here. The relay needs only public
control-plane configuration and no Firebase or Miakapp credential.
This credential transition is intentionally fail-closed rather than dual-stack: an old Firebase-token relay rejects the new user profile, and this relay rejects Firebase source tokens. Roll out compatible control-plane, browser SDK, and relay revisions together, retain the previous complete set for rollback, and do not route browser traffic to a partially upgraded relay.
Process admission is applied before WebSocket upgrade. It keys active and
fixed-minute attempt limits only by RemoteAddr, the immediate TCP peer, and
never trusts X-Forwarded-For or another caller-controlled header. A shared
reverse proxy can therefore make this deliberately coarser than an end-user IP;
deployments that require client-level fairness must enforce it at a trusted edge.
The independent process-wide connection ceiling still bounds work in either
case. Source buckets, live and grace-retained Homes, per-connection queues and
aggregate queued bytes all have finite configurable ceilings. Complete frames,
subscriptions, calls, declared home dictionaries, presence and coordinator
counts retain their protocol bounds.
The relay contains no platform secret and makes no authenticated outbound call. Its deployment ingress terminates WSS before the Go process handles plaintext home data, so a home trusts its selected relay operator for confidentiality and correct routing. This is not end-to-end encryption.
All home state is in memory. A relay restart creates new home epochs and requires coordinators to declare complete slices again. Protocol 1.0 has no session replay or durable event delivery.