96 lines
2.9 KiB
TypeScript
96 lines
2.9 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { PrismaClient, Curator } from '@prisma/client';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
export type StaffContext =
|
|
| { role: 'admin' }
|
|
| { role: 'curator'; curator: Curator };
|
|
|
|
/**
|
|
* Authentication middleware for admin API routes
|
|
* Verifies that the request includes a valid admin session token
|
|
*/
|
|
export async function requireAdminAuth(request: NextRequest): Promise<NextResponse | null> {
|
|
const authHeader = request.headers.get('x-admin-auth');
|
|
|
|
if (!authHeader || authHeader !== 'authenticated') {
|
|
return NextResponse.json(
|
|
{ error: 'Unauthorized - Admin authentication required' },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
|
|
return null; // Auth successful
|
|
}
|
|
|
|
/**
|
|
* Resolve current staff (admin or curator) from headers.
|
|
*
|
|
* Admin:
|
|
* - x-admin-auth: 'authenticated'
|
|
*
|
|
* Curator:
|
|
* - x-curator-auth: 'authenticated'
|
|
* - x-curator-username: <username>
|
|
*/
|
|
export async function getStaffContext(request: NextRequest): Promise<StaffContext | null> {
|
|
const adminHeader = request.headers.get('x-admin-auth');
|
|
if (adminHeader === 'authenticated') {
|
|
return { role: 'admin' };
|
|
}
|
|
|
|
const curatorAuth = request.headers.get('x-curator-auth');
|
|
const curatorUsername = request.headers.get('x-curator-username');
|
|
|
|
if (curatorAuth === 'authenticated' && curatorUsername) {
|
|
const curator = await prisma.curator.findUnique({
|
|
where: { username: curatorUsername },
|
|
});
|
|
|
|
if (curator) {
|
|
return { role: 'curator', curator };
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Require that the current request is authenticated as staff (admin or curator).
|
|
* Returns either an error response or a resolved context.
|
|
*/
|
|
export async function requireStaffAuth(request: NextRequest): Promise<{ error?: NextResponse; context?: StaffContext }> {
|
|
const context = await getStaffContext(request);
|
|
|
|
if (!context) {
|
|
return {
|
|
error: NextResponse.json(
|
|
{ error: 'Unauthorized - Staff authentication required' },
|
|
{ status: 401 }
|
|
),
|
|
};
|
|
}
|
|
|
|
return { context };
|
|
}
|
|
|
|
/**
|
|
* Helper to verify admin password
|
|
*/
|
|
export async function verifyAdminPassword(password: string): Promise<boolean> {
|
|
const bcrypt = await import('bcryptjs');
|
|
|
|
// Validate that ADMIN_PASSWORD is set (security best practice)
|
|
if (!process.env.ADMIN_PASSWORD) {
|
|
console.error('SECURITY WARNING: ADMIN_PASSWORD environment variable is not set!');
|
|
// Fallback to default hash only in development
|
|
if (process.env.NODE_ENV === 'production') {
|
|
throw new Error('ADMIN_PASSWORD environment variable is required in production');
|
|
}
|
|
}
|
|
|
|
const adminPasswordHash = process.env.ADMIN_PASSWORD || '$2b$10$SHOt9G1qUNIvHoWre7499.eEtp5PtOII0daOQGNV.dhDEuPmOUdsq';
|
|
return bcrypt.compare(password, adminPasswordHash);
|
|
}
|