feat: rotate GitHub tokens in place + re-point external projects at a different token - #85
Merged
dvcdsys merged 2 commits intoJun 22, 2026
Conversation
…rnal projects at a different token
An expired GitHub PAT silently broke every external project bound to it:
tokens were immutable (delete + recreate minted a new id), and a project's
token_id was set once at creation with no way to change it.
Two admin-only capabilities, both flowing through the OpenAPI spec:
1. Rotate a key in place — PUT /api/v1/github-tokens/{id} replaces the secret,
re-validates against GitHub (GET /user) like create, and refreshes the
stored scopes. id + name are unchanged, so linked projects keep working
with no re-binding. UI: rotate dialog per token row in TokensTab.
2. Re-point an existing external project — PUT /api/v1/projects/{hash}/git-repo/token
changes git_repos.token_id (null detaches → public). Validates the token id
exists (422 otherwise); webhook left intact. UI: token dropdown in the
project's SyncSettingsCard.
Adds githubtokens.Update() + gitrepos.SetTokenID() service methods, regenerated
openapi.gen.go, and tests: service round-trip, HTTP rotate (refresh scopes /
invalid / not-found), project token attach-detach-422-404, and admin-gating 403.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PUT /api/v1/github-tokens/{id} and PUT /api/v1/projects/{hash}/git-repo/token
gate on mustBeAdmin, which returns 403 for an authenticated non-admin — but the
spec only listed 401. Add the shared Forbidden (403) response to both, between
401 and 404. openapi.gen.go regenerated via make openapi-gen (only the embedded
spec blob changes; no types/interface/route changes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
What
Two admin-only capabilities for GitHub Integration:
PUT /api/v1/github-tokens/{id}replaces a stored PAT's secret, re-validating against GitHub and refreshing scopes. Theid/namestay the same, so every external project bound to the token keeps working with no re-binding.PUT /api/v1/projects/{hash}/git-repo/tokenchanges which stored PAT a project uses (nulldetaches → public), via a token dropdown on the project's settings page.Why
An expired GitHub PAT silently broke every external project bound to it. Tokens were immutable — the only "fix" was delete + recreate, which mints a new id, leaving all linked projects pointing at the old one (clone/fetch/webhook ops fail). And a project's
token_idwas set once at creation with no way to change it afterward. These two features close both gaps.How
githubtokens.Update()(re-encrypt + refresh scopes, id/name preserved) andgitrepos.SetTokenID()(nullableString→ public when empty; webhook left intact).UpdateGithubToken(re-runs the sameValidateTokenflow as create) andUpdateProjectGitRepoToken(validates the token id exists → 422 otherwise, 404 for local projects). Both gate onmustBeAdminfirst.doc/openapi.yaml;openapi.gen.goregenerated viamake openapi-gen(confirmed idempotent).TokensTab, token<Select>+useUpdateProjectTokeninSyncSettingsCard;generated.tsregenerated, dashboard rebuilt.docs/AUTH_REVIEW.mdmatrix updated (both new rows are Admin).Type of change
Checklist
go test ./...pass (service round-trip, rotate refresh-scopes/invalid/not-found, project token attach-detach-422-404, admin-gating 403)go vetclean; dashboardtsctypecheck + lint +vite buildpass🤖 Generated with Claude Code