docs(cloud-console): document user authentication

This commit is contained in:
2026-07-13 17:55:21 +08:00
parent cdef630e67
commit c72c31de04
10 changed files with 595 additions and 113 deletions
@@ -0,0 +1,58 @@
## ADDED Requirements
### Requirement: Account login is the primary Console authentication flow
The Console SHALL use username/password login and a server-managed user session as its primary authentication flow, while retaining the existing tab-scoped bearer-token flow behind an explicit compatibility action.
#### Scenario: Operator opens the Console without authentication
- **WHEN** `/v1/auth/me` reports no valid user session and no compatibility bearer token is active
- **THEN** the Console displays the username/password login form as the primary action and offers a secondary “Use API token” action
#### Scenario: Login succeeds
- **WHEN** an operator submits valid account credentials
- **THEN** the Console enters session mode, displays the authenticated user's identity, and loads only views allowed by the returned effective scopes
#### Scenario: Login fails
- **WHEN** the login endpoint rejects credentials or throttles the attempt
- **THEN** the Console shows the generic authentication failure without revealing whether the username exists or persisting the password
#### Scenario: Operator chooses token compatibility
- **WHEN** an operator explicitly selects “Use API token” and enters a bearer token
- **THEN** the Console retains that token only in tab-scoped session storage and uses the existing bearer request behavior
### Requirement: Console manages authenticated session lifecycle
The Console SHALL send cookies on user-session requests, attach session CSRF proof to unsafe requests, provide logout and password-change controls, and distinguish authentication loss from insufficient authorization.
#### Scenario: User logs out
- **WHEN** a logged-in user activates logout
- **THEN** the Console submits CSRF-protected logout, clears local authentication state, and returns to the login screen
#### Scenario: Session expires
- **WHEN** a session-authenticated request returns `401`
- **THEN** the Console clears session UI state and returns to login with a session-expired message
#### Scenario: User lacks a required scope
- **WHEN** an otherwise valid session-authenticated request returns `403`
- **THEN** the Console preserves the session and shows an authorization error for that operation
#### Scenario: User must change a temporary password
- **WHEN** the current-user response sets `must_change_password`
- **THEN** the Console restricts navigation to password change and logout until password change succeeds
### Requirement: Administrators manage users from the Console
The Console SHALL expose a user-management view only to principals whose effective scopes include `users:admin`, with controls to list/create users, change role or enabled state, reset passwords, and revoke sessions.
#### Scenario: Administrator opens user management
- **WHEN** an authenticated administrator opens the Users view
- **THEN** the Console displays non-secret user records and the supported lifecycle controls without exposing password or session credential material
#### Scenario: Administrator creates an account
- **WHEN** an administrator submits valid user details and an initial password
- **THEN** the Console creates the account, clears password fields immediately, and shows that the new user must change the initial password
#### Scenario: Non-admin loads the Console
- **WHEN** the current principal lacks `users:admin`
- **THEN** the Console does not render the Users navigation or management controls, while backend authorization remains authoritative
#### Scenario: User-management mutation fails
- **WHEN** a create, update, reset, or revoke request returns validation, conflict, or authorization failure
- **THEN** the Console preserves non-secret form state as appropriate, clears all password fields, and displays the API error without retrying the mutation automatically
@@ -0,0 +1,136 @@
## ADDED Requirements
### Requirement: Persistent user accounts protect password material
The system SHALL persist uniquely identifiable human user accounts with a case-insensitive unique username, display name, fixed role, enabled state, forced-password-change state, and security timestamps, and SHALL store passwords only as salted Argon2id hashes that are never returned or logged.
#### Scenario: Administrator creates a user
- **WHEN** an authorized administrator creates a user with a username, display name, role, and valid initial password
- **THEN** the system stores the normalized unique identity and password hash, returns only non-secret account fields, and marks the account to change its initial password
#### Scenario: Username differs only by case
- **WHEN** an administrator attempts to create a username that differs from an existing username only by normalization or letter case
- **THEN** the system rejects the duplicate without changing either account
#### Scenario: Stored password needs stronger parameters
- **WHEN** a user successfully signs in and the password hasher reports that the stored Argon2id parameters are outdated
- **THEN** the system replaces the stored hash using current parameters without retaining or logging the submitted password
### Requirement: Fixed roles map to operation scopes
The system SHALL map `viewer`, `operator`, and `admin` users to the existing Cloud API scopes so resource handlers authorize user sessions and bearer principals through the same `Principal` scope checks.
#### Scenario: Viewer accesses read dashboards
- **WHEN** a logged-in `viewer` calls task-read, pool-read, or plugin-read operations
- **THEN** the request is authorized, while task submission, plugin administration, and user administration remain forbidden
#### Scenario: Operator submits a task
- **WHEN** a logged-in `operator` submits a task
- **THEN** the request is authorized in addition to all viewer read operations, while plugin and user administration remain forbidden
#### Scenario: Administrator manages users
- **WHEN** a logged-in `admin` invokes an operation requiring `users:admin` or another Cloud API scope
- **THEN** the request is authorized subject to the operation's normal validation
### Requirement: Password login creates a revocable server-side session
The system SHALL authenticate enabled users with username and password, SHALL create an opaque expiring session whose secret is stored only as a digest, and SHALL deliver the session secret in an `HttpOnly`, `SameSite` cookie that is `Secure` in production.
#### Scenario: Valid credentials create a session
- **WHEN** an enabled user submits a correct username and password within throttle limits
- **THEN** the system records a revocable session, returns the non-secret current-user representation, and sets the session and CSRF cookies with their required security attributes
#### Scenario: Credentials are not valid
- **WHEN** the username is unknown, disabled, temporarily throttled, or paired with a wrong password
- **THEN** the system returns the same generic authentication failure without revealing which condition occurred and without setting a session cookie
#### Scenario: Session is expired or revoked
- **WHEN** a request presents a session whose idle or absolute expiry has passed, whose row is revoked, or whose authentication version no longer matches the user
- **THEN** authentication fails and the system clears the browser session cookies
### Requirement: User session lifecycle is controllable
The system SHALL let a logged-in user inspect the current account, sign out, and change their own password, and SHALL revoke sessions when a password, role, enabled state, or authentication version changes.
#### Scenario: User signs out
- **WHEN** a logged-in user signs out with valid CSRF proof
- **THEN** the current session is revoked server-side and both browser cookies are cleared
#### Scenario: User changes password
- **WHEN** a logged-in user proves the current password and supplies a valid new password
- **THEN** the password hash and authentication version are updated, other sessions are revoked, and the user must continue only with a newly established valid session
#### Scenario: Temporary password requires replacement
- **WHEN** a user signs in while `must_change_password` is set
- **THEN** the session may call current-user, password-change, and logout operations but cannot access other Cloud API resources until the password is changed
### Requirement: Cookie-authenticated writes require CSRF proof
The system SHALL require a session-bound CSRF value on unsafe HTTP methods authenticated by a user cookie, while requests authenticated by an explicit bearer header SHALL remain exempt from cookie CSRF validation.
#### Scenario: Valid cookie session submits a write
- **WHEN** a cookie-authenticated request uses an unsafe method and its `X-CSRF-Token` header matches the CSRF cookie and session-bound digest
- **THEN** the request proceeds to normal scope and payload validation
#### Scenario: Cookie request omits CSRF proof
- **WHEN** a cookie-authenticated request uses an unsafe method without matching CSRF proof
- **THEN** the system rejects it before executing the operation
#### Scenario: Bearer client submits a write
- **WHEN** an authorized client sends an unsafe request with an `Authorization: Bearer` credential and no session cookie is used for authentication
- **THEN** the request is evaluated by existing bearer scope checks without requiring a CSRF header
### Requirement: Failed login attempts are throttled without permanent lockout
The system SHALL enforce a bounded failed-login window and temporary block by normalized username and trusted client-address bucket, SHALL use generic client responses, and SHALL clear applicable failure state after successful authentication.
#### Scenario: Repeated failures exceed the limit
- **WHEN** repeated failed logins for the same username and client bucket exceed the configured limit within the failure window
- **THEN** further attempts are temporarily blocked, audited, and answered with the generic authentication failure
#### Scenario: Temporary block expires
- **WHEN** the configured block duration passes
- **THEN** the account can attempt authentication again without administrator intervention
#### Scenario: Unknown username is submitted
- **WHEN** a login names no stored user
- **THEN** the system performs timing-resistant dummy password verification and applies the same throttle and response behavior as a wrong password
### Requirement: Administrators control user lifecycle
The system SHALL expose `users:admin`-protected operations to list and create users, change display name/role/enabled state, reset passwords, and revoke sessions, and SHALL prevent API actions that remove the last enabled administrator.
#### Scenario: Administrator disables an operator
- **WHEN** an administrator disables an enabled operator
- **THEN** the account can no longer authenticate and all of its active sessions are revoked
#### Scenario: Administrator resets a password
- **WHEN** an administrator assigns a valid temporary password to another user
- **THEN** existing sessions are revoked and the account is required to change that password after its next login
#### Scenario: Non-admin attempts user management
- **WHEN** a viewer, operator, anonymous caller, or Host principal calls a user-administration operation
- **THEN** the system rejects the request without exposing password or session state
#### Scenario: Last administrator would be removed
- **WHEN** an API request would disable or demote the only enabled administrator
- **THEN** the system rejects the request and preserves an enabled administrator
### Requirement: Deployment administrators can bootstrap and recover accounts safely
The system SHALL provide an administration CLI that checks the current database schema, reads new passwords interactively without echo, and can create, reset, enable, or revoke sessions for user accounts without accepting passwords in command arguments.
#### Scenario: First administrator is created in Compose
- **WHEN** a deployment administrator runs the user-create command inside the Cloud API container and enters a valid password twice at the interactive prompts
- **THEN** an enabled administrator account is created without placing the password in process arguments, Compose configuration, or command output
#### Scenario: Interactive password confirmation differs
- **WHEN** the two interactive password entries do not match
- **THEN** the CLI exits unsuccessfully without changing the account
#### Scenario: Database schema is not current
- **WHEN** a user administration CLI command runs against a database missing the required migration
- **THEN** it fails with a migration diagnostic rather than creating partial schema state
### Requirement: Authentication security events are auditable without secrets
The system SHALL record durable structured audit events for authentication and user-administration outcomes with actor, target, action, timestamp, outcome, and correlation context, and SHALL exclude submitted passwords, hashes, raw cookies, CSRF values, and bearer credentials.
#### Scenario: Login fails
- **WHEN** a login attempt fails or is throttled
- **THEN** the system records a safe failure category and correlation context without recording the submitted password or confirming whether the username exists
#### Scenario: Administrator changes a role
- **WHEN** an administrator changes a user's role
- **THEN** the system records the actor, target user id, old/new role metadata, outcome, and associated session revocation without any credential material
@@ -0,0 +1,89 @@
## ADDED Requirements
### Requirement: Versioned user authentication routes
The public API SHALL expose username/password login, logout, current-user, and password-change operations under `/v1/auth/...`, with login as the only anonymously callable user-authentication operation.
#### Scenario: Browser logs in
- **WHEN** a caller submits valid credentials to `/v1/auth/login`
- **THEN** the API establishes the secure user session and returns the user's non-secret identity, role, and effective scopes
#### Scenario: Caller gets the current user
- **WHEN** a caller presents a valid user session to `/v1/auth/me`
- **THEN** the API returns the current user's non-secret identity, role, effective scopes, and forced-password-change state
#### Scenario: Anonymous caller invokes another auth operation
- **WHEN** an anonymous caller invokes logout, current-user, or password-change
- **THEN** the API returns an authentication error without executing the operation
### Requirement: Versioned user administration routes
The public API SHALL expose user listing, creation, update, password reset, and session-revocation operations under `/v1/users/...`, all protected by `users:admin`.
#### Scenario: Administrator lists users
- **WHEN** a principal with `users:admin` lists users
- **THEN** the API returns bounded non-secret account records and no password hash, session digest, CSRF value, or login credential
#### Scenario: User administration lacks scope
- **WHEN** an authenticated principal without `users:admin` invokes any `/v1/users/...` operation
- **THEN** the API returns an authorization error before reading or changing protected user state
### Requirement: Existing bearer authentication remains compatible
The public and internal APIs SHALL continue to accept existing configured public, Host, enrollment, and dynamically enrolled Host bearer credentials with their previous scope and host-binding semantics after user authentication is enabled.
#### Scenario: CloudClient uses a configured token
- **WHEN** an existing `CloudClient` sends a valid configured public bearer token
- **THEN** its permitted resource operation succeeds without a browser session or CSRF header
#### Scenario: Host Agent uses its bearer credential
- **WHEN** an existing Host Agent authenticates to an internal Host route
- **THEN** host identity binding and authorization behave exactly as before the user system was added
## MODIFIED Requirements
### Requirement: Pluggable authentication hook with a safe default
The system SHALL evaluate every protected route through a configurable scope-aware `AuthProvider` chain that can authenticate existing bearer credentials or a valid repository-backed user session, and the deployable Cloud Control Plane SHALL reject anonymous resource access unless an explicit insecure-development override is enabled outside production.
#### Scenario: Production starts without a safe authentication mechanism
- **WHEN** the Cloud Control Plane is configured as production without user-session authentication, a usable bearer provider, or other safe public authentication provider
- **THEN** startup or readiness fails rather than exposing anonymous platform routes
#### Scenario: Explicit local anonymous override
- **WHEN** a non-production operator explicitly enables the insecure anonymous-development override
- **THEN** platform routes may use an anonymous principal and the application records that insecure mode is active
#### Scenario: Custom AuthProvider is honored
- **WHEN** a caller configures a custom `AuthProvider` that rejects a request or omits its required scope
- **THEN** the platform SDK route returns an authentication or authorization error without executing its handler operation
#### Scenario: User session provider is honored
- **WHEN** the authentication chain resolves a valid user session to a principal with the required scope
- **THEN** the protected route authorizes that principal through the same scope check used for bearer callers
### Requirement: Python SDK client mirrors the REST API
The system SHALL provide a Python `CloudClient` exposing methods corresponding to the `/v1/...` resource, user-authentication, and user-administration routes, with cookie persistence for login sessions and continued support for injected bearer authentication.
#### Scenario: Client submits a task and retrieves status
- **WHEN** a caller uses `CloudClient` to submit a task and then fetch its status by the returned id
- **THEN** the client's methods produce the same result as calling the corresponding `/v1/...` endpoints directly over HTTP
#### Scenario: Client authenticates a user session
- **WHEN** a caller uses `CloudClient` to log in with valid user credentials and then requests the current user
- **THEN** the client preserves the session cookies and returns the same non-secret user representation as the direct REST calls
#### Scenario: Bearer administrator manages users
- **WHEN** a caller configures `CloudClient` with a bearer token having `users:admin` and invokes a user-administration method
- **THEN** the client sends bearer authentication and returns the corresponding user-administration result without requiring cookie login
### Requirement: Public API operations enforce scopes
The public platform API SHALL require operation-specific scopes for task submission, task reading, pool reading, plugin reading, plugin administration, and user administration, regardless of whether the principal came from a bearer credential or user session.
#### Scenario: Submit principal has task scope
- **WHEN** a bearer or user principal with `tasks:submit` calls the task-submission endpoint
- **THEN** the request is authorized subject to normal task validation
#### Scenario: Non-admin principal attempts plugin registration
- **WHEN** an authenticated principal without `plugins:admin` calls plugin registration
- **THEN** the API rejects the request before resolving or loading the plugin target
#### Scenario: Non-admin principal attempts user administration
- **WHEN** an authenticated principal without `users:admin` calls a user-administration endpoint
- **THEN** the API rejects the request before reading or changing protected user state