implementation of the email systrem and the verification for the user account
This commit is contained in:
+62
-9
@@ -1878,6 +1878,17 @@ def login():
|
|||||||
stored_user = _normalize_user_doc(stored_user_raw)
|
stored_user = _normalize_user_doc(stored_user_raw)
|
||||||
|
|
||||||
if stored_user:
|
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['username'] = stored_user.get("username")
|
||||||
session['display_name'] = stored_user.get("display_name") or 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)
|
session['is_admin'] = stored_user.get("is_admin", False)
|
||||||
@@ -1889,8 +1900,8 @@ def login():
|
|||||||
|
|
||||||
if request.is_json:
|
if request.is_json:
|
||||||
return jsonify({"error": "Invalid credentials"}), 401
|
return jsonify({"error": "Invalid credentials"}), 401
|
||||||
|
|
||||||
flash('Login fehlgeschlagen. Bitte prüfen Sie Ihre Eingaben.', 'error')
|
flash('Login fehlgeschlagen. Bitte prüfen Sie Ihre Eingaben.', 'error')
|
||||||
get_flashed_messages()
|
|
||||||
|
|
||||||
return render_template('login.html')
|
return render_template('login.html')
|
||||||
|
|
||||||
@@ -1934,26 +1945,73 @@ def register():
|
|||||||
contact_person = _sanitize_text(contact_person, 120)
|
contact_person = _sanitize_text(contact_person, 120)
|
||||||
email = _sanitize_text(email, 254)
|
email = _sanitize_text(email, 254)
|
||||||
|
|
||||||
|
from modules.emailservice.email import send_register_token
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
token = secrets.token_hex(3).upper()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
existing_users = user_store.get_all_users() or []
|
existing_users = user_store.get_all_users() or []
|
||||||
is_first_user = len(existing_users) == 0
|
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")
|
flash("Benutzer konnte nicht erstellt werden.", "error")
|
||||||
return redirect(url_for("register"))
|
return redirect(url_for("register"))
|
||||||
|
|
||||||
if is_first_user:
|
if is_first_user:
|
||||||
user_store.make_admin(username)
|
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:
|
except Exception:
|
||||||
flash("MongoDB ist derzeit nicht erreichbar.", "error")
|
flash("MongoDB ist derzeit nicht erreichbar.", "error")
|
||||||
return redirect(url_for("register"))
|
return redirect(url_for("register"))
|
||||||
|
|
||||||
flash("Schulregistrierung erfolgreich. Bitte jetzt einloggen.", "success")
|
return redirect(url_for("verify_account", username=username))
|
||||||
return redirect(url_for("login"))
|
|
||||||
|
|
||||||
return render_template("register.html")
|
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')
|
@app.route('/download-sample')
|
||||||
def download_sample():
|
def download_sample():
|
||||||
# Ensure the file is only served from the specific directory
|
# Ensure the file is only served from the specific directory
|
||||||
@@ -1969,11 +2027,6 @@ def preise():
|
|||||||
return render_template("preise.html")
|
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'])
|
@app.route('/preise/book-option', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def book_option_package():
|
def book_option_package():
|
||||||
|
|||||||
@@ -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 = """
|
||||||
|
<br><br>
|
||||||
|
<table cellpadding="0" cellspacing="0" border="0" style="font-family: Arial, Helvetica, sans-serif; font-size: 13px; color: #555555; line-height: 1.5; border-top: 1px solid #eaebed; padding-top: 15px; width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p style="margin:0 0 5px 0;">Mit freundlichen Grüßen,</p>
|
||||||
|
<p style="margin:0;"><strong style="font-size:15px; color: #333333;">Ihr Invario Team</strong></p><br>
|
||||||
|
<p style="margin:0 0 0 0; font-size: 12px; color: #888888;">
|
||||||
|
<strong>Invario UG</strong><br>
|
||||||
|
Am Sportplatz 10<br>
|
||||||
|
83052 Bruckmühl
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
"""
|
||||||
|
|
||||||
|
text_content = f"{text_body}{TEXT_SIGNATURE}"
|
||||||
|
|
||||||
|
if html_body:
|
||||||
|
html_content = f"<html><body style='background-color: #f9f9f9; padding: 20px;'><div style='background-color: #ffffff; padding: 30px; border-radius: 8px; max-width: 600px; margin: 0 auto; box-shadow: 0px 2px 5px rgba(0,0,0,0.05);'>{html_body}{HTML_SIGNATURE}</div></body></html>"
|
||||||
|
else:
|
||||||
|
# Fallback if only text is provided
|
||||||
|
html_safe_text = text_body.replace('\n', '<br>')
|
||||||
|
html_content = f"<html><body><p style='font-family: Arial, sans-serif; color: #333333;'>{html_safe_text}</p>{HTML_SIGNATURE}</body></html>"
|
||||||
|
|
||||||
|
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 <no-reply@invario-software.de>"
|
||||||
|
msg["To"] = str(recipient)
|
||||||
|
|
||||||
|
msg.attach(MIMEText(text_content, "plain"))
|
||||||
|
msg.attach(MIMEText(html_content, "html"))
|
||||||
|
|
||||||
|
smtp.sendmail(
|
||||||
|
from_addr="no-reply@invario-software.de",
|
||||||
|
to_addrs=[recipient],
|
||||||
|
msg=msg.as_string()
|
||||||
|
)
|
||||||
|
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
sleep_time = interval - elapsed_time
|
||||||
|
|
||||||
|
if sleep_time > 0 and i < len(email) - 1:
|
||||||
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Debug: Fehler beim Senden der E-Mail: {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if smtp:
|
||||||
|
try:
|
||||||
|
smtp.quit()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def send_register_token(email: str, token: str) -> bool:
|
||||||
|
"""Sends a professionally styled registration token to the user."""
|
||||||
|
|
||||||
|
subject = "Aktion erforderlich: Ihr Bestätigungscode für Invario"
|
||||||
|
|
||||||
|
text_note = (
|
||||||
|
"Herzlich willkommen bei Invario!\n\n"
|
||||||
|
"Um die Einrichtung Ihres Kontos abzuschließen und Ihre E-Mail-Adresse zu bestätigen, "
|
||||||
|
"geben Sie bitte den folgenden Sicherheitscode ein:\n\n"
|
||||||
|
f"Code: {token}\n\n"
|
||||||
|
"Dieser Code ist aus Sicherheitsgründen nur für begrenzte Zeit gültig. "
|
||||||
|
"Falls Sie diese Registrierung nicht angefordert haben, können Sie diese E-Mail einfach ignorieren."
|
||||||
|
)
|
||||||
|
|
||||||
|
html_note = f"""
|
||||||
|
<div style="font-family: Arial, Helvetica, sans-serif; color: #333333;">
|
||||||
|
<h2 style="color: #2c3e50; margin-top: 0;">Willkommen bei Invario!</h2>
|
||||||
|
<p style="font-size: 15px; line-height: 1.6;">
|
||||||
|
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:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div style="background-color: #f1f5f9; border-left: 4px solid #3b82f6; border-radius: 4px; padding: 25px; text-align: center; margin: 30px 0;">
|
||||||
|
<span style="font-size: 32px; font-weight: bold; letter-spacing: 8px; color: #1e293b;">{token}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style="font-size: 13px; color: #64748b; line-height: 1.5;">
|
||||||
|
Dieser Code ist aus Sicherheitsgründen nur für eine begrenzte Zeit gültig.<br>
|
||||||
|
Falls Sie kein Konto bei uns erstellt haben, können Sie diese E-Mail sicher ignorieren.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
return send(email, subject, text_body=text_note, html_body=html_note)
|
||||||
@@ -4,3 +4,4 @@ bleach>=6.1,<7.0
|
|||||||
pymongo>=4.8,<5.0
|
pymongo>=4.8,<5.0
|
||||||
gunicorn>=22.0,<23.0
|
gunicorn>=22.0,<23.0
|
||||||
requests
|
requests
|
||||||
|
email
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
from email.mime.multipart import MIMEMultipart
|
|
||||||
from email.mime.text import MIMEText
|
|
||||||
import smtplib
|
|
||||||
import time
|
|
||||||
|
|
||||||
import Web.modules.database.settings as cfg
|
|
||||||
|
|
||||||
|
|
||||||
def _build_smtp_client():
|
|
||||||
smtp = smtplib.SMTP(
|
|
||||||
cfg.EMAIL_SMTP_HOST,
|
|
||||||
cfg.EMAIL_SMTP_PORT,
|
|
||||||
timeout=cfg.EMAIL_TIMEOUT_SECONDS,
|
|
||||||
)
|
|
||||||
smtp.ehlo()
|
|
||||||
if cfg.EMAIL_USE_TLS:
|
|
||||||
smtp.starttls()
|
|
||||||
smtp.ehlo()
|
|
||||||
if cfg.EMAIL_USERNAME:
|
|
||||||
smtp.login(cfg.EMAIL_USERNAME, cfg.EMAIL_PASSWORD or "")
|
|
||||||
return smtp
|
|
||||||
|
|
||||||
|
|
||||||
def send(email: list | str, subject: str, note: str, sender: str) -> bool:
|
|
||||||
"""Sends the email with the link to the Clients."""
|
|
||||||
if not cfg.MODULES.is_enabled("mail"):
|
|
||||||
print("Debug: Module not enabled")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if isinstance(email, str):
|
|
||||||
email = [email]
|
|
||||||
|
|
||||||
body_message = note
|
|
||||||
|
|
||||||
HTML_SIGNATURE = f"""
|
|
||||||
<table cellpadding="0" cellspacing="0" border="0" style="font-family: Arial, Helvetica, sans-serif; font-size: 13px; color: #333333; line-height: 1.5;">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<p style="margin:0 0 12px 0;">Mit freundlichen Grüßen</p>
|
|
||||||
<p style="margin:0;"><strong style="font-size:16px;">Automatisierter Email Verteiler für die Schule: {cfg.get_school_info().get("name")}</strong><br></p><br>
|
|
||||||
<p style="margin:12px 0 0 0;"><strong>Invario UG</strong><br>Am Sportplatz 10<br>83052 Bruckmühl</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
"""
|
|
||||||
|
|
||||||
text_content = f"{body_message}\n\nMit freundlichen Grüßen\n{sender}\n"
|
|
||||||
|
|
||||||
html_content = f"""
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<p>{body_message}</p>
|
|
||||||
<br>
|
|
||||||
{HTML_SIGNATURE}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
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"] = str(subject)
|
|
||||||
msg["From"] = f"{sender} <{cfg.EMAIL_USERNAME}>"
|
|
||||||
msg["To"] = str(recipient)
|
|
||||||
|
|
||||||
msg.attach(MIMEText(text_content, "plain"))
|
|
||||||
msg.attach(MIMEText(html_content, "html"))
|
|
||||||
|
|
||||||
smtp.sendmail(
|
|
||||||
from_addr=cfg.EMAIL_USERNAME,
|
|
||||||
to_addrs=[recipient],
|
|
||||||
msg=msg.as_string()
|
|
||||||
)
|
|
||||||
|
|
||||||
elapsed_time = time.time() - start_time
|
|
||||||
sleep_time = interval - elapsed_time
|
|
||||||
|
|
||||||
if sleep_time > 0 and i < len(email) - 1:
|
|
||||||
time.sleep(sleep_time)
|
|
||||||
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Debug: Fehler beim Senden der E-Mail: {e}")
|
|
||||||
return False
|
|
||||||
finally:
|
|
||||||
if smtp:
|
|
||||||
try:
|
|
||||||
smtp.quit()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
@@ -87,6 +87,82 @@
|
|||||||
color: #0a5f8f;
|
color: #0a5f8f;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom Modal Overlay Styles */
|
||||||
|
.custom-modal-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(14, 32, 48, 0.5);
|
||||||
|
backdrop-filter: blur(3px);
|
||||||
|
z-index: 1000;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal-overlay.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal {
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 16px;
|
||||||
|
max-width: 440px;
|
||||||
|
width: 90%;
|
||||||
|
box-shadow: 0 20px 44px rgba(14, 32, 48, 0.15);
|
||||||
|
border: 1px solid #d4e0e9;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal h3 {
|
||||||
|
color: #0f344d;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal p {
|
||||||
|
color: #547184;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-primary {
|
||||||
|
background: #0a5f8f;
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-secondary {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #19445f;
|
||||||
|
border: 1px solid #c4d5e2;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-secondary:hover {
|
||||||
|
background: #e2e8f0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -107,4 +183,45 @@
|
|||||||
</form>
|
</form>
|
||||||
<p class="auth-meta">Noch kein Konto? <a href="{{ url_for('register') }}">Jetzt registrieren</a></p>
|
<p class="auth-meta">Noch kein Konto? <a href="{{ url_for('register') }}">Jetzt registrieren</a></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Custom Verification Warning Modal -->
|
||||||
|
<div id="verifyModal" class="custom-modal-overlay">
|
||||||
|
<div class="custom-modal">
|
||||||
|
<h3>Konto nicht verifiziert</h3>
|
||||||
|
<p id="modalMessage">Ihr Konto wurde noch nicht aktiviert. Bitte geben Sie Ihren Bestätigungscode ein oder fordern Sie einen neuen an.</p>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<a id="goToVerifyBtn" href="{{ url_for('verify_account') }}" class="modal-btn-primary">Code eingeben</a>
|
||||||
|
<a href="#" id="changeEmailLink" class="modal-btn-secondary">E-Mail-Adresse ändern / Hilfe</a>
|
||||||
|
<button type="button" onclick="closeModal()" class="modal-btn-secondary" style="border: none; background: transparent; color: #64748b;">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function showVerificationModal(username) {
|
||||||
|
const modal = document.getElementById('verifyModal');
|
||||||
|
const verifyBtn = document.getElementById('goToVerifyBtn');
|
||||||
|
if (username) {
|
||||||
|
verifyBtn.href = "{{ url_for('verify_account') }}?username=" + encodeURIComponent(username);
|
||||||
|
}
|
||||||
|
modal.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
const modal = document.getElementById('verifyModal');
|
||||||
|
modal.classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example hook: If your backend flashes or passes an unverified flag,
|
||||||
|
// you can automatically trigger it, or trigger it via a failed JSON login response.
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
{% if "verif" in message|lower %}
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
showVerificationModal("{{ request.form.get('username', '') }}");
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endwith %}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Konto verifizieren{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{{ super() }}
|
||||||
|
<style>
|
||||||
|
/* Add the same .auth-wrap and related CSS from register.html here,
|
||||||
|
or move them to a shared base.css file to keep templates clean */
|
||||||
|
.auth-wrap {
|
||||||
|
max-width: 560px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 18px;
|
||||||
|
border: 1px solid #d4e0e9;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 20px 44px rgba(14, 32, 48, 0.08);
|
||||||
|
}
|
||||||
|
.auth-wrap h1 { font-size: 2rem; color: #0f344d; margin-bottom: 0.4rem; }
|
||||||
|
.auth-wrap p { margin-bottom: 1.3rem; }
|
||||||
|
.field { margin-bottom: 0.9rem; }
|
||||||
|
.field label { display: block; margin-bottom: 0.35rem; font-weight: 700; color: #19445f; }
|
||||||
|
.field input { width: 100%; padding: 0.68rem 0.78rem; border-radius: 10px; border: 1px solid #c4d5e2; font-size: 1rem; color: #12384f; }
|
||||||
|
.field input:focus { outline: none; border-color: #2f79a7; box-shadow: 0 0 0 3px rgba(58, 133, 180, 0.2); }
|
||||||
|
.auth-btn { width: 100%; border: none; border-radius: 999px; padding: 0.72rem; font-size: 1rem; font-weight: 700; color: #ffffff; background: linear-gradient(120deg, #0a5f8f 0%, #0b4567 100%); cursor: pointer; margin-top: 1rem;}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="auth-wrap">
|
||||||
|
<h1>E-Mail bestätigen</h1>
|
||||||
|
<p>Wir haben Ihnen einen Bestätigungscode per E-Mail gesendet. Bitte geben Sie diesen ein, um Ihr Konto zu aktivieren.</p>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ url_for('verify_account') }}">
|
||||||
|
<div class="field">
|
||||||
|
<label for="username">Benutzername</label>
|
||||||
|
<input id="username" name="username" type="text" value="{{ username }}" required {% if username %}readonly{% endif %}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="token">Bestätigungscode</label>
|
||||||
|
<input id="token" name="token" type="text" placeholder="z.B. A1B2C3" autocomplete="off" required autofocus>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="auth-btn" type="submit">Konto aktivieren</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user