Files
zeekayandHanzo Dev 7d293212cb refactor: web/ is now console/ — bootnode/bootnode is the backend, bootnode/console the frontend
Names the two halves for what they are. The repo already holds both (12407e9 merged
bootnode/bootnode into this history); this makes the frontend directory say so, so the layout
reads the way the owner describes the system: backend and frontend separate, one repo.

git mv throughout — 187 renames, history preserved. Every path reference follows: hanzo.yml
build context and dockerfile, compose.yml, compose.prod.yml, start.sh, LLM.md,
infra/k8s/web-deployments.yaml, infra/cloud/deploy.sh, and the Dockerfile's own COPY paths.
Image and container names (bootnode-web, the -web tag suffix) are deliberately NOT renamed here:
those are a deploy contract the App CR and ingress bind to on port 3001, and breaking them is a
separate, staged change.

Console builds green after the move; the Go tree builds.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 17:47:51 -07:00

59 lines
2.3 KiB
TypeScript

// Canonical IAM auth (HIP-0111) via @hanzo/iam SPA PKCE.
//
// One source of truth for the browser IAM client, white-labelled per brand:
// serverUrl + clientId come from the active brand (lib/brand.ts), resolved
// from window.location.hostname. There are no env overrides — see below.
//
// HIP-0111: authenticate ONLY through @hanzo/iam against the canonical OIDC
// endpoints. The SDK resolves /v1/iam/oauth/* via live discovery (with a
// canonical-path fallback), drives PKCE S256, and owns `state` for CSRF. No
// hand-rolled authorize URLs, no genericOAuth({discoveryUrl}), no /api/auth.
"use client"
import { IAM } from "@hanzo/iam/browser"
import { getBrand } from "@/lib/brand"
// Stash the post-login destination across the IAM round-trip. The SDK owns
// `state` for CSRF; the app owns where to land after sign-in.
const RETURN_URL_KEY = "bootnode_return_url"
export function getIam(): IAM {
// The brand is the ONE source for issuer + client, and it is resolved from
// the hostname of the page the user is standing on. There is no env
// override, by design: this deployment serves MANY white-label hosts, so a
// build-time NEXT_PUBLIC_IAM_URL is a single global value that every brand
// would share — it cannot express per-host white-labeling, and it silently
// outranked the correct per-brand default. That is exactly what shipped:
// bootno.de/dashboard bounced to hanzo.id and asked a Bootnode customer to
// "Sign in to Hanzo ID" under the Hanzo mark.
//
// `getBrand()` and not `useBrand()`: this runs imperatively at click time,
// when `window` exists, so it reads the true host rather than the prerender
// default — and it must stay callable outside React.
const { url: serverUrl, clientId } = getBrand().iam
return new IAM({
serverUrl,
clientId,
redirectUri: `${window.location.origin}/auth/callback`,
scope: "openid profile email",
})
}
export function setReturnUrl(url: string): void {
try {
sessionStorage.setItem(RETURN_URL_KEY, url)
} catch {
/* sessionStorage unavailable — fall back to default destination */
}
}
export function takeReturnUrl(): string {
try {
const url = sessionStorage.getItem(RETURN_URL_KEY)
sessionStorage.removeItem(RETURN_URL_KEY)
return url || "/dashboard"
} catch {
return "/dashboard"
}
}