Initial import of zappier-identity from zapier monorepo

This commit is contained in:
George Lambert 2026-09-11 18:38:50 -04:00
commit 3f136070b6
8 changed files with 204 additions and 0 deletions

15
src/ids.js Normal file
View file

@ -0,0 +1,15 @@
import { createHash } from 'node:crypto';
export function stableVeraeUserId(username) {
const n = String(username || '')
.trim()
.toLowerCase();
return `vu_${createHash('sha256').update(n).digest('hex').slice(0, 16)}`;
}
export function bindEmail(email) {
const veraeUsername = String(email || '')
.trim()
.toLowerCase();
return { veraeUserId: stableVeraeUserId(veraeUsername), veraeUsername, bound: true };
}

89
src/server.js Normal file
View file

@ -0,0 +1,89 @@
#!/usr/bin/env node
/** Mailbox: bind/lookup veraeUserId. JWT never stored here. */
import http from 'node:http';
import { bindEmail } from './ids.js';
import { load, put } from './store.js';
import { SUBJECTS } from './subjects.js';
const PORT = Number(process.env.PORT || 3026);
const BIND = process.env.FLEET_HEALTH_BIND || '0.0.0.0';
const db = load();
function handleBind(p) {
const email = p.email || p.veraeUsername;
const bound = bindEmail(email);
const customerId = p.customerId || bound.veraeUsername;
put(db, { customerId, ...bound });
return { ...bound, customerId };
}
function handleLookup(p) {
if (p.customerId && db.byCustomer[p.customerId]) return db.byCustomer[p.customerId];
if (p.veraeUserId && db.byVerae[p.veraeUserId]) return db.byVerae[p.veraeUserId];
if (p.email) return bindEmail(p.email);
return { error: 'not found' };
}
async function startNats() {
const url = process.env.NATS_URL;
if (!url) return;
const { connect, StringCodec } = await import('nats');
const nc = await connect({ servers: url.split(','), name: 'zappier-identity' });
const sc = StringCodec();
for (const subj of [SUBJECTS.BIND, SUBJECTS.LOOKUP]) {
(async () => {
for await (const m of nc.subscribe(subj, { queue: SUBJECTS.QUEUE })) {
let p = {};
try {
p = JSON.parse(sc.decode(m.data) || '{}');
} catch {
p = {};
}
const out = subj === SUBJECTS.BIND ? handleBind(p) : handleLookup(p);
if (m.reply) m.respond(sc.encode(JSON.stringify(out)));
}
})();
}
}
function readBody(req) {
return new Promise((resolve) => {
const chunks = [];
req.on('data', (c) => chunks.push(c));
req.on('end', () => {
try {
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}'));
} catch {
resolve({});
}
});
});
}
const server = http.createServer(async (req, res) => {
const url = new URL(req.url || '/', `http://127.0.0.1:${PORT}`);
const json = (code, obj) => {
res.writeHead(code, { 'content-type': 'application/json' });
res.end(JSON.stringify(obj));
};
try {
if (req.method === 'GET' && url.pathname === '/health') {
return json(200, { ok: true, role: 'zappier-identity', subjects: SUBJECTS });
}
if (req.method === 'POST' && url.pathname === '/bind') {
return json(200, handleBind(await readBody(req)));
}
const who = url.pathname.match(/^\/lookup\/([^/]+)$/);
if (req.method === 'GET' && who) {
return json(200, handleLookup({ customerId: who[1] }));
}
json(404, { error: 'not found' });
} catch (err) {
json(500, { error: err.message });
}
});
server.listen(PORT, BIND, () => {
process.stdout.write(`zappier-identity http://${BIND}:${PORT}/\n`);
});
startNats().catch((err) => process.stderr.write(`nats optional: ${err.message}\n`));

30
src/store.js Normal file
View file

@ -0,0 +1,30 @@
import fs from 'node:fs';
import path from 'node:path';
const file = () =>
process.env.IDENTITY_PATH ||
path.join(process.env.FLEET_STATE_DIR || process.cwd(), 'data', 'identity.json');
export function load() {
const p = file();
if (!fs.existsSync(p)) return { byCustomer: {}, byVerae: {} };
try {
return { byCustomer: {}, byVerae: {}, ...JSON.parse(fs.readFileSync(p, 'utf8')) };
} catch {
return { byCustomer: {}, byVerae: {} };
}
}
export function save(db) {
const p = file();
fs.mkdirSync(path.dirname(p), { recursive: true });
fs.writeFileSync(p, JSON.stringify(db, null, 2));
}
export function put(db, { customerId, veraeUserId, veraeUsername }) {
if (!customerId || !veraeUserId) return db;
db.byCustomer[customerId] = { veraeUserId, veraeUsername };
db.byVerae[veraeUserId] = { customerId, veraeUsername };
save(db);
return db;
}

5
src/subjects.js Normal file
View file

@ -0,0 +1,5 @@
export const SUBJECTS = {
BIND: 'verae.identity.bind',
LOOKUP: 'verae.identity.lookup',
QUEUE: 'identity',
};