aegida-console / docs / superpowers / specs / 2026-07-28-user-service-auth-minikube-design.md
2026-07-28-user-service-auth-minikube-design.md
Raw

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:

{
  "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:

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.