Repository navigation
feat(builderlab): tenant table — a login origin per hosted relay, granted natively and accepted by the binding signer - #1817
Conversation
c133edb to
eccd287
Compare
28c3c73 to
c1bfe83
Compare
eccd287 to
1ee6dc5
Compare
c1bfe83 to
b353bdf
Compare
…nted natively and accepted by the binding signer
Pure configuration plumbing; no first-run page uses it yet. A build-time
table `BUZZ_BUILDERLAB_TENANTS` — rows `{ relayHost, self?, loginOrigin }`
— says which relays each Builderlab deployment hosts and where its people
sign in, so a later first-launch screen can send a typed community URL's
sign-in to the right portal instead of one hard-coded Builderlab.
Frontend (`src/features/identity/builderlab-tenants.ts`, new):
`parseBuilderlabTenants` validates the table at Vite configuration time
(hostname shape, ASCII-only folding, 253-byte cap, unique hosts, unique
64-hex `self`, `loginOrigin` equal to its own canonical https origin, no
unknown fields) and fails without echoing the value;
`discoverAuthentication(found, tenants)` answers `{ kind: "builderlab",
origin }` or `{ kind: "none" }`: hostname match (exact, or a suffix at a
label boundary — `block.builderlab.xyz` covers `buzz.block.builderlab.xyz`
but not `notblock.builderlab.xyz` — default port only, longest host wins),
then NIP-11 `self` equality (a known deployment survives a DNS move; it can
only select an origin already in the table), else `none`.
`vite.config.ts` parses the table and defines
`VITE_BUZZ_BUILDERLAB_TENANTS`. `browserCredential(host, signal, bridge,
origin?)` (`oauth/browser.ts`) runs the whole OAuth round-trip —
`/v1/auth/login`, `/login/exchange`, `/auth/me` — at `oauthTarget(origin)`,
the tenant origin going through the same https/no-credential/no-query
check as the build URL; `nativeBridge` is exported for the next PR. The
Settings card is unchanged and always uses the build URL.
Native (`crates/plugin-manager/build.rs`, `src/lib.rs`): `build.rs` loads
`BUZZ_BUILDERLAB_TENANTS` beside `BUZZ_BUILDERLAB_URL` through
`build_env::load`, validates every row with the same rules as the frontend
parser (at most 16 origins), and emits `BUZZ_BUILDERLAB_ORIGINS` (build URL
origin first, then each distinct tenant origin); `builderlab_origins()`
feeds the `block.builderlab`/`bundled` grant's `network_origins`, and
`BUILDERLAB_ORIGIN` stays for `identity.rs`. Malformed input fails the
build with a fixed message. `serde_json` joins that crate's
`[build-dependencies]` (already in the lockfile; no Cargo.lock change).
`src-tauri/src/identity.rs`: the kind-24243 binding signer accepts a
challenge for production Builderlab or any login origin the build grants
(`builderlab_origins()`) instead of only production or the build URL;
unknown, case-changed, trailing-slash, port-changed and empty origins are
still refused.
Config and docs: `.env.example` and `docs/configuration.md` document the
input, the matching rules and the grant, with the three verified rows
(`communities.buzz.xyz` → `https://app.builderlab.xyz`,
`block.builderlab.xyz` → `https://block.builderlab.xyz`,
`test.blockstaging.build` → `https://test.blockstaging.build`, each with its
relay key). The repository ships no deployment default. The Builderlab
README's two grant sentences name the tenant origins.
Tests: `builderlab-tenants.test.ts` (the three seed rows, subdomain,
case/final-dot, lookalikes, non-default port, `self` fallback and its
limits, nested-row precedence, parser rejections without echo incl. an
over-long host and a Kelvin sign); `oauth/browser.test.ts` (an explicit
origin for the whole round-trip and the same refusal of `http://`);
the tenant lines of `browser-host/vite-config.test.mjs` (the define and the
non-echoing rejection); Rust `build.rs` tests (origin union, file-sourced
table, malformed rows), `host_request.rs` (every granted origin),
`identity/tests.rs` (granted, production, ungranted and malformed origins;
the IPC test signs and verifies for every granted origin and refuses a
foreign one).
Not in this PR: `src-tauri/src/identity.rs` hosted-communities binding for
other tenants stays as on main apart from the origin rule;
`src/bundled/hosted-communities/native.ts` still hard-codes the production
API.
Stack: PR 04 of the onboarding stack, on 02 (`builderlab-tenants.ts`
imports `type FoundCommunity` from `communities/find-community.ts`);
independent of 03. 05–07 build on it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
b353bdf to
6546483
Compare
1ee6dc5 to
1d6ceda
Compare
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord’s automated source review via Wes’s account.
Please replace the internal deployment data identified inline. Optional inline suggestions cover configuration-limit parity and lossless origin serialization. Align the description’s origin cap with the enforced limit: 15 distinct login origins, including the build URL.
Rocket, Mantis and Groot supplied independent source-review lanes. Reviewed head 1d6cedae14db488ec7ef86ccf16325823128226c, base 6546483b07fd588ed2972beb47ab0029a6403836. Source-only: no tests, app execution, native sign-in, live services or CI verification; first-run UI consumers arrive in later PRs.
| # plugin-manager build, which grants every loginOrigin to the bundled Builderlab | ||
| # plugin, so set it where BUZZ_BUILDERLAB_URL is set. Public configuration; no | ||
| # deployment table is supplied by the repo. Rows: {"relayHost","self"?,"loginOrigin"}. | ||
| # BUZZ_BUILDERLAB_TENANTS='[{"relayHost":"communities.buzz.xyz","self":"12f6870117eff1a6318bd38c82a65d51dd19879b7489f57247114d0ee8a96de3","loginOrigin":"https://app.builderlab.xyz"},{"relayHost":"block.builderlab.xyz","self":"76aa9daa7edc9c2e1dc6a2a90dbce10c1bd52f060fa6506ba7bad4f50240fc01","loginOrigin":"https://block.builderlab.xyz"},{"relayHost":"test.blockstaging.build","self":"f7f4df9401ecf46cc608b0889a61d957b306721cdefe42cb24519cb5a473dcf1","loginOrigin":"https://test.blockstaging.build"}]' |
There was a problem hiding this comment.
🤖 P2 — Replace internal deployment data with synthetic examples. This newly added table publishes internal tenant/staging hostnames and their relay-key mappings in a public repository. The same data appears in docs/configuration.md, the new TS tenant tests, Rust build/signing fixtures, and commit prose; the PR description also uses the internal host in its matching example. Use reserved example domains and clearly synthetic 64-hex keys throughout these public surfaces, preserving the suffix/identity test cases and legitimate attribution trailers. This is deployment-information exposure, not a claim that public relay keys are credentials.
| parseCommunityAliases(aliases); | ||
| // Public too: the same table the native build grants to the Builderlab plugin. | ||
| const tenants = env.BUZZ_BUILDERLAB_TENANTS ?? ""; | ||
| parseBuilderlabTenants(tenants); |
There was a problem hiding this comment.
🤖 Optional P3 — Validate the same distinct-origin limit in Vite. parseBuilderlabTenants(tenants) checks rows but never counts login origins or includes BUZZ_BUILDERLAB_URL. Consequently 15 distinct tenant origins plus a different build URL pass Vite configuration, while crates/plugin-manager/build.rs:114–128 rejects that same configuration because its union exceeds 15. Check the same deduplicated union here and add boundary cases for a shared versus distinct build origin. Native enforcement already fails closed; this fixes the promised cross-host build-input parity.
| if origins.len() > MAX_ORIGINS { | ||
| return Err("Too many Builderlab login origins"); | ||
| } | ||
| Ok((origin, origins.join(","))) |
There was a problem hiding this comment.
🤖 Optional P3 — Preserve origins across serialization. Canonical HTTPS URL validation does not exclude commas in a hostname: the pinned url 2.5.8 host parser uses the WHATWG/IDNA URL deny list, which permits them. Joining a permitted origin such as https://a,b.example here and splitting it in builderlab_origins() turns it into https://a and b.example, so the compiled grant/signing list no longer represents the configured origin. Consider rejecting commas in both build-input validators or using an unambiguous encoding, with a round-trip case. This is trusted configuration handling, not an untrusted runtime bypass.
Onboarding stack (merge in order, top to bottom):
Depends on: PR 02 (
builderlab-tenants.tsimportstype FoundCommunityfromcommunities/find-community.ts). Independent of 03. PRs 05–07 build on it.Diff: production +409/−73; tests +295/−28; docs +56/−2
What this layer is
BUZZ_BUILDERLAB_TENANTS([{relayHost, self?, loginOrigin}]) parsed identically on both hosts:builderlab-tenants.ts(parseBuilderlabTenants,discoverAuthentication(found, tenants)— hostname suffix at a label boundary, default port only, longest host wins, then NIP-11self, elsenone) andcrates/plugin-manager/build.rs(same rules, emitsBUZZ_BUILDERLAB_ORIGINS);builderlab_origins()feeds the bundled plugin'snetwork_origins;identity.rs's kind-24243 signer accepts any granted origin instead of only the build URL;browserCredential(host, signal, bridge, origin?)runs the whole OAuth round-trip at a tenant portal (nativeBridgeexported for the next layer). Pure config plumbing; no first-run page uses it yet.Review focus
build.rsaccepts,parseBuilderlabTenantsaccepts, and vice versa (ASCII-only folding, 253-byte host cap, unique hosts/self, canonical httpsloginOrigin, ≤16 origins). Both fail without echoing the value.discoverAuthentication: label-boundary suffix matching (notblock.builderlab.xyzmust not matchblock.builderlab.xyz), default port only, longest host wins,selfcan only select an origin already in the table.src-tauri/src/identity.rs: the signer's allow-list is nowbuilderlab_origins()(build URL + tenants) plus production; the tests cover granted, production, ungranted, case-changed, trailing-slash, port-changed and empty origins.oauth/browser.ts: the tenant origin goes through the sameoauthTargetcheck as the build URL; the Settings card is unchanged. The straddling files (vite.config.ts,vite-config.test.mjs,.env.example,docs/configuration.md, the README) carry only their tenant hunks here; their seed hunks land in PR 05.Checks run on this branch's tree
--no-verify; the hosted checks on this PR are the verification. The plan's checks for this layer were:cargo test -p buzzodz-plugins --locked,cargo test --manifest-path src-tauri/Cargo.toml --locked identity host_request,cargo fmt --check,vitest run src/features/identity/builderlab-tenants.test.ts src/bundled/builderlab browser-host/vite-config.test.mjs,tsc --noEmit.Native evidence
None on this tree. The native first-run recordings and the hands-on checks on the branch were made on the full tree, which only the DO-NOT-MERGE PR 09 reproduces (it adds the isolated debug Keychain slot). Treat every layer below 09 as unit/integration-tested only.
🤖 Generated with Claude Code