auth-context.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. "use client"
  2. import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react'
  3. import { authApi } from './api'
  4. interface User {
  5. id: string
  6. username: string
  7. email?: string
  8. role: 'admin' | 'user'
  9. createdAt: string
  10. lastLogin?: string
  11. }
  12. interface AuthState {
  13. user: User | null
  14. token: string | null
  15. isAuthenticated: boolean
  16. isLoading: boolean
  17. error: string | null
  18. }
  19. interface AuthContextType extends AuthState {
  20. login: (username: string, password?: string) => Promise<void>
  21. logout: () => void
  22. refreshToken: () => Promise<void>
  23. clearError: () => void
  24. authMethod: 'none' | 'unix' | 'jwt'
  25. authEnabled: boolean
  26. }
  27. const AuthContext = createContext<AuthContextType | undefined>(undefined)
  28. export function AuthProvider({ children }: { children: ReactNode }) {
  29. const [state, setState] = useState<AuthState>({
  30. user: null,
  31. token: null,
  32. isAuthenticated: false,
  33. isLoading: true,
  34. error: null
  35. })
  36. // Get authentication method from server config
  37. const authMethod = typeof window !== 'undefined' && window.__SERVER_CONFIG__
  38. ? window.__SERVER_CONFIG__.authMethod
  39. : 'jwt';
  40. const authEnabled = typeof window !== 'undefined' && window.__SERVER_CONFIG__
  41. ? window.__SERVER_CONFIG__.authEnabled
  42. : false;
  43. useEffect(() => {
  44. // Check for existing authentication on mount
  45. if (authMethod === 'unix') {
  46. // For Unix auth, check for unix_user in localStorage
  47. const unixUser = localStorage.getItem('unix_user')
  48. if (unixUser) {
  49. validateUnixUser(unixUser)
  50. } else {
  51. setState(prev => ({ ...prev, isLoading: false }))
  52. }
  53. } else {
  54. // For JWT auth, check for token in localStorage
  55. const token = localStorage.getItem('auth_token')
  56. if (token) {
  57. validateToken(token)
  58. } else {
  59. setState(prev => ({ ...prev, isLoading: false }))
  60. }
  61. }
  62. }, [authMethod])
  63. const validateToken = async (token: string) => {
  64. try {
  65. const data = await authApi.validateToken(token)
  66. setState({
  67. user: data.user,
  68. token,
  69. isAuthenticated: true,
  70. isLoading: false,
  71. error: null
  72. })
  73. localStorage.setItem('auth_token', token)
  74. } catch (error) {
  75. console.error('Token validation error:', error)
  76. localStorage.removeItem('auth_token')
  77. setState({
  78. user: null,
  79. token: null,
  80. isAuthenticated: false,
  81. isLoading: false,
  82. error: 'Session expired. Please log in again.'
  83. })
  84. }
  85. }
  86. const validateUnixUser = async (unixUser: string) => {
  87. try {
  88. // For Unix auth, we need to validate by attempting to get current user
  89. const data = await authApi.getCurrentUser()
  90. setState({
  91. user: data.user,
  92. token: `unix_${unixUser}`, // Store a pseudo-token for consistency
  93. isAuthenticated: true,
  94. isLoading: false,
  95. error: null
  96. })
  97. localStorage.setItem('unix_user', unixUser)
  98. localStorage.setItem('auth_token', `unix_${unixUser}`) // Store pseudo-token for API calls
  99. } catch (error) {
  100. console.error('Unix user validation error:', error)
  101. localStorage.removeItem('unix_user')
  102. localStorage.removeItem('auth_token')
  103. setState({
  104. user: null,
  105. token: null,
  106. isAuthenticated: false,
  107. isLoading: false,
  108. error: 'Session expired. Please log in again.'
  109. })
  110. }
  111. }
  112. const login = async (username: string, password?: string) => {
  113. setState(prev => ({ ...prev, isLoading: true, error: null }))
  114. try {
  115. const data = await authApi.login(username, password)
  116. const { token, user } = data
  117. setState({
  118. user,
  119. token,
  120. isAuthenticated: true,
  121. isLoading: false,
  122. error: null
  123. })
  124. if (authMethod === 'unix') {
  125. localStorage.setItem('unix_user', username)
  126. localStorage.setItem('auth_token', token) // Store token for API calls
  127. } else {
  128. localStorage.setItem('auth_token', token)
  129. }
  130. } catch (error) {
  131. setState(prev => ({
  132. ...prev,
  133. isLoading: false,
  134. error: error instanceof Error ? error.message : 'Login failed'
  135. }))
  136. }
  137. }
  138. const logout = () => {
  139. if (authMethod === 'unix') {
  140. localStorage.removeItem('unix_user')
  141. }
  142. localStorage.removeItem('auth_token')
  143. setState({
  144. user: null,
  145. token: null,
  146. isAuthenticated: false,
  147. isLoading: false,
  148. error: null
  149. })
  150. }
  151. const refreshToken = async () => {
  152. const { token } = state
  153. if (!token) return
  154. try {
  155. const data = await authApi.refreshToken()
  156. const newToken = data.token
  157. setState(prev => ({ ...prev, token: newToken }))
  158. localStorage.setItem('auth_token', newToken)
  159. } catch (error) {
  160. console.error('Token refresh error:', error)
  161. logout()
  162. }
  163. }
  164. const clearError = () => {
  165. setState(prev => ({ ...prev, error: null }))
  166. }
  167. const value: AuthContextType = {
  168. ...state,
  169. login,
  170. logout,
  171. refreshToken,
  172. clearError,
  173. authMethod,
  174. authEnabled
  175. }
  176. return (
  177. <AuthContext.Provider value={value}>
  178. {children}
  179. </AuthContext.Provider>
  180. )
  181. }
  182. export function useAuth(): AuthContextType {
  183. const context = useContext(AuthContext)
  184. if (context === undefined) {
  185. throw new Error('useAuth must be used within an AuthProvider')
  186. }
  187. return context
  188. }