Skip to content

feat(organization): manage service accounts and API tokens, and show everything a role allows - #1917

Open
DavidCockerill wants to merge 1 commit into
stagefrom
david/576-org-api-tokens
Open

DavidCockerill wants to merge 1 commit into
stagefrom
david/576-org-api-tokens

Conversation

@DavidCockerill

@DavidCockerill DavidCockerill commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

⊙ Problem

Central Manager is adding service accounts and their API tokens (HarperFast/central-manager#576, in HarperFast/central-manager#943): non-human members of an organization, managed by its admins, whose revocable, expiring Bearer tokens a CI pipeline uses instead of a person's password. Admins need somewhere to create the accounts, mint and revoke their tokens, and turn tokens on for the organization. Service accounts act with one of the organization's roles, and the Roles dialog didn't show what a role can actually do. Refs #1791.

❓ Your call: ship order. The API access section calls /ServiceAccount/, /ApiToken/ and the apiTokens setting, which exist only once the Central Manager PR is deployed; until then the section shows an error. Holding this PR until Central Manager is live in each environment is simpler than a feature flag, and is what I'd suggest.

💡 Solution

Settings → Authentication is now two sections: OAuth providers, and a new API access section shown only to the organization's admins (the people Central Manager lets manage it):

  • Allow API tokens, the organization-wide switch, off until an admin turns it on. Off refuses every token on its next request.
  • Service accounts lists each account's name and id, role, status, live token count and creation date. Create service account asks for a name and one of the organization's roles; a row's menu changes its role, disables or enables it, or deletes it.
  • Selecting an account opens its tokens beneath the list: name, creator (email, id on hover), created, expires, last used, with Revoke after a confirmation. Create token asks for a name and a lifetime in days (default 30, at most 90), then shows the token once with a copy button.

The token never outlives its dialog: the create mutation keeps it out of the mutation cache, the dialog can't be closed or resubmitted while a request is in flight, and it mints at most one token. A token that still arrives unseen (after navigating away, say) is revoked at once with a warning toast. Cache upkeep lives on the mutation hooks, so a page that unmounts mid-request still leaves the lists current, and a revoked token or deleted account leaves the list at once even if the refresh after it fails.

❓ Your call: that cleanup revoke is best effort. If it fails (signed out mid-request, network drop), an error toast says so, and the token, whose secret nobody holds, stays listed until revoked or expired. Making it airtight would need a server-side "pending reveal" state, which is out of proportion to the risk.

Roles keeps the shared table and gains a heading. The role dialog now shows everything a role allows, in Central Manager's own terms:

  • Organization: Update organization and Delete organization.
  • Resource permissions: a View/Create/Update/Delete matrix for Roles (which also governs adding, inviting and removing members) and Clusters.
  • Cluster access: all clusters, or selected clusters, mapped exactly onto Central Manager's per-cluster clusters.resources entries: View, Update and Delete apply only to the chosen clusters, while Create stays organization-wide. A role whose per-cluster entries the form can't express exactly is shown as custom and left untouched.
  • Advanced JSON edits the same value as the form, keeps fields the form doesn't show, and is validated before it can be saved or left. New roles start with the defaults the create form always had, and the built-in admin role and your own role open read-only.

❓ Your call: the Roles list stays on SimpleBrowseDataTable, with the sort, filter and paging from #1837 and stage's columns and Refresh/Add buttons; this branch only adds the heading. The dialog is titled "Create role" while the button still says "Add".

Screenshots

Taken on the local Fabric rig at this branch's head (1280×860).

View Light Dark
Settings: API access API access, light API access, dark
A selected account's tokens Token panel, light Token panel, dark
Create token Create token, light Create token, dark
Roles list Roles list, light Roles list, dark
Create role: permissions Create role, light Create role, dark
Create role: selected clusters Selected clusters, light Selected clusters, dark
Create role: Advanced JSON Advanced JSON, light Advanced JSON, dark

Docs: this is a user-facing feature, so a companion page in HarperFast/documentation follows (service accounts, tokens and the switch). It isn't written yet; its tracking issue needs approval first.

⚖️ Alternatives

  • A personal API Tokens page listing the signed-in member's own tokens: this PR's first version. Central Manager moved tokens to org-managed service accounts (a pipeline shouldn't break, or keep access, when a person leaves), so the page moved with it.
  • Role presets and a permissions summary inferred from role names: the first Roles rework. Dropped for the matrix, which shows Central Manager's actual fields and never guesses from a name.
  • A custom Roles table with each id under its name: dropped for the shared table, so the org tables stay consistent with feat(organization): sort, filter and page the org roles table #1837.

❓ Your call: the 90-day lifetime ceiling is hardcoded to match Central Manager's default. A lower configured limit comes back as a 400 that names it, but a higher one can't be reached from this form.

❓ Your call: to fix the table's bottom corners, the border of SimpleBrowseDataTable moved from its body to its container, which changes every table that uses it (Users, Roles and the others).

🔧 Changes

✅ Verification

❓ Your call: this branch was rebased onto stage as one commit after #1832, #1837, #1862, #1887, #1888 and #1897 merged. The role dialogs and the service-account and token name fields adopt their length-limit and no-verdict-toast conventions; a full final review round on the rebased commit found nothing new.

🤖 Generated by Anthropic Claude (Opus 5.5) with Claude Code; posted via @DavidCockerill.

Related PRs: #1837 independent (merged; the roles list keeps its sort, filter and paging), #1832 independent (merged), #1862 independent (merged), #1887 independent (merged; its settings test stubs this branch's API access section), #1888 independent (merged; this branch adopts its length limits), #1897 independent (merged; this branch adopts its no-verdict toasts), #1780 independent, #1896 independent, #1875 independent, #1793 independent, #1882 independent (merged; its role-dialog changes are carried into this branch's rewrite), #1891 independent (merged; likewise), #1899 independent (merged), #1868 independent (merged), #1898 independent (merged)
Complexity: medium

Review-Coverage: authored=claude; ran=gemini,codex; adjudicated=domain; declined=cursor-grok,cursor-composer,cursor-kimi,cursor-muse; rounds=12; full=3 @ 1b951d5

Review-Attention: study ~20m (critical: OrganizationRoleFormSchema.ts; decisions: role-editor-snapshot, role-field-whitelist, unseen-token-revoke, api-access-admin-only, do-less-alternative) @ 1b951d5

@DavidCockerill DavidCockerill self-assigned this Oct 12, 2026
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 78.43% 14122 / 18005
🔵 Statements 78.38% 14997 / 19132
🔵 Functions 73.9% 3779 / 5113
🔵 Branches 73.29% 11051 / 15077
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/components/SimpleBrowseDataTable.tsx 100% 95% 100% 100%
src/features/organization/mutations/OrganizationRoleFormSchema.ts 100% 100% 100% 100%
src/features/organization/roles/index.tsx 86.95% 75% 71.42% 86.36% 47-50, 57, 68
src/features/organization/roles/rolePermissions.ts 100% 100% 100% 100%
src/features/organization/roles/components/RolePermissionsEditor.tsx 96.55% 82.05% 91.66% 96.22% 119, 224
src/features/organization/roles/modals/AddOrganizationRoleModal.tsx 83.33% 63.63% 75% 83.33% 54-56, 66
src/features/organization/roles/modals/EditOrganizationRoleModal.tsx 68.29% 71.05% 50% 69.23% 28, 48-52, 89-96, 107-115
src/features/organization/serviceAccounts/ServiceAccountsSection.tsx 93.61% 92.5% 86.95% 92.85% 66, 195, 245
src/features/organization/serviceAccounts/__tests__/selectStub.tsx 85.71% 100% 83.33% 85.71% 27
src/features/organization/serviceAccounts/components/RevokeApiTokenButton.tsx 100% 100% 100% 100%
src/features/organization/serviceAccounts/components/ServiceAccountTokens.tsx 88.88% 83.33% 83.33% 88.88% 37, 147
src/features/organization/serviceAccounts/components/Timestamp.tsx 100% 100% 100% 100%
src/features/organization/serviceAccounts/lib/timeText.ts 100% 100% 100% 100%
src/features/organization/serviceAccounts/modals/ChangeServiceAccountRoleModal.tsx 90.9% 58.33% 80% 90.9% 33
src/features/organization/serviceAccounts/modals/CreateApiTokenModal.tsx 93.22% 82.6% 92.85% 94.54% 99-102, 130, 154
src/features/organization/serviceAccounts/modals/CreateServiceAccountModal.tsx 96.42% 78.57% 100% 100% 82
src/features/organization/serviceAccounts/mutations/createOrganizationApiToken.ts 100% 100% 100% 100%
src/features/organization/serviceAccounts/mutations/revokeOrganizationApiToken.ts 100% 100% 100% 100%
src/features/organization/serviceAccounts/mutations/serviceAccounts.ts 100% 100% 100% 100%
src/features/organization/serviceAccounts/queries/serviceAccounts.ts 100% 100% 100% 100%
src/features/organization/settings/ApiAccessSettings.tsx 100% 100% 100% 100%
src/features/organization/settings/ApiTokensSetting.tsx 100% 100% 100% 100%
src/features/organization/settings/SettingsSection.tsx 100% 100% 100% 100%
src/features/organization/settings/index.tsx 72.6% 66.66% 64.7% 74.64% 87-88, 128, 129, 141-145, 152-153, 158-159, 164-167, 217, 281-387, 422-423, 497-498
src/hooks/usePermissions.ts 60.48% 56.77% 73.33% 60.65% 59-65, 84-86, 107-114, 147, 152, 223, 226, 229, 241-251, 290-292, 310-312, 345-373
Generated in workflow #2349 for commit 1b951d5 by the Vitest Coverage Report Action

gemini-code-assist[bot]

This comment was marked as resolved.

@DavidCockerill
DavidCockerill marked this pull request as ready for review October 12, 2026 17:36
@DavidCockerill
DavidCockerill requested a review from a team as a code owner October 12, 2026 17:36
…everything a role allows

Settings → Authentication gains an admin-only API access section: the
organization's Allow API tokens switch (off until an admin turns it on), and
service accounts with their tokens (create and revoke tokens, change an
account's role, disable or enable it, delete it), for central manager's
service accounts (HarperFast/central-manager#576).

The role dialog shows everything a role allows in central manager's terms:
organization update and delete, a View/Create/Update/Delete matrix for Roles
and Clusters, cluster access (all or selected clusters, exactly as
clusters.resources expresses it, anything else shown as custom), and an
Advanced JSON tab that shares the form's value and is validated before it
can be saved or left. The Roles list stays on the shared table, with a
heading.

Refs #1791

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DavidCockerill
DavidCockerill force-pushed the david/576-org-api-tokens branch from 3f8c708 to 1b951d5 Compare October 13, 2026 20:22
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