This commit is contained in:
Ben de Roo
2026-08-20 16:51:58 +02:00
parent a980812137
commit 08e02dd556
119 changed files with 254 additions and 17469 deletions
+230 -124
View File
@@ -1,17 +1,19 @@
import hashlib
import secrets
import sqlite3
from datetime import datetime, timedelta, timezone
from pathlib import Path
import os
import secrets
import smtplib
import sqlite3
from datetime import datetime, timedelta, timezone
from email.message import EmailMessage
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
DB_NAME = Path(__file__).parent.parent / "database" / "calendar.db"
@@ -28,7 +30,207 @@ def hash_token(token: str) -> str:
).hexdigest()
def update_email(user_id: int, new_email: str):
# ============================================================
# EMAIL
# ============================================================
def send_email(
email: str,
subject: str,
plain_text: str,
html: str,
):
smtp_host = os.getenv("SMTP_HOST")
smtp_port = int(os.getenv("SMTP_PORT", "587"))
smtp_username = os.getenv("SMTP_USERNAME")
smtp_password = os.getenv("SMTP_PASSWORD")
smtp_from = os.getenv("SMTP_FROM")
if not all([
smtp_host,
smtp_username,
smtp_password,
smtp_from,
]):
raise RuntimeError("SMTP configuration is incomplete")
message = EmailMessage()
message["From"] = smtp_from
message["To"] = email
message["Subject"] = subject
# Plain-text fallback
message.set_content(plain_text)
# HTML version
message.add_alternative(
html,
subtype="html",
)
print(f"Sending email to {email}")
with smtplib.SMTP(
smtp_host,
smtp_port,
) as smtp:
print("SMTP connected")
smtp.starttls()
print("TLS started")
smtp.login(
smtp_username,
smtp_password,
)
print("SMTP login successful")
smtp.send_message(message)
print("Email sent")
def send_verification_email(
email: str,
token: str,
):
verification_url = (
"https://ben.de-roo.org"
f"/api/verify-email?token={token}"
)
plain_text = f"""Hello,
You requested to use this email address for your Calendar account.
Verify your email address using this link:
{verification_url}
This link expires after 24 hours.
If you did not request this, you can ignore this email.
"""
html = f"""\
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<p>Hello,</p>
<p>
You requested to use this email address
for your Calendar account.
</p>
<p>
<a href="{verification_url}">
Verify your email address
</a>
</p>
<p>
This link expires after 24 hours.
</p>
<p>
If you did not request this,
you can ignore this email.
</p>
</body>
</html>
"""
send_email(
email=email,
subject="Verify your Calendar email address",
plain_text=plain_text,
html=html,
)
def send_password_reset_email(
email: str,
token: str,
):
password_reset_url = (
"https://ben.de-roo.org"
f"/api/update-password_page?token={token}"
)
plain_text = f"""Hello,
You requested to reset your Calendar password.
Reset your password using this link:
{password_reset_url}
This link expires after 1 hour.
If you did not request this, you can ignore this email.
"""
html = f"""\
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<p>Hello,</p>
<p>
You requested to reset your Calendar password.
</p>
<p>
<a href="{password_reset_url}">
Reset your password
</a>
</p>
<p>
This link expires after 1 hour.
</p>
<p>
If you did not request this,
you can ignore this email.
</p>
</body>
</html>
"""
send_email(
email=email,
subject="Reset your password",
plain_text=plain_text,
html=html,
)
# ============================================================
# EMAIL ADDRESS VERIFICATION
# ============================================================
def update_email(
user_id: int,
new_email: str,
):
new_email = new_email.strip().lower()
conn = get_connection()
@@ -57,7 +259,10 @@ def update_email(user_id: int, new_email: str):
WHERE email = ?
AND id != ?
""",
(new_email, user_id),
(
new_email,
user_id,
),
)
if cur.fetchone() is not None:
@@ -73,7 +278,10 @@ def update_email(user_id: int, new_email: str):
email_verified = 0
WHERE id = ?
""",
(new_email, user_id),
(
new_email,
user_id,
),
)
cur.execute(
@@ -104,7 +312,9 @@ def update_email(user_id: int, new_email: str):
conn.close()
def create_verification(user_id: int):
def create_verification(
user_id: int,
):
token = secrets.token_urlsafe(32)
token_hash = hash_token(token)
@@ -149,7 +359,9 @@ def create_verification(user_id: int):
conn.close()
def verify_email(token: str):
def verify_email(
token: str,
):
token_hash = hash_token(token)
conn = get_connection()
@@ -181,6 +393,7 @@ def verify_email(token: str):
)
if datetime.now(timezone.utc) >= expires_at:
cur.execute(
"""
DELETE FROM email_verifications
@@ -222,121 +435,14 @@ def verify_email(token: str):
finally:
conn.close()
def send_verification_email(email: str, token: str):
smtp_host = os.getenv("SMTP_HOST")
smtp_port = int(os.getenv("SMTP_PORT", "587"))
smtp_username = os.getenv("SMTP_USERNAME")
smtp_password = os.getenv("SMTP_PASSWORD")
smtp_from = os.getenv("SMTP_FROM")
if not all([
smtp_host,
smtp_username,
smtp_password,
smtp_from,
]):
raise RuntimeError("SMTP configuration is incomplete")
# ============================================================
# PASSWORD RESET
# ============================================================
verification_url = (
f"https://ben.de-roo.org/api/verify-email?token={token}"
)
message = EmailMessage()
message["From"] = smtp_from
message["To"] = email
message["Subject"] = "Verify your Calendar email address"
message.set_content(
f"""Hello,
You requested to use this email address for your Calendar account.
Verify your email address using this link:
{verification_url}
This link expires after 24 hours.
If you did not request this, you can ignore this email.
"""
)
print(f"Sending verification email to {email}")
with smtplib.SMTP(smtp_host, smtp_port) as smtp:
print("SMTP connected")
smtp.starttls()
print("TLS started")
smtp.login(
smtp_username,
smtp_password,
)
print("SMTP login successful")
smtp.send_message(message)
print("Verification email sent")
def send_password_reset_email(email: str, token: str):
smtp_host = os.getenv("SMTP_HOST")
smtp_port = int(os.getenv("SMTP_PORT", "587"))
smtp_username = os.getenv("SMTP_USERNAME")
smtp_password = os.getenv("SMTP_PASSWORD")
smtp_from = os.getenv("SMTP_FROM")
if not all([
smtp_host,
smtp_username,
smtp_password,
smtp_from,
]):
raise RuntimeError("SMTP configuration is incomplete")
password_reset_url = (
f"https://ben.de-roo.org/api/update-password_page?token={token}"
)
message = EmailMessage()
message["From"] = smtp_from
message["To"] = email
message["Subject"] = "Reset your password"
message.set_content(
f"""Hello,
You requested to use this email address for reseting your Calendar password.
Reset your password using this link:
{password_reset_url}
This link expires after 24 hours.
If you did not request this, you can ignore this email.
"""
)
print(f"Sending password reset email to {email}")
with smtplib.SMTP(smtp_host, smtp_port) as smtp:
print("SMTP connected")
smtp.starttls()
print("TLS started")
smtp.login(
smtp_username,
smtp_password,
)
print("SMTP login successful")
smtp.send_message(message)
print("Password reset email sent")
def create_password_reset(user_id: int):
def create_password_reset(
user_id: int,
):
token = secrets.token_urlsafe(32)
token_hash = hash_token(token)
@@ -378,4 +484,4 @@ def create_password_reset(user_id: int):
return token
finally:
conn.close()
conn.close()