← back to All Designerwallcoverings
scripts/adapters/creds.js
67 lines
// scripts/adapters/creds.js — resolve a login-gated vendor's trade creds WITHOUT copying them.
//
// SECURITY MODEL (see docs/live-stock-creds-in-place.md):
// Priority 1 — the dw-price-stock credential VAULT, referenced BY PATH at runtime. The vault file
// is read, parsed with dotenv.parse into process memory, used for ONE login, and
// discarded. Values are NEVER written back, logged, echoed, or copied into this
// repo's .env or dw_unified. DW_PRICE_STOCK_ENV is a PATH POINTER, not a secret.
// Priority 2 — dw_unified.vendor_registry (the existing wallquest/romo DB-cred pattern), used only
// when the vault has no entry for the vendor.
//
// The returned object carries the raw user/pass so the adapter can log in; callers MUST NOT log it.
const fs = require('fs');
// Path pointer to Parker's vault. Overridable via env; defaults to the on-host location.
const VAULT_PATH = process.env.DW_PRICE_STOCK_ENV || '/root/DW-Agents/dw-price-stock/.env';
let _vault = null; // parsed once per process
function vault() {
if (_vault) return _vault;
_vault = {};
try {
const txt = fs.readFileSync(VAULT_PATH, 'utf8');
// dotenv.parse — reference the vault in place; nothing is ever written back.
_vault = require('dotenv').parse(txt);
} catch { _vault = {}; }
return _vault;
}
// True iff the vault holds a usable cred pair for this ENV prefix — WITHOUT surfacing any value.
// (build-coverage can call this to decide enable/dim without ever reading a secret.)
function vaultHas(prefix) {
const v = vault();
return !!(v[`${prefix}_USERNAME`] && v[`${prefix}_PASSWORD`]);
}
// {user,pass,loginUrl,source} from the vault, or null. Never logs values.
function fromVault(prefix) {
const v = vault();
const user = v[`${prefix}_USERNAME`];
const pass = v[`${prefix}_PASSWORD`];
const loginUrl = v[`${prefix}_LOGIN_URL`] || null;
if (user && pass) return { user, pass, loginUrl, source: 'vault' };
return null;
}
// Fallback: dw_unified.vendor_registry (same columns wallquest/romo adapters read).
async function fromRegistry(pool, vendorLike) {
if (!pool || !vendorLike) return null;
try {
const { rows } = await pool.query(
`SELECT trade_username, trade_password, login_url FROM vendor_registry
WHERE vendor_name ILIKE $1 OR vendor_code ILIKE $1 LIMIT 1`, [vendorLike]);
const r = rows[0];
if (r && r.trade_password) {
return { user: r.trade_username || null, pass: r.trade_password, loginUrl: r.login_url || null, source: 'registry' };
}
} catch { /* optional */ }
return null;
}
// Vault-first, registry-fallback. { prefix, pool?, vendorLike? } → {user,pass,loginUrl,source}|null.
async function resolve({ prefix, pool, vendorLike }) {
return (prefix && fromVault(prefix)) || (await fromRegistry(pool, vendorLike)) || null;
}
module.exports = { resolve, fromVault, vaultHas, VAULT_PATH };