import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' const COOKIE = 'admin_token' /** Constant-time string comparison to prevent timing attacks */ function safeEqual(a: string, b: string): boolean { if (a.length !== b.length) return false let diff = 0 for (let i = 0; i < a.length; i++) { diff |= a.charCodeAt(i) ^ b.charCodeAt(i) } return diff === 0 } /** * Derive a session token from the admin password using SHA-256. * The raw password is never stored in the cookie. */ async function deriveSessionToken(password: string): Promise { const data = new TextEncoder().encode(`admin-session-v1:${password}`) const hash = await crypto.subtle.digest('SHA-256', data) return Array.from(new Uint8Array(hash)) .map((b) => b.toString(16).padStart(2, '0')) .join('') } export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl if (pathname === '/shop/admin/login' || pathname === '/api/admin/login') { return NextResponse.next() } if (pathname.startsWith('/shop/admin') || pathname.startsWith('/api/admin')) { const token = request.cookies.get(COOKIE)?.value const password = process.env.ADMIN_PASSWORD if (!password || !token) { if (pathname.startsWith('/api/')) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } return NextResponse.redirect(new URL('/shop/admin/login', request.url)) } const expected = await deriveSessionToken(password) if (!safeEqual(token, expected)) { if (pathname.startsWith('/api/')) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } return NextResponse.redirect(new URL('/shop/admin/login', request.url)) } } return NextResponse.next() } export const config = { matcher: ['/shop/admin/:path*', '/api/admin/:path*'], }