feat: auth capture — remote browser credential extraction

- BrowserSessionService: add create_ephemeral() for temp sessions
- New auth_capture_service.py: extract cookies, localStorage, sessionStorage from page
- New auth_capture router: POST /sessions, GET /sessions/{id}/extract, DELETE /sessions/{id}
- Frontend AuthCaptureDialog: URL input → browser view → extract → pick candidate
- Upstreams.vue: '提取' button next to Bearer Token field
- No sensitive values logged
This commit is contained in:
SmartUp Developer
2026-05-17 21:04:36 +08:00
parent c809139470
commit 4d1237c58f
7 changed files with 659 additions and 4 deletions
+43 -2
View File
@@ -240,7 +240,13 @@
</el-form-item>
<template v-if="form.auth_type === 'bearer'">
<el-form-item label="Bearer Token">
<el-input v-model="form.auth_config.token" type="password" show-password placeholder="***" />
<div class="auth-field-row">
<el-input v-model="form.auth_config.token" type="password" show-password placeholder="***" />
<el-button size="small" @click="openAuthCapture">
<el-icon><Pointer /></el-icon>
提取
</el-button>
</div>
</el-form-item>
</template>
<template v-else-if="form.auth_type === 'api_key'">
@@ -388,6 +394,12 @@
<el-button size="small" :disabled="snapshots.length < snapshotLimit" @click="nextSnapPage">下一页</el-button>
</div>
</el-drawer>
<AuthCaptureDialog
v-model="authCaptureVisible"
:initial-url="form.base_url"
@select="handleAuthCaptureSelect"
/>
</div>
</template>
@@ -396,8 +408,9 @@ import { ref, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import dayjs from 'dayjs'
import { Refresh, Plus, Edit, List, Delete, Warning, Clock, ArrowRight } from '@element-plus/icons-vue'
import { Refresh, Plus, Edit, List, Delete, Warning, Clock, ArrowRight, Pointer } from '@element-plus/icons-vue'
import { upstreamsApi, type UpstreamData } from '@/api'
import AuthCaptureDialog from '@/components/AuthCaptureDialog.vue'
const list = ref<(UpstreamData & { _testing?: boolean; _checking?: boolean })[]>([])
const tableLoading = ref(false)
@@ -425,6 +438,22 @@ const rules = {
base_url: [{ required: true, message: '请输入 Base URL', trigger: 'blur' }],
}
const authCaptureVisible = ref(false)
function openAuthCapture() {
authCaptureVisible.value = true
}
function handleAuthCaptureSelect(candidate: { type: string; value: string; cookie_name?: string; cookie_value?: string }) {
if (candidate.type === 'bearer_token') {
form.value.auth_config.token = candidate.value
} else if (candidate.type === 'cookie') {
// For cookie auth, store as a formatted cookie string
form.value.auth_config.token = candidate.value
}
ElMessage.success('已填入认证信息')
}
const quickPlatform = ref('sub2api')
function handlePlatformChange(val: string) {
@@ -649,6 +678,18 @@ onMounted(loadList)
</script>
<style scoped>
.auth-field-row {
display: flex;
gap: 8px;
align-items: center;
}
.auth-field-row .el-input {
flex: 1;
}
.auth-field-row .el-button {
flex-shrink: 0;
}
.upstreams-page {
padding-bottom: 1rem;
}