Files
timemaster/frontend/src/context/AuthContext.tsx
T
patrickandClaude Opus 4.8 fcda813ba6 feat: Reseller-Rolle + SUPER_ADMIN-Mandantenübersicht
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>
2026-06-23 01:15:51 +02:00

59 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}