Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | /** * Middleware d'authentification API — Verifie la cle API et injecte req.user + req.apiKey. * * Accepte le header : Authorization: Bearer iliacloud_xxxxx * * Fonctionnement : * 1. Extrait la cle du header Authorization * 2. Hash la cle avec SHA-256 * 3. Cherche le hash dans user_api_keys * 4. Verifie que la cle n'est pas expiree * 5. Met a jour last_used_at * 6. Charge l'utilisateur et son plan * 7. Injecte req.user et req.apiKey (avec scopes) * * @module middleware/apiAuth */ const crypto = require('node:crypto'); const { pool: db } = require('../db'); /** * Hash une cle API avec SHA-256. * @param {string} key * @returns {string} */ function hashKey(key) { return crypto.createHash('sha256').update(key).digest('hex'); } /** * Middleware Express — verifie la cle API et injecte req.user + req.apiKey. */ async function requireApiAuth(req, res, next) { try { const authHeader = req.headers['authorization']; if (!authHeader?.startsWith('Bearer ')) { return res.status(401).json({ error: { code: 'MISSING_API_KEY', message: 'Header Authorization: Bearer <api_key> requis.' }, }); } const key = authHeader.slice(7); if (!key?.startsWith('iliacloud_')) { return res.status(401).json({ error: { code: 'INVALID_API_KEY', message: 'Format de cle API invalide.' }, }); } const keyHash = hashKey(key); const { rows } = await db.query( `SELECT ak.id, ak.user_id, ak.scopes, ak.expires_at, ak.label, u.email, u.plan FROM user_api_keys ak JOIN users u ON u.id = ak.user_id WHERE ak.key_hash = $1`, [keyHash], ); if (rows.length === 0) { return res.status(401).json({ error: { code: 'INVALID_API_KEY', message: 'Cle API invalide ou revoquee.' }, }); } const apiKey = rows[0]; // Verifier l'expiration if (apiKey.expires_at && new Date(apiKey.expires_at) < new Date()) { return res.status(401).json({ error: { code: 'EXPIRED_API_KEY', message: 'Cle API expiree.' }, }); } // Mettre a jour last_used_at (fire and forget) db.query('UPDATE user_api_keys SET last_used_at = NOW() WHERE id = $1', [apiKey.id]).catch(() => {}); // Injecter req.user et req.apiKey req.user = { id: apiKey.user_id, email: apiKey.email, plan: apiKey.plan }; req.apiKey = { id: apiKey.id, scopes: apiKey.scopes || [], label: apiKey.label }; next(); } catch (err) { next(err); } } /** * Middleware de verification de scope. * Bloque si la cle API n'a pas le scope requis. * * @param {string} scope — 'read', 'write' ou 'admin' * @returns {Function} Express middleware */ function requireScope(scope) { return (req, res, next) => { const scopes = req.apiKey?.scopes || []; if (scopes.includes('admin') || scopes.includes(scope)) { return next(); } return res.status(403).json({ error: { code: 'INSUFFICIENT_SCOPE', message: `Scope '${scope}' requis. Scopes actuels : [${scopes.join(', ')}].` }, }); }; } module.exports = { requireApiAuth, requireScope, hashKey }; |