-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmiddleware.ts
194 lines (168 loc) · 5.36 KB
/
middleware.ts
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import micromatch from "micromatch";
import { getToken } from "next-auth/jwt";
import { NextRequest, NextResponse } from "next/server";
// Constants for common URLs and route patterns
const URLS = {
SIGNIN: "/auth/signin",
DASHBOARD: "/dashboard",
ONBOARDING: "/form/onboarding",
};
const ROUTES = {
PROTECTED_ROUTES: [
"/form/**",
"/dashboard/**",
"/settings/**",
"/profile/**",
],
PUBLIC_AUTH_ROUTES: ["/auth/**"],
PRIVATE_API: ["/api/inngest/**"],
PROTECTED_API: ["/api/form/onboarding/**"], // Added onboarding API route
PUBLIC_API: ["/api/user/**", "/api/auth/*"],
};
// Define the structure of the token
interface Token {
name: string;
email: string;
role: "CONSULTANT" | "CONSULTEE" | "STAFF";
onboardingCompleted: boolean;
consultantProfileId?: string;
consulteeProfileId?: string;
staffProfileId?: string;
picture: string;
exp: number;
iat: number;
jti: string;
sub: string;
}
/**
* Check if a pathname matches any of the given patterns
*/
const isMatchingRoute = (pathname: string, patterns: string[]): boolean =>
micromatch.isMatch(pathname, patterns);
/**
* Get the correct dashboard URL based on user role and profile
*/
const getCorrectDashboardUrl = (token: Token): string | null => {
const { role, consultantProfileId, consulteeProfileId, staffProfileId } =
token;
if (role === "CONSULTANT" && consultantProfileId) {
return `/dashboard/consultant/${consultantProfileId}`;
} else if (role === "CONSULTEE" && consulteeProfileId) {
return `/dashboard/consultee/${consulteeProfileId}`;
} else if (role === "STAFF" && staffProfileId) {
return `/dashboard/staff/${staffProfileId}`;
}
return null;
};
/**
* Handle authentication check and redirection
*/
const handleAuthCheck = (
isAuthenticated: boolean,
req: NextRequest,
): NextResponse | null => {
if (!isAuthenticated) {
return NextResponse.redirect(new URL(URLS.SIGNIN, req.url));
}
return null;
};
/**
* Handle onboarding check and redirection
*/
const handleOnboardingCheck = (
isOnboarded: boolean,
pathname: string,
req: NextRequest,
): NextResponse | null => {
if (!isOnboarded && pathname !== URLS.ONBOARDING) {
return NextResponse.redirect(new URL(URLS.ONBOARDING, req.url));
}
return null;
};
/**
* Handle dashboard redirection
*/
const handleDashboardRedirect = (
token: Token,
pathname: string,
req: NextRequest,
): NextResponse | null => {
const correctDashboardUrl = getCorrectDashboardUrl(token);
if (correctDashboardUrl && pathname !== correctDashboardUrl) {
return NextResponse.redirect(new URL(correctDashboardUrl, req.url));
}
return null;
};
/**
* Middleware function to handle authentication and authorization for routes.
*/
export async function middleware(req: NextRequest): Promise<NextResponse> {
// Bypass middleware in test mode
if (
process.env.NODE_ENV === "test" ||
process.env.NODE_ENV === "development"
) {
console.warn("Bypassing middleware in development or test mode");
return NextResponse.next();
}
const { pathname } = req.nextUrl;
const token = (await getToken({ req })) as Token | null;
const isAuthenticated = !!token;
const isOnboarded = token?.onboardingCompleted ?? false;
// Handle private API routes (including Inngest and Auth)
if (isMatchingRoute(pathname, ROUTES.PRIVATE_API)) {
return isAuthenticated
? NextResponse.next()
: NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Handle protected API routes
if (isMatchingRoute(pathname, ROUTES.PROTECTED_API)) {
return isAuthenticated
? NextResponse.next()
: NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Handle public API routes
if (isMatchingRoute(pathname, ROUTES.PUBLIC_API)) {
return NextResponse.next();
}
// Handle public auth routes
if (isMatchingRoute(pathname, ROUTES.PUBLIC_AUTH_ROUTES)) {
if (isAuthenticated) {
const onboardingCheck = handleOnboardingCheck(isOnboarded, pathname, req);
if (onboardingCheck) return onboardingCheck;
if (token) {
const correctDashboardUrl = getCorrectDashboardUrl(token);
return NextResponse.redirect(
new URL(correctDashboardUrl || URLS.DASHBOARD, req.url),
);
}
}
return NextResponse.next();
}
// Handle protected routes
if (isMatchingRoute(pathname, ROUTES.PROTECTED_ROUTES)) {
const authCheck = handleAuthCheck(isAuthenticated, req);
if (authCheck) return authCheck;
const onboardingCheck = handleOnboardingCheck(isOnboarded, pathname, req);
if (onboardingCheck) return onboardingCheck;
if (pathname === URLS.ONBOARDING && isOnboarded && token) {
const dashboardRedirect = handleDashboardRedirect(token, pathname, req);
if (dashboardRedirect) return dashboardRedirect;
}
if (pathname.startsWith("/dashboard/") && token) {
const dashboardRedirect = handleDashboardRedirect(token, pathname, req);
if (dashboardRedirect) return dashboardRedirect;
}
return NextResponse.next();
}
// Handle other authenticated routes
if (isAuthenticated) {
const onboardingCheck = handleOnboardingCheck(isOnboarded, pathname, req);
if (onboardingCheck) return onboardingCheck;
}
// Allow access to all other routes
return NextResponse.next();
}
export const config = {
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
};