Repository navigation
feat(admin): the user actions behind the admin gate - #13
Merged
Merged
Conversation
The console's Users screen needs the account actions the CLI reaches
the database for directly (users unlock, oauth unlink): an operator
who cannot reach the database has no path at all, so the read-only
position in user_handlers.go gives way. Still absent on purpose: no
password reset, no delete, nothing that reads PasswordHash.
Six endpoints, all behind AdminOnly, each audited with the operator's
id in metadata and answering the account's state after the action:
POST /v1/admin/users (cryden.SignUp)
POST /v1/admin/users/{id}/unlock (ResetFailedAttempts)
POST /v1/admin/users/{userID}/suspend (LockAccount + revoke-all)
POST /v1/admin/users/{userID}/unsuspend (same op as unlock,
its own audit type)
POST /v1/admin/users/{userID}/revoke-sessions (RevokeAllForUser)
POST /v1/admin/users/{userID}/mfa/disable (totp.Delete + revoke)
DELETE /v1/admin/users/{userID}/oauth/{provider} (ListByUser + Unlink)
Suspend is LockAccount with a far-future deadline, not a new engine
mechanism: a suspended sign-in is refused by the engine's own lockout
path. MFA disable deletes the enrolment through the TOTP store rather
than cryden.DisableTOTP, which requires the holder's password an
operator does not have; the enrolment's removal revokes the account's
sessions so access held through the factor does not outlive it. Unlock
and unsuspend are one engine operation with two names, kept apart for
the audit trail: lifting an operator-imposed suspension and clearing an
engine-imposed lockout are different sentences in the history.
Admin-created accounts go through cryden.SignUp, so the password
policy, breach check and rate limiter all run as they would at
POST /v1/signup; the audit records both the engine's signup event and
the operator's admin_user_created.
Every action answers AdminActionDTO: the account as it stands, so a
console re-renders without a second GET, with detail carrying what is
worth reporting (the revoked session count, the unlinked provider).
Tested with a real engine on memory stores behind the real gate: create
(sign-up through the policy, duplicate 409), unlock clearing lockout
and counter and the proof being a working login, suspend revoking live
sessions and blocking the next login, unsuspend restoring sign-in,
revoke-sessions emptying the live list, TOTP removal through the
engine's own enrolment path and its 404 without one, OAuth unlink and
its unknown-provider 404, malformed ids answering 404, and every
action refusing a non-operator token. The Tier 6 SQLite 501 test
extends to the new paths through its existing adminPaths table.
openapi/spec.yaml documents each route and the AdminActionDTO schema;
the embedded copy is regenerated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The console's Users screen needs the account actions the CLI reaches the database for directly (users unlock, oauth unlink): an operator who cannot reach the database has no path at all, so the read-only position in user_handlers.go gives way. Still absent on purpose: no password reset, no delete, nothing that reads PasswordHash.
Six endpoints, all behind AdminOnly, each audited with the operator's id in metadata and answering the account's state after the action:
POST /v1/admin/users (cryden.SignUp)
POST /v1/admin/users/{id}/unlock (ResetFailedAttempts)
POST /v1/admin/users/{userID}/suspend (LockAccount + revoke-all)
POST /v1/admin/users/{userID}/unsuspend (same op as unlock,
its own audit type)
POST /v1/admin/users/{userID}/revoke-sessions (RevokeAllForUser)
POST /v1/admin/users/{userID}/mfa/disable (totp.Delete + revoke)
DELETE /v1/admin/users/{userID}/oauth/{provider} (ListByUser + Unlink)
Suspend is LockAccount with a far-future deadline, not a new engine mechanism: a suspended sign-in is refused by the engine's own lockout path. MFA disable deletes the enrolment through the TOTP store rather than cryden.DisableTOTP, which requires the holder's password an operator does not have; the enrolment's removal revokes the account's sessions so access held through the factor does not outlive it. Unlock and unsuspend are one engine operation with two names, kept apart for the audit trail: lifting an operator-imposed suspension and clearing an engine-imposed lockout are different sentences in the history.
Admin-created accounts go through cryden.SignUp, so the password policy, breach check and rate limiter all run as they would at POST /v1/signup; the audit records both the engine's signup event and the operator's admin_user_created.
Every action answers AdminActionDTO: the account as it stands, so a console re-renders without a second GET, with detail carrying what is worth reporting (the revoked session count, the unlinked provider).
Tested with a real engine on memory stores behind the real gate: create (sign-up through the policy, duplicate 409), unlock clearing lockout and counter and the proof being a working login, suspend revoking live sessions and blocking the next login, unsuspend restoring sign-in, revoke-sessions emptying the live list, TOTP removal through the engine's own enrolment path and its 404 without one, OAuth unlink and its unknown-provider 404, malformed ids answering 404, and every action refusing a non-operator token. The Tier 6 SQLite 501 test extends to the new paths through its existing adminPaths table. openapi/spec.yaml documents each route and the AdminActionDTO schema; the embedded copy is regenerated.