fix: Corriger le script usage.js pour respecter VAULT_CONFS_DIR
- Modifier examples/usage.ts pour utiliser VAULT_CONFS_DIR depuis .env au lieu de '../confs' hardcodé - Ajouter test-usage.js comme script de test fonctionnel - Nettoyer les anciens fichiers de debug et test - Ajouter **/confs*/ au .gitignore pour éviter de committer les dossiers de test Le SDK respecte maintenant correctement le paramètre VAULT_CONFS_DIR du fichier .env.
This commit is contained in:
parent
81b4648147
commit
f4a8dd2e7f
3
.gitignore
vendored
3
.gitignore
vendored
@ -21,3 +21,6 @@
|
|||||||
# Clés utilisateur et données sensibles
|
# Clés utilisateur et données sensibles
|
||||||
**/_keys/
|
**/_keys/
|
||||||
**/keys.json*
|
**/keys.json*
|
||||||
|
|
||||||
|
# Dossiers de synchronisation de test
|
||||||
|
**/confs*/
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Script de debug pour les variables circulaires"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
sys.path.append('/home/debian/4NK_vault')
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from api_server import EnvProcessor
|
|
||||||
|
|
||||||
def test_circular_variables():
|
|
||||||
print("🔍 Test des variables circulaires HOST/DOMAIN")
|
|
||||||
|
|
||||||
# Test avec le fichier .env de dev
|
|
||||||
env_file = Path('/home/debian/4NK_vault/storage/dev/.env')
|
|
||||||
processor = EnvProcessor(env_file)
|
|
||||||
|
|
||||||
# Test de résolution des variables problématiques
|
|
||||||
test_variables = ['HOST', 'DOMAIN', 'ROOT_HOST', 'ROOT_URL']
|
|
||||||
|
|
||||||
print(f"\n🔍 Test de résolution des variables:")
|
|
||||||
for var in test_variables:
|
|
||||||
if var in processor.variables:
|
|
||||||
original_value = processor.variables[var]
|
|
||||||
try:
|
|
||||||
resolved = processor._resolve_variable(var)
|
|
||||||
print(f" {var}: {original_value} → {resolved}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" {var}: {original_value} → ERREUR: {e}")
|
|
||||||
else:
|
|
||||||
print(f" {var}: NON TROVÉE")
|
|
||||||
|
|
||||||
# Test avec un contenu qui utilise ces variables
|
|
||||||
test_content = """
|
|
||||||
GF_SERVER_ROOT_URL=https://dev4.$HOST/grafana
|
|
||||||
ROOT_URL=https://$ROOT_HOST
|
|
||||||
GRAFANA_URL=$ROOT_URL/grafana
|
|
||||||
"""
|
|
||||||
|
|
||||||
print(f"\n📄 Contenu de test:")
|
|
||||||
print(test_content)
|
|
||||||
|
|
||||||
processed_content = processor.process_content(test_content)
|
|
||||||
|
|
||||||
print(f"\n📄 Contenu traité:")
|
|
||||||
print(processed_content)
|
|
||||||
|
|
||||||
# Vérifier si des variables sont encore présentes
|
|
||||||
if '$' in processed_content:
|
|
||||||
print("\n⚠️ Variables non résolues détectées!")
|
|
||||||
import re
|
|
||||||
remaining_vars = re.findall(r'\$[A-Za-z_][A-Za-z0-9_]*', processed_content)
|
|
||||||
print(f"Variables restantes: {remaining_vars}")
|
|
||||||
else:
|
|
||||||
print("\n✅ Toutes les variables ont été résolues!")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_circular_variables()
|
|
@ -1,58 +0,0 @@
|
|||||||
const https = require('https');
|
|
||||||
|
|
||||||
async function debugApiResponse() {
|
|
||||||
console.log('🔍 Debug de la réponse API...');
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
hostname: 'vault.4nkweb.com',
|
|
||||||
port: 6666,
|
|
||||||
path: '/dev/bitcoin/bitcoin.conf',
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'X-User-ID': 'demo_user_001'
|
|
||||||
},
|
|
||||||
rejectUnauthorized: false // Pour accepter les certificats auto-signés
|
|
||||||
};
|
|
||||||
|
|
||||||
const req = https.request(options, (res) => {
|
|
||||||
console.log('Status:', res.statusCode);
|
|
||||||
console.log('Headers:', res.headers);
|
|
||||||
|
|
||||||
let data = '';
|
|
||||||
res.on('data', (chunk) => {
|
|
||||||
data += chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
res.on('end', () => {
|
|
||||||
console.log('Response length:', data.length);
|
|
||||||
console.log('First 200 chars:', data.substring(0, 200));
|
|
||||||
|
|
||||||
// Essayer de décoder base64
|
|
||||||
try {
|
|
||||||
const decoded = Buffer.from(data, 'base64');
|
|
||||||
console.log('Decoded length:', decoded.length);
|
|
||||||
console.log('First 50 bytes (hex):', decoded.subarray(0, 50).toString('hex'));
|
|
||||||
|
|
||||||
if (decoded.length >= 16) {
|
|
||||||
const nonce = decoded.subarray(0, 12);
|
|
||||||
const metadataSize = decoded.readUInt32BE(12);
|
|
||||||
const metadataJson = decoded.subarray(16, 16 + metadataSize);
|
|
||||||
|
|
||||||
console.log('Nonce (hex):', nonce.toString('hex'));
|
|
||||||
console.log('Metadata size:', metadataSize);
|
|
||||||
console.log('Metadata:', metadataJson.toString('utf-8'));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Error decoding:', e.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
req.on('error', (e) => {
|
|
||||||
console.error('Request error:', e);
|
|
||||||
});
|
|
||||||
|
|
||||||
req.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
debugApiResponse();
|
|
@ -1,54 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test de débogage du SDK client
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { createSecureVaultClient } = require('./dist/index.js');
|
|
||||||
|
|
||||||
async function debugTest() {
|
|
||||||
console.log('🔍 Test de débogage du SDK client');
|
|
||||||
console.log('='.repeat(50));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. Création du client avec ID utilisateur
|
|
||||||
console.log('📝 Création du client...');
|
|
||||||
const client = createSecureVaultClient(
|
|
||||||
'https://vault.4nkweb.com:6666',
|
|
||||||
'demo_user_001'
|
|
||||||
);
|
|
||||||
console.log('✅ Client créé avec succès');
|
|
||||||
|
|
||||||
// 2. Test direct de getRoutes()
|
|
||||||
console.log('\n🛣️ Test direct de getRoutes()...');
|
|
||||||
const routes = await client.getRoutes();
|
|
||||||
|
|
||||||
console.log(`\n📋 Résultats:`);
|
|
||||||
console.log(` Total des routes: ${routes.total_routes}`);
|
|
||||||
console.log(` Utilisateur: ${routes.user_id}`);
|
|
||||||
console.log(` Timestamp: ${routes.timestamp}`);
|
|
||||||
|
|
||||||
console.log('\n📝 Routes disponibles:');
|
|
||||||
routes.routes.forEach((route, index) => {
|
|
||||||
console.log(` ${index + 1}. ${route.method} ${route.path}`);
|
|
||||||
console.log(` ${route.description}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n✅ Test réussi !');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('\n❌ Erreur lors du test:');
|
|
||||||
console.error(` Type: ${error.name}`);
|
|
||||||
console.error(` Message: ${error.message}`);
|
|
||||||
if (error.statusCode) {
|
|
||||||
console.error(` Code HTTP: ${error.statusCode}`);
|
|
||||||
}
|
|
||||||
if (error.code) {
|
|
||||||
console.error(` Code d'erreur: ${error.code}`);
|
|
||||||
}
|
|
||||||
console.error(` Stack: ${error.stack}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exécution du test
|
|
||||||
debugTest().catch(console.error);
|
|
387
sdk-client/examples/examples/usage.js
Normal file
387
sdk-client/examples/examples/usage.js
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
"use strict";
|
||||||
|
/**
|
||||||
|
* Exemple d'utilisation du client Vault
|
||||||
|
* Scénario complet : Initialisation → Routes → Parcours → Déchiffrement
|
||||||
|
*/
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.step1_Initialization = step1_Initialization;
|
||||||
|
exports.step2_GetRoutes = step2_GetRoutes;
|
||||||
|
exports.step3_ParseRoutes = step3_ParseRoutes;
|
||||||
|
exports.step4_SyncLocalFiles = step4_SyncLocalFiles;
|
||||||
|
exports.step5_DecryptContents = step5_DecryptContents;
|
||||||
|
const index_1 = require("../src/index");
|
||||||
|
/**
|
||||||
|
* ÉTAPE 1: Initialisation + Gestion des erreurs
|
||||||
|
*/
|
||||||
|
async function step1_Initialization() {
|
||||||
|
console.log('🚀 ÉTAPE 1: Initialisation du client + Gestion des erreurs');
|
||||||
|
console.log('='.repeat(70));
|
||||||
|
try {
|
||||||
|
// Test avec différents IDs utilisateur
|
||||||
|
const testUserIds = [
|
||||||
|
'demo_user_001', // ID existant
|
||||||
|
'invalid@user', // ID invalide (caractères interdits)
|
||||||
|
'ab', // ID trop court
|
||||||
|
'a'.repeat(129), // ID trop long
|
||||||
|
'3506ea43d9207038eea58caca84d51e4ccc01c496b6572bbf4dfda7fa03085b8' // Votre clé
|
||||||
|
];
|
||||||
|
let validClient = null;
|
||||||
|
for (const userId of testUserIds) {
|
||||||
|
try {
|
||||||
|
console.log(`\n🔍 Test avec l'ID: ${userId.substring(0, 20)}${userId.length > 20 ? '...' : ''}`);
|
||||||
|
// Utiliser le constructeur avec chargement automatique des clés .env
|
||||||
|
// Pour le test avec les vraies clés, utiliser le constructeur sans paramètres
|
||||||
|
const client = userId === 'demo_user_001' ? new index_1.SecureVaultClient() : new index_1.SecureVaultClient({
|
||||||
|
baseUrl: 'https://vault.4nkweb.com:6666',
|
||||||
|
userId: userId,
|
||||||
|
timeout: 30000,
|
||||||
|
verifySsl: false
|
||||||
|
});
|
||||||
|
// Test de connectivité
|
||||||
|
const isConnected = await client.ping();
|
||||||
|
if (isConnected) {
|
||||||
|
console.log(` ✅ ID valide et connecté: ${userId}`);
|
||||||
|
if (!validClient) {
|
||||||
|
validClient = client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(` ❌ ID valide mais non connecté: ${userId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof index_1.VaultAuthenticationError) {
|
||||||
|
console.log(` 🔑 Erreur d'authentification: ${error.message}`);
|
||||||
|
}
|
||||||
|
else if (error.message.includes('ID utilisateur requis') || error.message.includes('invalide')) {
|
||||||
|
console.log(` ⚠️ ID invalide: ${error.message}`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(` ❌ Erreur inattendue: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!validClient) {
|
||||||
|
throw new Error('Aucun client valide trouvé - impossible de continuer');
|
||||||
|
}
|
||||||
|
console.log('\n✅ ÉTAPE 1 TERMINÉE: Client initialisé avec succès');
|
||||||
|
return validClient;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('\n❌ ÉTAPE 1 ÉCHOUÉE:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* ÉTAPE 2: Récupération de toutes les routes + Gestion des erreurs
|
||||||
|
*/
|
||||||
|
async function step2_GetRoutes(client) {
|
||||||
|
console.log('\n🛣️ ÉTAPE 2: Récupération de toutes les routes + Gestion des erreurs');
|
||||||
|
console.log('='.repeat(70));
|
||||||
|
try {
|
||||||
|
// Test de récupération des routes
|
||||||
|
console.log('\n📋 Récupération des routes disponibles...');
|
||||||
|
const routes = await client.getRoutes();
|
||||||
|
console.log(` ✅ Total des routes: ${routes.total_routes}`);
|
||||||
|
console.log(` ✅ Utilisateur: ${routes.user_id}`);
|
||||||
|
console.log(` ✅ Type d'authentification: ${routes.authentication.type}`);
|
||||||
|
console.log('\n📝 Routes disponibles:');
|
||||||
|
routes.routes.forEach((route, index) => {
|
||||||
|
console.log(` ${index + 1}. ${route.method} ${route.path}`);
|
||||||
|
console.log(` → ${route.description}`);
|
||||||
|
console.log(` → Authentification: ${route.authentication}`);
|
||||||
|
console.log(` → Type de réponse: ${route.response_type}`);
|
||||||
|
if (route.parameters) {
|
||||||
|
console.log(` → Paramètres:`);
|
||||||
|
Object.entries(route.parameters).forEach(([key, value]) => {
|
||||||
|
console.log(` • ${key}: ${value}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (route.examples && route.examples.length > 0) {
|
||||||
|
console.log(` → Exemples:`);
|
||||||
|
route.examples.forEach(example => {
|
||||||
|
console.log(` • ${example}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log('');
|
||||||
|
});
|
||||||
|
console.log('✅ ÉTAPE 2 TERMINÉE: Routes récupérées avec succès');
|
||||||
|
return routes;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('\n❌ ÉTAPE 2 ÉCHOUÉE:');
|
||||||
|
if (error instanceof index_1.VaultApiError) {
|
||||||
|
console.error(` Erreur API: ${error.message}`);
|
||||||
|
console.error(` Code HTTP: ${error.statusCode}`);
|
||||||
|
console.error(` Code d'erreur: ${error.code}`);
|
||||||
|
}
|
||||||
|
else if (error instanceof index_1.VaultAuthenticationError) {
|
||||||
|
console.error(` Erreur d'authentification: ${error.message}`);
|
||||||
|
console.error(` Code HTTP: ${error.statusCode}`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(` Erreur inattendue: ${error.message}`);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* ÉTAPE 3: Parcours de toutes les routes pour récupération du contenu + Gestion des erreurs
|
||||||
|
*/
|
||||||
|
async function step3_ParseRoutes(routes, client) {
|
||||||
|
console.log('\n📁 ÉTAPE 3: Parcours des routes pour récupération du contenu + Gestion des erreurs');
|
||||||
|
console.log('='.repeat(70));
|
||||||
|
const results = [];
|
||||||
|
try {
|
||||||
|
for (const route of routes.routes) {
|
||||||
|
console.log(`\n🔍 Test de la route: ${route.method} ${route.path}`);
|
||||||
|
try {
|
||||||
|
let result = null;
|
||||||
|
switch (route.path) {
|
||||||
|
case '/health':
|
||||||
|
result = await client.health();
|
||||||
|
console.log(` ✅ Health: ${result.status} - ${result.service}`);
|
||||||
|
break;
|
||||||
|
case '/info':
|
||||||
|
result = await client.info();
|
||||||
|
console.log(` ✅ Info: ${result.name} v${result.version}`);
|
||||||
|
break;
|
||||||
|
case '/routes':
|
||||||
|
result = await client.getRoutes();
|
||||||
|
console.log(` ✅ Routes: ${result.total_routes} routes disponibles`);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Route dynamique /<env>/<file_path>
|
||||||
|
if (route.path.includes('<env>') && route.path.includes('<file_path>')) {
|
||||||
|
console.log(` 📂 Test des fichiers de configuration...`);
|
||||||
|
// Tester quelques fichiers de configuration connus
|
||||||
|
const testFiles = [
|
||||||
|
'bitcoin/bitcoin.conf',
|
||||||
|
'nginx/nginx.conf',
|
||||||
|
'grafana/grafana.ini'
|
||||||
|
];
|
||||||
|
for (const filePath of testFiles) {
|
||||||
|
try {
|
||||||
|
const fileResult = await client.getFile('dev', filePath);
|
||||||
|
console.log(` ✅ ${filePath}: ${fileResult.size} caractères`);
|
||||||
|
results.push({
|
||||||
|
route: `${route.method} /dev/${filePath}`,
|
||||||
|
success: true,
|
||||||
|
data: fileResult
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (fileError) {
|
||||||
|
console.log(` ❌ ${filePath}: ${fileError.message}`);
|
||||||
|
results.push({
|
||||||
|
route: `${route.method} /dev/${filePath}`,
|
||||||
|
success: false,
|
||||||
|
error: fileError.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (result) {
|
||||||
|
results.push({
|
||||||
|
route: `${route.method} ${route.path}`,
|
||||||
|
success: true,
|
||||||
|
data: result
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(` ❌ Erreur: ${error.message}`);
|
||||||
|
if (error instanceof index_1.VaultApiError) {
|
||||||
|
console.log(` → Code HTTP: ${error.statusCode}`);
|
||||||
|
console.log(` → Code d'erreur: ${error.code}`);
|
||||||
|
}
|
||||||
|
else if (error instanceof index_1.VaultAuthenticationError) {
|
||||||
|
console.log(` → Erreur d'authentification`);
|
||||||
|
}
|
||||||
|
else if (error instanceof index_1.VaultDecryptionError) {
|
||||||
|
console.log(` → Erreur de déchiffrement`);
|
||||||
|
}
|
||||||
|
results.push({
|
||||||
|
route: `${route.method} ${route.path}`,
|
||||||
|
success: false,
|
||||||
|
error: error.message,
|
||||||
|
errorType: error.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`\n📊 Résumé des tests:`);
|
||||||
|
const successCount = results.filter(r => r.success).length;
|
||||||
|
const errorCount = results.filter(r => !r.success).length;
|
||||||
|
console.log(` ✅ Succès: ${successCount}/${results.length}`);
|
||||||
|
console.log(` ❌ Erreurs: ${errorCount}/${results.length}`);
|
||||||
|
console.log('\n✅ ÉTAPE 3 TERMINÉE: Parcours des routes terminé');
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('\n❌ ÉTAPE 3 ÉCHOUÉE:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* ÉTAPE 4: Synchronisation locale des fichiers déchiffrés + Gestion des erreurs
|
||||||
|
*/
|
||||||
|
async function step4_SyncLocalFiles(client) {
|
||||||
|
console.log('\n💾 ÉTAPE 4: Synchronisation locale des fichiers déchiffrés + Gestion des erreurs');
|
||||||
|
console.log('='.repeat(70));
|
||||||
|
try {
|
||||||
|
console.log('\n🔄 Synchronisation des fichiers vers le dossier local...');
|
||||||
|
console.log(' Mapping: /<env>/<project>/<file> -> ../confs/<project>/<file>');
|
||||||
|
// Synchronisation avec options détaillées
|
||||||
|
const syncResult = await client.syncLocalFiles({
|
||||||
|
environment: 'dev',
|
||||||
|
localDir: '../confs',
|
||||||
|
verbose: true
|
||||||
|
});
|
||||||
|
console.log(`\n📊 Résultats de synchronisation:`);
|
||||||
|
console.log(` ✅ Fichiers synchronisés: ${syncResult.synced}`);
|
||||||
|
console.log(` ⏭️ Fichiers ignorés: ${syncResult.skipped}`);
|
||||||
|
console.log(` ❌ Erreurs: ${syncResult.errors}`);
|
||||||
|
// Affichage détaillé des résultats
|
||||||
|
if (syncResult.details.length > 0) {
|
||||||
|
console.log('\n📋 Détails par fichier:');
|
||||||
|
syncResult.details.forEach(detail => {
|
||||||
|
const icon = detail.status === 'synced' ? '✅' :
|
||||||
|
detail.status === 'skipped' ? '⏭️' : '❌';
|
||||||
|
console.log(` ${icon} ${detail.file}: ${detail.status}`);
|
||||||
|
if (detail.message) {
|
||||||
|
console.log(` → ${detail.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log('\n✅ ÉTAPE 4 TERMINÉE: Synchronisation locale terminée');
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('\n❌ ÉTAPE 4 ÉCHOUÉE:');
|
||||||
|
if (error instanceof index_1.VaultApiError) {
|
||||||
|
console.error(` Erreur API: ${error.message}`);
|
||||||
|
console.error(` Code HTTP: ${error.statusCode}`);
|
||||||
|
}
|
||||||
|
else if (error.message.includes('synchronisation')) {
|
||||||
|
console.error(` Erreur de synchronisation: ${error.message}`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(` Erreur inattendue: ${error.message}`);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* ÉTAPE 5: Déchiffrement des contenus récupérés (non stocké) + Gestion des erreurs
|
||||||
|
*/
|
||||||
|
async function step5_DecryptContents(results) {
|
||||||
|
console.log('\n🔓 ÉTAPE 5: Déchiffrement des contenus récupérés + Gestion des erreurs');
|
||||||
|
console.log('='.repeat(70));
|
||||||
|
try {
|
||||||
|
const fileResults = results.filter(r => r.success && r.data && r.data.content);
|
||||||
|
if (fileResults.length === 0) {
|
||||||
|
console.log(' ⚠️ Aucun fichier récupéré pour déchiffrement');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`\n🔍 Déchiffrement de ${fileResults.length} fichier(s)...`);
|
||||||
|
for (const fileResult of fileResults) {
|
||||||
|
console.log(`\n📄 Fichier: ${fileResult.route}`);
|
||||||
|
try {
|
||||||
|
const content = fileResult.data.content;
|
||||||
|
// Vérifier si le contenu est chiffré (format de démonstration)
|
||||||
|
if (content.includes('[CONTENU CHIFFRÉ - DÉCHIFFREMENT NÉCESSAIRE]')) {
|
||||||
|
console.log(' 🔐 Contenu chiffré détecté (format de démonstration)');
|
||||||
|
// Extraire les métadonnées du format de démonstration
|
||||||
|
const lines = content.split('\n');
|
||||||
|
const metadata = {};
|
||||||
|
lines.forEach((line) => {
|
||||||
|
if (line.includes('Utilisateur:')) {
|
||||||
|
metadata.user = line.split('Utilisateur:')[1]?.trim() || '';
|
||||||
|
}
|
||||||
|
else if (line.includes('Version de clé:')) {
|
||||||
|
metadata.keyVersion = line.split('Version de clé:')[1]?.trim() || '';
|
||||||
|
}
|
||||||
|
else if (line.includes('Algorithme:')) {
|
||||||
|
metadata.algorithm = line.split('Algorithme:')[1]?.trim() || '';
|
||||||
|
}
|
||||||
|
else if (line.includes('Rotation:')) {
|
||||||
|
metadata.rotation = line.split('Rotation:')[1]?.trim() || '';
|
||||||
|
}
|
||||||
|
else if (line.includes('Taille chiffrée:')) {
|
||||||
|
metadata.encryptedSize = line.split('Taille chiffrée:')[1]?.trim() || '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(` 📋 Métadonnées extraites:`);
|
||||||
|
console.log(` → Utilisateur: ${metadata.user}`);
|
||||||
|
console.log(` → Version de clé: ${metadata.keyVersion}`);
|
||||||
|
console.log(` → Algorithme: ${metadata.algorithm}`);
|
||||||
|
console.log(` → Rotation: ${metadata.rotation}`);
|
||||||
|
console.log(` → Taille chiffrée: ${metadata.encryptedSize}`);
|
||||||
|
// Dans un vrai déchiffrement, on utiliserait la clé utilisateur
|
||||||
|
console.log(` 🔑 Déchiffrement simulé: Contenu accessible avec la clé utilisateur`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(' 📝 Contenu non chiffré détecté');
|
||||||
|
console.log(` 📄 Aperçu: ${content.substring(0, 100)}${content.length > 100 ? '...' : ''}`);
|
||||||
|
}
|
||||||
|
console.log(` ✅ Déchiffrement traité avec succès`);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(` ❌ Erreur de déchiffrement: ${error.message}`);
|
||||||
|
if (error instanceof index_1.VaultDecryptionError) {
|
||||||
|
console.log(` → Erreur de déchiffrement spécifique`);
|
||||||
|
console.log(` → Code: ${error.code}`);
|
||||||
|
}
|
||||||
|
else if (error.message.includes('déchiffrement')) {
|
||||||
|
console.log(` → Erreur de traitement du contenu`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(` → Erreur inattendue lors du déchiffrement`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('\n✅ ÉTAPE 5 TERMINÉE: Déchiffrement des contenus terminé');
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('\n❌ ÉTAPE 5 ÉCHOUÉE:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Fonction principale - Exécute le scénario complet
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
console.log('🚀 DÉMONSTRATION COMPLÈTE DU CLIENT VAULT');
|
||||||
|
console.log('Scénario: Initialisation → Routes → Parcours → Synchronisation → Déchiffrement');
|
||||||
|
console.log('='.repeat(80));
|
||||||
|
try {
|
||||||
|
// ÉTAPE 1: Initialisation
|
||||||
|
const client = await step1_Initialization();
|
||||||
|
// ÉTAPE 2: Récupération des routes
|
||||||
|
const routes = await step2_GetRoutes(client);
|
||||||
|
// ÉTAPE 3: Parcours des routes
|
||||||
|
const results = await step3_ParseRoutes(routes, client);
|
||||||
|
// ÉTAPE 4: Synchronisation locale
|
||||||
|
await step4_SyncLocalFiles(client);
|
||||||
|
// ÉTAPE 5: Déchiffrement
|
||||||
|
await step5_DecryptContents(results);
|
||||||
|
console.log('\n🎉 SCÉNARIO COMPLET TERMINÉ AVEC SUCCÈS!');
|
||||||
|
console.log('\n📝 Résumé du système sécurisé:');
|
||||||
|
console.log(' • ✅ Authentification par ID utilisateur validée');
|
||||||
|
console.log(' • ✅ Récupération des routes API fonctionnelle');
|
||||||
|
console.log(' • ✅ Parcours de toutes les routes testé');
|
||||||
|
console.log(' • ✅ Synchronisation locale des fichiers déchiffrés');
|
||||||
|
console.log(' • ✅ Déchiffrement des contenus géré');
|
||||||
|
console.log(' • ✅ Gestion d\'erreurs complète à chaque étape');
|
||||||
|
console.log(' • ✅ Rotation automatique des clés active');
|
||||||
|
console.log(' • ✅ Chiffrement quantum-résistant (ChaCha20-Poly1305)');
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('\n💥 ÉCHEC DU SCÉNARIO:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Exécution si appelé directement
|
||||||
|
if (require.main === module) {
|
||||||
|
main().catch(console.error);
|
||||||
|
}
|
555
sdk-client/examples/src/index.js
Normal file
555
sdk-client/examples/src/index.js
Normal file
@ -0,0 +1,555 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.SecureVaultClient = exports.VaultAuthenticationError = exports.VaultDecryptionError = exports.VaultApiError = void 0;
|
||||||
|
exports.createSecureVaultClient = createSecureVaultClient;
|
||||||
|
exports.createSecureVaultClientWithConfig = createSecureVaultClientWithConfig;
|
||||||
|
const node_fetch_1 = require("node-fetch");
|
||||||
|
const https_1 = require("https");
|
||||||
|
const dotenv_1 = require("dotenv");
|
||||||
|
const { chacha20poly1305 } = require('@noble/ciphers/chacha.js');
|
||||||
|
const fs_1 = require("fs");
|
||||||
|
const path_1 = require("path");
|
||||||
|
class VaultApiError extends Error {
|
||||||
|
constructor(message, code, statusCode) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'VaultApiError';
|
||||||
|
this.code = code;
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.VaultApiError = VaultApiError;
|
||||||
|
class VaultDecryptionError extends Error {
|
||||||
|
constructor(message, code) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'VaultDecryptionError';
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.VaultDecryptionError = VaultDecryptionError;
|
||||||
|
class VaultAuthenticationError extends Error {
|
||||||
|
constructor(message, code, statusCode) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'VaultAuthenticationError';
|
||||||
|
this.code = code;
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.VaultAuthenticationError = VaultAuthenticationError;
|
||||||
|
/**
|
||||||
|
* Client sécurisé pour l'API Vault avec authentification par clés utilisateur
|
||||||
|
* Les clés sont gérées côté serveur avec rotation automatique
|
||||||
|
*/
|
||||||
|
class SecureVaultClient {
|
||||||
|
constructor(config) {
|
||||||
|
this.vaultKey = null;
|
||||||
|
// Charger les variables d'environnement depuis .env
|
||||||
|
// Essayer plusieurs emplacements pour le fichier .env
|
||||||
|
this._loadEnvironmentVariables();
|
||||||
|
// Si pas de config fournie, utiliser les variables d'environnement
|
||||||
|
if (!config) {
|
||||||
|
const envUser = process.env['VAULT_USER'];
|
||||||
|
const envKey = process.env['VAULT_KEY'];
|
||||||
|
const envEnv = process.env['VAULT_ENV'];
|
||||||
|
if (!envUser || !envKey || !envEnv) {
|
||||||
|
throw new Error('Variables d\'environnement requises: VAULT_USER, VAULT_KEY, VAULT_ENV');
|
||||||
|
}
|
||||||
|
config = {
|
||||||
|
baseUrl: 'https://vault.4nkweb.com:6666',
|
||||||
|
userId: envUser,
|
||||||
|
timeout: 30000,
|
||||||
|
verifySsl: false
|
||||||
|
};
|
||||||
|
this.vaultKey = envKey;
|
||||||
|
}
|
||||||
|
this.config = {
|
||||||
|
verifySsl: false,
|
||||||
|
timeout: 30000,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
// Validation de l'ID utilisateur
|
||||||
|
if (!this.config.userId || this.config.userId.length < 3 || this.config.userId.length > 128) {
|
||||||
|
throw new Error('ID utilisateur requis (3-128 caractères)');
|
||||||
|
}
|
||||||
|
if (!/^[a-zA-Z0-9_-]+$/.test(this.config.userId)) {
|
||||||
|
throw new Error('ID utilisateur invalide - caractères autorisés: a-z, A-Z, 0-9, _, -');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Récupère un fichier depuis l'API Vault
|
||||||
|
*/
|
||||||
|
async getFile(env, filePath) {
|
||||||
|
const url = `${this.config.baseUrl}/${env}/${filePath}`;
|
||||||
|
try {
|
||||||
|
const response = await this._fetchApi(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new VaultAuthenticationError('Authentification échouée - vérifiez votre ID utilisateur', 'AUTH_FAILED', response.status);
|
||||||
|
}
|
||||||
|
if (response.status === 403) {
|
||||||
|
throw new VaultApiError('Accès non autorisé à ce fichier', 'ACCESS_DENIED', response.status);
|
||||||
|
}
|
||||||
|
throw new VaultApiError(`Erreur API: ${response.status} ${response.statusText}`, 'API_ERROR', response.status);
|
||||||
|
}
|
||||||
|
const encryptedData = await response.arrayBuffer();
|
||||||
|
// Extraction des métadonnées depuis les headers
|
||||||
|
const user_id = response.headers.get('X-User-ID') || undefined;
|
||||||
|
const keyRotation = response.headers.get('X-Key-Rotation') || undefined;
|
||||||
|
const algorithm = response.headers.get('X-Algorithm') || undefined;
|
||||||
|
// Déchiffrement du contenu avec les headers pour la prochaine clé
|
||||||
|
const decryptedContent = this.decryptContent(Buffer.from(encryptedData), response.headers);
|
||||||
|
return {
|
||||||
|
content: decryptedContent,
|
||||||
|
filename: filePath.split('/').pop() || filePath,
|
||||||
|
size: decryptedContent.length,
|
||||||
|
encrypted: true,
|
||||||
|
algorithm,
|
||||||
|
user_id,
|
||||||
|
key_version: keyRotation,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof VaultApiError || error instanceof VaultAuthenticationError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new VaultApiError(`Erreur lors de la récupération du fichier: ${error instanceof Error ? error.message : 'Inconnue'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Récupère plusieurs fichiers en parallèle
|
||||||
|
*/
|
||||||
|
async getFiles(env, filePaths) {
|
||||||
|
const promises = filePaths.map(filePath => this.getFile(env, filePath));
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Recherche des fichiers correspondant à un pattern
|
||||||
|
*/
|
||||||
|
async searchFiles(_env, _pattern) {
|
||||||
|
// Cette fonctionnalité nécessiterait une implémentation côté serveur
|
||||||
|
// Pour l'instant, retour d'une erreur explicative
|
||||||
|
throw new VaultApiError('Recherche de fichiers non implémentée - contactez l\'administrateur', 'NOT_IMPLEMENTED');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Vérifie l'état de santé de l'API
|
||||||
|
*/
|
||||||
|
async health() {
|
||||||
|
const url = `${this.config.baseUrl}/health`;
|
||||||
|
try {
|
||||||
|
const response = await this._fetchApi(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new VaultApiError(`Erreur de santé API: ${response.status}`, 'HEALTH_CHECK_FAILED', response.status);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof VaultApiError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new VaultApiError(`Erreur lors du contrôle de santé: ${error instanceof Error ? error.message : 'Inconnue'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Récupère les informations sur l'API
|
||||||
|
*/
|
||||||
|
async info() {
|
||||||
|
const url = `${this.config.baseUrl}/info`;
|
||||||
|
try {
|
||||||
|
const response = await this._fetchApi(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new VaultApiError(`Erreur info API: ${response.status}`, 'INFO_ERROR', response.status);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof VaultApiError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new VaultApiError(`Erreur lors de la récupération des informations: ${error instanceof Error ? error.message : 'Inconnue'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Test de connectivité simple
|
||||||
|
*/
|
||||||
|
async ping() {
|
||||||
|
try {
|
||||||
|
await this.health();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Récupère toutes les routes disponibles de l'API
|
||||||
|
*/
|
||||||
|
async getRoutes() {
|
||||||
|
const url = `${this.config.baseUrl}/routes`;
|
||||||
|
try {
|
||||||
|
const response = await this._fetchApi(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new VaultAuthenticationError('Authentification échouée - vérifiez votre ID utilisateur', 'AUTH_FAILED', response.status);
|
||||||
|
}
|
||||||
|
throw new VaultApiError(`Erreur API routes: ${response.status} ${response.statusText}`, 'ROUTES_API_ERROR', response.status);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof VaultApiError || error instanceof VaultAuthenticationError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new VaultApiError(`Erreur lors de la récupération des routes: ${error instanceof Error ? error.message : 'Inconnue'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Synchronise les fichiers déchiffrés localement
|
||||||
|
* Route vault /<env>/<project>/<file_name> -> ../confs/<project>/<file_name>
|
||||||
|
* Les fichiers existants dans confs/ sont toujours écrasés pour avoir le contenu le plus récent
|
||||||
|
*/
|
||||||
|
async syncLocalFiles(options) {
|
||||||
|
// Recharger les variables d'environnement au cas où elles auraient changé
|
||||||
|
this._loadEnvironmentVariables();
|
||||||
|
// Récupérer le dossier de destination depuis les variables d'environnement
|
||||||
|
const defaultConfsDir = process.env['VAULT_CONFS_DIR'] || '../confs';
|
||||||
|
const { environment, localDir = defaultConfsDir, verbose = false } = options;
|
||||||
|
const result = {
|
||||||
|
synced: 0,
|
||||||
|
skipped: 0,
|
||||||
|
errors: 0,
|
||||||
|
details: []
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
// 1. Créer le dossier de destination s'il n'existe pas
|
||||||
|
// Résoudre le chemin par rapport au répertoire du fichier .env
|
||||||
|
let targetDir;
|
||||||
|
if (path_1.default.isAbsolute(localDir)) {
|
||||||
|
targetDir = localDir;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Pour les chemins relatifs, partir du répertoire du fichier .env
|
||||||
|
const envFileDir = this._getEnvFileDirectory();
|
||||||
|
targetDir = path_1.default.resolve(envFileDir, localDir);
|
||||||
|
}
|
||||||
|
if (!fs_1.default.existsSync(targetDir)) {
|
||||||
|
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
||||||
|
if (verbose) {
|
||||||
|
console.log(`📁 Dossier créé: ${targetDir}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. Récupérer la liste des routes pour identifier les fichiers
|
||||||
|
const routes = await this.getRoutes();
|
||||||
|
// 3. Extraire les fichiers disponibles depuis les exemples de routes
|
||||||
|
const fileRoute = routes.routes.find(route => route.path.includes('<env>') && route.path.includes('<file_path>'));
|
||||||
|
if (!fileRoute || !fileRoute.examples) {
|
||||||
|
throw new VaultApiError('Impossible de déterminer les fichiers disponibles');
|
||||||
|
}
|
||||||
|
// 4. Parser les exemples pour extraire les projets et fichiers
|
||||||
|
const filesToSync = [];
|
||||||
|
for (const example of fileRoute.examples) {
|
||||||
|
// Format: /dev/bitcoin/bitcoin.conf -> project: bitcoin, fileName: bitcoin.conf
|
||||||
|
const pathParts = example.split('/').filter(part => part && part !== environment);
|
||||||
|
if (pathParts.length >= 2) {
|
||||||
|
const project = pathParts[0] || 'unknown';
|
||||||
|
const fileName = pathParts[pathParts.length - 1] || 'unknown';
|
||||||
|
const vaultPath = pathParts.join('/');
|
||||||
|
filesToSync.push({
|
||||||
|
project,
|
||||||
|
fileName,
|
||||||
|
vaultPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 5. Synchroniser chaque fichier
|
||||||
|
for (const file of filesToSync) {
|
||||||
|
try {
|
||||||
|
const localProjectDir = path_1.default.join(targetDir, file.project);
|
||||||
|
const localFilePath = path_1.default.join(localProjectDir, file.fileName);
|
||||||
|
// Créer le dossier du projet s'il n'existe pas
|
||||||
|
if (!fs_1.default.existsSync(localProjectDir)) {
|
||||||
|
fs_1.default.mkdirSync(localProjectDir, { recursive: true });
|
||||||
|
if (verbose) {
|
||||||
|
console.log(`📁 Dossier projet créé: ${localProjectDir}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Toujours écraser les fichiers dans confs/ pour avoir le contenu le plus récent
|
||||||
|
// La vérification d'existence est conservée pour le logging uniquement
|
||||||
|
const fileExists = fs_1.default.existsSync(localFilePath);
|
||||||
|
if (fileExists && verbose) {
|
||||||
|
console.log(`🔄 Écrasement du fichier existant: ${file.vaultPath}`);
|
||||||
|
}
|
||||||
|
// Récupérer le fichier depuis le vault
|
||||||
|
const vaultFile = await this.getFile(environment, file.vaultPath);
|
||||||
|
// Extraire le contenu déchiffré (simulation pour le format de démonstration)
|
||||||
|
let content = vaultFile.content;
|
||||||
|
// Le contenu est maintenant déchiffré automatiquement par decryptContent
|
||||||
|
// Pas besoin de récupérer depuis le storage, le contenu vient de l'API déchiffrée
|
||||||
|
// Écrire le fichier local
|
||||||
|
fs_1.default.writeFileSync(localFilePath, content, 'utf8');
|
||||||
|
result.synced++;
|
||||||
|
result.details.push({
|
||||||
|
file: file.vaultPath,
|
||||||
|
status: 'synced',
|
||||||
|
message: `Synchronisé vers ${localFilePath}`
|
||||||
|
});
|
||||||
|
if (verbose) {
|
||||||
|
console.log(`✅ Synchronisé: ${file.vaultPath} -> ${localFilePath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
result.errors++;
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Erreur inconnue';
|
||||||
|
result.details.push({
|
||||||
|
file: file.vaultPath,
|
||||||
|
status: 'error',
|
||||||
|
message: errorMessage
|
||||||
|
});
|
||||||
|
if (verbose) {
|
||||||
|
console.log(`❌ Erreur: ${file.vaultPath} - ${errorMessage}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (verbose) {
|
||||||
|
console.log(`\n📊 Résumé de synchronisation:`);
|
||||||
|
console.log(` ✅ Synchronisés: ${result.synced}`);
|
||||||
|
console.log(` ⏭️ Ignorés: ${result.skipped}`);
|
||||||
|
console.log(` ❌ Erreurs: ${result.errors}`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
throw new VaultApiError(`Erreur lors de la synchronisation: ${error instanceof Error ? error.message : 'Inconnue'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Déchiffre le contenu avec les métadonnées utilisateur et gère la prochaine clé
|
||||||
|
*/
|
||||||
|
decryptContent(encryptedData, responseHeaders) {
|
||||||
|
try {
|
||||||
|
// Décoder le base64
|
||||||
|
const decoded = Buffer.from(encryptedData.toString(), 'base64');
|
||||||
|
// Nouveau format: nonce (12 bytes) + taille_métadonnées (4 bytes) + métadonnées + contenu chiffré
|
||||||
|
if (decoded.length < 16) {
|
||||||
|
throw new Error('Données chiffrées invalides - format incorrect');
|
||||||
|
}
|
||||||
|
const nonce = decoded.subarray(0, 12);
|
||||||
|
const metadataSize = decoded.readUInt32BE(12);
|
||||||
|
const metadataJson = decoded.subarray(16, 16 + metadataSize);
|
||||||
|
const ciphertext = decoded.subarray(16 + metadataSize);
|
||||||
|
// Parse des métadonnées
|
||||||
|
const metadata = JSON.parse(metadataJson.toString('utf-8'));
|
||||||
|
// Vérification de l'utilisateur
|
||||||
|
if (metadata.user_id !== this.config.userId) {
|
||||||
|
throw new VaultAuthenticationError('Métadonnées utilisateur ne correspondent pas', 'USER_MISMATCH');
|
||||||
|
}
|
||||||
|
// Récupération de la prochaine clé depuis les headers ou les métadonnées
|
||||||
|
const nextKey = responseHeaders?.get('X-Next-Key') || metadata.next_key;
|
||||||
|
// Pour le déchiffrement, utiliser la clé courante d'abord
|
||||||
|
const keyToUse = this.vaultKey;
|
||||||
|
if (!keyToUse) {
|
||||||
|
throw new VaultDecryptionError('Clé de déchiffrement non disponible');
|
||||||
|
}
|
||||||
|
console.log(`🔑 Utilisation de la clé courante pour déchiffrement: ${keyToUse.substring(0, 20)}...`);
|
||||||
|
// Ne pas mettre à jour la clé immédiatement, attendre un déchiffrement réussi
|
||||||
|
try {
|
||||||
|
// Convertir la clé base64 en Uint8Array
|
||||||
|
const key = Buffer.from(keyToUse, 'base64');
|
||||||
|
// Déchiffrement ChaCha20-Poly1305 avec @noble/ciphers
|
||||||
|
const cipher = chacha20poly1305(key, nonce);
|
||||||
|
const decrypted = cipher.decrypt(ciphertext);
|
||||||
|
// Déchiffrement réussi, mettre à jour la clé pour la prochaine requête
|
||||||
|
if (nextKey) {
|
||||||
|
this.updateNextKey(nextKey);
|
||||||
|
}
|
||||||
|
// Retourner le contenu déchiffré
|
||||||
|
return Buffer.from(decrypted).toString('utf-8');
|
||||||
|
}
|
||||||
|
catch (decryptError) {
|
||||||
|
// Si le déchiffrement échoue, essayer avec la prochaine clé si elle est différente
|
||||||
|
if (nextKey && nextKey !== keyToUse) {
|
||||||
|
console.log(`🔄 Tentative de déchiffrement avec la prochaine clé...`);
|
||||||
|
try {
|
||||||
|
const nextKeyBuffer = Buffer.from(nextKey, 'base64');
|
||||||
|
const cipherNext = chacha20poly1305(nextKeyBuffer, nonce);
|
||||||
|
const decryptedNext = cipherNext.decrypt(ciphertext);
|
||||||
|
console.log(`✅ Déchiffrement réussi avec la prochaine clé !`);
|
||||||
|
// Déchiffrement réussi avec la prochaine clé, mettre à jour
|
||||||
|
this.updateNextKey(nextKey);
|
||||||
|
return Buffer.from(decryptedNext).toString('utf-8');
|
||||||
|
}
|
||||||
|
catch (nextDecryptError) {
|
||||||
|
console.warn(`⚠️ Déchiffrement avec la prochaine clé échoué: ${nextDecryptError instanceof Error ? nextDecryptError.message : 'Erreur inconnue'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new VaultDecryptionError(`Erreur de déchiffrement ChaCha20-Poly1305: ${decryptError instanceof Error ? decryptError.message : 'Inconnue'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof VaultAuthenticationError || error instanceof VaultDecryptionError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new VaultDecryptionError(`Erreur de déchiffrement: ${error instanceof Error ? error.message : 'Inconnue'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Met à jour la prochaine clé pour les requêtes suivantes et le fichier .env
|
||||||
|
*/
|
||||||
|
updateNextKey(nextKey) {
|
||||||
|
if (nextKey) {
|
||||||
|
this.vaultKey = nextKey; // Mettre à jour la clé courante
|
||||||
|
// Mettre à jour le fichier .env avec la nouvelle clé
|
||||||
|
this.updateEnvFile(nextKey);
|
||||||
|
console.log(`🔑 Prochaine clé mise à jour: ${nextKey.substring(0, 20)}...`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Retourne le répertoire du fichier .env trouvé
|
||||||
|
*/
|
||||||
|
_getEnvFileDirectory() {
|
||||||
|
// Chercher le fichier .env dans l'ordre de priorité
|
||||||
|
const possibleEnvPaths = [
|
||||||
|
path_1.default.join(__dirname, '.env'), // Répertoire du SDK (priorité 1)
|
||||||
|
path_1.default.join(process.cwd(), '.env'), // Répertoire de travail (priorité 2)
|
||||||
|
'.env', // Répertoire courant (priorité 3)
|
||||||
|
'../.env', // Répertoire parent (priorité 4)
|
||||||
|
'../../.env', // Répertoire grand-parent (priorité 5)
|
||||||
|
path_1.default.join(__dirname, '../.env'), // Parent du SDK (priorité 6)
|
||||||
|
path_1.default.join(__dirname, '../../.env'), // Grand-parent du SDK (priorité 7)
|
||||||
|
];
|
||||||
|
for (const envPath of possibleEnvPaths) {
|
||||||
|
try {
|
||||||
|
if (fs_1.default.existsSync(envPath)) {
|
||||||
|
// Retourner le répertoire du fichier .env
|
||||||
|
const envDir = path_1.default.dirname(path_1.default.resolve(envPath));
|
||||||
|
return envDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// Ignorer les erreurs et continuer avec le chemin suivant
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback vers le répertoire du SDK si aucun .env n'est trouvé
|
||||||
|
return __dirname;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Charge les variables d'environnement depuis plusieurs emplacements possibles
|
||||||
|
*/
|
||||||
|
_loadEnvironmentVariables() {
|
||||||
|
const possibleEnvPaths = [
|
||||||
|
'.env', // Répertoire courant
|
||||||
|
'../.env', // Répertoire parent
|
||||||
|
'../../.env', // Répertoire grand-parent
|
||||||
|
path_1.default.join(__dirname, '.env'), // Répertoire du SDK
|
||||||
|
path_1.default.join(__dirname, '../.env'), // Parent du SDK
|
||||||
|
path_1.default.join(__dirname, '../../.env'), // Grand-parent du SDK
|
||||||
|
path_1.default.join(process.cwd(), '.env'), // Répertoire de travail
|
||||||
|
];
|
||||||
|
let envLoaded = false;
|
||||||
|
for (const envPath of possibleEnvPaths) {
|
||||||
|
try {
|
||||||
|
if (fs_1.default.existsSync(envPath)) {
|
||||||
|
const result = dotenv_1.default.config({ path: envPath });
|
||||||
|
if (!result.error) {
|
||||||
|
console.log(`📄 Variables d'environnement chargées depuis: ${envPath}`);
|
||||||
|
envLoaded = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// Ignorer les erreurs et continuer avec le chemin suivant
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Si aucun fichier .env n'a été trouvé, essayer le chargement par défaut
|
||||||
|
if (!envLoaded) {
|
||||||
|
try {
|
||||||
|
dotenv_1.default.config();
|
||||||
|
console.log('📄 Variables d\'environnement chargées depuis .env par défaut');
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log('⚠️ Aucun fichier .env trouvé, utilisation des variables système uniquement');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Met à jour le fichier .env avec la nouvelle clé
|
||||||
|
*/
|
||||||
|
updateEnvFile(newKey) {
|
||||||
|
try {
|
||||||
|
const envPath = path_1.default.join(__dirname, '../../.env');
|
||||||
|
// Lire le fichier .env actuel
|
||||||
|
let envContent = '';
|
||||||
|
if (fs_1.default.existsSync(envPath)) {
|
||||||
|
envContent = fs_1.default.readFileSync(envPath, 'utf8');
|
||||||
|
}
|
||||||
|
// Mettre à jour ou ajouter la clé VAULT_KEY
|
||||||
|
const keyRegex = /^VAULT_KEY=.*$/m;
|
||||||
|
if (keyRegex.test(envContent)) {
|
||||||
|
envContent = envContent.replace(keyRegex, `VAULT_KEY="${newKey}"`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
envContent += `\nVAULT_KEY="${newKey}"\n`;
|
||||||
|
}
|
||||||
|
// Écrire le fichier .env mis à jour
|
||||||
|
fs_1.default.writeFileSync(envPath, envContent, 'utf8');
|
||||||
|
console.log(`📝 Fichier .env mis à jour avec la nouvelle clé`);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.warn(`⚠️ Impossible de mettre à jour le fichier .env: ${error instanceof Error ? error.message : 'Erreur inconnue'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Effectue une requête vers l'API avec authentification
|
||||||
|
*/
|
||||||
|
async _fetchApi(url) {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
||||||
|
try {
|
||||||
|
const mergedOptions = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'SecureVaultClient/2.0.0',
|
||||||
|
'X-User-ID': this.config.userId,
|
||||||
|
'Accept': 'application/octet-stream'
|
||||||
|
},
|
||||||
|
signal: controller.signal
|
||||||
|
};
|
||||||
|
// Configuration SSL pour les certificats auto-signés
|
||||||
|
if (this.config.verifySsl === false) {
|
||||||
|
mergedOptions.agent = new https_1.default.Agent({
|
||||||
|
rejectUnauthorized: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const response = await (0, node_fetch_1.default)(url, mergedOptions);
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
|
throw new VaultApiError(`Timeout de la requête (${this.config.timeout}ms)`, 'TIMEOUT');
|
||||||
|
}
|
||||||
|
throw new VaultApiError(`Erreur de connexion: ${error instanceof Error ? error.message : 'Inconnue'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.SecureVaultClient = SecureVaultClient;
|
||||||
|
/**
|
||||||
|
* Fonction utilitaire pour créer un client sécurisé
|
||||||
|
*/
|
||||||
|
function createSecureVaultClient(baseUrl, userId) {
|
||||||
|
return new SecureVaultClient({
|
||||||
|
baseUrl,
|
||||||
|
userId,
|
||||||
|
verifySsl: false, // Pour les certificats auto-signés en développement
|
||||||
|
timeout: 15000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Fonction utilitaire pour créer un client sécurisé avec configuration complète
|
||||||
|
*/
|
||||||
|
function createSecureVaultClientWithConfig(config) {
|
||||||
|
return new SecureVaultClient(config);
|
||||||
|
}
|
@ -8,7 +8,7 @@ exports.step1_Initialization = step1_Initialization;
|
|||||||
exports.step2_GetRoutes = step2_GetRoutes;
|
exports.step2_GetRoutes = step2_GetRoutes;
|
||||||
exports.step3_ParseRoutes = step3_ParseRoutes;
|
exports.step3_ParseRoutes = step3_ParseRoutes;
|
||||||
exports.step4_DecryptContents = step4_DecryptContents;
|
exports.step4_DecryptContents = step4_DecryptContents;
|
||||||
const index_1 = require("../src/index");
|
const index_1 = require("../dist/src/index");
|
||||||
/**
|
/**
|
||||||
* ÉTAPE 1: Initialisation + Gestion des erreurs
|
* ÉTAPE 1: Initialisation + Gestion des erreurs
|
||||||
*/
|
*/
|
||||||
|
File diff suppressed because one or more lines are too long
@ -253,12 +253,12 @@ async function step4_SyncLocalFiles(client: SecureVaultClient) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('\n🔄 Synchronisation des fichiers vers le dossier local...');
|
console.log('\n🔄 Synchronisation des fichiers vers le dossier local...');
|
||||||
console.log(' Mapping: /<env>/<project>/<file> -> ../confs/<project>/<file>');
|
console.log(' Utilise VAULT_CONFS_DIR depuis le fichier .env');
|
||||||
|
|
||||||
// Synchronisation avec options détaillées
|
// Synchronisation avec options détaillées
|
||||||
|
// Ne pas spécifier localDir pour utiliser VAULT_CONFS_DIR du .env
|
||||||
const syncResult = await client.syncLocalFiles({
|
const syncResult = await client.syncLocalFiles({
|
||||||
environment: 'dev',
|
environment: 'dev',
|
||||||
localDir: '../confs',
|
|
||||||
verbose: true
|
verbose: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
const fetch = require('node-fetch');
|
|
||||||
|
|
||||||
async function test() {
|
|
||||||
try {
|
|
||||||
console.log('Test simple de l\'API...');
|
|
||||||
|
|
||||||
const response = await fetch('https://127.0.0.1:6666/health', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
console.log('✅ API fonctionne:', data);
|
|
||||||
} else {
|
|
||||||
console.log('❌ Erreur HTTP:', response.status, response.statusText);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('❌ Erreur:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test();
|
|
@ -1,74 +0,0 @@
|
|||||||
/**
|
|
||||||
* Test de connexion au SDK Vault
|
|
||||||
* Affiche l'URL utilisée et teste la connectivité
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { SecureVaultClient } = require('./dist/src/index.js');
|
|
||||||
|
|
||||||
async function testConnection() {
|
|
||||||
console.log('🔍 Test de connexion au SDK Vault');
|
|
||||||
console.log('===================================');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Afficher les variables d'environnement chargées
|
|
||||||
console.log('\n📋 Variables d\'environnement:');
|
|
||||||
console.log(` VAULT_USER: ${process.env.VAULT_USER || 'NON DÉFINI'}`);
|
|
||||||
console.log(` VAULT_ENV: ${process.env.VAULT_ENV || 'NON DÉFINI'}`);
|
|
||||||
console.log(` VAULT_URL: ${process.env.VAULT_URL || 'NON DÉFINI'}`);
|
|
||||||
console.log(` VAULT_CONFS_DIR: ${process.env.VAULT_CONFS_DIR || 'NON DÉFINI'}`);
|
|
||||||
|
|
||||||
// Créer le client
|
|
||||||
const client = new SecureVaultClient();
|
|
||||||
|
|
||||||
// Afficher l'URL configurée
|
|
||||||
console.log('\n🌐 Configuration du client:');
|
|
||||||
console.log(` URL de base: ${client.config?.baseUrl || 'NON DÉFINI'}`);
|
|
||||||
console.log(` Utilisateur: ${client.config?.userId || 'NON DÉFINI'}`);
|
|
||||||
|
|
||||||
// Test de connectivité
|
|
||||||
console.log('\n🔌 Test de connectivité...');
|
|
||||||
const isConnected = await client.ping();
|
|
||||||
|
|
||||||
if (isConnected) {
|
|
||||||
console.log(' ✅ Connexion réussie !');
|
|
||||||
|
|
||||||
// Test de récupération des routes
|
|
||||||
console.log('\n📁 Test de récupération des routes...');
|
|
||||||
const routes = await client.getRoutes('dev');
|
|
||||||
console.log(` ✅ ${routes.length} routes récupérées`);
|
|
||||||
|
|
||||||
// Test de synchronisation
|
|
||||||
console.log('\n📂 Test de synchronisation...');
|
|
||||||
const syncResult = await client.syncLocalFiles({
|
|
||||||
environment: 'dev',
|
|
||||||
localDir: './test-confs',
|
|
||||||
verbose: true
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(` ✅ Synchronisation terminée:`);
|
|
||||||
console.log(` - Fichiers synchronisés: ${syncResult.synced}`);
|
|
||||||
console.log(` - Fichiers ignorés: ${syncResult.skipped}`);
|
|
||||||
console.log(` - Erreurs: ${syncResult.errors}`);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.log(' ❌ Connexion échouée !');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('\n❌ Erreur:', error.message);
|
|
||||||
|
|
||||||
if (error.message.includes('Variables d\'environnement requises')) {
|
|
||||||
console.log('\n💡 Solution:');
|
|
||||||
console.log(' 1. Créez un fichier .env dans le dossier sdk-client/');
|
|
||||||
console.log(' 2. Copiez .env.example vers .env');
|
|
||||||
console.log(' 3. Modifiez les valeurs dans .env');
|
|
||||||
console.log('\n Exemple de .env:');
|
|
||||||
console.log(' VAULT_USER=demo_user_001');
|
|
||||||
console.log(' VAULT_KEY=JYyybYFXe9hghRI9d1mpoQ1uYYxpt/6lzYPOWrxruG0=');
|
|
||||||
console.log(' VAULT_ENV=dev');
|
|
||||||
console.log(' VAULT_URL=https://vault.4nkweb.com:6666');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testConnection();
|
|
@ -1,21 +0,0 @@
|
|||||||
const { SecureVaultClient } = require('./dist/src/index.js');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
async function testDecryption() {
|
|
||||||
console.log('🔍 Test de déchiffrement simple...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const client = new SecureVaultClient();
|
|
||||||
console.log('✅ Client créé');
|
|
||||||
|
|
||||||
// Test avec un fichier simple
|
|
||||||
const result = await client.getFile('dev', 'bitcoin/bitcoin.conf');
|
|
||||||
console.log('✅ Fichier récupéré:', result.content.substring(0, 100) + '...');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Erreur:', error.message);
|
|
||||||
console.error('Stack:', error.stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testDecryption();
|
|
@ -1,47 +0,0 @@
|
|||||||
const { SecureVaultClient } = require('./dist/src/index.js');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
async function testMultipleFiles() {
|
|
||||||
console.log('🔍 Test de chiffrement/déchiffrement sur plusieurs fichiers');
|
|
||||||
|
|
||||||
const testFiles = [
|
|
||||||
'bitcoin/bitcoin.conf',
|
|
||||||
'tor/torrc',
|
|
||||||
'grafana/grafana.ini'
|
|
||||||
];
|
|
||||||
|
|
||||||
const client = new SecureVaultClient();
|
|
||||||
|
|
||||||
for (const filePath of testFiles) {
|
|
||||||
try {
|
|
||||||
console.log(`\n📁 Test du fichier: ${filePath}`);
|
|
||||||
|
|
||||||
// 1. Lire le fichier original
|
|
||||||
const originalFile = `/home/debian/4NK_vault/storage/dev/${filePath}`;
|
|
||||||
const originalContent = fs.readFileSync(originalFile, 'utf8');
|
|
||||||
|
|
||||||
// 2. Récupérer via API (chiffré)
|
|
||||||
const result = await client.getFile('dev', filePath);
|
|
||||||
|
|
||||||
// 3. Comparer
|
|
||||||
if (originalContent === result.content) {
|
|
||||||
console.log(`✅ ${filePath}: Chiffrement/déchiffrement réussi`);
|
|
||||||
console.log(` Taille: ${originalContent.length} → ${result.content.length} caractères`);
|
|
||||||
} else {
|
|
||||||
console.log(`❌ ${filePath}: Échec du chiffrement/déchiffrement`);
|
|
||||||
console.log(` Différences détectées !`);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`❌ ${filePath}: Erreur - ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n🎯 Résumé:');
|
|
||||||
console.log('✅ Chiffrement réel par l\'API Python (ChaCha20-Poly1305)');
|
|
||||||
console.log('✅ Déchiffrement réel par le SDK Node.js (@noble/ciphers)');
|
|
||||||
console.log('✅ Contenu identique après chiffrement/déchiffrement');
|
|
||||||
console.log('✅ Flux de données sécurisé et fonctionnel');
|
|
||||||
}
|
|
||||||
|
|
||||||
testMultipleFiles();
|
|
@ -1,79 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test de la nouvelle fonctionnalité getRoutes() du SDK client
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { createSecureVaultClient } = require('./dist/index.js');
|
|
||||||
|
|
||||||
async function testRoutes() {
|
|
||||||
console.log('🔐 Test de la fonctionnalité getRoutes()');
|
|
||||||
console.log('='.repeat(60));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. Création du client avec ID utilisateur
|
|
||||||
const client = createSecureVaultClient(
|
|
||||||
'https://vault.4nkweb.com:6666',
|
|
||||||
'demo_user_001' // ID utilisateur obligatoire
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. Test de connectivité via health
|
|
||||||
console.log('🔍 Test de connectivité...');
|
|
||||||
try {
|
|
||||||
await client.health();
|
|
||||||
console.log('✅ Connecté avec succès');
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('❌ Impossible de se connecter à l\'API');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Test de la nouvelle méthode getRoutes()
|
|
||||||
console.log('\n🛣️ Récupération des routes disponibles...');
|
|
||||||
const routes = await client.getRoutes();
|
|
||||||
|
|
||||||
console.log(`\n📋 Résultats:`);
|
|
||||||
console.log(` Total des routes: ${routes.total_routes}`);
|
|
||||||
console.log(` Utilisateur: ${routes.user_id}`);
|
|
||||||
console.log(` Timestamp: ${routes.timestamp}`);
|
|
||||||
console.log(` Type d'authentification: ${routes.authentication.type}`);
|
|
||||||
|
|
||||||
console.log('\n📝 Routes disponibles:');
|
|
||||||
routes.routes.forEach((route, index) => {
|
|
||||||
console.log(`\n ${index + 1}. ${route.method} ${route.path}`);
|
|
||||||
console.log(` Description: ${route.description}`);
|
|
||||||
console.log(` Authentification: ${route.authentication}`);
|
|
||||||
console.log(` Headers requis: ${route.headers_required.join(', ')}`);
|
|
||||||
console.log(` Type de réponse: ${route.response_type}`);
|
|
||||||
|
|
||||||
if (route.parameters) {
|
|
||||||
console.log(` Paramètres:`);
|
|
||||||
Object.entries(route.parameters).forEach(([key, value]) => {
|
|
||||||
console.log(` - ${key}: ${value}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.examples && route.examples.length > 0) {
|
|
||||||
console.log(` Exemples:`);
|
|
||||||
route.examples.forEach(example => {
|
|
||||||
console.log(` - ${example}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n✅ Test réussi - Toutes les routes ont été récupérées');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('\n❌ Erreur lors du test:');
|
|
||||||
console.error(` Type: ${error.name}`);
|
|
||||||
console.error(` Message: ${error.message}`);
|
|
||||||
if (error.statusCode) {
|
|
||||||
console.error(` Code HTTP: ${error.statusCode}`);
|
|
||||||
}
|
|
||||||
if (error.code) {
|
|
||||||
console.error(` Code d'erreur: ${error.code}`);
|
|
||||||
}
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exécution du test
|
|
||||||
testRoutes().catch(console.error);
|
|
58
sdk-client/test-usage.js
Normal file
58
sdk-client/test-usage.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Script de test simple qui respecte VAULT_CONFS_DIR
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { SecureVaultClient } = require('./dist/src/index.js');
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('🧪 Test du SDK avec VAULT_CONFS_DIR');
|
||||||
|
console.log('='.repeat(50));
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Initialiser le client (charge automatiquement .env)
|
||||||
|
const client = new SecureVaultClient();
|
||||||
|
|
||||||
|
// Afficher les variables chargées
|
||||||
|
console.log('📄 Variables d\'environnement:');
|
||||||
|
console.log(` VAULT_CONFS_DIR: ${process.env.VAULT_CONFS_DIR}`);
|
||||||
|
console.log(` VAULT_USER: ${process.env.VAULT_USER}`);
|
||||||
|
console.log(` VAULT_ENV: ${process.env.VAULT_ENV}`);
|
||||||
|
|
||||||
|
// Test de connectivité
|
||||||
|
console.log('\n🔍 Test de connectivité...');
|
||||||
|
const isConnected = await client.ping();
|
||||||
|
console.log(` ✅ Connecté: ${isConnected}`);
|
||||||
|
|
||||||
|
// Récupération des routes
|
||||||
|
console.log('\n🛣️ Récupération des routes...');
|
||||||
|
const routes = await client.getRoutes();
|
||||||
|
console.log(` ✅ ${routes.total_routes} routes disponibles`);
|
||||||
|
|
||||||
|
// Synchronisation (utilise VAULT_CONFS_DIR automatiquement)
|
||||||
|
console.log('\n💾 Synchronisation des fichiers...');
|
||||||
|
console.log(' Utilise VAULT_CONFS_DIR depuis .env');
|
||||||
|
|
||||||
|
const syncResult = await client.syncLocalFiles({
|
||||||
|
environment: 'dev',
|
||||||
|
verbose: true
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n📊 Résultats:');
|
||||||
|
console.log(` ✅ Fichiers synchronisés: ${syncResult.synced}`);
|
||||||
|
console.log(` ⏭️ Fichiers ignorés: ${syncResult.skipped}`);
|
||||||
|
console.log(` ❌ Erreurs: ${syncResult.errors}`);
|
||||||
|
|
||||||
|
console.log('\n🎉 Test terminé avec succès !');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ Erreur:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exécution
|
||||||
|
if (require.main === module) {
|
||||||
|
main().catch(console.error);
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
const { createSecureVaultClient } = require('./dist/index.js'); const client = createSecureVaultClient('https://localhost:6666', 'demo_user_001'); client.getFile('dev', 'bitcoin/bitcoin.conf').then(result => { console.log('✅ Succès:', result.filename); console.log('🔑 Contenu:', result.content.substring(0, 200)); console.log('📊 Métadonnées:', { algorithm: result.algorithm, user_id: result.user_id, key_version: result.key_version }); }).catch(err => console.error('❌ Erreur:', err.message));
|
|
273
test_api.py
273
test_api.py
@ -1,273 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Test de l'API Vault sécurisée avec authentification par clés utilisateur
|
|
||||||
"""
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
import base64
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
BASE_URL = 'https://127.0.0.1:6666'
|
|
||||||
USER_ID = 'demo_user_001' # ID utilisateur de test
|
|
||||||
VERIFY_SSL = False # Certificats auto-signés
|
|
||||||
|
|
||||||
def test_health():
|
|
||||||
"""Test de l'endpoint de santé"""
|
|
||||||
print("🔍 Test de santé de l'API...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.get(
|
|
||||||
f"{BASE_URL}/health",
|
|
||||||
verify=VERIFY_SSL,
|
|
||||||
headers={'X-User-ID': USER_ID}
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
health_data = response.json()
|
|
||||||
print(f"✅ API en bonne santé")
|
|
||||||
print(f" Service: {health_data.get('service')}")
|
|
||||||
print(f" Chiffrement: {health_data.get('encryption')}")
|
|
||||||
print(f" Algorithme: {health_data.get('algorithm')}")
|
|
||||||
print(f" Authentification: {health_data.get('authentication')}")
|
|
||||||
print(f" Rotation des clés: {health_data.get('key_rotation')}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f"❌ Erreur de santé: {response.status_code}")
|
|
||||||
print(f" Réponse: {response.text}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Erreur de connexion: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_info():
|
|
||||||
"""Test de l'endpoint d'informations"""
|
|
||||||
print("\n📋 Test des informations API...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.get(
|
|
||||||
f"{BASE_URL}/info",
|
|
||||||
verify=VERIFY_SSL,
|
|
||||||
headers={'X-User-ID': USER_ID}
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
info_data = response.json()
|
|
||||||
print(f"✅ Informations récupérées")
|
|
||||||
print(f" Nom: {info_data.get('name')}")
|
|
||||||
print(f" Version: {info_data.get('version')}")
|
|
||||||
print(f" Domaine: {info_data.get('domain')}")
|
|
||||||
print(f" Protocole: {info_data.get('protocol')}")
|
|
||||||
print(f" Authentification: {info_data.get('authentication')}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f"❌ Erreur d'informations: {response.status_code}")
|
|
||||||
print(f" Réponse: {response.text}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Erreur de connexion: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_file_access():
|
|
||||||
"""Test d'accès à un fichier"""
|
|
||||||
print("\n📁 Test d'accès au fichier...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Test avec l'environnement dev qui existe
|
|
||||||
response = requests.get(
|
|
||||||
f"{BASE_URL}/dev/bitcoin/bitcoin.conf",
|
|
||||||
verify=VERIFY_SSL,
|
|
||||||
headers={'X-User-ID': USER_ID}
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
print(f"✅ Fichier récupéré avec succès")
|
|
||||||
print(f" Taille: {len(response.content)} bytes")
|
|
||||||
print(f" Type de contenu: {response.headers.get('Content-Type')}")
|
|
||||||
print(f" Type de chiffrement: {response.headers.get('X-Encryption-Type')}")
|
|
||||||
print(f" Algorithme: {response.headers.get('X-Algorithm')}")
|
|
||||||
print(f" ID utilisateur: {response.headers.get('X-User-ID')}")
|
|
||||||
print(f" Rotation des clés: {response.headers.get('X-Key-Rotation')}")
|
|
||||||
|
|
||||||
# Tentative de décodage des métadonnées
|
|
||||||
try:
|
|
||||||
decoded = base64.b64decode(response.content)
|
|
||||||
if len(decoded) >= 16:
|
|
||||||
nonce = decoded[:12]
|
|
||||||
metadata_size = int.from_bytes(decoded[12:16], 'big')
|
|
||||||
metadata_json = decoded[16:16+metadata_size]
|
|
||||||
metadata = json.loads(metadata_json.decode('utf-8'))
|
|
||||||
|
|
||||||
print(f"\n📊 Métadonnées de chiffrement:")
|
|
||||||
print(f" Utilisateur: {metadata.get('user_id')}")
|
|
||||||
print(f" Version de clé: {metadata.get('key_version')}")
|
|
||||||
print(f" Timestamp: {metadata.get('timestamp')}")
|
|
||||||
print(f" Algorithme: {metadata.get('algorithm')}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"⚠️ Impossible de décoder les métadonnées: {e}")
|
|
||||||
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f"❌ Erreur d'accès au fichier: {response.status_code}")
|
|
||||||
print(f" Réponse: {response.text}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Erreur de connexion: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_authentication():
|
|
||||||
"""Test d'authentification avec différents ID utilisateur"""
|
|
||||||
print("\n🔐 Test d'authentification...")
|
|
||||||
|
|
||||||
test_users = [
|
|
||||||
('valid_user_001', True),
|
|
||||||
('invalid@user', False),
|
|
||||||
('ab', False), # Trop court
|
|
||||||
('a' * 51, False), # Trop long
|
|
||||||
('', False), # Vide
|
|
||||||
]
|
|
||||||
|
|
||||||
for user_id, should_succeed in test_users:
|
|
||||||
print(f"\n Test utilisateur: '{user_id}' (attendu: {'succès' if should_succeed else 'échec'})")
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.get(
|
|
||||||
f"{BASE_URL}/health",
|
|
||||||
verify=VERIFY_SSL,
|
|
||||||
headers={'X-User-ID': user_id} if user_id else {}
|
|
||||||
)
|
|
||||||
|
|
||||||
success = response.status_code == 200
|
|
||||||
if success == should_succeed:
|
|
||||||
print(f" ✅ Résultat attendu")
|
|
||||||
else:
|
|
||||||
print(f" ❌ Résultat inattendu: {response.status_code}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
if not should_succeed:
|
|
||||||
print(f" ✅ Erreur attendue: {e}")
|
|
||||||
else:
|
|
||||||
print(f" ❌ Erreur inattendue: {e}")
|
|
||||||
|
|
||||||
def test_https_requirement():
|
|
||||||
"""Test de l'obligation HTTPS"""
|
|
||||||
print("\n🔒 Test de l'obligation HTTPS...")
|
|
||||||
|
|
||||||
# Tentative d'accès HTTP (devrait échouer)
|
|
||||||
http_url = BASE_URL.replace('https://', 'http://')
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.get(
|
|
||||||
f"{http_url}/health",
|
|
||||||
verify=VERIFY_SSL,
|
|
||||||
headers={'X-User-ID': USER_ID},
|
|
||||||
timeout=5
|
|
||||||
)
|
|
||||||
print(f"❌ HTTP autorisé (ne devrait pas l'être): {response.status_code}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✅ HTTP refusé comme attendu: {e}")
|
|
||||||
|
|
||||||
def test_key_rotation():
|
|
||||||
"""Test de la rotation des clés"""
|
|
||||||
print("\n🔄 Test de la rotation des clés...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Premier accès
|
|
||||||
response1 = requests.get(
|
|
||||||
f"{BASE_URL}/dev/bitcoin/bitcoin.conf",
|
|
||||||
verify=VERIFY_SSL,
|
|
||||||
headers={'X-User-ID': USER_ID}
|
|
||||||
)
|
|
||||||
|
|
||||||
if response1.status_code == 200:
|
|
||||||
# Attendre un peu et refaire un accès
|
|
||||||
import time
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
response2 = requests.get(
|
|
||||||
f"{BASE_URL}/dev/bitcoin/bitcoin.conf",
|
|
||||||
verify=VERIFY_SSL,
|
|
||||||
headers={'X-User-ID': USER_ID}
|
|
||||||
)
|
|
||||||
|
|
||||||
if response2.status_code == 200:
|
|
||||||
# Comparer les réponses (les clés peuvent avoir changé)
|
|
||||||
print(f"✅ Accès multiples réussis")
|
|
||||||
print(f" Premier accès: {len(response1.content)} bytes")
|
|
||||||
print(f" Deuxième accès: {len(response2.content)} bytes")
|
|
||||||
|
|
||||||
# Les réponses peuvent être différentes à cause de la rotation des clés
|
|
||||||
if response1.content != response2.content:
|
|
||||||
print(f" ✅ Contenu différent détecté (rotation des clés possible)")
|
|
||||||
else:
|
|
||||||
print(f" ⚠️ Contenu identique (rotation pas encore déclenchée)")
|
|
||||||
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f"❌ Deuxième accès échoué: {response2.status_code}")
|
|
||||||
else:
|
|
||||||
print(f"❌ Premier accès échoué: {response1.status_code}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Erreur lors du test de rotation: {e}")
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Fonction principale de test"""
|
|
||||||
print("🚀 Test de l'API Vault sécurisée")
|
|
||||||
print("=" * 50)
|
|
||||||
print(f"URL de base: {BASE_URL}")
|
|
||||||
print(f"ID utilisateur: {USER_ID}")
|
|
||||||
print(f"Vérification SSL: {VERIFY_SSL}")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
# Tests
|
|
||||||
tests = [
|
|
||||||
("Santé de l'API", test_health),
|
|
||||||
("Informations API", test_info),
|
|
||||||
("Accès aux fichiers", test_file_access),
|
|
||||||
("Authentification", test_authentication),
|
|
||||||
("Obligation HTTPS", test_https_requirement),
|
|
||||||
("Rotation des clés", test_key_rotation),
|
|
||||||
]
|
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
for test_name, test_func in tests:
|
|
||||||
try:
|
|
||||||
result = test_func()
|
|
||||||
results.append((test_name, result))
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Erreur dans le test '{test_name}': {e}")
|
|
||||||
results.append((test_name, False))
|
|
||||||
|
|
||||||
# Résumé
|
|
||||||
print("\n" + "=" * 50)
|
|
||||||
print("📊 RÉSUMÉ DES TESTS")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
passed = 0
|
|
||||||
total = len(results)
|
|
||||||
|
|
||||||
for test_name, result in results:
|
|
||||||
status = "✅ PASSÉ" if result else "❌ ÉCHOUÉ"
|
|
||||||
print(f"{status} - {test_name}")
|
|
||||||
if result:
|
|
||||||
passed += 1
|
|
||||||
|
|
||||||
print(f"\nRésultat global: {passed}/{total} tests passés")
|
|
||||||
|
|
||||||
if passed == total:
|
|
||||||
print("🎉 Tous les tests sont passés avec succès!")
|
|
||||||
else:
|
|
||||||
print("⚠️ Certains tests ont échoué. Vérifiez la configuration.")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
Loading…
x
Reference in New Issue
Block a user