fix(auth): Startseite/AdminRoute redirecten bei Reload fälschlich auf Objekte
Race Condition: bei bereits eingeloggter Session (Direktaufruf/Reload von "/" oder /dashboard/etc.) ist "eingeloggt" sofort true (Token im Storage), "rollen" aber noch [] bis /auth/me durch ist. Startseite/AdminRoute haben in genau diesem kurzen Fenster mit istVerantwortlich=false entschieden und per Navigate replace auf /objekte umgeleitet, bevor die echten Rollen (z.B. "administration") eintrafen - Admin landete dadurch nie auf dem Dashboard, sondern immer auf Objekte. Neues rollenGeladen-Flag im AuthContext, Startseite/AdminRoute warten jetzt darauf statt vorschnell mit rollen=[] zu entscheiden. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
@@ -6658,3 +6658,17 @@ Keine Commits in dieser Session.
|
|||||||
- frontend/src/styles/global.css | 2 ++
|
- frontend/src/styles/global.css | 2 ++
|
||||||
|
|
||||||
---
|
---
|
||||||
|
## 2026-09-07 14:03 – 14:07 (4m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** frontend
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
- 6db571f fix(dashboard): Resize-Griff war unsichtbar - children-Injection landete in falscher Ebene
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 14 ++++++++++++++
|
||||||
|
- frontend/DEVLOG.md | 14 ++++++++++++++
|
||||||
|
- frontend/src/pages/dashboard/DashboardPage.tsx | 5 ++---
|
||||||
|
- frontend/src/pages/dashboard/TileFrame.tsx | 13 ++++++++++---
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
+11
-2
@@ -21,16 +21,25 @@ function GeschuetzteRoute({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Startseite() {
|
function Startseite() {
|
||||||
const { eingeloggt, istVerantwortlich } = useAuth();
|
const { eingeloggt, istVerantwortlich, rollenGeladen } = useAuth();
|
||||||
if (!eingeloggt) return <Navigate to="/login" replace />;
|
if (!eingeloggt) return <Navigate to="/login" replace />;
|
||||||
|
// Ohne dieses Warten würde bei bereits eingeloggter Session (Reload/Direktaufruf)
|
||||||
|
// kurz mit rollen=[] entschieden und fälschlich auf /objekte redirected, bevor
|
||||||
|
// /auth/me durch ist.
|
||||||
|
if (!rollenGeladen) return null;
|
||||||
return <Navigate to={istVerantwortlich ? "/dashboard" : "/objekte"} replace />;
|
return <Navigate to={istVerantwortlich ? "/dashboard" : "/objekte"} replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AdminRoute({ children }: { children: ReactNode }) {
|
function AdminRoute({ children }: { children: ReactNode }) {
|
||||||
const { eingeloggt, istVerantwortlich } = useAuth();
|
const { eingeloggt, istVerantwortlich, rollenGeladen } = useAuth();
|
||||||
if (!eingeloggt) {
|
if (!eingeloggt) {
|
||||||
return <Navigate to="/login" replace />;
|
return <Navigate to="/login" replace />;
|
||||||
}
|
}
|
||||||
|
// Gleiches Race wie in Startseite: bei Direktaufruf/Reload noch nicht auf
|
||||||
|
// rollen=[] entscheiden, sonst kurzzeitig fälschlicher Redirect.
|
||||||
|
if (!rollenGeladen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
// Nicht mehr admin-exklusiv: Materialverantwortliche/Leitungsverantwortliche
|
// Nicht mehr admin-exklusiv: Materialverantwortliche/Leitungsverantwortliche
|
||||||
// dürfen rein, sehen dort aber nur die für sie freigeschalteten Tabs
|
// dürfen rein, sehen dort aber nur die für sie freigeschalteten Tabs
|
||||||
// (AdminPage filtert selbst nach Rolle, Backend erzwingt es ohnehin je Endpunkt).
|
// (AdminPage filtert selbst nach Rolle, Backend erzwingt es ohnehin je Endpunkt).
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ import type { Me } from "../api/types";
|
|||||||
|
|
||||||
interface AuthContextValue {
|
interface AuthContextValue {
|
||||||
eingeloggt: boolean;
|
eingeloggt: boolean;
|
||||||
|
// Beim Direktladen/Reload einer bereits eingeloggten Session ist "eingeloggt"
|
||||||
|
// sofort true (Token liegt im Storage), "rollen" aber noch [] bis /auth/me
|
||||||
|
// durch ist - ohne dieses Flag würden Startseite/AdminRoute in genau diesem
|
||||||
|
// Fenster fälschlich "istVerantwortlich=false" annehmen und auf /objekte
|
||||||
|
// redirecten, obwohl die echten Rollen kurz danach eintreffen.
|
||||||
|
rollenGeladen: boolean;
|
||||||
rollen: string[];
|
rollen: string[];
|
||||||
istAdmin: boolean;
|
istAdmin: boolean;
|
||||||
// Rollen-Vorgabe: Materialverantwortliche/Leitungsverantwortliche sahen bisher
|
// Rollen-Vorgabe: Materialverantwortliche/Leitungsverantwortliche sahen bisher
|
||||||
@@ -23,20 +29,25 @@ const AuthContext = createContext<AuthContextValue | null>(null);
|
|||||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
const [eingeloggt, setEingeloggt] = useState<boolean>(getAuthToken() !== null);
|
const [eingeloggt, setEingeloggt] = useState<boolean>(getAuthToken() !== null);
|
||||||
const [rollen, setRollen] = useState<string[]>([]);
|
const [rollen, setRollen] = useState<string[]>([]);
|
||||||
|
const [rollenGeladen, setRollenGeladen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!eingeloggt) {
|
if (!eingeloggt) {
|
||||||
setRollen([]);
|
setRollen([]);
|
||||||
|
setRollenGeladen(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setRollenGeladen(false);
|
||||||
apiRequest<Me>("/auth/me")
|
apiRequest<Me>("/auth/me")
|
||||||
.then((me) => setRollen(me.rollen))
|
.then((me) => setRollen(me.rollen))
|
||||||
.catch(() => setRollen([]));
|
.catch(() => setRollen([]))
|
||||||
|
.finally(() => setRollenGeladen(true));
|
||||||
}, [eingeloggt]);
|
}, [eingeloggt]);
|
||||||
|
|
||||||
const value = useMemo<AuthContextValue>(
|
const value = useMemo<AuthContextValue>(
|
||||||
() => ({
|
() => ({
|
||||||
eingeloggt,
|
eingeloggt,
|
||||||
|
rollenGeladen,
|
||||||
rollen,
|
rollen,
|
||||||
istAdmin: rollen.includes("administration"),
|
istAdmin: rollen.includes("administration"),
|
||||||
istMaterialverantwortlich: rollen.includes("materialverantwortlicher"),
|
istMaterialverantwortlich: rollen.includes("materialverantwortlicher"),
|
||||||
@@ -55,7 +66,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
setEingeloggt(false);
|
setEingeloggt(false);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[eingeloggt, rollen]
|
[eingeloggt, rollenGeladen, rollen]
|
||||||
);
|
);
|
||||||
|
|
||||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||||
|
|||||||
Reference in New Issue
Block a user