Lewati ke konten utama
Versi: 0.0.1 (belum rilis)

Users, roles, sessions, and audit API

Every route on this page requires a session cookie. POST, PATCH, and DELETE requests also require the matching X-CSRF-Token. These are global administrative resources: proxy-host scopes do not authorize them.

Route table

MethodPathPermissionSuccessKey failures
GET/api/usersusers.manage200 User[]401 unauthorized, 403 forbidden, 500 database_error
POST/api/usersusers.manage201 User400 invalid_input, 401, 403, 409 duplicate_email, 500
PATCH/api/users/{id}users.manage200 User400, 401, 403, 404, 409 last_admin, 500
DELETE/api/users/{id}users.manage204401, 403 self_mutation, 404, 409 last_admin, 500
POST/api/users/{id}/sessions/revokesessions.revoke200 revoked-count object400, 401, 403, 404, 500
GET/api/rolesroles.manage200 RoleDetail[]401, 403, 500 database_error
POST/api/rolesroles.manage201 RoleDetail400 invalid_input, 401, 403, 409 conflict, 500
GET/api/roles/{id}roles.manage200 RoleDetail400, 401, 403, 404 not_found, 500
PATCH/api/roles/{id}roles.manage200 RoleDetail400, 401, 403, 404, 409 conflict, 500
DELETE/api/roles/{id}roles.manage204400, 401, 403, 404, 409 conflict, 500
GET/api/audit-logsaudit_logs.read200 AuditLogPage400 invalid_input, 401, 403, 500 database_error

204 from user or role deletion deliberately has no JSON body. Successful user changes publish users.changed; user role/disabled changes and session revocation publish sessions.changed; role changes publish roles.changed.

Users and session revocation

Users are serialized as:

{
"id": 8,
"email": "operator@example.test",
"role": "operator",
"created_at": "2026-08-17T00:00:00Z",
"disabled": false,
"preferred_locale": null
}

Create a user with exactly email, password, and role. Email is trimmed and lowercased; password must be at least 12 characters; role must name an existing built-in or custom role. A duplicate email returns 409 duplicate_email. Passwords are accepted only for creation and never appear in responses or audit details.

{
"email": "operator@example.test",
"password": "test-only-password-at-least-12",
"role": "operator"
}

Patch a user with one or both of role and disabled; an empty patch is 400 invalid_input. An administrator cannot disable themself (403 self_mutation), delete themself (403 self_mutation), or revoke their own sessions (403 self_mutation). The server rejects a role or disabled-state change, or a deletion, that would remove the last enabled admin, with 409 last_admin. This invariant applies atomically to combined user updates.

POST /api/users/{id}/sessions/revoke revokes every session for a different, existing target and returns:

{"revoked":2}

The target's next session-authenticated request returns 401. Revocation does not expose session identifiers or token hashes.

Built-in roles, custom permissions, and scopes

The system roles are admin, operator, and viewer. Built-in role behavior is fixed: admin has every built-in permission; operator reads and writes proxy hosts and certificates and reads audit history; viewer reads proxy hosts, certificates, and audit history. Built-in roles cannot be patched or deleted (409 conflict).

GET /api/roles returns a RoleDetail for every list item. The collection and the POST, item GET, and PATCH responses all include id, slug, name, description, system_managed, permissions, and scopes:

{
"id": 17,
"slug": "host-operator",
"name": "Host operator",
"description": "Test-only scoped role",
"system_managed": false,
"permissions": ["proxy_hosts.read", "proxy_hosts.write"],
"scopes": [
{"permission":"proxy_hosts.read","proxy_host_ids":[42]},
{"permission":"proxy_hosts.write","proxy_host_ids":[42]}
]
}

Create a role with slug, name, optional description, optional permissions, and optional scopes. The slug is normalized to lowercase ASCII words joined by hyphens and must be nonempty and at most 64 characters. The custom-role write contract accepts these permission keys:

proxy_hosts.read, proxy_hosts.write, certificates.read, certificates.write, users.manage, roles.manage, audit_logs.read, audit_logs.export, system.settings.manage, sessions.revoke, and bot_protection.manage.

A scope object has exactly permission and proxy_host_ids; scope permissions are only proxy_hosts.read or proxy_hosts.write, and each listed host ID must exist. Duplicate, unknown-host, or invalid scope assignments return 400 invalid_input. A duplicate role slug returns 409 conflict. Patching a custom role accepts any subset of name, description, permissions, and scopes; setting permissions or scopes replaces that collection.

The complete permission vocabulary also contains ai_advisor.read, ai_advisor.request, ai_advisor.approve, plugins.read, and plugins.manage; their API endpoints are documented separately. Do not infer that a proxy-host scope applies to them or to any global administrative route.

Audit logs

GET /api/audit-logs accepts these optional query parameters:

ParameterContract
eventExact event filter; empty is ignored.
actor_idNon-negative numeric user ID; empty is ignored.
fromRFC 3339 timestamp inclusive lower bound; normalized to UTC.
toRFC 3339 timestamp inclusive upper bound; normalized to UTC.
qCase/database text match against event or details; empty is ignored.
pageInteger, default 1, minimum 1.
page_sizeInteger, default 25, range 1--100.

Malformed numbers or timestamps return 400 invalid_input. Results are ordered by created_at descending and then id descending. The response fields are exactly items, page, page_size, and total; every item contains id, actor, event, details, and created_at.

Using the session cookie from the API overview:

curl --fail-with-body -k -b "$cookie_jar" \
'https://127.0.0.1:8081/api/audit-logs?event=user_created&page=1&page_size=25'
{
"items": [
{
"id": 99,
"actor": "admin@example.test",
"event": "user_created",
"details": "target_user_id=8;outcome=success",
"created_at": "2026-08-17T00:00:00Z"
}
],
"page": 1,
"page_size": 25,
"total": 1
}

actor is the current email when its user still exists, deleted-user for a deleted user ID, or system for a system action. Audit output redacts JSON fields and text values named or containing password, token, secret, private-key/private_key, credential, or request-body/request_body. The redacted value is [REDACTED]; submitted request bodies, password hashes, and session token hashes are not exposed. Audit publication is best-effort and does not change the mutation's HTTP outcome.

Source pointers

Route handlers: bearust/src/control_plane/mod.rs; RBAC roles, permissions, and resource contexts: bearust/src/control_plane/rbac.rs; payload models: bearust/src/control_plane/models.rs; audit query, actor labels, ordering, and redaction: bearust/src/control_plane/repository.rs. Integration coverage is in tests/control_plane_users.rs, tests/control_plane_roles.rs, and tests/control_plane_audit.rs.