feat(aep): carte AEP — push Gitea 2026-04-28
This commit is contained in:
87
server/utils/rateLimitJson.ts
Normal file
87
server/utils/rateLimitJson.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Rate limiting via fichiers JSON locaux
|
||||
* Implémentation recommandée spec F §8 : /tmp/nav-ratelimit/{IP_hash}.json
|
||||
*
|
||||
* - IP hashée SHA-256 (RGPD — pas de stockage IP en clair)
|
||||
* - Fichier JSON par IP, reset automatique si date != today
|
||||
* - Dossier créé au premier appel si absent
|
||||
*
|
||||
* Usage :
|
||||
* const allowed = await checkRateLimitJson(ip, 'chatbot', 10)
|
||||
* if (!allowed) throw createError({ statusCode: 429, ... })
|
||||
*/
|
||||
|
||||
import { createHash } from 'crypto'
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
const RATELIMIT_DIR = '/tmp/nav-ratelimit'
|
||||
|
||||
type RateLimitFile = {
|
||||
[action: string]: { count: number; date: string }
|
||||
}
|
||||
|
||||
function ensureDir() {
|
||||
if (!existsSync(RATELIMIT_DIR)) {
|
||||
mkdirSync(RATELIMIT_DIR, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
function hashIp(ip: string): string {
|
||||
return createHash('sha256').update(ip).digest('hex')
|
||||
}
|
||||
|
||||
function todayStr(): string {
|
||||
const d = new Date()
|
||||
return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function readFile(ipHash: string): RateLimitFile {
|
||||
const path = join(RATELIMIT_DIR, `${ipHash}.json`)
|
||||
try {
|
||||
return JSON.parse(readFileSync(path, 'utf-8'))
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function writeFile(ipHash: string, data: RateLimitFile) {
|
||||
const path = join(RATELIMIT_DIR, `${ipHash}.json`)
|
||||
writeFileSync(path, JSON.stringify(data), 'utf-8')
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie et incrémente le compteur pour une IP et une action.
|
||||
* @param ip Adresse IP du client (sera hashée SHA-256)
|
||||
* @param action Clé d'action (ex : 'chatbot', 'submit', 'comment')
|
||||
* @param maxPerDay Nombre max d'appels autorisés par jour
|
||||
* @returns true si autorisé, false si limite dépassée
|
||||
*/
|
||||
export function checkRateLimitJson(
|
||||
ip: string,
|
||||
action: string,
|
||||
maxPerDay: number,
|
||||
): boolean {
|
||||
ensureDir()
|
||||
|
||||
const ipHash = hashIp(ip)
|
||||
const today = todayStr()
|
||||
const data = readFile(ipHash)
|
||||
|
||||
const entry = data[action]
|
||||
|
||||
if (!entry || entry.date !== today) {
|
||||
// Nouveau jour ou premier appel : reset et autoriser
|
||||
data[action] = { count: 1, date: today }
|
||||
writeFile(ipHash, data)
|
||||
return true
|
||||
}
|
||||
|
||||
if (entry.count >= maxPerDay) {
|
||||
return false
|
||||
}
|
||||
|
||||
entry.count++
|
||||
writeFile(ipHash, data)
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user