Mandantenfähigkeit ausgebaut: - Neue Rolle RESELLER (company_id NULL); companies.reseller_id + is_active - RLS-Erweiterung (Migration 0034): companies/users zusätzlich auf app.reseller_id gefenced → Reseller sieht/verwaltet DB-seitig nur eigene Firmen, keine personenbezogenen Zeit-/Abwesenheitsdaten (DSGVO: nur Verwaltung) - get_current_user setzt app.reseller_id + Bypass aus für RESELLER - tenant_service: Firma + Erst-Admin (Einladung), Übersicht mit Kennzahlen - Router /reseller/* (Self-Service) und /admin/* (SUPER_ADMIN: Mandanten + Reseller) - Login-Sperre bei deaktiviertem Mandanten - Frontend: TenantsPage (/admin/tenants), eigene ResellerCompaniesPage (/reseller), rollenbasierte Login-Weiterleitung, Nav "Mandanten" für SUPER_ADMIN - 4 neue Tests inkl. Cross-Reseller-RLS-Isolation; 172/172 grün Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
59 lines
1.8 KiB
TypeScript
59 lines
1.8 KiB
TypeScript
import { createContext, useContext, useState, ReactNode } from 'react'
|
||
import { useNavigate } from 'react-router-dom'
|
||
import { api } from '../api/client'
|
||
|
||
interface AuthContextType {
|
||
token: string | null
|
||
login: (email: string, password: string) => Promise<void>
|
||
logout: () => void
|
||
}
|
||
|
||
const AuthContext = createContext<AuthContextType>(null!)
|
||
|
||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||
const [token, setToken] = useState<string | null>(
|
||
() => localStorage.getItem('access_token'),
|
||
)
|
||
const navigate = useNavigate()
|
||
|
||
async function login(email: string, password: string) {
|
||
// refresh_token kommt nicht mehr im Body – nur noch als HttpOnly-Cookie
|
||
const data = await api.post<{ access_token: string; refresh_token?: string | null }>(
|
||
'/auth/login',
|
||
{ email, password },
|
||
)
|
||
localStorage.setItem('access_token', data.access_token)
|
||
// refresh_token NICHT mehr in localStorage speichern (M-2: HttpOnly-Cookie)
|
||
setToken(data.access_token)
|
||
// Reseller haben keine Firma → eigene Mandanten-Oberfläche statt Dashboard
|
||
try {
|
||
const me = await api.get<{ role: string }>('/auth/me')
|
||
navigate(me.role === 'RESELLER' ? '/reseller' : '/dashboard')
|
||
} catch {
|
||
navigate('/dashboard')
|
||
}
|
||
}
|
||
|
||
async function logout() {
|
||
// Cookie wird vom Backend gelöscht; kein refresh_token aus localStorage nötig
|
||
try {
|
||
await api.post('/auth/logout', {})
|
||
} catch {
|
||
// Logout-Fehler ignorieren – lokal trotzdem ausloggen
|
||
}
|
||
localStorage.removeItem('access_token')
|
||
setToken(null)
|
||
navigate('/login')
|
||
}
|
||
|
||
return (
|
||
<AuthContext.Provider value={{ token, login, logout }}>
|
||
{children}
|
||
</AuthContext.Provider>
|
||
)
|
||
}
|
||
|
||
export function useAuth() {
|
||
return useContext(AuthContext)
|
||
}
|