auth-context.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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' | 'shared-key'
  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 === 'shared-key') {
  46. // For shared-key auth, automatically authenticate
  47. validateSharedKey()
  48. } else if (authMethod === 'unix') {
  49. // For Unix auth, check for unix_user in localStorage
  50. const unixUser = localStorage.getItem('unix_user')
  51. if (unixUser) {
  52. validateUnixUser(unixUser)
  53. } else {
  54. setState(prev => ({ ...prev, isLoading: false }))
  55. }
  56. } else {
  57. // For JWT auth, check for token in localStorage
  58. const token = localStorage.getItem('auth_token')
  59. if (token) {
  60. validateToken(token)
  61. } else {
  62. setState(prev => ({ ...prev, isLoading: false }))
  63. }
  64. }
  65. }, [authMethod])
  66. const validateToken = async (token: string) => {
  67. try {
  68. const data = await authApi.validateToken(token)
  69. setState({
  70. user: data.user,
  71. token,
  72. isAuthenticated: true,
  73. isLoading: false,
  74. error: null
  75. })
  76. localStorage.setItem('auth_token', token)
  77. } catch (error) {
  78. console.error('Token validation error:', error)
  79. localStorage.removeItem('auth_token')
  80. setState({
  81. user: null,
  82. token: null,
  83. isAuthenticated: false,
  84. isLoading: false,
  85. error: 'Session expired. Please log in again.'
  86. })
  87. }
  88. }
  89. const validateSharedKey = async () => {
  90. try {
  91. // For shared-key auth, we can validate by making a simple API call
  92. const data = await authApi.getCurrentUser()
  93. setState({
  94. user: data.user,
  95. token: 'shared-key-auth', // Store a pseudo-token for consistency
  96. isAuthenticated: true,
  97. isLoading: false,
  98. error: null
  99. })
  100. localStorage.setItem('auth_token', 'shared-key-auth') // Store pseudo-token for API calls
  101. } catch (error) {
  102. console.error('Shared key validation error:', error)
  103. localStorage.removeItem('auth_token')
  104. setState({
  105. user: null,
  106. token: null,
  107. isAuthenticated: false,
  108. isLoading: false,
  109. error: 'Shared key authentication failed.'
  110. })
  111. }
  112. }
  113. const validateUnixUser = async (unixUser: string) => {
  114. try {
  115. // For Unix auth, we need to validate by attempting to get current user
  116. const data = await authApi.getCurrentUser()
  117. setState({
  118. user: data.user,
  119. token: `unix_${unixUser}`, // Store a pseudo-token for consistency
  120. isAuthenticated: true,
  121. isLoading: false,
  122. error: null
  123. })
  124. localStorage.setItem('unix_user', unixUser)
  125. localStorage.setItem('auth_token', `unix_${unixUser}`) // Store pseudo-token for API calls
  126. } catch (error) {
  127. console.error('Unix user validation error:', error)
  128. localStorage.removeItem('unix_user')
  129. localStorage.removeItem('auth_token')
  130. setState({
  131. user: null,
  132. token: null,
  133. isAuthenticated: false,
  134. isLoading: false,
  135. error: 'Session expired. Please log in again.'
  136. })
  137. }
  138. }
  139. const login = async (username: string, password?: string) => {
  140. setState(prev => ({ ...prev, isLoading: true, error: null }))
  141. try {
  142. const data = await authApi.login(username, password)
  143. const { token, user } = data
  144. setState({
  145. user,
  146. token,
  147. isAuthenticated: true,
  148. isLoading: false,
  149. error: null
  150. })
  151. if (authMethod === 'shared-key') {
  152. localStorage.setItem('auth_token', token) // Store pseudo-token for API calls
  153. } else if (authMethod === 'unix') {
  154. localStorage.setItem('unix_user', username)
  155. localStorage.setItem('auth_token', token) // Store token for API calls
  156. } else {
  157. localStorage.setItem('auth_token', token)
  158. }
  159. } catch (error) {
  160. setState(prev => ({
  161. ...prev,
  162. isLoading: false,
  163. error: error instanceof Error ? error.message : 'Login failed'
  164. }))
  165. }
  166. }
  167. const logout = () => {
  168. if (authMethod === 'unix') {
  169. localStorage.removeItem('unix_user')
  170. }
  171. localStorage.removeItem('auth_token')
  172. setState({
  173. user: null,
  174. token: null,
  175. isAuthenticated: false,
  176. isLoading: false,
  177. error: null
  178. })
  179. }
  180. const refreshToken = async () => {
  181. const { token } = state
  182. if (!token) return
  183. try {
  184. const data = await authApi.refreshToken()
  185. const newToken = data.token
  186. setState(prev => ({ ...prev, token: newToken }))
  187. localStorage.setItem('auth_token', newToken)
  188. } catch (error) {
  189. console.error('Token refresh error:', error)
  190. logout()
  191. }
  192. }
  193. const clearError = () => {
  194. setState(prev => ({ ...prev, error: null }))
  195. }
  196. const value: AuthContextType = {
  197. ...state,
  198. login,
  199. logout,
  200. refreshToken,
  201. clearError,
  202. authMethod,
  203. authEnabled
  204. }
  205. return (
  206. <AuthContext.Provider value={value}>
  207. {children}
  208. </AuthContext.Provider>
  209. )
  210. }
  211. export function useAuth(): AuthContextType {
  212. const context = useContext(AuthContext)
  213. if (context === undefined) {
  214. throw new Error('useAuth must be used within an AuthProvider')
  215. }
  216. return context
  217. }