Fix OpenAPI spec parsing errors in Swagger UI
- Replace type: [string, "null"] array syntax with
type: string + nullable: true on AuditEvent.actor_id,
AuditEvent.target_id, PolicyRule.not_before, and
PolicyRule.expires_at; Swagger UI 5 cannot parse the
JSON Schema array form
- Add missing username field to /v1/token/validate response
schema (added to handler in d6cc827 but never synced)
- Add missing GET /v1/pgcreds endpoint to spec
- Sync web/static/openapi.yaml (served file) with root;
the static copy was many commits out of date, missing
all policy/tags schemas and endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
82
openapi.yaml
82
openapi.yaml
@@ -100,13 +100,15 @@ components:
|
||||
format: date-time
|
||||
example: "2026-03-11T09:01:23Z"
|
||||
actor_id:
|
||||
type: [string, "null"]
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: UUID of the account that performed the action. Null for bootstrap events.
|
||||
example: 550e8400-e29b-41d4-a716-446655440000
|
||||
target_id:
|
||||
type: [string, "null"]
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: UUID of the affected account, if applicable.
|
||||
ip_address:
|
||||
type: string
|
||||
@@ -205,16 +207,18 @@ components:
|
||||
type: boolean
|
||||
example: true
|
||||
not_before:
|
||||
type: [string, "null"]
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: |
|
||||
Earliest time the rule becomes active. NULL means no constraint
|
||||
(always active). Rules where `not_before > now()` are skipped
|
||||
during evaluation.
|
||||
example: "2026-04-01T00:00:00Z"
|
||||
expires_at:
|
||||
type: [string, "null"]
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: |
|
||||
Time after which the rule is no longer active. NULL means no
|
||||
constraint (never expires). Rules where expires_at is in the past
|
||||
@@ -602,6 +606,10 @@ paths:
|
||||
format: uuid
|
||||
description: Subject (account UUID). Present when valid=true.
|
||||
example: 550e8400-e29b-41d4-a716-446655440000
|
||||
username:
|
||||
type: string
|
||||
description: Account username. Present when valid=true and the account exists.
|
||||
example: alice
|
||||
roles:
|
||||
type: array
|
||||
items:
|
||||
@@ -615,7 +623,7 @@ paths:
|
||||
example: "2026-04-10T12:34:56Z"
|
||||
examples:
|
||||
valid:
|
||||
value: {valid: true, sub: "550e8400-...", roles: [editor], expires_at: "2026-04-10T12:34:56Z"}
|
||||
value: {valid: true, sub: "550e8400-...", username: alice, roles: [editor], expires_at: "2026-04-10T12:34:56Z"}
|
||||
invalid:
|
||||
value: {valid: false}
|
||||
"429":
|
||||
@@ -1263,6 +1271,70 @@ paths:
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
/v1/pgcreds:
|
||||
get:
|
||||
summary: List accessible Postgres credentials
|
||||
description: |
|
||||
Return all Postgres credentials accessible to the authenticated account:
|
||||
credentials owned by the account plus any explicitly granted by an admin.
|
||||
|
||||
The `id` field is the credential record ID; use it together with the
|
||||
`service_account_id` to fetch full details via
|
||||
`GET /v1/accounts/{id}/pgcreds`. Passwords are **not** returned by this
|
||||
endpoint.
|
||||
operationId: listAccessiblePGCreds
|
||||
tags: [Admin — Credentials]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Array of accessible Postgres credential summaries.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, service_account_id, host, port, database, username, created_at, updated_at]
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: Credential record ID.
|
||||
example: 7
|
||||
service_account_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the system account that owns these credentials.
|
||||
example: 550e8400-e29b-41d4-a716-446655440000
|
||||
service_account_name:
|
||||
type: string
|
||||
description: Username of the owning system account (omitted if unavailable).
|
||||
example: payments-api
|
||||
host:
|
||||
type: string
|
||||
example: db.example.com
|
||||
port:
|
||||
type: integer
|
||||
example: 5432
|
||||
database:
|
||||
type: string
|
||||
example: mydb
|
||||
username:
|
||||
type: string
|
||||
example: myuser
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-03-11T09:00:00Z"
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-03-11T09:00:00Z"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"503":
|
||||
$ref: "#/components/responses/VaultSealed"
|
||||
|
||||
/v1/audit:
|
||||
get:
|
||||
summary: Query audit log (admin)
|
||||
|
||||
@@ -118,6 +118,121 @@ components:
|
||||
description: JSON blob with event-specific metadata. Never contains credentials.
|
||||
example: '{"jti":"f47ac10b-..."}'
|
||||
|
||||
TagsResponse:
|
||||
type: object
|
||||
required: [tags]
|
||||
properties:
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Current tag list for the account.
|
||||
example: ["env:production", "svc:payments-api"]
|
||||
|
||||
RuleBody:
|
||||
type: object
|
||||
required: [effect]
|
||||
description: |
|
||||
The match conditions and effect of a policy rule. All fields except
|
||||
`effect` are optional; an omitted field acts as a wildcard.
|
||||
properties:
|
||||
effect:
|
||||
type: string
|
||||
enum: [allow, deny]
|
||||
example: allow
|
||||
roles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Subject must have at least one of these roles.
|
||||
example: ["svc:payments-api"]
|
||||
account_types:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum: [human, system]
|
||||
description: Subject account type must be one of these.
|
||||
example: ["system"]
|
||||
subject_uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Match only this specific subject UUID.
|
||||
example: 550e8400-e29b-41d4-a716-446655440000
|
||||
actions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: |
|
||||
One of the defined action constants, e.g. `pgcreds:read`,
|
||||
`accounts:list`. Subject action must be in this list.
|
||||
example: ["pgcreds:read"]
|
||||
resource_type:
|
||||
type: string
|
||||
description: Resource type the rule applies to.
|
||||
example: pgcreds
|
||||
owner_matches_subject:
|
||||
type: boolean
|
||||
description: Resource owner UUID must equal the subject UUID.
|
||||
example: true
|
||||
service_names:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Resource service name must be one of these.
|
||||
example: ["payments-api"]
|
||||
required_tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Resource must have ALL of these tags.
|
||||
example: ["env:staging"]
|
||||
|
||||
PolicyRule:
|
||||
type: object
|
||||
required: [id, priority, description, rule, enabled, created_at, updated_at]
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
example: 1
|
||||
priority:
|
||||
type: integer
|
||||
description: Lower number = evaluated first.
|
||||
example: 100
|
||||
description:
|
||||
type: string
|
||||
example: Allow payments-api to read its own pgcreds
|
||||
rule:
|
||||
$ref: "#/components/schemas/RuleBody"
|
||||
enabled:
|
||||
type: boolean
|
||||
example: true
|
||||
not_before:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: |
|
||||
Earliest time the rule becomes active. NULL means no constraint
|
||||
(always active). Rules where `not_before > now()` are skipped
|
||||
during evaluation.
|
||||
example: "2026-04-01T00:00:00Z"
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: |
|
||||
Time after which the rule is no longer active. NULL means no
|
||||
constraint (never expires). Rules where expires_at is in the past
|
||||
are skipped during evaluation.
|
||||
example: "2026-06-01T00:00:00Z"
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-03-11T09:00:00Z"
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-03-11T09:00:00Z"
|
||||
|
||||
PGCreds:
|
||||
type: object
|
||||
required: [host, port, database, username, password]
|
||||
@@ -192,6 +307,18 @@ components:
|
||||
error: rate limit exceeded
|
||||
code: rate_limited
|
||||
|
||||
VaultSealed:
|
||||
description: |
|
||||
The vault is sealed. The server is running but has no key material.
|
||||
Unseal via `POST /v1/vault/unseal` before retrying.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: vault is sealed
|
||||
code: vault_sealed
|
||||
|
||||
paths:
|
||||
|
||||
# ── Public ────────────────────────────────────────────────────────────────
|
||||
@@ -200,14 +327,16 @@ paths:
|
||||
get:
|
||||
summary: Health check
|
||||
description: |
|
||||
Returns `{"status":"ok"}` if the server is running and the vault is
|
||||
unsealed, or `{"status":"sealed"}` if the vault is sealed.
|
||||
No auth required.
|
||||
Returns server health status. Always returns HTTP 200, even when the
|
||||
vault is sealed. No auth required.
|
||||
|
||||
When the vault is sealed, `status` is `"sealed"` and most other
|
||||
endpoints return 503. When healthy, `status` is `"ok"`.
|
||||
operationId: getHealth
|
||||
tags: [Public]
|
||||
responses:
|
||||
"200":
|
||||
description: Server is healthy (may be sealed).
|
||||
description: Server is running (check `status` for sealed state).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -218,84 +347,6 @@ paths:
|
||||
enum: [ok, sealed]
|
||||
example: ok
|
||||
|
||||
/v1/vault/status:
|
||||
get:
|
||||
summary: Vault seal status
|
||||
description: Returns `{"sealed": true}` or `{"sealed": false}`. No auth required.
|
||||
operationId: getVaultStatus
|
||||
tags: [Vault]
|
||||
responses:
|
||||
"200":
|
||||
description: Current seal state.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
sealed:
|
||||
type: boolean
|
||||
|
||||
/v1/vault/unseal:
|
||||
post:
|
||||
summary: Unseal the vault
|
||||
description: |
|
||||
Accepts a passphrase, derives the master key, and unseals the vault.
|
||||
Rate-limited to 3 requests per second, burst of 5.
|
||||
No auth required (the vault is sealed, so no tokens can be validated).
|
||||
operationId: unsealVault
|
||||
tags: [Vault]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [passphrase]
|
||||
properties:
|
||||
passphrase:
|
||||
type: string
|
||||
description: Master passphrase for key derivation.
|
||||
responses:
|
||||
"200":
|
||||
description: Vault unsealed successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: unsealed
|
||||
"401":
|
||||
description: Unseal failed (wrong passphrase).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/v1/vault/seal:
|
||||
post:
|
||||
summary: Seal the vault
|
||||
description: |
|
||||
Seals the vault, zeroing all key material in memory.
|
||||
Requires admin authentication. The caller's token becomes invalid
|
||||
after sealing.
|
||||
operationId: sealVault
|
||||
tags: [Vault]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Vault sealed successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: sealed
|
||||
|
||||
/v1/keys/public:
|
||||
get:
|
||||
summary: Ed25519 public key (JWK)
|
||||
@@ -336,6 +387,121 @@ paths:
|
||||
description: Base64url-encoded public key bytes.
|
||||
example: 11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo
|
||||
|
||||
/v1/vault/status:
|
||||
get:
|
||||
summary: Vault seal status
|
||||
description: |
|
||||
Returns whether the vault is currently sealed. Always accessible,
|
||||
even when sealed. No auth required.
|
||||
|
||||
Clients should poll this after startup or after a 503 `vault_sealed`
|
||||
response to determine when to attempt an unseal.
|
||||
operationId: getVaultStatus
|
||||
tags: [Public]
|
||||
responses:
|
||||
"200":
|
||||
description: Current vault seal state.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [sealed]
|
||||
properties:
|
||||
sealed:
|
||||
type: boolean
|
||||
example: false
|
||||
|
||||
/v1/vault/unseal:
|
||||
post:
|
||||
summary: Unseal the vault
|
||||
description: |
|
||||
Provide the master passphrase to derive the encryption key, decrypt
|
||||
the Ed25519 signing key, and unseal the vault. Once unsealed, all
|
||||
other endpoints become available.
|
||||
|
||||
Rate limited to 3 requests per second per IP (burst 5) to limit
|
||||
brute-force attempts against the passphrase.
|
||||
|
||||
The passphrase is never logged. A generic `"unseal failed"` error
|
||||
is returned for any failure (wrong passphrase, vault already unsealed
|
||||
mid-flight, etc.) to avoid leaking information.
|
||||
operationId: unsealVault
|
||||
tags: [Public]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [passphrase]
|
||||
properties:
|
||||
passphrase:
|
||||
type: string
|
||||
description: Master passphrase used to derive the encryption key.
|
||||
example: correct-horse-battery-staple
|
||||
responses:
|
||||
"200":
|
||||
description: Vault unsealed (or was already unsealed).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum: [unsealed, already unsealed]
|
||||
example: unsealed
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
description: Wrong passphrase or key decryption failure.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: unseal failed
|
||||
code: unauthorized
|
||||
"429":
|
||||
$ref: "#/components/responses/RateLimited"
|
||||
|
||||
/v1/vault/seal:
|
||||
post:
|
||||
summary: Seal the vault (admin)
|
||||
description: |
|
||||
Zero all key material in memory and transition the server to the
|
||||
sealed state. After this call:
|
||||
|
||||
- All subsequent requests (except health, vault status, and unseal)
|
||||
return 503 `vault_sealed`.
|
||||
- The caller's own JWT is immediately invalidated because the public
|
||||
key needed to verify it is no longer held in memory.
|
||||
- The server can be unsealed again via `POST /v1/vault/unseal`.
|
||||
|
||||
This is an emergency operation. Use it to protect key material if a
|
||||
compromise is suspected. It does **not** restart the server or wipe
|
||||
the database.
|
||||
operationId: sealVault
|
||||
tags: [Admin — Vault]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Vault sealed (or was already sealed).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum: [sealed, already sealed]
|
||||
example: sealed
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
/v1/auth/login:
|
||||
post:
|
||||
summary: Login
|
||||
@@ -440,6 +606,10 @@ paths:
|
||||
format: uuid
|
||||
description: Subject (account UUID). Present when valid=true.
|
||||
example: 550e8400-e29b-41d4-a716-446655440000
|
||||
username:
|
||||
type: string
|
||||
description: Account username. Present when valid=true and the account exists.
|
||||
example: alice
|
||||
roles:
|
||||
type: array
|
||||
items:
|
||||
@@ -453,7 +623,7 @@ paths:
|
||||
example: "2026-04-10T12:34:56Z"
|
||||
examples:
|
||||
valid:
|
||||
value: {valid: true, sub: "550e8400-...", roles: [editor], expires_at: "2026-04-10T12:34:56Z"}
|
||||
value: {valid: true, sub: "550e8400-...", username: alice, roles: [editor], expires_at: "2026-04-10T12:34:56Z"}
|
||||
invalid:
|
||||
value: {valid: false}
|
||||
"429":
|
||||
@@ -578,6 +748,68 @@ paths:
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
|
||||
/v1/auth/password:
|
||||
put:
|
||||
summary: Change own password (self-service)
|
||||
description: |
|
||||
Change the password of the currently authenticated human account.
|
||||
The caller must supply the correct `current_password` to prevent
|
||||
token-theft attacks: possession of a valid JWT alone is not sufficient.
|
||||
|
||||
On success:
|
||||
- The stored Argon2id hash is replaced with the new password hash.
|
||||
- All active sessions *except* the caller's current token are revoked.
|
||||
- The lockout failure counter is cleared.
|
||||
|
||||
On failure (wrong current password):
|
||||
- A login failure is recorded against the account, subject to the
|
||||
same lockout rules as `POST /v1/auth/login`.
|
||||
|
||||
Only applies to human accounts. System accounts have no password.
|
||||
operationId: changePassword
|
||||
tags: [Auth]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [current_password, new_password]
|
||||
properties:
|
||||
current_password:
|
||||
type: string
|
||||
description: The account's current password (required for verification).
|
||||
example: old-s3cr3t
|
||||
new_password:
|
||||
type: string
|
||||
description: The new password. Minimum 12 characters.
|
||||
example: new-s3cr3t-long
|
||||
responses:
|
||||
"204":
|
||||
description: Password changed. Other active sessions revoked.
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
description: Current password is incorrect.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: current password is incorrect
|
||||
code: unauthorized
|
||||
"429":
|
||||
description: Account temporarily locked due to too many failed attempts.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: account temporarily locked
|
||||
code: account_locked
|
||||
|
||||
# ── Admin ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/v1/auth/totp:
|
||||
@@ -911,6 +1143,76 @@ paths:
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
post:
|
||||
summary: Grant a role to an account (admin)
|
||||
description: |
|
||||
Add a single role to an account's role set. If the role already exists,
|
||||
this is a no-op. Roles take effect in the **next** token issued or
|
||||
renewed; existing tokens continue to carry the roles embedded at
|
||||
issuance time.
|
||||
operationId: grantRole
|
||||
tags: [Admin — Accounts]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [role]
|
||||
properties:
|
||||
role:
|
||||
type: string
|
||||
example: editor
|
||||
responses:
|
||||
"204":
|
||||
description: Role granted.
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
/v1/accounts/{id}/roles/{role}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: 550e8400-e29b-41d4-a716-446655440000
|
||||
- name: role
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: editor
|
||||
|
||||
delete:
|
||||
summary: Revoke a role from an account (admin)
|
||||
description: |
|
||||
Remove a single role from an account's role set. Roles take effect in
|
||||
the **next** token issued or renewed; existing tokens continue to carry
|
||||
the roles embedded at issuance time.
|
||||
operationId: revokeRole
|
||||
tags: [Admin — Accounts]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"204":
|
||||
description: Role revoked.
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
/v1/accounts/{id}/pgcreds:
|
||||
parameters:
|
||||
- name: id
|
||||
@@ -969,6 +1271,70 @@ paths:
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
/v1/pgcreds:
|
||||
get:
|
||||
summary: List accessible Postgres credentials
|
||||
description: |
|
||||
Return all Postgres credentials accessible to the authenticated account:
|
||||
credentials owned by the account plus any explicitly granted by an admin.
|
||||
|
||||
The `id` field is the credential record ID; use it together with the
|
||||
`service_account_id` to fetch full details via
|
||||
`GET /v1/accounts/{id}/pgcreds`. Passwords are **not** returned by this
|
||||
endpoint.
|
||||
operationId: listAccessiblePGCreds
|
||||
tags: [Admin — Credentials]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Array of accessible Postgres credential summaries.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, service_account_id, host, port, database, username, created_at, updated_at]
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: Credential record ID.
|
||||
example: 7
|
||||
service_account_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the system account that owns these credentials.
|
||||
example: 550e8400-e29b-41d4-a716-446655440000
|
||||
service_account_name:
|
||||
type: string
|
||||
description: Username of the owning system account (omitted if unavailable).
|
||||
example: payments-api
|
||||
host:
|
||||
type: string
|
||||
example: db.example.com
|
||||
port:
|
||||
type: integer
|
||||
example: 5432
|
||||
database:
|
||||
type: string
|
||||
example: mydb
|
||||
username:
|
||||
type: string
|
||||
example: myuser
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-03-11T09:00:00Z"
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-03-11T09:00:00Z"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"503":
|
||||
$ref: "#/components/responses/VaultSealed"
|
||||
|
||||
/v1/audit:
|
||||
get:
|
||||
summary: Query audit log (admin)
|
||||
@@ -980,7 +1346,10 @@ paths:
|
||||
`token_issued`, `token_renewed`, `token_revoked`, `token_expired`,
|
||||
`account_created`, `account_updated`, `account_deleted`,
|
||||
`role_granted`, `role_revoked`, `totp_enrolled`, `totp_removed`,
|
||||
`pgcred_accessed`, `pgcred_updated`.
|
||||
`pgcred_accessed`, `pgcred_updated`, `pgcred_access_granted`,
|
||||
`pgcred_access_revoked`, `tag_added`, `tag_removed`,
|
||||
`policy_rule_created`, `policy_rule_updated`, `policy_rule_deleted`,
|
||||
`policy_deny`, `vault_sealed`, `vault_unsealed`.
|
||||
operationId: listAudit
|
||||
tags: [Admin — Audit]
|
||||
security:
|
||||
@@ -1041,6 +1410,310 @@ paths:
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
/v1/accounts/{id}/tags:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: 550e8400-e29b-41d4-a716-446655440000
|
||||
|
||||
get:
|
||||
summary: Get account tags (admin)
|
||||
description: |
|
||||
Return the current tag set for an account. Tags are used by the policy
|
||||
engine for machine/service gating (e.g. `env:production`,
|
||||
`svc:payments-api`).
|
||||
operationId: getAccountTags
|
||||
tags: [Admin — Policy]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Tag list.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TagsResponse"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
put:
|
||||
summary: Set account tags (admin)
|
||||
description: |
|
||||
Replace the account's full tag set atomically. Pass an empty array to
|
||||
clear all tags. Changes take effect immediately for new policy
|
||||
evaluations; no token renewal is required.
|
||||
operationId: setAccountTags
|
||||
tags: [Admin — Policy]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [tags]
|
||||
properties:
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["env:production", "svc:payments-api"]
|
||||
responses:
|
||||
"200":
|
||||
description: Updated tag list.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TagsResponse"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
/v1/accounts/{id}/password:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: 550e8400-e29b-41d4-a716-446655440000
|
||||
|
||||
put:
|
||||
summary: Admin password reset (admin)
|
||||
description: |
|
||||
Reset the password for a human account without requiring the current
|
||||
password. This is intended for account recovery (e.g. a user forgot
|
||||
their password).
|
||||
|
||||
On success:
|
||||
- The stored Argon2id hash is replaced with the new password hash.
|
||||
- All active sessions for the target account are revoked.
|
||||
|
||||
Only applies to human accounts. The new password must be at least
|
||||
12 characters.
|
||||
operationId: adminSetPassword
|
||||
tags: [Admin — Accounts]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [new_password]
|
||||
properties:
|
||||
new_password:
|
||||
type: string
|
||||
description: The new password. Minimum 12 characters.
|
||||
example: new-s3cr3t-long
|
||||
responses:
|
||||
"204":
|
||||
description: Password reset. All active sessions for the account revoked.
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
/v1/policy/rules:
|
||||
get:
|
||||
summary: List policy rules (admin)
|
||||
description: |
|
||||
Return all operator-defined policy rules ordered by priority (ascending).
|
||||
Built-in default rules (IDs -1 to -7) are not included.
|
||||
operationId: listPolicyRules
|
||||
tags: [Admin — Policy]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Array of policy rules.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/PolicyRule"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
post:
|
||||
summary: Create policy rule (admin)
|
||||
description: |
|
||||
Create a new operator policy rule. Rules are evaluated in priority order
|
||||
(lower number = evaluated first, default 100). Deny-wins: if any matching
|
||||
rule has effect `deny`, access is denied regardless of allow rules.
|
||||
operationId: createPolicyRule
|
||||
tags: [Admin — Policy]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [description, rule]
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
example: Allow payments-api to read its own pgcreds
|
||||
priority:
|
||||
type: integer
|
||||
description: Evaluation priority. Lower = first. Default 100.
|
||||
example: 50
|
||||
rule:
|
||||
$ref: "#/components/schemas/RuleBody"
|
||||
not_before:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Earliest activation time (RFC3339, optional).
|
||||
example: "2026-04-01T00:00:00Z"
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Expiry time (RFC3339, optional).
|
||||
example: "2026-06-01T00:00:00Z"
|
||||
responses:
|
||||
"201":
|
||||
description: Rule created.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/PolicyRule"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
/v1/policy/rules/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
example: 1
|
||||
|
||||
get:
|
||||
summary: Get policy rule (admin)
|
||||
operationId: getPolicyRule
|
||||
tags: [Admin — Policy]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Policy rule.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/PolicyRule"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
patch:
|
||||
summary: Update policy rule (admin)
|
||||
description: |
|
||||
Update one or more fields of an existing policy rule. All fields are
|
||||
optional; omitted fields are left unchanged.
|
||||
operationId: updatePolicyRule
|
||||
tags: [Admin — Policy]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
example: Updated description
|
||||
priority:
|
||||
type: integer
|
||||
example: 75
|
||||
enabled:
|
||||
type: boolean
|
||||
example: false
|
||||
rule:
|
||||
$ref: "#/components/schemas/RuleBody"
|
||||
not_before:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Set earliest activation time (RFC3339).
|
||||
example: "2026-04-01T00:00:00Z"
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Set expiry time (RFC3339).
|
||||
example: "2026-06-01T00:00:00Z"
|
||||
clear_not_before:
|
||||
type: boolean
|
||||
description: Set to true to remove not_before constraint.
|
||||
clear_expires_at:
|
||||
type: boolean
|
||||
description: Set to true to remove expires_at constraint.
|
||||
responses:
|
||||
"200":
|
||||
description: Updated rule.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/PolicyRule"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
delete:
|
||||
summary: Delete policy rule (admin)
|
||||
description: Permanently delete a policy rule. This action cannot be undone.
|
||||
operationId: deletePolicyRule
|
||||
tags: [Admin — Policy]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"204":
|
||||
description: Rule deleted.
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
tags:
|
||||
- name: Public
|
||||
description: No authentication required.
|
||||
@@ -1056,3 +1729,7 @@ tags:
|
||||
description: Requires admin role.
|
||||
- name: Admin — Audit
|
||||
description: Requires admin role.
|
||||
- name: Admin — Policy
|
||||
description: Requires admin role. Manage policy rules and account tags.
|
||||
- name: Admin — Vault
|
||||
description: Requires admin role. Emergency vault seal operation.
|
||||
|
||||
Reference in New Issue
Block a user