← 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 };