refactor(auth): centralize OAuth identity matching

This commit is contained in:
Vincent Koc
2026-06-23 01:40:54 +08:00
parent 65adb13581
commit 305a44388b
2 changed files with 10 additions and 51 deletions
+2 -27
View File
@@ -16,6 +16,7 @@ import {
OPENAI_CODEX_DEFAULT_PROFILE_ID,
} from "./constants.js";
import { log } from "./constants.js";
import { isSafeToCopyOAuthIdentity } from "./oauth-identity.js";
import {
areOAuthCredentialsEquivalent,
hasUsableOAuthCredential,
@@ -61,15 +62,6 @@ type ExternalCliSyncProvider = {
bootstrapOnly?: boolean;
};
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();
}
// Keep this gate aligned with the canonical identity-copy rule in oauth.ts.
/** Return true when imported CLI credentials match an existing profile identity. */
export function isSafeToUseExternalCliCredential(
@@ -82,24 +74,7 @@ export function isSafeToUseExternalCliCredential(
if (existing.provider !== imported.provider) {
return false;
}
const existingAccountId = normalizeAuthIdentityToken(existing.accountId);
const importedAccountId = normalizeAuthIdentityToken(imported.accountId);
const existingEmail = normalizeAuthEmailToken(existing.email);
const importedEmail = normalizeAuthEmailToken(imported.email);
if (existingAccountId !== undefined && importedAccountId !== undefined) {
return existingAccountId === importedAccountId;
}
if (existingEmail !== undefined && importedEmail !== undefined) {
return existingEmail === importedEmail;
}
const existingHasIdentity = existingAccountId !== undefined || existingEmail !== undefined;
if (existingHasIdentity) {
return false;
}
return true;
return isSafeToCopyOAuthIdentity(existing, imported);
}
const EXTERNAL_CLI_SYNC_PROVIDERS: ExternalCliSyncProvider[] = [
+8 -24
View File
@@ -6,8 +6,15 @@
import { asDateTimestampMs } from "../../shared/number-coercion.js";
import { cloneAuthProfileStore } from "./clone.js";
import { hasUsableOAuthCredential as hasUsableStoredOAuthCredential } from "./credential-state.js";
import {
isSafeToCopyOAuthIdentity,
normalizeAuthEmailToken,
normalizeAuthIdentityToken,
} from "./oauth-identity.js";
import type { AuthProfileStore, OAuthCredential } from "./types.js";
export { normalizeAuthEmailToken, normalizeAuthIdentityToken } from "./oauth-identity.js";
/** OAuth profile imported from a runtime external CLI source. */
export type RuntimeExternalOAuthProfile = {
profileId: string;
@@ -74,17 +81,6 @@ export function hasUsableOAuthCredential(
return hasUsableStoredOAuthCredential(credential, { now });
}
/** Normalizes account identity tokens for equality checks. */
export function normalizeAuthIdentityToken(value: string | undefined): string | undefined {
const trimmed = value?.trim();
return trimmed ? trimmed : undefined;
}
/** Normalizes auth email identity tokens for equality checks. */
export function normalizeAuthEmailToken(value: string | undefined): string | undefined {
return normalizeAuthIdentityToken(value)?.toLowerCase();
}
/** Returns true when an OAuth credential has account or email identity. */
export function hasOAuthIdentity(
credential: Pick<OAuthCredential, "accountId" | "email">,
@@ -100,19 +96,7 @@ export 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;
return hasOAuthIdentity(existing) && isSafeToCopyOAuthIdentity(existing, incoming);
}
// Different adoption paths have different safety thresholds. Bootstrap can