feat: one-click upstream auth refresh from custom page viewer

- Add linked_upstream_id to CustomPage model with DB migration
- New POST /api/custom-pages/{pid}/refresh-auth endpoint extracts
  credentials from active remote browser and updates linked upstream
- PageViewer toolbar shows key icon button when page has linked upstream
- CustomPages form adds upstream dropdown for remote_browser pages
- Auth capture extracts New-Api-User from localStorage uid/user/self API
- Upstream client sends New-Api-User header in cookie auth mode
- Fix auth capture dialog: transparent background, field persistence,
  login URL defaults to base_url/login, focus on click for keyboard input
- Fix upstream test ASCII encoding with non-header characters validation

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
SmartUp Developer
2026-05-19 09:27:14 +08:00
parent 7cb0ff1608
commit 4c71148ff9
13 changed files with 462 additions and 53 deletions
+23 -4
View File
@@ -270,8 +270,8 @@
</el-form-item>
</template>
<template v-else-if="form.auth_type === 'login_password'">
<el-form-item label="登录邮箱">
<el-input v-model="form.auth_config.email" placeholder="admin@example.com" />
<el-form-item :label="form.auth_config.username_field === 'username' ? '登录账号' : '登录邮箱'">
<el-input v-model="form.auth_config.email" :placeholder="form.auth_config.username_field === 'username' ? 'admin' : 'admin@example.com'" />
</el-form-item>
<el-form-item label="登录密码">
<el-input v-model="form.auth_config.password" type="password" show-password placeholder="***" />
@@ -409,7 +409,7 @@
<AuthCaptureDialog
v-model="authCaptureVisible"
:initial-url="form.base_url"
:initial-url="authCaptureInitialUrl"
@select="handleAuthCaptureSelect"
/>
</div>
@@ -452,11 +452,17 @@ const rules = {
const authCaptureVisible = ref(false)
const authCaptureInitialUrl = computed(() => {
const base = (form.value.base_url || '').replace(/\/+$/, '')
if (!base) return ''
return base + '/login'
})
function openAuthCapture() {
authCaptureVisible.value = true
}
function handleAuthCaptureSelect(candidate: { type: string; value: string; cookie_name?: string; cookie_value?: string }) {
function handleAuthCaptureSelect(candidate: { type: string; value: string; cookie_name?: string; cookie_value?: string; new_api_user?: string }) {
if (candidate.type === 'bearer_token') {
form.value.auth_type = 'bearer'
form.value.auth_config.token = candidate.value
@@ -466,6 +472,17 @@ function handleAuthCaptureSelect(candidate: { type: string; value: string; cooki
form.value.auth_config.cookie_string = candidate.cookie_name && candidate.cookie_value
? `${candidate.cookie_name}=${candidate.cookie_value}`
: candidate.value
if (candidate.new_api_user) {
form.value.auth_config.new_api_user = candidate.new_api_user
form.value.api_prefix = ''
form.value.groups_endpoint = '/api/user/self/groups'
form.value.rate_endpoint = '/api/user/self/groups'
} else if (quickPlatform.value === 'new-api-user') {
form.value.api_prefix = ''
form.value.groups_endpoint = '/api/user/self/groups'
form.value.rate_endpoint = '/api/user/self/groups'
ElMessage.warning('已填入 Cookie,但未提取到 New-Api-User,请重新登录后再提取')
}
ElMessage.success('已填入 Cookie')
} else if (candidate.type === 'api_key') {
form.value.auth_type = 'api_key'
@@ -495,6 +512,7 @@ function handlePlatformChange(val: string) {
form.value.rate_endpoint = '/groups/rates'
form.value.auth_type = 'login_password'
form.value.auth_config.login_path = '/auth/login'
form.value.auth_config.username_field = 'email'
} else if (val === 'new-api') {
form.value.api_prefix = ''
form.value.groups_endpoint = '/api/group/'
@@ -506,6 +524,7 @@ function handlePlatformChange(val: string) {
form.value.rate_endpoint = '/api/user/self/groups'
form.value.auth_type = 'login_password'
form.value.auth_config.login_path = '/api/user/login'
form.value.auth_config.username_field = 'username'
}
}