--- status: Active lastUpdated: 2026-09-09 applyTo: - "Fuchs/Controllers/IntranetController.AdminUsers.cs" - "Fuchs/js/intranet/modules/fis.usr*.js" - "Fuchs/js/intranet/modules/fis.usr.scss" - "Fuchs/js/intranet/fis_main_menu.js" - "Fuchs_Database/dbo/Functions/fis_admin_*.sql" - "Fuchs_Database/dbo/Stored Procedures/fis_admin_*.sql" relatedDecisions: - "0014-admin-user-management.md" - "0015-split-admin-user-management-module.md" --- # User Management module (`usr`) ## Summary The **Benutzerverwaltung** module lets a privileged operator see every non-deleted intranet user account (name + email + active/disabled), reset a password, change an email address, and view/change group assignments. It shares its sidemenu-visibility and server-side authorization gate with the separate Admin/status-dashboard module (`fds_sys > 4`) but is otherwise fully independent — own `fn=usr`, own frontend bundle, own controller partial. See [0015](../Decisions/0015-split-admin-user-management-module.md) for why these were split from one module into two, and [0014](../Decisions/0014-admin-user-management.md) for the original decisions this one carries forward (password always emailed, dual auth axis, reuse of `fis_admin_setUserAccount` for email changes). ## How it works ### Authorization (two independent axes — read this before granting access) 1. **Menu visibility + `fds_sys` gate** — identical mechanism to the Admin module: `fis_main_menu.js#addAdminMenuButtonsIfAuthorized` checks `fds_sys > 4` once and, if authorized, adds a single "Administration" sidemenu dropdown containing this module's "Benutzerverwaltung" (`init:usr`) entry alongside Admin's "Systemüberwachung" (`init:admin`) entry. Every `/do/usr/*` endpoint re-checks `fds_sys > 4` server-side (`Do_Process_AdminUsers`, reusing `AdminMinAuthExclusive`/`GetSystemAdminAuthAsync` from `IntranetController.Admin.cs`) and 401s otherwise — defense in depth, same as Admin. 2. **Separate SQL-layer gate** — every `fis_admin_*` proc/function called below independently checks the caller's `intranet_admin` setting via `fis_admin_getUserAuth(@authaccount) > 1` (a `fis_useraccounts_settings` row, unrelated to `fis_getModuleAuth`/`fis_module_auth`). An account with `fds_sys > 4` but no `intranet_admin` setting reaches every `usr` endpoint but gets **empty results / silent no-op writes** from SQL — not an error. **Any account meant to do user management needs both `fds_sys > 4` and `intranet_admin >= 2`.** ### Request flow ``` click "Benutzerverwaltung" → $ocms.init('usr') POST /do/usr/auth → { manage, level } (manage>0 ⇒ authorized) load /web/fis.usr.de.js + /web/fis.usr.css $ocms.usr.init2() → init3() → renderLst() POST /do/usr/list → { users: [...] } grid of every non-deleted account row click → per-row ribbon (iMn): Passwort zurücksetzen / E-Mail ändern / Gruppenzugehörigkeit POST /do/usr/pwdreset {useraccount_id, newpassword?} → { ok, emailed } POST /do/usr/email {useraccount_id, email} → { ok } | 409 duplicate_email POST /do/usr/groups {useraccount_id} → { groups: [...] } POST /do/usr/groupset {useraccount_id, usergroup_id, member} → { ok, member } ``` ### Backend (`IntranetController.AdminUsers.cs`) - `list` → `fis_admin_getUserAccountList(@authaccount, NULL, NULL, NULL)`, all non-deleted accounts with decrypted name/email/disabled flag. - `pwdreset` → the admin may type a specific password or leave it blank; validated with a plain `ValidatePassword(minLength: 6, numUpper: 0, numLower: 0, numNumbers: 0, numSpecial: 0)` — no hidden composition requirements, matching what the UI actually tells the admin (a prior bug had this call still requiring `OCORE.commons.ValidatePassword`'s default 2-upper/2-lower/ 2-digit minimums despite the "mind. 6 Zeichen" message). Calls `fis_admin_resetUserPassword` (takes an optional `@newpassword`; `NULL` ⇒ the proc auto-generates one). On success, re-fetches the target's row (server-trusted email — never client-supplied) and emails the plaintext password via `IComService.SendEmailAsync` — **the admin never sees it**. Email subject/body built by `IntranetController.BuildPasswordResetEmail` (pure, unit-tested in `Fuchs.Tests/AdminUserManagementTests.cs`). Any unexpected failure (SQL throw, email non-delivery, no usable email on file) calls `IEventService.UserIssueAsync` so the admin gets a visible notification either way, per [0003](../Decisions/0003-interrupting-exceptions-notify-the-user.md). - `email` → fetches the current row and calls `fis_admin_setUserAccount` with only the email changed (`@password = NULL`, so the `WHEN MATCHED` update path leaves `password_enc` alone). A duplicate email (`THROW 60001` in the proc) surfaces as HTTP 409 `{ error: "duplicate_email" }`. - `groups` → the new `fis_admin_getUserGroupMembership(@authaccount, @useraccount_id)` function returns every group plus a `member` bit for that one user. - `groupset` → calls `fis_admin_addGroupMember`/`remGroupMember` depending on the posted `member` flag — applied immediately per checkbox toggle, no separate save step. ### Frontend (`fis.usr.js` + `fis.usr_txt_de.js` + `fis.usr.scss`) A self-contained list+ribbon module modeled on Banking (`fis.bam.js`)'s `eM`/`iMn` pattern rather than the Admin dashboard's card layout: `renderLst` builds the table via `$fis.frm_list`/`$$.tblset`; a row click toggles `.selected` and swaps the `#topbar` ribbon between the base "Neu Laden" button (`eM`) and the three per-row actions (`iMn`). Each action is a dialog (`pwdResetDlg`/`emailDlg`/`groupsDlg`) using `$ocms.dlgform`/`$ocms.dlg` with a manual `submit`/`postXT` (not the built-in `success`-URL wiring) so a 409's `error` code can be mapped to a specific German message via `$uct.err`. ## Key files - `Fuchs/Controllers/IntranetController.AdminUsers.cs` — `Do_Process_AdminUsers` dispatch + `BuildPasswordResetEmail` + `NotifyAdminUserActionFailedAsync`. - `Fuchs/Controllers/IntranetController.Admin.cs` — shared `AdminMinAuthExclusive`/`GetSystemAdminAuthAsync`/`AdminJson` this module reuses. - `Fuchs/js/intranet/modules/fis.usr.js`, `fis.usr_txt_de.js`, `fis.usr.scss` — the module, texts, styles (bundled via `bdlconfig.json`, `intranet:usr` context). - `Fuchs/js/intranet/fis_main_menu.js` (`addAdminMenuButtonsIfAuthorized`) + `fis_main_go.js` — conditional menu buttons (this module and `admin`). - `Fuchs_Database/dbo/Functions/fis_admin_getUserAccountList.sql`, `fis_admin_getUserGroupList.sql`, `fis_admin_getUserGroupMembership.sql` — read-side user/group queries. - `Fuchs_Database/dbo/Stored Procedures/fis_admin_resetUserPassword.sql`, `fis_admin_setUserAccount.sql`, `fis_admin_addGroupMember.sql`, `fis_admin_remGroupMember.sql` — write-side user/group procs. - `Fuchs.Tests/AdminUserManagementTests.cs` — password-reset email content tests. ## Related decisions - [0014 — Admin user management (original decisions, still in force)](../Decisions/0014-admin-user-management.md) - [0015 — Split Admin into separate Status/User-Management modules](../Decisions/0015-split-admin-user-management-module.md) - [0003 — Interrupting exceptions notify the user](../Decisions/0003-interrupting-exceptions-notify-the-user.md)