# 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.