mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-28 00:07:33 +00:00
feat: add codex personal access token auth
This commit is contained in:
@@ -18,6 +18,7 @@ import type {
|
||||
AdminDataImportResult,
|
||||
CodexSessionImportRequest,
|
||||
CodexSessionImportResult,
|
||||
OpenAICodexPATCreateRequest,
|
||||
CheckMixedChannelRequest,
|
||||
CheckMixedChannelResponse
|
||||
} from '@/types'
|
||||
@@ -612,6 +613,11 @@ export async function importCodexSession(payload: CodexSessionImportRequest): Pr
|
||||
return data
|
||||
}
|
||||
|
||||
export async function createOpenAICodexPAT(payload: OpenAICodexPATCreateRequest): Promise<Account> {
|
||||
const { data } = await apiClient.post<Account>('/admin/openai/create-from-codex-pat', payload)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Antigravity default model mapping from backend
|
||||
* @returns Default model mapping (from -> to)
|
||||
@@ -812,6 +818,7 @@ export const accountsAPI = {
|
||||
exportData,
|
||||
importData,
|
||||
importCodexSession,
|
||||
createOpenAICodexPAT,
|
||||
getAntigravityDefaultModelMapping,
|
||||
batchClearError,
|
||||
batchRefresh,
|
||||
|
||||
@@ -2869,6 +2869,7 @@
|
||||
:show-session-token-option="false"
|
||||
:show-access-token-option="false"
|
||||
:show-codex-session-import-option="form.platform === 'openai'"
|
||||
:show-codex-pat-option="form.platform === 'openai'"
|
||||
:platform="form.platform"
|
||||
:show-project-id="geminiOAuthType === 'code_assist'"
|
||||
@generate-url="handleGenerateUrl"
|
||||
@@ -2877,6 +2878,7 @@
|
||||
@validate-mobile-refresh-token="handleOpenAIValidateMobileRT"
|
||||
@validate-session-token="handleValidateSessionToken"
|
||||
@import-codex-session="handleOpenAIImportCodexSession"
|
||||
@import-codex-pat="handleOpenAIImportCodexPAT"
|
||||
/>
|
||||
|
||||
</div>
|
||||
@@ -3262,6 +3264,7 @@ interface OAuthFlowExposed {
|
||||
refreshToken: string
|
||||
sessionToken: string
|
||||
codexSession: string
|
||||
codexPAT: string
|
||||
inputMethod: AuthInputMethod
|
||||
reset: () => void
|
||||
}
|
||||
@@ -4976,6 +4979,55 @@ const handleOpenAIImportCodexSession = async (content: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenAIImportCodexPAT = async (accessToken: string) => {
|
||||
const oauthClient = openaiOAuth
|
||||
const trimmed = accessToken.trim()
|
||||
if (!trimmed) {
|
||||
oauthClient.error.value = t('admin.accounts.oauth.openai.codexPatEmpty')
|
||||
return
|
||||
}
|
||||
|
||||
const credentialExtras = buildOpenAICodexImportCredentialExtras()
|
||||
if (credentialExtras === null) {
|
||||
return
|
||||
}
|
||||
|
||||
oauthClient.loading.value = true
|
||||
oauthClient.error.value = ''
|
||||
|
||||
try {
|
||||
const extra = buildOpenAIExtra()
|
||||
await adminAPI.accounts.createOpenAICodexPAT({
|
||||
access_token: trimmed,
|
||||
name: form.name,
|
||||
notes: form.notes || null,
|
||||
proxy_id: form.proxy_id,
|
||||
concurrency: form.concurrency,
|
||||
load_factor: form.load_factor ?? undefined,
|
||||
priority: form.priority,
|
||||
rate_multiplier: form.rate_multiplier,
|
||||
group_ids: form.group_ids,
|
||||
expires_at: form.expires_at,
|
||||
auto_pause_on_expired: autoPauseOnExpired.value,
|
||||
credential_extras: Object.keys(credentialExtras).length > 0 ? credentialExtras : undefined,
|
||||
extra
|
||||
})
|
||||
|
||||
appStore.showSuccess(t('admin.accounts.messages.accountCreated'))
|
||||
emit('created')
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
oauthClient.error.value =
|
||||
error.response?.data?.detail ||
|
||||
error.response?.data?.message ||
|
||||
error.message ||
|
||||
t('admin.accounts.oauth.openai.codexPatImportFailed')
|
||||
appStore.showError(oauthClient.error.value)
|
||||
} finally {
|
||||
oauthClient.loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// OpenAI RT 批量验证和创建(共享逻辑)
|
||||
const handleOpenAIBatchRT = async (refreshTokenInput: string, clientId?: string) => {
|
||||
const oauthClient = openaiOAuth
|
||||
|
||||
@@ -92,6 +92,17 @@
|
||||
t('admin.accounts.oauth.openai.codexSessionAuth')
|
||||
}}</span>
|
||||
</label>
|
||||
<label v-if="showCodexPatOption" class="flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
v-model="inputMethod"
|
||||
type="radio"
|
||||
value="codex_pat"
|
||||
class="text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span class="text-sm text-blue-900 dark:text-blue-200">{{
|
||||
t('admin.accounts.oauth.openai.codexPatAuth')
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -179,7 +190,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Codex JSON / AT 批量输入 -->
|
||||
<!-- Codex OAuth/session JSON batch import -->
|
||||
<div v-if="inputMethod === 'codex_session'" class="space-y-4">
|
||||
<div
|
||||
class="rounded-lg border border-blue-300 bg-white/80 p-4 dark:border-blue-600 dark:bg-gray-800/80"
|
||||
@@ -258,6 +269,79 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Codex Personal Access Token -->
|
||||
<div v-if="inputMethod === 'codex_pat'" class="space-y-4">
|
||||
<div
|
||||
class="rounded-lg border border-blue-300 bg-white/80 p-4 dark:border-blue-600 dark:bg-gray-800/80"
|
||||
>
|
||||
<p class="mb-3 text-sm text-blue-700 dark:text-blue-300">
|
||||
{{ t('admin.accounts.oauth.openai.codexPatDesc') }}
|
||||
</p>
|
||||
|
||||
<div class="mb-4">
|
||||
<label
|
||||
class="mb-2 flex items-center gap-2 text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<Icon name="key" size="sm" class="text-blue-500" />
|
||||
{{ t('admin.accounts.oauth.openai.codexPatInputLabel') }}
|
||||
</label>
|
||||
<textarea
|
||||
v-model="codexPATInput"
|
||||
rows="3"
|
||||
class="input w-full resize-y font-mono text-sm"
|
||||
:placeholder="t('admin.accounts.oauth.openai.codexPatPlaceholder')"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
<p class="mt-1 text-xs text-blue-600 dark:text-blue-400">
|
||||
{{ t('admin.accounts.oauth.openai.codexPatHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="error"
|
||||
class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 dark:border-red-700 dark:bg-red-900/30"
|
||||
>
|
||||
<p class="whitespace-pre-line text-sm text-red-600 dark:text-red-400">
|
||||
{{ error }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary w-full"
|
||||
:disabled="loading || !codexPATInput.trim()"
|
||||
@click="handleImportCodexPAT"
|
||||
>
|
||||
<svg
|
||||
v-if="loading"
|
||||
class="-ml-1 mr-2 h-4 w-4 animate-spin"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<Icon v-else name="sparkles" size="sm" class="mr-2" />
|
||||
{{
|
||||
loading
|
||||
? t('admin.accounts.oauth.openai.validating')
|
||||
: t('admin.accounts.oauth.openai.codexPatImportAndCreate')
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cookie Auto-Auth Form -->
|
||||
<div v-if="inputMethod === 'cookie'" class="space-y-4">
|
||||
<div
|
||||
@@ -652,6 +736,7 @@ interface Props {
|
||||
showSessionTokenOption?: boolean
|
||||
showAccessTokenOption?: boolean
|
||||
showCodexSessionImportOption?: boolean
|
||||
showCodexPatOption?: boolean
|
||||
platform?: AccountPlatform // Platform type for different UI/text
|
||||
showProjectId?: boolean // New prop to control project ID visibility
|
||||
}
|
||||
@@ -671,6 +756,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
showSessionTokenOption: false,
|
||||
showAccessTokenOption: false,
|
||||
showCodexSessionImportOption: false,
|
||||
showCodexPatOption: false,
|
||||
platform: 'anthropic',
|
||||
showProjectId: true
|
||||
})
|
||||
@@ -684,6 +770,7 @@ const emit = defineEmits<{
|
||||
'validate-session-token': [sessionToken: string]
|
||||
'import-access-token': [accessToken: string]
|
||||
'import-codex-session': [content: string]
|
||||
'import-codex-pat': [accessToken: string]
|
||||
'update:inputMethod': [method: AuthInputMethod]
|
||||
}>()
|
||||
|
||||
@@ -724,12 +811,13 @@ const sessionKeyInput = ref('')
|
||||
const refreshTokenInput = ref('')
|
||||
const sessionTokenInput = ref('')
|
||||
const codexSessionInput = ref('')
|
||||
const codexPATInput = ref('')
|
||||
const showHelpDialog = ref(false)
|
||||
const oauthState = ref('')
|
||||
const projectId = ref('')
|
||||
|
||||
// Computed: show method selection when either cookie or refresh token option is enabled
|
||||
const showMethodSelection = computed(() => props.showCookieOption || props.showRefreshTokenOption || props.showMobileRefreshTokenOption || props.showSessionTokenOption || props.showAccessTokenOption || props.showCodexSessionImportOption)
|
||||
const showMethodSelection = computed(() => props.showCookieOption || props.showRefreshTokenOption || props.showMobileRefreshTokenOption || props.showSessionTokenOption || props.showAccessTokenOption || props.showCodexSessionImportOption || props.showCodexPatOption)
|
||||
|
||||
// Clipboard
|
||||
const { copied, copyToClipboard } = useClipboard()
|
||||
@@ -837,6 +925,12 @@ const handleImportCodexSession = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleImportCodexPAT = () => {
|
||||
if (codexPATInput.value.trim()) {
|
||||
emit('import-codex-pat', codexPATInput.value.trim())
|
||||
}
|
||||
}
|
||||
|
||||
// Expose methods and state
|
||||
defineExpose({
|
||||
authCode: authCodeInput,
|
||||
@@ -846,6 +940,7 @@ defineExpose({
|
||||
refreshToken: refreshTokenInput,
|
||||
sessionToken: sessionTokenInput,
|
||||
codexSession: codexSessionInput,
|
||||
codexPAT: codexPATInput,
|
||||
inputMethod,
|
||||
reset: () => {
|
||||
authCodeInput.value = ''
|
||||
@@ -855,6 +950,7 @@ defineExpose({
|
||||
refreshTokenInput.value = ''
|
||||
sessionTokenInput.value = ''
|
||||
codexSessionInput.value = ''
|
||||
codexPATInput.value = ''
|
||||
inputMethod.value = 'manual'
|
||||
showHelpDialog.value = false
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useAppStore } from '@/stores/app'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
|
||||
export type AddMethod = 'oauth' | 'setup-token'
|
||||
export type AuthInputMethod = 'manual' | 'cookie' | 'refresh_token' | 'mobile_refresh_token' | 'session_token' | 'access_token' | 'codex_session'
|
||||
export type AuthInputMethod = 'manual' | 'cookie' | 'refresh_token' | 'mobile_refresh_token' | 'session_token' | 'access_token' | 'codex_session' | 'codex_pat'
|
||||
|
||||
export interface OAuthState {
|
||||
authUrl: string
|
||||
|
||||
@@ -3802,6 +3802,14 @@ export default {
|
||||
codexSessionImportFailed: 'Failed to import Codex account',
|
||||
codexSessionImportSuccess: 'Import completed: created {created}, updated {updated}, skipped {skipped}',
|
||||
codexSessionImportPartial: 'Partial success: created {created}, updated {updated}, skipped {skipped}, failed {failed}',
|
||||
codexPatAuth: 'Codex Personal Access Token',
|
||||
codexPatDesc: 'Enter a Codex at- personal access token. The system validates it with OpenAI whoami before creating the account.',
|
||||
codexPatInputLabel: 'Codex PAT',
|
||||
codexPatPlaceholder: 'at-...',
|
||||
codexPatHint: 'This is a separate auth mode. It does not save refresh_token or write an OAuth access_token expiration.',
|
||||
codexPatImportAndCreate: 'Validate & Create Codex PAT Account',
|
||||
codexPatEmpty: 'Please enter a Codex personal access token',
|
||||
codexPatImportFailed: 'Failed to create Codex PAT account',
|
||||
sessionTokenAuth: 'Manual ST Input',
|
||||
sessionTokenDesc: 'Enter your existing Session Token(s). Supports batch input (one per line). The system will automatically validate and create accounts.',
|
||||
sessionTokenPlaceholder: 'Paste your Session Token...\nSupports multiple, one per line',
|
||||
|
||||
@@ -3945,6 +3945,14 @@ export default {
|
||||
codexSessionImportFailed: 'Codex 账号导入失败',
|
||||
codexSessionImportSuccess: '导入完成:新增 {created},更新 {updated},跳过 {skipped}',
|
||||
codexSessionImportPartial: '部分成功:新增 {created},更新 {updated},跳过 {skipped},失败 {failed}',
|
||||
codexPatAuth: 'Codex Personal Access Token',
|
||||
codexPatDesc: '输入 Codex at- Personal Access Token,系统会先调用 OpenAI whoami 校验后再创建账号。',
|
||||
codexPatInputLabel: 'Codex PAT',
|
||||
codexPatPlaceholder: 'at-...',
|
||||
codexPatHint: '这是独立认证模式,不保存 refresh_token,也不会写入 OAuth access_token 过期时间。',
|
||||
codexPatImportAndCreate: '校验并创建 Codex PAT 账号',
|
||||
codexPatEmpty: '请输入 Codex Personal Access Token',
|
||||
codexPatImportFailed: 'Codex PAT 账号创建失败',
|
||||
sessionTokenAuth: '手动输入 ST',
|
||||
sessionTokenDesc: '输入您已有的 Session Token,支持批量输入(每行一个),系统将自动验证并创建账号。',
|
||||
sessionTokenPlaceholder: '粘贴您的 Session Token...\n支持多个,每行一个',
|
||||
|
||||
@@ -1175,6 +1175,24 @@ export interface CodexSessionImportRequest {
|
||||
confirm_mixed_channel_risk?: boolean
|
||||
}
|
||||
|
||||
export interface OpenAICodexPATCreateRequest {
|
||||
access_token: string
|
||||
name?: string
|
||||
notes?: string | null
|
||||
group_ids?: number[]
|
||||
proxy_id?: number | null
|
||||
concurrency?: number
|
||||
priority?: number
|
||||
rate_multiplier?: number
|
||||
load_factor?: number | null
|
||||
expires_at?: number | null
|
||||
auto_pause_on_expired?: boolean
|
||||
credential_extras?: Record<string, unknown>
|
||||
extra?: Record<string, unknown>
|
||||
skip_default_group_bind?: boolean
|
||||
confirm_mixed_channel_risk?: boolean
|
||||
}
|
||||
|
||||
export interface CodexSessionImportMessage {
|
||||
index: number
|
||||
name?: string
|
||||
|
||||
Reference in New Issue
Block a user