212 lines
6.4 KiB
JavaScript
212 lines
6.4 KiB
JavaScript
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;
|