mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 18:50:42 +00:00
* Harden OAuth refresh and Codex CLI bootstrap flows - Treat near-expiry OAuth credentials as unusable for bootstrap and refresh - Add clearer timeout and callback validation handling for OpenAI Codex OAuth - Tighten file lock retry behavior for stale OAuth refresh contention * fix(auth): address PR review threads * fix(auth): adopt fresher imported refresh tokens * test(auth): align oauth expiry fixtures with refresh margin * fix(auth): tighten Codex OAuth bootstrap and local fallback * Keep explicit local auth over CLI bootstrap - Preserve existing non-OAuth local profiles during external CLI OAuth sync - Add regression coverage for OpenAI Codex and generic external OAuth overlays * fix(auth): distinguish oauth lock timeout sources * fix(auth): reject cross-account external oauth bootstrap * fix(auth): narrow refresh contention classification
66 lines
3.1 KiB
TypeScript
66 lines
3.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { OAUTH_REFRESH_CALL_TIMEOUT_MS, OAUTH_REFRESH_LOCK_OPTIONS } from "./constants.js";
|
|
|
|
function computeMinimumRetryBudgetMs(): number {
|
|
let total = 0;
|
|
for (let attempt = 0; attempt < OAUTH_REFRESH_LOCK_OPTIONS.retries.retries; attempt += 1) {
|
|
total += Math.min(
|
|
OAUTH_REFRESH_LOCK_OPTIONS.retries.maxTimeout,
|
|
Math.max(
|
|
OAUTH_REFRESH_LOCK_OPTIONS.retries.minTimeout,
|
|
OAUTH_REFRESH_LOCK_OPTIONS.retries.minTimeout *
|
|
OAUTH_REFRESH_LOCK_OPTIONS.retries.factor ** attempt,
|
|
),
|
|
);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
// Invariant tests for the two constants that together bound the OAuth
|
|
// refresh critical section. Behavioural tests for the inner `setTimeout`
|
|
// mechanics are deliberately omitted: the implementation is a thin
|
|
// `Promise.race` around `setTimeout`, and exercising it end-to-end requires
|
|
// stepping through nested file-lock I/O that mixes awkwardly with Vitest
|
|
// fake timers. A regression in the timeout wiring would be caught by the
|
|
// #26322 regression test (oauth.concurrent-20-agents.test.ts) because a
|
|
// stuck refresh would time out the whole suite.
|
|
|
|
describe("OAuth refresh call timeout (invariants)", () => {
|
|
it("OAUTH_REFRESH_CALL_TIMEOUT_MS is strictly below OAUTH_REFRESH_LOCK_OPTIONS.stale", () => {
|
|
// The whole point of the two constants: the refresh call must always
|
|
// finish (or time out) before peers would consider the lock reclaimable.
|
|
// If this invariant ever regresses, the #26322 race can come back.
|
|
expect(OAUTH_REFRESH_CALL_TIMEOUT_MS).toBeLessThan(OAUTH_REFRESH_LOCK_OPTIONS.stale);
|
|
});
|
|
|
|
it("OAUTH_REFRESH_CALL_TIMEOUT_MS has a reasonable floor for OAuth token exchanges", () => {
|
|
// 30s is a sane lower bound: typical OAuth refresh RTT is <5s, but a
|
|
// cold TCP/TLS handshake + plugin bootstrap can push into double-digit
|
|
// seconds. Anything below 30s would start false-positive aborting.
|
|
expect(OAUTH_REFRESH_CALL_TIMEOUT_MS).toBeGreaterThanOrEqual(30_000);
|
|
});
|
|
|
|
it("OAUTH_REFRESH_LOCK_OPTIONS.stale leaves a generous safety margin beyond the call timeout", () => {
|
|
// Require at least 30s of headroom between the refresh deadline and
|
|
// the stale threshold: enough to cover normal scheduling jitter and
|
|
// the file-lock release round-trip without letting peers reclaim a
|
|
// still-active lock.
|
|
expect(OAUTH_REFRESH_LOCK_OPTIONS.stale - OAUTH_REFRESH_CALL_TIMEOUT_MS).toBeGreaterThanOrEqual(
|
|
30_000,
|
|
);
|
|
});
|
|
|
|
it("OAUTH_REFRESH_LOCK_OPTIONS.stale is well above the slow-refresh ceiling", () => {
|
|
// Sanity check: the stale window must clearly exceed a plausible slow-
|
|
// refresh ceiling (60s) so waiting agents never prematurely reclaim a
|
|
// lock during a legitimate slow-but-successful refresh.
|
|
expect(OAUTH_REFRESH_LOCK_OPTIONS.stale).toBeGreaterThan(60_000);
|
|
});
|
|
|
|
it("OAUTH_REFRESH_LOCK_OPTIONS retry budget outlasts the refresh call timeout", () => {
|
|
// Waiters should not exhaust their retry budget while a legitimate slow
|
|
// refresh is still within its allowed runtime budget.
|
|
expect(computeMinimumRetryBudgetMs()).toBeGreaterThan(OAUTH_REFRESH_CALL_TIMEOUT_MS);
|
|
});
|
|
});
|