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
+20 -12
View File
@@ -259,6 +259,7 @@ export interface CustomPageData {
login_submit_selector: string | null
login_autofill_enabled: boolean
login_password_configured: boolean
linked_upstream_id: number | null
created_at: string
updated_at: string
}
@@ -279,6 +280,7 @@ export interface CustomPageForm {
login_submit_selector?: string
login_autofill_enabled?: boolean
login_password_clear?: boolean
linked_upstream_id?: number | null
}
export const customPagesApi = {
@@ -287,6 +289,7 @@ export const customPagesApi = {
create: (data: CustomPageForm) => api.post<CustomPageData>('/api/custom-pages', data),
update: (id: number, data: Partial<CustomPageForm>) => api.put<CustomPageData>(`/api/custom-pages/${id}`, data),
delete: (id: number) => api.delete(`/api/custom-pages/${id}`),
refreshAuth: (id: number) => api.post<{ success: boolean; message: string }>(`/api/custom-pages/${id}/refresh-auth`),
}
// ——— Remote browser sessions ———
@@ -333,28 +336,33 @@ export interface AuthCaptureSession {
ws_url: string
}
export interface AuthCaptureCandidate {
type: 'bearer_token' | 'cookie' | 'credential' | 'api_key'
source: string
preview: string
label: string
confidence: number
value?: string
cookie_name?: string
cookie_value?: string
new_api_user?: string
}
export interface AuthCaptureResult {
cookies: Record<string, any>[]
storage: Record<string, string>
session_storage: Record<string, string>
auth_headers: Record<string, string>[]
candidates: {
type: 'bearer_token' | 'cookie' | 'credential' | 'api_key'
source: string
value: string
preview: string
label: string
confidence: number
cookie_name?: string
cookie_value?: string
}[]
candidates: AuthCaptureCandidate[]
}
export const authCaptureApi = {
createSession: (url: string, width?: number, height?: number) =>
api.post<AuthCaptureSession>('/api/auth-capture/sessions', { url, width, height }),
extract: (sessionId: string) =>
api.get<AuthCaptureResult>(`/api/auth-capture/sessions/${sessionId}/extract`),
extract: (sessionId: string, options?: { includeRaw?: boolean }) =>
api.get<AuthCaptureResult>(`/api/auth-capture/sessions/${sessionId}/extract`, {
params: options?.includeRaw ? { include_raw: true } : undefined,
}),
closeSession: (sessionId: string) =>
api.delete(`/api/auth-capture/sessions/${sessionId}`),
wsUrl: (sessionId: string, token?: string) => {