/** * Bridge to `git credential` so app passwords can live in the user's * configured credential store under protocol=atproto. */ import { spawnSync } from 'node:child_process'; /** * @typedef {Object} Credential * @property {string} [username] * @property {string} [password] */ /** * @param {'fill'|'approve'|'reject'} action * @param {Record} fields * @returns {Credential} */ function credential(action, fields) { const input = `${Object.entries(fields) .map(([k, v]) => `${k}=${v}`) .join('\n')}\n\n`; const result = spawnSync('git', ['credential', action], { input, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], }); if (result.status !== 0) return {}; /** @type {Credential} */ const parsed = {}; for (const line of (result.stdout ?? '').split('\n')) { const eq = line.indexOf('='); if (eq === -1) continue; const key = line.slice(0, eq); if (key === 'username') parsed.username = line.slice(eq + 1); if (key === 'password') parsed.password = line.slice(eq + 1); } return parsed; } /** * @param {string} host * @returns {Credential} */ export function credentialFill(host) { return credential('fill', { protocol: 'atproto', host }); } /** * @param {string} host * @param {Credential} cred */ export function credentialApprove(host, cred) { if (!cred.password) return; credential('approve', { protocol: 'atproto', host, username: cred.username ?? '', password: cred.password, }); } /** * @param {string} host * @param {Credential} cred */ export function credentialReject(host, cred) { if (!cred.password) return; credential('reject', { protocol: 'atproto', host, username: cred.username ?? '', password: cred.password, }); }