Compare commits

...

11 Commits

5 changed files with 101 additions and 90 deletions
+73 -69
View File
@@ -18,7 +18,6 @@ Features:
""" """
from flask import Flask, render_template, request, redirect, url_for, session, flash, send_from_directory, get_flashed_messages, jsonify, Response, make_response, send_file, abort from flask import Flask, render_template, request, redirect, url_for, session, flash, send_from_directory, get_flashed_messages, jsonify, Response, make_response, send_file, abort
from flask_wtf.csrf import CSRFProtect
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.proxy_fix import ProxyFix
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
@@ -116,7 +115,6 @@ app.config['PREFERRED_URL_SCHEME'] = 'https' if app.config['SESSION_COOKIE_SECUR
# app.config['QR_CODE_FOLDER'] = cfg.QR_CODE_FOLDER # QR Code storage deactivated # app.config['QR_CODE_FOLDER'] = cfg.QR_CODE_FOLDER # QR Code storage deactivated
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1) app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1)
app.register_blueprint(terminplaner_bp, url_prefix='/terminplaner') app.register_blueprint(terminplaner_bp, url_prefix='/terminplaner')
csrf = CSRFProtect(app)
"""--------------------------------------------------------------Path Init-------------------------------------------------------""" """--------------------------------------------------------------Path Init-------------------------------------------------------"""
@@ -3607,23 +3605,26 @@ def api_item_detail(item_id):
borrows_html = '' borrows_html = ''
if borrow_records: if borrow_records:
rows = [] rows = []
for rec in borrow_records: count = 1
user_raw = rec.get('User') while count < 3:
try: for rec in borrow_records:
user = decrypt_text(user_raw) if user_raw is not None else '' user_raw = rec.get('User')
except Exception: try:
user = user_raw user = decrypt_text(user_raw) if user_raw is not None else ''
status = rec.get('Status', '') except Exception:
start = fmt_dt(rec.get('Start')) user = user_raw
end = fmt_dt(rec.get('End')) status = rec.get('Status', '')
notes = html.escape(str(rec.get('Notes') or '')) start = fmt_dt(rec.get('Start'))
end = fmt_dt(rec.get('End'))
rows.append( notes = html.escape(str(rec.get('Notes') or ''))
f"<li><strong>{html.escape(str(user or '-'))}</strong> — "
f"{html.escape(str(status))}" rows.append(
f"{html.escape(str(start))}{html.escape(str(end))}" f"<li><strong>{html.escape(str(user or '-'))}</strong> — "
f"{('' + notes) if notes else ''}</li>" f"{html.escape(str(status))}"
) f"{html.escape(str(start))}{html.escape(str(end))}"
f"{('' + notes) if notes else ''}</li>"
)
count += 1
borrows_html = f"<h3>Ausleihhistorie</h3><ul>{''.join(rows)}</ul>" borrows_html = f"<h3>Ausleihhistorie</h3><ul>{''.join(rows)}</ul>"
detail_html = f""" detail_html = f"""
@@ -7905,66 +7906,69 @@ def terminplan():
@app.route('/register', methods=['GET', 'POST']) @app.route('/register', methods=['GET', 'POST'])
def register(): def register():
""" """
User registration route.false User registration route.
Returns: Returns:
flask.Response: Rendered template or redirect flask.Response: Rendered template or redirect
""" """
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 Adresse zu nutzen, versuchen Sie es erneut, nachdem Sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if 'username' in session:
if request.method == 'POST': if request.method == 'POST':
password = request.form['password'] password = request.form['password']
name = (request.form.get('name') or '').strip() name = (request.form.get('name') or '').strip()
last_name = (request.form.get('last-name') or '').strip() last_name = (request.form.get('last-name') or '').strip()
# Generate a username from the first 3 letters of first and last name. username = us.build_unique_username_from_name(name, last_name)
username = us.build_unique_username_from_name(name, last_name)
permission_preset = (request.form.get('permission_preset') or 'standard_user').strip()
use_custom_permissions = request.form.get('use_custom_permissions') == 'on'
if not username or not password or not name or not last_name:
flash('Bitte füllen Sie alle Felder aus', 'error')
return redirect(url_for('register'))
permission_preset = (request.form.get('permission_preset') or 'standard_user').strip() if not us.check_password_strength(password):
use_custom_permissions = request.form.get('use_custom_permissions') == 'on' flash('Passwort ist zu schwach oder entspricht nicht den Richtlinien', 'error')
return redirect(url_for('register'))
action_permissions = None
page_permissions = None
if use_custom_permissions:
action_permissions = {}
for action_key, _ in PERMISSION_ACTION_OPTIONS:
action_permissions[action_key] = request.form.get(f'action_{action_key}') == 'on'
page_permissions = {}
for endpoint_name, _ in PERMISSION_PAGE_OPTIONS:
page_permissions[endpoint_name] = request.form.get(f'page_{endpoint_name}') == 'on'
if not username or not password or not name or not last_name: us.add_user(
flash('Bitte füllen Sie alle Felder aus', 'error') username,
return redirect(url_for('register')) password,
if not us.check_password_strength(password): name,
flash('Passwort ist zu schwach', 'error') last_name,
return redirect(url_for('register')) is_student=False,
student_card_id=None,
action_permissions = None max_borrow_days=None,
page_permissions = None permission_preset=permission_preset,
if use_custom_permissions: action_permissions=action_permissions,
action_permissions = {} page_permissions=page_permissions,
for action_key, _ in PERMISSION_ACTION_OPTIONS:
action_permissions[action_key] = request.form.get(f'action_{action_key}') == 'on'
page_permissions = {}
for endpoint_name, _ in PERMISSION_PAGE_OPTIONS:
page_permissions[endpoint_name] = request.form.get(f'page_{endpoint_name}') == 'on'
us.add_user(
username,
password,
name,
last_name,
is_student=False,
student_card_id=None,
max_borrow_days=None,
permission_preset=permission_preset,
action_permissions=action_permissions,
page_permissions=page_permissions,
)
return redirect(url_for('home_admin'))
return render_template(
'register.html',
library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS
) )
flash('Sie sind nicht berechtigt, diese Seite anzuzeigen', 'error')
return redirect(url_for('login')) flash(f'Benutzer "{username}" wurde erfolgreich registriert!', 'success')
return redirect(url_for('home_admin'))
return render_template(
'register.html',
library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS,
permission_presets=getattr(us, 'PERMISSION_PRESETS', {}),
permission_action_options=PERMISSION_ACTION_OPTIONS,
permission_page_options=PERMISSION_PAGE_OPTIONS
)
@app.route('/user_del', methods=['GET']) @app.route('/user_del', methods=['GET'])
def user_del(): def user_del():
+1
View File
@@ -1,4 +1,5 @@
flask flask
flask-wtf
werkzeug werkzeug
gunicorn gunicorn
pymongo pymongo
+24 -15
View File
@@ -1375,8 +1375,10 @@
<li><a class="dropdown-item" href="{{ url_for('library_loans_admin') }}">Ausleihen / Defekte Items</a></li> <li><a class="dropdown-item" href="{{ url_for('library_loans_admin') }}">Ausleihen / Defekte Items</a></li>
{% endif %} {% endif %}
{% if student_cards_module_enabled %} {% if student_cards_module_enabled %}
{% if current_permissions.actions.get('can_manage_users', False) %}
<li><a class="dropdown-item" href="{{ url_for('student_cards_admin') }}">Bibliotheksausweis</a></li> <li><a class="dropdown-item" href="{{ url_for('student_cards_admin') }}">Bibliotheksausweis</a></li>
{% endif %} {% endif %}
{% endif %}
{% if current_permissions.pages.get('admin_school_settings', False) %} {% if current_permissions.pages.get('admin_school_settings', False) %}
<li><a class="dropdown-item" href="{{ url_for('admin_school_settings') }}">Schulstammdaten</a></li> <li><a class="dropdown-item" href="{{ url_for('admin_school_settings') }}">Schulstammdaten</a></li>
{% endif %} {% endif %}
@@ -1619,11 +1621,10 @@
<div id="cookie-banner" role="dialog" aria-live="polite" aria-label="Cookie-Hinweis"> <div id="cookie-banner" role="dialog" aria-live="polite" aria-label="Cookie-Hinweis">
<div class="cb-inner"> <div class="cb-inner">
<div class="cb-text"> <div class="cb-text">
Wir verwenden technisch notwendige Cookies, um Ihre Sitzung zu verwalten und die Anwendung bereitzustellen. Bitte akzeptieren Sie Cookies, um fortzufahren. Wir verwenden ausschließlich technisch notwendige Cookies, um Ihre Sitzung zu verwalten und die Anwendung bereitzustellen. Bitte bestätigen Sie dies, um fortzufahren.
</div> </div>
<div class="cb-actions"> <div class="cb-actions">
<button class="btn-decline" id="cookie-decline">Ablehnen</button> <button class="btn-accept" id="cookie-accept">Notwendige Cookies akzeptieren</button>
<button class="btn-accept" id="cookie-accept">Akzeptieren</button>
</div> </div>
</div> </div>
</div> </div>
@@ -1728,31 +1729,39 @@
(function(){ (function(){
function getCookie(name){ function getCookie(name){
const v = document.cookie.split(';').map(s=>s.trim()); const v = document.cookie.split(';').map(s=>s.trim());
for(const c of v){ if(c.startsWith(name+'=')) return decodeURIComponent(c.split('=')[1]); } for(const c of v){
if(c.startsWith(name+'=')) return decodeURIComponent(c.split('=')[1]);
}
return null; return null;
} }
function setCookie(name, value, days){ function setCookie(name, value, days){
const d = new Date(); d.setTime(d.getTime() + (days*24*60*60*1000)); const d = new Date();
d.setTime(d.getTime() + (days*24*60*60*1000));
document.cookie = name + '=' + encodeURIComponent(value) + ';expires=' + d.toUTCString() + ';path=/;SameSite=Lax'; document.cookie = name + '=' + encodeURIComponent(value) + ';expires=' + d.toUTCString() + ';path=/;SameSite=Lax';
} }
function showBanner(){ var el = document.getElementById('cookie-banner'); if(el) el.style.display = 'block'; }
function hideBanner(){ var el = document.getElementById('cookie-banner'); if(el) el.style.display = 'none'; }
// If not decided yet, show banner and block app until decision function showBanner(){
var el = document.getElementById('cookie-banner');
if(el) el.style.display = 'block';
}
function hideBanner(){
var el = document.getElementById('cookie-banner');
if(el) el.style.display = 'none';
}
// Prüfen, ob der Nutzer bereits zugestimmt hat
const consent = getCookie('cookie_consent'); const consent = getCookie('cookie_consent');
if(!consent){ if(!consent){
showBanner(); showBanner();
// Optionally blur content until consent
document.body.style.filter = 'none';
} }
// Nur noch der Akzeptieren-Button für vitale Cookies ist vorhanden
document.getElementById('cookie-accept')?.addEventListener('click', function(){ document.getElementById('cookie-accept')?.addEventListener('click', function(){
setCookie('cookie_consent','accepted',365); setCookie('cookie_consent', 'vital_accepted', 365);
hideBanner(); hideBanner();
}); });
document.getElementById('cookie-decline')?.addEventListener('click', function(){
setCookie('cookie_consent','declined',365);
window.location.href = 'https://www.ecosia.org/';
});
const username = {{ (session['username'] if 'username' in session else '')|tojson }}; const username = {{ (session['username'] if 'username' in session else '')|tojson }};
const isTutorialPage = window.location.pathname === {{ url_for('tutorial_page')|tojson }}; const isTutorialPage = window.location.pathname === {{ url_for('tutorial_page')|tojson }};
+2 -6
View File
@@ -25,9 +25,6 @@
<div class="content"> <div class="content">
<div class="form-card"> <div class="form-card">
<form method="POST" action="{{ url_for('register') }}"> <form method="POST" action="{{ url_for('register') }}">
<!-- CSRF-Schutz (Zwingend erforderlich für POST) -->
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group"> <div class="form-group">
<label for="name">Vorname</label> <label for="name">Vorname</label>
@@ -65,17 +62,16 @@
<div class="input-container"> <div class="input-container">
<span class="input-icon">🔒</span> <span class="input-icon">🔒</span>
<!-- HTML5 Pattern blockiert unsichere Passwörter vor dem Absenden --> <!-- HTML5 Pattern blockiert unsichere Passwörter vor dem Absenden -->
<input type="password" <input
id="password" id="password"
name="password" name="password"
placeholder="Geben Sie ein sicheres Passwort ein" placeholder="Geben Sie ein sicheres Passwort ein"
required required
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).{12,}"> pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).{12,}">
</div> </div>
<button type="button" class="btn-secondary" id="toggle-pw-btn" onclick="togglePasswordVisibility()" title="Passwort anzeigen/verbergen">👁️</button>
</div> </div>
<div class="pw-actions"> <div class="pw-actions">
<button type="button" class="btn-secondary" onclick="generateSecurePassword()">🎲 Automatisches Passwort generieren</button> <button type="button" class="btn-secondary" onclick="generateSecurePassword()">Passwort generieren</button>
</div> </div>
</div> </div>
+1
View File
@@ -1,4 +1,5 @@
flask flask
flask-wtf
werkzeug werkzeug
gunicorn gunicorn
pymongo==4.6.3 pymongo==4.6.3