Work / Nexus / Changelog / Day 7

Day 7: CORS and the Path to MinIO

Preflight now returns 204. Console points at gateway :3100. Presigned MinIO URLs use a browser-reachable host. Five tests cover CORS and the artifact download path.

Day 7 is CORS plus proving Console → Gateway → MinIO without the browser getting blocked.

CORS middleware was already in create_app(). It was not enough. Preflight returned 200 instead of 204. Allowed headers were only Authorization and Content-Type. The Console defaulted to gateway port 8080. The real gateway is 3100. And a presigned MinIO URL generated inside compose would have been http://minio:9000/..., which the browser cannot open.

What changed

Gateway CORS. Subclass of Starlette’s middleware so OPTIONS preflight is HTTP 204 with Access-Control-Allow-Origin, credentials, methods, and headers. Auth still skips OPTIONS. 401 JSON from the JWT middleware still gets CORS headers, so a logged-out Console can read the error instead of a opaque CORS failure.

Console URL. defaults.ts now falls back to http://localhost:3100. The Console Dockerfile bakes VITE_API_GATEWAY_URL at build time with the same default. Vite .env already had 3100; Docker did not.

MinIO from the browser. Gateway setting NEXUS_GW_MINIO_PUBLIC_ENDPOINT=localhost:9000 rewrites the internal minio:9000 host on presigned URLs. Compose sets MINIO_API_CORS_ALLOW_ORIGIN so the follow-up GET from the SPA origin is allowed.

Tests

Five passed:

  • Preflight 204 + CORS headers for http://localhost:5173
  • Unknown origin is not reflected
  • Unauthenticated GET still includes CORS headers on 401
  • Authenticated list artifacts → presigned URL, CORS on both
  • Public endpoint rewrite strips minio:9000 from the URL

This is not a live compose loop on my machine tonight. It is the contract the loop needs. Bring up deploy/compose/dev.yml, open the Artifacts panel, pick Skills, download. If MinIO is empty you get an empty list with CORS intact, not a browser error.

Skill from the work: docs/skills/code-console-gateway-minio-browser-path.md.

What I am not claiming

Wazuh still is not in this path. Skills listed from MinIO are not yet the git docs/skills/ copies unless something uploaded them. Day 7 is the pipe, not the inventory.

Later note (Aug 29): lab still defaults to MinIO. Cloudflare R2 is a prod/k8s overlay on the same S3 client (NEXUS_GW_OBJECT_STORE_BACKEND=r2) — it does not replace this Day 7 browser path; it changes where the bucket lives.

Next is Day 8: the Settings page is still a stub.