import { db, id, now, visibleClause } from '../db.js'; import { vcenterConnectionSchema } from '../forms/schemas.js'; import { decryptSecret, encryptSecret } from '../services/cryptoStore.js'; import { camelVCenterConnection } from './mappers.js'; function normalizeConnection(body, existing = {}) { const parsed = vcenterConnectionSchema.parse({ ...existing, ...body, password: body.password || '', credentialId: body.credentialId ?? existing.credentialId ?? existing.credential_id ?? null }); const targetType = parsed.targetType || 'vcenter'; const apiMode = targetType === 'host' ? 'web-services' : (parsed.apiMode === 'web-services' ? 'auto' : parsed.apiMode); return { ...parsed, targetType, apiMode, baseUrl: String(parsed.baseUrl || '').trim().replace(/\/+$/, ''), credentialId: parsed.credentialId || null, username: parsed.username || '', groupId: parsed.visibility === 'group' ? parsed.groupId || null : null }; } function connectionSelect() { return ` SELECT c.*, g.name AS group_name, cred.name AS credential_name, cred.username AS credential_username, cred.kind AS credential_kind, cred.secret_cipher AS credential_secret_cipher, cred.secret_iv AS credential_secret_iv, cred.secret_tag AS credential_secret_tag FROM vcenter_connections c LEFT JOIN groups g ON g.id = c.group_id LEFT JOIN credentials cred ON cred.id = c.credential_id `; } function getVisibleCredential(credentialId, userId) { if (!credentialId) return null; return db.prepare(`SELECT * FROM credentials WHERE id = ? AND ${visibleClause()}`).get(credentialId, userId, userId); } function assertUsableCredential(credentialId, userId) { const credential = getVisibleCredential(credentialId, userId); if (!credential) throw new Error('Choose a visible Credential Vault entry for this VMware connection.'); if (credential.kind !== 'username_password') throw new Error('VMware connections require a username/password Credential Vault entry.'); if (!credential.username) throw new Error('The selected Credential Vault entry must include a username.'); return credential; } function attachSecret(row, includeSecret) { if (!includeSecret) return camelVCenterConnection(row); if (row.credential_id) { return { ...row, username: row.credential_username || '', password: decryptSecret({ secret_cipher: row.credential_secret_cipher, secret_iv: row.credential_secret_iv, secret_tag: row.credential_secret_tag }) }; } return { ...row, password: decryptSecret(row) }; } export function listVCenterConnections(userId) { return db.prepare(` ${connectionSelect()} WHERE ${visibleClause('c')} ORDER BY c.name `).all(userId, userId).map(camelVCenterConnection); } export function getVCenterConnection(connectionId, userId, includeSecret = false) { if (!connectionId) return null; const row = db.prepare(` ${connectionSelect()} WHERE c.id = ? AND ${visibleClause('c')} `).get(connectionId, userId, userId); if (!row) return null; return attachSecret(row, includeSecret); } export function getVCenterConnectionSystem(connectionId, includeSecret = false) { if (!connectionId) return null; const row = db.prepare(`${connectionSelect()} WHERE c.id = ?`).get(connectionId); if (!row) return null; return attachSecret(row, includeSecret); } export function createVCenterConnection(body, userId) { const payload = normalizeConnection(body); const credential = payload.credentialId ? assertUsableCredential(payload.credentialId, userId) : null; if (!credential && !payload.password) throw new Error('Choose a Credential Vault entry for this VMware connection.'); const encrypted = credential ? encryptSecret('') : encryptSecret(payload.password); const connectionId = id('vc'); db.prepare(` INSERT INTO vcenter_connections ( id, name, base_url, username, secret_cipher, secret_iv, secret_tag, credential_id, target_type, api_mode, request_timeout_ms, allow_untrusted_tls, enabled, visibility, owner_user_id, group_id, created_by, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `).run( connectionId, payload.name, payload.baseUrl, credential?.username || payload.username || '', encrypted.cipher, encrypted.iv, encrypted.tag, payload.credentialId, payload.targetType, payload.apiMode, payload.requestTimeoutMs, payload.allowUntrustedTls ? 1 : 0, payload.enabled ? 1 : 0, payload.visibility, userId, payload.groupId, userId, now(), now() ); return getVCenterConnection(connectionId, userId); } export function updateVCenterConnection(connectionId, body, userId) { const existing = getVCenterConnection(connectionId, userId, true); if (!existing) return null; const payload = normalizeConnection(body, { name: existing.name, baseUrl: existing.base_url, username: existing.username, credentialId: existing.credential_id, targetType: existing.target_type || 'vcenter', apiMode: existing.api_mode, requestTimeoutMs: existing.request_timeout_ms, allowUntrustedTls: Boolean(existing.allow_untrusted_tls), enabled: Boolean(existing.enabled), visibility: existing.visibility, groupId: existing.group_id }); const credential = payload.credentialId ? assertUsableCredential(payload.credentialId, userId) : null; if (!credential && !payload.password && !existing.secret_cipher) throw new Error('Choose a Credential Vault entry for this VMware connection.'); const encrypted = credential ? encryptSecret('') : payload.password ? encryptSecret(payload.password) : { cipher: existing.secret_cipher, iv: existing.secret_iv, tag: existing.secret_tag }; db.prepare(` UPDATE vcenter_connections SET name = ?, base_url = ?, username = ?, secret_cipher = ?, secret_iv = ?, secret_tag = ?, credential_id = ?, target_type = ?, api_mode = ?, request_timeout_ms = ?, allow_untrusted_tls = ?, enabled = ?, visibility = ?, group_id = ?, updated_at = ? WHERE id = ? `).run( payload.name, payload.baseUrl, credential?.username || payload.username || '', encrypted.cipher, encrypted.iv, encrypted.tag, payload.credentialId, payload.targetType, payload.apiMode, payload.requestTimeoutMs, payload.allowUntrustedTls ? 1 : 0, payload.enabled ? 1 : 0, payload.visibility, payload.groupId, now(), connectionId ); return getVCenterConnection(connectionId, userId); } export function deleteVCenterConnection(connectionId, userId) { db.prepare(`DELETE FROM vcenter_connections WHERE id = ? AND ${visibleClause()}`).run(connectionId, userId, userId); } export function recordVCenterConnectionTest(connectionId, status, message) { db.prepare(` UPDATE vcenter_connections SET last_test_status = ?, last_test_message = ?, last_test_at = ?, updated_at = ? WHERE id = ? `).run(status, message || '', now(), now(), connectionId); }