This commit is contained in:
2026-06-25 18:34:45 -05:00
parent da16e5ff56
commit 552b86c323
12 changed files with 439 additions and 125 deletions

View File

@@ -52,8 +52,11 @@ export function camelVCenterConnection(row) {
id: row.id,
name: row.name,
baseUrl: row.base_url,
username: row.username,
hasPassword: Boolean(row.secret_cipher),
username: row.credential_username || row.username || '',
credentialId: row.credential_id || null,
credentialName: row.credential_name || null,
hasPassword: Boolean(row.credential_id || row.secret_cipher),
hasLegacySecret: Boolean(row.secret_cipher && !row.credential_id),
targetType: row.target_type || 'vcenter',
apiMode: row.api_mode || 'auto',
requestTimeoutMs: row.request_timeout_ms || 20000,

View File

@@ -7,7 +7,8 @@ function normalizeConnection(body, existing = {}) {
const parsed = vcenterConnectionSchema.parse({
...existing,
...body,
password: body.password || ''
password: body.password || '',
credentialId: body.credentialId ?? existing.credentialId ?? existing.credential_id ?? null
});
const targetType = parsed.targetType || 'vcenter';
const apiMode = targetType === 'host'
@@ -18,15 +19,55 @@ function normalizeConnection(body, existing = {}) {
targetType,
apiMode,
baseUrl: String(parsed.baseUrl || '').trim().replace(/\/+$/, ''),
credentialId: parsed.credentialId || null,
username: parsed.username || '',
groupId: parsed.visibility === 'group' ? parsed.groupId || null : null
};
}
export function listVCenterConnections(userId) {
return db.prepare(`
SELECT c.*, g.name AS group_name
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);
@@ -35,41 +76,42 @@ export function listVCenterConnections(userId) {
export function getVCenterConnection(connectionId, userId, includeSecret = false) {
if (!connectionId) return null;
const row = db.prepare(`
SELECT c.*, g.name AS group_name
FROM vcenter_connections c
LEFT JOIN groups g ON g.id = c.group_id
${connectionSelect()}
WHERE c.id = ? AND ${visibleClause('c')}
`).get(connectionId, userId, userId);
if (!row) return null;
return includeSecret ? { ...row, password: decryptSecret(row) } : camelVCenterConnection(row);
return attachSecret(row, includeSecret);
}
export function getVCenterConnectionSystem(connectionId, includeSecret = false) {
if (!connectionId) return null;
const row = db.prepare('SELECT * FROM vcenter_connections WHERE id = ?').get(connectionId);
const row = db.prepare(`${connectionSelect()} WHERE c.id = ?`).get(connectionId);
if (!row) return null;
return includeSecret ? { ...row, password: decryptSecret(row) } : camelVCenterConnection(row);
return attachSecret(row, includeSecret);
}
export function createVCenterConnection(body, userId) {
const payload = normalizeConnection(body);
const encrypted = encryptSecret(payload.password);
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,
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
connectionId,
payload.name,
payload.baseUrl,
payload.username,
credential?.username || payload.username || '',
encrypted.cipher,
encrypted.iv,
encrypted.tag,
payload.credentialId,
payload.targetType,
payload.apiMode,
payload.requestTimeoutMs,
@@ -92,6 +134,7 @@ export function updateVCenterConnection(connectionId, body, userId) {
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,
@@ -100,24 +143,27 @@ export function updateVCenterConnection(connectionId, body, userId) {
visibility: existing.visibility,
groupId: existing.group_id
});
const encrypted = payload.password ? encryptSecret(payload.password) : {
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 = ?,
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,
payload.username,
credential?.username || payload.username || '',
encrypted.cipher,
encrypted.iv,
encrypted.tag,
payload.credentialId,
payload.targetType,
payload.apiMode,
payload.requestTimeoutMs,