v1.0.4: Security hardening - rate limiting, middleware, HSTS, password strength, anti-enumeration
This commit is contained in:
@@ -2,9 +2,16 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/db'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { rateLimit, getClientIp, rateLimitResponse } from '@/lib/rate-limit'
|
||||
|
||||
const changePwLimiter = rateLimit({ id: 'change-pw', max: 5, windowSeconds: 60 * 15 })
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const ip = getClientIp(req)
|
||||
const rl = changePwLimiter.check(ip)
|
||||
if (!rl.success) return rateLimitResponse(rl.resetAt)
|
||||
|
||||
const user = await getSession()
|
||||
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
||||
|
||||
@@ -14,8 +21,8 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: 'Beide Felder sind erforderlich' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (newPassword.length < 6) {
|
||||
return NextResponse.json({ error: 'Neues Kennwort muss mindestens 6 Zeichen lang sein' }, { status: 400 })
|
||||
if (newPassword.length < 8) {
|
||||
return NextResponse.json({ error: 'Neues Kennwort muss mindestens 8 Zeichen lang sein' }, { status: 400 })
|
||||
}
|
||||
|
||||
const dbUser = await (prisma as any).user.findUnique({
|
||||
|
||||
@@ -3,10 +3,15 @@ import { prisma } from '@/lib/db'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { cookies } from 'next/headers'
|
||||
import { deleteAccountLimiter, getClientIp, rateLimitResponse } from '@/lib/rate-limit'
|
||||
|
||||
// POST: User deletes their own account
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const ip = getClientIp(req)
|
||||
const rl = deleteAccountLimiter.check(ip)
|
||||
if (!rl.success) return rateLimitResponse(rl.resetAt)
|
||||
|
||||
const session = await getSession()
|
||||
if (!session) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
||||
|
||||
|
||||
@@ -2,9 +2,14 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/db'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { sendEmail, getSmtpConfig } from '@/lib/email'
|
||||
import { forgotPasswordLimiter, getClientIp, rateLimitResponse } from '@/lib/rate-limit'
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const ip = getClientIp(req)
|
||||
const rl = forgotPasswordLimiter.check(ip)
|
||||
if (!rl.success) return rateLimitResponse(rl.resetAt)
|
||||
|
||||
const { email } = await req.json()
|
||||
if (!email) {
|
||||
return NextResponse.json({ error: 'E-Mail erforderlich' }, { status: 400 })
|
||||
|
||||
@@ -3,9 +3,14 @@ import { cookies } from 'next/headers'
|
||||
import { login, createToken } from '@/lib/auth'
|
||||
import { loginSchema } from '@/lib/validations'
|
||||
import { prisma } from '@/lib/db'
|
||||
import { loginLimiter, getClientIp, rateLimitResponse } from '@/lib/rate-limit'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const ip = getClientIp(request)
|
||||
const rl = loginLimiter.check(ip)
|
||||
if (!rl.success) return rateLimitResponse(rl.resetAt)
|
||||
|
||||
const body = await request.json()
|
||||
|
||||
const validated = loginSchema.safeParse(body)
|
||||
|
||||
@@ -4,16 +4,21 @@ import { hashPassword } from '@/lib/auth'
|
||||
import { sendEmail } from '@/lib/email'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { z } from 'zod'
|
||||
import { registerLimiter, getClientIp, rateLimitResponse } from '@/lib/rate-limit'
|
||||
|
||||
const registerSchema = z.object({
|
||||
organizationName: z.string().min(2, 'Organisationsname zu kurz').max(200),
|
||||
name: z.string().min(2, 'Name zu kurz').max(200),
|
||||
email: z.string().email('Ungültige E-Mail-Adresse'),
|
||||
password: z.string().min(6, 'Passwort muss mindestens 6 Zeichen haben'),
|
||||
password: z.string().min(8, 'Passwort muss mindestens 8 Zeichen haben'),
|
||||
})
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const ip = getClientIp(req)
|
||||
const rl = registerLimiter.check(ip)
|
||||
if (!rl.success) return rateLimitResponse(rl.resetAt)
|
||||
|
||||
const body = await req.json()
|
||||
const data = registerSchema.parse(body)
|
||||
|
||||
|
||||
@@ -2,9 +2,14 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/db'
|
||||
import { sendEmail } from '@/lib/email'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { resendVerificationLimiter, getClientIp, rateLimitResponse } from '@/lib/rate-limit'
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const ip = getClientIp(req)
|
||||
const rl = resendVerificationLimiter.check(ip)
|
||||
if (!rl.success) return rateLimitResponse(rl.resetAt)
|
||||
|
||||
const { email } = await req.json()
|
||||
|
||||
if (!email) {
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/db'
|
||||
import { hashPassword } from '@/lib/auth'
|
||||
import { resetPasswordLimiter, getClientIp, rateLimitResponse } from '@/lib/rate-limit'
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const ip = getClientIp(req)
|
||||
const rl = resetPasswordLimiter.check(ip)
|
||||
if (!rl.success) return rateLimitResponse(rl.resetAt)
|
||||
|
||||
const { token, password } = await req.json()
|
||||
if (!token || !password) {
|
||||
return NextResponse.json({ error: 'Token und Passwort erforderlich' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return NextResponse.json({ error: 'Passwort muss mindestens 6 Zeichen lang sein' }, { status: 400 })
|
||||
if (password.length < 8) {
|
||||
return NextResponse.json({ error: 'Passwort muss mindestens 8 Zeichen lang sein' }, { status: 400 })
|
||||
}
|
||||
|
||||
const user = await (prisma as any).user.findFirst({
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/db'
|
||||
import { sendEmail, getSmtpConfig } from '@/lib/email'
|
||||
import { z } from 'zod'
|
||||
import { contactLimiter, getClientIp, rateLimitResponse } from '@/lib/rate-limit'
|
||||
|
||||
const contactSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
@@ -23,6 +24,10 @@ async function getContactEmail(): Promise<string> {
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const ip = getClientIp(req)
|
||||
const rl = contactLimiter.check(ip)
|
||||
if (!rl.success) return rateLimitResponse(rl.resetAt)
|
||||
|
||||
const body = await req.json()
|
||||
const data = contactSchema.parse(body)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user