/**
* Vault write utilities for backend operations
* Uses native fetch (Node 18+), reads token the same way as @commtool/vault-secrets
*/
import fs from 'fs';
function getVaultTokenAndAddr() {
const vaultAddr = process.env.VAULT_ADDR || 'https://vault.commtool.org';
let token = process.env.VAULT_TOKEN;
if (!token) {
const tokenPath = '/vault-token';
if (fs.existsSync(tokenPath)) {
token = fs.readFileSync(tokenPath, 'utf8').trim();
}
}
if (!token) {
throw new Error(
'No Vault token available for write operations. ' +
'Set VAULT_TOKEN or mount token at /vault-token.'
);
}
return { token, vaultAddr };
}
/**
* Write (or update) a secret in Vault KV v2.
* Merges the new data on top of any existing secret (patch semantics via POST).
*
* @param {string} secretPath - Vault KV v2 path including the "data" segment,
* e.g. "orgas/data/UUID-xxxx/domains"
* @param {Record<string, unknown>} data - Key/value data to store
* @returns {Promise<void>}
*/
export async function saveSecretsToVault(secretPath, data) {
const { token, vaultAddr } = getVaultTokenAndAddr();
const prefix = secretPath.startsWith('/') ? '' : '/';
const apiPath = `${vaultAddr}/v1${prefix}${secretPath}`;
const response = await fetch(apiPath, {
method: 'POST',
headers: {
'X-Vault-Token': token,
'Content-Type': 'application/json',
},
body: JSON.stringify({ data }),
});
if (!response.ok) {
const errorText = await response.text().catch(() => '');
throw new Error(`Vault write failed (${response.status}) at ${apiPath}: ${errorText}`);
}
}