feat: Unbekannte und nicht erlaubte Seiten leiten zum Dashboard um
Backend Image / build-and-push (push) Successful in 2m55s
Frontend Image / build-and-push (push) Successful in 2m23s

- src/app/not-found.tsx: jede nicht existierende Route landet auf
  /dashboard statt der Next.js-Standard-404-Seite (der bestehende
  Auth-Guard in (app)/layout.tsx schickt nicht angemeldete User von
  dort automatisch weiter zu /login).
- settings/layout.tsx: fehlt die Berechtigung fuer den
  Einstellungen-Bereich, wird jetzt zu /dashboard umgeleitet statt nur
  einen Hinweistext anzuzeigen (gleiches Redirect-Muster wie der
  bestehende Login-Guard).

Login-nach-Anmeldung-Redirect zu /dashboard und der Auth-Gate in
(app)/layout.tsx bestanden bereits.
This commit is contained in:
Nick Szaunig committed 2026-08-26 23:14:47 +02:00
1 parent 9de123ab32
commit b3157484c4
3 files changed
+61 -8

No files matched your search

@@ -0,0 +1,46 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import SettingsLayout from "./layout";
const replaceMock = vi.fn();
vi.mock("next/navigation", () => ({
useRouter: () => ({ replace: replaceMock }),
}));
let mockUser: { permissions: string[] } | null = null;
vi.mock("@/lib/auth/auth-context", () => ({
useAuth: () => ({ user: mockUser }),
}));
describe("SettingsLayout", () => {
beforeEach(() => {
replaceMock.mockReset();
mockUser = null;
});
it("renders children when the user has a settings-area permission", () => {
mockUser = { permissions: ["users:read"] };
render(
<SettingsLayout>
<p>Einstellungen-Inhalt</p>
</SettingsLayout>,
);
expect(screen.getByText("Einstellungen-Inhalt")).toBeInTheDocument();
expect(replaceMock).not.toHaveBeenCalled();
});
it("redirects to the dashboard when the user lacks every settings-area permission", () => {
mockUser = { permissions: ["colonies:read"] };
render(
<SettingsLayout>
<p>Einstellungen-Inhalt</p>
</SettingsLayout>,
);
expect(screen.queryByText("Einstellungen-Inhalt")).not.toBeInTheDocument();
expect(replaceMock).toHaveBeenCalledWith("/dashboard");
});
});
@@ -1,6 +1,7 @@
"use client";
import { ReactNode } from "react";
import { ReactNode, useEffect } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "@/lib/auth/auth-context";
const SETTINGS_AREA_PERMISSIONS = [
@@ -14,19 +15,20 @@ const SETTINGS_AREA_PERMISSIONS = [
];
export default function SettingsLayout({ children }: { children: ReactNode }) {
const router = useRouter();
const { user } = useAuth();
const hasSettingsAccess = SETTINGS_AREA_PERMISSIONS.some((permission) =>
user?.permissions.includes(permission),
);
useEffect(() => {
if (!hasSettingsAccess) {
return (
<main className="p-6">
<p className="text-sm text-muted-foreground">
Diese Seite ist nur für Administratoren verfügbar.
</p>
</main>
);
router.replace("/dashboard");
}
}, [hasSettingsAccess, router]);
if (!hasSettingsAccess) {
return null;
}
return <>{children}</>;
+5
View File
@@ -0,0 +1,5 @@
import { redirect } from "next/navigation";
export default function NotFound() {
redirect("/dashboard");
}