fix(oauth): clamp grok-cli expired-token expiresIn to a positive value (#5775 follow-up) (#5820)

An already-expired grok-cli token (real expires_at/exp in the past) produced a
negative expiresIn, which is truthy in the import-token route and maps to a PAST
expiresAt — AutoCombo then reads that as 'already expired' and excludes the
connection instead of refreshing it. Clamp with Math.max(1, expiresIn) so an
expired token is treated as due-for-refresh. Extends #5775 (thanks @Chewji9875).

Regression: 2 new cases in tests/unit/grok-cli-oauth.test.ts (expired JWT exp +
expired JSON expires_at), both failing-then-passing.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-01 09:06:07 -03:00
committed by GitHub
parent 057ca116db
commit 7d07be9b20
3 changed files with 37 additions and 1 deletions

View File

@@ -34,7 +34,7 @@
- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Regression guard: `tests/unit/model-aliases-settings-route-selfheal.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. Regression guards: 3 new cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, and the fallback). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875))
- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649))

View File

@@ -131,6 +131,13 @@ export const grokCli = {
expiresIn = exp - currentSec;
}
// #5775 follow-up: guard against an already-expired token yielding a negative
// expiresIn. A negative value is truthy downstream (import-token route) and maps
// to a PAST expiresAt, which AutoCombo reads as "already expired" and excludes the
// connection instead of refreshing it. Clamp to a tiny positive TTL so the token is
// treated as due-for-refresh.
expiresIn = Math.max(1, expiresIn);
return {
accessToken,
refreshToken,

View File

@@ -152,3 +152,32 @@ test("Grok Build OAuth Provider - mapTokens falls back to 21600 if no exp or exp
assert.equal(result.expiresIn, 21600);
});
// #5775 follow-up: an already-expired token must NOT produce a negative expiresIn.
// A negative value is truthy in the import-token route (route.ts), yielding a PAST
// expiresAt that AutoCombo (virtualFactory.ts) reads as "already expired" and excludes
// the connection immediately — instead of clamping to a tiny positive TTL so the token
// is treated as due-for-refresh. Clamp with Math.max(1, …).
test("Grok Build OAuth Provider - mapTokens clamps expired JWT exp to a positive expiresIn", () => {
const pastSec = Math.floor(Date.now() / 1000) - 3600; // expired 1h ago
const payload = { sub: "12345", email: "test@example.com", exp: pastSec };
const payloadBase64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
const mockJwt = `eyJhbGciOiJFUzI1NiJ9.${payloadBase64}.signature`;
const result = grokCli.mapTokens(mockJwt, null);
assert.ok(result.expiresIn >= 1, `expected expiresIn >= 1, got ${result.expiresIn}`);
});
test("Grok Build OAuth Provider - mapTokens clamps expired JSON expires_at to a positive expiresIn", () => {
const pastDateStr = new Date(Date.now() - 3600 * 1000).toISOString(); // expired 1h ago
const authJson = {
"https://auth.x.ai::clientId": {
key: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20ifQ.signature",
refresh_token: "test-refresh-token",
expires_at: pastDateStr,
},
};
const result = grokCli.mapTokens(authJson, null);
assert.ok(result.expiresIn >= 1, `expected expiresIn >= 1, got ${result.expiresIn}`);
});