-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmiddleware.ts
More file actions
117 lines (102 loc) · 3.69 KB
/
Copy pathmiddleware.ts
File metadata and controls
117 lines (102 loc) · 3.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { getToken } from "next-auth/jwt";
export async function middleware(request: NextRequest) {
const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET });
const { pathname } = request.nextUrl;
const isAuthPage = pathname === "/login";
const isApiAuth = pathname.startsWith("/api/auth");
const isWebhook = pathname.startsWith("/api/webhooks");
const isPublicFile = pathname.includes(".") || pathname.startsWith("/_next");
const isDevLogin = pathname.startsWith("/api/dev");
// 1. Exclude static assets, NextAuth routes, dev logins, and webhooks
if (isApiAuth || isPublicFile || isWebhook || isDevLogin) {
return NextResponse.next();
}
// 2. If user is NOT authenticated
if (!token) {
if (isAuthPage || pathname === "/") {
return NextResponse.next();
}
if (pathname.startsWith("/api/")) {
return NextResponse.json({ error: "Unauthorized: Please log in." }, { status: 401 });
}
return NextResponse.redirect(new URL("/login", request.url));
}
// 3. User IS authenticated: Resolve role and onboarding state
const role = token.role;
const profileCompleted = token.profileCompleted;
const isSelectRolePage = pathname === "/select-role";
const isSelectRoleApi = pathname === "/api/auth/select-role";
const isCompleteProfilePage = pathname === "/complete-profile";
const isCompleteProfileApi = pathname === "/api/users/complete-profile";
// STATE A: Role is not selected yet
if (!role) {
if (isSelectRolePage || isSelectRoleApi) {
return NextResponse.next();
}
if (pathname.startsWith("/api/")) {
return NextResponse.json(
{ error: "Forbidden: Role selection required." },
{ status: 403 }
);
}
return NextResponse.redirect(new URL("/select-role", request.url));
}
// STATE B: Role selected, but profile is not completed
if (!profileCompleted) {
if (isCompleteProfilePage || isCompleteProfileApi) {
return NextResponse.next();
}
if (pathname.startsWith("/api/")) {
return NextResponse.json(
{ error: "Forbidden: Profile completion required." },
{ status: 403 }
);
}
return NextResponse.redirect(new URL("/complete-profile", request.url));
}
// STATE C: Fully onboarded user (has role and profile Completed)
// Prevent re-visiting onboarding pages
if (isAuthPage || isSelectRolePage || isCompleteProfilePage) {
return NextResponse.redirect(new URL("/", request.url));
}
// 4. Role-based Gating of Pages & API routes
if (pathname.startsWith("/admin/")) {
if (role !== "ADMIN") {
return NextResponse.redirect(new URL("/", request.url));
}
}
if (pathname.startsWith("/client/")) {
if (role !== "CLIENT") {
return NextResponse.redirect(new URL("/", request.url));
}
}
if (pathname === "/projects") {
if (role === "CLIENT") {
return NextResponse.redirect(new URL("/client/projects", request.url));
}
if (role === "ADMIN") {
return NextResponse.redirect(new URL("/admin/assignments", request.url));
}
}
// API Gating for Admin endpoints
if (pathname.startsWith("/api/admin/")) {
if (role !== "ADMIN") {
return NextResponse.json({ error: "Forbidden: Admin access required." }, { status: 403 });
}
}
return NextResponse.next();
}
export const config = {
matcher: [
/*
* Match all request paths except for:
* - api/auth (internal NextAuth route)
* - _next/static (static assets)
* - _next/image (image optimization assets)
* - favicon.ico (favicon)
*/
"/((?!api/auth|_next/static|_next/image|favicon.ico).*)",
],
};