feat: magic-link auth with access requests and RBAC

This commit is contained in:
Jonas Miederer 2026-09-11 17:27:58 +02:00
parent c2125f063b
commit c8280f7078
13 changed files with 1058 additions and 2 deletions

66
backend/app/auth/mail.py Normal file
View file

@ -0,0 +1,66 @@
"""Magic-link delivery. SMTP when configured, dev log fallback otherwise."""
from __future__ import annotations
import logging
import smtplib
from email.message import EmailMessage
from app.settings import settings
logger = logging.getLogger(__name__)
def magic_link(token: str) -> str:
return f"{settings.frontend_url.rstrip('/')}/login?token={token}"
def send_magic_link(to_email: str, link: str) -> bool:
"""Mail the login link. Returns False (logs instead) when SMTP is unset.
The plaintext link only ever appears here: service/api logging must
never include tokens or session ids (see test_auth no-leak test).
"""
if not settings.smtp_host:
logger.info("SMTP not configured; magic link for %s: %s", to_email, link)
return False
message = EmailMessage()
message["Subject"] = "Your kirche-einfach login link"
message["To"] = to_email
message["From"] = settings.mail_from or settings.contact_to or to_email
if settings.contact_to:
message["Reply-To"] = settings.contact_to
message.set_content(
f"Hello,\n\nUse this link to log in (valid 15 minutes, single use):\n{link}\n"
)
with smtplib.SMTP(settings.smtp_host, settings.smtp_port, timeout=10) as smtp:
smtp.starttls()
if settings.smtp_username:
smtp.login(settings.smtp_username, settings.smtp_password)
smtp.send_message(message)
logger.info("magic link mailed to %s", to_email)
return True
def send_access_notification(request_id: str, name: str, email: str) -> bool:
"""Notify CONTACT_TO about a new access request (best effort)."""
if not settings.smtp_host or not settings.contact_to:
logger.info("new access request %s from %s <%s>", request_id, name, email)
return False
message = EmailMessage()
message["Subject"] = f"New access request: {name}"
message["To"] = settings.contact_to
message["From"] = settings.mail_from or settings.contact_to
message.set_content(
f"Name: {name}\nEmail: {email}\nRequest: {request_id}\n"
)
try:
with smtplib.SMTP(settings.smtp_host, settings.smtp_port, timeout=10) as smtp:
smtp.starttls()
if settings.smtp_username:
smtp.login(settings.smtp_username, settings.smtp_password)
smtp.send_message(message)
except OSError:
logger.warning("access-request notification failed for %s", request_id)
return False
return True