openclaw / src /agents /agent-auth-credentials.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
35f99ee verified
Raw History Blame Contribute Delete
6.07 kB
/** Converts auth-profile credentials into agent runtime credential maps. */
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { asDateTimestampMs } from "@openclaw/normalization-core/number-coercion";
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { coerceSecretRef } from "../config/types.secrets.js";
import type {
PreparedAgentCredentialMode,
PreparedAgentCredentialModes,
} from "./agent-auth-credential-modes.js";
import { isOAuthRefreshFence } from "./auth-profiles/oauth-refresh-marker.js";
import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
import type { AuthProfileCredential, AuthProfileStore } from "./auth-profiles/types.js";
import type { ApiKeyCredential, AuthStorageData } from "./sessions/auth-storage.js";
// Converts auth-profile credentials into the compact credential map consumed by
// agent runtimes. Secret refs can be represented by markers without reading
// secret values.
type AgentApiKeyCredential = ApiKeyCredential;
type AgentOAuthCredential = {
type: "oauth";
access: string;
refresh: string;
expires: number;
};
/** Credential value shape consumed by agent runtimes after auth-profile normalization. */
type AgentCredential = AgentApiKeyCredential | AgentOAuthCredential;
export type AgentCredentialMap = Record<string, AgentCredential>;
type ResolveAgentCredentialMapOptions = {
includeSecretRefPlaceholders?: boolean;
config?: OpenClawConfig;
};
const AGENT_SECRET_REF_CONFIGURED_MARKER = "openclaw-secret-ref-configured";
/** Records only credential modes whose secret material is usable by a prepared runtime owner. */
export function resolveUsableAgentCredentialModes(
credentials: Readonly<AuthStorageData>,
): PreparedAgentCredentialModes {
const modes: Record<string, PreparedAgentCredentialMode> = {};
for (const [rawProvider, credential] of Object.entries(credentials)) {
const provider = normalizeProviderId(rawProvider);
if (!provider) {
continue;
}
if (
credential.type === "api_key" &&
credential.key &&
credential.key !== AGENT_SECRET_REF_CONFIGURED_MARKER
) {
const native = credential.nativeAuth;
if (!native || normalizeProviderId(native.runtime) === provider) {
modes[provider] = native
? Object.freeze({
source: "native",
mode: native.mode === "api-key" ? "api_key" : native.mode,
})
: "api_key";
}
} else if (
credential.type === "token" &&
credential.token &&
(credential.expires === undefined || credential.expires > Date.now())
) {
modes[provider] = "token";
} else if (
credential.type === "oauth" &&
!isOAuthRefreshFence(credential) &&
credential.access &&
credential.refresh &&
credential.expires > 0
) {
modes[provider] = "oauth";
}
}
return Object.freeze(modes);
}
function hasConfiguredSecretRef(value: unknown): boolean {
return coerceSecretRef(value) !== null;
}
function secretRefPlaceholder(
options: ResolveAgentCredentialMapOptions | undefined,
): AgentCredential | null {
if (options?.includeSecretRefPlaceholders === true) {
return { type: "api_key", key: AGENT_SECRET_REF_CONFIGURED_MARKER };
}
return null;
}
function convertAuthProfileCredentialToAgent(
cred: AuthProfileCredential,
options?: ResolveAgentCredentialMapOptions,
): AgentCredential | null {
if (cred.type === "api_key") {
const key = normalizeOptionalString(cred.key) ?? "";
if (!key) {
// A configured secret ref proves the credential exists, but this converter
// must not resolve or leak the actual secret value.
return hasConfiguredSecretRef(cred.keyRef) ? secretRefPlaceholder(options) : null;
}
return { type: "api_key", key };
}
if (cred.type === "token") {
if (cred.expires !== undefined) {
const expires = asDateTimestampMs(cred.expires);
if (expires === undefined || Date.now() >= expires) {
return null;
}
}
const token = normalizeOptionalString(cred.token) ?? "";
if (!token) {
return hasConfiguredSecretRef(cred.tokenRef) ? secretRefPlaceholder(options) : null;
}
return { type: "api_key", key: token };
}
if (cred.type === "oauth") {
if (isOAuthRefreshFence(cred)) {
return null;
}
const access = normalizeOptionalString(cred.access) ?? "";
const refresh = normalizeOptionalString(cred.refresh) ?? "";
const expires = asDateTimestampMs(cred.expires);
if (!access || !refresh || expires === undefined || expires <= 0) {
return null;
}
return {
type: "oauth",
access,
refresh,
expires,
};
}
return null;
}
/** Build one canonically selected credential per normalized provider. */
export function resolveAgentCredentialMapFromStore(
store: AuthProfileStore,
options?: ResolveAgentCredentialMapOptions,
): AgentCredentialMap {
const credentials: AgentCredentialMap = {};
for (const credential of Object.values(store.profiles)) {
const provider = normalizeProviderId(credential.provider ?? "");
if (!provider) {
continue;
}
if (credentials[provider]) {
continue;
}
// Discovery must not grow a second auth policy: explicit order, provider
// aliases, eligibility, and automatic preference all belong to this resolver.
const profileIds = resolveAuthProfileOrder({
cfg: options?.config,
store,
provider,
...(options?.includeSecretRefPlaceholders === true ? { readinessMode: "read-only" } : {}),
});
for (const profileId of profileIds) {
const profile = store.profiles[profileId];
if (!profile) {
continue;
}
const converted = convertAuthProfileCredentialToAgent(profile, options);
if (converted) {
credentials[provider] = converted;
break;
}
}
}
return credentials;
}