confluent/ConfluentTranslator/auth.js
StillHammer 3cd73e6598 Ajout système d'authentification complet avec interface de connexion
Backend:
- auth.js: Système de tokens avec API keys UUID
- rateLimiter.js: Rate limiting multi-tiers (global, traduction, admin)
- logger.js: Logging des requêtes avec rotation automatique
- adminRoutes.js: Routes admin pour gestion des tokens
- server.js: Intégration de tous les middlewares de sécurité

Frontend:
- Interface de connexion modale élégante
- Stockage sécurisé API key dans localStorage
- Bouton déconnexion dans le header
- authFetch() wrapper pour toutes les requêtes protégées
- Protection automatique des endpoints de traduction

Sécurité:
- Token admin généré automatiquement au premier lancement
- Limites quotidiennes par token configurables
- Rate limiting pour prévenir les abus
- Logs détaillés de toutes les requêtes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 12:01:01 +08:00

207 lines
4.5 KiB
JavaScript

const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const { v4: uuidv4 } = require('uuid');
const fs = require('fs');
const path = require('path');
const TOKENS_FILE = path.join(__dirname, 'data', 'tokens.json');
const JWT_SECRET = process.env.JWT_SECRET || 'confluent-secret-key-change-in-production';
// Structure des tokens
let tokens = {};
function loadTokens() {
try {
const dataDir = path.join(__dirname, 'data');
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
}
if (fs.existsSync(TOKENS_FILE)) {
return JSON.parse(fs.readFileSync(TOKENS_FILE, 'utf8'));
}
} catch (error) {
console.error('Error loading tokens:', error);
}
// Default: créer un token admin si aucun token n'existe
const defaultTokens = {
admin: {
id: 'admin',
name: 'Admin',
role: 'admin',
apiKey: uuidv4(),
createdAt: new Date().toISOString(),
active: true,
requestsToday: 0,
dailyLimit: -1 // illimité
}
};
saveTokens(defaultTokens);
console.log('🔑 Token admin créé:', defaultTokens.admin.apiKey);
return defaultTokens;
}
function saveTokens() {
try {
fs.writeFileSync(TOKENS_FILE, JSON.stringify(tokens, null, 2));
} catch (error) {
console.error('Error saving tokens:', error);
}
}
// Middleware d'authentification
function authenticate(req, res, next) {
// Routes publiques (GET seulement)
const publicRoutes = ['/api/lexique', '/api/stats', '/lexique'];
if (req.method === 'GET' && publicRoutes.some(route => req.path.startsWith(route))) {
return next();
}
const apiKey = req.headers['x-api-key'] || req.query.apiKey;
if (!apiKey) {
return res.status(401).json({ error: 'API key required' });
}
// Chercher le token
const token = Object.values(tokens).find(t => t.apiKey === apiKey);
if (!token) {
return res.status(401).json({ error: 'Invalid API key' });
}
if (!token.active) {
return res.status(403).json({ error: 'Token disabled' });
}
// Vérifier la limite quotidienne
const today = new Date().toISOString().split('T')[0];
const tokenToday = token.lastUsed?.split('T')[0];
if (tokenToday !== today) {
token.requestsToday = 0;
}
if (token.dailyLimit > 0 && token.requestsToday >= token.dailyLimit) {
return res.status(429).json({ error: 'Daily limit reached' });
}
// Mettre à jour les stats
token.requestsToday++;
token.lastUsed = new Date().toISOString();
saveTokens();
// Ajouter les infos au req
req.user = {
id: token.id,
name: token.name,
role: token.role
};
next();
}
// Middleware admin uniquement
function requireAdmin(req, res, next) {
if (!req.user || req.user.role !== 'admin') {
return res.status(403).json({ error: 'Admin access required' });
}
next();
}
// Créer un nouveau token
function createToken(name, role = 'user', dailyLimit = 100) {
const id = uuidv4();
const apiKey = uuidv4();
tokens[id] = {
id,
name,
role,
apiKey,
createdAt: new Date().toISOString(),
active: true,
requestsToday: 0,
dailyLimit
};
saveTokens();
return tokens[id];
}
// Lister tous les tokens
function listTokens() {
return Object.values(tokens).map(t => ({
id: t.id,
name: t.name,
role: t.role,
apiKey: t.apiKey.substring(0, 8) + '...',
createdAt: t.createdAt,
active: t.active,
requestsToday: t.requestsToday,
dailyLimit: t.dailyLimit,
lastUsed: t.lastUsed
}));
}
// Désactiver un token
function disableToken(id) {
if (tokens[id]) {
tokens[id].active = false;
saveTokens();
return true;
}
return false;
}
// Réactiver un token
function enableToken(id) {
if (tokens[id]) {
tokens[id].active = true;
saveTokens();
return true;
}
return false;
}
// Supprimer un token
function deleteToken(id) {
if (id === 'admin') {
return false; // Ne pas supprimer l'admin
}
if (tokens[id]) {
delete tokens[id];
saveTokens();
return true;
}
return false;
}
// Stats globales
function getGlobalStats() {
const tokenList = Object.values(tokens);
return {
totalTokens: tokenList.length,
activeTokens: tokenList.filter(t => t.active).length,
totalRequestsToday: tokenList.reduce((sum, t) => sum + t.requestsToday, 0)
};
}
// Charger les tokens au démarrage
tokens = loadTokens();
module.exports = {
authenticate,
requireAdmin,
createToken,
listTokens,
disableToken,
enableToken,
deleteToken,
getGlobalStats,
loadTokens,
tokens
};