-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmiddleware.ts
More file actions
68 lines (53 loc) · 2.19 KB
/
middleware.ts
File metadata and controls
68 lines (53 loc) · 2.19 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
import { NextResponse, type NextRequest } from 'next/server';
import { verifySessionToken } from './src/lib/authToken';
const COOKIE_NAME = 'cf_admin';
function getSecret(): string {
return process.env.ADMIN_SESSION_SECRET ?? process.env.ADMIN_SECRET ?? '';
}
function isPublicPath(pathname: string): boolean {
if (pathname.startsWith('/_next')) return true;
if (pathname === '/favicon.ico') return true;
if (pathname === '/favicon.png') return true;
if (pathname === '/robots.txt') return true;
if (pathname === '/sitemap.xml') return true;
// Auth endpoints + login page
if (pathname === '/login') return true;
if (pathname.startsWith('/api/auth/')) return true;
// External webhook (authenticated by signature, not cookies)
if (pathname === '/api/razorpay/webhook' || pathname.startsWith('/api/razorpay/webhook/')) return true;
// Career Booster — career webhook + portal has its own JWT auth
if (pathname === '/api/career/webhook') return true;
if (pathname.startsWith('/api/career/auth/')) return true; // includes magic-link, verify, pin-login (set-pin requires session)
if (pathname.startsWith('/api/career/portal/')) return true;
if (pathname.startsWith('/portal')) return true;
return false;
}
export async function middleware(request: NextRequest) {
const { pathname, search } = request.nextUrl;
if (isPublicPath(pathname)) {
return NextResponse.next();
}
try {
const secret = getSecret();
const token = request.cookies.get(COOKIE_NAME)?.value ?? '';
// If no secret is configured, always block — misconfiguration must not grant access
if (!secret) {
throw new Error('ADMIN_SESSION_SECRET is not configured');
}
const ok = await verifySessionToken(secret, token);
if (ok) return NextResponse.next();
} catch {
// Fall through to redirect/401 below
}
if (pathname.startsWith('/api/')) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const next = `${pathname}${search}`;
const url = request.nextUrl.clone();
url.pathname = '/login';
url.searchParams.set('next', next);
return NextResponse.redirect(url);
}
export const config = {
matcher: ['/((?!_next/static|_next/image).*)'],
};