Checkpoint: password reset, rule expiry, migrations
- Self-service and admin password-change endpoints
(PUT /v1/auth/password, PUT /v1/accounts/{id}/password)
- Policy rule time-scoped expiry (not_before / expires_at)
with migration 000006 and engine filtering
- golang-migrate integration; embedded SQL migrations
- PolicyRecord fieldalignment lint fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
162
openapi.yaml
162
openapi.yaml
@@ -206,6 +206,24 @@ components:
|
||||
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 <= now()` are
|
||||
skipped during evaluation.
|
||||
example: "2026-06-01T00:00:00Z"
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
@@ -582,6 +600,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:
|
||||
@@ -984,7 +1064,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`.
|
||||
operationId: listAudit
|
||||
tags: [Admin — Audit]
|
||||
security:
|
||||
@@ -1118,6 +1201,57 @@ paths:
|
||||
"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)
|
||||
@@ -1169,6 +1303,16 @@ paths:
|
||||
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.
|
||||
@@ -1239,6 +1383,22 @@ paths:
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user