-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmiddleware.js
More file actions
145 lines (126 loc) · 4.29 KB
/
middleware.js
File metadata and controls
145 lines (126 loc) · 4.29 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
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
import { authMiddleware } from "@clerk/nextjs";
import { NextResponse } from "next/server";
import { jwtVerify } from "jose";
/**
* Main Application Middleware
*
* Handles authentication for all routes:
* - Admin routes: JWT token verification
* - Other routes: Clerk authentication
*
* @param {Request} request - The incoming request
* @returns {NextResponse} The middleware response
*/
export default async function middleware(request) {
const pathname = request.nextUrl.pathname;
// Allow public quests routes (but not /api/quests/user which requires auth)
if (pathname.startsWith("/api/quests") && pathname !== "/api/quests/user") {
return NextResponse.next();
}
// Check if the request is for admin routes
if (pathname.startsWith("/admin") || pathname.startsWith("/api/admin")) {
const loginPath = "/admin/login";
const authPath = "/api/admin/auth";
// Allow access to login page and auth endpoint
if (
request.nextUrl.pathname === loginPath ||
request.nextUrl.pathname === authPath
) {
return NextResponse.next();
}
// Check for admin authentication
const authHeader = request.headers.get("authorization");
const adminToken = request.cookies.get("adminToken")?.value;
// Get token from either Authorization header (Bearer token) or cookie
let token = null;
if (authHeader && authHeader.startsWith("Bearer ")) {
token = authHeader.slice(7);
} else if (adminToken) {
token = adminToken;
}
// No token found
if (!token) {
// If it's an API request, return 401
if (request.nextUrl.pathname.startsWith("/api/")) {
return NextResponse.json(
{
error: "Unauthorized",
message: "No authentication token provided",
},
{
status: 401,
headers: { "WWW-Authenticate": 'Bearer realm="Admin Access"' },
}
);
}
// Otherwise redirect to login
return NextResponse.redirect(new URL(loginPath, request.url));
}
// Verify JWT token
try {
const secret = process.env.ADMIN_JWT_SECRET;
if (!secret) {
console.error("ADMIN_JWT_SECRET not configured");
// If it's an API request, return 500
if (request.nextUrl.pathname.startsWith("/api/")) {
return NextResponse.json(
{ error: "Server configuration error" },
{ status: 500 }
);
}
return NextResponse.redirect(new URL(loginPath, request.url));
}
// Verify the JWT token using jose (Edge-compatible)
const { payload } = await jwtVerify(
token,
new TextEncoder().encode(secret)
);
// Check if token has admin role
if (payload.role !== "admin") {
throw new Error("Invalid role");
}
// Token is valid, allow the request to proceed
// Add admin info to headers for downstream use
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-admin-username", payload.username);
requestHeaders.set("x-admin-role", payload.role);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} catch (error) {
// Token verification failed (invalid, expired, or tampered)
console.error("Admin token verification failed:", error.message);
// If it's an API request, return 401
if (request.nextUrl.pathname.startsWith("/api/")) {
return NextResponse.json(
{ error: "Invalid or expired token" },
{
status: 401,
headers: { "WWW-Authenticate": 'Bearer realm="Admin Access"' },
}
);
}
// Redirect to login and clear the invalid token
const response = NextResponse.redirect(new URL(loginPath, request.url));
response.cookies.delete("adminToken");
return response;
}
}
// For non-admin routes, use Clerk authentication
const clerkMiddleware = authMiddleware({
publicRoutes: [
"/",
"sign-in",
"sign-up",
"/api/video-search",
// Note: /api/quests routes are handled above, before Clerk middleware
],
ignoredRoutes: ["/api/webhooks(.*)"],
});
return clerkMiddleware(request);
}
export const config = {
matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};