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!