feat: rate limiting connexion (5 essais / 15 min)
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
checkRateLimit,
|
||||
recordFailedAttempt,
|
||||
clearAttempts,
|
||||
_resetForTests,
|
||||
} from '@/lib/rate-limit'
|
||||
|
||||
beforeEach(() => _resetForTests())
|
||||
|
||||
describe('checkRateLimit', () => {
|
||||
it('autorise une nouvelle clé', () => {
|
||||
expect(checkRateLimit('ip:user@test.com')).toBe(true)
|
||||
})
|
||||
|
||||
it('autorise après moins de 5 tentatives', () => {
|
||||
const key = 'ip:user@test.com'
|
||||
recordFailedAttempt(key)
|
||||
recordFailedAttempt(key)
|
||||
recordFailedAttempt(key)
|
||||
recordFailedAttempt(key)
|
||||
expect(checkRateLimit(key)).toBe(true)
|
||||
})
|
||||
|
||||
it('bloque après 5 tentatives', () => {
|
||||
const key = 'ip:user@test.com'
|
||||
for (let i = 0; i < 5; i++) recordFailedAttempt(key)
|
||||
expect(checkRateLimit(key)).toBe(false)
|
||||
})
|
||||
|
||||
it('débloque après clearAttempts', () => {
|
||||
const key = 'ip:user@test.com'
|
||||
for (let i = 0; i < 5; i++) recordFailedAttempt(key)
|
||||
clearAttempts(key)
|
||||
expect(checkRateLimit(key)).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
const MAX_ATTEMPTS = 5
|
||||
const BLOCK_DURATION_MS = 15 * 60 * 1000 // 15 minutes
|
||||
|
||||
type Entry = { count: number; blockedAt: number | null }
|
||||
let attempts = new Map<string, Entry>()
|
||||
|
||||
export function checkRateLimit(key: string): boolean {
|
||||
const now = Date.now()
|
||||
const entry = attempts.get(key)
|
||||
if (!entry) return true
|
||||
if (entry.blockedAt !== null) {
|
||||
if (now - entry.blockedAt < BLOCK_DURATION_MS) return false
|
||||
attempts.delete(key)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function recordFailedAttempt(key: string): void {
|
||||
const entry = attempts.get(key) ?? { count: 0, blockedAt: null }
|
||||
entry.count += 1
|
||||
if (entry.count >= MAX_ATTEMPTS) entry.blockedAt = Date.now()
|
||||
attempts.set(key, entry)
|
||||
}
|
||||
|
||||
export function clearAttempts(key: string): void {
|
||||
attempts.delete(key)
|
||||
}
|
||||
|
||||
/** Réinitialisation pour les tests uniquement */
|
||||
export function _resetForTests(): void {
|
||||
attempts = new Map()
|
||||
}
|
||||
Reference in New Issue
Block a user