Audit: Namespace /api/content/*
Status: Internal inventory after the refactor of the Session/Center lists (/api/news, /api/events, …).
Objective: Transparency regarding which routes are publicly semi-open and where without centerId all records may potentially be visible.
Legend Risk: “low” = expected behavior or session protected; “medium/high” = data leakage possible without authentication or without Center parameter.
GET Lists (classic “Content API” style, CORS *)
| Route | Session | Center if no centerId | Main Caller | Risk & Note |
|---|---|---|---|---|
GET /api/content/events | no | all Events in the system | ree-carree-event-banner-section-config, content-selector-dialog (with filter publicOnWebsite + usually centerId), demo script | high: unauthenticated; without ?centerId= complete table. With centerId only this center. For website/dialog typically always centerId. |
GET /api/content/news | no | all News | Ree-Carree, Goldbeck-Hero (centerId+published), content-selector-dialog, demo script | high as above. published=true reduces content-wise, not tenant-wise. |
GET /api/content/offers | no | all Offers | Ree-Carree, content-selector-dialog, demo script | high as above. |
GET /api/content/jobs | no | all Jobs | content-selector-dialog, demo script | high as above. |
GET /api/content/services | no | all Services | content-selector-dialog, demo script | high as above. |
GET /api/content/shop-chains | no | all ShopChains including locations | Demo script (create-cms-demo-data.js) | very high for production URL: global chain list without session. |
Design Note: These endpoints are historically embedding/widget-friendly (CORS, no login). Tenant separation is only done via ?centerId= — those omitting the parameter see system-wide.
Dashboard-bound (Session + Center Logic)
| Route | Session | Center/Scope Logic | Main Caller | Risk & Note |
|---|---|---|---|---|
GET /api/content/metrics | yes (without session: 401, unless otherwise documented in future) | mergeSessionCenterScopeIntoListParams + pickCenterWhereFragment | /dashboard/content (cookie), MCP optional /api/content/metrics | low after fix. |
GET /api/content/recent | yes (without session: 401) | as above | /dashboard/content | low after fix. |
GET /api/content/planner | requireSession | getCenterIdsForUser + query centerId / organizationId | /dashboard/content/planner | low |
GET /api/content/drafts | requireSession | getCenterIdsForUser | PendingDraftsSection, workflow page | low |
GET/PUT/DELETE …/drafts/[draftId] | session | access via draft/center (route checks) | workflow, pending drafts | low (details in route) |
POST …/customer-touchpoint-suggestion | session | — | workflow | low |
POST /api/content/reclassify | session | transaction checks entities | content reclassify dialog | low |
Other POST/Admin
| Route | Session | Note | Risk |
|---|---|---|---|
POST /api/content/drafts/retry-approved | yes | Processes all drafts with status APPROVED (no center filter in the WHERE clause) — deliberately an admin tool; may restrict by center later. | medium (operational: too broad) |
| `POST /api/content/events | news | offers | jobs |
Discrepancy / Bug in Frontend (outside /api/content/)
| Finding | Location | Recommendation |
|---|---|---|
Incorrect path /api/content/hot-picks does not exist | editor-dashboard.tsx (Hot-Picks statistics) | Change to /api/hot-picks (or alias only if deliberately desired). Currently very likely 404 / empty data. |
Comparison with Protected List APIs
For logged-in editing, the dashboard predominantly uses:
/api/news,/api/events,/api/offers,/api/jobs,/api/shops,/api/services— withmergeSessionCenterScopeor equivalent offer filter.
The namespace /api/content/… (GET legacy) remains not automatically equally secure — deliberately or due to technical fault.
Recommended Next Steps (brief)
- Production: Evaluate
GET /api/content/shop-chainsand anonymous GET withoutcenterId— potentially API key, IP allowlist, or deprecation in favor of onlycenterId+ public filters. - editor-dashboard: Correct Hot-Picks URL.
- Optional: Merge public readers under e.g.
/api/public/centers/[id]/…and gradually shut down/api/content/*GET or only allow with signature.
Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /en/developer-guide/api-content-namespace-audit