From af10a5fdeeb026239e64209a4276e0686c3f8aa2 Mon Sep 17 00:00:00 2001 From: Md Bayazid Bostame Date: Thu, 26 Mar 2026 10:34:31 +0100 Subject: [PATCH] snapshot: preserve auth invite flow and password reset UX cleanup --- backend/config/urls.py | 34 +- backend/locale/en/LC_MESSAGES/django.mo | Bin 29845 -> 31194 bytes backend/locale/en/LC_MESSAGES/django.po | 366 ++++++++++++------ backend/workflows/forms.py | 40 +- .../registration/password_reset_complete.html | 22 ++ .../registration/password_reset_confirm.html | 45 +++ .../registration/password_reset_done.html | 22 ++ .../registration/password_reset_form.html | 28 ++ .../templates/workflows/auth/login.html | 39 ++ .../auth/password_reset_complete.html | 22 ++ .../auth/password_reset_confirm.html | 47 +++ .../workflows/auth/password_reset_done.html | 22 ++ .../workflows/auth/password_reset_form.html | 29 ++ .../workflows/developer_handbook.html | 2 +- .../templates/workflows/project_wiki.html | 2 +- .../templates/workflows/user_management.html | 13 +- backend/workflows/views.py | 72 ++-- 17 files changed, 635 insertions(+), 170 deletions(-) create mode 100644 backend/workflows/templates/registration/password_reset_complete.html create mode 100644 backend/workflows/templates/registration/password_reset_confirm.html create mode 100644 backend/workflows/templates/registration/password_reset_done.html create mode 100644 backend/workflows/templates/registration/password_reset_form.html create mode 100644 backend/workflows/templates/workflows/auth/login.html create mode 100644 backend/workflows/templates/workflows/auth/password_reset_complete.html create mode 100644 backend/workflows/templates/workflows/auth/password_reset_confirm.html create mode 100644 backend/workflows/templates/workflows/auth/password_reset_done.html create mode 100644 backend/workflows/templates/workflows/auth/password_reset_form.html diff --git a/backend/config/urls.py b/backend/config/urls.py index a6b96bc..12c21f2 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -1,12 +1,44 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib import admin +from django.contrib.auth import views as auth_views from django.urls import include, path +from workflows.forms import AppAuthenticationForm, AppPasswordResetForm, AppSetPasswordForm + urlpatterns = [ path('admin/', admin.site.urls), path('i18n/', include('django.conf.urls.i18n')), - path('accounts/', include('django.contrib.auth.urls')), + path( + 'accounts/login/', + auth_views.LoginView.as_view(template_name='workflows/auth/login.html', authentication_form=AppAuthenticationForm), + name='login', + ), + path( + 'accounts/logout/', + auth_views.LogoutView.as_view(), + name='logout', + ), + path( + 'accounts/password_reset/', + auth_views.PasswordResetView.as_view(template_name='workflows/auth/password_reset_form.html', form_class=AppPasswordResetForm), + name='password_reset', + ), + path( + 'accounts/password_reset/done/', + auth_views.PasswordResetDoneView.as_view(template_name='workflows/auth/password_reset_done.html'), + name='password_reset_done', + ), + path( + 'accounts/reset///', + auth_views.PasswordResetConfirmView.as_view(template_name='workflows/auth/password_reset_confirm.html', form_class=AppSetPasswordForm), + name='password_reset_confirm', + ), + path( + 'accounts/reset/done/', + auth_views.PasswordResetCompleteView.as_view(template_name='workflows/auth/password_reset_complete.html'), + name='password_reset_complete', + ), path('', include('workflows.urls')), ] diff --git a/backend/locale/en/LC_MESSAGES/django.mo b/backend/locale/en/LC_MESSAGES/django.mo index f50c5ec5ece6db6f058e9581e46e90701a8a0cec..f69d1a631a88264e9e713cee9cf78c091bc88852 100644 GIT binary patch delta 9787 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 delta 8467 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 V1>TgxpZ&c{$~ro{2g^fz{s({f-;n?S diff --git a/backend/locale/en/LC_MESSAGES/django.po b/backend/locale/en/LC_MESSAGES/django.po index 04ef874..af32b22 100644 --- a/backend/locale/en/LC_MESSAGES/django.po +++ b/backend/locale/en/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: tubco-portal\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-26 09:06+0000\n" +"POT-Creation-Date: 2026-03-26 09:25+0000\n" "PO-Revision-Date: 2026-03-24 00:00+0000\n" "Language: en\n" "MIME-Version: 1.0\n" @@ -55,52 +55,54 @@ msgstr "" msgid "Remote Backup in Nextcloud konnte nicht gelöscht werden." msgstr "" -#: workflows/forms.py:102 -msgid "Vorname" -msgstr "" - -#: workflows/forms.py:103 -msgid "Nachname" -msgstr "" - -#: workflows/forms.py:104 workflows/templates/workflows/user_management.html:81 +#: workflows/forms.py:103 workflows/forms.py:128 +#: workflows/templates/workflows/user_management.html:72 msgid "Benutzername" msgstr "" -#: workflows/forms.py:105 +#: workflows/forms.py:104 +msgid "Passwort" +msgstr "Password" + +#: workflows/forms.py:108 workflows/forms.py:129 #, fuzzy #| msgid "E-Mail" msgid "E-Mail-Adresse" msgstr "Email" -#: workflows/forms.py:106 workflows/templates/workflows/user_management.html:83 -#: workflows/templates/workflows/user_management.html:102 +#: workflows/forms.py:113 workflows/templates/workflows/user_management.html:77 +#: workflows/templates/workflows/user_management.html:108 +msgid "Neues Passwort" +msgstr "New password" + +#: workflows/forms.py:119 +msgid "Neues Passwort bestätigen" +msgstr "Confirm new password" + +#: workflows/forms.py:126 +msgid "Vorname" +msgstr "" + +#: workflows/forms.py:127 +msgid "Nachname" +msgstr "" + +#: workflows/forms.py:130 workflows/templates/workflows/user_management.html:74 +#: workflows/templates/workflows/user_management.html:93 #, fuzzy #| msgid "Rolle:" msgid "Rolle" msgstr "Role:" -#: workflows/forms.py:107 -msgid "Passwort" -msgstr "Password" - -#: workflows/forms.py:108 -msgid "Passwort bestätigen" -msgstr "Confirm password" - -#: workflows/forms.py:121 +#: workflows/forms.py:143 msgid "Dieser Benutzername ist bereits vergeben." msgstr "This username is already taken." -#: workflows/forms.py:130 workflows/views.py:433 +#: workflows/forms.py:152 workflows/views.py:472 msgid "Ungültige Rolle." msgstr "Invalid role." -#: workflows/forms.py:138 -msgid "Die Passwörter stimmen nicht überein." -msgstr "The passwords do not match." - -#: workflows/forms.py:394 +#: workflows/forms.py:408 #, python-format msgid "" "Das Übergabedatum muss mindestens %(days)s Tage in der Zukunft liegen " @@ -465,28 +467,129 @@ msgstr "Handover / successor context reviewed: %(value)s" #: workflows/templates/registration/login.html:4 #: workflows/templates/registration/login.html:19 +#: workflows/templates/workflows/auth/login.html:4 +#: workflows/templates/workflows/auth/login.html:17 msgid "Anmeldung" msgstr "Sign in" #: workflows/templates/registration/login.html:20 +#: workflows/templates/workflows/auth/login.html:18 msgid "Bitte melden Sie sich mit Ihrem Benutzerkonto an." msgstr "Please sign in with your user account." #: workflows/templates/registration/login.html:30 +#: workflows/templates/workflows/auth/login.html:28 #, fuzzy #| msgid "Fehlgeschlagen" msgid "Anmeldung fehlgeschlagen" msgstr "Failed" #: workflows/templates/registration/login.html:31 +#: workflows/templates/workflows/auth/login.html:29 msgid "" "Benutzername oder Passwort sind nicht korrekt. Bitte versuchen Sie es erneut." msgstr "" #: workflows/templates/registration/login.html:37 +#: workflows/templates/workflows/auth/login.html:35 msgid "Anmelden" msgstr "Sign in" +#: workflows/templates/registration/password_reset_complete.html:4 +#: workflows/templates/registration/password_reset_complete.html:17 +#: workflows/templates/workflows/auth/password_reset_complete.html:4 +#: workflows/templates/workflows/auth/password_reset_complete.html:17 +msgid "Passwort gespeichert" +msgstr "Password saved" + +#: workflows/templates/registration/password_reset_complete.html:18 +#: workflows/templates/workflows/auth/password_reset_complete.html:18 +msgid "" +"Ihr Passwort wurde erfolgreich gesetzt. Sie können sich jetzt mit Ihrem " +"Benutzerkonto anmelden." +msgstr "Your password has been set successfully. You can now sign in with your account." + +#: workflows/templates/registration/password_reset_complete.html:19 +#: workflows/templates/registration/password_reset_confirm.html:41 +#: workflows/templates/registration/password_reset_done.html:19 +#: workflows/templates/workflows/auth/password_reset_complete.html:19 +#: workflows/templates/workflows/auth/password_reset_confirm.html:44 +#: workflows/templates/workflows/auth/password_reset_done.html:19 +msgid "Zur Anmeldung" +msgstr "Back to sign in" + +#: workflows/templates/registration/password_reset_confirm.html:4 +#: workflows/templates/registration/password_reset_confirm.html:18 +#: workflows/templates/workflows/auth/password_reset_confirm.html:4 +#: workflows/templates/workflows/auth/password_reset_confirm.html:18 +msgid "Passwort festlegen" +msgstr "Set password" + +#: workflows/templates/registration/password_reset_confirm.html:19 +#: workflows/templates/workflows/auth/password_reset_confirm.html:19 +msgid "Bitte vergeben Sie jetzt ein neues Passwort für Ihr Konto." +msgstr "Please set a new password for your account now." + +#: workflows/templates/registration/password_reset_confirm.html:25 +#: workflows/templates/workflows/auth/password_reset_confirm.html:25 +msgid "Passwort konnte nicht gespeichert werden" +msgstr "Password could not be saved" + +#: workflows/templates/registration/password_reset_confirm.html:26 +#: workflows/templates/workflows/auth/password_reset_confirm.html:26 +msgid "Bitte prüfen Sie die beiden Passwortfelder und versuchen Sie es erneut." +msgstr "Please check both password fields and try again." + +#: workflows/templates/registration/password_reset_confirm.html:36 +#: workflows/templates/workflows/auth/password_reset_confirm.html:39 +msgid "Passwort speichern" +msgstr "Save password" + +#: workflows/templates/registration/password_reset_confirm.html:39 +#: workflows/templates/workflows/auth/password_reset_confirm.html:42 +msgid "Link ungültig" +msgstr "Invalid link" + +#: workflows/templates/registration/password_reset_confirm.html:40 +#: workflows/templates/workflows/auth/password_reset_confirm.html:43 +msgid "" +"Dieser Link ist nicht mehr gültig. Bitte fordern Sie einen neuen Passwort-" +"Link an." +msgstr "This link is no longer valid. Please request a new password link." + +#: workflows/templates/registration/password_reset_done.html:4 +#: workflows/templates/registration/password_reset_done.html:17 +#: workflows/templates/workflows/auth/password_reset_done.html:4 +#: workflows/templates/workflows/auth/password_reset_done.html:17 +msgid "E-Mail versendet" +msgstr "Email sent" + +#: workflows/templates/registration/password_reset_done.html:18 +#: workflows/templates/workflows/auth/password_reset_done.html:18 +msgid "" +"Wenn ein passendes Konto existiert, wurde ein Passwort-Link an die " +"hinterlegte E-Mail-Adresse verschickt." +msgstr "If a matching account exists, a password link has been sent to the stored email address." + +#: workflows/templates/registration/password_reset_form.html:4 +#: workflows/templates/registration/password_reset_form.html:17 +#: workflows/templates/workflows/auth/password_reset_form.html:4 +#: workflows/templates/workflows/auth/password_reset_form.html:17 +msgid "Passwort zurücksetzen" +msgstr "Reset password" + +#: workflows/templates/registration/password_reset_form.html:18 +#: workflows/templates/workflows/auth/password_reset_form.html:18 +msgid "" +"Geben Sie Ihre E-Mail-Adresse ein. Wenn ein Konto vorhanden ist, erhalten " +"Sie einen Passwort-Link." +msgstr "Enter your email address. If an account exists, you will receive a password link." + +#: workflows/templates/registration/password_reset_form.html:24 +#: workflows/templates/workflows/auth/password_reset_form.html:25 +msgid "Link anfordern" +msgstr "Request link" + #: workflows/templates/workflows/audit_log.html:4 #: workflows/templates/workflows/audit_log.html:15 #: workflows/templates/workflows/home.html:132 @@ -596,7 +699,7 @@ msgid "" msgstr "Create database and media backups and verify existing bundles safely." #: workflows/templates/workflows/backup_recovery.html:20 -#: workflows/templates/workflows/user_management.html:87 +#: workflows/templates/workflows/user_management.html:78 msgid "Aktionen" msgstr "Actions" @@ -716,7 +819,7 @@ msgstr "Delete this backup bundle?" #: workflows/templates/workflows/intro_builder.html:66 #: workflows/templates/workflows/intro_builder.html:102 #: workflows/templates/workflows/requests_dashboard.html:286 -#: workflows/templates/workflows/user_management.html:136 +#: workflows/templates/workflows/user_management.html:127 #: workflows/templates/workflows/welcome_emails.html:70 msgid "Löschen" msgstr "Delete" @@ -817,7 +920,7 @@ msgstr "Label (EN)" #: workflows/templates/workflows/form_builder.html:91 #: workflows/templates/workflows/integrations_setup.html:263 #: workflows/templates/workflows/intro_builder.html:65 -#: workflows/templates/workflows/user_management.html:84 +#: workflows/templates/workflows/user_management.html:75 msgid "Aktiv" msgstr "Active" @@ -1033,13 +1136,13 @@ msgstr "Nextcloud:" #: workflows/templates/workflows/home.html:40 #: workflows/templates/workflows/integrations_setup.html:60 -#: workflows/templates/workflows/user_management.html:112 +#: workflows/templates/workflows/user_management.html:103 msgid "aktiv" msgstr "active" #: workflows/templates/workflows/home.html:40 #: workflows/templates/workflows/integrations_setup.html:60 -#: workflows/templates/workflows/user_management.html:112 +#: workflows/templates/workflows/user_management.html:103 msgid "inaktiv" msgstr "inactive" @@ -1774,7 +1877,7 @@ msgstr "Employee" #: workflows/templates/workflows/onboarding_intro_session.html:27 #: workflows/templates/workflows/request_timeline.html:66 -#: workflows/templates/workflows/user_management.html:80 +#: workflows/templates/workflows/user_management.html:71 msgid "Name" msgstr "Name" @@ -1787,7 +1890,7 @@ msgid "Dienstliche E-Mail" msgstr "Work email" #: workflows/templates/workflows/onboarding_intro_session.html:31 -#: workflows/views.py:688 +#: workflows/views.py:708 msgid "Vertragsbeginn" msgstr "Contract start" @@ -2047,7 +2150,7 @@ msgstr "" #: workflows/templates/workflows/request_timeline.html:74 #: workflows/templates/workflows/requests_dashboard.html:190 -#: workflows/templates/workflows/user_management.html:82 +#: workflows/templates/workflows/user_management.html:73 msgid "E-Mail" msgstr "Email" @@ -2234,60 +2337,63 @@ msgstr "Super admins manage user accounts, roles, and active access." msgid "Benutzer anlegen" msgstr "Create user" -#: workflows/templates/workflows/user_management.html:64 -msgid "Benutzer erstellen" +#: workflows/templates/workflows/user_management.html:23 +msgid "" +"Nach dem Anlegen wird automatisch eine Zugangseinladung mit Passwort-Link " +"per E-Mail versendet." +msgstr "" + +#: workflows/templates/workflows/user_management.html:55 +#, fuzzy +#| msgid "Benutzer anlegen" +msgid "Benutzer anlegen und einladen" msgstr "Create user" -#: workflows/templates/workflows/user_management.html:72 +#: workflows/templates/workflows/user_management.html:63 msgid "Benutzerübersicht" msgstr "User overview" -#: workflows/templates/workflows/user_management.html:73 +#: workflows/templates/workflows/user_management.html:64 msgid "Rollen ändern, Zugriffe sperren oder ein neues Passwort setzen." msgstr "Change roles, block access, or set a new password." -#: workflows/templates/workflows/user_management.html:85 +#: workflows/templates/workflows/user_management.html:76 msgid "Letzte Anmeldung" msgstr "Last login" -#: workflows/templates/workflows/user_management.html:86 -#: workflows/templates/workflows/user_management.html:117 -msgid "Neues Passwort" -msgstr "New password" - -#: workflows/templates/workflows/user_management.html:96 +#: workflows/templates/workflows/user_management.html:87 msgid "Sie selbst" msgstr "You" -#: workflows/templates/workflows/user_management.html:118 +#: workflows/templates/workflows/user_management.html:109 msgid "Optional" msgstr "Optional" -#: workflows/templates/workflows/user_management.html:124 +#: workflows/templates/workflows/user_management.html:115 msgid "Speichern" msgstr "Save" -#: workflows/templates/workflows/user_management.html:128 +#: workflows/templates/workflows/user_management.html:119 msgid "Reset-Link senden" msgstr "" -#: workflows/templates/workflows/user_management.html:131 +#: workflows/templates/workflows/user_management.html:122 #, fuzzy #| msgid "Welcome E-Mails" msgid "Keine E-Mail" msgstr "Welcome Emails" -#: workflows/templates/workflows/user_management.html:136 +#: workflows/templates/workflows/user_management.html:127 #, fuzzy #| msgid "Option wirklich löschen?" msgid "Benutzer wirklich löschen?" msgstr "Delete this option?" -#: workflows/templates/workflows/user_management.html:143 +#: workflows/templates/workflows/user_management.html:134 msgid "Es sind noch keine Benutzer vorhanden." msgstr "No users exist yet." -#: workflows/templates/workflows/user_management.html:149 +#: workflows/templates/workflows/user_management.html:140 msgid "" "Hinweis: Der aktuell angemeldete Super Admin kann sich hier nicht selbst " "deaktivieren oder auf eine niedrigere Rolle setzen." @@ -2397,7 +2503,7 @@ msgstr "Devices, software, and access" msgid "Notizen und Freigabe" msgstr "Notes and approval" -#: workflows/views.py:128 workflows/views.py:774 workflows/views.py:779 +#: workflows/views.py:128 workflows/views.py:794 workflows/views.py:799 msgid "Sie haben keine Berechtigung für diese Aktion." msgstr "You do not have permission for this action." @@ -2609,48 +2715,35 @@ msgstr "Request saved" msgid "Backup-Einstellungen gespeichert" msgstr "Save welcome settings" -#: workflows/views.py:407 -msgid "Benutzer konnte nicht erstellt werden. Bitte prüfen Sie die Eingaben." -msgstr "User could not be created. Please check the input." - -#: workflows/views.py:419 -#, python-format -msgid "Benutzer wurde erstellt: %(username)s" -msgstr "User created: %(username)s" - -#: workflows/views.py:437 -msgid "" -"Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren oder " -"herabstufen." -msgstr "" -"The currently signed-in super admin cannot lock or downgrade themselves here." - -#: workflows/views.py:440 -#, fuzzy -#| msgid "" -#| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " -#| "oder herabstufen." -msgid "" -"Der letzte aktive Super Admin kann nicht deaktiviert oder herabgestuft " -"werden." -msgstr "" -"The currently signed-in super admin cannot lock or downgrade themselves here." - -#: workflows/views.py:457 -#, python-format -msgid "Benutzer wurde aktualisiert: %(username)s" -msgstr "User updated: %(username)s" - -#: workflows/views.py:468 +#: workflows/views.py:400 msgid "Für diesen Benutzer ist keine E-Mail-Adresse hinterlegt." msgstr "" -#: workflows/views.py:477 +#: workflows/views.py:408 +#, python-format +msgid "Zugangseinladung für %(username)s" +msgstr "" + +#: workflows/views.py:410 +#, python-format +msgid "" +"Hallo %(name)s,\n" +"\n" +"für Sie wurde ein Benutzerkonto im TUBCO Onboarding- und Offboarding-Portal " +"angelegt.\n" +"Bitte öffnen Sie den folgenden Link, um Ihr Passwort zu setzen:\n" +"%(url)s\n" +"\n" +"Wenn Sie diese Einladung nicht erwartet haben, melden Sie sich bitte bei " +"Ihrem Administrator." +msgstr "" + +#: workflows/views.py:420 #, python-format msgid "Passwort zurücksetzen für %(username)s" msgstr "" -#: workflows/views.py:479 +#: workflows/views.py:422 #, python-format msgid "" "Hallo %(name)s,\n" @@ -2663,13 +2756,46 @@ msgid "" "ignorieren." msgstr "" -#: workflows/views.py:498 +#: workflows/views.py:445 +msgid "Benutzer konnte nicht erstellt werden. Bitte prüfen Sie die Eingaben." +msgstr "User could not be created. Please check the input." + +#: workflows/views.py:458 +#, fuzzy, python-format +#| msgid "Benutzer wurde erstellt: %(username)s" +msgid "Benutzer wurde erstellt und eingeladen: %(username)s" +msgstr "User created: %(username)s" + +#: workflows/views.py:476 +msgid "" +"Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren oder " +"herabstufen." +msgstr "" +"The currently signed-in super admin cannot lock or downgrade themselves here." + +#: workflows/views.py:479 +#, fuzzy +#| msgid "" +#| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " +#| "oder herabstufen." +msgid "" +"Der letzte aktive Super Admin kann nicht deaktiviert oder herabgestuft " +"werden." +msgstr "" +"The currently signed-in super admin cannot lock or downgrade themselves here." + +#: workflows/views.py:496 +#, python-format +msgid "Benutzer wurde aktualisiert: %(username)s" +msgstr "User updated: %(username)s" + +#: workflows/views.py:518 #, fuzzy, python-format #| msgid "Benutzer wurde erstellt: %(username)s" msgid "Passwort-Reset-Link wurde versendet: %(username)s" msgstr "User created: %(username)s" -#: workflows/views.py:509 +#: workflows/views.py:529 #, fuzzy #| msgid "" #| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " @@ -2679,7 +2805,7 @@ msgid "" msgstr "" "The currently signed-in super admin cannot lock or downgrade themselves here." -#: workflows/views.py:512 +#: workflows/views.py:532 #, fuzzy #| msgid "" #| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " @@ -2688,124 +2814,130 @@ msgid "Der letzte aktive Super Admin kann nicht gelöscht werden." msgstr "" "The currently signed-in super admin cannot lock or downgrade themselves here." -#: workflows/views.py:525 +#: workflows/views.py:545 #, fuzzy, python-format #| msgid "Benutzer wurde erstellt: %(username)s" msgid "Benutzer wurde gelöscht: %(username)s" msgstr "User created: %(username)s" -#: workflows/views.py:612 +#: workflows/views.py:632 #, python-format msgid "Backup wurde erstellt: %(name)s" msgstr "" -#: workflows/views.py:614 +#: workflows/views.py:634 #, python-format msgid "Backup konnte nicht erstellt werden: %(error)s" msgstr "" -#: workflows/views.py:630 +#: workflows/views.py:650 #, python-format msgid "Backup wurde verifiziert: %(name)s" msgstr "" -#: workflows/views.py:632 +#: workflows/views.py:652 #, python-format msgid "Backup-Verifikation fehlgeschlagen: %(error)s" msgstr "" -#: workflows/views.py:648 +#: workflows/views.py:668 #, python-format msgid "Backup wurde gelöscht: %(name)s" msgstr "" -#: workflows/views.py:650 +#: workflows/views.py:670 #, python-format msgid "Backup konnte nicht gelöscht werden: %(error)s" msgstr "" -#: workflows/views.py:676 +#: workflows/views.py:696 #, fuzzy #| msgid "Anfrage gespeichert" msgid "Anfrage erstellt" msgstr "Request saved" -#: workflows/views.py:678 +#: workflows/views.py:698 #, fuzzy, python-format #| msgid "Sitzungsstatus" msgid "Status: %(status)s" msgstr "Session status" -#: workflows/views.py:690 +#: workflows/views.py:710 #, fuzzy #| msgid "Geplant für" msgid "Geplanter Start" msgstr "Scheduled for" -#: workflows/views.py:700 +#: workflows/views.py:720 msgid "Geräteübergabe / Hardware-Abholung" msgstr "" -#: workflows/views.py:702 +#: workflows/views.py:722 msgid "Geplanter Hardware-Termin" msgstr "" -#: workflows/views.py:711 +#: workflows/views.py:731 #, fuzzy #| msgid "Noch nicht verfügbar" msgid "PDF verfügbar" msgstr "Not available yet" -#: workflows/views.py:737 +#: workflows/views.py:757 #, fuzzy #| msgid "Einweisung" msgid "Einweisungssitzung" msgstr "Introduction" -#: workflows/views.py:749 +#: workflows/views.py:769 #, fuzzy #| msgid "Welcome E-Mails" msgid "Welcome E-Mail" msgstr "Welcome Emails" -#: workflows/views.py:788 +#: workflows/views.py:808 msgid "Keine Einträge ausgewählt." msgstr "No entries selected." -#: workflows/views.py:831 +#: workflows/views.py:851 #, python-format msgid "%(count)s Eintrag/Einträge gelöscht." msgstr "%(count)s entry/entries deleted." -#: workflows/views.py:833 +#: workflows/views.py:853 #, python-format msgid "%(count)s Auswahl(en) konnten nicht verarbeitet werden." msgstr "%(count)s selection(s) could not be processed." -#: workflows/views.py:835 +#: workflows/views.py:855 msgid "Keine passenden Einträge gefunden." msgstr "No matching entries found." -#: workflows/views.py:1062 +#: workflows/views.py:1082 msgid "Einweisungs- und Übergabeprotokoll wurde erzeugt." msgstr "Introduction and handover protocol was generated." -#: workflows/views.py:1079 +#: workflows/views.py:1099 msgid "Einweisungsprotokoll aus Live-Status wurde erzeugt." msgstr "Introduction protocol from live status was generated." -#: workflows/views.py:1108 +#: workflows/views.py:1128 msgid "Einweisung wurde zurückgesetzt." msgstr "Introduction was reset." -#: workflows/views.py:1122 +#: workflows/views.py:1142 msgid "Einweisung wurde als abgeschlossen gespeichert." msgstr "Introduction was saved as completed." -#: workflows/views.py:1135 +#: workflows/views.py:1155 msgid "Einweisung wurde als Entwurf gespeichert." msgstr "Introduction was saved as draft." +#~ msgid "Die Passwörter stimmen nicht überein." +#~ msgstr "The passwords do not match." + +#~ msgid "Benutzer erstellen" +#~ msgstr "Create user" + #~ msgid "Backup läuft" #~ msgstr "Backup in progress" diff --git a/backend/workflows/forms.py b/backend/workflows/forms.py index 0c326ad..2853db5 100644 --- a/backend/workflows/forms.py +++ b/backend/workflows/forms.py @@ -1,9 +1,10 @@ from django import forms from pathlib import Path from datetime import timedelta -from django.contrib.auth import get_user_model +from django.contrib.auth import get_user_model, password_validation +from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm from django.utils import timezone -from django.utils.translation import get_language, gettext as _ +from django.utils.translation import get_language, gettext as _, gettext_lazy from .form_builder import apply_form_field_config from .models import EmployeeProfile, FormOption, OffboardingRequest, OnboardingRequest, WorkflowConfig @@ -98,14 +99,35 @@ HARDWARE_EXTRA_CHOICES = [('Smartphone', 'Smartphone'), ('Anderes', 'Anderes')] SOFTWARE_EXTRA_CHOICES = [('Adobe Acrobat Pro (Abonnement: Zusätzliche Kosten)', 'Adobe Acrobat Pro (Abonnement: Zusätzliche Kosten)'), ('Anderes', 'Anderes')] +class AppAuthenticationForm(AuthenticationForm): + username = forms.CharField(label=gettext_lazy('Benutzername')) + password = forms.CharField(label=gettext_lazy('Passwort'), strip=False, widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'})) + + +class AppPasswordResetForm(PasswordResetForm): + email = forms.EmailField(label=gettext_lazy('E-Mail-Adresse')) + + +class AppSetPasswordForm(SetPasswordForm): + new_password1 = forms.CharField( + label=gettext_lazy('Neues Passwort'), + strip=False, + widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}), + help_text=password_validation.password_validators_help_text_html(), + ) + new_password2 = forms.CharField( + label=gettext_lazy('Neues Passwort bestätigen'), + strip=False, + widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}), + ) + + class UserManagementCreateForm(forms.Form): first_name = forms.CharField(label=_('Vorname'), max_length=150, required=False) last_name = forms.CharField(label=_('Nachname'), max_length=150, required=False) username = forms.CharField(label=_('Benutzername'), max_length=150) email = forms.EmailField(label=_('E-Mail-Adresse')) role_key = forms.ChoiceField(label=_('Rolle')) - password1 = forms.CharField(label=_('Passwort'), widget=forms.PasswordInput()) - password2 = forms.CharField(label=_('Passwort bestätigen'), widget=forms.PasswordInput()) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -130,20 +152,12 @@ class UserManagementCreateForm(forms.Form): raise forms.ValidationError(_('Ungültige Rolle.')) return role_key - def clean(self): - cleaned = super().clean() - password1 = cleaned.get('password1') - password2 = cleaned.get('password2') - if password1 and password2 and password1 != password2: - self.add_error('password2', _('Die Passwörter stimmen nicht überein.')) - return cleaned - def save(self): user_model = get_user_model() user = user_model.objects.create_user( username=self.cleaned_data['username'], email=self.cleaned_data['email'], - password=self.cleaned_data['password1'], + password=None, first_name=self.cleaned_data.get('first_name', ''), last_name=self.cleaned_data.get('last_name', ''), is_active=True, diff --git a/backend/workflows/templates/registration/password_reset_complete.html b/backend/workflows/templates/registration/password_reset_complete.html new file mode 100644 index 0000000..4dd2712 --- /dev/null +++ b/backend/workflows/templates/registration/password_reset_complete.html @@ -0,0 +1,22 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Passwort gespeichert" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/registration/password_reset_confirm.html b/backend/workflows/templates/registration/password_reset_confirm.html new file mode 100644 index 0000000..a8c854e --- /dev/null +++ b/backend/workflows/templates/registration/password_reset_confirm.html @@ -0,0 +1,45 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Passwort festlegen" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/registration/password_reset_done.html b/backend/workflows/templates/registration/password_reset_done.html new file mode 100644 index 0000000..d3db1cd --- /dev/null +++ b/backend/workflows/templates/registration/password_reset_done.html @@ -0,0 +1,22 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "E-Mail versendet" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/registration/password_reset_form.html b/backend/workflows/templates/registration/password_reset_form.html new file mode 100644 index 0000000..4ef41cb --- /dev/null +++ b/backend/workflows/templates/registration/password_reset_form.html @@ -0,0 +1,28 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Passwort zurücksetzen" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/workflows/auth/login.html b/backend/workflows/templates/workflows/auth/login.html new file mode 100644 index 0000000..cbca437 --- /dev/null +++ b/backend/workflows/templates/workflows/auth/login.html @@ -0,0 +1,39 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Anmeldung" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_show_lang=1 header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/workflows/auth/password_reset_complete.html b/backend/workflows/templates/workflows/auth/password_reset_complete.html new file mode 100644 index 0000000..af9d634 --- /dev/null +++ b/backend/workflows/templates/workflows/auth/password_reset_complete.html @@ -0,0 +1,22 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Passwort gespeichert" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_show_lang=1 header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/workflows/auth/password_reset_confirm.html b/backend/workflows/templates/workflows/auth/password_reset_confirm.html new file mode 100644 index 0000000..671bf77 --- /dev/null +++ b/backend/workflows/templates/workflows/auth/password_reset_confirm.html @@ -0,0 +1,47 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Passwort festlegen" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_show_lang=1 header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/workflows/auth/password_reset_done.html b/backend/workflows/templates/workflows/auth/password_reset_done.html new file mode 100644 index 0000000..8b0870f --- /dev/null +++ b/backend/workflows/templates/workflows/auth/password_reset_done.html @@ -0,0 +1,22 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "E-Mail versendet" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_show_lang=1 header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/workflows/auth/password_reset_form.html b/backend/workflows/templates/workflows/auth/password_reset_form.html new file mode 100644 index 0000000..9395dd2 --- /dev/null +++ b/backend/workflows/templates/workflows/auth/password_reset_form.html @@ -0,0 +1,29 @@ +{% extends 'workflows/base_shell.html' %} +{% load static i18n %} + +{% block title %}{% trans "Passwort zurücksetzen" %}{% endblock %} + +{% block shell_header %} +{% include 'workflows/includes/app_header.html' with header_show_lang=1 header_inside_shell=1 %} +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block shell_body %} + +{% endblock %} diff --git a/backend/workflows/templates/workflows/developer_handbook.html b/backend/workflows/templates/workflows/developer_handbook.html index 0e94ede..94841e4 100644 --- a/backend/workflows/templates/workflows/developer_handbook.html +++ b/backend/workflows/templates/workflows/developer_handbook.html @@ -107,7 +107,7 @@ docker compose exec -T web python manage.py check
  • Capability checks are centralized in workflows.roles.CAPABILITIES.
  • Use _require_capability(...) in views instead of flat is_staff checks.
  • Templates receive permission flags from workflows.context_processors.role_context.
  • -
  • Super-admin-only user management lives at /admin-tools/users/ and is the preferred path for normal role assignment, account activation, password-reset mail dispatch, and controlled user deletion.
  • +
  • Super-admin-only user management lives at /admin-tools/users/ and is the preferred path for normal role assignment, account activation, invitation mail dispatch, password-reset mail dispatch, and controlled user deletion.
  • Backward-compatibility rule: authenticated legacy users with is_staff=True but no explicit role group currently fall back to the Admin capability set.
  • superuser accounts resolve to Super Admin.
  • When adding a new operational page or action, define the capability in roles.py, gate the view, and hide the UI affordance when the capability is absent.
  • diff --git a/backend/workflows/templates/workflows/project_wiki.html b/backend/workflows/templates/workflows/project_wiki.html index add71c6..31e8982 100644 --- a/backend/workflows/templates/workflows/project_wiki.html +++ b/backend/workflows/templates/workflows/project_wiki.html @@ -178,7 +178,7 @@
  • Form Builder: manage field visibility/order/options.
  • Einweisungs-Builder: manage custom checklist items for the intro PDF and live introduction checklist, including section, visibility, and conditional display logic.
  • Integrations: Nextcloud, SMTP, default routing addresses, notification rules, workflow rules, and remote backup target settings.
  • -
  • Benutzer & Rollen: super-admin-only page for creating users, assigning roles, activating/deactivating access, sending password-reset links, and deleting accounts when appropriate.
  • +
  • Benutzer & Rollen: super-admin-only page for creating users, assigning roles, activating/deactivating access, sending access or password-reset links by email, and deleting accounts when appropriate.
  • Welcome Emails: scheduled jobs, pause/resume/cancel/trigger now.
  • Audit Log: staff-only trace of important admin changes such as builder edits, settings updates, PDF generation, welcome-email operations, and request deletions. Supports filtering by action, user, and date range.
  • Requests Dashboard: search records, open PDFs, delete records (single/bulk for staff).
  • diff --git a/backend/workflows/templates/workflows/user_management.html b/backend/workflows/templates/workflows/user_management.html index 98b1771..9067263 100644 --- a/backend/workflows/templates/workflows/user_management.html +++ b/backend/workflows/templates/workflows/user_management.html @@ -20,6 +20,7 @@

    {% trans "Benutzer anlegen" %}

    +

    {% trans "Nach dem Anlegen wird automatisch eine Zugangseinladung mit Passwort-Link per E-Mail versendet." %}

    {% csrf_token %}
    @@ -48,20 +49,10 @@ {{ create_form.role_key }} {% for error in create_form.role_key.errors %}
    {{ error }}
    {% endfor %}
    -
    - - {{ create_form.password1 }} - {% for error in create_form.password1.errors %}
    {{ error }}
    {% endfor %} -
    -
    - - {{ create_form.password2 }} - {% for error in create_form.password2.errors %}
    {{ error }}
    {% endfor %} -
    {% for error in create_form.non_field_errors %}
    {{ error }}
    {% endfor %}
    - +
    diff --git a/backend/workflows/views.py b/backend/workflows/views.py index 51d475b..5bd956f 100644 --- a/backend/workflows/views.py +++ b/backend/workflows/views.py @@ -394,6 +394,44 @@ def _would_remove_last_super_admin(user, new_role_key: str | None = None, new_is return False +def _send_user_access_email(request, target_user, *, invitation: bool) -> None: + email = (target_user.email or '').strip() + if not email: + raise ValueError(_('Für diesen Benutzer ist keine E-Mail-Adresse hinterlegt.')) + + uid = urlsafe_base64_encode(force_bytes(target_user.pk)) + token = default_token_generator.make_token(target_user) + reset_path = reverse('password_reset_confirm', kwargs={'uidb64': uid, 'token': token}) + reset_url = request.build_absolute_uri(reset_path) + + if invitation: + subject = _('Zugangseinladung für %(username)s') % {'username': target_user.username} + body = _( + 'Hallo %(name)s,\n\n' + 'für Sie wurde ein Benutzerkonto im TUBCO Onboarding- und Offboarding-Portal angelegt.\n' + 'Bitte öffnen Sie den folgenden Link, um Ihr Passwort zu setzen:\n' + '%(url)s\n\n' + 'Wenn Sie diese Einladung nicht erwartet haben, melden Sie sich bitte bei Ihrem Administrator.' + ) % { + 'name': _display_user_name(target_user), + 'url': reset_url, + } + else: + subject = _('Passwort zurücksetzen für %(username)s') % {'username': target_user.username} + body = _( + 'Hallo %(name)s,\n\n' + 'für Ihr Konto wurde ein Link zum Zurücksetzen des Passworts erstellt.\n' + 'Bitte öffnen Sie den folgenden Link:\n' + '%(url)s\n\n' + 'Wenn Sie diese Anfrage nicht erwartet haben, können Sie diese E-Mail ignorieren.' + ) % { + 'name': _display_user_name(target_user), + 'url': reset_url, + } + + send_system_email(subject=subject, body=body, to=[email]) + + @_require_capability('manage_users') def user_management_page(request): return _render_user_management(request) @@ -408,15 +446,16 @@ def create_user_from_admin(request): return _render_user_management(request, create_form=form, status_code=400) user = form.save() + _send_user_access_email(request, user, invitation=True) _audit( request, 'user_created', target_type='user', target_id=user.id, target_label=_display_user_name(user), - details={'username': user.username, 'role': get_user_role_key(user)}, + details={'username': user.username, 'role': get_user_role_key(user), 'invitation_sent': True}, ) - messages.success(request, _('Benutzer wurde erstellt: %(username)s') % {'username': user.username}) + messages.success(request, _('Benutzer wurde erstellt und eingeladen: %(username)s') % {'username': user.username}) return redirect('user_management_page') @@ -463,37 +502,18 @@ def update_user_from_admin(request, user_id: int): def send_password_reset_from_admin(request, user_id: int): user_model = get_user_model() target_user = get_object_or_404(user_model, id=user_id) - email = (target_user.email or '').strip() - if not email: - messages.error(request, _('Für diesen Benutzer ist keine E-Mail-Adresse hinterlegt.')) + try: + _send_user_access_email(request, target_user, invitation=False) + except ValueError as exc: + messages.error(request, str(exc)) return redirect('user_management_page') - - uid = urlsafe_base64_encode(force_bytes(target_user.pk)) - token = default_token_generator.make_token(target_user) - reset_path = reverse('password_reset_confirm', kwargs={'uidb64': uid, 'token': token}) - reset_url = request.build_absolute_uri(reset_path) - - send_system_email( - subject=_('Passwort zurücksetzen für %(username)s') % {'username': target_user.username}, - body=_( - 'Hallo %(name)s,\n\n' - 'für Ihr Konto wurde ein Link zum Zurücksetzen des Passworts erstellt.\n' - 'Bitte öffnen Sie den folgenden Link:\n' - '%(url)s\n\n' - 'Wenn Sie diese Anfrage nicht erwartet haben, können Sie diese E-Mail ignorieren.' - ) % { - 'name': _display_user_name(target_user), - 'url': reset_url, - }, - to=[email], - ) _audit( request, 'user_password_reset_sent', target_type='user', target_id=target_user.id, target_label=_display_user_name(target_user), - details={'username': target_user.username, 'email': email}, + details={'username': target_user.username, 'email': target_user.email}, ) messages.success(request, _('Passwort-Reset-Link wurde versendet: %(username)s') % {'username': target_user.username}) return redirect('user_management_page')