Playwright Tests / test (push) Canceled after 0s
Wire up two previously-unwired stored procedures for the usr module: - fis_admin_createUserAccount gained the missing @authaccount + intranet_admin auth gate, a duplicate-email check, and now returns the (possibly auto-generated) password so it can be emailed to the new user, mirroring fis_admin_resetUserPassword's pattern. - fis_admin_delUserAccount gained the same missing auth gate plus a guard against an admin deleting their own account. New /do/usr/create and /do/usr/delete endpoints in IntranetController.AdminUsers.cs, a "Neuer Benutzer" button and a "Benutzer löschen" ribbon action in fis.usr.js, and unit tests for the new welcome-email content (BuildWelcomeEmail). Also restyle the user list (fis.usr.scss) from a plain bordered table to a card-style row grid with avatar-initial badges and status pills, matching the Admin dashboard's visual language. Documented as ADR 0020 and in the admin-user-management concept doc. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.2 KiB
6.2 KiB
status, date, applyTo, supersededBy
| status | date | applyTo | supersededBy | |||||
|---|---|---|---|---|---|---|---|---|
| Accepted | 2026-09-14 |
|
0020 — User management: wire up create/delete, welcome email on create, guard delete
Context
The usr module (see 0015) let an admin list
users, reset a password, change an email address, and manage group membership, but had no way
to add a new account or remove one — the admin had to do that directly in the database. Two
stored procedures for this already existed in Fuchs_Database but were completely unwired from
any controller: fis_admin_createUserAccount and fis_admin_delUserAccount. Neither followed
the module's established conventions:
fis_admin_createUserAccounttook no@authaccountand performed nointranet_adminauthorization check at all (every other write in this module checksfis_admin_getUserAuth(@authaccount) > 1, per 0014's dual auth axis), had no duplicate-email guard (fis_admin_setUserAccountdoes), and generated the initial password inline in theINSERTwithout ever returning it — so nothing calling it could email the new user their credentials.fis_admin_delUserAccountlikewise had nointranet_admincheck, and nothing stopped an admin from deleting their own account.
Decision
createanddeleteare newidcases inDo_Process_AdminUsers, behind the samefds_sys > 4gate as every other action in the module.fis_admin_createUserAccountwas brought in line with its siblings rather than reused viafis_admin_setUserAccount's existing "insert when@useraccount_idis NULL" branch (which would have been the smaller diff): it gained@authaccount+ the standardfis_admin_getUserAuth(@authaccount) > 1gate, the same duplicate-email checkfis_admin_setUserAccountalready has, and — mirroringfis_admin_resetUserPassword's existing pattern — resolves@password(auto-generating one viaocms_fn_generatePasswordwhen the admin leaves it blank) before encrypting it, and returns it in the result set asnewpasswordalongside the created row.fis_admin_setUserAccount's insert branch was left alone (still reachable in principle, but has no caller) since changing its shared behavior risked the existingemailaction; a dedicated create proc with an explicit contract was judged clearer than overloading an update proc's edge case.- New account creation always emails the credentials, never displays them — identical
handling to password reset (0014):
HandleLogin-styleIComService.SendEmailAsync(respectingFuchs:Email:OverrideRecipient) sends name, email, and password viaIntranetController.BuildWelcomeEmail; on delivery failure the admin gets anIEventService.UserIssueAsyncnotification per 0003, matchingpwdreset's existingemailedflag semantics. fis_admin_delUserAccountgained the sameintranet_admingate (previously entirely missing — a real authorization hole once the proc is reachable from a controller) and a self-delete guard (@authaccount <> @useraccount_id), enforced at both the SQL layer (belt) and the controller (suspenders — returns 409cannot_delete_selfwithout a round-trip when the caller targets their own account).- Frontend:
fis.usr.js's base ribbon gained an always-visible "Neuer Benutzer" button (createDlg, a$ocms.dlgformcollecting Vorname/Nachname/E-Mail required, Titel/Anrede/Mobil optional); the per-row ribbon gained "Benutzer löschen" (deleteUser), using the sameconfirm(...)guard pattern Banking'sfis.bam.jsuses before destructive actions (smd/mbd) rather than a modal dialog, since delete has no fields to collect.
Consequences
fis_admin_createUserAccount's signature changed (@authaccountadded as the first parameter) and its result set gained anewpasswordcolumn; safe in-place change since it had no existing caller.fis_admin_delUserAccountnow silently no-ops (same "no-op instead of error" style as every otherfis_admin_*proc in this module) for a caller withoutintranet_admin >= 2or who targets their own account — the controller's owncannot_delete_selfcheck makes the self-delete case surface as a clear error to the admin instead of a silent no-op, but a caller lackingintranet_adminstill only sees an unexplained failure, consistent with the accepted gap from 0014.- Delete is a soft-delete (
fis_admin_delUserAccountalready setDateDeleted/UserDeletedbefore this change) — deleted accounts simply stop appearing infis_admin_getUserAccountListand can only be restored directly in the database; no "undo" is exposed in the UI. - The list view (
fis.usr.scss) was restyled from a plain bordered<table>to a card-style row grid (avatar-initial badge, status pill, hover/selection highlighting) matching the visual languagefis.admin.scssalready established (adm_pill,adm_card) — a pure presentation change, the DOM is still built by the samerenderLst/$ocms.postXT('usr/list')flow.
Alternatives considered
- Reuse
fis_admin_setUserAccountfor create by calling it with@useraccount_id = NULL. Rejected — its insert branch doesn't return the generated password (nothing to email to the new user), and giving one endpoint two very different response shapes depending on a hidden parameter was judged more surprising than a small, explicitcreateproc. - Hard-delete instead of soft-delete. Not considered —
fis_admin_delUserAccountalready implemented soft-delete before this change and every other user-facing list in the app (fis_admin_getUserAccountListet al.) already filters onDateDeleted is null; changing to a hard delete would be an unrelated, larger change. - Let an admin delete their own account and rely on another admin to recover. Rejected — trivially avoidable lockout risk with no upside.