pinbox-mongo-proxy — Operations
Service: pinbox-mongo-proxy · Host: bms-4 (54.36.123.110) · Runtime: Node.js 20 (Hono) in
Docker Compose · Ingress: Traefik → https://pinbox-mongo-proxy.bms-4.infra.zintegrowana.online
Source: infra-src/pinbox-mongo-proxy/ · Design:
docs/designs/5408-pinbox-mongo-proxy.md · Issues: #5408, #5400
Purpose
Authenticated proxy that lets et-operational-platform (Vercel, Pro plan, no static egress IP)
read w4_db.regRecords (and edit one record at a time, #5597) from the Pinbox24 rs0 replica set
without exposing port 27017 to Vercel’s rotating serverless IPs. bms-4 is already on the rs0 27017
ufw allowlist and already runs Traefik on 80/443, so the proxy adds no new open port and no new ufw
rule. The et_oper MongoDB credential stays on bms-4; Vercel holds only the shared proxy secret.
Mirror-image sibling of infra-src/redis-bridge.
Endpoints
| Method | Path | Auth | Response |
|---|---|---|---|
GET | /health | none | 200 ok (liveness — never touches Mongo) |
POST | /records | X-Proxy-Key | 200 {records, count} — bounded w4_db.regRecords.find(filter, {projection, sort}).skip().limit() |
POST | /files | X-Proxy-Key | 200 {records, count} — same, bound to w4_db.files (#5572) |
POST | /registries | X-Proxy-Key | 200 {records, count} — same, bound to w4_db.registries (#5572) |
POST | /records/update | X-Proxy-Key | 200 {record} / 404 not_found — w4_db.regRecords.findOneAndUpdate({_id}, {$set}, {returnDocument:"after"}) (#5597) |
The three read POST routes share one request/response contract; each is bound to a single fixed
collection (the caller cannot select one). /files and /registries were added in #5572 to unblock
et-op’s /zarzad/pinbox preview/download/sync. /records/update (#5597) is the one narrow write:
body {filter:{_id:{$oid:...}}, set:{...}}, filter pinned to exactly one _id (never update-many),
set used verbatim as the $set document. et-op whitelists writable fields upstream (against
pinbox_column_defs.is_editable), so the proxy carries no field whitelist of its own.
Errors: 401 unauthorized, 400 invalid_json / invalid_filter / invalid_set (write route) /
forbidden_operator / invalid_oid / invalid_pagination, 404 not_found (write route matched no
record), 413 body_too_large, 502 mongo_query_failed. Full contract in the service
README.
ObjectId values — {"$oid": "..."} request contract (#5480)
regId / officeId and other _id-style fields in w4_db.regRecords are BSON ObjectIds. The
caller must send them in MongoDB Extended-JSON form — {"$oid": "<24-hex>"} — not as a bare hex
string. JSON.stringify(ObjectId) emits a plain hex string, and Mongo will not auto-cast it for
equality, so a bare-string filter matches zero documents (200 {records:[], count:0}) — a silent
failure worse than an error. The proxy reconstructs {"$oid":...} wrappers into real ObjectIds
recursively across filter/projection/sort (and, on /records/update, across filter and set)
including inside $in arrays before querying; a malformed $oid (not a 24-char hex string) is rejected
400 invalid_oid. The forbidden-operator guard runs on the raw filter (and write-route set) before
reconstruction and is unaffected.
Configuration
Env is injected from bms-4/.env (populated by secrets-sync.yml). See bms-4/docker-compose.yml
service pinbox-mongo-proxy.
| Env | Source | Notes |
|---|---|---|
PINBOX24_MONGODB_URI | secrets/et-operational-platform.env.sops | Must be distributed to bms-4/.env — currently synced to Vercel only. secret-manager action. |
PINBOX_PROXY_SHARED_SECRET | new SOPS key | Created + distributed (bms-4 and Vercel) by secret-manager. |
MONGO_DB_NAME / MONGO_COLLECTION | compose (w4_db / regRecords) | Fixed; caller cannot change. |
MAX_LIMIT | compose (1000) | Hard result cap. |
The container fails closed — a missing PINBOX24_MONGODB_URI or PINBOX_PROXY_SHARED_SECRET
exits on boot. Both keys must be in bms-4/.env before docker compose up.
Deploy / rebuild (sys-admin on bms-4)
Prerequisite: secret-manager has placed both env keys in
bms-4/.env.
cd /opt/p24-infra/bms-4
docker compose build pinbox-mongo-proxy
docker compose up -d pinbox-mongo-proxy
docker compose logs --tail=20 pinbox-mongo-proxy # expect "connected to w4_db [regRecords, files, registries]"Verify
# 1. Health (public) — expect: ok, with a valid Let's Encrypt cert
curl https://pinbox-mongo-proxy.bms-4.infra.zintegrowana.online/health
# 2. Auth gate — expect: 401
curl -s -o /dev/null -w '%{http_code}\n' -X POST \
https://pinbox-mongo-proxy.bms-4.infra.zintegrowana.online/records -d '{}'
# 3. Injection guard — expect: 400 forbidden_operator (key sourced silently from bms-4/.env)
KEY=$(grep '^PINBOX_PROXY_SHARED_SECRET=' /opt/p24-infra/bms-4/.env | cut -d= -f2-)
curl -s -X POST -H "X-Proxy-Key: $KEY" -H 'Content-Type: application/json' \
https://pinbox-mongo-proxy.bms-4.infra.zintegrowana.online/records \
-d '{"filter":{"$where":"1"}}' ; unset KEY
# 4. Authed read — expect: 200 {records, count}
# 5. New routes reachable (#5572) — expect: 200 for each (key sourced silently from bms-4/.env)
KEY=$(grep '^PINBOX_PROXY_SHARED_SECRET=' /opt/p24-infra/bms-4/.env | cut -d= -f2-)
for route in files registries; do
echo -n "$route: "
curl -s -o /dev/null -w '%{http_code}\n' -X POST -H "X-Proxy-Key: $KEY" \
-H 'Content-Type: application/json' \
"https://pinbox-mongo-proxy.bms-4.infra.zintegrowana.online/$route" -d '{"limit":1}'
done ; unset KEY
# 6. Write route live + guards (#5597) — NON-DESTRUCTIVE checks only (key sourced silently):
KEY=$(grep '^PINBOX_PROXY_SHARED_SECRET=' /opt/p24-infra/bms-4/.env | cut -d= -f2-)
# 6a. Filter-shape guard — a non-_id filter is rejected before any Mongo call. Expect: 400 invalid_filter
curl -s -X POST -H "X-Proxy-Key: $KEY" -H 'Content-Type: application/json' \
https://pinbox-mongo-proxy.bms-4.infra.zintegrowana.online/records/update \
-d '{"filter":{"regId":{"$oid":"000000000000000000000000"}},"set":{"x":1}}'
# 6b. Route live — a well-formed update against a non-existent _id matches nothing → NO write occurs.
# Expect: 404 {"error":"not_found"} (all-zeros _id will never exist in regRecords)
curl -s -X POST -H "X-Proxy-Key: $KEY" -H 'Content-Type: application/json' \
https://pinbox-mongo-proxy.bms-4.infra.zintegrowana.online/records/update \
-d '{"filter":{"_id":{"$oid":"000000000000000000000000"}},"set":{"__probe":1}}'
unset KEYMonitoring
Liveness is served by GET /health. A blackbox HTTP probe of /health can be added to the monitoring
stack in a follow-up (not required for launch). Container logs never contain the URI, password, or
proxy-key value — only Mongo error messages.
Compliance — dev_r_services registration
Register the new service in Supabase dev_r_services:
| Column | Value |
|---|---|
name | pinbox-mongo-proxy |
category | service (bms-4 Docker) |
host | bms-4 |
compliance_workbook | yes |
workbook_url | docs/pinbox-mongo-proxy-operations.md |
dev_r_servicesis a VIEW in this project (seeCLAUDE.md§Do NOT). The worker attempts a best-effort registration against the underlying relation; if the write is rejected, this is a secret-manager / admin follow-up — the row must exist before the service is treated as compliant.
Ownership
- Code / config: dev-coder (this repo).
- Secrets: secret-manager (
PINBOX_PROXY_SHARED_SECRETcreation + both keys →bms-4/.env). - Deploy / server ops: sys-admin / infra-task on bms-4.
- et-op route swap: cross-repo follow-up (see #5400) — not owned here.