134 lines
3.8 KiB
React
134 lines
3.8 KiB
React
|
|
import { createContext, useContext, useState, useEffect } from 'react'
|
||
|
|
|
||
|
|
const AuthContext = createContext(null)
|
||
|
|
|
||
|
|
const TOKEN_KEY = 'bodytrack_token'
|
||
|
|
const PROFILE_KEY = 'bodytrack_active_profile'
|
||
|
|
|
||
|
|
export function AuthProvider({ children }) {
|
||
|
|
const [session, setSession] = useState(null) // {token, profile_id, role}
|
||
|
|
const [loading, setLoading] = useState(true)
|
||
|
|
const [needsSetup, setNeedsSetup] = useState(false)
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
checkStatus()
|
||
|
|
}, [])
|
||
|
|
|
||
|
|
const checkStatus = async () => {
|
||
|
|
try {
|
||
|
|
const r = await fetch('/api/auth/status')
|
||
|
|
const status = await r.json()
|
||
|
|
|
||
|
|
if (status.needs_setup) {
|
||
|
|
setNeedsSetup(true)
|
||
|
|
setLoading(false)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Try existing token
|
||
|
|
const token = localStorage.getItem(TOKEN_KEY)
|
||
|
|
if (token) {
|
||
|
|
const me = await fetch('/api/auth/me', {
|
||
|
|
headers: { 'X-Auth-Token': token }
|
||
|
|
})
|
||
|
|
if (me.ok) {
|
||
|
|
const profile = await me.json()
|
||
|
|
setSession({ token, profile_id: profile.id, role: profile.role, profile })
|
||
|
|
setLoading(false)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
// Token expired
|
||
|
|
localStorage.removeItem(TOKEN_KEY)
|
||
|
|
}
|
||
|
|
} catch(e) {
|
||
|
|
console.error('Auth check failed', e)
|
||
|
|
}
|
||
|
|
setLoading(false)
|
||
|
|
}
|
||
|
|
|
||
|
|
const login = async (credentials) => {
|
||
|
|
// Support both new {email, pin} and legacy {profile_id, pin}
|
||
|
|
const body = typeof credentials === 'object' ? credentials : { profile_id: credentials }
|
||
|
|
const r = await fetch('/api/auth/login', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
body: JSON.stringify(body)
|
||
|
|
})
|
||
|
|
if (!r.ok) {
|
||
|
|
const err = await r.json()
|
||
|
|
throw new Error(err.detail || 'Login fehlgeschlagen')
|
||
|
|
}
|
||
|
|
const data = await r.json()
|
||
|
|
localStorage.setItem(TOKEN_KEY, data.token)
|
||
|
|
localStorage.setItem(PROFILE_KEY, data.profile_id)
|
||
|
|
// Fetch full profile
|
||
|
|
const me = await fetch('/api/auth/me', { headers: { 'X-Auth-Token': data.token } })
|
||
|
|
const profile = await me.json()
|
||
|
|
setSession({ token: data.token, profile_id: data.profile_id, role: data.role, profile })
|
||
|
|
return data
|
||
|
|
}
|
||
|
|
|
||
|
|
const setup = async (formData) => {
|
||
|
|
const r = await fetch('/api/auth/setup', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
body: JSON.stringify(formData)
|
||
|
|
})
|
||
|
|
if (!r.ok) {
|
||
|
|
const err = await r.json()
|
||
|
|
throw new Error(err.detail || 'Setup fehlgeschlagen')
|
||
|
|
}
|
||
|
|
const data = await r.json()
|
||
|
|
localStorage.setItem(TOKEN_KEY, data.token)
|
||
|
|
localStorage.setItem(PROFILE_KEY, data.profile_id)
|
||
|
|
setNeedsSetup(false)
|
||
|
|
await checkStatus()
|
||
|
|
return data
|
||
|
|
}
|
||
|
|
|
||
|
|
const setAuthFromToken = (token, profile) => {
|
||
|
|
// Direct token/profile set (for email verification auto-login)
|
||
|
|
localStorage.setItem(TOKEN_KEY, token)
|
||
|
|
localStorage.setItem(PROFILE_KEY, profile.id)
|
||
|
|
setSession({
|
||
|
|
token,
|
||
|
|
profile_id: profile.id,
|
||
|
|
role: profile.role || 'user',
|
||
|
|
profile
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const logout = async () => {
|
||
|
|
const token = localStorage.getItem(TOKEN_KEY)
|
||
|
|
if (token) {
|
||
|
|
await fetch('/api/auth/logout', { method: 'POST', headers: { 'X-Auth-Token': token } })
|
||
|
|
}
|
||
|
|
localStorage.removeItem(TOKEN_KEY)
|
||
|
|
setSession(null)
|
||
|
|
}
|
||
|
|
|
||
|
|
const isAdmin = session?.role === 'admin'
|
||
|
|
const canUseAI = session?.profile?.ai_enabled !== 0
|
||
|
|
const canExport = session?.profile?.export_enabled !== 0
|
||
|
|
|
||
|
|
return (
|
||
|
|
<AuthContext.Provider value={{
|
||
|
|
session, loading, needsSetup,
|
||
|
|
login, setup, logout, setAuthFromToken,
|
||
|
|
isAdmin, canUseAI, canExport,
|
||
|
|
token: session?.token,
|
||
|
|
profileId: session?.profile_id,
|
||
|
|
}}>
|
||
|
|
{children}
|
||
|
|
</AuthContext.Provider>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useAuth() {
|
||
|
|
return useContext(AuthContext)
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getToken() {
|
||
|
|
return localStorage.getItem(TOKEN_KEY)
|
||
|
|
}
|