feat(oauth): 新增 Codex account_user_id 和 organizations 字段采集、展示与判重

- 从 Codex id_token claims 和 token_response 中提取 account_user_id 和 organizations
- OAuth 判重逻辑改为优先按 account_user_id 匹配,支持同用户不同 Team 不误判
- 号池和 Provider 详情页展示组织标签、account ID 和 account_user_id
- 前端重复的 OAuth identity 工具函数提取到 utils/oauthIdentity.ts
- 后端重复的 normalize_oauth_organizations 提取到 core/provider_oauth_utils.py
This commit is contained in:
fawney19
2026-03-11 21:37:08 +08:00
parent b45f021bba
commit 0d770d1c4d
16 changed files with 546 additions and 15 deletions

View File

@@ -0,0 +1,43 @@
import type { OAuthOrganizationInfo } from '@/api/endpoints/types/provider'
export function formatOAuthIdentityShort(
value: string | null | undefined,
head = 8,
tail = 6,
): string {
const normalized = String(value || '').trim()
if (!normalized) return ''
if (normalized.length <= head + tail + 3) return normalized
return `${normalized.slice(0, head)}...${normalized.slice(-tail)}`
}
export function getPrimaryOAuthOrganizationTitle(
value: { oauth_organizations?: OAuthOrganizationInfo[] | null } | null | undefined,
): string | null {
const organizations = Array.isArray(value?.oauth_organizations) ? value.oauth_organizations : []
const defaultOrg = organizations.find(
(org) => org?.is_default && typeof org?.title === 'string' && org.title.trim(),
)
if (defaultOrg?.title) return defaultOrg.title.trim()
const firstWithTitle = organizations.find(
(org) => typeof org?.title === 'string' && org.title.trim(),
)
return firstWithTitle?.title?.trim() || null
}
export function getOAuthOrganizationsTooltip(
value: { oauth_organizations?: OAuthOrganizationInfo[] | null } | null | undefined,
): string {
const organizations = Array.isArray(value?.oauth_organizations) ? value.oauth_organizations : []
if (organizations.length === 0) return ''
return organizations
.map((org) => {
const title =
typeof org?.title === 'string' && org.title.trim() ? org.title.trim() : '未命名组织'
const role =
typeof org?.role === 'string' && org.role.trim() ? ` (${org.role.trim()})` : ''
const suffix = org?.is_default ? ' [default]' : ''
return `${title}${role}${suffix}`
})
.join('\n')
}