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:
+20
-12
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user