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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | /** * Routes User API Keys — CRUD des cles d'acces a l'API publique REST. * * Endpoints : * GET /user-api-keys — Liste des cles (prefix, label, scopes, last_used) * POST /user-api-keys — Generer une nouvelle cle (affichee une seule fois) * DELETE /user-api-keys/:id — Revoquer une cle * * Securite : * - Les cles sont hashees SHA-256 avant stockage (jamais en clair en base) * - La cle en clair est retournee uniquement a la creation * - Le prefix (8 chars) est stocke pour identification dans l'UI * * @module routes/userApiKeys */ const crypto = require('node:crypto'); const express = require('express'); const { pool: db } = require('../db'); const { requireAuth } = require('../middleware/auth'); const { getUsage } = require('../middleware/apiRateLimit'); const { getPlanLimits } = require('../config/plans'); const router = express.Router(); router.use(requireAuth); const VALID_SCOPES = ['read', 'write', 'admin']; /** * Genere une cle API au format iliacloud_<48 chars base62>. * @returns {string} */ function generateApiKey() { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const bytes = crypto.randomBytes(48); let result = 'iliacloud_'; for (let i = 0; i < 48; i++) { result += chars[bytes[i] % chars.length]; } return result; } /** * Hash une cle avec SHA-256. * @param {string} key * @returns {string} */ function hashKey(key) { return crypto.createHash('sha256').update(key).digest('hex'); } // ─── GET /user-api-keys ───────────────────────────────────────────────────── router.get('/', async (req, res, next) => { try { const { rows } = await db.query( `SELECT id, key_prefix, label, scopes, last_used_at, expires_at, created_at FROM user_api_keys WHERE user_id = $1 ORDER BY created_at DESC`, [req.user.id], ); res.json(rows); } catch (err) { next(err); } }); // ─── POST /user-api-keys ──────────────────────────────────────────────────── router.post('/', async (req, res, next) => { try { const { label = '', scopes = ['read'], expires_at } = req.body; // Valider les scopes for (const scope of scopes) { if (!VALID_SCOPES.includes(scope)) { return res.status(400).json({ error: `Scope invalide : '${scope}'. Valeurs : ${VALID_SCOPES.join(', ')}` }); } } // Limite : max 10 cles par utilisateur const { rows: countRows } = await db.query( 'SELECT COUNT(*) FROM user_api_keys WHERE user_id = $1', [req.user.id], ); if (Number.parseInt(countRows[0].count) >= 10) { return res.status(400).json({ error: 'Maximum 10 cles API par compte.' }); } // Generer la cle const key = generateApiKey(); const keyHash = hashKey(key); const keyPrefix = key.substring(0, 18); // "iliacloud_" + 8 chars const { rows } = await db.query( `INSERT INTO user_api_keys (user_id, key_hash, key_prefix, label, scopes, expires_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, key_prefix, label, scopes, expires_at, created_at`, [req.user.id, keyHash, keyPrefix, label, scopes, expires_at || null], ); // Retourner la cle en clair UNE SEULE FOIS res.status(201).json({ ...rows[0], key, }); } catch (err) { next(err); } }); // ─── DELETE /user-api-keys/:id ────────────────────────────────────────────── router.delete('/:id', async (req, res, next) => { try { const { rows } = await db.query( 'DELETE FROM user_api_keys WHERE id = $1 AND user_id = $2 RETURNING id', [req.params.id, req.user.id], ); if (rows.length === 0) { return res.status(404).json({ error: 'Cle API introuvable.' }); } res.json({ message: 'Cle API revoquee.' }); } catch (err) { next(err); } }); // ─── GET /user-api-keys/usage — usage rate limit API (pour le frontend) ───── router.get('/usage', async (req, res, next) => { try { const plan = req.user.plan || 'free'; const limits = await getPlanLimits(plan); const limit = limits.api_requests_per_hour ?? 0; const usage = getUsage(req.user.id); res.json({ plan, api_access: limits.api_access ?? false, used: usage?.used ?? 0, limit, remaining: usage ? usage.remaining : limit, reset_at: usage?.reset_at ?? null, }); } catch (err) { next(err); } }); module.exports = router; |