mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 03:52:54 +00:00
98 lines
3.2 KiB
TypeScript
98 lines
3.2 KiB
TypeScript
import { resolveExpiresAtMsFromEpochSeconds } from "../shared/number-coercion.js";
|
|
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
|
|
|
const OPENAI_CODEX_AUTH_CLAIM = "https://api.openai.com/auth";
|
|
const OPENAI_CODEX_PROFILE_CLAIM = "https://api.openai.com/profile";
|
|
|
|
export type OpenAICodexAuthIdentity = {
|
|
accountId?: string;
|
|
chatgptPlanType?: string;
|
|
email?: string;
|
|
profileName?: string;
|
|
};
|
|
|
|
export function decodeOpenAICodexJwtPayload(token: string): Record<string, unknown> | undefined {
|
|
const payload = token.split(".")[1];
|
|
if (!payload) {
|
|
return undefined;
|
|
}
|
|
try {
|
|
const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
? (parsed as Record<string, unknown>)
|
|
: undefined;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function readRecord(value: unknown): Record<string, unknown> {
|
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
? (value as Record<string, unknown>)
|
|
: {};
|
|
}
|
|
|
|
export function resolveOpenAICodexAuthIdentity(params: {
|
|
access: string;
|
|
accountId?: string;
|
|
}): OpenAICodexAuthIdentity {
|
|
const payload = decodeOpenAICodexJwtPayload(params.access);
|
|
const auth = readRecord(payload?.[OPENAI_CODEX_AUTH_CLAIM]);
|
|
const profile = readRecord(payload?.[OPENAI_CODEX_PROFILE_CLAIM]);
|
|
const email = normalizeOptionalString(profile.email);
|
|
const accountId = params.accountId ?? normalizeOptionalString(auth.chatgpt_account_id);
|
|
const chatgptPlanType = normalizeOptionalString(auth.chatgpt_plan_type);
|
|
if (email) {
|
|
return {
|
|
...(accountId ? { accountId } : {}),
|
|
...(chatgptPlanType ? { chatgptPlanType } : {}),
|
|
email,
|
|
profileName: email,
|
|
};
|
|
}
|
|
|
|
const stableSubject =
|
|
normalizeOptionalString(auth.chatgpt_account_user_id) ??
|
|
normalizeOptionalString(auth.chatgpt_user_id) ??
|
|
normalizeOptionalString(auth.user_id) ??
|
|
normalizeOptionalString(payload?.sub) ??
|
|
accountId;
|
|
return {
|
|
...(accountId ? { accountId } : {}),
|
|
...(chatgptPlanType ? { chatgptPlanType } : {}),
|
|
...(stableSubject
|
|
? { profileName: `id-${Buffer.from(stableSubject).toString("base64url")}` }
|
|
: {}),
|
|
};
|
|
}
|
|
|
|
export function resolveOpenAICodexAccessTokenExpiry(access: string): number | undefined {
|
|
const payload = decodeOpenAICodexJwtPayload(access);
|
|
const exp = payload?.exp;
|
|
return resolveExpiresAtMsFromEpochSeconds(exp);
|
|
}
|
|
|
|
export function buildOpenAICodexCredentialExtra(
|
|
identity: OpenAICodexAuthIdentity & { idToken?: string },
|
|
): Record<string, unknown> | undefined {
|
|
const extra = {
|
|
...(identity.accountId ? { accountId: identity.accountId } : {}),
|
|
...(identity.chatgptPlanType ? { chatgptPlanType: identity.chatgptPlanType } : {}),
|
|
...(identity.idToken ? { idToken: identity.idToken } : {}),
|
|
};
|
|
return Object.keys(extra).length > 0 ? extra : undefined;
|
|
}
|
|
|
|
export function resolveOpenAICodexImportProfileName(
|
|
identity: Pick<OpenAICodexAuthIdentity, "accountId" | "profileName">,
|
|
fallback: string,
|
|
): string {
|
|
if (identity.accountId) {
|
|
return `account-${identity.accountId.replaceAll(/[^A-Za-z0-9._-]+/gu, "-")}`;
|
|
}
|
|
if (identity.profileName?.startsWith("id-")) {
|
|
return identity.profileName;
|
|
}
|
|
return fallback;
|
|
}
|