feat: Update user registration to include first and last name fields and enhance permission checks for upload actions
This commit is contained in:
+39
-30
@@ -208,8 +208,8 @@ PERMISSION_ACTION_ENDPOINTS = {
|
|||||||
'admin_update_user_permissions': 'can_manage_users',
|
'admin_update_user_permissions': 'can_manage_users',
|
||||||
'admin_anonymize_names': 'can_manage_users',
|
'admin_anonymize_names': 'can_manage_users',
|
||||||
'home_admin': 'can_manage_settings',
|
'home_admin': 'can_manage_settings',
|
||||||
'upload_admin': 'can_manage_settings',
|
'upload_admin': 'can_insert',
|
||||||
'library_admin': 'can_manage_settings',
|
'library_admin': 'can_insert',
|
||||||
'admin_borrowings': 'can_manage_settings',
|
'admin_borrowings': 'can_manage_settings',
|
||||||
'library_loans_admin': 'can_manage_settings',
|
'library_loans_admin': 'can_manage_settings',
|
||||||
'admin_damaged_items': 'can_manage_settings',
|
'admin_damaged_items': 'can_manage_settings',
|
||||||
@@ -1826,8 +1826,9 @@ def _upload_excel_items(scope='inventory'):
|
|||||||
flash('Nicht angemeldet.', 'error')
|
flash('Nicht angemeldet.', 'error')
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
if not us.check_admin(session['username']):
|
permissions = _get_current_user_permissions() or us.build_default_permission_payload('standard_user')
|
||||||
flash('Administratorrechte erforderlich.', 'error')
|
if not _action_access_allowed(permissions, 'can_insert'):
|
||||||
|
flash('Einfüge-Rechte erforderlich.', 'error')
|
||||||
return redirect(url_for('home'))
|
return redirect(url_for('home'))
|
||||||
|
|
||||||
is_library_scope = scope == 'library'
|
is_library_scope = scope == 'library'
|
||||||
@@ -1837,7 +1838,7 @@ def _upload_excel_items(scope='inventory'):
|
|||||||
if is_library_scope:
|
if is_library_scope:
|
||||||
if not cfg.LIBRARY_MODULE_ENABLED:
|
if not cfg.LIBRARY_MODULE_ENABLED:
|
||||||
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
|
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for('home'))
|
||||||
|
|
||||||
excel_file = request.files.get(file_field)
|
excel_file = request.files.get(file_field)
|
||||||
if not excel_file or not excel_file.filename:
|
if not excel_file or not excel_file.filename:
|
||||||
@@ -3017,8 +3018,8 @@ def api_library_item_update(item_id):
|
|||||||
@app.route('/upload_admin')
|
@app.route('/upload_admin')
|
||||||
def upload_admin():
|
def upload_admin():
|
||||||
"""
|
"""
|
||||||
Admin upload page route.
|
Upload page route for inventory items.
|
||||||
Only accessible by users with admin privileges.
|
Accessible to users with insert permission.
|
||||||
Supports duplication by passing duplicate_from parameter.
|
Supports duplication by passing duplicate_from parameter.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -3027,7 +3028,8 @@ def upload_admin():
|
|||||||
if 'username' not in session:
|
if 'username' not in session:
|
||||||
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
if not us.check_admin(session['username']):
|
permissions = _get_current_user_permissions() or us.build_default_permission_payload('standard_user')
|
||||||
|
if not _action_access_allowed(permissions, 'can_insert'):
|
||||||
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
@@ -3091,13 +3093,14 @@ def upload_admin():
|
|||||||
@app.route('/library_admin')
|
@app.route('/library_admin')
|
||||||
def library_admin():
|
def library_admin():
|
||||||
"""
|
"""
|
||||||
Dedicated admin page for library/book uploads with ISBN scanning.
|
Dedicated page for library/book uploads with ISBN scanning.
|
||||||
Only accessible by admins and only when the library module is enabled.
|
Accessible to users with insert permission when the library module is enabled.
|
||||||
"""
|
"""
|
||||||
if 'username' not in session:
|
if 'username' not in session:
|
||||||
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
if not us.check_admin(session['username']):
|
permissions = _get_current_user_permissions() or us.build_default_permission_payload('standard_user')
|
||||||
|
if not _action_access_allowed(permissions, 'can_insert'):
|
||||||
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
if not cfg.LIBRARY_MODULE_ENABLED:
|
if not cfg.LIBRARY_MODULE_ENABLED:
|
||||||
@@ -4192,10 +4195,14 @@ def upload_item():
|
|||||||
if 'username' not in session:
|
if 'username' not in session:
|
||||||
return jsonify({'success': False, 'message': 'Nicht angemeldet'}), 401
|
return jsonify({'success': False, 'message': 'Nicht angemeldet'}), 401
|
||||||
|
|
||||||
# Check if user is an admin
|
# Check if user may insert items
|
||||||
username = session['username']
|
username = session['username']
|
||||||
if not us.check_admin(username):
|
permissions = _get_current_user_permissions() or us.build_default_permission_payload('standard_user')
|
||||||
return jsonify({'success': False, 'message': 'Administratorrechte erforderlich'}), 403
|
if not _action_access_allowed(permissions, 'can_insert'):
|
||||||
|
return jsonify({'success': False, 'message': 'Einfüge-Rechte erforderlich'}), 403
|
||||||
|
|
||||||
|
can_access_admin_home = _page_access_allowed(permissions, 'home_admin') and _action_access_allowed(permissions, 'can_manage_settings')
|
||||||
|
success_redirect_endpoint = 'home_admin' if can_access_admin_home else 'home'
|
||||||
|
|
||||||
# Detect if request is from mobile device
|
# Detect if request is from mobile device
|
||||||
is_mobile = 'Mobile' in request.headers.get('User-Agent', '')
|
is_mobile = 'Mobile' in request.headers.get('User-Agent', '')
|
||||||
@@ -4277,7 +4284,7 @@ def upload_item():
|
|||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
return jsonify({'success': False, 'message': error_msg}), 400
|
||||||
else:
|
else:
|
||||||
flash('Fehler beim Verarbeiten der Formulardaten. Bitte versuchen Sie es erneut.', 'error')
|
flash('Fehler beim Verarbeiten der Formulardaten. Bitte versuchen Sie es erneut.', 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
|
|
||||||
# Expand special "all values" selections for predefined filters.
|
# Expand special "all values" selections for predefined filters.
|
||||||
filter_upload = expand_filter_selection(filter_upload, 1)
|
filter_upload = expand_filter_selection(filter_upload, 1)
|
||||||
@@ -4290,7 +4297,7 @@ def upload_item():
|
|||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
return jsonify({'success': False, 'message': error_msg}), 400
|
||||||
else:
|
else:
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
|
|
||||||
item_isbn = ''
|
item_isbn = ''
|
||||||
item_type = 'general'
|
item_type = 'general'
|
||||||
@@ -4301,7 +4308,7 @@ def upload_item():
|
|||||||
if is_mobile:
|
if is_mobile:
|
||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
return jsonify({'success': False, 'message': error_msg}), 400
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
if item_isbn:
|
if item_isbn:
|
||||||
item_type = 'book'
|
item_type = 'book'
|
||||||
|
|
||||||
@@ -4311,7 +4318,7 @@ def upload_item():
|
|||||||
if is_mobile:
|
if is_mobile:
|
||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
return jsonify({'success': False, 'message': error_msg}), 400
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
if not item_isbn:
|
if not item_isbn:
|
||||||
error_msg = 'Für Bücher ist eine gültige ISBN erforderlich.'
|
error_msg = 'Für Bücher ist eine gültige ISBN erforderlich.'
|
||||||
if is_mobile:
|
if is_mobile:
|
||||||
@@ -4328,7 +4335,7 @@ def upload_item():
|
|||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
return jsonify({'success': False, 'message': error_msg}), 400
|
||||||
else:
|
else:
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
|
|
||||||
# Check if base code is unique for single-item uploads
|
# Check if base code is unique for single-item uploads
|
||||||
if code_4 and item_count == 1 and not it.is_code_unique(code_4[0]):
|
if code_4 and item_count == 1 and not it.is_code_unique(code_4[0]):
|
||||||
@@ -4337,7 +4344,7 @@ def upload_item():
|
|||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
return jsonify({'success': False, 'message': error_msg}), 400
|
||||||
else:
|
else:
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
|
|
||||||
# Validate optional per-item codes
|
# Validate optional per-item codes
|
||||||
if individual_codes:
|
if individual_codes:
|
||||||
@@ -4346,14 +4353,14 @@ def upload_item():
|
|||||||
if is_mobile:
|
if is_mobile:
|
||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
return jsonify({'success': False, 'message': error_msg}), 400
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
|
|
||||||
if len(set(individual_codes)) != len(individual_codes):
|
if len(set(individual_codes)) != len(individual_codes):
|
||||||
error_msg = 'Doppelte Einzelcodes erkannt. Bitte alle Codes eindeutig eintragen.'
|
error_msg = 'Doppelte Einzelcodes erkannt. Bitte alle Codes eindeutig eintragen.'
|
||||||
if is_mobile:
|
if is_mobile:
|
||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
return jsonify({'success': False, 'message': error_msg}), 400
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
|
|
||||||
for specific_code in individual_codes:
|
for specific_code in individual_codes:
|
||||||
if not it.is_code_unique(specific_code):
|
if not it.is_code_unique(specific_code):
|
||||||
@@ -4361,7 +4368,7 @@ def upload_item():
|
|||||||
if is_mobile:
|
if is_mobile:
|
||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
return jsonify({'success': False, 'message': error_msg}), 400
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
|
|
||||||
def generate_unique_batch_code(base_code, position):
|
def generate_unique_batch_code(base_code, position):
|
||||||
"""Generate a unique code for every item in a batch."""
|
"""Generate a unique code for every item in a batch."""
|
||||||
@@ -5054,7 +5061,7 @@ def upload_item():
|
|||||||
if is_mobile:
|
if is_mobile:
|
||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
return jsonify({'success': False, 'message': error_msg}), 400
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
|
|
||||||
parent_item_id = str(created_item_ids[0]) if created_item_ids else None
|
parent_item_id = str(created_item_ids[0]) if created_item_ids else None
|
||||||
item_id = it.add_item(
|
item_id = it.add_item(
|
||||||
@@ -5081,7 +5088,7 @@ def upload_item():
|
|||||||
if is_mobile:
|
if is_mobile:
|
||||||
return jsonify({'success': False, 'message': error_msg}), 500
|
return jsonify({'success': False, 'message': error_msg}), 500
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
|
|
||||||
item_id = created_item_ids[0] if created_item_ids else None
|
item_id = created_item_ids[0] if created_item_ids else None
|
||||||
|
|
||||||
@@ -5105,14 +5112,14 @@ def upload_item():
|
|||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
flash(success_msg, 'success')
|
flash(success_msg, 'success')
|
||||||
return redirect(url_for('home_admin', highlight_item=str(item_id)))
|
return redirect(url_for(success_redirect_endpoint, highlight_item=str(item_id)))
|
||||||
else:
|
else:
|
||||||
error_msg = 'Fehler beim Hinzufügen des Elements'
|
error_msg = 'Fehler beim Hinzufügen des Elements'
|
||||||
if is_mobile:
|
if is_mobile:
|
||||||
return jsonify({'success': False, 'message': error_msg}), 500
|
return jsonify({'success': False, 'message': error_msg}), 500
|
||||||
else:
|
else:
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/duplicate_item', methods=['POST'])
|
@app.route('/duplicate_item', methods=['POST'])
|
||||||
@@ -6688,12 +6695,14 @@ def register():
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
username = request.form['username']
|
username = request.form['username']
|
||||||
password = request.form['password']
|
password = request.form['password']
|
||||||
|
name = (request.form.get('name') or '').strip()
|
||||||
|
last_name = (request.form.get('last-name') or '').strip()
|
||||||
permission_preset = (request.form.get('permission_preset') or 'standard_user').strip()
|
permission_preset = (request.form.get('permission_preset') or 'standard_user').strip()
|
||||||
use_custom_permissions = request.form.get('use_custom_permissions') == 'on'
|
use_custom_permissions = request.form.get('use_custom_permissions') == 'on'
|
||||||
is_student = bool(request.form.get('is_student')) if cfg.STUDENT_CARDS_MODULE_ENABLED else False
|
is_student = bool(request.form.get('is_student')) if cfg.STUDENT_CARDS_MODULE_ENABLED else False
|
||||||
student_card_id = us.normalize_student_card_id(request.form.get('student_card_id')) if cfg.STUDENT_CARDS_MODULE_ENABLED else ''
|
student_card_id = us.normalize_student_card_id(request.form.get('student_card_id')) if cfg.STUDENT_CARDS_MODULE_ENABLED else ''
|
||||||
max_borrow_days_raw = request.form.get('max_borrow_days') if cfg.STUDENT_CARDS_MODULE_ENABLED else None
|
max_borrow_days_raw = request.form.get('max_borrow_days') if cfg.STUDENT_CARDS_MODULE_ENABLED else None
|
||||||
if not username or not password:
|
if not username or not password or not name or not last_name:
|
||||||
flash('Bitte füllen Sie alle Felder aus', 'error')
|
flash('Bitte füllen Sie alle Felder aus', 'error')
|
||||||
return redirect(url_for('register'))
|
return redirect(url_for('register'))
|
||||||
if us.get_user(username):
|
if us.get_user(username):
|
||||||
@@ -6731,8 +6740,8 @@ def register():
|
|||||||
us.add_user(
|
us.add_user(
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
username,
|
name,
|
||||||
'',
|
last_name,
|
||||||
is_student=is_student,
|
is_student=is_student,
|
||||||
student_card_id=student_card_id if is_student else None,
|
student_card_id=student_card_id if is_student else None,
|
||||||
max_borrow_days=max_borrow_days,
|
max_borrow_days=max_borrow_days,
|
||||||
|
|||||||
@@ -786,7 +786,7 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.pages.get('upload_admin', True) and current_permissions.actions.get('can_manage_settings', True) %}
|
{% if 'username' in session and current_permissions.pages.get('upload_admin', True) and current_permissions.actions.get('can_insert', True) %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link nav-priority-link {% if current_path == url_for('upload_admin') %}nav-active{% endif %}" href="{{ url_for('upload_admin') }}">➕ Hochladen</a>
|
<a class="nav-link nav-priority-link {% if current_path == url_for('upload_admin') %}nav-active{% endif %}" href="{{ url_for('upload_admin') }}">➕ Hochladen</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -907,7 +907,7 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.actions.get('can_manage_settings', True) and current_permissions.pages.get('library_admin', True) %}
|
{% if 'username' in session and current_permissions.actions.get('can_insert', True) and current_permissions.pages.get('library_admin', True) %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link nav-priority-link {% if current_path == url_for('library_admin') %}nav-active{% endif %}" href="{{ url_for('library_admin') }}">📖 Hochladen</a>
|
<a class="nav-link nav-priority-link {% if current_path == url_for('library_admin') %}nav-active{% endif %}" href="{{ url_for('library_admin') }}">📖 Hochladen</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -39,12 +39,31 @@
|
|||||||
<span class="input-icon">👤</span>
|
<span class="input-icon">👤</span>
|
||||||
<input type="text" id="username" name="username" placeholder="Geben Sie einen Benutzernamen ein" required>
|
<input type="text" id="username" name="username" placeholder="Geben Sie einen Benutzernamen ein" required>
|
||||||
</div>
|
</div>
|
||||||
<p class="anonymize-hint">Klarnamen werden nicht gespeichert. Ein anonymes Kürzel wird automatisch aus dem Benutzernamen erzeugt.</p>
|
<label for="name">Vorname</label>
|
||||||
|
<div class="input-container">
|
||||||
|
<span class="input-icon">👤</span>
|
||||||
|
<input type="text" id="name" name="name" placeholder="Geben Sie den Vornamen ein" required>
|
||||||
|
</div>
|
||||||
|
<label for="last-name">Nachname</label>
|
||||||
|
<div class="input-container">
|
||||||
|
<span class="input-icon">👤</span>
|
||||||
|
<input type="text" id="last-name" name="last-name" placeholder="Geben Sie den Nachnamen ein" required>
|
||||||
|
</div>
|
||||||
|
<p class="anonymize-hint">Klarnamen werden nur zur Erzeugung eines Kuerzels (z.B. SimFri) verwendet und nicht als Klarname gespeichert.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">Passwort</label>
|
<label for="password">Passwort</label>
|
||||||
<a class="richtlinen">Das Password muss mindestens 6 Zeichen beinhalten mit Sonderzeichen, groß und klein Buchstaben sowie Zahlen!</a>
|
<div class="password-rules" id="password-rules" aria-live="polite">
|
||||||
|
<p class="password-rules-title">Passwort-Anforderungen (live):</p>
|
||||||
|
<ul>
|
||||||
|
<li id="pw-rule-length" class="pw-rule">Mindestens 12 Zeichen</li>
|
||||||
|
<li id="pw-rule-lower" class="pw-rule">Mindestens ein Kleinbuchstabe</li>
|
||||||
|
<li id="pw-rule-upper" class="pw-rule">Mindestens ein Grossbuchstabe</li>
|
||||||
|
<li id="pw-rule-digit" class="pw-rule">Mindestens eine Zahl</li>
|
||||||
|
<li id="pw-rule-symbol" class="pw-rule">Mindestens ein Sonderzeichen</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<span class="input-icon">🔒</span>
|
<span class="input-icon">🔒</span>
|
||||||
<input type="password" id="password" name="password" placeholder="Geben Sie ein sicheres Passwort ein" required>
|
<input type="password" id="password" name="password" placeholder="Geben Sie ein sicheres Passwort ein" required>
|
||||||
@@ -342,8 +361,49 @@ input::placeholder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.richtlinen{
|
.password-rules {
|
||||||
color: #ec0920;
|
margin-bottom: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-rules-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-rules ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-rule {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 22px;
|
||||||
|
margin: 5px 0;
|
||||||
|
color: #b91c1c;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-rule::before {
|
||||||
|
content: '✗';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-rule.ok {
|
||||||
|
color: #166534;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-rule.ok::before {
|
||||||
|
content: '✓';
|
||||||
}
|
}
|
||||||
|
|
||||||
.anonymize-hint {
|
.anonymize-hint {
|
||||||
@@ -427,6 +487,41 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
toggleCustomPermissions();
|
toggleCustomPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const passwordInput = document.getElementById('password');
|
||||||
|
const passwordRules = {
|
||||||
|
length: document.getElementById('pw-rule-length'),
|
||||||
|
lower: document.getElementById('pw-rule-lower'),
|
||||||
|
upper: document.getElementById('pw-rule-upper'),
|
||||||
|
digit: document.getElementById('pw-rule-digit'),
|
||||||
|
symbol: document.getElementById('pw-rule-symbol')
|
||||||
|
};
|
||||||
|
|
||||||
|
function setRuleState(node, ok) {
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.classList.toggle('ok', !!ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePasswordRules() {
|
||||||
|
if (!passwordInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = String(passwordInput.value || '');
|
||||||
|
setRuleState(passwordRules.length, value.length >= 12);
|
||||||
|
setRuleState(passwordRules.lower, /[a-z]/.test(value));
|
||||||
|
setRuleState(passwordRules.upper, /[A-Z]/.test(value));
|
||||||
|
setRuleState(passwordRules.digit, /[0-9]/.test(value));
|
||||||
|
setRuleState(passwordRules.symbol, /[^A-Za-z0-9]/.test(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordInput) {
|
||||||
|
passwordInput.addEventListener('input', updatePasswordRules);
|
||||||
|
passwordInput.addEventListener('blur', updatePasswordRules);
|
||||||
|
updatePasswordRules();
|
||||||
|
}
|
||||||
|
|
||||||
{% if student_cards_module_enabled %}
|
{% if student_cards_module_enabled %}
|
||||||
const studentCheckbox = document.getElementById('is-student');
|
const studentCheckbox = document.getElementById('is-student');
|
||||||
const studentFields = document.getElementById('student-fields');
|
const studentFields = document.getElementById('student-fields');
|
||||||
|
|||||||
@@ -446,5 +446,13 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-backdrop {
|
||||||
|
z-index: 1998 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
z-index: 1999 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
+3
-2
@@ -415,8 +415,9 @@ def add_user(
|
|||||||
for key, value in page_permissions.items():
|
for key, value in page_permissions.items():
|
||||||
permission_defaults['pages'][str(key)] = bool(value)
|
permission_defaults['pages'][str(key)] = bool(value)
|
||||||
|
|
||||||
alias_source = name if str(name or '').strip() else username
|
alias_first = name if str(name or '').strip() else username
|
||||||
name_alias = build_name_synonym(alias_source, '')
|
alias_last = last_name if str(last_name or '').strip() else ''
|
||||||
|
name_alias = build_name_synonym(alias_first, alias_last)
|
||||||
|
|
||||||
user_doc = {
|
user_doc = {
|
||||||
'Username': username,
|
'Username': username,
|
||||||
|
|||||||
Reference in New Issue
Block a user