feat: add credential priority controls and OAuth defaults
This commit is contained in:
1 parent
0f3e5093e8
commit
bbe22a96eb
13 files changed
+582
-416
No files matched your search
@@ -2,6 +2,7 @@ import { readBoolean, readString } from './managementApi';
|
||||
import { getCurrentLocale, translate } from '../i18n';
|
||||
|
||||
export type AuthFileRecord = Record<string, unknown>;
|
||||
export type AuthFileSnapshot = Map<string, string>;
|
||||
|
||||
export const authFileName = (file: AuthFileRecord) =>
|
||||
readString(file, 'name') || translate(getCurrentLocale(), 'authFiles.unnamed');
|
||||
@@ -9,6 +10,66 @@ export const authFileName = (file: AuthFileRecord) =>
|
||||
export const isRuntimeOnlyAuthFile = (file: AuthFileRecord) =>
|
||||
readBoolean(file, 'runtime_only', 'runtimeOnly');
|
||||
|
||||
export const parseAuthFilePriority = (value: unknown): number | undefined => {
|
||||
if (typeof value === 'number') {
|
||||
return Number.isSafeInteger(value) ? value : undefined;
|
||||
}
|
||||
if (typeof value !== 'string') return undefined;
|
||||
const normalized = value.trim();
|
||||
if (!/^-?\d+$/.test(normalized)) return undefined;
|
||||
const priority = Number(normalized);
|
||||
return Number.isSafeInteger(priority) ? priority : undefined;
|
||||
};
|
||||
|
||||
export const normalizeAuthFilePriorityInput = (value: string): number | null => {
|
||||
const normalized = value.trim();
|
||||
if (!normalized) return 0;
|
||||
return parseAuthFilePriority(normalized) ?? null;
|
||||
};
|
||||
|
||||
const normalizeOAuthProvider = (value: string) => {
|
||||
const provider = value.trim().toLowerCase();
|
||||
if (provider === 'anthropic') return 'claude';
|
||||
if (provider === 'openai') return 'codex';
|
||||
return provider;
|
||||
};
|
||||
|
||||
const authFileProvider = (file: AuthFileRecord) =>
|
||||
normalizeOAuthProvider(readString(file, 'provider', 'type'));
|
||||
|
||||
export const snapshotAuthFiles = (files: AuthFileRecord[]): AuthFileSnapshot => {
|
||||
const grouped = new Map<string, string[]>();
|
||||
files.forEach((file) => {
|
||||
const name = readString(file, 'name');
|
||||
if (!name) return;
|
||||
const fingerprints = grouped.get(name) ?? [];
|
||||
fingerprints.push(JSON.stringify(file));
|
||||
grouped.set(name, fingerprints);
|
||||
});
|
||||
return new Map(Array.from(grouped, ([name, fingerprints]) => [
|
||||
name,
|
||||
fingerprints.sort().join('\n'),
|
||||
]));
|
||||
};
|
||||
|
||||
export const changedOAuthAuthFileNames = (
|
||||
before: AuthFileSnapshot,
|
||||
files: AuthFileRecord[],
|
||||
provider: string,
|
||||
) => {
|
||||
const expectedProvider = normalizeOAuthProvider(provider);
|
||||
const after = snapshotAuthFiles(files);
|
||||
const names = new Set<string>();
|
||||
files.forEach((file) => {
|
||||
const name = readString(file, 'name');
|
||||
if (!name || authFileProvider(file) !== expectedProvider) return;
|
||||
const priority = parseAuthFilePriority(file.priority);
|
||||
if (priority !== undefined && priority !== 0) return;
|
||||
if (before.get(name) !== after.get(name)) names.add(name);
|
||||
});
|
||||
return Array.from(names);
|
||||
};
|
||||
|
||||
const hasMeaningfulValue = (value: unknown) => {
|
||||
if (value === null || value === undefined) return false;
|
||||
if (typeof value === 'string') return value.trim().length > 0;
|
||||
|
||||
@@ -58,7 +58,7 @@ export const managementApi = {
|
||||
data,
|
||||
});
|
||||
},
|
||||
downloadAuthFile: (name: string) => invoke<string>('download_auth_file', { name }),
|
||||
openAuthFilesDirectory: () => invoke<void>('open_auth_files_directory'),
|
||||
};
|
||||
|
||||
export function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
|
||||
@@ -540,24 +540,8 @@ export const quotaRowsFor = (provider: QuotaProvider, payload: unknown): QuotaRo
|
||||
});
|
||||
};
|
||||
|
||||
const resolveProjectId = async (file: AuthFile): Promise<string> => {
|
||||
const direct = readString(file, 'project_id', 'projectId');
|
||||
if (direct) return direct;
|
||||
try {
|
||||
const content = await managementApi.downloadAuthFile(fileName(file));
|
||||
const parsed = JSON.parse(content) as Record<string, unknown>;
|
||||
const direct = readString(parsed, 'project_id', 'projectId');
|
||||
if (direct) return direct;
|
||||
for (const container of [parsed.installed, parsed.web]) {
|
||||
if (!isRecord(container)) continue;
|
||||
const nested = readString(container, 'project_id', 'projectId');
|
||||
if (nested) return nested;
|
||||
}
|
||||
return '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
const resolveProjectId = (file: AuthFile): string =>
|
||||
readString(file, 'project_id', 'projectId');
|
||||
|
||||
const decodeJwtPayload = (value: unknown): Record<string, unknown> | null => {
|
||||
if (isRecord(value)) return value;
|
||||
@@ -614,18 +598,8 @@ const codexAccountIdFromRecord = (record: Record<string, unknown>): string => {
|
||||
return '';
|
||||
};
|
||||
|
||||
const resolveCodexAccountId = async (file: AuthFile): Promise<string> => {
|
||||
const direct = codexAccountIdFromRecord(file);
|
||||
if (direct) return direct;
|
||||
|
||||
try {
|
||||
const content = await managementApi.downloadAuthFile(fileName(file));
|
||||
const parsed = JSON.parse(content);
|
||||
return isRecord(parsed) ? codexAccountIdFromRecord(parsed) : '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
const resolveCodexAccountId = (file: AuthFile): string =>
|
||||
codexAccountIdFromRecord(file);
|
||||
|
||||
const xaiUserIdFromRecord = (record: Record<string, unknown>): string => {
|
||||
const nestedRecords = [record, record.metadata, record.attributes]
|
||||
@@ -642,17 +616,8 @@ const xaiUserIdFromRecord = (record: Record<string, unknown>): string => {
|
||||
return '';
|
||||
};
|
||||
|
||||
const resolveXaiUserId = async (file: AuthFile): Promise<string> => {
|
||||
const direct = xaiUserIdFromRecord(file);
|
||||
if (direct) return direct;
|
||||
try {
|
||||
const content = await managementApi.downloadAuthFile(fileName(file));
|
||||
const parsed = JSON.parse(content);
|
||||
return isRecord(parsed) ? xaiUserIdFromRecord(parsed) : '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
const resolveXaiUserId = (file: AuthFile): string =>
|
||||
xaiUserIdFromRecord(file);
|
||||
|
||||
const requestQuotaPayload = async (
|
||||
authIndex: string,
|
||||
|
||||
Reference in new issue
Block a user