mirror of
https://github.com/movixcorp/MovixOpenSource.git
synced 2026-07-26 20:42:08 +00:00
171 lines
5.7 KiB
JavaScript
171 lines
5.7 KiB
JavaScript
/**
|
|
* checkVip.js - Module centralisé de vérification VIP
|
|
*
|
|
* Vérifie la validité d'une clé d'accès VIP en interrogeant la table `access_keys` MySQL.
|
|
* Utilisé par : liveTvRoutes, wishboardRoutes, commentsRoutes, server.js (fstream), etc.
|
|
*
|
|
* Le frontend envoie la clé via le header `x-access-key`.
|
|
* Le serveur vérifie que la clé existe, est active et non expirée.
|
|
*/
|
|
|
|
const { getPool } = require('./mysqlPool');
|
|
|
|
// Cache en mémoire pour éviter de spammer MySQL à chaque requête
|
|
// TTL de 5 minutes — si une clé est révoquée, il faut max 5 min pour que ça prenne effet
|
|
const vipCache = new Map();
|
|
const VIP_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
|
|
/**
|
|
* Vérifie si une clé d'accès est valide dans la table `access_keys` MySQL.
|
|
*
|
|
* @param {string} accessKey - La clé d'accès à vérifier
|
|
* @returns {Promise<{vip: boolean, expiresAt: string|null, duration: string|null}>}
|
|
*/
|
|
async function verifyAccessKey(accessKey) {
|
|
// Pas de clé = pas VIP
|
|
if (!accessKey || typeof accessKey !== 'string' || accessKey.trim() === '') {
|
|
return { vip: false, expiresAt: null, duration: null };
|
|
}
|
|
|
|
const trimmedKey = accessKey.trim();
|
|
|
|
// Fix encoding: HTTP headers carry non-ASCII bytes as Latin-1.
|
|
// Node stores them as-is in the string, so 0xe9 (é) stays as \xe9.
|
|
// Convert Latin-1 bytes → Buffer → UTF-8 string to recover proper Unicode.
|
|
let fixedKey = trimmedKey;
|
|
try {
|
|
const buf = Buffer.from(trimmedKey, 'latin1');
|
|
const decoded = buf.toString('utf8');
|
|
// Only use the fixed version if it's valid UTF-8 (no replacement chars)
|
|
if (!decoded.includes('\ufffd')) {
|
|
fixedKey = decoded;
|
|
}
|
|
} catch (e) {
|
|
// Encoding fix failed, use original
|
|
}
|
|
|
|
// Vérifier le cache d'abord
|
|
const cached = vipCache.get(fixedKey);
|
|
if (cached && (Date.now() - cached.timestamp < VIP_CACHE_TTL)) {
|
|
return cached.result;
|
|
}
|
|
|
|
try {
|
|
const pool = getPool();
|
|
if (!pool) {
|
|
console.warn('[checkVip] MySQL pool not available');
|
|
return { vip: false, expiresAt: null, duration: null };
|
|
}
|
|
|
|
const [rows] = await pool.execute(
|
|
'SELECT key_value, active, expires_at, duree_validite FROM access_keys WHERE key_value = ? LIMIT 1',
|
|
[fixedKey]
|
|
);
|
|
|
|
if (rows.length === 0) {
|
|
// Clé inexistante — mettre en cache le résultat négatif (durée plus courte: 1 min)
|
|
const result = { vip: false, expiresAt: null, duration: null };
|
|
vipCache.set(fixedKey, { result, timestamp: Date.now() });
|
|
return result;
|
|
}
|
|
|
|
const keyData = rows[0];
|
|
|
|
// Vérifier si la clé est active
|
|
if (!keyData.active) {
|
|
const result = { vip: false, expiresAt: null, duration: null, reason: 'key_inactive' };
|
|
vipCache.set(fixedKey, { result, timestamp: Date.now() });
|
|
return result;
|
|
}
|
|
|
|
// Vérifier si la clé a expiré
|
|
if (keyData.expires_at && new Date() > new Date(keyData.expires_at)) {
|
|
const result = { vip: false, expiresAt: keyData.expires_at, duration: keyData.duree_validite, reason: 'key_expired' };
|
|
vipCache.set(fixedKey, { result, timestamp: Date.now() });
|
|
return result;
|
|
}
|
|
|
|
// Clé valide !
|
|
const result = {
|
|
vip: true,
|
|
expiresAt: keyData.expires_at || null,
|
|
duration: keyData.duree_validite || null
|
|
};
|
|
vipCache.set(fixedKey, { result, timestamp: Date.now() });
|
|
return result;
|
|
|
|
} catch (error) {
|
|
console.error('[checkVip] Error verifying access key:', error.message);
|
|
// En cas d'erreur DB, ne pas bloquer — retourner non-VIP
|
|
return { vip: false, expiresAt: null, duration: null };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Middleware Express qui extrait la clé du header `x-access-key`
|
|
* et attache le résultat VIP à `req.vipStatus`.
|
|
*
|
|
* Usage: router.get('/route', vipMiddleware, (req, res) => { ... })
|
|
* Puis accéder à req.vipStatus.vip (boolean)
|
|
*/
|
|
async function vipMiddleware(req, res, next) {
|
|
const accessKey = req.headers['x-access-key'] || null;
|
|
req.vipStatus = await verifyAccessKey(accessKey);
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* Middleware Express qui BLOQUE l'accès si l'utilisateur n'est pas VIP.
|
|
* Renvoie 403 si la clé est absente ou invalide.
|
|
*
|
|
* Usage: router.get('/vip-only-route', requireVip, (req, res) => { ... })
|
|
*/
|
|
async function requireVip(req, res, next) {
|
|
const accessKey = req.headers['x-access-key'] || null;
|
|
const vipStatus = await verifyAccessKey(accessKey);
|
|
req.vipStatus = vipStatus;
|
|
|
|
if (!vipStatus.vip) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: 'Accès réservé aux membres VIP',
|
|
reason: vipStatus.reason || 'no_valid_key'
|
|
});
|
|
}
|
|
next();
|
|
}
|
|
|
|
// Nettoyage périodique du cache VIP — supprime les entrées expirées pour éviter les fuites mémoire
|
|
setInterval(() => {
|
|
const now = Date.now();
|
|
for (const [key, cached] of vipCache) {
|
|
if (now - cached.timestamp > VIP_CACHE_TTL) {
|
|
vipCache.delete(key);
|
|
}
|
|
}
|
|
}, VIP_CACHE_TTL).unref(); // Nettoyage toutes les 5 minutes
|
|
|
|
/**
|
|
* Invalider le cache pour une clé spécifique (utile quand un admin révoque une clé)
|
|
* @param {string} accessKey
|
|
*/
|
|
function invalidateVipCache(accessKey) {
|
|
if (accessKey) {
|
|
vipCache.delete(accessKey.trim());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalider tout le cache VIP (utile pour un flush global)
|
|
*/
|
|
function invalidateAllVipCache() {
|
|
vipCache.clear();
|
|
}
|
|
|
|
module.exports = {
|
|
verifyAccessKey,
|
|
vipMiddleware,
|
|
requireVip,
|
|
invalidateVipCache,
|
|
invalidateAllVipCache
|
|
};
|