mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 22:04:10 +00:00
fix: apply CSRF protection and centralize rate limiting
- Migrate /api/posts from legacy checkRateLimit to central searchLimiter - Add IP blocklist check and rateLimitHeaders to /api/posts - Apply CSRF validation to /api/send-email (POST) - Apply CSRF validation to /api/users/login (POST) - Apply CSRF validation to /api/auth/login (POST) CSRF protection uses the Double Submit Cookie pattern which: - Skips safe methods (GET, HEAD, OPTIONS) - Allows server-to-server requests with Authorization header - Validates Origin header for browser requests - Requires matching tokens in header and cookie for browser POSTs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
cb2e903db5
commit
2fae62eaf3
4 changed files with 51 additions and 11 deletions
|
|
@ -4,32 +4,41 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { getPayload } from 'payload'
|
import { getPayload } from 'payload'
|
||||||
import config from '@payload-config'
|
import config from '@payload-config'
|
||||||
import { getPostsByCategory, checkRateLimit } from '@/lib/search'
|
import { getPostsByCategory } from '@/lib/search'
|
||||||
import type { Category } from '@/payload-types'
|
import type { Category } from '@/payload-types'
|
||||||
|
import {
|
||||||
|
searchLimiter,
|
||||||
|
rateLimitHeaders,
|
||||||
|
getClientIpFromRequest,
|
||||||
|
isIpBlocked,
|
||||||
|
} from '@/lib/security'
|
||||||
|
|
||||||
// Validation constants
|
// Validation constants
|
||||||
const MAX_LIMIT = 50
|
const MAX_LIMIT = 50
|
||||||
const DEFAULT_LIMIT = 10
|
const DEFAULT_LIMIT = 10
|
||||||
|
const POSTS_RATE_LIMIT = 30
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
// Rate limiting
|
// IP-Blocklist prüfen
|
||||||
const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
|
const ip = getClientIpFromRequest(request)
|
||||||
request.headers.get('x-real-ip') ||
|
if (isIpBlocked(ip)) {
|
||||||
'unknown'
|
return NextResponse.json(
|
||||||
|
{ error: 'Access denied' },
|
||||||
|
{ status: 403 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const rateLimit = checkRateLimit(ip)
|
// Rate limiting (zentral)
|
||||||
|
const rateLimit = await searchLimiter.check(ip)
|
||||||
|
|
||||||
if (!rateLimit.allowed) {
|
if (!rateLimit.allowed) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Too many requests. Please try again later.' },
|
{ error: 'Too many requests. Please try again later.' },
|
||||||
{
|
{
|
||||||
status: 429,
|
status: 429,
|
||||||
headers: {
|
headers: rateLimitHeaders(rateLimit, POSTS_RATE_LIMIT),
|
||||||
'Retry-After': String(rateLimit.retryAfter || 60),
|
|
||||||
'X-RateLimit-Remaining': '0',
|
|
||||||
},
|
},
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
rateLimitHeaders,
|
rateLimitHeaders,
|
||||||
getClientIpFromRequest,
|
getClientIpFromRequest,
|
||||||
isIpBlocked,
|
isIpBlocked,
|
||||||
|
validateCsrf,
|
||||||
} from '@/lib/security'
|
} from '@/lib/security'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -53,6 +54,15 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CSRF-Schutz für Browser-basierte Requests
|
||||||
|
const csrfResult = validateCsrf(req)
|
||||||
|
if (!csrfResult.valid) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: 'CSRF validation failed' },
|
||||||
|
{ status: 403 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Rate-Limiting prüfen (Anti-Brute-Force)
|
// Rate-Limiting prüfen (Anti-Brute-Force)
|
||||||
const rateLimit = await authLimiter.check(clientIp)
|
const rateLimit = await authLimiter.check(clientIp)
|
||||||
if (!rateLimit.allowed) {
|
if (!rateLimit.allowed) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
rateLimitHeaders,
|
rateLimitHeaders,
|
||||||
validateIpAccess,
|
validateIpAccess,
|
||||||
createSafeLogger,
|
createSafeLogger,
|
||||||
|
validateCsrf,
|
||||||
} from '@/lib/security'
|
} from '@/lib/security'
|
||||||
|
|
||||||
const RATE_LIMIT_MAX = 10
|
const RATE_LIMIT_MAX = 10
|
||||||
|
|
@ -74,6 +75,16 @@ export async function POST(req: NextRequest) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CSRF-Schutz für Browser-basierte Requests
|
||||||
|
const csrfResult = validateCsrf(req)
|
||||||
|
if (!csrfResult.valid) {
|
||||||
|
logger.warn('CSRF validation failed', { reason: csrfResult.reason })
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'CSRF validation failed' },
|
||||||
|
{ status: 403 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const payload = await getPayload({ config })
|
const payload = await getPayload({ config })
|
||||||
|
|
||||||
// Authentifizierung prüfen (aus Cookie/Header)
|
// Authentifizierung prüfen (aus Cookie/Header)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import {
|
||||||
rateLimitHeaders,
|
rateLimitHeaders,
|
||||||
getClientIpFromRequest,
|
getClientIpFromRequest,
|
||||||
isIpBlocked,
|
isIpBlocked,
|
||||||
|
validateCsrf,
|
||||||
} from '@/lib/security'
|
} from '@/lib/security'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -50,6 +51,15 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CSRF-Schutz für Browser-basierte Requests
|
||||||
|
const csrfResult = validateCsrf(req)
|
||||||
|
if (!csrfResult.valid) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ errors: [{ message: 'CSRF validation failed' }] },
|
||||||
|
{ status: 403 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Rate-Limiting prüfen (Anti-Brute-Force)
|
// Rate-Limiting prüfen (Anti-Brute-Force)
|
||||||
const rateLimit = await authLimiter.check(clientIp)
|
const rateLimit = await authLimiter.check(clientIp)
|
||||||
if (!rateLimit.allowed) {
|
if (!rateLimit.allowed) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue