feat: add password change

Password page with the AD policy listed first, ordered service (throttle limit below the lockout threshold, verification, local hints, directory change, ended sessions, audit and confirmation mail in one transaction), plain-language errors, German text and tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
This commit is contained in:
2026-10-18 10:08:17 +02:00
co-authored by Claude Sonnet 5.5
parent a1a766adef
commit 49c7fd4a0b
22 changed files with 1346 additions and 17 deletions
+7
View File
@@ -0,0 +1,7 @@
{% load i18n %}
<div class="field{% if field.errors %} has-error{% endif %}">
<label for="{{ field.id_for_label }}">{{ label }}</label>
<input type="password" name="{{ field.html_name }}" id="{{ field.id_for_label }}" autocomplete="{{ autocomplete }}" maxlength="256" required{% if field.errors %} aria-invalid="true"{% endif %} aria-describedby="{{ field.id_for_label }}-hint{% if field.errors %} {{ field.id_for_label }}-error{% endif %}">
{% if hint %}<p class="hint" id="{{ field.id_for_label }}-hint">{{ hint }}</p>{% else %}<span id="{{ field.id_for_label }}-hint"></span>{% endif %}
{% for error in field.errors %}<p class="error" id="{{ field.id_for_label }}-error">{{ error }}</p>{% endfor %}
</div>
+47
View File
@@ -0,0 +1,47 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans "Change password" %}{% endblock %}
{% block content %}
<h1>{% trans "Change password" %}</h1>
<p class="lead">{% trans "This password applies to every service that uses your account. After the change you are signed out on your other devices and you receive a confirmation by e-mail." %}</p>
{% if throttled %}
<div class="notice" role="alert" tabindex="-1" data-focus-on-load>
<p>{% trans "Too many attempts. Please wait a few minutes." %}</p>
</div>
<p><a class="button" href="{% url 'home' %}">{% trans "Back to the start page" %}</a></p>
{% else %}
{% if form.errors %}
<div class="error-summary" role="alert" tabindex="-1" data-focus-on-load>
<p><strong>{% trans "The password was not changed." %}</strong></p>
<ul>
{% for field in form %}{% for error in field.errors %}
<li><a href="#{{ field.id_for_label }}">{{ error }}</a></li>
{% endfor %}{% endfor %}
</ul>
</div>
{% endif %}
<form method="post" action="{% url 'password' %}" class="card" data-busy-on-submit novalidate>
{% csrf_token %}
{% if rules %}
<div class="rules">
<p class="hint">{% trans "Your new password must follow these rules:" %}</p>
<ul>
{% for rule in rules %}<li>{{ rule }}</li>{% endfor %}
</ul>
{% if partly_known %}<p class="hint">{% trans "A stricter rule may also apply to your account." %}</p>{% endif %}
</div>
{% endif %}
{% trans "Current password" as label %}{% trans "We check it before anything is changed." as hint %}
{% include "profile/_field.html" with field=form.current_password label=label hint=hint autocomplete="current-password" %}
{% trans "New password" as label %}
{% include "profile/_field.html" with field=form.new_password label=label hint="" autocomplete="new-password" %}
{% trans "Repeat new password" as label %}
{% include "profile/_field.html" with field=form.confirm_password label=label hint="" autocomplete="new-password" %}
<p class="actions">
<button type="submit" class="primary"><span class="spinner" aria-hidden="true"></span>{% trans "Change password" %}</button>
<a class="button" href="{% url 'home' %}">{% trans "Cancel" %}</a>
</p>
</form>
<p class="hint">{% trans "Forgotten or expired password? Please contact the administrator." %}</p>
{% endif %}
{% endblock %}
@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans "Password changed" %}{% endblock %}
{% block content %}
<h1>{% trans "Password changed" %}</h1>
<div class="notice success" role="status">
<p>{% trans "Your password was changed." %}</p>
</div>
<p>{% trans "You were signed out on your other devices." %}</p>
{% if mail %}<p>{% trans "A confirmation was sent to your e-mail address." %}</p>{% endif %}
<p><a class="button" href="{% url 'home' %}">{% trans "Back to the start page" %}</a></p>
{% endblock %}
@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans "Result unclear" %}{% endblock %}
{% block content %}
<h1>{% trans "Result unclear" %}</h1>
<div class="notice" role="alert">
<p>{% trans "Your password may have been changed, but the portal could not finish. Try to sign in with the new password. If that does not work, contact the administrator." %}</p>
</div>
<p><a class="button" href="{% url 'home' %}">{% trans "Back to the start page" %}</a></p>
{% endblock %}