Files
MoShiro/web/admin/src/auth.tsx
T
2026-10-04 21:46:39 +08:00

70 lines
2.1 KiB
TypeScript

import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react'
import { api, ApiError, setCsrf } from '@shared/api/client'
import { isRemote, setToken } from '@shared/api/source'
import type { LoginResponse, MeDto } from '@shared/api/types'
interface Auth {
me: MeDto | null
ready: boolean
login: (username: string, password: string) => Promise<void>
/** 连别的后端时用 API 令牌登录(不用 Cookie)。 */
loginWithToken: (token: string) => Promise<void>
logout: () => Promise<void>
}
const AuthContext = createContext<Auth | null>(null)
export function AuthProvider({ children }: { children: ReactNode }) {
const [me, setMe] = useState<MeDto | null>(null)
const [ready, setReady] = useState(false)
useEffect(() => {
api
.get<MeDto>('/api/admin/auth/me')
.then((m) => {
setCsrf(m.csrf_token ?? null)
setMe(m)
})
.catch((e: unknown) => {
if (!(e instanceof ApiError) || (e.status !== 401 && e.status !== 403)) console.error(e)
})
.finally(() => setReady(true))
}, [])
const login = useCallback(async (username: string, password: string) => {
const r = await api.post<LoginResponse>('/api/admin/auth/login', { username, password })
setCsrf(r.csrf_token)
setMe({ kind: 'user', scope: 'admin', user_id: r.user_id, username: r.username, csrf_token: r.csrf_token })
}, [])
const loginWithToken = useCallback(async (token: string) => {
setToken(token)
try {
const m = await api.get<MeDto>('/api/admin/auth/me')
setCsrf(m.csrf_token ?? null)
setMe(m)
} catch (e) {
setToken('')
throw e
}
}, [])
const logout = useCallback(async () => {
try {
if (isRemote()) setToken('')
else await api.post('/api/admin/auth/logout')
} finally {
setCsrf(null)
setMe(null)
}
}, [])
return <AuthContext.Provider value={{ me, ready, login, loginWithToken, logout }}>{children}</AuthContext.Provider>
}
export function useAuth(): Auth {
const value = useContext(AuthContext)
if (!value) throw new Error('AuthProvider is missing')
return value
}