Files
codex-proxy-rs/frontend/src/stores/modules/auth.ts
T
84382761fc feat(auth): add focused API key usage page (#102)
* feat(auth): add API key workspace

Add persistent API key sessions and enforce admin/key authorization boundaries. Expose key-scoped quotas, trends, health, and paginated usage records through server-projected data.

Share the application shell, system overview, and usage views between admin and key sessions, reorganize frontend domains around view ownership, and omit account, provider, and authentication details from the key workspace.

BREAKING CHANGE: Browser authentication now uses /api/auth/* and cpr_session; existing browser sessions must sign in again.

Validated frontend lint, type checking, production build, backend workspace tests, and live authenticated key views.

Co-authored-by: Codex <noreply@openai.com>

* feat(console): refine key workspace experience

Unify role-aware console routes and use mode/role in authentication contracts.
Reuse request state, stable pagination and performance projections across workspaces.
Refine quota cards, usage panels, empty states and key-aware theme previews.
Skip session restoration on login and retain form inputs until unmount.
Keep the full health timeline grid visible without samples.

Co-authored-by: Codex <noreply@openai.com>

* feat(key-usage): replace shared workspace with focused usage page

Preserve unified admin/key authentication, restore admin pages, and remove the previous client workspace implementation. Add a scoped usage page with metrics, trends, budgets, request logs, and section loading placeholders.

Co-authored-by: Codex <noreply@openai.com>

---------

Co-authored-by: Zyy <zyycn@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-16 01:49:37 +08:00

92 lines
2.4 KiB
TypeScript

import type { AuthSession } from '@/api'
import { defineStore } from 'pinia'
import { computed, shallowRef } from 'vue'
import { login as apiLogin, logout as apiLogout, getAuthStatus } from '@/api'
import { resetUnauthorizedHandling } from '@/api/request'
export const useAuthStore = defineStore('auth', () => {
const session = shallowRef<AuthSession | null>(null)
const isAuthenticated = computed(() => session.value !== null)
const isAdmin = computed(() => session.value?.role === 'admin')
const sessionChecked = shallowRef(false)
const loading = shallowRef(false)
let revision = 0
let pendingCheck: Promise<boolean> | undefined
function checkAuth(): Promise<boolean> {
if (pendingCheck)
return pendingCheck
const currentRevision = revision
const check = getAuthStatus().then((status) => {
// 登录或退出之后到达的旧状态响应,不覆盖新会话。
if (currentRevision === revision) {
session.value = status.session
sessionChecked.value = true
if (status.authenticated)
resetUnauthorizedHandling()
}
return isAuthenticated.value
}).finally(() => {
if (pendingCheck === check)
pendingCheck = undefined
})
pendingCheck = check
return check
}
async function login(payload: Parameters<typeof apiLogin>[0]) {
if (loading.value)
return null
revision += 1
pendingCheck = undefined
loading.value = true
try {
const result = await apiLogin(payload)
revision += 1
pendingCheck = undefined
session.value = result
sessionChecked.value = true
resetUnauthorizedHandling()
return result
}
catch {
return null
}
finally {
loading.value = false
}
}
async function logout() {
if (loading.value)
return false
loading.value = true
revision += 1
pendingCheck = undefined
try {
await apiLogout()
invalidateSession()
return true
}
catch {
// 只有服务端确认撤销后才退出,避免刷新又恢复一个未撤销的会话。
return false
}
finally {
loading.value = false
}
}
function invalidateSession() {
revision += 1
pendingCheck = undefined
session.value = null
sessionChecked.value = true
resetUnauthorizedHandling()
}
return { session, isAuthenticated, isAdmin, sessionChecked, loading, checkAuth, login, logout, invalidateSession }
})