Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,107 @@ paths:
'204':
description: Role removed (idempotent)

/api/v1/users/{id}:reset-password:
post:
operationId: postUserResetPassword
summary: Admin-reset a user's password (own or another's)
description: |
Sets a user's password on administrator authority - no current
password required. The new password runs through the role-aware
policy + breach screen; the target's active sessions are revoked so
they must re-authenticate. RBAC: admin:user_manage. Spec api-users.
x-required-permission: admin:user_manage
x-audit-events: [admin.user.password_reset]
parameters:
- {name: id, in: path, required: true, schema: {type: string, format: uuid}}
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/UserPasswordResetRequest'}
responses:
'204':
description: Password reset; the target's sessions were revoked
'400':
description: New password rejected by policy (too short/long/breached)
content:
application/json:
schema: {$ref: '#/components/schemas/ErrorEnvelope'}
'403':
description: Caller lacks admin:user_manage permission
content:
application/json:
schema: {$ref: '#/components/schemas/ErrorEnvelope'}
'404':
description: User not found
content:
application/json:
schema: {$ref: '#/components/schemas/ErrorEnvelope'}

/api/v1/users/{id}:disable:
post:
operationId: postUserDisable
summary: Disable a user account
description: |
Marks the account disabled: it can no longer authenticate (login is
rejected) and the user's active sessions are revoked immediately. An
admin cannot disable their own account (lockout prevention, 409).
RBAC: admin:user_manage. Spec api-users.
x-required-permission: admin:user_manage
x-audit-events: [admin.user.disabled]
parameters:
- {name: id, in: path, required: true, schema: {type: string, format: uuid}}
responses:
'200':
description: The disabled user
content:
application/json:
schema: {$ref: '#/components/schemas/UserResponse'}
'403':
description: Caller lacks admin:user_manage permission
content:
application/json:
schema: {$ref: '#/components/schemas/ErrorEnvelope'}
'404':
description: User not found
content:
application/json:
schema: {$ref: '#/components/schemas/ErrorEnvelope'}
'409':
description: Cannot disable your own account
content:
application/json:
schema: {$ref: '#/components/schemas/ErrorEnvelope'}

/api/v1/users/{id}:enable:
post:
operationId: postUserEnable
summary: Re-enable a disabled user account
description: |
Clears the disabled flag so the user can authenticate again with a
fresh login (sessions revoked while disabled stay dead). RBAC:
admin:user_manage. Spec api-users.
x-required-permission: admin:user_manage
x-audit-events: [admin.user.enabled]
parameters:
- {name: id, in: path, required: true, schema: {type: string, format: uuid}}
responses:
'200':
description: The re-enabled user
content:
application/json:
schema: {$ref: '#/components/schemas/UserResponse'}
'403':
description: Caller lacks admin:user_manage permission
content:
application/json:
schema: {$ref: '#/components/schemas/ErrorEnvelope'}
'404':
description: User not found
content:
application/json:
schema: {$ref: '#/components/schemas/ErrorEnvelope'}

/api/v1/roles:create:
post:
operationId: postRolesCreate
Expand Down Expand Up @@ -3708,6 +3809,17 @@ components:
last_password_change_at: {type: string, format: date-time}
created_at: {type: string, format: date-time}
updated_at: {type: string, format: date-time}
disabled_at:
type: string
format: date-time
nullable: true
description: Non-null when the account is disabled (cannot authenticate)

UserPasswordResetRequest:
type: object
required: [new_password]
properties:
new_password: {type: string, minLength: 1, maxLength: 256}

UsersListResponse:
type: object
Expand Down
25 changes: 25 additions & 0 deletions audit/events.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,31 @@ events:
- code: admin.user.deleted
severity: warning

- code: admin.user.password_reset
severity: warning
description: An administrator reset another user's (or their own) password.
detail_schema:
type: object
properties:
target_user_id: {type: string}
self: {type: boolean}

- code: admin.user.disabled
severity: warning
description: An administrator disabled a user account (cannot authenticate).
detail_schema:
type: object
properties:
target_user_id: {type: string}

- code: admin.user.enabled
severity: warning
description: An administrator re-enabled a previously disabled user account.
detail_schema:
type: object
properties:
target_user_id: {type: string}

- code: admin.role.changed
severity: warning

Expand Down
216 changes: 216 additions & 0 deletions frontend/src/api/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,74 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/v1/users/{id}:reset-password": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Admin-reset a user's password (own or another's)
* @description Sets a user's password on administrator authority - no current
* password required. The new password runs through the role-aware
* policy + breach screen; the target's active sessions are revoked so
* they must re-authenticate. RBAC: admin:user_manage. Spec api-users.
*/
post: operations["postUserResetPassword"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/users/{id}:disable": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Disable a user account
* @description Marks the account disabled: it can no longer authenticate (login is
* rejected) and the user's active sessions are revoked immediately. An
* admin cannot disable their own account (lockout prevention, 409).
* RBAC: admin:user_manage. Spec api-users.
*/
post: operations["postUserDisable"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/users/{id}:enable": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Re-enable a disabled user account
* @description Clears the disabled flag so the user can authenticate again with a
* fresh login (sessions revoked while disabled stay dead). RBAC:
* admin:user_manage. Spec api-users.
*/
post: operations["postUserEnable"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/roles:create": {
parameters: {
query?: never;
Expand Down Expand Up @@ -2366,6 +2434,14 @@ export interface components {
created_at?: string;
/** Format: date-time */
updated_at?: string;
/**
* Format: date-time
* @description Non-null when the account is disabled (cannot authenticate)
*/
disabled_at?: string | null;
};
UserPasswordResetRequest: {
new_password: string;
};
UsersListResponse: {
users: components["schemas"]["UserResponse"][];
Expand Down Expand Up @@ -4700,6 +4776,146 @@ export interface operations {
};
};
};
postUserResetPassword: {
parameters: {
query?: never;
header?: never;
path: {
id: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["UserPasswordResetRequest"];
};
};
responses: {
/** @description Password reset; the target's sessions were revoked */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description New password rejected by policy (too short/long/breached) */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorEnvelope"];
};
};
/** @description Caller lacks admin:user_manage permission */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorEnvelope"];
};
};
/** @description User not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorEnvelope"];
};
};
};
};
postUserDisable: {
parameters: {
query?: never;
header?: never;
path: {
id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description The disabled user */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["UserResponse"];
};
};
/** @description Caller lacks admin:user_manage permission */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorEnvelope"];
};
};
/** @description User not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorEnvelope"];
};
};
/** @description Cannot disable your own account */
409: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorEnvelope"];
};
};
};
};
postUserEnable: {
parameters: {
query?: never;
header?: never;
path: {
id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description The re-enabled user */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["UserResponse"];
};
};
/** @description Caller lacks admin:user_manage permission */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorEnvelope"];
};
};
/** @description User not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorEnvelope"];
};
};
};
};
postRolesCreate: {
parameters: {
query?: never;
Expand Down
Loading
Loading