beachPartyBalloons/estore/src/middleware.ts
chris 21ebb9667b Add 'estore/' from commit 'e34dfc397c94025670baa2b73b482c01f3033a6a'
git-subtree-dir: estore
git-subtree-mainline: 746868d720b9be1003a2f783b7a12d526d8eea60
git-subtree-split: e34dfc397c94025670baa2b73b482c01f3033a6a
2026-04-13 19:22:23 -04:00

61 lines
1.9 KiB
TypeScript

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<string> {
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*'],
}