- Introduced a new module for managing user groups (`usrgrp`) in the administration interface. - Implemented CRUD operations for user groups: list, create, rename, and delete. - Added authorization checks to ensure only users with appropriate permissions can manage groups. - Created corresponding frontend JavaScript and SCSS files for the new module. - Updated documentation to reflect the new group management functionality and its authorization model. - Added SQL stored procedures for group management operations. - Established a new login script for database access, ensuring proper security practices.
140 lines
10 KiB
Markdown
140 lines
10 KiB
Markdown
---
|
|
status: Active
|
|
lastUpdated: 2026-09-16
|
|
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"
|
|
- "0020-user-management-create-and-delete.md"
|
|
- "0021-user-group-management.md"
|
|
---
|
|
|
|
# User Management module (`usr`)
|
|
|
|
## Summary
|
|
The **Benutzerverwaltung** module lets a privileged operator see every non-deleted intranet
|
|
user account (name + email + active/disabled), create a new account, delete an existing one,
|
|
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, [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), and
|
|
[0020](../Decisions/0020-user-management-create-and-delete.md) for create/delete.
|
|
|
|
## 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 (incl. `groups` csv)
|
|
POST /do/usr/list {useraccount_id} → { users: [one] } single-row refresh (refreshRow)
|
|
base ribbon (eM) → "Neuer Benutzer" (always available, not just when a row is selected)
|
|
POST /do/usr/create {name, firstname, email, title?, gender?, mobile?} → { ok, useraccount_id, emailed } | 409 duplicate_email
|
|
row click → per-row ribbon (iMn): Passwort zurücksetzen / E-Mail ändern / Gruppenzugehörigkeit / Benutzer löschen
|
|
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 }
|
|
POST /do/usr/delete {useraccount_id} → { ok } | 409 cannot_delete_self
|
|
```
|
|
|
|
### Backend (`IntranetController.AdminUsers.cs`)
|
|
- `list` → `fis_admin_getUserAccountList(@authaccount, @useraccount_id, NULL, NULL)`, all non-deleted
|
|
accounts with decrypted name/email/disabled flag plus a `groups` column — a `', '`-separated,
|
|
alphabetically ordered csv of the account's active group memberships (`STRING_AGG` over
|
|
decrypted `fis_usergroups.name_enc`, deleted groups/memberships excluded). An optional
|
|
`useraccount_id` form field narrows the result to one account so the frontend can refresh a
|
|
single row without re-rendering the grid.
|
|
- `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. The group catalog itself (create/
|
|
rename/delete) is managed in the separate `usrgrp` module — see
|
|
[admin-group-management.md](admin-group-management.md).
|
|
- `groupset` → calls `fis_admin_addGroupMember`/`remGroupMember` depending on the posted
|
|
`member` flag — applied immediately per checkbox toggle, no separate save step.
|
|
- `create` → `fis_admin_createUserAccount(@authaccount, @name, @firstname, @title, @gender,
|
|
@email, @mobile, @password, @enc_key)`. Only name/firstname/email are required from the
|
|
dialog; the admin may leave password blank (the proc auto-generates one and returns it as
|
|
`newpassword`, mirroring `fis_admin_resetUserPassword`'s pattern). The controller then emails
|
|
the new account's credentials via `BuildWelcomeEmail` — same "emailed, never shown on screen"
|
|
rule as `pwdreset` — and returns `emailed` so the UI can tell the admin whether delivery
|
|
succeeded. Duplicate email → 409 `duplicate_email`, same mapping as `email`.
|
|
- `delete` → `fis_admin_delUserAccount(@authaccount, @useraccount_id)` (soft-delete: sets
|
|
`UserDeleted`/`DateDeleted`). The controller rejects a self-delete with 409
|
|
`cannot_delete_self` before even calling SQL; the proc enforces the same guard independently.
|
|
See [0020](../Decisions/0020-user-management-create-and-delete.md) for why both procs needed
|
|
the `intranet_admin` auth gate added when they were wired up.
|
|
|
|
### 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 a card-style row grid (avatar
|
|
initials badge, name/email/groups columns, a status pill) rather than a plain `<table>`, with
|
|
each row produced by `buildRow(rw)`. Changes that only affect one account (`emailDlg`, every
|
|
checkbox toggle in `groupsDlg`) call `refreshRow(rw)`, which re-posts `usr/list` with that
|
|
`useraccount_id` and swaps the row in place — selection and the per-row ribbon survive, and the
|
|
groups csv is always current. Create/delete/password-reset still do a full `renderLst`. A row click
|
|
toggles `.selected` and swaps the `#topbar` ribbon between the base ribbon (`eM`: "Neuer
|
|
Benutzer" always available, plus "Neu Laden" once a row was selected) and the four per-row
|
|
actions (`iMn`: Passwort zurücksetzen / E-Mail ändern / Gruppenzugehörigkeit / Benutzer löschen).
|
|
`pwdResetDlg`/`emailDlg`/`groupsDlg`/`createDlg` use `$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`; `deleteUser` instead follows Banking's
|
|
`confirm(...)`-before-destructive-action pattern (`fis.bam.js`'s `smd`/`mbd`) since there's
|
|
nothing to collect beyond the confirmation itself.
|
|
|
|
## Key files
|
|
- `Fuchs/Controllers/IntranetController.AdminUsers.cs` — `Do_Process_AdminUsers` dispatch + `BuildPasswordResetEmail`/`BuildWelcomeEmail` + `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`, `fis_admin_createUserAccount.sql`, `fis_admin_delUserAccount.sql` — write-side user/group procs.
|
|
- `Fuchs.Tests/AdminUserManagementTests.cs` — password-reset and welcome-email content tests.
|
|
|
|
## Related decisions
|
|
- [0014 — Admin user management (original decisions, still in force)](../Decisions/0014-admin-user-management.md)
|
|
- [0020 — Wire up create/delete, welcome email on create, guard delete](../Decisions/0020-user-management-create-and-delete.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)
|