- Fixed authentication system (removed HTTP Basic Auth popup) - Added session-based authentication with proper logout - Updated rate limiting (20 req/s for login, 5 req/m for admin) - Created production deployment scripts and configs - Updated nginx configuration for dk0.dev domain - Added comprehensive production deployment guide - Fixed logout button functionality - Optimized for production with proper resource limits
139 lines
4.0 KiB
TypeScript
139 lines
4.0 KiB
TypeScript
import { NextRequest } from 'next/server';
|
|
|
|
// Server-side authentication utilities
|
|
export function verifyAdminAuth(request: NextRequest): boolean {
|
|
// Check for basic auth header
|
|
const authHeader = request.headers.get('authorization');
|
|
|
|
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const base64Credentials = authHeader.split(' ')[1];
|
|
const credentials = atob(base64Credentials);
|
|
const [username, password] = credentials.split(':');
|
|
|
|
// Get admin credentials from environment
|
|
const adminAuth = process.env.ADMIN_BASIC_AUTH || 'admin:default_password_change_me';
|
|
const [expectedUsername, expectedPassword] = adminAuth.split(':');
|
|
|
|
return username === expectedUsername && password === expectedPassword;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function requireAdminAuth(request: NextRequest): Response | null {
|
|
if (!verifyAdminAuth(request)) {
|
|
return new Response(
|
|
JSON.stringify({ error: 'Unauthorized' }),
|
|
{
|
|
status: 401,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Session-based authentication (no browser popup)
|
|
export function verifySessionAuth(request: NextRequest): boolean {
|
|
// Check for session token in headers
|
|
const sessionToken = request.headers.get('x-session-token');
|
|
if (!sessionToken) return false;
|
|
|
|
try {
|
|
// Decode and validate session token
|
|
const decodedJson = atob(sessionToken);
|
|
const sessionData = JSON.parse(decodedJson);
|
|
|
|
// Validate session data structure
|
|
if (!sessionData.timestamp || !sessionData.random || !sessionData.ip || !sessionData.userAgent) {
|
|
return false;
|
|
}
|
|
|
|
// Check if session is still valid (2 hours)
|
|
const sessionTime = sessionData.timestamp;
|
|
const now = Date.now();
|
|
const sessionDuration = 2 * 60 * 60 * 1000; // 2 hours
|
|
|
|
if (now - sessionTime > sessionDuration) {
|
|
return false;
|
|
}
|
|
|
|
// Validate IP address (optional, but good security practice)
|
|
const currentIp = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown';
|
|
if (sessionData.ip !== currentIp) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function requireSessionAuth(request: NextRequest): Response | null {
|
|
if (!verifySessionAuth(request)) {
|
|
return new Response(
|
|
JSON.stringify({ error: 'Session expired or invalid' }),
|
|
{
|
|
status: 401,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Rate limiting for admin endpoints
|
|
const rateLimitMap = new Map<string, { count: number; resetTime: number }>();
|
|
|
|
// Clear rate limit cache on startup
|
|
if (typeof window === 'undefined') {
|
|
// Server-side: clear cache periodically
|
|
setInterval(() => {
|
|
const now = Date.now();
|
|
for (const [key, value] of rateLimitMap.entries()) {
|
|
if (now > value.resetTime) {
|
|
rateLimitMap.delete(key);
|
|
}
|
|
}
|
|
}, 60000); // Clear every minute
|
|
}
|
|
|
|
export function checkRateLimit(ip: string, maxRequests: number = 10, windowMs: number = 60000): boolean {
|
|
const now = Date.now();
|
|
const key = `admin_${ip}`;
|
|
|
|
const current = rateLimitMap.get(key);
|
|
|
|
if (!current || now > current.resetTime) {
|
|
rateLimitMap.set(key, { count: 1, resetTime: now + windowMs });
|
|
return true;
|
|
}
|
|
|
|
if (current.count >= maxRequests) {
|
|
return false;
|
|
}
|
|
|
|
current.count++;
|
|
return true;
|
|
}
|
|
|
|
export function getRateLimitHeaders(ip: string, maxRequests: number = 10, windowMs: number = 60000): Record<string, string> {
|
|
const current = rateLimitMap.get(`admin_${ip}`);
|
|
const remaining = current ? Math.max(0, maxRequests - current.count) : maxRequests;
|
|
|
|
return {
|
|
'X-RateLimit-Limit': maxRequests.toString(),
|
|
'X-RateLimit-Remaining': remaining.toString(),
|
|
'X-RateLimit-Reset': current ? Math.ceil(current.resetTime / 1000).toString() : Math.ceil((Date.now() + windowMs) / 1000).toString()
|
|
};
|
|
}
|