Files
portfolio/lib/auth.ts
denshooter c7bc0ecb1d feat: production deployment configuration for dk0.dev
- 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
2025-10-19 21:48:26 +02:00

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()
};
}