feat(fabric-admin): Fabric Admin section with API token generator#1533
Conversation
New top-level "Fabric Admin" area, gated to fabric admins / super users, built as an extensible section (layout + sub-nav rail) so future admin endpoints slot in as sub-pages. First page generates a short-lived API token for programmatic access. - Navbar: fabric-admin-gated "Fabric Admin" item (isAdminMode). - routes: fabricAdminLayoutRoute (under dashboardLayout) + api-token index, wired into rootRouteTree keeping getParentRoute/addChildren in lockstep. - FabricAdminShell: SubNavRail shell, render-gated via useCloudAuth/isAdminMode (dashboard guard still handles the unauthenticated redirect). - ApiToken page: generate button -> POST /Admin/ApiToken, shows the token (read-only field + copy) with expiry and a "copy it now" note. - ApiTokenResult type in api.patch.d.ts; endpoint isn't in generated types so the mutation casts the URL + response (like getCurrentUser). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a new Fabric Admin section, allowing administrators to generate short-lived API tokens for programmatic access. It includes the addition of a 'Fabric Admin' link in the navigation bar, a shell layout with sub-navigation, and the API token generation interface and mutation. Feedback on the changes suggests removing the local 'onError' handler from the token generation mutation to prevent duplicate error notifications, and correcting the 'variant="submit"' prop on the generate button to 'type="button"'.
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Review feedback (Gemini): the global MutationCache.onError in react-query/queryClient already toasts mutation failures, so the local onError produced a duplicate toast. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kriszyp
left a comment
There was a problem hiding this comment.
I see there are comments about super user; I don't know if a super user is really a valid concern, if you manage to login as the super user, I assume you don't need a token. So I think this looks good.
…ription Review feedback (Kris): isAdminMode includes super_user, but the token endpoint requires an SSO session only fabric_admin accounts have — a password-authenticated super user would see the page and only ever get 403. New isFabricAdmin helper (accepts the cloud/local user union) gates the shell and navbar item. Navbar now derives it from its existing useOverallAuth() subscription instead of useAdminMode(), which added a second authStore listener per Navbar. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
I can't verify this locally since fabric admins must use Google auth, even locally... pushing to dev, though I don't think I have a user there either... how did you verify locally, David? |
dawsontoth
left a comment
There was a problem hiding this comment.
Let's just call it Admin, drop Fabric from the name. We're already on Fabric, that's understood. And there's no Admin for anyone except us, so it's safe ambiguation of the term.
Review feedback (Dawson): we're already on Fabric and there's no admin area for anyone but us, so "Admin" is unambiguous. Renames the route (/fabric-admin -> /admin), nav label, section heading, feature dir (features/fabricAdmin -> features/admin), and FabricAdminShell -> AdminShell. The Harper role identifier (fabric_admin / fabricRole / isFabricAdmin) is unchanged — that's the actual role name, not the section label — and it now aligns with CM's existing /Admin namespace. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Done in 7446b78 — renamed to Admin throughout: route On verifying locally — you're right that you can't, and neither could I do a true end-to-end run: real Google SSO won't complete against remote CM from localhost (the OAuth callback lands on the dev domain). What I checked locally was appearance only — I seeded a fake admin user and stubbed the generate-token mutation for a pure-visual pass, then reverted it. The happy path (real SSO → mint) was never exercised locally. The actual unblock for "fabric admins must use Google even locally" is the CM side: central-manager#459 adds — Claude Opus 4.8 (on behalf of @DavidCockerill) |
kriszyp
left a comment
There was a problem hiding this comment.
Looks like there is an edge case to cover, but looks good.
Proposed inline comments (anchors failed):
src/features/fabricAdmin/apiToken/mutations/useGenerateApiToken.ts:17: Could we keep this bearer token out of the global mutation cache once the page stops using it? Eachmutatestores the successfulApiTokenResult; generating again or unmounting only makes the old entry inactive, and logout currently clearsQueryCachebut notMutationCache, so the credential remains readable throughqueryClient.getMutationCache()until GC. SettinggcTime: 0here would discard each token as soon as its observer is removed. If we want the token to live only inApiTokenIndexstate even while mounted, we can also call the mutation'sreset()after copying the success result into state.src/hooks/useAuth.ts:54: Could we pin the role boundary introduced in the follow-up commit? A small table test forisFabricAdmin(fabric_admintrue;super_user,least_privileged, a local user, andnullfalse) would prevent the exact regression this re-review fixed. The current mutation test mocks only the transport and cannot catch the route being exposed to password-authenticatedsuper_userusers again.
| } | ||
|
|
||
| export function useGenerateApiTokenMutation() { | ||
| return useMutation<ApiTokenResult, Error, void>({ mutationFn: generateApiToken }); |
There was a problem hiding this comment.
Could we avoid returning the bearer credential through React Query mutation state? useMutation stores successful data in the global MutationCache; with the browser default it remains there for five minutes after this route unmounts, and logoutOnSuccess() clears only the QueryCache. That means a generated operationToken survives navigation/logout in memory (and is directly shown in the mutation inspector in development builds). Please keep the secret out of shared mutation state, or reset/remove this mutation with immediate GC after moving the value into local component state, and add a test asserting the cache no longer contains operationToken after success/unmount/logout.
There was a problem hiding this comment.
Fixed in 21cfcd1. The mutation now sets gcTime: 0, and the page calls reset() right after copying the token into local component state — so the credential lives only in ApiTokenIndex state, is dropped from the shared MutationCache immediately, and can't survive navigation/logout (or show in the mutation inspector). Added a test that generates a token and asserts the MutationCache contains no operationToken after success and is empty after unmount.
— Claude Opus 4.8 (on behalf of @DavidCockerill)
…oundary Review feedback (Kris): - The minted token is a bearer credential but was stored in React Query's global MutationCache — readable via getMutationCache(), shown in the mutation inspector, and NOT cleared on logout (logoutOnSuccess clears only the QueryCache). Set gcTime:0 and reset() after copying it into local component state, so it lives only in ApiTokenIndex state. Test asserts the cache holds no token after success and is empty after unmount. - Add an isFabricAdmin table test pinning the role boundary (fabric_admin true; super_user / least_privileged / local user / null false) so the section can't be re-exposed to a password-auth super_user. Full suite 1725 passing; tsc/lint/format clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Both of your suggestions are in (21cfcd1):
Full suite 1725 green; tsc/lint/format clean. Thanks for the careful passes. — Claude Opus 4.8 (on behalf of @DavidCockerill) |
What
Adds a Fabric Admin section to Studio — an extensible home for internal admin tooling — and ships its first tool: a short-lived API Token generator.
/fabric-adminarea (src/features/fabricAdmin/): a layout shell (FabricAdminShell) with a sub-nav rail and an<Outlet/>, so future admin tools drop in as sibling routes.apiToken/index.tsx): a Generate button that callsPOST /Admin/ApiTokenand shows the returned token (read-only field + copy) with its expiry and a short usage note. The token authenticates as the signed-in admin, with their permissions.useAdminMode()→fabricRoleoffabric_admin/super_user) and are hidden entirely in local Studio.Why
We're forcing internal
fabric_adminaccounts onto Google SSO (see the companion central-manager PR). SSO gives humans a browser session but no password for programmatic/API access. This page lets an admin mint a short-lived, SSO-derived token for CLI/API use without reintroducing a password. It also gives us a place to grow the rest of the admin surface.Depends on / related
fabric-admin-auth-hardening— provides thePOST /Admin/ApiTokenendpoint this page calls (plus the SSO-session hardening). That endpoint must ship first, or Generate returns 404.Testing
vitest run— full suite green (241 files, 1722 passing).tsc -b,oxlint,dprint check— all clean.getParentRoute/addChildrenlockstep rule (CLAUDE.md).Notes