feat: magic-link auth with access requests and RBAC
This commit is contained in:
parent
c2125f063b
commit
c8280f7078
13 changed files with 1058 additions and 2 deletions
66
backend/app/auth/mail.py
Normal file
66
backend/app/auth/mail.py
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue