82 lines
2.1 KiB
TypeScript
82 lines
2.1 KiB
TypeScript
import { NextResponse } from "next/server";
|
||
import type { NextRequest } from "next/server";
|
||
import { jwtVerify } from "jose";
|
||
|
||
const COOKIE_NAME = "auth_token";
|
||
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);
|
||
|
||
function isExpired(exp?: number) {
|
||
return exp ? exp * 1000 <= Date.now() : false;
|
||
}
|
||
|
||
async function validateToken(token: string) {
|
||
const raw = token.startsWith("Bearer ") ? token.slice(7) : token;
|
||
|
||
try {
|
||
const { payload } = await jwtVerify(raw, JWT_SECRET, {
|
||
algorithms: ["HS256"],
|
||
});
|
||
|
||
return payload as {
|
||
exp?: number;
|
||
MustChangePassword?: boolean;
|
||
[key: string]: unknown;
|
||
};
|
||
} catch (err) {
|
||
console.error("Token validation error:", err);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
export async function middleware(request: NextRequest) {
|
||
const token = request.cookies.get(COOKIE_NAME)?.value;
|
||
const loginUrl = new URL("/login", request.url);
|
||
const currentPath = request.nextUrl.pathname;
|
||
|
||
// 1️⃣ No token
|
||
if (!token) {
|
||
loginUrl.searchParams.set("reason", "no-token");
|
||
loginUrl.searchParams.set("redirect", currentPath);
|
||
return NextResponse.redirect(loginUrl);
|
||
}
|
||
|
||
// 2️⃣ Validate + decode
|
||
const payload = await validateToken(token);
|
||
|
||
if (!payload) {
|
||
const res = NextResponse.redirect(loginUrl);
|
||
res.cookies.delete(COOKIE_NAME);
|
||
loginUrl.searchParams.set("reason", "invalid-token");
|
||
loginUrl.searchParams.set("redirect", currentPath);
|
||
return res;
|
||
}
|
||
|
||
// 3️⃣ Expiry check
|
||
if (isExpired(payload.exp)) {
|
||
const res = NextResponse.redirect(loginUrl);
|
||
res.cookies.delete(COOKIE_NAME);
|
||
loginUrl.searchParams.set("reason", "expired-token");
|
||
loginUrl.searchParams.set("redirect", currentPath);
|
||
return res;
|
||
}
|
||
|
||
// 4️⃣ Must change password check
|
||
if (payload.MustChangePassword) {
|
||
loginUrl.searchParams.set("reason", "change-password");
|
||
loginUrl.searchParams.set("redirect", currentPath);
|
||
return NextResponse.redirect(loginUrl);
|
||
}
|
||
|
||
// ✅ All good
|
||
return NextResponse.next();
|
||
}
|
||
|
||
export const config = {
|
||
matcher: [
|
||
"/dashboard/:path*",
|
||
"/settings/:path*",
|
||
"/admin/:path*",
|
||
"/change-password/:path*",
|
||
],
|
||
};
|