# User-service authentication and minikube deployment design ## Goal Replace local password authentication with the organization `user-service`, personalize the chat interface from the returned profile, and run the complete interface stack in minikube with access at `http://localhost:3000`. ## Existing contract The deployed `user-service` exposes `POST /api/user/auth` and accepts: ```json { "identifier": "login-or-email", "password": "plaintext-at-authentication-time" } ``` A successful response is a safe user profile containing numeric `id`, `login`, `full_name`, `position`, `email`, `last_name`, `first_name`, `middle_name`, `roles`, `is_active`, `created_at`, and `updated_at`. It never contains a password or password hash. Invalid or inactive users produce `401`. ## Authentication architecture The browser continues to call only `POST /api/auth/login` on the chat application. The login route calls `${USER_SERVICE_URL}/api/user/auth` from the server with JSON `{ identifier, password }`. Browser code never calls `user-service` directly and does not learn its internal URL. On successful authentication, the chat application validates the full upstream profile and upserts a local user projection. The projection preserves a UUID primary key for existing chat foreign keys and stores a unique external service ID plus profile fields. No password or password hash from the external service is stored. Existing legacy local users remain readable, but local password authentication is removed. The application issues its existing HS256 JWT with a 24-hour lifetime. Its subject is the local UUID and its claims include the email. `/api/auth/me` verifies the JWT against the local projection and returns the current stored profile. A later login refreshes changed profile fields from `user-service`. ## Database changes Add a migration that: - makes `users.password_hash` nullable for external identities; - adds `external_user_id bigint UNIQUE`, `login`, `full_name`, `position`, `first_name`, `last_name`, `middle_name`, and `roles jsonb`; - keeps `last_model_id` and the UUID `id` unchanged; - adds constraints requiring `external_user_id`, email, login, full name, first name, last name, position, and roles for new external users. The repository exposes a single upsert operation keyed by `external_user_id`. It updates profile fields and `updated_at`, preserves the local UUID and `last_model_id`, and returns the complete local identity. ## Error and security behavior - Missing or malformed credentials return `400`. - An upstream `401` maps to the existing generic `401` login response without exposing upstream details. - Network errors, timeouts, invalid upstream JSON, and upstream 5xx responses map to `503`. - The user-service request has an explicit timeout of 5 seconds and `Cache-Control: no-store` remains on auth responses. - Passwords are never logged, persisted, included in JWTs, or returned to the browser. - `USER_SERVICE_URL` is a required server-only environment variable and is normalized to avoid duplicate slashes. ## Personalization Extend `AuthIdentity` with `externalUserId`, `login`, `fullName`, `position`, `firstName`, `lastName`, `middleName`, and `roles`. The login form label and autocomplete behavior support either login or email. The sidebar displays initials derived from first and last name, the full name, position, email, and compact role badges. The empty chat state greets the user by first name. Mobile and desktop layouts use the same profile component. Missing optional middle name does not create empty punctuation or spacing. ## Minikube topology All resources run in namespace `aegida-services`: - existing `user-service` and `user-service-postgres` remain unchanged; - `ai-control-chat-ui` Deployment and ClusterIP Service expose port 3000; - dedicated chat PostgreSQL uses a StatefulSet, ClusterIP Service, Secret, persistent volume claim, and port 5432 inside the cluster; - MinIO uses a Deployment, ClusterIP Service, Secret, persistent volume claim, API port 9000, and console port 9001; - the UI ConfigMap sets `USER_SERVICE_URL=http://user-service.aegida-services.svc.cluster.local:8080`, database/S3 endpoints, Gateway mock mode, and non-secret application values; - the UI Secret contains JWT, database and MinIO credentials. The image is built for minikube with a deterministic local tag and `imagePullPolicy: IfNotPresent`. Startup uses the existing entrypoint to wait/retry for dependencies, apply migrations, seed only legacy development data when explicitly enabled, ensure the private attachment bucket, then start Next.js. Kubernetes readiness and liveness probes use a lightweight application health endpoint. ## Localhost access After rollout succeeds, run: ```bash kubectl -n aegida-services port-forward service/ai-control-chat-ui 3000:3000 ``` The interface is then available at `http://localhost:3000`. This is intentionally workstation-local rather than a public internet endpoint. The handoff includes the port-forward process status and exact stop/restart commands. ## Verification - Unit tests cover upstream response validation, URL normalization, timeout and error mapping. - Route tests verify identifier/password translation, local profile upsert, JWT issuance, generic `401`, and safe `503` behavior. - Repository tests verify profile refresh preserves local UUID and last selected model. - Component tests cover login by identifier, personalized sidebar and greeting. - Static checks cover manifests with `kubectl apply --dry-run=client` or server-side dry run where available. - Deployment verification waits for every rollout, checks pod logs and readiness, authenticates through the localhost UI API, fetches `/api/auth/me`, and confirms the returned personalized profile.