62 lines
1.5 KiB
TypeScript
62 lines
1.5 KiB
TypeScript
|
|
import { NextResponse } from 'next/server';
|
||
|
|
import type { NextRequest } from 'next/server';
|
||
|
|
|
||
|
|
// Basic Auth configuration
|
||
|
|
const AUTH_USERNAME = process.env.AUTH_USERNAME || 'admin';
|
||
|
|
const AUTH_PASSWORD = process.env.AUTH_PASSWORD || 'openclaw';
|
||
|
|
|
||
|
|
// Skip auth for public routes (if any)
|
||
|
|
const publicRoutes = ['/api/status']; // Allow status API without auth for health checks
|
||
|
|
|
||
|
|
function basicAuth(req: NextRequest) {
|
||
|
|
const authHeader = req.headers.get('authorization');
|
||
|
|
|
||
|
|
if (!authHeader) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const [type, credentials] = authHeader.split(' ');
|
||
|
|
|
||
|
|
if (type !== 'Basic') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const decoded = Buffer.from(credentials, 'base64').toString('utf-8');
|
||
|
|
const [username, password] = decoded.split(':');
|
||
|
|
|
||
|
|
return username === AUTH_USERNAME && password === AUTH_PASSWORD;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function middleware(req: NextRequest) {
|
||
|
|
// Skip auth for public routes
|
||
|
|
if (publicRoutes.some(route => req.nextUrl.pathname.startsWith(route))) {
|
||
|
|
return NextResponse.next();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!basicAuth(req)) {
|
||
|
|
return new NextResponse(
|
||
|
|
'Authentication required',
|
||
|
|
{
|
||
|
|
status: 401,
|
||
|
|
headers: {
|
||
|
|
'WWW-Authenticate': 'Basic realm="OpenClaw Dashboard"',
|
||
|
|
},
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return NextResponse.next();
|
||
|
|
}
|
||
|
|
|
||
|
|
export const config = {
|
||
|
|
matcher: [
|
||
|
|
/*
|
||
|
|
* Match all request paths except for the ones starting with:
|
||
|
|
* - _next/static (static files)
|
||
|
|
* - _next/image (image optimization files)
|
||
|
|
* - favicon.ico (favicon file)
|
||
|
|
*/
|
||
|
|
'/((?!_next/static|_next/image|favicon.ico).*)',
|
||
|
|
],
|
||
|
|
};
|