mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:00:42 +00:00
refactor(auth): reuse shared oauth policy helpers
This commit is contained in:
committed by
Peter Steinberger
parent
5f2e77a6e1
commit
6f450c2d1f
@@ -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,
|
||||
}),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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">,
|
||||
|
||||
Reference in New Issue
Block a user