mirror of
https://github.com/hanzoai/id.git
synced 2026-10-26 20:59:52 +00:00
Login/Signup are now the polished two-column split — auth card on the left, per-org marketing/branding panel on the right — and the Portal shows the apps launcher grid for signed-in users, both ported from the frozen legacy-nextjs design. Auth wiring is untouched: LoginForm from @hanzo/id-auth still drives the /v1/iam/login + responseType=code flow with a type=text username input. - apps/web/src/marketing.ts: per-org marketing copy + app links + billing URLs keyed by tenant.orgId (ported from legacy staticBranding.content + orgApps). Keeps the brand-neutral BrandContract visual-only. - apps/web/src/components/MarketingPanel.tsx: rotating-quote branding panel. - apps/web/src/components/BrandLogo.tsx: brand.logoUrl with self-hosted /brand/<pkg>/assets/logo/logo.svg fallback (fixes the broken-logo CDN 404). - Portal detects auth via /v1/iam/get-account OR the signed_in=1 marker the bare-login flow now returns (cross-proxy get-account does not echo the session). pkgs/auth response shaping only — wire contract unchanged. - app.css: split-view + marketing + apps-launcher styles (no Tailwind). - LLM.md: document the UI surfaces. Deployed registry.digitalocean.com/hanzo/id:0.1.10. Verified on hanzo.id and lux.id: split-view renders, apps launcher shows post-login, sign-in still works (authenticated, no Unable to sign in).
7.1 KiB
7.1 KiB
Hanzo ID
What this is
White-label login + identity verification portal. Vite SPA, served from the Hanzo K8s cluster, white-labels per hostname.
Replaces:
~/work/hanzo/hanzo.id-worker(Cloudflare Worker) — being decommissioned~/work/hanzo/id/legacy-nextjs/(Next.js 15) — frozen, kept for diff
Architecture
DNS → hanzo cluster ingress (129.212.164.5)
hanzo.id ──┐
lux.id ──┤ ─ TLS → ingress ─ K8s ─→ Service id ─→ Deployment id (2 replicas)
zoo.id ──┤ (Traefik) ghcr.io/hanzoai/id:vX.Y.Z
pars.id ──┘ │
│ on boot:
│ resolveTenant(hostname)
│ loadBrand(tenant.brandPackage)
▼
@hanzo|luxfi|zooai|parsdao /brand
│
▼
createAuthClient(tenant)
│
▼
https://iam.hanzo.ai (Casdoor fork, Go)
│
▼
iam-* postgres in hanzo namespace
Workspace
apps/
web/ @hanzo/id-web — Vite + React 19 + @hanzo/gui SPA
pkgs/
shared/ @hanzo/id-shared — TenantConfig, resolveTenant, loadBrand
auth/ @hanzo/id-auth — composable login/signup/OTP forms +
AuthClient (wraps @hanzo/iam REST)
idv/ @hanzo/id-idv — pluggable identity verification
(Persona, Onfido, Veriff, stub)
legacy-nextjs/ Frozen predecessor. Delete after v0.1.0 ships.
Why this layout (and not just Next.js)
- Vite > Next.js for an SPA. No SSR needed; auth is post-load only. ~200kb gzip vs ~600kb. Build is 3s vs 90s.
- @hanzo/gui v7 is the canonical UI. Same shell as every other Hanzo admin surface. No bespoke components.
- Per-org brand packages.
@hanzo/brand,@luxfi/brand,@zooai/brand,@parsdao/brandalready shipbrand.jsonfiles; we fetch them at runtime. Adding a brand =pnpm add @newco/brand- one line in
pkgs/shared/src/tenant.ts(or a runtime catalog entry — no rebuild).
- one line in
- Provider-pluggable IDV. Same surface for Persona, Onfido, Veriff, custom backends. No vendor lock-in at the portal layer.
- Mirrors downstream tenant id-app forks. Same
apps/+pkgs/pattern, same@hanzo/guishell, same per-tenant brand resolution.
UI surfaces
- Login / Signup (
apps/web/src/pages/Login.tsx,Signup.tsx): split-view — auth card (left) + per-org marketing/branding panel (right,.hanzo-id-split-brand, hidden < 1024px). Restored fromlegacy-nextjsdesign. The auth itself is the untouched<LoginForm>/<SignupForm>from@hanzo/id-auth; the page only wraps it in layout. Do not move auth logic into the page. - Portal (
apps/web/src/pages/Portal.tsx): after a bare sign-in the user lands on/?signed_in=1and sees the apps launcher (" apps", grid ofappsFor(orgId)cards) + account/billing nav. Logged-out visitors get the brand hero. Auth state =/v1/iam/get-account(cookie session) OR the?signed_in=1marker (the cross-proxyget-accountdoes not echo the bare-login session, so the marker is the authoritative "just authenticated" signal). - Marketing copy + app links + billing URLs are per-org in
apps/web/src/marketing.ts, keyed bytenant.orgId(hanzo/lux/zoo/pars), ported from the legacystaticBranding.content+orgApps. The brand-neutralBrandContractstays visual-only; this file holds the org-specific copy. - Logo:
apps/web/src/components/BrandLogo.tsxrendersbrand.logoUrl(CDN) and falls back to the in-image/brand/<pkg>/assets/logo/logo.svgon error (the jsdelivr CDN path 404s for some brands — the self-hosted asset always works). - Styling is hand-written CSS in
apps/web/src/app.css(.hanzo-id-*). No Tailwind in this SPA — match existing class conventions.
Local dev
pnpm install
pnpm dev # http://localhost:5173 → defaults to hanzo brand
For multi-tenant preview:
echo "127.0.0.1 lux.id zoo.id pars.id" | sudo tee -a /etc/hosts
then visit http://lux.id:5173 etc.
Build + deploy
pnpm build
# Deploy is managed in hanzoai/universe (infra/k8s/id/): operator/kustomize
# applies it; CI image bump flows via universe. App repo ships the
# brand-neutral image + Dockerfile only — no deploy/brand config here.
Adding a new brand
pnpm add -F @hanzo/id-web @newco/brand- Add
TenantConfigfor the hostname inpkgs/shared/src/tenant.ts(or put it inIAM_TENANT_CONFIG_JSONat runtime — no rebuild). - Add the hostname to
BRAND_PACKAGESinapps/web/vite.config.ts. - Add host + TLS secret to
apps/web/k8s/ingress.yaml. - DNS → cluster ingress IP.
Plugging an IDV provider
import { registerProvider, createPersonaProvider } from '@hanzo/id-idv'
registerProvider(createPersonaProvider({ templateId, apiKey, environment: 'production' }))
Pre-built providers
| id | source | docs |
|---|---|---|
stub |
@hanzo/id-idv/providers/stub |
in-memory, dev-only |
persona |
@hanzo/id-idv/providers/persona |
https://docs.withpersona.com |
onfido |
@hanzo/id-idv/providers/onfido |
https://documentation.onfido.com |
veriff |
@hanzo/id-idv/providers/veriff |
https://developers.veriff.com |
Custom providers: implement the IDVProvider interface in
pkgs/idv/src/provider.ts and register it.
Cutover from the CF Worker
- Build + push image (PR scaffolds; image bump comes after CI green).
kubectl apply -k apps/web/k8s— Deployment + Service + Ingress live.- cert-manager issues TLS for all 4 hosts.
- Remove the Cloudflare Worker routes for the 4 identity hosts.
- Repoint CF A records:
hanzo.id,lux.id,zoo.id,pars.id→129.212.164.5(hanzo cluster ingress LB), CF-proxied. - Verify each host loads its own brand.
- Archive
hanzo.id-workerrepo.
Backend
The Go IAM backend lives at ~/work/hanzo/iam (Casdoor fork, module
github.com/hanzoai/iam, image ghcr.io/hanzoai/iam). This portal talks
to it via the routes in pkgs/auth/src/client.ts:
/v1/iam/login/v1/iam/signup/v1/iam/send-verification-code/oauth/authorize/oauth/token/oauth/logout
All hostnames talk to the same IAM backend — the org is carried in the
request body (organization: <orgId>), and the IAM backend tenant-scopes
on that.