aegida-console / .superpowers / sdd / 2026-07-28-user-service-auth-minikube / final-fix-report.md
final-fix-report.md
Raw

Final fix report: user-service auth and minikube

Status

PASS. The critical, important, and minor findings from the final review were fixed, regression-tested, built into the minikube image, deployed, and smoke-tested through the live application.

Commit: the final fix commit containing this report; its immutable SHA is recorded in the parent handoff because a Git commit cannot contain its own hash.

Changes

  • Represented the upstream PostgreSQL bigint user ID as a canonical positive decimal string throughout the application. Unsafe JavaScript numbers are rejected instead of rounded.
  • Added one shared runtime validator for auth identities, strict UUID validation for application user/JWT IDs, and the exact supported model set.
  • Adopted legacy email-matched users atomically before external-ID upsert, preserving the existing application UUID, history, and selected model.
  • Rejected blank/whitespace credentials and delegated interactive login to user-service.
  • Replaced the private MinIO redirect with an authenticated, owner-checked, same-origin streamed download. The browser now downloads with the stored Bearer token and shows a safe error on failure.
  • Fixed the PostgreSQL UUID/text inference bug in pending attachment object-key generation.
  • Added a 180-second startup probe window while retaining the existing readiness/liveness probes.
  • Split build and production dependency installation so the runtime image contains production dependencies only.
  • Updated the auth setup documentation and environment comments without staging unrelated working-tree edits.

Files in this fix wave

  • .env.example
  • Dockerfile
  • README.md
  • app/api/attachments/[id]/route.ts
  • app/api/attachments/[id]/route.test.ts
  • app/api/auth/login/route.ts
  • app/api/auth/routes.test.ts
  • components/auth/auth-gate.tsx
  • components/auth/auth-gate.test.tsx
  • components/auth/login-screen.tsx
  • components/chat/chat-app.test.tsx
  • components/chat/chat-message.tsx
  • components/chat/chat-message.test.tsx
  • k8s/app.yaml
  • lib/auth/server.ts
  • lib/auth/server.test.ts
  • lib/auth/types.ts
  • lib/auth/types.test.ts
  • lib/auth/user-service.ts
  • lib/auth/user-service.test.ts
  • lib/db/attachments.ts
  • lib/db/attachments-query.test.ts
  • lib/db/users.ts
  • lib/db/users.test.ts
  • lib/storage/s3.ts

Red/green evidence

  • Auth regression tests first failed in 6 of 7 files with 20 expected failures for numeric IDs, permissive identities, non-UUID subjects, and legacy-user replacement. After the changes: 7 files, 57 tests passed.
  • Attachment tests first showed the private redirect (302 instead of 200) and no authenticated download control. After the changes: route/component coverage passed (2 files, 5 tests).
  • The live upload first returned 503; PostgreSQL reported ambiguous and then inconsistent $1 types. A focused regression test was added and failed before the final explicit UUID/text casts. After the fix: 3 focused attachment files, 6 tests passed, and live upload/download passed.
  • The startup-probe semantic check initially returned false/exit 1 and passed after the manifest change.

Final verification

  • npm test: 22 files, 117 tests passed.
  • npm run lint: passed.
  • npx tsc --noEmit: passed.
  • Next.js production build: passed in the final minikube image build.
  • kubectl apply --dry-run=client -f k8s/: passed for all 10 objects.
  • Final image: sha256:ce32811723087cc756e722c3b5d73b1f35d2257fa83be7e74f263c7000ce424a.
  • Deployment: 1 desired, 1 updated, 1 ready, 1 available replica.
  • Stack: UI, MinIO, PostgreSQL, user-service, and user-service PostgreSQL all Running/Ready, with zero restarts.
  • Runtime package check: AWS SDK and pg are present; eslint is absent.

Live smoke test

All requests used bounded timeouts, and no password or token was printed or written to the report.

  • Login through user-service: HTTP 200.
  • Authenticated /api/auth/me: HTTP 200; externalUserId is a string at runtime.
  • Attachment upload: HTTP 201.
  • Authenticated same-origin attachment download: HTTP 200.
  • Downloaded 5,964 bytes and matched the uploaded file byte-for-byte.
  • Safe profile fields observed: Test Admin, Administrator, role admin, model chatgpt.
  • The workstation-local port-forward remains active on http://localhost:3000 (PID 48069 at verification time).

Security and residual concerns

  • JWT subjects are rejected before database access unless they are UUIDs; ownership is checked before object storage access; no cluster-internal MinIO URL reaches the browser.
  • Responses use Cache-Control: no-store, safe attachment disposition encoding, and X-Content-Type-Options: nosniff.
  • npm audit --omit=dev reports 5 production dependency findings (2 moderate, 3 high). Broad dependency upgrades were intentionally not mixed into this fix wave and need separate triage with regression testing.
  • The current upstream JSON contract emits the external ID as a number. IDs beyond JavaScript's safe-integer range are deliberately rejected; full signed-bigint support requires user-service to emit the ID as a decimal string.
  • The smoke upload creates a pending attachment governed by the existing 24-hour expiry policy.