mirror of
https://github.com/hanzoai/id.git
synced 2026-10-16 18:38:15 +00:00
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
79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
import { defineConfig } from 'vite'
|
|
import react from '@vitejs/plugin-react'
|
|
import { resolve } from 'path'
|
|
import { readFileSync, existsSync } from 'fs'
|
|
|
|
/**
|
|
* Per-brand brand.json copy plugin.
|
|
*
|
|
* Each per-org brand package (`@hanzo/brand`, `@luxfi/brand`, `@zooai/brand`,
|
|
* `@parsdao/brand`) ships a `brand.json` at the package root. We serve them
|
|
* verbatim at `/brand/<pkg>/brand.json` so the runtime tenant resolver can
|
|
* fetch the right one based on hostname.
|
|
*
|
|
* Assets (logos, favicons) are imported by URL inside the per-brand `brand.json`
|
|
* (CDN URLs in production), so no further asset copying is needed.
|
|
*/
|
|
const BRAND_PACKAGES = ['@hanzo/brand', '@luxfi/brand', '@zooai/brand', '@parsdao/brand']
|
|
|
|
function brandJsonPlugin() {
|
|
return {
|
|
name: 'hanzo-id-brand-json',
|
|
configureServer(server: any) {
|
|
server.middlewares.use((req: any, res: any, next: any) => {
|
|
const m = /^\/brand\/(.+)\/brand\.json$/.exec(req.url ?? '')
|
|
if (!m) return next()
|
|
const pkg = decodeURIComponent(m[1]!)
|
|
if (!BRAND_PACKAGES.includes(pkg)) {
|
|
res.statusCode = 404
|
|
return res.end()
|
|
}
|
|
try {
|
|
const path = require.resolve(`${pkg}/brand.json`)
|
|
res.setHeader('Content-Type', 'application/json')
|
|
res.setHeader('Cache-Control', 'no-store')
|
|
return res.end(readFileSync(path, 'utf8'))
|
|
} catch {
|
|
res.statusCode = 404
|
|
return res.end()
|
|
}
|
|
})
|
|
},
|
|
generateBundle(this: any) {
|
|
for (const pkg of BRAND_PACKAGES) {
|
|
try {
|
|
const path = require.resolve(`${pkg}/brand.json`)
|
|
if (!existsSync(path)) continue
|
|
this.emitFile({
|
|
type: 'asset',
|
|
fileName: `brand/${pkg}/brand.json`,
|
|
source: readFileSync(path, 'utf8'),
|
|
})
|
|
} catch {
|
|
// pkg not installed — skip silently; only the brands listed in
|
|
// package.json deps actually need their JSON shipped.
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
export default defineConfig({
|
|
plugins: [react(), brandJsonPlugin()],
|
|
resolve: {
|
|
alias: { '@': resolve(__dirname, 'src') },
|
|
},
|
|
server: {
|
|
port: 5173,
|
|
host: '0.0.0.0',
|
|
},
|
|
preview: {
|
|
port: 5174,
|
|
host: '0.0.0.0',
|
|
},
|
|
build: {
|
|
target: 'es2022',
|
|
sourcemap: true,
|
|
},
|
|
})
|