Raven CMS Users
This document explains Raven's User system for both panel users and developers/agents.
Maintenance note: keep this file updated whenever user structure, user routes, or User panel views change (private/tpl/panel/user/*, user controller/repository behavior, or user-group assignment rules).
1) Panel Guide (Create And Edit Users)
Where To Go
- Open panel sidebar:
Users & Permissions->Users.
User List (/user)
What you can do:
New User(top and bottom action bars): opens create form.Invite Tokens(top and bottom action bars): opens token administration for registration invites.Delete Selected(top and bottom action bars): deletes checked rows after confirmation.Searchfilter: filters rows by username, display name, email, or groups as you type.Filter by Groupdropdown:All Groupsplus detected group names from the current list.- Row checkbox: marks a user for bulk delete.
- Clickable table headers (
ID,Username,Display Name,Email,Groups): client-side sort. - Row
Editbutton (pencil icon): opens user editor. - Row
Deletebutton (trash icon): deletes one user after confirmation.
Columns shown:
IDUsernameDisplay NameEmailGroups(comma-separated group names)Actions
Important delete note:
- You cannot delete your currently logged-in account from this screen.
Invite Tokens (/user/invites)
What you can do:
- create one token as
Single-useorReusable - optionally set a custom
Token Slugwhen creating a single-use token (blank = random token) - set optional expiration datetime
- generate a batch of randomized single-use tokens
- delete existing tokens
Important behavior:
- generated token values are shown immediately after creation/generation
- stored rows now include full token values (plus hash metadata for validation)
Reusabletokens can be used multiple times until expiry;Single-usetokens expire after first successful use
User Editor (/user/edit and /user/edit/{id})
Account, permission, profile, contact, password, and two-factor controls include short inline descriptions to clarify their scope.
Top and bottom action bars (same controls in both places):
Save UserBack to UsersDelete User(existing users only)
Fields/options:
Username(required)Display NameBioEmail(required)Change Passwordbutton on existing usersPassword- Required on create
- Optional on edit (leave blank to keep existing password)
Confirm Passwordwhen password entry is enabledEnter new password again to confirm:helper text under the confirmation fieldPanel Theme(<Default>,Corporate,Ice,Midnight)Avatar- file upload (
gif/jpg/jpeg/png) - optional
Remove current avatarcheckbox when avatar exists Cover Image- optional local image upload on the Profile tab
- optional
Remove current cover imagecheckbox when a cover exists Primary Group(single-select dropdown; required)Group Memberships(multi-select checkboxes)Two-Factor Methods(existing users)- read-only method entries with
Type,Label, andDetails - recovery entries show masked phrase details and whether they are one-time or reusable
- per-method remove action for recovery workflows
Group assignment notes:
- If no group is selected, user is auto-assigned to
User. - Only Admin users can assign the
Admingroup. - Only Admin users can newly assign groups with
Manage System Configuration. - Editing an account without changing its identity preserves its existing primary group, including when a restricted form control is omitted by the browser.
Panel permissions are assigned to groups rather than directly to user rows. Use the
user editor's Permissions tab to assign group memberships, and use the group editor's
Permissions tab to change the checkbox matrix. Enabled system extensions such as
Database Manager appear in the sidebar only for users with the relevant panel access.
2) Developer And Agent Internals
Key Files
- Panel views:
private/tpl/panel/user/list.phpprivate/tpl/panel/user/edit.phpprivate/tpl/panel/user/invites.phpprivate/tpl/panel/auth/login.phpprivate/tpl/panel/auth/login_2fa.php- Public auth views:
private/tpl/public/auth/login.phpprivate/tpl/public/auth/login_2fa.phpprivate/tpl/public/auth/register.php- Panel controller:
private/sys/Controller/Panel/UserListController.php, private/sys/Controller/Panel/UserEditController.php, private/sys/Controller/Panel/UserInviteController.php- Public auth controller:
private/sys/Controller/Public/AuthController.php- Public profile controller:
private/sys/Controller/Public/ProfileController.php- Public group controller:
private/sys/Controller/Public/GroupController.php- Public content controller:
private/sys/Controller/Public/PageController.php- Shared login workflow:
private/lib/Auth/LoginAttempt.phpprivate/lib/Auth/LoginChallenge.phpprivate/lib/Auth/LoginUiState.php- Persistence:
private/sys/Repository/UserRead.php, private/sys/Repository/UserWrite.phpprivate/sys/Repository/InviteRead.php, private/sys/Repository/InviteWrite.phpprivate/sys/Repository/GroupRead.php, private/sys/Repository/GroupWrite.php(group option lookups and role constraints)
Panel Routes
Declared in private/sys/Router/Panel/UserRouter.php:
GET /user-> listGET /user/edit-> create formGET /user/edit/{id}-> edit formPOST /user/save-> create/updatePOST /user/delete-> delete (single or bulk)GET /user/invites-> invite token list/adminPOST /user/invites/create-> create one tokenPOST /user/invites/generate-> generate single-use token batchPOST /user/invites/delete-> delete one token
All state-changing routes use CSRF validation.
Public routes (declared in private/sys/Router/Public/AuthRouter.php):
GET /login-> public login helper viewPOST /login-> public login submit handlerGET /login/2fa-> public two-factor challenge formPOST /login/2fa-> public two-factor challenge submit handlerPOST /login/2fa/select-> public two-factor method selectionPOST /login/2fa/webauthn/options-> public WebAuthn assertion-options endpointPOST /login/2fa/webauthn/verify-> public WebAuthn assertion verify endpointGET /register-> registration formPOST /register-> registration submit handler
Controller Flow
Split user handlers:
userList()- Owned by
UserListController. - Requires login +
Manage Userspermission. - Renders list with
UserRead::listAll(). userEdit(?int $id)- Owned by
UserEditController. - Loads existing row when id is provided.
- Provides group options and theme options.
- Includes capability flags for admin-group and configuration-capable-group assignment.
userSave(array $post, array $files)- Owned by
UserEditController. - Validates CSRF.
- Sanitizes/normalizes user fields via
InputSanitizer. - Validates username/email/theme.
- Enforces password length rules (create required, update optional).
- Normalizes selected group ids to existing groups only.
- Enforces admin-only assignment rules for the
Admingroup and configuration-capable groups. - Applies fallback
usergroup if none selected. - Validates avatar upload with
AvatarValidatorand stores sanitized image output. - Stores avatar originals using deterministic names:
public/uploads/user/avatar/{user_string}.{extension}. - Generates companion avatar thumbnails as
public/uploads/user/avatar/{user_string}_thumb.jpg. - If avatar exceeds
120x120, thumb is center-cropped/resized to120x120JPEG. - If avatar is
<=120x120, thumb file is a direct copy of the sanitized original. - Saves through
UserWrite::save(...). - Removes superseded avatar file when avatar changes/removal succeeds.
userDelete(array $post)- Owned by
UserEditController. - Validates CSRF.
- Blocks self-delete in both single and bulk flows.
- Supports bulk delete with deleted/failed/skipped counters.
userInvites()- Owned by
UserListController. - Requires login +
Manage Users. - Renders invite token admin/list view.
userInvitesCreate(array $post)/userInvitesGenerate(array $post)/userInvitesDelete(array $post)- Owned by
UserInviteController. - Validate CSRF and mutate invite-token rows through
InviteWrite. Public\AuthController::login()/loginSubmit(array $post)/loginTwoFactor()/loginTwoFactorSubmit(array $post)/loginTwoFactorSelect(array $post)- Render and process the public login + login-time 2FA screens.
- Persist a sanitized post-login redirect target in
LoginUiState. - Reuse shared login-attempt throttling and challenge workflow services.
Public\AuthController::loginTwoFactorWebauthnOptions(array $post)/loginTwoFactorWebauthnVerify(array $post)- Provide JSON WebAuthn assertion options and verification for public login-time 2FA.
Public\AuthController::register()/registerSubmit(array $post)- Render and process the public registration screen.
- Enforces
user.auth.registrationmode (open|invite|closed). - Applies configured public captcha validation before user creation when
captcha.provideris enabled. - Reuses the shared brute-force policy window/lock settings to temporarily lock repeated failed registration attempts per client IP.
- Requires invite token when mode is
invite. - Keeps duplicate-account and persistence failures user-generic instead of reflecting raw repository exception text.
- Creates user via
UserWrite::save(...)and consumes invite token atomically where possible. Public\ProfileController::profile(string $username)- public profile routes use the selector configured by
user.selector - selector mode
iduses numeric user ids - selector mode
usernameuses usernames and is only valid when username login mode is enabled - selector mode
stringuses each user's generated random alphanumericstring Public\GroupController::group(string $groupSlug)- public group routes render one group plus its public member list using the configured
group.prefix. - Guest-facing profile/group payloads suppress username output entirely when the install is configured for email login.
Data Model And Repository Behavior
UserRead + UserWrite behavior:
UserRead::listAll()loads users and joins group names intogroups_textsummaries.UserRead::findById()returns user + assignedgroup_ids.UserWrite::save(...)handles create/update in one method:- enforces unique username/email
- generates and persists a unique random alphanumeric
stringfor each user when missing - honors config key
user.stringas the target generated string length - persists plaintext
biowith the max length capped by config keyuser.bio - hashes password when provided
- persists optional local
cover_imagefilename - updates avatar filename when
set_avataris true - writes Delight-compatible auth fields on create
- replaces group memberships via
setUserGroups(...) UserWrite::setUserGroups(...)is replace-all transactional membership sync.UserWrite::deleteById(...)removes user-group memberships and then deletes auth row.
Storage detail:
- Auth user rows are stored in auth database handle/tables.
- Group memberships are stored in app database handle/tables (
user_groups). - SQLite mode maps group tables through
groups.*aliases. - User avatars are stored locally under
public/uploads/user/avatar/using the user string as the filename base. - User cover images are stored locally under
public/uploads/user/cover/using the user string as the filename base. - Avatar thumbs live alongside the avatar original with the
_thumb.jpgsuffix.
Security/Validation Expectations
- Permission gate:
Manage Users. - CSRF on POST actions.
- Public registration uses the shared captcha provider config and shared brute-force window/lock settings.
- Guest-facing public profile/group views suppress username-derived output when
user.auth.login=email. - Sanitization via centralized
InputSanitizer. - Avatar checks are centralized in
AvatarValidator; uploads are re-encoded/sanitized before final storage. - Repository operations use prepared statements.
Update Discipline
When user behavior changes, update this document in the same task. That includes list/editor UI controls, assignment and promotion rules, avatar handling, save/delete semantics, and membership sync behavior.
UI Labels Reference
ProfileChange PasswordConfirm PasswordEnter new password again to confirm:Contact InformationAdd More Contact InformationTwo-Factor MethodsDetailsRecovery PhraseReusableValueNextPrevious