Heads‑up: the public demo link is currently offline. We hit deployment snags right before the deadline and couldn’t finish troubleshooting in time. The repo + write‑up still show the full implementation path, side‑car PDP pattern, and Permit.io policy setup.


TL;DR

 🔗 GitHub https://github.com/enkaypeter/crm-api
 📚 Swagger Docs GET /docs (local)
 👤 Test headers x-user-id: 1 (admin) • 2 (sales_rep) • 3 (support) • 4 (customer) • 999 (ai_agent)

1 — Why externalize authorization?

Traditional vs. Permit.io

Criteria Hard‑coded checks Externalized ( Permit.io )
Change a rule Re‑deploy code Update policy in UI/API
Auditability Grep code 🤕 Built‑in graph + logs
Granularity Role only Role + resource + context
Multi‑tenant Custom logic Native support

2 — Architecture at a glance

graph TD
  subgraph Cloud Run Service
    API[Express API] -->|localhost:7000| PDP[Permit PDP side‑car]
  end
  PDP --> PermitCloud[(Permit.io Cloud API)]
  API --> SwaggerUI[[Swagger UI /docs]]
  • Side‑car pattern keeps latency < 5 ms.
  • Secret Manager injects API keys at runtime.
  • Cloud Build patches cloudrun.yaml, deploys both containers, then opens the service to unauthenticated testers.

3 — Roles, resources & rules

Roles: admin, sales_rep, support, customer, ai_agent

Resource types: account, transaction, analytics, credit_limit

# Sample ABAC rule
- role: sales_rep
  resource: account
  action: edit
  when:
    match: account.assigned_to == user.id

4 — API surface

Method Endpoint Authorized roles
GET /accounts/:id customer (own), support, sales_rep (assigned), admin
PUT /accounts/:id sales_rep (assigned), admin
GET /transactions/:ref customer (own), support, sales_rep, admin
POST /transactions sales_rep, admin
GET /admin/analytics admin
POST /ai/recommend-credit-increase ai_agent

Responses follow an Apigee‑style wrapper:

{
  "code": 200,
  "status": "success",
  "message": "Account retrieved",
  "data": { ... }
}

5 — Key implementation snippets

5.1 Middleware authorization

import axios from "axios";
const pdp = axios.create({ baseURL: process.env.PERMIT_PDP_URL });

export default (action, resourceFn) => async (req, res, next) => {
  const [type, key] = resourceFn(req).split(":");
  const { data } = await pdp.post("/allowed", {
    user: { key: req.user.id },
    action,
    resource: { type, key },
    tenant: "default"
  });
  return data.allow ? next()
    : res.status(403).json({ code: 403, status: "error", message: "Access denied" });
};

5.2 Auto‑seeding Permit on startup

import seedPermit from "./scripts/syncPermitData.js";

(async () => {
  await seedPermit();               // idempotent bulk sync
  const PORT = process.env.PORT || 8080;
  app.listen(PORT, () =>
    console.log(`API running on ${PORT}`));
})();

6 — CI/CD & side‑car deployment

# cloudrun.yaml  (multi‑container)
containers:
- name: api
  image: gcr.io/PROJECT_ID/fincrm-api
  ports: [{ containerPort: 8080 }]
  env:
    - name: PERMIT_PDP_URL  value: http://pdp:7000
    - name: PERMIT_API_KEY  value: ${_PERMIT_API_KEY}
    - name: PERMIT_PROJECT_ID value: ${_PERMIT_PROJECT_ID}
    - name: PERMIT_ENV_ID value: ${_PERMIT_ENV_ID}

- name: pdp
  image: permitio/pdp-v2:latest
  env:
    - name: PDP_API_KEY value: ${_PERMIT_API_KEY}

.cloudbuild.yaml patches the image URL, deploys, then runs:

gcloud run services add-iam-policy-binding fincrm-api \
  --region=europe-west1 \
  --member=allUsers \
  --role=roles/run.invoker

Secrets (_PERMIT_*) are mapped from Secret Manager in the Cloud Build trigger, never stored in git.


7 — AI credit‑limit flow

1. ai_agent POSTs to /ai/recommend-credit-increase

2. Middleware → authorize("recommend_credit_increase", accountCtx)

3. PDP checks transaction‑based rule (avg deposit ≥ £500)

4. If allowed, GPT‑4o returns a JSON recommendation

5. sales_rep (assigned) can approve via /credit-limit/:id/approve


8 — What works & what’s missing

✅ Local dev — PDP + API run with Docker compose and all tests pass

✅ CI/CD — Cloud Build builds & deploys multi‑container Cloud Run

✅ Externalized policies in Permit dashboard

❌ Live demo — still fails auth header validation on Cloud Run; we suspect residual newline chars in secrets. Time ran out to re‑seed secrets & redeploy.


9 — Lessons learned

  • Side‑car PDP keeps secrets off the public internet yet avoids round‑trip latency.
  • Secret Manager + Cloud Build trigger variables let us ship CI/CD with zero secrets in git.
  • Treating the AI agent as just another role simplifies governance — Permit.io enforces identical rules for humans and bots.

Questions / feedback?

Open an issue in the repo.

Built for the Permit.io Authorization Challenge — thanks for reading!