mirror of
https://github.com/hanzoai/bootnode.git
synced 2026-10-25 02:31:30 +00:00
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]>
111 lines
4.9 KiB
TypeScript
111 lines
4.9 KiB
TypeScript
// The white-label contract, as executable assertions.
|
|
//
|
|
// Run: `pnpm test` (node:test + native TypeScript stripping — no test
|
|
// framework, no new dependency).
|
|
//
|
|
// Every expectation here was verified against the live IAM before it was
|
|
// written down; see the probe table at the top of lib/brand.ts. This file is
|
|
// what stops it drifting: change a client id or a host and the build's test
|
|
// step says so, instead of a customer discovering it at the login screen.
|
|
|
|
import { strict as assert } from "node:assert"
|
|
import { describe, it } from "node:test"
|
|
import { getBrand, getBrandKey } from "./brand.ts"
|
|
|
|
// host → [brand key, IAM issuer, OIDC client id]. The five hosts this one
|
|
// image must serve, plus the aliases that ride along with them.
|
|
const CONTRACT: [string, string, string, string][] = [
|
|
["bootno.de", "bootnode", "https://id.bootno.de", "bootnode-platform"],
|
|
["web3.hanzo.ai", "hanzo", "https://hanzo.id", "hanzo-web3"],
|
|
["lux.cloud", "lux", "https://lux.id", "lux-cloud"],
|
|
["zoo.cloud", "zoo", "https://zoolabs.id", "zoo-cloud"],
|
|
["cloud.zoo.network", "zoo", "https://zoolabs.id", "zoo-cloud"],
|
|
// Aliases that must inherit their parent's brand.
|
|
["www.bootno.de", "bootnode", "https://id.bootno.de", "bootnode-platform"],
|
|
["nodes.lux.cloud", "lux", "https://lux.id", "lux-cloud"],
|
|
["console.zoo.cloud", "zoo", "https://zoolabs.id", "zoo-cloud"],
|
|
["cloud.pars.network", "pars", "https://pars.id", "pars-app"],
|
|
]
|
|
|
|
describe("white-label by hostname", () => {
|
|
for (const [host, key, issuer, clientId] of CONTRACT) {
|
|
it(`${host} → ${key} / ${clientId}`, () => {
|
|
const brand = getBrand(host)
|
|
assert.equal(getBrandKey(host), key)
|
|
assert.equal(brand.key, key)
|
|
assert.equal(brand.iam.url, issuer)
|
|
assert.equal(brand.iam.clientId, clientId)
|
|
})
|
|
}
|
|
|
|
it("does not regress the one login that works today", () => {
|
|
// lux.cloud + lux-cloud + https://lux.cloud/auth/callback is the triple
|
|
// that answers 302 on lux.id and lands a real user on /dashboard. If this
|
|
// assertion ever fails, the merge broke a working production login.
|
|
const lux = getBrand("lux.cloud")
|
|
assert.equal(lux.iam.url, "https://lux.id")
|
|
assert.equal(lux.iam.clientId, "lux-cloud")
|
|
})
|
|
|
|
it("resolves unknown hosts and localhost to the neutral default", () => {
|
|
for (const host of ["localhost", "127.0.0.1", "example.com", ""]) {
|
|
assert.equal(getBrandKey(host), "bootnode")
|
|
}
|
|
})
|
|
|
|
it("matches on exact host or dot-suffix, never on substring", () => {
|
|
// The old resolver used `hostname.includes(match)`, which would hand a
|
|
// real brand's OIDC client id — and therefore its login — to any host an
|
|
// attacker can name. These must all fall through to the default.
|
|
for (const host of [
|
|
"lux.cloud.attacker.example",
|
|
"zoo.cloudy.example",
|
|
"notbootno.de.example",
|
|
"web3.hanzo.ai.evil.test",
|
|
"xlux.cloud",
|
|
]) {
|
|
assert.equal(getBrandKey(host), "bootnode", host)
|
|
}
|
|
})
|
|
|
|
it("gives every brand its own mark, wordmark and favicon", () => {
|
|
// One image, five identities: a shared /logo.svg for all of them would
|
|
// mean bootno.de's mark showing up on zoo.cloud.
|
|
const seen = new Set<string>()
|
|
for (const host of ["bootno.de", "web3.hanzo.ai", "lux.cloud", "zoo.cloud", "cloud.pars.network"]) {
|
|
const brand = getBrand(host)
|
|
assert.match(brand.logoIcon, new RegExp(`/logo/${brand.key}-icon\\.svg$`))
|
|
assert.match(brand.logoWhite, new RegExp(`/logo/${brand.key}-logo-white\\.svg$`))
|
|
assert.equal(brand.favicon, brand.logoIcon)
|
|
assert.ok(!seen.has(brand.name), `duplicate brand name ${brand.name}`)
|
|
seen.add(brand.name)
|
|
}
|
|
assert.equal(seen.size, 5)
|
|
})
|
|
|
|
it("flags exactly the marks that cannot paint themselves", () => {
|
|
// Measured from public/logo/*-icon.svg: bootnode is fill="currentColor"
|
|
// and lux is fill="white" — both need the current-text-colour mask, or one
|
|
// is invisible on black and the other on white. The rest ship a palette.
|
|
assert.equal(getBrand("bootno.de").monoMark, true)
|
|
assert.equal(getBrand("lux.cloud").monoMark, true)
|
|
assert.equal(getBrand("zoo.cloud").monoMark, false)
|
|
assert.equal(getBrand("web3.hanzo.ai").monoMark, false)
|
|
assert.equal(getBrand("cloud.pars.network").monoMark, false)
|
|
})
|
|
|
|
it("carries a complete, non-empty config for every brand", () => {
|
|
for (const host of ["bootno.de", "web3.hanzo.ai", "lux.cloud", "zoo.cloud", "cloud.pars.network"]) {
|
|
const b = getBrand(host)
|
|
for (const field of ["name", "shortName", "tagline", "domain", "apiUrl", "statusUrl", "networkName"] as const) {
|
|
assert.ok(b[field], `${host}: ${field} is empty`)
|
|
}
|
|
// Partial catalog entries used to produce `undefined` colours and crash
|
|
// rendering, which is why no catalog was ever shipped.
|
|
assert.ok(b.colors.accent && b.colors.accentEnd)
|
|
assert.ok(b.iam.idLabel)
|
|
assert.match(b.apiUrl, /^https:\/\//)
|
|
}
|
|
})
|
|
})
|