diff --git a/Website/main.py b/Website/main.py
index 3581428..de44222 100644
--- a/Website/main.py
+++ b/Website/main.py
@@ -1878,6 +1878,17 @@ def login():
stored_user = _normalize_user_doc(stored_user_raw)
if stored_user:
+ if stored_user.get("verified", True) is False:
+ if request.is_json:
+ return jsonify({
+ "error": "Account not verified",
+ "username": stored_user.get("username"),
+ "needs_verification": True
+ }, 403)
+
+ flash('Bitte verifizieren Sie Ihre E-Mail-Adresse, bevor Sie sich einloggen.', 'error')
+ return redirect(url_for('login'))
+
session['username'] = stored_user.get("username")
session['display_name'] = stored_user.get("display_name") or stored_user.get("username")
session['is_admin'] = stored_user.get("is_admin", False)
@@ -1889,8 +1900,8 @@ def login():
if request.is_json:
return jsonify({"error": "Invalid credentials"}), 401
+
flash('Login fehlgeschlagen. Bitte prüfen Sie Ihre Eingaben.', 'error')
- get_flashed_messages()
return render_template('login.html')
@@ -1934,26 +1945,73 @@ def register():
contact_person = _sanitize_text(contact_person, 120)
email = _sanitize_text(email, 254)
+ from modules.emailservice.email import send_register_token
+ import secrets
+
+ token = secrets.token_hex(3).upper()
+
try:
existing_users = user_store.get_all_users() or []
is_first_user = len(existing_users) == 0
- if not user_store.add_user(username, password, school_name, contact_person, email, marketing_opt_in=marketing_opt_in):
+ if not user_store.add_user(
+ username, password, school_name, contact_person, email,
+ marketing_opt_in=marketing_opt_in,
+ verified=False,
+ verification_token=token
+ ):
flash("Benutzer konnte nicht erstellt werden.", "error")
return redirect(url_for("register"))
if is_first_user:
user_store.make_admin(username)
+
+ email_sent = send_register_token(email, token)
+ if not email_sent:
+ flash("Konto erstellt, aber E-Mail konnte nicht gesendet werden. Bitte Support kontaktieren.", "error")
+ else:
+ flash("Schulregistrierung erfolgreich. Bitte überprüfen Sie Ihre E-Mails für den Bestätigungscode.", "success")
+
except Exception:
flash("MongoDB ist derzeit nicht erreichbar.", "error")
return redirect(url_for("register"))
- flash("Schulregistrierung erfolgreich. Bitte jetzt einloggen.", "success")
- return redirect(url_for("login"))
+ return redirect(url_for("verify_account", username=username))
return render_template("register.html")
+@app.route("/verify", methods=["GET", "POST"])
+def verify_account():
+ if request.method == "POST":
+ username = (request.form.get("username") or "").strip()
+ token = (request.form.get("token") or "").strip().upper()
+
+ if not username or not token:
+ flash("Bitte Benutzername und Token eingeben.", "error")
+ return redirect(url_for("verify_account"))
+
+ user = _find_user(username)
+ if not user:
+ flash("Benutzer nicht gefunden.", "error")
+ return redirect(url_for("verify_account"))
+
+ if user.get("verified"):
+ flash("Konto ist bereits verifiziert. Bitte einloggen.", "success")
+ return redirect(url_for("login"))
+
+ if user.get("verification_token") == token:
+ user_store.verify_user(username)
+ flash("Konto erfolgreich verifiziert! Sie können sich jetzt einloggen.", "success")
+ return redirect(url_for("login"))
+ else:
+ flash("Ungültiger Bestätigungscode. Bitte erneut versuchen.", "error")
+ return redirect(url_for("verify_account", username=username))
+
+ prefilled_username = request.args.get("username", "")
+ return render_template("verify.html", username=prefilled_username)
+
+
@app.route('/download-sample')
def download_sample():
# Ensure the file is only served from the specific directory
@@ -1969,11 +2027,6 @@ def preise():
return render_template("preise.html")
-from datetime import datetime
-from flask import request, jsonify, session, flash, redirect, url_for, render_template
-from pymongo.errors import PyMongoError
-
-
@app.route('/preise/book-option', methods=['POST'])
@login_required
def book_option_package():
diff --git a/Website/static/modules/emailservice/__init__.py b/Website/modules/emailservice/__init__.py
similarity index 100%
rename from Website/static/modules/emailservice/__init__.py
rename to Website/modules/emailservice/__init__.py
diff --git a/Website/modules/emailservice/email.py b/Website/modules/emailservice/email.py
new file mode 100644
index 0000000..f9f086f
--- /dev/null
+++ b/Website/modules/emailservice/email.py
@@ -0,0 +1,136 @@
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+import smtplib
+import time
+
+
+def _build_smtp_client():
+ smtp = smtplib.SMTP(
+ "mail.invario-software.de",
+ 587,
+ timeout=10,
+ )
+ smtp.ehlo()
+ if True: # Always use TLS for security
+ smtp.starttls()
+ smtp.ehlo()
+ smtp.login("no-reply@invario-software.de", "#,EATwIn,68")
+ return smtp
+
+
+def send(email: list | str, subject: str, text_body: str, html_body: str = None) -> bool:
+ """Sends the email with both Plain Text and HTML support."""
+
+ if isinstance(email, str):
+ email = [email]
+
+ TEXT_SIGNATURE = (
+ "\n\n--\n"
+ "Mit freundlichen Grüßen\n"
+ "Ihr Invario Team\n\n"
+ "Invario UG\n"
+ "Am Sportplatz 10\n"
+ "83052 Bruckmühl"
+ )
+
+ HTML_SIGNATURE = """
+
+
|
+ Mit freundlichen Grüßen, +Ihr Invario Team +
+ Invario UG |
+
{html_safe_text}
{HTML_SIGNATURE}" + + mails_per_second = 10 + interval = 1.0 / mails_per_second + smtp = None + + try: + smtp = _build_smtp_client() + + for i, recipient in enumerate(email): + start_time = time.time() + + msg = MIMEMultipart("alternative") + msg["Subject"] = subject + msg["From"] = "Invario Team+ Vielen Dank für Ihre Registrierung. Um die Einrichtung Ihres Kontos abzuschließen und Ihre E-Mail-Adresse zu verifizieren, verwenden Sie bitte den folgenden Bestätigungscode: +
+ +
+ Dieser Code ist aus Sicherheitsgründen nur für eine begrenzte Zeit gültig.
+ Falls Sie kein Konto bei uns erstellt haben, können Sie diese E-Mail sicher ignorieren.
+
|
- Mit freundlichen Grüßen -Automatisierter Email Verteiler für die Schule: {cfg.get_school_info().get("name")} - Invario UG |
-
{body_message}
-Wir haben Ihnen einen Bestätigungscode per E-Mail gesendet. Bitte geben Sie diesen ein, um Ihr Konto zu aktivieren.
+ + +