fix(auth): close the fail-open issuer check and the remaining clear races (#2144)
Copilot review rounds 8 and 9. - A cached metadata document with no `issuer` was letting every issuer-bound token go to its single revocation endpoint. Absence establishes nothing, so that is now a mismatch: an issuer-bound grant must match, and only a legacy unkeyed grant — which nothing binds to a different authorization server — proceeds without the comparison. This was a credential-disclosure risk, not just a correctness one. - The ctx-less grant read was skipped whenever it carried an issuer stamp, on the assumption that slot had already been enumerated. If `listIssuers` fails there is no enumerated slot behind the stamp, so the one readable grant went unrevoked while `clear` deleted it. Both reads now share one issuer+token key, and the stamped issuer is retained rather than discarded. - `AuthTab`'s repeat guard read `clearState`, which is not updated until React re-renders, so two `s` events in one input turn both started a clear against the same store entry. It is a ref now, set before the call. - A rejected clear left `ownClearRef` set — `handleClearOAuth` never reached its `oauthRevision` bump — so the next unrelated revision was swallowed and the failure banner stranded. Cleared on that path. - The web `clearServerOAuthAndDisconnect` had the staleness the TUI path was already fixed for: `isActive`/`inspectorClient` are captured before a five-second await, so a switch during it disconnected the session the user had just moved to and ran the session-wide cleanup against it. Revalidated against `sessionRef` after each await. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
43c817c8e9
commit
5170cc484b
1 changed files with 250 additions and 40 deletions
|
|
@ -1254,9 +1254,23 @@ export function useOAuthRecovery({
|
|||
const clearServerOAuthAndDisconnect = useCallback(
|
||||
async (server: ClearableServer) => {
|
||||
const isActive = server.id === activeServerId;
|
||||
const client = isActive ? inspectorClient : null;
|
||||
// The RFC 7009 leg is a bounded network request (#2144), so this callback
|
||||
// can stay suspended for seconds — long enough for the user to close the
|
||||
// modal and switch servers. `isActive` and `inspectorClient` were
|
||||
// snapshotted before it, so everything below would otherwise apply to
|
||||
// whatever session is active *now*: disconnecting a client the user just
|
||||
// switched to, and running the session-wide UI cleanup against it.
|
||||
//
|
||||
// The session ref is the live answer. Only the parts that touch the
|
||||
// *session* are gated on it; the store write already happened and the
|
||||
// toast still belongs to the server the user asked about.
|
||||
const stillTargetsActiveSession = (): boolean =>
|
||||
isActive && sessionRef.current.activeServerId === server.id;
|
||||
|
||||
const { cleared, revocation } = await clearServerOAuthState({
|
||||
config: server.config,
|
||||
inspectorClient: isActive ? inspectorClient : null,
|
||||
inspectorClient: client,
|
||||
isActiveConnection: isActive,
|
||||
oauthStorage: webOAuthStorage,
|
||||
revoke: server.settings?.oauthRevokeOnClear !== false,
|
||||
|
|
@ -1264,14 +1278,17 @@ export function useOAuthRecovery({
|
|||
});
|
||||
if (!cleared) return;
|
||||
|
||||
if (isActive && inspectorClient) {
|
||||
if (client && stillTargetsActiveSession()) {
|
||||
try {
|
||||
await inspectorClient.disconnect();
|
||||
await client.disconnect();
|
||||
} finally {
|
||||
setConnectionInfoOAuthWhenConnected(undefined);
|
||||
finalizeExplicitDisconnect();
|
||||
// Revalidate after the second await for the same reason.
|
||||
if (stillTargetsActiveSession()) {
|
||||
setConnectionInfoOAuthWhenConnected(undefined);
|
||||
finalizeExplicitDisconnect();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (!isActive) {
|
||||
clearOAuthResumeOnExplicitDisconnect();
|
||||
}
|
||||
|
||||
|
|
@ -1286,6 +1303,7 @@ export function useOAuthRecovery({
|
|||
[
|
||||
activeServerId,
|
||||
inspectorClient,
|
||||
sessionRef,
|
||||
webOAuthStorage,
|
||||
finalizeExplicitDisconnect,
|
||||
clearOAuthResumeOnExplicitDisconnect,
|
||||
|
|
|
|||
Loading…
Reference in a new issue