v1.0.4: Security hardening - rate limiting, middleware, HSTS, password strength, anti-enumeration
This commit is contained in:
114
src/middleware.ts
Normal file
114
src/middleware.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { jwtVerify } from 'jose'
|
||||
|
||||
const JWT_SECRET = new TextEncoder().encode(
|
||||
process.env.NEXTAUTH_SECRET || 'dev-only-fallback-do-not-use-in-production'
|
||||
)
|
||||
|
||||
// Routes that require authentication
|
||||
const PROTECTED_ROUTES = ['/app', '/settings', '/admin']
|
||||
|
||||
// Routes that should redirect to /app if already logged in
|
||||
const AUTH_ROUTES = ['/login', '/register']
|
||||
|
||||
// API routes that are public (no auth needed)
|
||||
const PUBLIC_API_PREFIXES = [
|
||||
'/api/auth/login',
|
||||
'/api/auth/register',
|
||||
'/api/auth/forgot-password',
|
||||
'/api/auth/reset-password',
|
||||
'/api/auth/verify-email',
|
||||
'/api/auth/resend-verification',
|
||||
'/api/auth/logout',
|
||||
'/api/contact',
|
||||
'/api/demo',
|
||||
'/api/donate',
|
||||
'/api/rapports/',
|
||||
'/api/tenants/by-slug/',
|
||||
]
|
||||
|
||||
export async function middleware(req: NextRequest) {
|
||||
const { pathname } = req.nextUrl
|
||||
const token = req.cookies.get('auth-token')?.value
|
||||
|
||||
// Verify token if present
|
||||
let user: any = null
|
||||
if (token) {
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, JWT_SECRET)
|
||||
user = payload.user
|
||||
} catch {
|
||||
// Invalid/expired token — clear it
|
||||
const response = NextResponse.redirect(new URL('/login', req.url))
|
||||
response.cookies.delete('auth-token')
|
||||
// Only redirect if accessing protected routes
|
||||
if (PROTECTED_ROUTES.some(r => pathname.startsWith(r))) {
|
||||
return response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Protected routes: redirect to login if not authenticated
|
||||
if (PROTECTED_ROUTES.some(r => pathname.startsWith(r))) {
|
||||
if (!user) {
|
||||
const loginUrl = new URL('/login', req.url)
|
||||
loginUrl.searchParams.set('redirect', pathname)
|
||||
return NextResponse.redirect(loginUrl)
|
||||
}
|
||||
|
||||
// Admin routes: only SERVER_ADMIN and TENANT_ADMIN
|
||||
if (pathname.startsWith('/admin') && user.role !== 'SERVER_ADMIN' && user.role !== 'TENANT_ADMIN') {
|
||||
return NextResponse.redirect(new URL('/app', req.url))
|
||||
}
|
||||
}
|
||||
|
||||
// Auth routes: redirect to /app if already logged in
|
||||
if (AUTH_ROUTES.some(r => pathname.startsWith(r))) {
|
||||
if (user) {
|
||||
return NextResponse.redirect(new URL('/app', req.url))
|
||||
}
|
||||
}
|
||||
|
||||
// API routes: check auth for non-public endpoints
|
||||
if (pathname.startsWith('/api/') && !PUBLIC_API_PREFIXES.some(p => pathname.startsWith(p))) {
|
||||
if (!user) {
|
||||
// Allow /api/auth/me to return null (used for auth check)
|
||||
if (pathname === '/api/auth/me') {
|
||||
return NextResponse.next()
|
||||
}
|
||||
// Allow /api/icons GET (public for symbol loading)
|
||||
if (pathname === '/api/icons' && req.method === 'GET') {
|
||||
return NextResponse.next()
|
||||
}
|
||||
return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
||||
}
|
||||
}
|
||||
|
||||
// Security: block common attack paths
|
||||
if (
|
||||
pathname.includes('..') ||
|
||||
pathname.includes('.env') ||
|
||||
pathname.includes('wp-admin') ||
|
||||
pathname.includes('wp-login') ||
|
||||
pathname.includes('.php') ||
|
||||
pathname.includes('xmlrpc') ||
|
||||
pathname.match(/\.(sql|bak|config|log|ini)$/i)
|
||||
) {
|
||||
return new NextResponse(null, { status: 404 })
|
||||
}
|
||||
|
||||
return NextResponse.next()
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except:
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization)
|
||||
* - favicon.ico, sitemap.xml, robots.txt
|
||||
* - public files (images, sw.js, etc.)
|
||||
*/
|
||||
'/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|icons/|sw.js|manifest.json|opengraph-image).*)',
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user