Outcome
Change the authenticated subject password after current-password verification; independently verify non-atomic lockout and session revocation.
These examples prove local handler behavior with real isolated hashing/SQL and explicitly simulated identity dependencies.
Before you start
-
Verify pinned identity and the published action. Change and initial setup require the access-token subject to equal userId; admin reset requires exact-target management authority. An authenticated denial is not proof of expired OAuth.
-
Supply actual passwords only through the intended private approved workflow. Published passwords belong solely to isolated synthetic fixtures. Do not log or share a live password/hash, use a public example password, or retain passwords as rollback material. Any temporary live payload requires restricted local handling and removal after execution.
-
Review the validated plan and effects before approval. Stored password configuration, usable sign-in, activation, session revocation and notification delivery are separate outcomes. Initial setup preserves sessions; change/reset revocation can fail after storage.
Discover the target
topolo whoami --json
topolo apps --json
topolo actions get app_topolo_auth.user_password.change --detail --json
Use the exact authenticated subject for self actions or an authorized discovered managed user for admin reset. Read users.get passwordConfigured/status, user_security.get lockout and sessions.list for that exact user. Never retrieve a password hash as a discovery step.
Input fields
| Field | Required | Meaning |
|---|---|---|
userId | Yes | Existing Auth identity from authorized discovery. Change and initial setup require the access-token sub to equal this user id; an administrator or userId-only alias does not substitute for self. Admin reset requires management authority over the selected target organization. |
currentPassword | Yes | Current password supplied privately by the intended user for self change, at least eight characters under this published input contract. Do not log it, place it in shared files, or copy fixture passwords into a live account. An old value cannot be reused after a completed change. |
newPassword | Yes | Privately supplied new password: 8–128 characters with lowercase, uppercase, a digit and one allowed punctuation character. It must differ from any stored password for change/reset. Initial setup requires an account without a password. Fixture values are synthetic and authorize no live mutation. |
Effects
-
Changes only the authenticated subject password after verifying their current password.
-
Resets failed-login lockout, then attempts refresh/session revocation. Storage and revocation are non-atomic; audit/notification failure is nonfatal.
Interpret the response
The acknowledgment identifies the affected user. Real current-password verification precedes the new hash write; identical new passwords are refused. No password/hash is returned.
Password write, lockout reset, refresh-token revocation and session revocation are sequential, not one atomic transaction. Session-store failures are swallowed and can still yield success; a security reset failure can yield 500 after the password already changed.
This handler does not increment token_version or clear browser cookies. Stored revocation is separate from actual access-token rejection. Lifecycle delivery and audit are nonfatal. Verify fresh sign-in, old-password rejection and actual old-session rejection independently.
Worked examples
These are complete raw target-handler responses, not CLI/gateway envelopes or live authorization proof. All identifiers and passwords are synthetic and exist only in isolated fixtures. Replace targets through authorized discovery and supply real passwords privately. The CLI examples describe validation, plan review and the approved call using a payload; do not retain or share live plaintext payloads. --confirm assumes the intended mutation is authorized. Cookies and Bearer identity must consistently select the intended principal. Cookie-authenticated self change/setup also require the approved CSRF transport. Password storage, actual sign-in and old-session rejection require separate verification.
Self changes a password with real hashing and revokes stored refresh tokens and sessions
Input · payload.json:
{
"userId": "usr_self",
"currentPassword": "FixtureCurrent83!Example",
"newPassword": "FixtureChanged94!Example"
}
CLI:
topolo actions validate app_topolo_auth.user_password.change --data @payload.json --json
topolo actions plan app_topolo_auth.user_password.change --data @payload.json --confirm --json
topolo actions call app_topolo_auth.user_password.change --data @payload.json --confirm --json
MCP call after validation and plan review:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.user_password.change",
"input": {
"userId": "usr_self",
"currentPassword": "FixtureCurrent83!Example",
"newPassword": "FixtureChanged94!Example"
},
"confirm": true
}
}
Captured local handler response · HTTP 200:
{
"success": true,
"message": "Password updated successfully",
"data": {
"userId": "usr_self",
"passwordUpdated": true
},
"timestamp": "2026-10-02T00:00:00.000Z"
}
Session-store failures are swallowed after the password write; success does not prove revocation
Input · payload.json:
{
"userId": "usr_self",
"currentPassword": "FixtureChanged94!Example",
"newPassword": "FixtureLater62!Example"
}
CLI:
topolo actions validate app_topolo_auth.user_password.change --data @payload.json --json
topolo actions plan app_topolo_auth.user_password.change --data @payload.json --confirm --json
topolo actions call app_topolo_auth.user_password.change --data @payload.json --confirm --json
MCP call after validation and plan review:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.user_password.change",
"input": {
"userId": "usr_self",
"currentPassword": "FixtureChanged94!Example",
"newPassword": "FixtureLater62!Example"
},
"confirm": true
}
}
Captured local handler response · HTTP 200:
{
"success": true,
"message": "Password updated successfully",
"data": {
"userId": "usr_self",
"passwordUpdated": true
},
"timestamp": "2026-10-02T00:00:00.000Z"
}
Verify the result
-
Require acknowledgment userId to equal the intended subject. Read users.get passwordConfigured and user_security.get lockout state without retrieving a hash.
-
Privately verify new-password sign-in and old-password refusal through the normal approved flow. Read sessions.list for the exact user and separately check old-session/access-token rejection; an empty list under a storage fault is not proof of revocation. Verify notification delivery separately.
Errors and recovery
Anonymous password change is refused
Captured local refusal · HTTP 401:
{
"input": {
"userId": "usr_self",
"currentPassword": "FixtureCurrent83!Example",
"newPassword": "FixtureChanged94!Example"
},
"response": {
"success": false,
"error": {
"message": "Access token required",
"code": "AUTH_ERROR",
"details": null,
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
Self password action cannot change another user
Captured local refusal · HTTP 403:
{
"input": {
"userId": "usr_managed",
"currentPassword": "FixtureCurrent83!Example",
"newPassword": "FixtureChanged94!Example"
},
"response": {
"success": false,
"error": {
"message": "You can only change your own password",
"code": "FORBIDDEN",
"details": null,
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
Administrator authority does not substitute for self identity in change password
Captured local refusal · HTTP 403:
{
"input": {
"userId": "usr_self",
"currentPassword": "FixtureCurrent83!Example",
"newPassword": "FixtureChanged94!Example"
},
"response": {
"success": false,
"error": {
"message": "You can only change your own password",
"code": "FORBIDDEN",
"details": null,
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
Current handler requires sub; a userId-only alias is not self password authority
Captured local refusal · HTTP 403:
{
"input": {
"userId": "usr_self",
"currentPassword": "FixtureCurrent83!Example",
"newPassword": "FixtureChanged94!Example"
},
"response": {
"success": false,
"error": {
"message": "You can only change your own password",
"code": "FORBIDDEN",
"details": null,
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
Missing self identity is not provisioned
Captured local refusal · HTTP 404:
{
"input": {
"userId": "usr_missing",
"currentPassword": "FixtureCurrent83!Example",
"newPassword": "FixtureChanged94!Example"
},
"response": {
"success": false,
"error": {
"message": "User not found",
"code": "NOT_FOUND",
"details": null,
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
Incorrect current password preserves the stored hash
Captured local refusal · HTTP 400:
{
"input": {
"userId": "usr_self",
"currentPassword": "IncorrectCurrent49!Example",
"newPassword": "FixtureChanged94!Example"
},
"response": {
"success": false,
"error": {
"message": "Current password is incorrect",
"code": "VALIDATION_ERROR",
"details": {
"currentPassword": true
},
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
The new password must differ from the current password
Captured local refusal · HTTP 400:
{
"input": {
"userId": "usr_self",
"currentPassword": "FixtureCurrent83!Example",
"newPassword": "FixtureCurrent83!Example"
},
"response": {
"success": false,
"error": {
"message": "New password must be different from the current password",
"code": "VALIDATION_ERROR",
"details": {
"newPassword": true
},
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
Retrying the old current password after success is not idempotent
Captured local refusal · HTTP 400:
{
"input": {
"userId": "usr_self",
"currentPassword": "FixtureCurrent83!Example",
"newPassword": "FixtureChanged94!Example"
},
"response": {
"success": false,
"error": {
"message": "Current password is incorrect",
"code": "VALIDATION_ERROR",
"details": {
"currentPassword": true
},
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
Published recovery guidance
-
state_conflict(HTTP 409): Read the current resource state, reconcile the intended change, and retry only if still valid. -
AUTH_ERROR(HTTP 401): Verify the pinned identity first. For an authenticated admin-reset denial, retain the refusal and obtain the intended target authority; do not widen context or assume OAuth expiry. -
FORBIDDEN(HTTP 403): Use the intended self principal and approved browser session/CSRF transport. Administrator authority cannot substitute for self; never bypass CSRF. -
NOT_FOUND(HTTP 404): Refresh authorized discovery. Self password reads require an active user; do not create a replacement identity. -
VALIDATION_ERROR(HTTP 400): Have the intended user privately correct the password or choose a different valid value. After an uncertain prior write, verify current sign-in and stored configuration before preparing any retry. -
PASSWORD_ALREADY_SET(HTTP 409): Read stored password configuration and verify the submitted password through the normal sign-in flow. Use a separately reviewed self-change or authorized recovery/reset workflow if needed; never clear the stored hash to force initial setup. -
SERVER_ERROR(HTTP 500): Preserve the request/result. Treat password and revocation as independently unknown; inspect authorized configuration/session reads and use the intended sign-in flow. Do not blindly retry or restore an old password. Authorized operator investigation may be needed.
Recovery boundaries
The prior password is not recoverable or automatically restored. Never store the prior hash/password as rollback material. After an unknown outcome inspect stored configuration and separately verify sign-in/revocation before reviewing any new password action. A further change/reset is a separately approved mutation with the actual current password or authorized target recovery, and cannot restore prior sessions.
Discovery and evidence
This record belongs to app_topolo_auth. Fixtures prove real controller authority, PBKDF2 hashing/verification, canonical password/security/session SQL and partial failure behavior with simulated tokens and organization hydration/admin lookup. Lifecycle notification is unconfigured; no live user, password, delivery, session or OAuth state was changed. Neither local capture nor stored configuration proves deployed authorization, usable sign-in, token invalidation or production completion.