Files
mcias/openapi.yaml
Kyle Isom 052d3ed1b8 Add PG creds + policy/tags UI; fix lint and build
- internal/ui/ui.go: add PGCred, Tags to AccountDetailData; register
  PUT /accounts/{id}/pgcreds and PUT /accounts/{id}/tags routes; add
  pgcreds_form.html and tags_editor.html to shared template set; remove
  unused AccountTagsData; fix fieldalignment on PolicyRuleView, PoliciesData
- internal/ui/handlers_accounts.go: add handleSetPGCreds — encrypts
  password via crypto.SealAESGCM, writes audit EventPGCredUpdated, renders
  pgcreds_form fragment; password never echoed; load PG creds and tags in
  handleAccountDetail
- internal/ui/handlers_policy.go: fix handleSetAccountTags to render with
  AccountDetailData instead of removed AccountTagsData
- internal/ui/ui_test.go: add 5 PG credential UI tests
- web/templates/fragments/pgcreds_form.html: new fragment — metadata display
  + set/replace form; system accounts only; password write-only
- web/templates/fragments/tags_editor.html: new fragment — textarea editor
  with HTMX PUT for atomic tag replacement
- web/templates/fragments/policy_form.html: rewrite to use structured fields
  matching handleCreatePolicyRule (roles/account_types/actions multi-select,
  resource_type, subject_uuid, service_names, required_tags, checkbox)
- web/templates/policies.html: new policies management page
- web/templates/fragments/policy_row.html: new HTMX table row with toggle
  and delete
- web/templates/account_detail.html: add Tags card and PG Credentials card
- web/templates/base.html: add Policies nav link
- internal/server/server.go: remove ~220 lines of duplicate tag/policy
  handler code (real implementations are in handlers_policy.go)
- internal/policy/engine_wrapper.go: fix corrupted source; use errors.New
- internal/db/policy_test.go: use model.AccountTypeHuman constant
- cmd/mciasctl/main.go: add nolint:gosec to int(os.Stdin.Fd()) calls
- gofmt/goimports: db/policy_test.go, policy/defaults.go,
  policy/engine_test.go, ui/ui.go, cmd/mciasctl/main.go
- fieldalignment: model.PolicyRuleRecord, policy.Engine, policy.Rule,
  policy.RuleBody, ui.PolicyRuleView
Security: PG password encrypted AES-256-GCM with fresh random nonce before
storage; plaintext never logged or returned in any response; audit event
written on every credential write.
2026-03-11 23:24:03 -07:00

1292 lines
39 KiB
YAML

