import express from 'express'; import crypto from 'crypto'; import db from '../database.js'; import { authenticateToken } from '../middleware/auth.js'; const router = express.Router(); const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || 'default-insecure-key-change-in-production'; // Supported sites const SUPPORTED_SITES = [ { id: 'thingiverse', name: 'Thingiverse', icon: 'fa-cube' }, { id: 'printables', name: 'Printables', icon: 'fa-print' }, { id: 'makerworld', name: 'MakerWorld', icon: 'fa-globe' }, { id: 'myminifactory', name: 'MyMiniFactory', icon: 'fa-factory' }, { id: 'cults3d', name: 'Cults3D', icon: 'fa-cube' } ]; // Encryption helper functions function encrypt(text) { try { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY.padEnd(32, '0').slice(0, 32)), iv); let encrypted = cipher.update(text); encrypted = Buffer.concat([encrypted, cipher.final()]); return iv.toString('hex') + ':' + encrypted.toString('hex'); } catch (error) { console.error('Encryption error:', error); return text; } } function decrypt(text) { try { const parts = text.split(':'); if (parts.length !== 2) return text; const iv = Buffer.from(parts[0], 'hex'); const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY.padEnd(32, '0').slice(0, 32)), iv); let decrypted = decipher.update(Buffer.from(parts[1], 'hex')); decrypted = Buffer.concat([decrypted, decipher.final()]); return decrypted.toString(); } catch (error) { console.error('Decryption error:', error); return text; } } // Get list of supported sites router.get('/sites', (req, res) => { res.json({ sites: SUPPORTED_SITES }); }); // Get user's connected accounts router.get('/my-accounts', authenticateToken, (req, res) => { const query = ` SELECT id, site_name, username, is_connected, last_updated FROM site_credentials WHERE user_id = ? ORDER BY site_name ASC `; db.all(query, [req.user.id], (err, credentials) => { if (err) { return res.status(500).json({ error: err.message }); } res.json({ accounts: credentials || [], sites: SUPPORTED_SITES }); }); }); // Add or update credentials for a site router.post('/connect', authenticateToken, async (req, res) => { const { siteName, username, password, apiKey, accessToken } = req.body; if (!siteName || (!username && !apiKey && !accessToken)) { return res.status(400).json({ error: 'Site name and credentials are required' }); } // Validate site name const validSite = SUPPORTED_SITES.find(s => s.id === siteName); if (!validSite) { return res.status(400).json({ error: 'Unsupported site' }); } try { const encryptedPassword = password ? encrypt(password) : null; const encryptedApiKey = apiKey ? encrypt(apiKey) : null; const encryptedAccessToken = accessToken ? encrypt(accessToken) : null; const query = ` INSERT INTO site_credentials (user_id, site_name, username, password, api_key, access_token, is_connected) VALUES (?, ?, ?, ?, ?, ?, 1) ON CONFLICT(user_id, site_name) DO UPDATE SET username = excluded.username, password = excluded.password, api_key = excluded.api_key, access_token = excluded.access_token, is_connected = 1, last_updated = CURRENT_TIMESTAMP `; db.run( query, [ req.user.id, siteName, username || null, encryptedPassword, encryptedApiKey, encryptedAccessToken ], (err) => { if (err) { return res.status(500).json({ error: err.message }); } res.json({ message: `Connected to ${validSite.name} successfully`, site: siteName }); } ); } catch (error) { res.status(500).json({ error: 'Failed to save credentials: ' + error.message }); } }); // Remove credentials for a site router.delete('/disconnect/:siteName', authenticateToken, (req, res) => { const { siteName } = req.params; const query = 'DELETE FROM site_credentials WHERE user_id = ? AND site_name = ?'; db.run(query, [req.user.id, siteName], (err) => { if (err) { return res.status(500).json({ error: err.message }); } res.json({ message: `Disconnected from ${siteName}` }); }); }); // Test credentials (attempt to login) router.post('/test-connection/:siteName', authenticateToken, async (req, res) => { const { siteName } = req.params; const query = ` SELECT username, password, api_key, access_token FROM site_credentials WHERE user_id = ? AND site_name = ? `; db.get(query, [req.user.id, siteName], async (err, creds) => { if (err) { return res.status(500).json({ error: err.message }); } if (!creds) { return res.status(404).json({ error: 'No credentials found for this site' }); } try { // Decrypt credentials const decryptedPassword = creds.password ? decrypt(creds.password) : null; const decryptedApiKey = creds.api_key ? decrypt(creds.api_key) : null; const decryptedAccessToken = creds.access_token ? decrypt(creds.access_token) : null; // Test connection based on site let isConnected = false; let message = ''; switch (siteName) { case 'thingiverse': // Test Thingiverse API if (decryptedAccessToken) { const response = await fetch('https://api.thingiverse.com/user', { headers: { 'Authorization': `Bearer ${decryptedAccessToken}` } }); isConnected = response.ok; message = isConnected ? 'Connected to Thingiverse API' : 'Failed to connect to Thingiverse'; } break; case 'printables': // Test Printables login isConnected = true; // Simplified - would need actual login test message = 'Printables credentials saved'; break; case 'makerworld': // Test MakerWorld login isConnected = true; // Simplified - would need actual login test message = 'MakerWorld credentials saved'; break; default: message = 'Site connection test not yet implemented'; } res.json({ isConnected, message }); } catch (error) { res.status(500).json({ error: 'Connection test failed: ' + error.message }); } }); }); export default router;