All files / src/routes sshKeys.js

0% Statements 0/39
0% Branches 0/14
0% Functions 0/3
0% Lines 0/39

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                                                                                                                                                                                                                                       
/**
 * Routes Clés SSH — CRUD des clés SSH de l'utilisateur.
 *
 * Endpoints :
 *   GET    /ssh-keys          — Liste les clés (sans les données sensibles)
 *   POST   /ssh-keys          — Ajouter une clé (chiffrée avant stockage)
 *   DELETE /ssh-keys/:id      — Supprimer une clé
 *   POST   /ssh-keys/:id/test — Tester la connexion SSH avec cette clé
 *
 * Sécurité :
 *   - Les clés privées sont chiffrées (AES-256-GCM) avant stockage
 *   - Les clés déchiffrées ne sont jamais renvoyées au client
 *   - Scopé à l'user_id (multi-tenant)
 *
 * @module routes/sshKeys
 */
 
const express = require('express');
const { pool: db } = require('../db');
const { requireAuth } = require('../middleware/auth');
const { encrypt, decrypt } = require('../services/crypto');
const { testConnection } = require('../services/ssh');
const { makeDeleteHandler } = require('../helpers/routeHelpers');
 
const router = express.Router();
 
router.use(requireAuth);
 
// ─── GET /ssh-keys ───────────────────────────────────────────────────────────
 
router.get('/', async (req, res, next) => {
  try {
    // Ne jamais retourner la clé privée chiffrée — seulement les métadonnées
    const result = await db.query(
      'SELECT id, name, created_at FROM ssh_keys WHERE user_id = $1 ORDER BY created_at DESC',
      [req.user.id],
    );
    res.json(result.rows);
  } catch (err) {
    next(err);
  }
});
 
// ─── POST /ssh-keys ──────────────────────────────────────────────────────────
 
router.post('/', async (req, res, next) => {
  try {
    const { name, private_key, passphrase } = req.body;
 
    if (!name || !private_key) {
      return res.status(400).json({ error: 'name et private_key sont requis.' });
    }
    if (name.length > 100) {
      return res.status(400).json({ error: 'Nom trop long (max 100 caractères).' });
    }
 
    // Chiffrer la clé privée et la passphrase avant stockage
    const encryptedPrivateKey = encrypt(private_key);
    const passphraseEncrypted = passphrase ? encrypt(passphrase) : null;
 
    const result = await db.query(
      `INSERT INTO ssh_keys (user_id, name, encrypted_private_key, passphrase_encrypted)
       VALUES ($1, $2, $3, $4)
       RETURNING id, name, created_at`,
      [req.user.id, name, encryptedPrivateKey, passphraseEncrypted],
    );
 
    res.status(201).json(result.rows[0]);
  } catch (err) {
    next(err);
  }
});
 
// ─── DELETE /ssh-keys/:id ────────────────────────────────────────────────────
 
router.delete('/:id', makeDeleteHandler('ssh_keys', 'Clé SSH introuvable.', { message: 'Clé SSH supprimée.' }));
 
// ─── POST /ssh-keys/:id/test ─────────────────────────────────────────────────
// Teste la connexion SSH d'une clé associée à un serveur existant.
 
router.post('/:id/test', async (req, res, next) => {
  try {
    const { server_id } = req.body;
    if (!server_id) {
      return res.status(400).json({ error: 'server_id requis.' });
    }
 
    const result = await db.query(
      `SELECT s.host, s.port, s.ssh_user, k.encrypted_private_key, k.passphrase_encrypted
       FROM servers s
       JOIN ssh_keys k ON k.id = s.ssh_key_id
       WHERE s.id = $1 AND s.user_id = $2 AND k.id = $3`,
      [server_id, req.user.id, req.params.id],
    );
    if (result.rows.length === 0) {
      return res.status(404).json({ error: 'Serveur ou clé introuvable.' });
    }
 
    const row = result.rows[0];
    await testConnection({
      host: row.host,
      port: row.port,
      ssh_user: row.ssh_user,
      privateKey: decrypt(row.encrypted_private_key),
      passphrase: row.passphrase_encrypted ? decrypt(row.passphrase_encrypted) : undefined,
    });
 
    res.json({ ok: true, message: 'Connexion SSH réussie.' });
  } catch {
    // Message générique — pas de fuite d'info SSH
    res.status(502).json({ ok: false, error: 'Connexion SSH échouée. Vérifiez les paramètres.' });
  }
});
 
module.exports = router;