openapi: "3.1.0"
info:
title: MCIAS Authentication API
version: "1.0"
description: |
MCIAS (Metacircular Identity and Access System) provides JWT-based
authentication, account management, TOTP, and Postgres credential storage.
All tokens are Ed25519-signed JWTs (algorithm `EdDSA`). Bearer tokens must
be sent in the `Authorization` header as `Bearer <token>`.
Rate limiting applies to `/v1/auth/login` and `/v1/token/validate`:
10 requests per second per IP, burst of 10.
servers:
- url: https://auth.example.com:8443
description: Production
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
Error:
type: object
required: [error, code]
properties:
error:
type: string
description: Human-readable error message.
example: invalid credentials
code:
type: string
description: Machine-readable error code.
example: unauthorized
TokenResponse:
type: object
required: [token, expires_at]
properties:
token:
type: string
description: Ed25519-signed JWT (EdDSA).
example: eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...
expires_at:
type: string
format: date-time
description: Token expiry in RFC 3339 format.
example: "2026-04-10T12:34:56Z"
Account:
type: object
required: [id, username, account_type, status, created_at, updated_at, totp_enabled]
properties:
id:
type: string
format: uuid
description: Account UUID (use this in all API calls).
example: 550e8400-e29b-41d4-a716-446655440000
username:
type: string
example: alice
account_type:
type: string
enum: [human, system]
example: human
status:
type: string
enum: [active, inactive, deleted]
example: active
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"
totp_enabled:
type: boolean
description: Whether TOTP is enrolled and required for this account.
example: false
AuditEvent:
type: object
required: [id, event_type, event_time, ip_address]
properties:
id:
type: integer
example: 42
event_type:
type: string
example: login_ok
event_time:
type: string
format: date-time
example: "2026-03-11T09:01:23Z"
actor_id:
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
format: uuid
nullable: true
description: UUID of the affected account, if applicable.
ip_address:
type: string
example: "192.0.2.1"
details:
type: string
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
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]
properties:
host:
type: string
example: db.example.com
port:
type: integer
example: 5432
database:
type: string
example: mydb
username:
type: string
example: myuser
password:
type: string
description: >
Plaintext password (sent over TLS, stored encrypted at rest with
AES-256-GCM). Only returned to admin callers.
example: hunter2
responses:
Unauthorized:
description: Token missing, invalid, expired, or credentials incorrect.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
error: invalid credentials
code: unauthorized
Forbidden:
description: Token valid but lacks the required role.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
error: forbidden
code: forbidden
NotFound:
description: Requested resource does not exist.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
error: account not found
code: not_found
BadRequest:
description: Malformed request or missing required fields.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
error: username and password are required
code: bad_request
RateLimited:
description: Rate limit exceeded.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
error: rate limit exceeded
code: rate_limited
paths:
# ── Public ────────────────────────────────────────────────────────────────
/v1/health:
get:
summary: Health check
description: Returns `{"status":"ok"}` if the server is running. No auth required.
operationId: getHealth
tags: [Public]
responses:
"200":
description: Server is healthy.
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: ok
/v1/keys/public:
get:
summary: Ed25519 public key (JWK)
description: |
Returns the server's Ed25519 public key in JWK format (RFC 8037).
Relying parties use this to verify JWT signatures offline.
Cache this key at startup. Refresh it if signature verification begins
failing (indicates key rotation).
**Important:** Always validate the `alg` header of the JWT (`EdDSA`)
before calling the signature verification routine. Never accept `none`.
operationId: getPublicKey
tags: [Public]
responses:
"200":
description: Ed25519 public key in JWK format.
content:
application/json:
schema:
type: object
required: [kty, crv, use, alg, x]
properties:
kty:
type: string
example: OKP
crv:
type: string
example: Ed25519
use:
type: string
example: sig
alg:
type: string
example: EdDSA
x:
type: string
description: Base64url-encoded public key bytes.
example: 11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo
/v1/auth/login:
post:
summary: Login
description: |
Authenticate with username + password and optionally a TOTP code.
Returns an Ed25519-signed JWT.
Rate limited to 10 requests per second per IP (burst 10).
Error responses always use the generic message `"invalid credentials"`
regardless of whether the user exists, the password is wrong, or the
account is inactive. This prevents user enumeration.
If the account has TOTP enrolled, `totp_code` is required.
Omitting it returns HTTP 401 with code `totp_required`.
operationId: login
tags: [Public]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [username, password]
properties:
username:
type: string
example: alice
password:
type: string
example: s3cr3t
totp_code:
type: string
description: Current 6-digit TOTP code. Required if TOTP is enrolled.
example: "123456"
responses:
"200":
description: Login successful. Returns JWT and expiry.
content:
application/json:
schema:
$ref: "#/components/schemas/TokenResponse"
"400":
$ref: "#/components/responses/BadRequest"
"401":
description: Invalid credentials, inactive account, or missing TOTP code.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
invalid_credentials:
value: {error: invalid credentials, code: unauthorized}
totp_required:
value: {error: TOTP code required, code: totp_required}
"429":
$ref: "#/components/responses/RateLimited"
/v1/token/validate:
post:
summary: Validate a JWT
description: |
Validate a JWT and return its claims. Reflects revocations immediately
(online validation). Use this for high-security paths where offline
verification is insufficient.
The token may be supplied either as a Bearer header or in the JSON body.
**Always inspect the `valid` field.** The response is always HTTP 200;
do not branch on the status code.
Rate limited to 10 requests per second per IP (burst 10).
operationId: validateToken
tags: [Public]
security:
- bearerAuth: []
- {}
requestBody:
description: Optionally supply the token in the body instead of the header.
required: false
content:
application/json:
schema:
type: object
properties:
token:
type: string
example: eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...
responses:
"200":
description: Validation result. Always HTTP 200; check `valid`.
content:
application/json:
schema:
type: object
required: [valid]
properties:
valid:
type: boolean
sub:
type: string
format: uuid
description: Subject (account UUID). Present when valid=true.
example: 550e8400-e29b-41d4-a716-446655440000
roles:
type: array
items:
type: string
description: Role list. Present when valid=true.
example: [editor]
expires_at:
type: string
format: date-time
description: Expiry. Present when valid=true.
example: "2026-04-10T12:34:56Z"
examples:
valid:
value: {valid: true, sub: "550e8400-...", roles: [editor], expires_at: "2026-04-10T12:34:56Z"}
invalid:
value: {valid: false}
"429":
$ref: "#/components/responses/RateLimited"
# ── Authenticated ──────────────────────────────────────────────────────────
/v1/auth/logout:
post:
summary: Logout
description: |
Revoke the current bearer token immediately. The JTI is recorded in the
revocation table; subsequent validation calls will return `valid=false`.
operationId: logout
tags: [Auth]
security:
- bearerAuth: []
responses:
"204":
description: Token revoked.
"401":
$ref: "#/components/responses/Unauthorized"
/v1/auth/renew:
post:
summary: Renew token
description: |
Exchange the current token for a fresh one. The old token is revoked.
The new token reflects any role changes made since the original login.
Token expiry is recalculated: 30 days for regular users, 8 hours for
admins.
operationId: renewToken
tags: [Auth]
security:
- bearerAuth: []
responses:
"200":
description: New token issued. Old token revoked.
content:
application/json:
schema:
$ref: "#/components/schemas/TokenResponse"
"401":
$ref: "#/components/responses/Unauthorized"
/v1/auth/totp/enroll:
post:
summary: Begin TOTP enrollment
description: |
Generate a TOTP secret for the authenticated account and return it as a
bare secret and as an `otpauth://` URI (scan with any authenticator app).
The secret is shown **once**. It is stored encrypted at rest and is not
retrievable after this call.
TOTP is not required until the enrollment is confirmed via
`POST /v1/auth/totp/confirm`. Abandoning after this call does not lock
the account.
operationId: enrollTOTP
tags: [Auth]
security:
- bearerAuth: []
responses:
"200":
description: TOTP secret generated.
content:
application/json:
schema:
type: object
required: [secret, otpauth_uri]
properties:
secret:
type: string
description: Base32-encoded TOTP secret. Store in an authenticator app.
example: JBSWY3DPEHPK3PXP
otpauth_uri:
type: string
description: Standard otpauth URI for QR-code generation.
example: "otpauth://totp/MCIAS:alice?secret=JBSWY3DPEHPK3PXP&issuer=MCIAS"
"401":
$ref: "#/components/responses/Unauthorized"
/v1/auth/totp/confirm:
post:
summary: Confirm TOTP enrollment
description: |
Verify the provided TOTP code against the pending secret. On success,
TOTP becomes required for all future logins for this account.
operationId: confirmTOTP
tags: [Auth]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [code]
properties:
code:
type: string
description: Current 6-digit TOTP code.
example: "123456"
responses:
"204":
description: TOTP confirmed. Required for future logins.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
# ── Admin ──────────────────────────────────────────────────────────────────
/v1/auth/totp:
delete:
summary: Remove TOTP from account (admin)
description: |
Clear TOTP enrollment for an account. Use for account recovery when a
user loses their TOTP device. The account can log in with password only
after this call.
operationId: removeTOTP
tags: [Admin — Auth]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [account_id]
properties:
account_id:
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
responses:
"204":
description: TOTP removed.
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/token/issue:
post:
summary: Issue service account token (admin)
description: |
Issue a long-lived bearer token for a system account. If the account
already has an active token, it is revoked and replaced.
Only one active token exists per system account at a time.
Issued tokens expire after 1 year (configurable via
`tokens.service_expiry`).
operationId: issueServiceToken
tags: [Admin — Tokens]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [account_id]
properties:
account_id:
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
responses:
"200":
description: Token issued.
content:
application/json:
schema:
$ref: "#/components/schemas/TokenResponse"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/token/{jti}:
delete:
summary: Revoke token by JTI (admin)
description: |
Revoke any token by its JWT ID (`jti` claim). The token is immediately
invalid for all future validation calls.
operationId: revokeToken
tags: [Admin — Tokens]
security:
- bearerAuth: []
parameters:
- name: jti
in: path
required: true
schema:
type: string
format: uuid
example: f47ac10b-58cc-4372-a567-0e02b2c3d479
responses:
"204":
description: Token revoked.
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/accounts:
get:
summary: List accounts (admin)
operationId: listAccounts
tags: [Admin — Accounts]
security:
- bearerAuth: []
responses:
"200":
description: Array of accounts.
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Account"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
post:
summary: Create account (admin)
description: |
Create a human or system account.
- `human` accounts require a `password`.
- `system` accounts must not include a `password`; authenticate via
tokens issued by `POST /v1/token/issue`.
operationId: createAccount
tags: [Admin — Accounts]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [username, account_type]
properties:
username:
type: string
example: alice
account_type:
type: string
enum: [human, system]
example: human
password:
type: string
description: Required for human accounts. Hashed with Argon2id.
example: s3cr3t
responses:
"201":
description: Account created.
content:
application/json:
schema:
$ref: "#/components/schemas/Account"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"409":
description: Username already taken.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
error: username already exists
code: conflict
/v1/accounts/{id}:
parameters:
- name: id
in: path
required: true
description: Account UUID.
schema:
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
get:
summary: Get account (admin)
operationId: getAccount
tags: [Admin — Accounts]
security:
- bearerAuth: []
responses:
"200":
description: Account details.
content:
application/json:
schema:
$ref: "#/components/schemas/Account"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
patch:
summary: Update account (admin)
description: Update mutable account fields. Currently only `status` is patchable.
operationId: updateAccount
tags: [Admin — Accounts]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: [active, inactive]
example: inactive
responses:
"204":
description: Account updated.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
delete:
summary: Delete account (admin)
description: |
Soft-delete an account. Sets status to `deleted` and revokes all active
tokens. The account record is retained for audit purposes.
operationId: deleteAccount
tags: [Admin — Accounts]
security:
- bearerAuth: []
responses:
"204":
description: Account deleted.
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/accounts/{id}/roles:
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
get:
summary: Get account roles (admin)
operationId: getRoles
tags: [Admin — Accounts]
security:
- bearerAuth: []
responses:
"200":
description: Current role list.
content:
application/json:
schema:
type: object
required: [roles]
properties:
roles:
type: array
items:
type: string
example: [editor, readonly]
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
put:
summary: Set account roles (admin)
description: |
Replace the account's full role list. Roles take effect in the **next**
token issued or renewed; existing tokens continue to carry the roles
embedded at issuance time.
operationId: setRoles
tags: [Admin — Accounts]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [roles]
properties:
roles:
type: array
items:
type: string
example: [editor, readonly]
responses:
"204":
description: Roles updated.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/accounts/{id}/pgcreds:
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
get:
summary: Get Postgres credentials (admin)
description: |
Retrieve stored Postgres connection credentials. Password is returned
in plaintext over TLS. Stored encrypted at rest with AES-256-GCM.
operationId: getPGCreds
tags: [Admin — Credentials]
security:
- bearerAuth: []
responses:
"200":
description: Postgres credentials.
content:
application/json:
schema:
$ref: "#/components/schemas/PGCreds"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
put:
summary: Set Postgres credentials (admin)
description: Store or replace Postgres credentials for an account.
operationId: setPGCreds
tags: [Admin — Credentials]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PGCreds"
responses:
"204":
description: Credentials stored.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/audit:
get:
summary: Query audit log (admin)
description: |
Retrieve audit log entries, newest first. Supports pagination and
filtering. The log is append-only and never contains credentials.
Event types include: `login_ok`, `login_fail`, `login_totp_fail`,
`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`.
operationId: listAudit
tags: [Admin — Audit]
security:
- bearerAuth: []
parameters:
- name: limit
in: query
schema:
type: integer
default: 50
minimum: 1
maximum: 1000
example: 50
- name: offset
in: query
schema:
type: integer
default: 0
example: 0
- name: event_type
in: query
schema:
type: string
description: Filter by event type.
example: login_fail
- name: actor_id
in: query
schema:
type: string
format: uuid
description: Filter by actor account UUID.
example: 550e8400-e29b-41d4-a716-446655440000
responses:
"200":
description: Paginated audit log.
content:
application/json:
schema:
type: object
required: [events, total, limit, offset]
properties:
events:
type: array
items:
$ref: "#/components/schemas/AuditEvent"
total:
type: integer
description: Total number of matching events (for pagination).
example: 142
limit:
type: integer
example: 50
offset:
type: integer
example: 0
"401":
$ref: "#/components/responses/Unauthorized"
"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/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 -6) 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"
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"
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.
- name: Auth
description: Requires a valid bearer token.
- name: Admin — Auth
description: Requires admin role.
- name: Admin — Tokens
description: Requires admin role.
- name: Admin — Accounts
description: Requires admin role.
- name: Admin — Credentials
description: Requires admin role.
- name: Admin — Audit
description: Requires admin role.
- name: Admin — Policy
description: Requires admin role. Manage policy rules and account tags.