feat: add list-my-workspaces operation and document IAM in API specs (#961)

Add a new `list-my-workspaces` operation so non-admin users can
discover which workspaces they have access to.  For OSS IAM, regular
users see their home workspace; admins see all workspaces.

Also add the full IAM service to both OpenAPI and AsyncAPI specs —
it was previously undocumented despite being a first-class service
on both HTTP and WebSocket interfaces.
This commit is contained in:
cybermaggedon 2026-05-29 19:17:37 +01:00 committed by GitHub
parent 2a10e16c02
commit 6564adad80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 689 additions and 2 deletions

View file

@ -0,0 +1,21 @@
type: object
description: |
API key creation fields. Used with `create-api-key`.
properties:
user_id:
type: string
description: User to create the key for.
examples:
- usr_abc123
name:
type: string
description: Operator-facing label for the key (e.g. "laptop", "CI").
examples:
- laptop
expires:
type: string
description: |
Optional expiry timestamp in ISO-8601 UTC. Empty string or
omitted means the key does not expire.
examples:
- "2027-01-01T00:00:00Z"

View file

@ -0,0 +1,38 @@
type: object
description: API key record returned by IAM operations.
properties:
id:
type: string
description: Key identifier.
examples:
- key_xyz789
user_id:
type: string
description: Owning user identifier.
examples:
- usr_abc123
name:
type: string
description: Operator-facing label.
examples:
- laptop
prefix:
type: string
description: |
First 4 characters of the plaintext key, for identification
in listings. Never enough to reconstruct the key.
examples:
- tg_a
expires:
type: string
description: Expiry timestamp (ISO-8601 UTC). Empty if no expiry.
examples:
- "2027-01-01T00:00:00Z"
created:
type: string
description: Creation timestamp (ISO-8601 UTC).
examples:
- "2026-01-15T10:30:00Z"
last_used:
type: string
description: Last-used timestamp (ISO-8601 UTC). Empty if never used.

View file

@ -0,0 +1,106 @@
type: object
description: |
IAM service request.
The IAM service is a **global service** — it operates at system level,
not scoped to a specific workspace. All operations are dispatched via
the `operation` field.
Some operations require admin capabilities; others (like `whoami` and
`list-my-workspaces`) are available to any authenticated user. See
the capability vocabulary for details.
The `actor` field is injected by the gateway and cannot be set by
the client. It identifies the authenticated caller.
required:
- operation
properties:
operation:
type: string
enum:
- whoami
- list-my-workspaces
- create-user
- list-users
- get-user
- update-user
- disable-user
- enable-user
- delete-user
- create-workspace
- list-workspaces
- get-workspace
- update-workspace
- disable-workspace
- create-api-key
- list-api-keys
- revoke-api-key
- reset-password
- rotate-signing-key
description: |
Operation to perform.
**Any authenticated user:**
- `whoami`: Return the caller's own user record
- `list-my-workspaces`: List workspaces the caller has access to
**User management (requires `users:read`/`users:write`/`users:admin`):**
- `create-user`: Create a new user in a workspace
- `list-users`: List users (optionally filtered by workspace)
- `get-user`: Get a specific user record
- `update-user`: Update user fields (name, email, roles, enabled)
- `disable-user`: Soft-disable a user and revoke their API keys
- `enable-user`: Re-enable a previously disabled user
- `delete-user`: Hard-delete a user and their API keys
**Workspace management (requires `workspaces:admin`):**
- `create-workspace`: Create a new workspace
- `list-workspaces`: List all workspaces (admin view)
- `get-workspace`: Get a specific workspace record
- `update-workspace`: Update workspace name or enabled state
- `disable-workspace`: Disable workspace and all its users
**API key management (requires `keys:self` or `keys:admin`):**
- `create-api-key`: Create an API key for a user
- `list-api-keys`: List API keys for a user
- `revoke-api-key`: Revoke (delete) an API key
**Password management:**
- `reset-password`: Admin-initiated password reset (requires `users:admin`)
**System (requires `iam:admin`):**
- `rotate-signing-key`: Rotate the JWT signing key
workspace:
type: string
description: |
Workspace scope. Required on workspace-scoped operations
(e.g. `create-user`). Acts as an optional integrity check on
operations that target a user or key — when supplied, the target's
home workspace must match.
Omitted for system-level operations (`list-workspaces`,
`rotate-signing-key`) and for identity-resolution operations
(`whoami`, `list-my-workspaces`).
examples:
- default
- production
user_id:
type: string
description: |
Target user identifier. Required for operations that act on a
specific user: `get-user`, `update-user`, `disable-user`,
`enable-user`, `delete-user`, `reset-password`, `list-api-keys`.
examples:
- usr_abc123
user:
$ref: './UserInput.yaml'
workspace_record:
$ref: './WorkspaceInput.yaml'
key:
$ref: './ApiKeyInput.yaml'
key_id:
type: string
description: |
API key identifier. Required for `revoke-api-key`.
examples:
- key_xyz789

View file

@ -0,0 +1,51 @@
type: object
description: |
IAM service response. Fields are populated depending on the
operation that was invoked.
properties:
user:
$ref: './UserRecord.yaml'
users:
type: array
description: List of user records (populated by `list-users`).
items:
$ref: './UserRecord.yaml'
workspace:
$ref: './WorkspaceRecord.yaml'
workspaces:
type: array
description: |
List of workspace records (populated by `list-workspaces` and
`list-my-workspaces`).
items:
$ref: './WorkspaceRecord.yaml'
api_key_plaintext:
type: string
description: |
Plaintext API key. Returned **once** by `create-api-key`.
Never populated on any other operation. The caller must
capture this value — it cannot be retrieved again.
api_key:
$ref: './ApiKeyRecord.yaml'
api_keys:
type: array
description: List of API key records (populated by `list-api-keys`).
items:
$ref: './ApiKeyRecord.yaml'
temporary_password:
type: string
description: |
Temporary password returned once by `reset-password`.
error:
type: object
description: Error details (present on failure).
properties:
type:
type: string
description: |
Error type. One of: `invalid-argument`, `not-found`,
`duplicate`, `auth-failed`, `weak-password`, `disabled`,
`operation-not-permitted`, `internal-error`.
message:
type: string
description: Human-readable error description (not surfaced to end users).

View file

@ -0,0 +1,42 @@
type: object
description: |
User creation/update fields. Used with `create-user` and `update-user`.
The `password` field is only accepted on `create-user`.
properties:
username:
type: string
description: Login username. Unique within a workspace.
examples:
- alice
name:
type: string
description: Display name.
examples:
- Alice Smith
email:
type: string
description: Email address.
examples:
- alice@example.com
password:
type: string
description: |
Initial password. Only accepted on `create-user`; rejected on
`update-user`. Use `reset-password` or `change-password` to
modify passwords.
roles:
type: array
items:
type: string
description: |
Roles to assign. Open-source roles: `reader`, `writer`, `admin`.
examples:
- - reader
enabled:
type: boolean
description: Whether the user is enabled.
default: true
must_change_password:
type: boolean
description: Force password change on next login.
default: false

View file

@ -0,0 +1,46 @@
type: object
description: User record returned by IAM operations.
properties:
id:
type: string
description: Unique user identifier.
examples:
- usr_abc123
workspace:
type: string
description: User's home workspace.
examples:
- default
username:
type: string
description: Login username (unique within workspace).
examples:
- alice
name:
type: string
description: Display name.
examples:
- Alice Smith
email:
type: string
description: Email address.
examples:
- alice@example.com
roles:
type: array
items:
type: string
description: Assigned roles.
examples:
- - reader
enabled:
type: boolean
description: Whether the user is enabled.
must_change_password:
type: boolean
description: Whether the user must change password on next login.
created:
type: string
description: Creation timestamp (ISO-8601 UTC).
examples:
- "2026-01-15T10:30:00Z"

View file

@ -0,0 +1,23 @@
type: object
description: |
Workspace creation/update fields. Used with `create-workspace` and
`update-workspace`.
properties:
id:
type: string
description: |
Workspace identifier. Required for all workspace operations.
Immutable after creation.
examples:
- default
- production
name:
type: string
description: Human-readable workspace name.
examples:
- Default Workspace
- Production
enabled:
type: boolean
description: Whether the workspace is enabled.
default: true

View file

@ -0,0 +1,21 @@
type: object
description: Workspace record returned by IAM operations.
properties:
id:
type: string
description: Workspace identifier.
examples:
- default
name:
type: string
description: Human-readable workspace name.
examples:
- Default Workspace
enabled:
type: boolean
description: Whether the workspace is enabled.
created:
type: string
description: Creation timestamp (ISO-8601 UTC).
examples:
- "2026-01-01T00:00:00Z"

View file

@ -89,6 +89,8 @@ security:
- bearerAuth: []
tags:
- name: IAM
description: Identity and access management (global)
- name: Config
description: Configuration management (workspace-scoped)
- name: Flow
@ -109,6 +111,11 @@ tags:
description: System metrics and monitoring
paths:
# Global services
/api/v1/iam:
$ref: './paths/iam.yaml'
# Workspace-scoped services
/api/v1/config:
$ref: './paths/config.yaml'
/api/v1/flow:

206
specs/api/paths/iam.yaml Normal file
View file

@ -0,0 +1,206 @@
post:
tags:
- IAM
summary: IAM service (global)
description: |
Identity and access management service.
This is a **global service** — it operates at system level, not
scoped to a specific workspace. The `workspace` field in the
request body is used as a scope filter or integrity check on
certain operations, not as an addressing component.
## Authentication
Most operations require a bearer token. The gateway resolves the
token to an authenticated identity and injects the `actor` field
(the caller's user ID) into the request. Clients cannot set
`actor` — the gateway overwrites it.
## Operations by Capability
### Any authenticated user
- `whoami`: Return the caller's own user record
- `list-my-workspaces`: List workspaces the caller has access to.
For open-source IAM: returns the caller's home workspace, or all
workspaces if the caller has the `admin` role.
### User management (`users:read` / `users:write` / `users:admin`)
- `create-user`: Create a new user in a workspace
- `list-users`: List users, optionally filtered by workspace
- `get-user`: Get a user record by ID
- `update-user`: Update user fields (name, email, roles, enabled)
- `disable-user`: Soft-disable a user and revoke their API keys
- `enable-user`: Re-enable a disabled user
- `delete-user`: Hard-delete a user and their API keys
### Workspace management (`workspaces:admin`)
- `create-workspace`: Create a new workspace
- `list-workspaces`: List all workspaces (admin view)
- `get-workspace`: Get a workspace record
- `update-workspace`: Update workspace name or enabled state
- `disable-workspace`: Disable a workspace and all its users
### API key management (`keys:self` / `keys:admin`)
- `create-api-key`: Create an API key (plaintext returned once)
- `list-api-keys`: List API keys for a user
- `revoke-api-key`: Revoke (delete) an API key
### Password management (`users:admin`)
- `reset-password`: Admin-initiated password reset (returns temporary password)
### System (`iam:admin`)
- `rotate-signing-key`: Rotate the JWT signing key
operationId: iamService
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../components/schemas/iam/IamRequest.yaml'
examples:
whoami:
summary: Get the caller's own user record
value:
operation: whoami
listMyWorkspaces:
summary: List workspaces the caller has access to
value:
operation: list-my-workspaces
createUser:
summary: Create a new user
value:
operation: create-user
workspace: default
user:
username: alice
name: Alice Smith
email: alice@example.com
password: changeme123
roles:
- writer
listUsers:
summary: List users in a workspace
value:
operation: list-users
workspace: default
getUser:
summary: Get a specific user
value:
operation: get-user
user_id: usr_abc123
updateUser:
summary: Update a user's roles
value:
operation: update-user
user_id: usr_abc123
user:
roles:
- admin
disableUser:
summary: Disable a user
value:
operation: disable-user
user_id: usr_abc123
createWorkspace:
summary: Create a workspace
value:
operation: create-workspace
workspace_record:
id: production
name: Production Workspace
listWorkspaces:
summary: List all workspaces (admin)
value:
operation: list-workspaces
createApiKey:
summary: Create an API key
value:
operation: create-api-key
key:
user_id: usr_abc123
name: laptop
expires: "2027-01-01T00:00:00Z"
listApiKeys:
summary: List a user's API keys
value:
operation: list-api-keys
user_id: usr_abc123
revokeApiKey:
summary: Revoke an API key
value:
operation: revoke-api-key
key_id: key_xyz789
resetPassword:
summary: Admin-initiated password reset
value:
operation: reset-password
user_id: usr_abc123
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '../components/schemas/iam/IamResponse.yaml'
examples:
whoami:
summary: Caller's user record
value:
user:
id: usr_abc123
workspace: default
username: alice
name: Alice Smith
email: alice@example.com
roles:
- writer
enabled: true
must_change_password: false
created: "2026-01-15T10:30:00Z"
listMyWorkspaces:
summary: Workspaces the caller can access
value:
workspaces:
- id: default
name: Default Workspace
enabled: true
created: "2026-01-01T00:00:00Z"
listUsers:
summary: Users in a workspace
value:
users:
- id: usr_abc123
workspace: default
username: alice
name: Alice Smith
roles:
- writer
enabled: true
created: "2026-01-15T10:30:00Z"
createApiKey:
summary: New API key (plaintext returned once)
value:
api_key_plaintext: tg_aBcDeFgHiJkLmNoPqRsTuVwXyZ
api_key:
id: key_xyz789
user_id: usr_abc123
name: laptop
prefix: tg_a
expires: "2027-01-01T00:00:00Z"
created: "2026-05-29T14:00:00Z"
resetPassword:
summary: Temporary password (returned once)
value:
temporary_password: tmp_xK9mQ2pL
'400':
description: Bad request (unknown operation, missing required fields)
'401':
$ref: '../components/responses/Unauthorized.yaml'
'403':
description: Access denied (insufficient capabilities)
'500':
$ref: '../components/responses/Error.yaml'