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

77 lines
2.4 KiB
TypeScript

// OAuth Callback Handler for Hanzo IAM
"use client"
import { Suspense, useEffect, useState } from "react"
import { useRouter } from "next/navigation"
import { useAuth } from "@/lib/auth"
import { getIam, takeReturnUrl } from "@/lib/iam"
import { EmptyState, ErrorState, Loading } from "@/components/ui/states"
function CallbackContent() {
const [status, setStatus] = useState<"loading" | "success" | "error">("loading")
const [error, setError] = useState("")
const router = useRouter()
const { loginWithToken } = useAuth()
useEffect(() => {
handleCallback()
}, [])
async function handleCallback() {
try {
// Canonical PKCE exchange (HIP-0111): the IAM SDK validates state,
// exchanges the authorization code at the token endpoint with the stored
// PKCE verifier, and returns tokens. The resulting IAM access token is
// the session bearer the bootnode API verifies against IAM JWKS.
const tokens = await getIam().handleCallback()
await loginWithToken(tokens.accessToken)
setStatus("success")
// Redirect to the stashed destination after successful login.
const returnUrl = takeReturnUrl()
setTimeout(() => {
router.push(returnUrl)
}, 1500)
} catch (err) {
console.error("Auth callback error:", err)
setError(err instanceof Error ? err.message : "Authentication failed")
setStatus("error")
// Redirect to login after error
setTimeout(() => {
router.push("/login")
}, 3000)
}
}
// Three states, three shared primitives — no bespoke tick/cross SVGs, and no
// `bg-green-100` / `text-red-600` literals, which were the only two colours
// in the app that ignored the token layer and so stayed light-mode green and
// red on a black page.
if (status === "loading") return <Loading full label="Completing sign-in…" />
if (status === "error") {
return (
<div className="flex min-h-screen items-center justify-center">
<ErrorState title="Sign-in failed" description={`${error} Returning you to the login page…`} />
</div>
)
}
return (
<div className="flex min-h-screen items-center justify-center">
<EmptyState title="Signed in" description="Taking you to your dashboard…" />
</div>
)
}
export default function AuthCallback() {
return (
<Suspense fallback={<Loading full />}>
<CallbackContent />
</Suspense>
)
}