# User API The User API provides both self-service account operations and sudo-gated tenant user management. It complements the generic Data API by exposing explicit user workflows on top of the sudo-protected `users` model. ## Base Path All User API routes are prefixed with `/api/user` ## Content Type - **Request**: `application/json` - **Response**: `application/json` ## Authentication All User API routes require authentication via Abbotik bearer token in the Authorization header. - **Header**: `Authorization: Bearer ` Abbotik accepts either a signed JWT or a bearer API key from `/api/keys` in that bearer slot. ## Key Features ### Self-Service Operations Unlike the Data API (which requires sudo for the users table), the User API allows authenticated users to: - ✅ View their own profile - ✅ Update their own name and auth identifier - ✅ Deactivate their own account ### Administrative Operations With administrative Abbotik auth context, privileged users can: - ✅ List tenant users - ✅ Create users - ✅ Create invite codes for future human or machine users - ✅ Read, update, and delete other users ### Security Boundaries The User API enforces strict security controls: - ❌ Cannot modify own access level (prevents privilege escalation) - ❌ Cannot modify other users' profiles (use Data API with sudo) - ❌ Cannot update protected fields like access_read, access_edit, access_full ## Endpoint Summary | Method | Path | Description | |--------|------|-------------| | GET | `/api/user/me` | View your own user profile | | PUT | `/api/user/me` | Update your own name or auth identifier | | DELETE | `/api/user/me` | Deactivate your own account (soft delete) | | GET | `/api/user` | List users in the tenant (sudo required) | | POST | `/api/user` | Create a user in the tenant (sudo required) | | POST | `/api/user/invite` | Create a one-time invite for a future tenant user | | GET | `/api/user/machine-keys` | List tenant machine public keys | | POST | `/api/user/machine-keys` | Add a new tenant machine public key | | DELETE | `/api/user/machine-keys/:key_id` | Revoke one tenant machine public key | | POST | `/api/user/machine-keys/rotate` | Rotate a tenant machine public key | | GET | `/api/user/secrets` | List tenant secrets without exposing plaintext | | POST | `/api/user/secrets` | Create a new encrypted tenant secret | | PUT | `/api/user/secrets/:secret_name` | Replace one tenant secret by name | | DELETE | `/api/user/secrets/:secret_name` | Delete one tenant secret by name | | GET | `/api/user/introspect` | Return the trusted execution context for the current bearer token | | GET | `/api/user/:id` | View a specific user by UUID or `me` | | PUT | `/api/user/:id` | Update a specific user by UUID or `me` | | DELETE | `/api/user/:id` | Delete a specific user by UUID or `me` | | POST | `/api/user/sudo` | Mint a short-lived sudo token for administrative actions | | POST | `/api/user/fake` | Mint a short-lived impersonation token for another user | ## LLM Navigation Notes Use the exact router-shaped docs paths: - `/docs/api/user/GET` - `/docs/api/user/POST` - `/docs/api/user/invite/POST` - `/docs/api/user/machine-keys/GET` - `/docs/api/user/machine-keys/POST` - `/docs/api/user/machine-keys/key_id/DELETE` - `/docs/api/user/machine-keys/rotate/POST` - `/docs/api/user/secrets/GET` - `/docs/api/user/secrets/POST` - `/docs/api/user/secrets/secret_name/PUT` - `/docs/api/user/secrets/secret_name/DELETE` - `/docs/api/user/introspect/GET` - `/docs/api/user/id/GET` - `/docs/api/user/id/PUT` - `/docs/api/user/id/DELETE` - `/docs/api/user/sudo/POST` - `/docs/api/user/fake/POST` --- ## GET /api/user/introspect Return the trusted, post-validation execution context for the currently presented Abbotik bearer token. ### Authentication - **Required**: Yes (any authenticated user) - **Sudo**: Not required ### Purpose This route is execution-context-shaped, not profile-shaped. It is intended for sibling services and agents that need the resolved tenant, user, and token mode after the standard protected-route auth pipeline has already revalidated the request. ### Request ```bash curl -X GET \ -H "Authorization: Bearer $TOKEN" \ https://api.example.com/api/user/introspect ``` ### Success Response (200) ```json { "success": true, "data": { "user": { "id": "550e8400-e29b-41d4-a716-446655440000", "username": "john@example.com", "access": "full", "access_read": ["uuid1"], "access_edit": ["uuid2"], "access_full": ["uuid3"] }, "tenant": { "id": "660e8400-e29b-41d4-a716-446655440000", "name": "acme", "db_type": "postgresql", "database": "db_main", "schema": "ns_tenant_acme" }, "token": { "subject": "550e8400-e29b-41d4-a716-446655440000", "expires_at": "2026-04-14T20:00:00.000Z", "is_sudo": false, "is_fake": false, "auth_type": "username", "key_id": null } } } ``` ### Notes - Values come from the trusted auth context after signature verification, tenant resolution, user revalidation, and sudo/public-key checks - This route does **not** expose the raw JWT or upstream identity-provider details - `?unwrap` and `?select=` work through the normal protected-route response pipeline - `auth_type` is `username` for username/password human tokens, `public_key` for machine tokens, and `api_key` for global user API keys --- ## Machine Key Administration Tenant administrators can manage tenant-bound public keys under `/api/user/machine-keys`. ### Authentication - **Required**: Yes (any authenticated user) - **Sudo**: Root or full tenant access is required ### Endpoint Summary - `GET /api/user/machine-keys` — list machine keys with fingerprint-first metadata - `POST /api/user/machine-keys` — add a new machine key for a tenant-local user - `DELETE /api/user/machine-keys/:key_id` — revoke one machine key - `POST /api/user/machine-keys/rotate` — rotate a machine key with overlap ### Notes - The machine-key routes expose metadata only; they do not return private key material. - Rotation preserves overlap so callers can deploy a new private key before revocation. - Bootstrap and challenge flows still originate in `/auth/provision` and `/auth/challenge`. --- ## Tenant Secrets Tenant administrators can manage encrypted tenant-local secret material under `/api/user/secrets`. ### Authentication - **Required**: Yes - **Access**: `root` or `full` ### Endpoint Summary - `GET /api/user/secrets` — list tenant secret metadata without plaintext - `POST /api/user/secrets` — create a tenant secret by name - `PUT /api/user/secrets/:secret_name` — rotate or replace a tenant secret in place - `DELETE /api/user/secrets/:secret_name` — delete one tenant secret by name ### Notes - `ABBOTIK_SECRETS_MASTER_KEY` must be configured before this surface can store or resolve secrets. - Plaintext values are accepted only on create and update, encrypted before storage, and never returned by the API. - This table is tenant-local but intentionally not part of the broad Describe/Data surface. --- ## GET /api/user/me Retrieve your own user profile information including access levels and permissions. ### Authentication - **Required**: Yes (any authenticated user) - **Sudo**: Not required ### Request ```bash curl -X GET \ -H "Authorization: Bearer $TOKEN" \ https://api.example.com/api/user/me ``` ### Success Response (200) ```json { "success": true, "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "John Doe", "auth": "john@example.com", "access": "full", "access_read": ["uuid1", "uuid2"], "access_edit": ["uuid3"], "access_full": ["uuid4"], "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "trashed_at": null } } ``` ### Response Fields - `id` (uuid) - Unique user identifier - `name` (string) - Display name - `auth` (string) - Authentication identifier (username/email) - `access` (string) - Base access level: `deny`, `read`, `edit`, `full`, or `root` - `access_read` (uuid[]) - Record-level read ACLs - `access_edit` (uuid[]) - Record-level edit ACLs - `access_full` (uuid[]) - Record-level full ACLs - `created_at` (timestamp) - Account creation timestamp - `updated_at` (timestamp) - Last update timestamp - `trashed_at` (timestamp|null) - Soft delete timestamp (null if active) --- ## PUT /api/user/me Update your own profile information. You can only modify your `name` and `auth` fields - access levels and permissions require admin access via the Data API. ### Authentication - **Required**: Yes (any authenticated user) - **Sudo**: Not required ### Request Body ```json { "name": "Jane Doe", // Optional: Update display name "auth": "jane@example.com" // Optional: Update auth identifier } ``` ### Validation Rules - **name**: 2-100 characters - **auth**: 2-255 characters, must be unique across tenant - **Disallowed fields**: Cannot update `access`, `access_read`, `access_edit`, `access_full` ### Request ```bash curl -X PUT \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "Jane Doe", "auth": "jane@example.com"}' \ https://api.example.com/api/user/me ``` ### Success Response (200) ```json { "success": true, "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Jane Doe", "auth": "jane@example.com", "access": "full", "updated_at": "2025-01-15T11:00:00Z" } } ``` ### Error Responses **400 - Validation Error** ```json { "success": false, "error": "Name must be between 2 and 100 characters", "error_code": "VALIDATION_ERROR", "data": { "field": "name" } } ``` **400 - Disallowed Field** ```json { "success": false, "error": "Cannot update fields: access. Use admin endpoints to modify access levels.", "error_code": "VALIDATION_ERROR", "data": { "disallowed_fields": ["access"] } } ``` **409 - Duplicate Auth** ```json { "success": false, "error": "Auth identifier already exists", "error_code": "AUTH_CONFLICT", "data": { "field": "auth" } } ``` --- ## DELETE /api/user/me Deactivate your own account (soft delete). This sets the `trashed_at` timestamp and prevents future authentication. An administrator can reactivate the account if needed. ### Authentication - **Required**: Yes (any authenticated user) - **Sudo**: Not required ### Request Body ```json { "confirm": true, // Required: Must be true "reason": "Leaving company" // Optional: Reason for audit log } ``` ### Validation Rules - **confirm**: Must be exactly `true` (boolean) - **reason**: Optional string for audit purposes ### Request ```bash curl -X DELETE \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"confirm": true, "reason": "Leaving company"}' \ https://api.example.com/api/user/me ``` ### Success Response (200) ```json { "success": true, "data": { "message": "Account deactivated successfully", "deactivated_at": "2025-01-15T11:30:00Z", "reason": "Leaving company" } } ``` ### Error Responses **400 - Missing Confirmation** ```json { "success": false, "error": "Account deactivation requires explicit confirmation", "error_code": "CONFIRMATION_REQUIRED", "data": { "field": "confirm", "required_value": true } } ``` ### Notes - After deactivation, you cannot authenticate with this account - The account data is preserved (soft delete via `trashed_at`) - An administrator can reactivate the account using the Data API with sudo access - To permanently delete an account, an administrator must use the Data API with sudo and `permanent=true` --- ## Administrative User Management For tenant-wide user management, use the collection and `:id` endpoints with Abbotik auth context that has sudo access: ```bash # 1. Use an administrative Abbotik bearer token to list users curl -X GET \ -H "Authorization: Bearer $ADMIN_TOKEN" \ https://api.example.com/api/user # 2. Create or manage other users curl -X POST \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "New User", "auth": "new@example.com", "access": "edit"}' \ https://api.example.com/api/user ``` --- ## Comparison: User API vs Data API The User API provides self-service operations on the users table without requiring sudo access: | Feature | User API | Data API (`/api/data/users`) | |---------|----------|------------------------------| | **Authentication** | Abbotik bearer token required | Abbotik bearer token required | | **Sudo Required** | ❌ No (for self-service ops) | ✅ Yes (users table has `sudo=true`) | | **Self-Update** | ✅ Can update own profile | ❌ Cannot update without sudo | | **Access Control** | ✅ Cannot change own access level | ❌ No protection (if you have sudo) | | **Fields Allowed** | Only `name` and `auth` | All fields (with sudo) | | **Use Case** | Self-service profile management | Admin user management | ### When to Use User API - Users updating their own profiles - Users deactivating their own accounts - Self-service operations without admin involvement ### When to Use Data API - Admins creating new users - Admins modifying user access levels - Admins managing access_read/access_edit/access_full arrays - Any operation on other users' records --- ## Error Codes | Code | Status | Description | |------|--------|-------------| | `VALIDATION_ERROR` | 400 | Invalid input data (name/auth length, type errors) | | `CONFIRMATION_REQUIRED` | 400 | Account deactivation missing `confirm: true` | | `AUTH_CONFLICT` | 409 | Auth identifier already exists | --- ## Security Model ### Self-Service Security The User API implements the `withSelfServiceSudo()` pattern: 1. Route handlers wrap database operations with `withSelfServiceSudo(context, async () => {...})` 2. This temporarily sets the `as_sudo` flag in the request context 3. The sudo validator observer checks both `is_sudo` (JWT) and `as_sudo` (self-service flag) 4. Database operations proceed as if user has sudo, but only for their own record 5. Business logic ensures users can only modify their own profile ### Protection Against Privilege Escalation - Users cannot update their own `access` field - Attempting to modify `access` via `PUT /api/user/me` returns 400 error - Access level changes require admin access via Data API with sudo - The `as_sudo` flag is request-scoped and automatically cleaned up ### Audit Trail All operations are logged through the standard observer pipeline: - Profile updates logged with timestamp and user ID - Account deactivations logged with optional reason - All changes tracked via `updated_at` timestamp