← back to Secrets Manager
update-viewer/server.mjs
76 lines
#!/usr/bin/env node
// Credentials & Tokens Update Tracker — local web viewer. Zero deps.
// node server.mjs → http://localhost:9778
// Reads update-items.json; lets Steve toggle status (persisted). No secret VALUES.
import http from 'node:http';
import fs from 'node:fs';
import path from 'node:path';
const DIR = path.dirname(new URL(import.meta.url).pathname);
const ROOT = path.dirname(DIR);
const DATA = path.join(DIR, 'update-items.json');
const REGISTRY = path.join(ROOT, 'registry.json');
const ROUTES = path.join(ROOT, 'routes.json');
const PORT = process.env.PORT || 9778;
const send = (res, code, type, body) => { res.writeHead(code, { 'Content-Type': type, 'Cache-Control': 'no-store' }); res.end(body); };
function safeText(value) {
return String(value || '')
.replace(/sk_(?:live|test)_/gi, 'sk_[redacted]_')
.replace(/xox[baprs]-/gi, 'slack-[redacted]-')
.replace(/xapp-/gi, 'slack-app-[redacted]-')
.replace(/ghp_/gi, 'github-[redacted]_');
}
function catalog() {
const registry = JSON.parse(fs.readFileSync(REGISTRY, 'utf8'));
const routes = JSON.parse(fs.readFileSync(ROUTES, 'utf8'));
const saved = registry.secrets || {};
const configuredRoutes = { ...(routes.services || {}) };
for (const [key, value] of Object.entries(routes)) {
if (/^[A-Z][A-Z0-9_]+$/.test(key) && value && typeof value === 'object') configuredRoutes[key] = value;
}
const names = [...new Set([...Object.keys(configuredRoutes), ...Object.keys(saved)])].sort();
return names.map(name => {
const record = saved[name] || {};
const route = configuredRoutes[name] || {};
const destinations = Array.isArray(route.destinations) ? route.destinations
: Array.isArray(route.files) ? route.files
: Array.isArray(record.written_to) ? record.written_to : [];
return {
name,
label: safeText(route.label || record.label || name.replaceAll('_', ' ').toLowerCase()),
configured: Boolean(saved[name]),
digest: record.digest || null,
validated: record.validated === true,
verifyStatus: record.verify_status ?? null,
lastUpdated: record.last_updated || null,
destinationCount: destinations.length,
hasVerifier: Boolean(route.verify),
mintUrl: route.mintUrl || route.mint_url || null
};
});
}
http.createServer((req, res) => {
const u = new URL(req.url, 'http://localhost');
if (u.pathname === '/') return send(res, 200, 'text/html; charset=utf-8', fs.readFileSync(path.join(DIR, 'index.html')));
if (u.pathname === '/favicon.ico') { res.writeHead(204, { 'Cache-Control': 'public, max-age=86400' }); return res.end(); }
if (u.pathname === '/healthz') return send(res, 200, 'application/json', JSON.stringify({ ok: true, service: 'credentials-center' }));
if (u.pathname === '/api/items') return send(res, 200, 'application/json', fs.readFileSync(DATA));
if (u.pathname === '/api/catalog') return send(res, 200, 'application/json', JSON.stringify({ items: catalog() }));
if (u.pathname === '/api/toggle' && req.method === 'POST') {
let b = ''; req.on('data', c => b += c); req.on('end', () => {
try {
const { id, status } = JSON.parse(b);
const d = JSON.parse(fs.readFileSync(DATA, 'utf8'));
const it = d.items.find(x => x.id === id);
if (it) { it.status = status; fs.writeFileSync(DATA, JSON.stringify(d, null, 2) + '\n'); }
send(res, 200, 'application/json', JSON.stringify({ ok: !!it }));
} catch (e) { send(res, 400, 'application/json', JSON.stringify({ ok: false, error: e.message })); }
});
return;
}
send(res, 404, 'text/plain', 'not found');
}).listen(PORT, '127.0.0.1', () => console.log(`Credentials Update Tracker → http://127.0.0.1:${PORT}`));