Download src/agents/auth-profiles/external-cli-sync.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 14.4 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/auth-profiles/external-cli-sync.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/agents/auth-profiles/external-cli-sync.ts
-
curl -L -o external-cli-sync.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/auth-profiles/external-cli-sync.ts
14.4 kB
| /** | |
| * External CLI OAuth synchronization. | |
| * Reads supported CLI credential stores, decides whether those credentials can | |
| * safely bootstrap local auth profiles, and returns runtime/persisted overlays. | |
| */ | |
| import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; | |
| import { readMiniMaxCliCredentialsCached } from "../cli-credentials.js"; | |
| import { EXTERNAL_CLI_SYNC_TTL_MS, MINIMAX_CLI_PROFILE_ID, authProfilesLog } from "./constants.js"; | |
| import { hasUsableOAuthCredential } from "./credential-state.js"; | |
| import { isSafeToCopyOAuthIdentity } from "./oauth-identity.js"; | |
| import { isOAuthRefreshFence } from "./oauth-refresh-marker.js"; | |
| import { | |
| areOAuthCredentialsEquivalent, | |
| isSafeToAdoptBootstrapOAuthIdentity, | |
| shouldBootstrapFromExternalCliCredential, | |
| } from "./oauth-shared.js"; | |
| import type { AuthProfileStore, OAuthCredential } from "./types.js"; | |
| type ExternalCliResolvedProfile = { | |
| profileId: string; | |
| credential: OAuthCredential; | |
| persistence?: "runtime-only" | "persisted"; | |
| }; | |
| type ExternalCliAuthProfileOptions = { | |
| allowKeychainPrompt?: boolean; | |
| providerIds?: Iterable<string>; | |
| profileIds?: Iterable<string>; | |
| }; | |
| type ExternalCliSyncProvider = { | |
| profileId: string; | |
| profileAliases?: readonly string[]; | |
| provider: string; | |
| aliases?: readonly string[]; | |
| readCredentials: ( | |
| options?: Pick<ExternalCliAuthProfileOptions, "allowKeychainPrompt">, | |
| ) => OAuthCredential | null; | |
| persistence?: ExternalCliResolvedProfile["persistence"]; | |
| }; | |
| const PERSISTED_EXTERNAL_CLI_AUTH_FLOW = "external-cli"; | |
| // External CLI bootstrap must never replace a local profile with another identity. | |
| /** Return true when imported CLI credentials match an existing profile identity. */ | |
| function isSafeToUseExternalCliCredential( | |
| existing: OAuthCredential | undefined, | |
| imported: OAuthCredential, | |
| ): boolean { | |
| if (!existing) { | |
| return true; | |
| } | |
| if (existing.provider !== imported.provider) { | |
| return false; | |
| } | |
| return isSafeToCopyOAuthIdentity(existing, imported); | |
| } | |
| const EXTERNAL_CLI_SYNC_PROVIDERS: ExternalCliSyncProvider[] = [ | |
| { | |
| profileId: MINIMAX_CLI_PROFILE_ID, | |
| provider: "minimax-portal", | |
| aliases: ["minimax", "minimax-cli"], | |
| readCredentials: () => readMiniMaxCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS }), | |
| }, | |
| ]; | |
| function resolveExternalCliSyncProvider(params: { | |
| profileId: string; | |
| credential?: OAuthCredential; | |
| }): ExternalCliSyncProvider | null { | |
| const provider = EXTERNAL_CLI_SYNC_PROVIDERS.find((entry) => | |
| externalCliProfileIdMatches(entry, params.profileId), | |
| ); | |
| if (!provider) { | |
| return null; | |
| } | |
| if ( | |
| params.credential && | |
| !listExternalCliProviderIds(provider).includes(params.credential.provider) | |
| ) { | |
| return null; | |
| } | |
| return provider; | |
| } | |
| function resolveExternalCliPersistence( | |
| provider: ExternalCliSyncProvider, | |
| ): ExternalCliResolvedProfile["persistence"] { | |
| return provider.persistence ?? "persisted"; | |
| } | |
| /** True when durable metadata assigns this stored profile to an external CLI owner. */ | |
| export function isPersistedExternalCliAuthProfile(params: { | |
| profileId: string; | |
| credential: OAuthCredential; | |
| }): boolean { | |
| const provider = resolveExternalCliSyncProvider(params); | |
| if (!provider || resolveExternalCliPersistence(provider) !== "persisted") { | |
| return false; | |
| } | |
| // Historical native MiniMax logins and CLI imports share an unmarked shape. | |
| // Only exact CLI token backfill may assign durable external ownership. | |
| return params.credential.authFlow === PERSISTED_EXTERNAL_CLI_AUTH_FLOW; | |
| } | |
| function markPersistedExternalCliCredential( | |
| provider: ExternalCliSyncProvider, | |
| credential: OAuthCredential, | |
| ): OAuthCredential { | |
| return resolveExternalCliPersistence(provider) === "persisted" | |
| ? { ...credential, authFlow: PERSISTED_EXTERNAL_CLI_AUTH_FLOW } | |
| : credential; | |
| } | |
| function listExternalCliProfileIds(providerConfig: ExternalCliSyncProvider): string[] { | |
| return [providerConfig.profileId, ...(providerConfig.profileAliases ?? [])]; | |
| } | |
| function listExternalCliProviderIds(providerConfig: ExternalCliSyncProvider): string[] { | |
| return [providerConfig.provider, ...(providerConfig.aliases ?? [])]; | |
| } | |
| /** Provider ids whose external CLI credentials can be refreshed by this owner. */ | |
| export function listExternalCliSyncProviderIds(): string[] { | |
| return [...new Set(EXTERNAL_CLI_SYNC_PROVIDERS.flatMap(listExternalCliProviderIds))]; | |
| } | |
| function normalizeExternalCliCredentialProvider( | |
| credential: OAuthCredential | null, | |
| provider: string, | |
| ): OAuthCredential | null { | |
| return credential ? { ...credential, provider } : null; | |
| } | |
| function externalCliProfileIdMatches( | |
| providerConfig: ExternalCliSyncProvider, | |
| profileId: string, | |
| ): boolean { | |
| return listExternalCliProfileIds(providerConfig).includes(profileId); | |
| } | |
| /** Read a CLI credential only for safe bootstrap of an unusable local profile. */ | |
| export function readExternalCliBootstrapCredential(params: { | |
| store: AuthProfileStore; | |
| profileId: string; | |
| credential: OAuthCredential; | |
| allowInlineOAuthTokenMaterial?: boolean; | |
| allowKeychainPrompt?: boolean; | |
| }): OAuthCredential | null { | |
| const provider = resolveExternalCliSyncProvider(params); | |
| if (!provider) { | |
| return null; | |
| } | |
| const imported = normalizeExternalCliCredentialProvider( | |
| provider.readCredentials({ allowKeychainPrompt: params.allowKeychainPrompt }), | |
| params.credential.provider, | |
| ); | |
| if (imported && isOAuthRefreshFence(params.credential)) { | |
| // An external snapshot has no generation ordering proof. It must not clear | |
| // a fence and make an older single-use refresh token replayable. | |
| return null; | |
| } | |
| return imported; | |
| } | |
| function normalizeProviderScope(values: Iterable<string> | undefined): Set<string> | undefined { | |
| if (values === undefined) { | |
| return undefined; | |
| } | |
| const out = new Set<string>(); | |
| for (const value of values) { | |
| const raw = value.trim(); | |
| if (!raw) { | |
| continue; | |
| } | |
| out.add(raw.toLowerCase()); | |
| const normalized = normalizeProviderId(raw); | |
| if (normalized) { | |
| out.add(normalized); | |
| } | |
| } | |
| return out; | |
| } | |
| function isExternalCliProviderInScope(params: { | |
| providerConfig: ExternalCliSyncProvider; | |
| store: AuthProfileStore; | |
| options?: ExternalCliAuthProfileOptions; | |
| }): boolean { | |
| const { providerConfig, options, store } = params; | |
| const providerScope = normalizeProviderScope(options?.providerIds); | |
| if (providerScope === undefined && options?.profileIds === undefined) { | |
| return Object.entries(store.profiles).some(([profileId, existing]) => { | |
| return ( | |
| externalCliProfileIdMatches(providerConfig, profileId) && | |
| existing?.type === "oauth" && | |
| listExternalCliProviderIds(providerConfig).includes(existing.provider) | |
| ); | |
| }); | |
| } | |
| if ( | |
| Array.from(options?.profileIds ?? []).some((profileId) => | |
| externalCliProfileIdMatches(providerConfig, profileId.trim()), | |
| ) | |
| ) { | |
| return true; | |
| } | |
| if (!providerScope || providerScope.size === 0) { | |
| return false; | |
| } | |
| return listExternalCliProviderIds(providerConfig).some((alias) => { | |
| const raw = alias.trim().toLowerCase(); | |
| const normalized = normalizeProviderId(alias); | |
| return providerScope.has(raw) || (normalized ? providerScope.has(normalized) : false); | |
| }); | |
| } | |
| /** True when a previously resolved built-in CLI profile belongs to this refresh scope. */ | |
| export function isExternalCliAuthProfileInScope(params: { | |
| store: AuthProfileStore; | |
| profileId: string; | |
| providerIds?: Iterable<string>; | |
| profileIds?: Iterable<string>; | |
| }): boolean { | |
| const credential = params.store.profiles[params.profileId]; | |
| const providerConfig = resolveExternalCliSyncProvider({ | |
| profileId: params.profileId, | |
| ...(credential?.type === "oauth" ? { credential } : {}), | |
| }); | |
| if (!providerConfig) { | |
| // A retired reader still has to release its tagged runtime overlay on refresh. | |
| return ( | |
| Array.from(params.profileIds ?? []).includes(params.profileId) || | |
| (credential !== undefined && | |
| normalizeProviderScope(params.providerIds)?.has( | |
| normalizeProviderId(credential.provider), | |
| ) === true) | |
| ); | |
| } | |
| return isExternalCliProviderInScope({ | |
| providerConfig, | |
| store: params.store, | |
| options: { | |
| ...(params.providerIds ? { providerIds: params.providerIds } : {}), | |
| ...(params.profileIds ? { profileIds: params.profileIds } : {}), | |
| }, | |
| }); | |
| } | |
| function listScopedExternalCliProfileIds(params: { | |
| providerConfig: ExternalCliSyncProvider; | |
| store: AuthProfileStore; | |
| options?: ExternalCliAuthProfileOptions; | |
| }): string[] { | |
| const { options, providerConfig, store } = params; | |
| const requestedProfileIds = Array.from(options?.profileIds ?? []) | |
| .map((value) => value.trim()) | |
| .filter((value) => value.length > 0); | |
| const matchingRequestedProfileIds = requestedProfileIds.filter((profileId) => | |
| externalCliProfileIdMatches(providerConfig, profileId), | |
| ); | |
| if (matchingRequestedProfileIds.length > 0) { | |
| return matchingRequestedProfileIds; | |
| } | |
| const existingProfileIds = Object.keys(store.profiles).filter((profileId) => | |
| externalCliProfileIdMatches(providerConfig, profileId), | |
| ); | |
| if (existingProfileIds.length > 0) { | |
| return existingProfileIds; | |
| } | |
| return options?.providerIds ? [providerConfig.profileId] : []; | |
| } | |
| function backfillExternalCliIdentity(params: { | |
| providerConfig: ExternalCliSyncProvider; | |
| existingOAuth: OAuthCredential; | |
| allowKeychainPrompt?: boolean; | |
| }): OAuthCredential | null { | |
| const creds = params.providerConfig.readCredentials({ | |
| allowKeychainPrompt: params.allowKeychainPrompt, | |
| }); | |
| // Matching token material proves the stored profile came from this CLI owner. | |
| // Persist that fact so refresh ownership does not depend on a later file read. | |
| const sameLogin = creds?.refresh === params.existingOAuth.refresh; | |
| if (!sameLogin) { | |
| return null; | |
| } | |
| const credential = markPersistedExternalCliCredential(params.providerConfig, { | |
| ...params.existingOAuth, | |
| ...(params.existingOAuth.email || !creds.email ? {} : { email: creds.email }), | |
| }); | |
| return credential.authFlow === params.existingOAuth.authFlow && | |
| credential.email === params.existingOAuth.email | |
| ? null | |
| : credential; | |
| } | |
| /** Resolve scoped external CLI auth profiles available to overlay or persist. */ | |
| export function resolveExternalCliAuthProfiles( | |
| store: AuthProfileStore, | |
| options?: ExternalCliAuthProfileOptions, | |
| ): ExternalCliResolvedProfile[] { | |
| const profiles: ExternalCliResolvedProfile[] = []; | |
| const now = Date.now(); | |
| for (const providerConfig of EXTERNAL_CLI_SYNC_PROVIDERS) { | |
| if (!isExternalCliProviderInScope({ providerConfig, store, options })) { | |
| continue; | |
| } | |
| const scopedProfileIds = listScopedExternalCliProfileIds({ | |
| providerConfig, | |
| store, | |
| options, | |
| }); | |
| for (const profileId of scopedProfileIds) { | |
| const existing = store.profiles[profileId]; | |
| const existingOAuth = | |
| existing?.type === "oauth" && | |
| listExternalCliProviderIds(providerConfig).includes(existing.provider) | |
| ? existing | |
| : undefined; | |
| if (existing && !existingOAuth) { | |
| authProfilesLog.debug("kept explicit local auth over external cli bootstrap", { | |
| profileId, | |
| provider: providerConfig.provider, | |
| localType: existing.type, | |
| localProvider: existing.provider, | |
| }); | |
| continue; | |
| } | |
| if (existingOAuth && hasUsableOAuthCredential(existingOAuth, { now })) { | |
| // Profiles synced before identity capture carry no email; backfill the | |
| // non-secret metadata once the CLI read proves it is the same login. | |
| const backfilled = backfillExternalCliIdentity({ | |
| providerConfig, | |
| existingOAuth, | |
| allowKeychainPrompt: options?.allowKeychainPrompt, | |
| }); | |
| if (backfilled) { | |
| profiles.push({ | |
| profileId, | |
| credential: backfilled, | |
| persistence: resolveExternalCliPersistence(providerConfig), | |
| }); | |
| } | |
| continue; | |
| } | |
| const creds = normalizeExternalCliCredentialProvider( | |
| providerConfig.readCredentials({ | |
| allowKeychainPrompt: options?.allowKeychainPrompt, | |
| }), | |
| existingOAuth?.provider ?? providerConfig.provider, | |
| ); | |
| if (!creds) { | |
| continue; | |
| } | |
| if (existingOAuth && isOAuthRefreshFence(existingOAuth)) { | |
| authProfilesLog.warn("refused unordered external cli oauth recovery for a fenced profile", { | |
| profileId, | |
| provider: providerConfig.provider, | |
| }); | |
| continue; | |
| } | |
| if (existingOAuth && !isSafeToUseExternalCliCredential(existingOAuth, creds)) { | |
| authProfilesLog.warn("refused external cli oauth bootstrap: identity mismatch", { | |
| profileId, | |
| provider: providerConfig.provider, | |
| }); | |
| continue; | |
| } | |
| if ( | |
| existingOAuth && | |
| !isSafeToAdoptBootstrapOAuthIdentity(existingOAuth, creds) && | |
| !areOAuthCredentialsEquivalent(existingOAuth, creds) | |
| ) { | |
| authProfilesLog.warn( | |
| "refused external cli oauth bootstrap: identity mismatch or missing binding", | |
| { | |
| profileId, | |
| provider: providerConfig.provider, | |
| }, | |
| ); | |
| continue; | |
| } | |
| if ( | |
| !shouldBootstrapFromExternalCliCredential({ | |
| existing: existingOAuth, | |
| imported: creds, | |
| now, | |
| }) | |
| ) { | |
| if (existingOAuth) { | |
| authProfilesLog.debug("kept usable local oauth over external cli bootstrap", { | |
| profileId, | |
| provider: providerConfig.provider, | |
| localExpires: existingOAuth.expires, | |
| externalExpires: creds.expires, | |
| }); | |
| } | |
| continue; | |
| } | |
| authProfilesLog.debug( | |
| "used external cli oauth bootstrap because local oauth was missing or unusable", | |
| { | |
| profileId, | |
| provider: providerConfig.provider, | |
| localExpires: existingOAuth?.expires, | |
| externalExpires: creds.expires, | |
| }, | |
| ); | |
| profiles.push({ | |
| profileId, | |
| credential: markPersistedExternalCliCredential(providerConfig, creds), | |
| persistence: resolveExternalCliPersistence(providerConfig), | |
| }); | |
| } | |
| } | |
| return profiles; | |
| } | |