Files
id/legacy-nextjs/README.md
zeekayandGitHub a24ed753a4 rewrite: Vite + @hanzo/gui monorepo (drops CF Worker + Next.js) (#2)
Replaces the Cloudflare Worker (hanzo.id-worker) and the Next.js portal
with a pnpm monorepo following the a-monorepo/id pattern.

Layout:
  apps/web/        Vite + React 19 SPA, embeds @hanzo/gui shell
    k8s/           Deployment(2) + Service + Ingress (4 hosts, 4 TLS)
  pkgs/shared/     @hanzo/id-shared — TenantConfig, resolveTenant,
                                       loadBrand (browser + node)
  pkgs/auth/       @hanzo/id-auth   — AuthClient (wraps @hanzo/iam REST)
                                       + LoginForm/SignupForm/ForgotForm/OTPForm
  pkgs/idv/        @hanzo/id-idv    — pluggable IDV (stub, persona,
                                       onfido, veriff) behind one
                                       IDVProvider interface
  legacy-nextjs/   Frozen — Next.js predecessor. Delete after v0.1.0 ships.
  Dockerfile       Two-stage: pnpm build → hanzoai/static:0.4.1 serves /spa
  README.md
  LLM.md           Architecture, dev, deploy, cutover plan

Tenant resolution: hostname → TenantConfig (orgId, iamUrl, clientId,
appName, publicOrigin, brandPackage). Built-in defaults for
hanzo.id/lux.id/zoo.id/pars.id; runtime override via
IAM_TENANT_CONFIG_JSON env (served as /config.json at pod startup).

Brand resolution: each per-org brand pkg (@hanzo/brand, @luxfi/brand,
@zooai/brand, @parsdao/brand) ships brand.json. The Vite plugin
brandJsonPlugin emits /brand/<pkg>/brand.json verbatim; the browser
fetches the right one based on the resolved tenant. No bundle bloat.

IDV: stub (default for dev), persona, onfido, veriff. Each adapter
implements `IDVProvider` from pkgs/idv/src/provider.ts. Swap providers
with one registration call at boot — portal code unchanged.

Cutover (separate ops PR — not in this commit):
  1. Tag + push image ghcr.io/hanzoai/id:0.1.0
  2. Apply k8s manifests, cert-manager issues TLS
  3. Remove CF Worker routes for hanzo.id/lux.id/zoo.id/pars.id
  4. CF A records → 129.212.164.5 (hanzo ingress LB)
  5. Archive hanzo.id-worker repo
2026-05-24 06:00:20 -07:00

4.0 KiB

Hanzo ID - Hosted Login Pages

Configurable, white-label login pages for Hanzo IAM. Each organization can customize their login experience based on their domain (CNAME).

Architecture

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  hanzo.id   │     │   pars.id   │     │   lux.id    │
│  (CNAME)    │     │   (CNAME)   │     │   (CNAME)   │
└──────┬──────┘     └──────┬──────┘     └──────┬──────┘
       │                   │                   │
       └───────────────────┴───────────────────┘
                           │
                    ┌──────▼──────┐
                    │   Hanzo ID   │  ← This repo (frontend)
                    │   (Next.js)  │
                    └──────┬──────┘
                           │
                    ┌──────▼──────┐
                    │  Hanzo IAM  │  ← Backend auth services
                    │   (Go API)  │
                    └─────────────┘

Features

  • Domain-based branding: Logo, colors, content based on CNAME
  • Configurable auth methods: Password, code, WebAuthn, Face ID
  • Social providers: Google, GitHub, and more
  • Customizable content: Quotes, testimonials, feature highlights
  • Dark mode by default: Clean, modern design
  • Easy to fork: Simple structure for white-labeling

Configuration

Branding can be configured in two ways:

1. Static Configuration (for known domains)

Edit lib/branding.ts to add your domain:

export const staticBranding: Record<string, Partial<BrandingConfig>> = {
  'your-domain.com': {
    orgId: 'your-org',
    orgName: 'Your Organization',
    logo: '/logos/your-logo.svg',
    colors: {
      primary: '#3b82f6',
      primaryText: '#ffffff',
      background: '#000000',
      surface: '#0a0a0a',
      text: '#ffffff',
      textMuted: '#a1a1aa',
      border: '#27272a',
      error: '#dc2626',
    },
    content: {
      title: 'Welcome to Your App',
      subtitle: 'Sign in to continue',
    },
  },
}

2. Dynamic Configuration (from IAM backend)

The login page fetches branding from IAM API:

GET https://api.hanzo.id/api/branding?domain=your-domain.com

Response:

{
  "orgId": "your-org",
  "orgName": "Your Organization",
  "logo": "https://...",
  "colors": { ... },
  "content": { ... },
  "links": { ... },
  "auth": { ... }
}

Development

# Install dependencies
npm install

# Start development server
npm run dev

# Build for production
npm run build

# Start production server
npm start

Environment Variables

# IAM backend URL
HANZO_IAM_URL=https://api.hanzo.id

# Public IAM URL (for client-side redirects)
NEXT_PUBLIC_IAM_URL=https://api.hanzo.id

Forking for White-Label

  1. Fork this repository
  2. Update lib/branding.ts with your default branding
  3. Add your logo to public/logos/
  4. Update app/globals.css for custom styling
  5. Deploy to your infrastructure

Directory Structure

hanzo-id/
├── app/
│   ├── layout.tsx      # Root layout with metadata
│   ├── page.tsx        # Redirects to /login
│   ├── login/
│   │   └── page.tsx    # Main login page
│   ├── signup/         # Sign up page
│   ├── forgot-password # Password reset
│   └── callback/       # OAuth callback handler
├── components/
│   ├── LoginForm.tsx   # Login form component
│   └── MarketingPanel.tsx  # Right side marketing content
├── lib/
│   └── branding.ts     # Branding configuration
├── public/
│   └── logos/          # Organization logos
└── config/             # Additional configuration

License

MIT - Fork and customize freely!