Repository navigation
Re-authorize a credential in place - #2127
Merged
Merged
Conversation
A reconnect minted a new credential row, so everything that references
the old one by id (repositories, group hooks, the act-as identity)
pointed at a dead row until a succession subsystem moved it, with edge
cases for every shape of owner, instance and timing. Writing the new
token into the SAME row removes the cause: nothing moves, so nothing
can be left behind.
OAuth: InitCredentialOAuthCommand.ReplaceCredentialId is kept on the
pending state (migration 0250, nullable, additive) and the callback
hands the token to the new ICredentialReauthorizer instead of creating
a row. Pasted tokens: PUT /api/credentials/{id}/token does the same
write after probing the token at the provider, within the probe
deadline the health sweep keeps, so a provider that stops answering
costs the request that deadline.
The reauthorizer takes the lock a refresh takes (the in-process
semaphore and the advisory lock, extracted into CredentialRefreshLock
so there is one definition), re-reads the row under it, and writes the
payload, auth type, scopes, expiry and name in one compare-and-swap
that also sets the row Active, clears its last error and stamps
LastValidatedDate. A refresher can therefore neither rotate the pair
being replaced nor write its own over the new one, a pair this process
kept because it could not store a rotation of the replaced grant is
dropped under the same lock, and a Disconnect or a retirement that
lands mid-write is read again or refused, never undone. It then
revives the repositories and group hooks the dead token had broken,
relinks the owner's act-as identity, and after the commit asks the
provider to revoke the replaced tokens only, never the grant.
It leaves the row saying what is true of the new token, which is what
the credential health work reads: a credential its provider had refused
(Error) is Active again at once, a static token carries the expiry its
provider reports for the new token (and not the lapsed one's), so the
list derives its health from the new token without waiting for a sweep.
No row is created, so the connect-time rule that carries a refused
credential's bindings to a new one never fires. A verdict about the
replaced token that lands afterwards, from a sweep or a refresher, is a
compare-and-swap on a payload the row no longer holds and changes
nothing.
The sign-in must be the same provider account the credential was
connected as: the account its act-as identity names or, when a newer
connect re-pointed that one identity at another credential of the same
person, the account the person's identity speaks for on that instance
(another account is a new connection, refused with
credential_account_mismatch). A credential retired as its person left
or its connection was removed stays retired (credential_retired).
Connecting without ReplaceCredentialId behaves exactly as before. An
older pod that completes a pending row naming a credential ignores it
and creates a new row, as it always did.
The Providers modal gains Reconnect (OAuth) and Replace token, keeps a
credential the provider refused in its list so they stay reachable, and
says what the derived health says: needs reconnect, needs a new token,
or how many days a token has left. The Providers summary agrees with it.
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.
Summary
InitCredentialOAuthCommand.ReplaceCredentialIdis kept on the pending state (migration 0250, nullable and additive) and the callback hands the token to the newICredentialReauthorizerinstead of creating a row. Pasted tokens:PUT /api/credentials/{id}/tokenprobes the token at the provider, within the same probe deadline the health sweep keeps, then does the same write.CredentialRefreshLock), re-reads the row under it, and writes payload, auth type, scopes, expiry and name in one compare-and-swap that also sets the row Active, clears its last error and stampsLastValidatedDate.deleted_date IS NULLand the expected payload both guard the swap).health(active, or expiring withdaysToExpiry) without waiting for a sweep. No row is created, so the connect-time rule that carries a refused credential's bindings to a new one never fires. A sweep's or refresher's verdict about the replaced token that lands afterwards is a compare-and-swap on a payload the row no longer holds and changes nothing.credential_account_mismatch.credential_retired).ReplaceCredentialIdbehaves exactly as before.git revert; the column is nullable, has no foreign key, and is ignored by the previous build.healthanddaysToExpiryare optional on the client, so a new SPA on an older API still marksstatus: "Error"credentials.Test plan
CredentialReauthorizationFlowTests, 55): the id is kept, and repositories, hooks and identity come back intact; replaced tokens are revoked at token level only; a different account, a departed or retired credential are refused; concurrent refresh, another process of either build and racing reconnects are serialised; pre-0250 pending rows still complete.PUT /token, andGET /api/credentialsreadingrejectedbefore andactiveafter, with the new token's expiry.tsc --noEmit -p tsconfig.app.json, vitest 2613 passed (modal chips for refused, lapsed, expiring and team tokens; Settings summary), build.credential_account_mismatchvolume for a person who holds two different accounts on one instance. Those people are refused conservatively and connect the other account as its own credential.