zappier-customer-service/src/nats-billing.js

44 lines
1.5 KiB
JavaScript

export const SUBJECTS = {
STATEMENT_GET: 'verae.billing.statement.get',
BALANCE_ADJUST: 'verae.billing.balance.adjust',
AUTHZ_CHECK: 'verae.access.authz.check',
};
const PLANE = 'staff';
async function authz(subject, payload) {
const http = process.env.AUTHZ_URL;
if (!http && !process.env.NATS_URL) return { allow: true, subject };
if (http) {
const r = await fetch(`${http.replace(/\/$/, '')}/check`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ plane: PLANE, subject, kind: payload?.kind, payload, principal: payload?.agent }),
});
return r.json();
}
return { allow: true, subject };
}
export async function billingRequest(subject, payload) {
const decision = await authz(subject, payload);
if (decision && decision.allow === false) {
const err = new Error(decision.reason || 'denied');
err.status = 403;
err.decision = decision;
throw err;
}
const url = process.env.NATS_URL;
if (!url) return null;
try {
const { connect, StringCodec } = await import('nats');
const nc = await connect({ servers: url.split(','), name: 'zappier-customer-service' });
const sc = StringCodec();
const m = await nc.request(subject, sc.encode(JSON.stringify({ ...payload, plane: PLANE })), { timeout: 2000 });
const out = JSON.parse(sc.decode(m.data) || '{}');
await nc.close();
return out;
} catch (err) {
if (err.status === 403) throw err;
return null;
}
}