import { NextResponse } from "next/server" import type { NextRequest } from "next/server" import { getToken } from "next-auth/jwt" import { Permissions } from "@/shared/types/permissions" // Route prefix → minimum required permission // Note: /admin/announcements is covered by /admin prefix (requires school:manage) // Note: /announcements is accessible to all authenticated users (no permission entry needed) const ROUTE_PERMISSIONS: Record = { "/admin": Permissions.SCHOOL_MANAGE, "/teacher": Permissions.EXAM_READ, "/student": Permissions.HOMEWORK_SUBMIT, "/parent": Permissions.EXAM_READ, "/management": Permissions.GRADE_MANAGE, } // API route prefix → required permission const API_PERMISSIONS: Record = { "/api/ai/chat": Permissions.AI_CHAT, } function resolveDefaultPath(roles: string[]): string { if (roles.includes("admin")) return "/admin/dashboard" if (roles.includes("grade_head") || roles.includes("teaching_head")) return "/teacher/dashboard" if (roles.includes("teacher")) return "/teacher/dashboard" if (roles.includes("student")) return "/student/dashboard" if (roles.includes("parent")) return "/parent/dashboard" return "/dashboard" } // Next.js 16 renamed `middleware` to `proxy`. // See: https://nextjs.org/docs/messages/middleware-to-proxy export async function proxy(request: NextRequest) { const { pathname } = request.nextUrl // Skip static assets and auth pages if ( pathname.startsWith("/_next") || pathname.startsWith("/api/auth") || pathname === "/login" || pathname === "/register" || pathname === "/favicon.ico" ) { return NextResponse.next() } const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET, }) // Not authenticated → redirect to login if (!token) { const loginUrl = new URL("/login", request.url) loginUrl.searchParams.set("callbackUrl", request.url) return NextResponse.redirect(loginUrl) } const permissions: string[] = (token.permissions as string[]) ?? [] const roles: string[] = (token.roles as string[]) ?? [] // Check API route permissions for (const [prefix, requiredPerm] of Object.entries(API_PERMISSIONS)) { if (pathname.startsWith(prefix)) { if (!permissions.includes(requiredPerm)) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }) } break } } // Check page route permissions for (const [prefix, requiredPerm] of Object.entries(ROUTE_PERMISSIONS)) { if (pathname.startsWith(prefix)) { if (!permissions.includes(requiredPerm)) { const defaultPath = resolveDefaultPath(roles) // Carry original path + reason in URL so the target page can explain // why the user was redirected (Web Interface Guidelines: URL reflects state). const redirectUrl = new URL(defaultPath, request.url) redirectUrl.searchParams.set("from", pathname) redirectUrl.searchParams.set("reason", "forbidden") return NextResponse.redirect(redirectUrl) } break } } return NextResponse.next() } export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], }