refactor(auth): reuse shared oauth policy helpers

This commit is contained in:
Vincent Koc
2026-04-17 15:06:49 -07:00
committed by Peter Steinberger
parent 5f2e77a6e1
commit 6f450c2d1f
4 changed files with 82 additions and 102 deletions

View File

@@ -1,4 +1,4 @@
import { readExternalCliBootstrapCredential } from "./external-cli-sync.js";
import { readManagedExternalCliCredential } from "./external-cli-sync.js";
import { resolveEffectiveOAuthCredential as resolveManagedOAuthCredential } from "./oauth-manager.js";
import type { OAuthCredential } from "./types.js";
@@ -10,7 +10,7 @@ export function resolveEffectiveOAuthCredential(params: {
profileId: params.profileId,
credential: params.credential,
readBootstrapCredential: ({ profileId, credential }) =>
readExternalCliBootstrapCredential({
readManagedExternalCliCredential({
profileId,
credential,
}),

View File

@@ -1,6 +1,11 @@
import type { ProviderExternalAuthProfile } from "../../plugins/provider-external-auth.types.js";
import { resolveExternalAuthProfilesWithPlugins } from "../../plugins/provider-runtime.js";
import * as externalCliSync from "./external-cli-sync.js";
import {
overlayRuntimeExternalOAuthProfiles,
shouldPersistRuntimeExternalOAuthProfile,
type RuntimeExternalOAuthProfile,
} from "./oauth-manager.js";
import type { AuthProfileStore, OAuthCredential } from "./types.js";
type ExternalAuthProfileMap = Map<string, ProviderExternalAuthProfile>;
@@ -67,19 +72,17 @@ function resolveExternalAuthProfileMap(params: {
return resolved;
}
function oauthCredentialMatches(a: OAuthCredential, b: OAuthCredential): boolean {
return (
a.type === b.type &&
a.provider === b.provider &&
a.access === b.access &&
a.refresh === b.refresh &&
a.expires === b.expires &&
a.clientId === b.clientId &&
a.email === b.email &&
a.displayName === b.displayName &&
a.enterpriseUrl === b.enterpriseUrl &&
a.projectId === b.projectId &&
a.accountId === b.accountId
function listRuntimeExternalAuthProfiles(params: {
store: AuthProfileStore;
agentDir?: string;
env?: NodeJS.ProcessEnv;
}): RuntimeExternalOAuthProfile[] {
return Array.from(
resolveExternalAuthProfileMap({
store: params.store,
agentDir: params.agentDir,
env: params.env,
}).values(),
);
}
@@ -87,20 +90,12 @@ export function overlayExternalAuthProfiles(
store: AuthProfileStore,
params?: { agentDir?: string; env?: NodeJS.ProcessEnv },
): AuthProfileStore {
const profiles = resolveExternalAuthProfileMap({
const profiles = listRuntimeExternalAuthProfiles({
store,
agentDir: params?.agentDir,
env: params?.env,
});
if (profiles.size === 0) {
return store;
}
const next = structuredClone(store);
for (const [profileId, profile] of profiles) {
next.profiles[profileId] = profile.credential;
}
return next;
return overlayRuntimeExternalOAuthProfiles(store, profiles);
}
export function shouldPersistExternalAuthProfile(params: {
@@ -110,15 +105,16 @@ export function shouldPersistExternalAuthProfile(params: {
agentDir?: string;
env?: NodeJS.ProcessEnv;
}): boolean {
const external = resolveExternalAuthProfileMap({
const profiles = listRuntimeExternalAuthProfiles({
store: params.store,
agentDir: params.agentDir,
env: params.env,
}).get(params.profileId);
if (!external || external.persistence === "persisted") {
return true;
}
return !oauthCredentialMatches(external.credential, params.credential);
});
return shouldPersistRuntimeExternalOAuthProfile({
profileId: params.profileId,
credential: params.credential,
profiles,
});
}
// Compat aliases while file/function naming catches up.

View File

@@ -8,9 +8,21 @@ import {
OPENAI_CODEX_DEFAULT_PROFILE_ID,
} from "./constants.js";
import { log } from "./constants.js";
import { hasUsableOAuthCredential as hasUsableOAuthCredentialShared } from "./credential-state.js";
import {
areOAuthCredentialsEquivalent,
hasUsableOAuthCredential,
shouldBootstrapFromExternalCliCredential,
shouldReplaceStoredOAuthCredential,
} from "./oauth-manager.js";
import type { AuthProfileStore, OAuthCredential } from "./types.js";
export {
areOAuthCredentialsEquivalent,
hasUsableOAuthCredential,
shouldBootstrapFromExternalCliCredential,
shouldReplaceStoredOAuthCredential,
} from "./oauth-manager.js";
export type ExternalCliResolvedProfile = {
profileId: string;
credential: OAuthCredential;
@@ -22,25 +34,6 @@ type ExternalCliSyncProvider = {
readCredentials: () => OAuthCredential | null;
};
export function areOAuthCredentialsEquivalent(
a: OAuthCredential | undefined,
b: OAuthCredential,
): boolean {
if (!a || a.type !== "oauth") {
return false;
}
return (
a.provider === b.provider &&
a.access === b.access &&
a.refresh === b.refresh &&
a.expires === b.expires &&
a.email === b.email &&
a.enterpriseUrl === b.enterpriseUrl &&
a.projectId === b.projectId &&
a.accountId === b.accountId
);
}
function normalizeAuthIdentityToken(value: string | undefined): string | undefined {
const trimmed = value?.trim();
return trimmed ? trimmed : undefined;
@@ -81,53 +74,6 @@ export function isSafeToUseExternalCliCredential(
return true;
}
function hasNewerStoredOAuthCredential(
existing: OAuthCredential | undefined,
incoming: OAuthCredential,
): boolean {
return Boolean(
existing &&
existing.provider === incoming.provider &&
Number.isFinite(existing.expires) &&
(!Number.isFinite(incoming.expires) || existing.expires > incoming.expires),
);
}
export function shouldReplaceStoredOAuthCredential(
existing: OAuthCredential | undefined,
incoming: OAuthCredential,
): boolean {
if (!existing || existing.type !== "oauth") {
return true;
}
if (areOAuthCredentialsEquivalent(existing, incoming)) {
return false;
}
return !hasNewerStoredOAuthCredential(existing, incoming);
}
export function hasUsableOAuthCredential(
credential: OAuthCredential | undefined,
now = Date.now(),
): boolean {
return hasUsableOAuthCredentialShared(credential, { now });
}
export function shouldBootstrapFromExternalCliCredential(params: {
existing: OAuthCredential | undefined;
imported: OAuthCredential;
now?: number;
}): boolean {
const now = params.now ?? Date.now();
if (!isSafeToUseExternalCliCredential(params.existing, params.imported)) {
return false;
}
if (hasUsableOAuthCredential(params.existing, now)) {
return false;
}
return hasUsableOAuthCredential(params.imported, now);
}
const EXTERNAL_CLI_SYNC_PROVIDERS: ExternalCliSyncProvider[] = [
{
profileId: MINIMAX_CLI_PROFILE_ID,

View File

@@ -59,7 +59,13 @@ export class OAuthManagerRefreshError extends Error {
}
}
function areOAuthCredentialsEquivalent(
export type RuntimeExternalOAuthProfile = {
profileId: string;
credential: OAuthCredential;
persistence?: "runtime-only" | "persisted";
};
export function areOAuthCredentialsEquivalent(
a: OAuthCredential | undefined,
b: OAuthCredential,
): boolean {
@@ -90,7 +96,7 @@ function hasNewerStoredOAuthCredential(
);
}
function shouldReplaceStoredOAuthCredential(
export function shouldReplaceStoredOAuthCredential(
existing: OAuthCredential | undefined,
incoming: OAuthCredential,
): boolean {
@@ -103,7 +109,7 @@ function shouldReplaceStoredOAuthCredential(
return !hasNewerStoredOAuthCredential(existing, incoming);
}
function hasUsableOAuthCredential(
export function hasUsableOAuthCredential(
credential: OAuthCredential | undefined,
now = Date.now(),
): boolean {
@@ -116,7 +122,7 @@ function hasUsableOAuthCredential(
return resolveTokenExpiryState(credential.expires, now) === "valid";
}
function shouldBootstrapFromExternalCliCredential(params: {
export function shouldBootstrapFromExternalCliCredential(params: {
existing: OAuthCredential | undefined;
imported: OAuthCredential;
now?: number;
@@ -128,6 +134,38 @@ function shouldBootstrapFromExternalCliCredential(params: {
return hasUsableOAuthCredential(params.imported, now);
}
export function overlayRuntimeExternalOAuthProfiles(
store: AuthProfileStore,
profiles: Iterable<RuntimeExternalOAuthProfile>,
): AuthProfileStore {
const externalProfiles = Array.from(profiles);
if (externalProfiles.length === 0) {
return store;
}
const next = structuredClone(store);
for (const profile of externalProfiles) {
next.profiles[profile.profileId] = profile.credential;
}
return next;
}
export function shouldPersistRuntimeExternalOAuthProfile(params: {
profileId: string;
credential: OAuthCredential;
profiles: Iterable<RuntimeExternalOAuthProfile>;
}): boolean {
for (const profile of params.profiles) {
if (profile.profileId !== params.profileId) {
continue;
}
if (profile.persistence === "persisted") {
return true;
}
return !areOAuthCredentialsEquivalent(profile.credential, params.credential);
}
return true;
}
function hasOAuthCredentialChanged(
previous: Pick<OAuthCredential, "access" | "refresh" | "expires">,
current: Pick<OAuthCredential, "access" | "refresh" | "expires">,