opentriage-api / src /middleware.ts
KrishnaCosmic's picture
apply changes
db8667c
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { isPublicRoute, requiresAuth, getRequiredRole } from "@/lib/routeConfig";
// CORS configuration
const allowedOrigins = [
"http://localhost:5173", // Vite dev
"http://localhost:3000", // Next.js dev
"https://open-triage.vercel.app",
"https://opentriage.onrender.com",
"https://open-triage-6r2y.vercel.app", // Specific preview deployment
];
// JWT Secret for token verification
const JWT_SECRET = process.env.JWT_SECRET;
/**
* Verify JWT token and extract payload
*/
function verifyToken(token: string): { user_id: string; role: string | null } | null {
try {
// Simple JWT verification (base64 decode and verify signature)
const parts = token.split('.');
if (parts.length !== 3) return null;
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
// Check expiration
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
return null;
}
return {
user_id: payload.user_id,
role: payload.role || null
};
} catch {
return null;
}
}
export function middleware(request: NextRequest) {
const origin = request.headers.get("origin") || "";
// Allow any Vercel preview deployment or listed origins
const isAllowedOrigin = allowedOrigins.includes(origin) || origin.endsWith(".vercel.app");
const pathname = request.nextUrl.pathname;
// Handle preflight requests
if (request.method === "OPTIONS") {
return new NextResponse(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": isAllowedOrigin ? origin : "",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400",
},
});
}
// Check if route requires authentication
if (requiresAuth(pathname)) {
const authHeader = request.headers.get("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return NextResponse.json(
{ error: "Unauthorized", message: "Authentication required" },
{
status: 401,
headers: {
"Access-Control-Allow-Origin": isAllowedOrigin ? origin : "",
"Access-Control-Allow-Credentials": "true",
}
}
);
}
const token = authHeader.substring(7);
const payload = verifyToken(token);
if (!payload) {
return NextResponse.json(
{ error: "Unauthorized", message: "Invalid or expired token" },
{
status: 401,
headers: {
"Access-Control-Allow-Origin": isAllowedOrigin ? origin : "",
"Access-Control-Allow-Credentials": "true",
}
}
);
}
// Check role-based access
const requiredRole = getRequiredRole(pathname);
if (requiredRole && payload.role !== requiredRole) {
return NextResponse.json(
{ error: "Forbidden", message: `${requiredRole} role required` },
{
status: 403,
headers: {
"Access-Control-Allow-Origin": isAllowedOrigin ? origin : "",
"Access-Control-Allow-Credentials": "true",
}
}
);
}
}
// Handle actual requests
const response = NextResponse.next();
if (isAllowedOrigin) {
response.headers.set("Access-Control-Allow-Origin", origin);
response.headers.set("Access-Control-Allow-Credentials", "true");
}
return response;
}
// Apply middleware to all API routes
export const config = {
matcher: "/api/:path*",
};