~/bpabatao
← back to index
~/case/core-api

Multi-Tenant Core API

The middleware every utility-customer portal in the fleet stands on - two generations of it, and the migration from one to the other.

Role
Primary author of both generations, architecture and security owner
Period
2023 - present
Ownership
58% of v1 · 78% of v2
Stack
Fastify 5 · TypeScript (strict) · Zod · MongoDB · AWS SDK v3 · ECS Fargate · Vitest
deltamvunepdelcoalexrenewcarmelarubacore apiauthz on every routeper-tenant configfixed response shapeoauth 2.0oracle ccs

Problem

A fleet of utility companies each needed a customer portal talking to Oracle Utilities CCS. The v1 server I inherited when I joined in 2023 - an Express codebase that predates me, 58% mine by commit today - had authorization gaps and no clean way to vary behavior per tenant. Every new client meant forked code and re-audited security.

The v2 rebuild, started in early 2026, had one mandate: a single API where a new tenant is configuration, not code, and where authorization failure is impossible to ship by accident. Two tenants run production on it today, both launched on it; the rest of the fleet still runs on v1 and moves one tenant at a time.

Constraints

Utility customers pay bills through this thing - it has to be up and it has to be right. Oracle CCS is the system of record, reached over OAuth 2.0 with per-tenant credentials. The team is small, so the architecture had to make the secure path the easy path - and the migration cannot pause billing, so both generations run side by side, the portals carry interceptors for either backend, and a tenant moves only when its parity checks pass.

Architecture

Fastify 5 on Node 22, ESM-only, TypeScript strict with no `any`. Zod validates every input at the boundary; responses use one fixed envelope so clients never parse ad-hoc shapes.

Tenant behavior is composed from a base configuration plus per-tenant overrides, assembled by strategy factories - the request path never branches on tenant name. Authorization is enforced as a route-level gate: every endpoint proves the caller owns the account it touches before any data access, which is what kills IDOR as a class.

Accountability does not stop at the customer. Admin reads and impersonated sessions are audited as well as writes, attributed to the acting admin, with field-level capture of what a write actually changed and deep redaction so tokens and provider passwords never reach the log. Admin writes are kept for a year, admin reads for ninety days.

Where the billing system of record and the payment provider disagree, a detection-only reconciler surfaces the drift instead of silently writing to either, and the account-linking path guards against duplicate registrations and signup conflicts. Small guards pay: enforcing the ten-digit account number on every sign-in form removed about 215 CCS errors a day that partial or oversized numbers had been generating.

The integration runs in both directions. Where the fix belongs on the CCS side, I write the IWS change specification - request and response examples, tenant scope, blast radius - and the vendor team implements it; the portal side lands in step.

An 80% coverage gate and OWASP checks run in CI; Bitbucket Pipelines builds to ECR and deploys to ECS Fargate behind a shared ALB.

Outcome

← all projects