Files
Fuchs_Intranet/Fuchs/Docs/Decisions/0014-admin-user-management.md
T
StefanandClaude Sonnet 5 076290cbeb
Playwright Tests / test (push) Canceled after 0s
Split Admin module into Status and User Management under one sidemenu dropdown
Splits fn=admin into two independent modules: the original status
dashboard stays at fn=admin, user management moves to a new fn=usr
(IntranetController.AdminUsers.cs, fis.usr.js). Both are now reached
through a single "Administration" sidemenu dropdown (Systemüberwachung /
Benutzerverwaltung) gated by one shared fds_sys > 4 check, per ADR 0015
(supersedes 0014's "fold into one module" choice).

Also fixes the password-reset validation to check only a plain 6-char
minimum instead of silently also requiring ValidatePassword's default
2-uppercase/2-lowercase/2-digit composition, which contradicted the
"mind. 6 Zeichen" message shown to the admin.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 12:09:44 +02:00

5.4 KiB

status, date, applyTo, supersededBy
status date applyTo supersededBy
Superseded 2026-09-09
Fuchs/Controllers/IntranetController.Admin.cs
Fuchs/js/intranet/modules/fis.admin*.js
Fuchs/js/intranet/modules/fis.admin.scss
Fuchs_Database/dbo/Functions/fis_admin_*.sql
Fuchs_Database/dbo/Stored Procedures/fis_admin_*.sql
0015-split-admin-user-management-module.md

0014 — Admin user management: folded into the admin module, dual auth axis, password always emailed

Context

An operator needs to see the list of non-deleted intranet user accounts (name + email), and per account reset the password, change the email address, and view/change group assignments. The database layer for this already existed (fis_admin_getUserAccountList, fis_admin_getUserGroupList, fis_admin_setUserAccount, fis_admin_resetUserPassword, fis_admin_addGroupMember/remGroupMember) but was completely unwired from any controller. Those procs gate on a different authorization mechanism than the rest of the app: a per-user intranet_admin setting read via fis_admin_getUserAuth(@authaccount) > 1, entirely independent of the fis_getModuleAuth/fis_module_auth mechanism the rest of the app (including the existing Admin module, see 0011) uses.

Decision

  • Fold user management into the existing admin module (fn=admin) rather than create a new top-level module. New id cases (users, userpwdreset, useremail, usergroups, usergroupset) sit in Do_Process_Admin alongside the system-status diagnostics, behind the same fds_sys > 4 server-side gate as the rest of that module.
  • Known, accepted gap: two independent authorization axes. fds_sys > 4 controls whether the endpoints are reachable at all; the underlying fis_admin_* SQL objects separately require the caller's intranet_admin setting to be > 1 (fis_admin_getUserAuth). An account with fds_sys > 4 but no intranet_admin setting will reach these endpoints but get empty result sets / no-op writes from SQL — not an error, just silently nothing. Operational consequence: any account meant to do user management must be provisioned with both fds_sys > 4 and intranet_admin >= 2 (via fis_useraccounts_settings). This was a deliberate simplicity trade-off (see Alternatives) rather than unifying the two permission systems.
  • Password reset always emails the result; never displays it. The ribbon's "Passwort zurücksetzen" action lets the admin either type a specific new password or leave the field blank to auto-generate one (fis_admin_resetUserPassword gained an optional @newpassword parameter for this). Either way, the resulting plaintext password is sent only to the target user's stored email address via IComService (respecting the Fuchs:Email:OverrideRecipient dev safety net) — the admin never sees it on screen.
  • Change email reuses fis_admin_setUserAccount (prefilling the other fields from the existing row, @password = NULL so the update path leaves the password untouched) instead of adding a narrower proc — its WHEN MATCHED branch never touches password_enc, and its existing duplicate-email THROW 60001 is mapped to an HTTP 409 in the controller.
  • New fis_admin_getUserGroupMembership function returns every group plus a member bit for one target user (mirrors fis_admin_getUserGroupList's auth-gate style) — needed for the per-user group checkbox dialog; toggling a checkbox calls fis_admin_addGroupMember/ remGroupMember immediately (no separate save step, consistent with the rest of the module's ribbon actions).
  • Per the exception-user-notification rule, a failed password reset, a failed reset-email delivery, or a target with no usable email all call IEventService.UserIssueAsync so the admin is never left believing an operation succeeded silently.

Consequences

  • Deploying/onboarding an admin for user management is now a two-step provisioning task (fds_sys and intranet_admin), not obvious from the UI — document this whenever granting the permission.
  • fis_admin_resetUserPassword's signature changed (added optional @newpassword) and its result set gained a newpassword column; it was previously unwired from any caller, so this is a safe in-place change.
  • The Admin module's frontend now mixes two concerns (system diagnostics + user management) under one ribbon/menu entry; if user management grows substantially, revisit splitting it into its own module (see Alternatives).

Alternatives considered

  • Unify the two authorization axes (e.g. make fis_admin_* procs check fis_getModuleAuth instead of fis_admin_getUserAuth, or vice versa). Rejected for this change — it would touch every existing fis_admin_* proc/function (all already gate on fis_admin_getUserAuth) for a feature that only needed to wire up what already existed; revisit if a real conflict surfaces in practice.
  • New standalone top-level module (fn=usr) with its own menu button and its own fis_admin_getUserAuth-based gate. Rejected per explicit user direction — folding into the existing Administration module was preferred over a second admin-ish menu entry.
  • Show the new/reset password on screen instead of emailing it. Rejected per explicit user direction — the password must only ever reach the user's own inbox, never be visible to the admin performing the reset.