[Hardening] Proxy /public/ so the public analytics share page works #92

Closed
opened 2026-07-14 19:47:47 +00:00 by claude-bot · 2 comments
Contributor

Context

The analytics share page (/analytics/share/:token) is the app's one unauthenticated feature. The frontend fetches /public/analytics/{shareToken} (webapp/frontend/src/api/campaigns.js:150-151), and the backend mounts the public router at that prefix (webapp/backend/app/main.py:94: app.include_router(public.router, prefix="/public", ...)).

Current behavior

Neither frontend proxy layer forwards /public/* to the backend:

  • webapp/frontend/nginx.conf proxies only location /api/ (line 22) and location /auth/ (line 33)
  • webapp/frontend/vite.config.js dev proxy (block starting line 16) covers only /api and the concrete /auth backend endpoints

So /public/analytics/... falls through to the SPA catch-all and returns index.html; r.json() in fetchPublicAnalytics then throws parsing HTML. Result: share links never load in dev, and in the prod compose build they only work if an external reverse proxy happens to route /public to the backend itself.

Fix / Spec

Least-invasive option (preferred):

  1. Add a location /public/ { proxy_pass ... } block to webapp/frontend/nginx.conf, mirroring the existing /api/ block (same proxy_pass target and headers).
  2. Add a "/public" entry to the server.proxy map in webapp/frontend/vite.config.js, mirroring the /api entry.

(Alternative, only if preferred during implementation: move the backend route under /api/public and update campaigns.js — pick one approach, not both.)

Take care that no SPA route begins with /public (none does today) so the catch-all is unaffected.

Acceptance criteria

  • In the Vite dev server, opening /analytics/share/<valid-token> renders analytics data (network tab shows JSON from /public/analytics/..., not HTML).
  • In a prod compose build (nginx container), the same share link renders data without relying on any external reverse-proxy rule.
  • Authenticated routes and SPA routing unaffected.

References

  • webapp/frontend/src/api/campaigns.js:150-151 (fetchPublicAnalytics)
  • webapp/backend/app/main.py:94 (public router mount)
  • webapp/frontend/nginx.conf:22 (/api/), :33 (/auth/) — no /public/
  • webapp/frontend/vite.config.js:16+ (dev proxy map)

Filed from the July 2026 full-project review.

## Context The analytics share page (`/analytics/share/:token`) is the app's one unauthenticated feature. The frontend fetches `/public/analytics/{shareToken}` (`webapp/frontend/src/api/campaigns.js:150-151`), and the backend mounts the public router at that prefix (`webapp/backend/app/main.py:94`: `app.include_router(public.router, prefix="/public", ...)`). ## Current behavior Neither frontend proxy layer forwards `/public/*` to the backend: - `webapp/frontend/nginx.conf` proxies only `location /api/` (line 22) and `location /auth/` (line 33) - `webapp/frontend/vite.config.js` dev proxy (block starting line 16) covers only `/api` and the concrete `/auth` backend endpoints So `/public/analytics/...` falls through to the SPA catch-all and returns `index.html`; `r.json()` in `fetchPublicAnalytics` then throws parsing HTML. Result: share links never load in dev, and in the prod compose build they only work if an external reverse proxy happens to route `/public` to the backend itself. ## Fix / Spec Least-invasive option (preferred): 1. Add a `location /public/ { proxy_pass ... }` block to `webapp/frontend/nginx.conf`, mirroring the existing `/api/` block (same proxy_pass target and headers). 2. Add a `"/public"` entry to the `server.proxy` map in `webapp/frontend/vite.config.js`, mirroring the `/api` entry. (Alternative, only if preferred during implementation: move the backend route under `/api/public` and update `campaigns.js` — pick one approach, not both.) Take care that no SPA route begins with `/public` (none does today) so the catch-all is unaffected. ## Acceptance criteria - In the Vite dev server, opening `/analytics/share/<valid-token>` renders analytics data (network tab shows JSON from `/public/analytics/...`, not HTML). - In a prod compose build (nginx container), the same share link renders data without relying on any external reverse-proxy rule. - Authenticated routes and SPA routing unaffected. ## References - `webapp/frontend/src/api/campaigns.js:150-151` (`fetchPublicAnalytics`) - `webapp/backend/app/main.py:94` (public router mount) - `webapp/frontend/nginx.conf:22` (`/api/`), `:33` (`/auth/`) — no `/public/` - `webapp/frontend/vite.config.js:16+` (dev proxy map) _Filed from the July 2026 full-project review._
Author
Contributor

Picking this up as part of a v3.3.0 push. Landing on branch hardening/infra-docs together with #102 and #113 (grouped by component to keep the diffs reviewable). Taking the least-invasive option from the spec: add the /public/ proxy to both nginx.conf and the Vite dev proxy, leaving the backend route where it is.

Picking this up as part of a v3.3.0 push. Landing on branch `hardening/infra-docs` together with #102 and #113 (grouped by component to keep the diffs reviewable). Taking the least-invasive option from the spec: add the `/public/` proxy to both nginx.conf and the Vite dev proxy, leaving the backend route where it is.
Author
Contributor

Fixed on main (commit 5154b32, merged via 9448593). Added a location /public/ block to webapp/frontend/nginx.conf mirroring the existing /api/ block, and a "/public" entry to the Vite dev proxy in vite.config.js. Confirmed no SPA route begins with /public, so the catch-all is unaffected. Verified nginx -t passes on the final merged config. Backend route left where it is (least-invasive option). Frontend suite not run (no local Node — config-only change).

Fixed on `main` (commit `5154b32`, merged via `9448593`). Added a `location /public/` block to `webapp/frontend/nginx.conf` mirroring the existing `/api/` block, and a `"/public"` entry to the Vite dev proxy in `vite.config.js`. Confirmed no SPA route begins with `/public`, so the catch-all is unaffected. Verified `nginx -t` passes on the final merged config. Backend route left where it is (least-invasive option). Frontend suite not run (no local Node — config-only change).
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#92
No description provided.