70 lines
2.1 KiB
TypeScript
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
|
|
}
|