refactor(auth): break oauth helper import cycle

This commit is contained in:
Vincent Koc
2026-04-18 07:59:10 -07:00
committed by Peter Steinberger
parent 20debfab90
commit f6921fd733
5 changed files with 204 additions and 180 deletions

View File

@@ -5,7 +5,7 @@ import {
overlayRuntimeExternalOAuthProfiles,
shouldPersistRuntimeExternalOAuthProfile,
type RuntimeExternalOAuthProfile,
} from "./oauth-manager.js";
} from "./oauth-shared.js";
import type { AuthProfileStore, OAuthCredential } from "./types.js";
type ExternalAuthProfileMap = Map<string, ProviderExternalAuthProfile>;

View File

@@ -8,7 +8,7 @@ import {
isSafeToOverwriteStoredOAuthIdentity,
shouldBootstrapFromExternalCliCredential,
shouldReplaceStoredOAuthCredential,
} from "./oauth-manager.js";
} from "./oauth-shared.js";
import type { AuthProfileStore, OAuthCredential } from "./types.js";
export {
@@ -18,7 +18,7 @@ export {
isSafeToOverwriteStoredOAuthIdentity,
shouldBootstrapFromExternalCliCredential,
shouldReplaceStoredOAuthCredential,
} from "./oauth-manager.js";
} from "./oauth-shared.js";
export type ExternalCliResolvedProfile = {
profileId: string;

View File

@@ -124,7 +124,7 @@ describe("auth external oauth helpers", () => {
expect(shouldPersist).toBe(true);
});
it("overlays external CLI OAuth only when the stored credential is no longer usable", () => {
it("does not use Codex CLI OAuth as a runtime overlay source", () => {
readCodexCliCredentialsCachedMock.mockReturnValue(
createCredential({
access: "fresh-cli-access-token",
@@ -146,9 +146,9 @@ describe("auth external oauth helpers", () => {
);
expect(overlaid.profiles["openai-codex:default"]).toMatchObject({
access: "fresh-cli-access-token",
refresh: "fresh-cli-refresh-token",
expires: expect.any(Number),
access: "stale-store-access-token",
refresh: "stale-store-refresh-token",
accountId: "acct-cli",
});
});

View File

@@ -6,7 +6,17 @@ import {
OAUTH_REFRESH_LOCK_OPTIONS,
log,
} from "./constants.js";
import { hasUsableOAuthCredential as hasUsableStoredOAuthCredential } from "./credential-state.js";
import {
areOAuthCredentialsEquivalent,
hasUsableOAuthCredential,
isSafeToAdoptBootstrapOAuthIdentity,
isSafeToOverwriteStoredOAuthIdentity,
overlayRuntimeExternalOAuthProfiles,
shouldBootstrapFromExternalCliCredential,
shouldPersistRuntimeExternalOAuthProfile,
shouldReplaceStoredOAuthCredential,
type RuntimeExternalOAuthProfile,
} from "./oauth-shared.js";
import { ensureAuthStoreFile, resolveAuthStorePath, resolveOAuthRefreshLockPath } from "./paths.js";
import {
ensureAuthProfileStore,
@@ -76,179 +86,17 @@ export class OAuthManagerRefreshError extends Error {
}
}
export type RuntimeExternalOAuthProfile = {
profileId: string;
credential: OAuthCredential;
persistence?: "runtime-only" | "persisted";
export {
areOAuthCredentialsEquivalent,
hasUsableOAuthCredential,
isSafeToAdoptBootstrapOAuthIdentity,
isSafeToOverwriteStoredOAuthIdentity,
overlayRuntimeExternalOAuthProfiles,
shouldBootstrapFromExternalCliCredential,
shouldPersistRuntimeExternalOAuthProfile,
shouldReplaceStoredOAuthCredential,
};
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 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 hasUsableStoredOAuthCredential(credential, { now });
}
function normalizeAuthIdentityToken(value: string | undefined): string | undefined {
const trimmed = value?.trim();
return trimmed ? trimmed : undefined;
}
function normalizeAuthEmailToken(value: string | undefined): string | undefined {
return normalizeAuthIdentityToken(value)?.toLowerCase();
}
function hasOAuthIdentity(credential: Pick<OAuthCredential, "accountId" | "email">): boolean {
return (
normalizeAuthIdentityToken(credential.accountId) !== undefined ||
normalizeAuthEmailToken(credential.email) !== undefined
);
}
function hasMatchingOAuthIdentity(
existing: Pick<OAuthCredential, "accountId" | "email">,
incoming: Pick<OAuthCredential, "accountId" | "email">,
): boolean {
const existingAccountId = normalizeAuthIdentityToken(existing.accountId);
const incomingAccountId = normalizeAuthIdentityToken(incoming.accountId);
if (existingAccountId !== undefined && incomingAccountId !== undefined) {
return existingAccountId === incomingAccountId;
}
const existingEmail = normalizeAuthEmailToken(existing.email);
const incomingEmail = normalizeAuthEmailToken(incoming.email);
if (existingEmail !== undefined && incomingEmail !== undefined) {
return existingEmail === incomingEmail;
}
return false;
}
export function isSafeToOverwriteStoredOAuthIdentity(
existing: OAuthCredential | undefined,
incoming: OAuthCredential,
): boolean {
if (!existing || existing.type !== "oauth") {
return true;
}
if (existing.provider !== incoming.provider) {
return false;
}
if (areOAuthCredentialsEquivalent(existing, incoming)) {
return true;
}
if (!hasOAuthIdentity(existing)) {
return false;
}
return hasMatchingOAuthIdentity(existing, incoming);
}
export function isSafeToAdoptBootstrapOAuthIdentity(
existing: OAuthCredential | undefined,
incoming: OAuthCredential,
): boolean {
if (!existing || existing.type !== "oauth") {
return true;
}
if (existing.provider !== incoming.provider) {
return false;
}
if (areOAuthCredentialsEquivalent(existing, incoming)) {
return true;
}
if (!hasOAuthIdentity(existing)) {
return true;
}
return hasMatchingOAuthIdentity(existing, incoming);
}
export function shouldBootstrapFromExternalCliCredential(params: {
existing: OAuthCredential | undefined;
imported: OAuthCredential;
now?: number;
}): boolean {
const now = params.now ?? Date.now();
if (hasUsableOAuthCredential(params.existing, now)) {
return false;
}
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;
}
export type { RuntimeExternalOAuthProfile };
function hasOAuthCredentialChanged(
previous: Pick<OAuthCredential, "access" | "refresh" | "expires">,

View File

@@ -0,0 +1,176 @@
import { hasUsableOAuthCredential as hasUsableStoredOAuthCredential } from "./credential-state.js";
import type { AuthProfileStore, OAuthCredential } from "./types.js";
export type RuntimeExternalOAuthProfile = {
profileId: string;
credential: OAuthCredential;
persistence?: "runtime-only" | "persisted";
};
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 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 hasUsableStoredOAuthCredential(credential, { now });
}
function normalizeAuthIdentityToken(value: string | undefined): string | undefined {
const trimmed = value?.trim();
return trimmed ? trimmed : undefined;
}
function normalizeAuthEmailToken(value: string | undefined): string | undefined {
return normalizeAuthIdentityToken(value)?.toLowerCase();
}
function hasOAuthIdentity(credential: Pick<OAuthCredential, "accountId" | "email">): boolean {
return (
normalizeAuthIdentityToken(credential.accountId) !== undefined ||
normalizeAuthEmailToken(credential.email) !== undefined
);
}
function hasMatchingOAuthIdentity(
existing: Pick<OAuthCredential, "accountId" | "email">,
incoming: Pick<OAuthCredential, "accountId" | "email">,
): boolean {
const existingAccountId = normalizeAuthIdentityToken(existing.accountId);
const incomingAccountId = normalizeAuthIdentityToken(incoming.accountId);
if (existingAccountId !== undefined && incomingAccountId !== undefined) {
return existingAccountId === incomingAccountId;
}
const existingEmail = normalizeAuthEmailToken(existing.email);
const incomingEmail = normalizeAuthEmailToken(incoming.email);
if (existingEmail !== undefined && incomingEmail !== undefined) {
return existingEmail === incomingEmail;
}
return false;
}
export function isSafeToOverwriteStoredOAuthIdentity(
existing: OAuthCredential | undefined,
incoming: OAuthCredential,
): boolean {
if (!existing || existing.type !== "oauth") {
return true;
}
if (existing.provider !== incoming.provider) {
return false;
}
if (areOAuthCredentialsEquivalent(existing, incoming)) {
return true;
}
if (!hasOAuthIdentity(existing)) {
return false;
}
return hasMatchingOAuthIdentity(existing, incoming);
}
export function isSafeToAdoptBootstrapOAuthIdentity(
existing: OAuthCredential | undefined,
incoming: OAuthCredential,
): boolean {
if (!existing || existing.type !== "oauth") {
return true;
}
if (existing.provider !== incoming.provider) {
return false;
}
if (areOAuthCredentialsEquivalent(existing, incoming)) {
return true;
}
if (!hasOAuthIdentity(existing)) {
return true;
}
return hasMatchingOAuthIdentity(existing, incoming);
}
export function shouldBootstrapFromExternalCliCredential(params: {
existing: OAuthCredential | undefined;
imported: OAuthCredential;
now?: number;
}): boolean {
const now = params.now ?? Date.now();
if (hasUsableOAuthCredential(params.existing, now)) {
return false;
}
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;
}