Skip to content

Service accounts and API keys - #1989

Draft
GregorShear wants to merge 12 commits into
mainfrom
greg/access
Draft

Service accounts and API keys#1989
GregorShear wants to merge 12 commits into
mainfrom
greg/access

Conversation

@GregorShear

@GregorShear GregorShear commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

New Service Accounts admin section (replaces the CLI-API tab): create accounts with scoped capabilities on catalog prefixes, manage grants, and issue/revoke API keys — key secrets are shown once at creation and can't be retrieved again.

The old CLI-API tab splits into two:

  • flowctl access token/flowctl/accessToken page with a copy-to-clipboard button (token is never rendered on screen); /admin/api redirects here.
  • Personal Tokens — refresh-token management at /settings/personalTokens, linked from the user menu.

Rebased onto current main; uses the deployed apiKey service-account GraphQL API with regenerated GQL types.

Test plan

  • Create a service account, issue an API key, and confirm the secret shows once (copyable) and can't be retrieved again
  • Add/remove grants and revoke an API key from the detail view
  • /admin/api redirects to /flowctl/accessToken; copy works, token not shown
  • Personal Tokens opens from the user menu; refresh tokens create/revoke

@GregorShear GregorShear closed this Jun 3, 2026
@GregorShear
GregorShear deleted the greg/access branch June 3, 2026 02:44
@GregorShear
GregorShear restored the greg/access branch June 3, 2026 02:44
@GregorShear GregorShear reopened this Jun 3, 2026
@GregorShear
GregorShear changed the base branch from main to greg/refresh-tokens June 3, 2026 03:17
@GregorShear
GregorShear force-pushed the greg/access branch 4 times, most recently from 6c847e6 to f0bebb8 Compare June 3, 2026 04:50
@GregorShear
GregorShear force-pushed the greg/refresh-tokens branch 2 times, most recently from d60c562 to 4db5bdd Compare June 4, 2026 03:07
@GregorShear
GregorShear force-pushed the greg/refresh-tokens branch from 96f4766 to 2329fe9 Compare June 17, 2026 14:45
Base automatically changed from greg/refresh-tokens to main June 18, 2026 03:23
secret: tokenResult.data.createServiceAccountToken.secret,
description: keyDesc || 'API key',
expires: formatExpiryFromNow(keyLife),
account: catalogName,
@GregorShear
GregorShear changed the base branch from main to greg/dash/nav July 7, 2026 03:29
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

❌ Code Health

Introduces $\textcolor{red}{1}$ dead-code issue (removes $\textcolor{green}{1}$).

48 Unused files

File imported nowhere — delete (or import) it.

     src/hooks/useDelay.ts
     src/hooks/useDraft.ts
     src/pages/NoGrants.tsx
     src/pages/OAuth.tsx
     src/services/encryption.ts
     src/types/global.ts
     src/types/vitest.ts
     src/components/graphs/TaskHoursByMonthGraph.tsx
     src/components/tables/Link.tsx
     src/context/LoopIndex/index.tsx
…and 38 more

64 Unused exports $\textcolor{red}{+1}$ $\textcolor{green}{-1}$

Exported symbol with no references outside its own file — un-export it, or delete it if unused entirely

$\textcolor{red}{+}$ src/api/gql/serviceAccounts.ts : SERVICE_ACCOUNT_FRAGMENT
$\textcolor{green}{-}$ src/context/Theme.tsx : logoColors
     src/context/Theme.tsx : intensifiedOutlineThick
     src/context/Theme.tsx : tableAlternateRowsSx
     src/context/Theme.tsx : draggableChipIconSx
     src/context/Theme.tsx : hiddenButAccessibleInput
     src/context/Theme.tsx : primaryColoredBackground_hovered
     src/context/Theme.tsx : detailsPanelBgColor
     src/context/Theme.tsx : menuBackgroundColor
     src/context/Theme.tsx : flexGrowToSiblingsSx
…and 55 more

26 Unused exported types

Exported type with no references outside its own file — un-export it, or delete it if unused entirely

     src/utils/billing-utils.ts : FREE_GB_BY_TIER
     src/types/index.ts : InferredSchemas
     src/types/index.ts : Shard
     src/components/shared/WizardDialog/index.ts : WizardStep
     src/api/dataPlanes.ts : AwsDnsEntry
     src/stores/ShardDetail/types.ts : TaskShardDetailsWithShard
     src/stores/ShardDetail/types.ts : ShardDetails
     src/components/tables/Logs/types.ts : RefreshLogsFunction
     src/types/schemaModels.ts : CollectionSchema
     src/components/editor/Bindings/types.ts : SelectedCollectionChangeData
