- (super-admin)/layout.tsx: server guard (isSuperAdmin check), sidebar nav - (super-admin)/admin/page.tsx: dashboard with tenant stats (total/active/suspended) - (super-admin)/admin/tenants/page.tsx: tenant table with status badges, suspend/activate - src/middleware.ts: /admin/* routes require isSuperAdmin in JWT token - src/lib/__tests__/super-admin.test.ts: 11 tests covering middleware guard + suspension logic - All 93 tests pass (auth 8, RBAC 66, isolation 6, super-admin 11, setup 2) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
import { withAuth } from "next-auth/middleware";
|
|
import { NextResponse } from "next/server";
|
|
|
|
export default withAuth(
|
|
function middleware(req) {
|
|
const { pathname } = req.nextUrl;
|
|
const token = req.nextauth.token;
|
|
|
|
// Super-admin route protection — check isSuperAdmin at middleware level
|
|
// This is an early gate; the layout and API handlers also enforce this.
|
|
if (pathname.startsWith("/admin")) {
|
|
if (!token?.isSuperAdmin) {
|
|
// Redirect non-super-admin users to login (or show forbidden)
|
|
const loginUrl = new URL("/login", req.url);
|
|
loginUrl.searchParams.set("callbackUrl", req.url);
|
|
return NextResponse.redirect(loginUrl);
|
|
}
|
|
}
|
|
|
|
// If user is authenticated (and passed super-admin check above), allow through
|
|
return NextResponse.next();
|
|
},
|
|
{
|
|
callbacks: {
|
|
authorized({ token }) {
|
|
// Return true if token exists (user is authenticated)
|
|
return !!token;
|
|
},
|
|
},
|
|
pages: {
|
|
signIn: "/login",
|
|
},
|
|
}
|
|
);
|
|
|
|
// Apply middleware to all routes EXCEPT public ones
|
|
export const config = {
|
|
matcher: [
|
|
/*
|
|
* Match all request paths EXCEPT:
|
|
* - /login (sign-in page)
|
|
* - /signup (registration page)
|
|
* - /api/auth/* (NextAuth endpoints)
|
|
* - /_next/* (Next.js internals)
|
|
* - /favicon.ico, /robots.txt, /sitemap.xml (static files)
|
|
* - Image files (.png, .jpg, .jpeg, .gif, .webp, .svg, .ico)
|
|
*/
|
|
"/((?!login|signup|api/auth|_next/static|_next/image|favicon\\.ico|robots\\.txt|sitemap\\.xml|.*\\.(?:png|jpg|jpeg|gif|webp|svg|ico)).*)",
|
|
],
|
|
};
|