AuthContext.tsx 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from 'react'
  2. export interface User {
  3. id: string
  4. username: string
  5. email?: string
  6. real_name?: string
  7. roles: string[]
  8. is_active: boolean
  9. }
  10. interface AuthState {
  11. user: User | null
  12. token: string | null
  13. refreshToken: string | null
  14. isAuthenticated: boolean
  15. login: (token: string, refreshToken: string, user: User) => void
  16. logout: () => Promise<void>
  17. }
  18. const AuthContext = createContext<AuthState | null>(null)
  19. export function AuthProvider({ children }: { children: ReactNode }) {
  20. const [user, setUser] = useState<User | null>(null)
  21. const [token, setToken] = useState<string | null>(null)
  22. const [refreshToken, setRefreshToken] = useState<string | null>(null)
  23. useEffect(() => {
  24. const stored = localStorage.getItem('token')
  25. const storedRt = localStorage.getItem('refresh_token')
  26. const storedUser = localStorage.getItem('user')
  27. if (stored && storedUser) {
  28. setToken(stored)
  29. setRefreshToken(storedRt)
  30. setUser(JSON.parse(storedUser))
  31. }
  32. }, [])
  33. const login = useCallback((t: string, rt: string, u: User) => {
  34. setToken(t)
  35. setRefreshToken(rt)
  36. setUser(u)
  37. localStorage.setItem('token', t)
  38. localStorage.setItem('refresh_token', rt)
  39. localStorage.setItem('user', JSON.stringify(u))
  40. }, [])
  41. const logout = useCallback(async () => {
  42. const currentToken = localStorage.getItem('token')
  43. const currentRt = localStorage.getItem('refresh_token')
  44. if (currentToken && currentRt) {
  45. try {
  46. const res = await fetch('/api/v1/auth/logout', {
  47. method: 'POST',
  48. headers: {
  49. 'Content-Type': 'application/json',
  50. Authorization: `Bearer ${currentToken}`,
  51. },
  52. body: JSON.stringify({ token: currentToken, refresh_token: currentRt }),
  53. })
  54. const data = await res.json()
  55. if (data.data?.sso_logout_url) {
  56. clearAuth()
  57. window.location.href = data.data.sso_logout_url
  58. return
  59. }
  60. } catch {
  61. // ignore network errors during logout
  62. }
  63. }
  64. clearAuth()
  65. window.location.href = '/login'
  66. }, [])
  67. const clearAuth = useCallback(() => {
  68. setToken(null)
  69. setRefreshToken(null)
  70. setUser(null)
  71. localStorage.removeItem('token')
  72. localStorage.removeItem('refresh_token')
  73. localStorage.removeItem('user')
  74. }, [])
  75. return (
  76. <AuthContext.Provider
  77. value={{ user, token, refreshToken, isAuthenticated: !!token, login, logout }}
  78. >
  79. {children}
  80. </AuthContext.Provider>
  81. )
  82. }
  83. export function useAuth() {
  84. const ctx = useContext(AuthContext)
  85. if (!ctx) throw new Error('useAuth must be used inside AuthProvider')
  86. return ctx
  87. }