feat(site): tenant switcher chrome in generated admin shell (#79)#80
Merged
Conversation
Surface the operator's tenancy in the generated admin shell and let multi-tenant users switch the active tenant, building on the stateless header-driven model shipped in #67/#70 (no active_tenant token claim; the active tenant is chosen per-request via the X-Active-Tenant header). - auth.ts: activeTenantStore (sessionStorage), ACTIVE_TENANT_HEADER, fetchMe(), and MeResponse/TenantRef/ActiveTenant types mirroring the Rust shapes. The active tenant is cleared alongside the token so a lost session never strands a stale selection for the next operator. - api-client: attach X-Active-Tenant on every request (a caller-supplied header still wins). The server already 403s a non-member value, so this is convenience + display, never the security boundary. - App.tsx: TenantControl in the topbar — a picker for >=2 memberships, a chip for exactly one, and an "Operating as: platform_admin" badge for role holders (the server bypasses tenant scoping for them, so a picker would imply a constraint that does not exist). Switching stores the choice and hard-reloads to /admin so no prior-tenant data lingers in the React Query cache under the new scope. - vendor.rs: INDEX_CSS styles for the picker, chip, and badge. Frontend-only; no backend change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #79.
Frontend-only — surfaces the operator's tenancy in the generated admin shell and lets multi-tenant users switch the active tenant. Builds on the stateless, header-driven model shipped in #67/#70: the PASETO carries the full
tenant_chain; which membership scopes a request is chosen per-request via theX-Active-Tenantheader. Noactive_tenanttoken claim, no token re-mint, no new backend route (the design decision behind this approach is recorded in the issue discussion).Changes
lib/auth.ts—activeTenantStore(sessionStorage),ACTIVE_TENANT_HEADER,fetchMe(), andMeResponse/TenantRef/ActiveTenanttypes mirroring the Rust shapes. Active tenant is cleared with the token so a lost session never strands a stale selection.admin/api-client.ts— attachX-Active-Tenanton every request (a caller-supplied header still wins).App.tsx—TenantControlin the topbar: picker (≥2 memberships), chip (exactly one), and "Operating as: platform_admin" badge for role holders. Switching stores the choice and hard-reloads to/adminto avoid cross-tenant cache bleed in React Query.site/vendor.rs—INDEX_CSSstyles for the picker/chip/badge.Security note
The issue's defense-in-depth requirement ("reject switching to a tenant the caller isn't a member of") is already enforced server-side —
tenant_scopereturns403 ACTIVE_TENANT_FORBIDDENfor a non-member header value. This layer is convenience + display, never the boundary.Verification
cargo check -p schema-forge-cli— clean.demo.schema,pnpm install,tsc --noEmit— zero new type errors from this change.cargo nextest run -p schema-forge-cli— 332/332 pass (no render-snapshot regressions).templates/site/**.Out of scope
tscflags a pre-existing error atApp.tsx:541(toastOptions={{ ariaLive: "polite" }}— not a valid sonner key). Present onmain, invisible to CI because the Vite/esbuild path strips types. Left untouched to keep this PR scoped; can fix in a separatefix(site):PR.