Files
codex-proxy-rs/frontend/src/api/modules/login.ts
T
ZyyandCodex 3d1dc79f5d fix(auth): refresh sessions only after authentication failures
Use read-only status checks for initial authentication and remove activity-driven renewal. Keep failed and newly queued requests waiting for the current refresh before replaying with the updated cookie.

Co-authored-by: Codex <noreply@openai.com>
2026-10-07 03:10:50 +08:00

79 lines
1.9 KiB
TypeScript

import request from '../request'
export interface AuthSession {
role: 'admin' | 'key'
expiresAt: string
}
export type LoginParam
= | { mode: 'admin', username: string, password: string }
| { mode: 'key', apiKey: string }
export interface AuthStatusResponse {
authenticated: boolean
session: AuthSession | null
}
export interface LogoutResponse {
message: string
}
let pendingMutation: Promise<unknown> = Promise.resolve()
// Cookie 写入由浏览器完成,跨标签页串行认证操作以免晚到的续期覆盖登录或退出
function withSessionLock<T>(operation: () => Promise<T>): Promise<T> {
const run = () => navigator.locks
? navigator.locks.request('cpr-session', operation)
: operation()
const result = pendingMutation.then(run, run)
pendingMutation = result.catch(() => {})
return result
}
export function login(data: LoginParam) {
return withSessionLock(() => request<AuthSession>({
url: '/api/auth/login',
method: 'POST',
skipSessionRecovery: true,
data,
}))
}
export function getAuthStatus() {
return request<AuthStatusResponse>({
url: '/api/auth/status',
method: 'GET',
skipSessionRecovery: true,
silent: true,
timeout: 10_000,
})
}
export function refreshAuthSession() {
return withSessionLock(() => request<AuthStatusResponse>({
url: '/api/auth/refresh',
method: 'POST',
skipSessionRecovery: true,
retry: true,
silent: true,
timeout: 10_000,
}))
}
export function logout() {
return withSessionLock(() => request<LogoutResponse>({
url: '/api/auth/logout',
method: 'POST',
skipSessionRecovery: true,
}))
}
export function changeAdminPassword(data: { currentPassword: string, newPassword: string }) {
return withSessionLock(() => request<{ message: string }>({
url: '/api/auth/password',
method: 'POST',
skipSessionRecovery: true,
data,
}))
}