Files
zeekayandHanzo Dev 9b7352ae4a aml: auth is @hanzo/iam, and the console is configured by nothing
Two changes that are really one: stop owning things that are already owned.

AUTH. Deleted ui/src/auth.ts — a local PKCE implementation with its own token
storage, its own refresh, its own state handling and its own idea of what
signing out means. @hanzo/iam/browser is that, for every Hanzo app:
configureIam + startLogin + handleCallback + getSession + getUser + logout.
There is now no auth code in this repo to review, drift, or get wrong, and when
IAM's end-session behaviour improves every app gets it without a line changing
here. Verified against production IAM: hanzo.id reflects https://aml.hanzo.ai
on discovery, token and userinfo, so the flow works from the console's origin.

CONFIGURATION. Dropped config.json and the SPA_* env with it. The console
derives its API from the host it is served on — aml.hanzo.ai can only ever talk
to api.hanzo.ai — and reads its brand, issuer and IAM clientId from that API's
/v1/aml/config, which now carries client_id because that route already answers
'which identity does this surface have'. So there is no setting that points
authenticated requests somewhere, which is the setting worth attacking; the
same image is every brand's; and nothing writes the served root, which is what
lets the container run read-only and unprivileged.

The derivation is deliberate about what it refuses: an address literal has
three labels and no parent domain, so 127.0.0.1 would have become api.0.0.1.
Every host shape is asserted — brand hosts, a bare domain, localhost, IPv4,
IPv6.

Also here, from the same review: one replay per tenant on /v1/aml/rules/test,
because an empty sample opens every retained record and a handful at once
occupies the engine that still has to answer ingest; and the CI smoke now
asserts the invariants that replaced the old ones — no config.json is served,
and the bundle names no clientId.

CSP unchanged and re-proven: 7 routes, zero violations, zero console errors,
still no 'unsafe-inline'. Go build/vet/gofmt clean, 18 packages green, api
race-clean.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-30 19:18:57 -07:00

29 lines
1.3 KiB
Docker

# The AML console: a compiled bundle, served by the house static server.
#
# It is its own image because it is its own deploy unit. The API is reached at
# api.<brand>/v1/aml and this bundle is served on its own host — one door for
# data, one for the console, and neither inside the other. Building it into the
# daemon would put them back in one pod and one blast radius.
#
# npm with the committed lockfile: `npm ci` fails on lockfile drift rather than
# resolving fresh, and needs nothing in the image but the node base.
FROM node:22-alpine AS build
WORKDIR /ui
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# ghcr.io/hanzoai/static serves it. -spa sends unknown paths to index.html, which
# is what makes /cases/… and /callback resolve on a hard refresh; the security
# headers and the CSP come from the server.
#
# Nothing is templated into the bundle at startup and no SPA_* env is set, so the
# served root is never written and the container runs read-only and unprivileged.
# The console derives its API from the host it is served on and reads its brand,
# issuer and clientId from that API — so this image is every brand's, unchanged.
FROM ghcr.io/hanzoai/static:v0.5.1
COPY --from=build /ui/dist /public
EXPOSE 3000
ENTRYPOINT ["/static", "-port", "3000", "-root", "/public", "-spa"]