update emails and verifing

This commit is contained in:
ben
2026-08-18 11:32:01 +02:00
parent 5f77a4d995
commit 953c371e48
3 changed files with 357 additions and 14 deletions
+272
View File
@@ -0,0 +1,272 @@
import hashlib
import secrets
import sqlite3
from datetime import datetime, timedelta, timezone
from pathlib import Path
import os
import smtplib
from email.message import EmailMessage
from dotenv import load_dotenv
load_dotenv()
DB_NAME = Path(__file__).parent / "database" / "calendar.db"
def get_connection():
conn = sqlite3.connect(DB_NAME)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA foreign_keys = ON;")
return conn
def hash_token(token: str) -> str:
return hashlib.sha256(
token.encode("utf-8")
).hexdigest()
def update_email(user_id: int, new_email: str):
new_email = new_email.strip().lower()
conn = get_connection()
cur = conn.cursor()
try:
cur.execute(
"""
SELECT id
FROM users
WHERE id = ?
""",
(user_id,),
)
if cur.fetchone() is None:
return {
"status": "failed",
"reason": "user_not_found",
}
cur.execute(
"""
SELECT id
FROM users
WHERE email = ?
AND id != ?
""",
(new_email, user_id),
)
if cur.fetchone() is not None:
return {
"status": "failed",
"reason": "email_already_in_use",
}
cur.execute(
"""
UPDATE users
SET email = ?,
email_verified = 0
WHERE id = ?
""",
(new_email, user_id),
)
cur.execute(
"""
DELETE FROM email_verifications
WHERE user_id = ?
""",
(user_id,),
)
conn.commit()
return {
"status": "updated",
"email": new_email,
"email_verified": False,
}
except sqlite3.IntegrityError:
conn.rollback()
return {
"status": "failed",
"reason": "email_already_in_use",
}
finally:
conn.close()
def create_verification(user_id: int):
token = secrets.token_urlsafe(32)
token_hash = hash_token(token)
expires_at = (
datetime.now(timezone.utc)
+ timedelta(hours=24)
).isoformat()
conn = get_connection()
cur = conn.cursor()
try:
cur.execute(
"""
DELETE FROM email_verifications
WHERE user_id = ?
""",
(user_id,),
)
cur.execute(
"""
INSERT INTO email_verifications (
user_id,
token_hash,
expires_at
)
VALUES (?, ?, ?)
""",
(
user_id,
token_hash,
expires_at,
),
)
conn.commit()
return token
finally:
conn.close()
def verify_email(token: str):
token_hash = hash_token(token)
conn = get_connection()
cur = conn.cursor()
try:
cur.execute(
"""
SELECT
id,
user_id,
expires_at
FROM email_verifications
WHERE token_hash = ?
""",
(token_hash,),
)
verification = cur.fetchone()
if verification is None:
return {
"status": "failed",
"reason": "invalid_token",
}
expires_at = datetime.fromisoformat(
verification["expires_at"]
)
if datetime.now(timezone.utc) >= expires_at:
cur.execute(
"""
DELETE FROM email_verifications
WHERE id = ?
""",
(verification["id"],),
)
conn.commit()
return {
"status": "failed",
"reason": "token_expired",
}
cur.execute(
"""
UPDATE users
SET email_verified = 1
WHERE id = ?
""",
(verification["user_id"],),
)
cur.execute(
"""
DELETE FROM email_verifications
WHERE id = ?
""",
(verification["id"],),
)
conn.commit()
return {
"status": "verified",
}
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")
verification_url = (
f"https://jouwdomein.nl/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.
"""
)
with smtplib.SMTP(smtp_host, smtp_port) as smtp:
smtp.starttls()
smtp.login(
smtp_username,
smtp_password,
)
smtp.send_message(message)