…and 16 more

14 Unused exported enum members

An enum member referenced nowhere

     src/services/supabase.ts : CONNECTOR_TAGS
     src/services/supabase.ts : DRAFTS_EXT
     src/services/supabase.ts : TASKS_BY_DAY
     src/stores/Tables/hooks.ts : accessGrants
     src/stores/Tables/hooks.ts : accessLinks
     src/stores/Tables/hooks.ts : billing
     src/stores/Tables/hooks.ts : connectors
     src/stores/Tables/hooks.ts : entitySelector
     src/stores/Tables/hooks.ts : prefixes
     src/stores/Tables/hooks.ts : prefixAlerts
…and 4 more

5 Unused dependencies

In package.json but never imported

     package.json : @mui/lab
     package.json : @testing-library/jest-dom
     package.json : @urql/exchange-retry
     package.json : logrocket-react
     package.json : stripe

3 Unused devDependencies

In package.json devDependencies but never used

     package.json : @types/logrocket-react
     package.json : @types/react-inspector
     package.json : sharp

Base automatically changed from greg/dash/nav to main July 17, 2026 13:08
The admin area's four pages were reached through a tab bar rendered at the top of each page. They now appear as sub-items of the Admin entry in the sidebar, so the sidebar shows where you are within the section instead of the page repeating its own navigation.

While the sidebar is expanded, the sub-items list beneath the active section as indented labels without icons, and the section header drops its selected background so only the current page reads as selected. On the collapsed rail there is no room for the labels, so hovering or focusing the section opens them in a flyout beside the rail; the flyout closes on a short delay to let the pointer reach it, and ArrowRight moves focus into it for keyboard users.

Billing remains gated on hasAnyAccess, as it was in the tab bar. The access grants page title is now "Account Access" to match its nav label, which the tab bar had already been using.

NavigationTabProps is no longer exported now that the deleted admin tabs were its only outside consumer, and NavLink stays local to NavItems since the sidebar reaches it through the wrapper.
Service accounts are non-human identities that hold their own grants and API keys, so automation can authenticate as a durable principal with a scoped capability set. This adds the admin UI for managing them, replacing the Admin > API tab.

Admin > Service accounts (`/admin/serviceAccounts`):
- List of accounts as cards, with an empty state that offers both direct and guided creation
- Details view (`/admin/serviceAccounts/details`) with an API keys section and a grants section, each supporting create and revoke/remove
- CreateDialog for the full account creation flow (name, capability selection, prefix grants, key lifetime), plus CreateApiKeyDialog and GrantDialog for adding to an existing account
- SecretRevealModal shows a newly minted key once; the value is deliberately not cached
- ExpiryWarning and UsageIndicator surface key lifetime and usage at a glance
- Accounts get a stable monogram avatar color derived from their name (`src/utils/stableColor.ts`), and generated account names come from an alliterative word list (`src/utils/alliterate.ts`)

GraphQL (`src/api/gql/serviceAccounts.ts`) covers the serviceAccounts query and the createServiceAccount, createApiKey, revokeApiKey, revokeAllApiKeys, addServiceAccountGrant and removeServiceAccountGrant mutations, with URQL cache entries for ServiceAccount, ServiceAccountApiKey, UserGrant, and a null-keyed CreateApiKeyResult so the one-time secret stays uncached.

The personal refresh-token UI moves out of the admin area to `/settings/personalTokens`, reachable from the user menu, and the flowctl auth flow gets its own page at `/flowctl/accessToken`.
List: cap the account cards at 300px wide (`repeat(auto-fill, minmax(240px, 400px))` for the main grid, `minmax(200px, 300px)` for the "No access" compacts) so they keep a consistent size instead of stretching to fill the row.

CreateDialog: the full-name preview's color is derived from the name, so it changed on every keystroke. The text still updates immediately, but the color now settles 300ms after typing stops and crossfades over a second. Adds `useDebouncedValue`.

Details: show the selected account's leaf name as a trailing breadcrumb segment in the content header. `TopBar` gains a `headerDetail` field, set through `usePageTitle` and rendered by `PageContainer` after a "/" separator; it holds a resolved string rather than a message id, since entity names aren't translatable. The details page reads the name from the URL param so the breadcrumb is populated on first render rather than when the query lands, and the header segment is pluralized to "Service Accounts" so the trail names the parent section.

Also adds "mercurial" to the alliterative name library.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants