mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 17:24:05 +00:00
fix(agents): bound auth health expiry
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { MAX_DATE_TIMESTAMP_MS } from "../shared/number-coercion.js";
|
||||
import type { OAuthCredential } from "./auth-profiles/types.js";
|
||||
|
||||
const { readCodexCliCredentialsCachedMock } = vi.hoisted(() => ({
|
||||
@@ -376,6 +377,35 @@ describe("buildAuthHealthSummary", () => {
|
||||
expect(reasonCodes["github-copilot:invalid-expires"]).toBe("invalid_expires");
|
||||
});
|
||||
|
||||
it("does not expose out-of-range oauth expiry values in health rollups", () => {
|
||||
vi.spyOn(Date, "now").mockReturnValue(now);
|
||||
const store = {
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai-codex:bad-expiry": {
|
||||
type: "oauth" as const,
|
||||
provider: "openai-codex",
|
||||
access: "oauth-access",
|
||||
refresh: "oauth-refresh",
|
||||
expires: MAX_DATE_TIMESTAMP_MS + 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const summary = buildAuthHealthSummary({
|
||||
store,
|
||||
warnAfterMs: DEFAULT_OAUTH_WARN_MS,
|
||||
});
|
||||
|
||||
const profile = summary.profiles.find((entry) => entry.profileId === "openai-codex:bad-expiry");
|
||||
const provider = summary.providers.find((entry) => entry.provider === "openai-codex");
|
||||
|
||||
expect(profile?.status).toBe("missing");
|
||||
expect(profile?.expiresAt).toBeUndefined();
|
||||
expect(provider?.status).toBe("missing");
|
||||
expect(provider?.expiresAt).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not normalize provider aliases when filtering and grouping profile health", () => {
|
||||
vi.spyOn(Date, "now").mockReturnValue(now);
|
||||
const store = {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
normalizeProviderId,
|
||||
} from "@openclaw/model-catalog-core/provider-id";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { asDateTimestampMs } from "../shared/number-coercion.js";
|
||||
import { normalizeUniqueStringEntries } from "../shared/string-normalization.js";
|
||||
import {
|
||||
DEFAULT_OAUTH_REFRESH_MARGIN_MS,
|
||||
@@ -92,24 +93,25 @@ function resolveOAuthStatus(
|
||||
expiresAt: number | undefined,
|
||||
now: number,
|
||||
expiringWithinMs: number,
|
||||
): { status: AuthProfileHealthStatus; remainingMs?: number } {
|
||||
if (!expiresAt || !Number.isFinite(expiresAt) || expiresAt <= 0) {
|
||||
): { status: AuthProfileHealthStatus; expiresAt?: number; remainingMs?: number } {
|
||||
const normalizedExpiresAt = asDateTimestampMs(expiresAt);
|
||||
if (normalizedExpiresAt === undefined || normalizedExpiresAt <= 0) {
|
||||
return { status: "missing" };
|
||||
}
|
||||
const remainingMs = expiresAt - now;
|
||||
const expiryState = resolveTokenExpiryState(expiresAt, now, {
|
||||
const remainingMs = normalizedExpiresAt - now;
|
||||
const expiryState = resolveTokenExpiryState(normalizedExpiresAt, now, {
|
||||
expiringWithinMs,
|
||||
});
|
||||
if (expiryState === "invalid_expires" || expiryState === "missing") {
|
||||
return { status: "missing" };
|
||||
}
|
||||
if (expiryState === "expired") {
|
||||
return { status: "expired", remainingMs };
|
||||
return { status: "expired", expiresAt: normalizedExpiresAt, remainingMs };
|
||||
}
|
||||
if (expiryState === "expiring") {
|
||||
return { status: "expiring", remainingMs };
|
||||
return { status: "expiring", expiresAt: normalizedExpiresAt, remainingMs };
|
||||
}
|
||||
return { status: "ok", remainingMs };
|
||||
return { status: "ok", expiresAt: normalizedExpiresAt, remainingMs };
|
||||
}
|
||||
|
||||
function buildProfileHealth(params: {
|
||||
@@ -168,14 +170,18 @@ function buildProfileHealth(params: {
|
||||
label,
|
||||
};
|
||||
}
|
||||
const { status, remainingMs } = resolveOAuthStatus(expiresAt, now, warnAfterMs);
|
||||
const {
|
||||
status,
|
||||
expiresAt: normalizedExpiresAt,
|
||||
remainingMs,
|
||||
} = resolveOAuthStatus(expiresAt, now, warnAfterMs);
|
||||
return {
|
||||
profileId,
|
||||
provider,
|
||||
type: "token",
|
||||
status,
|
||||
reasonCode: status === "expired" ? "expired" : undefined,
|
||||
expiresAt,
|
||||
expiresAt: normalizedExpiresAt,
|
||||
remainingMs,
|
||||
source,
|
||||
label,
|
||||
@@ -187,17 +193,17 @@ function buildProfileHealth(params: {
|
||||
credential: healthCredential,
|
||||
});
|
||||
const oauthWarnAfterMs = Math.max(warnAfterMs, DEFAULT_OAUTH_REFRESH_MARGIN_MS);
|
||||
const { status: rawStatus, remainingMs } = resolveOAuthStatus(
|
||||
effectiveCredential.expires,
|
||||
now,
|
||||
oauthWarnAfterMs,
|
||||
);
|
||||
const {
|
||||
status: rawStatus,
|
||||
expiresAt,
|
||||
remainingMs,
|
||||
} = resolveOAuthStatus(effectiveCredential.expires, now, oauthWarnAfterMs);
|
||||
return {
|
||||
profileId,
|
||||
provider,
|
||||
type: "oauth",
|
||||
status: rawStatus,
|
||||
expiresAt: effectiveCredential.expires,
|
||||
expiresAt,
|
||||
remainingMs,
|
||||
source,
|
||||
label,
|
||||
@@ -317,7 +323,8 @@ export function buildAuthHealthSummary(params: {
|
||||
|
||||
let hasApiKeyProfile = false;
|
||||
let hasExpirableProfile = false;
|
||||
let hasExpiredOrMissing = false;
|
||||
let hasExpired = false;
|
||||
let hasMissing = false;
|
||||
let hasExpiring = false;
|
||||
let earliestExpiry: number | undefined;
|
||||
for (const profile of effectiveProfiles) {
|
||||
@@ -335,8 +342,10 @@ export function buildAuthHealthSummary(params: {
|
||||
? profile.expiresAt
|
||||
: Math.min(earliestExpiry, profile.expiresAt);
|
||||
}
|
||||
if (profile.status === "expired" || profile.status === "missing") {
|
||||
hasExpiredOrMissing = true;
|
||||
if (profile.status === "expired") {
|
||||
hasExpired = true;
|
||||
} else if (profile.status === "missing") {
|
||||
hasMissing = true;
|
||||
} else if (profile.status === "expiring") {
|
||||
hasExpiring = true;
|
||||
}
|
||||
@@ -352,8 +361,10 @@ export function buildAuthHealthSummary(params: {
|
||||
provider.remainingMs = provider.expiresAt - now;
|
||||
}
|
||||
|
||||
if (hasExpiredOrMissing) {
|
||||
if (hasExpired) {
|
||||
provider.status = "expired";
|
||||
} else if (hasMissing) {
|
||||
provider.status = "missing";
|
||||
} else if (hasExpiring) {
|
||||
provider.status = "expiring";
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user