"""Narrow authenticated transactional mail receiver for user-engine.""" from __future__ import annotations import hmac import hashlib import json import os import smtplib import sqlite3 import secrets from datetime import datetime, timedelta, timezone from email.message import EmailMessage from email.utils import parseaddr from http import HTTPStatus from threading import RLock from wsgiref.simple_server import make_server ALLOWED_EVENTS = {"family_member.invited", "family_invitation.resent"} class SQLiteDeliveryStore: def __init__(self, path: str) -> None: self.db = sqlite3.connect(path, check_same_thread=False) self.lock = RLock() self.db.execute("CREATE TABLE IF NOT EXISTS deliveries (event_id TEXT PRIMARY KEY, provider_ref TEXT NOT NULL)") self.db.execute("""CREATE TABLE IF NOT EXISTS verifications ( request_id TEXT PRIMARY KEY, handle_hash TEXT UNIQUE NOT NULL, registration_id TEXT NOT NULL, email TEXT NOT NULL, username TEXT NOT NULL, client_id TEXT NOT NULL, tenant TEXT NOT NULL, display_name TEXT, expires_at TEXT NOT NULL, consumed_at TEXT )""") self.db.commit() def reference(self, event_id: str) -> str | None: row = self.db.execute("SELECT provider_ref FROM deliveries WHERE event_id=?", (event_id,)).fetchone() return row[0] if row else None def record(self, event_id: str, reference: str) -> None: with self.db: self.db.execute("INSERT INTO deliveries VALUES (?, ?)", (event_id, reference)) def create_verification(self, payload: dict, handle_hash: str, expires_at: str) -> str: request_id = f"vrq_{secrets.token_hex(12)}" with self.db: self.db.execute("INSERT INTO verifications VALUES (?,?,?,?,?,?,?,?,?,NULL)", ( request_id, handle_hash, payload["registration_id"], payload["normalized_email"], payload["preferred_username"], payload["client_id"], payload["tenant"], payload.get("display_name"), expires_at, )) return request_id def consume_verification(self, handle_hash: str) -> dict: with self.lock, self.db: row = self.db.execute("SELECT * FROM verifications WHERE handle_hash=?", (handle_hash,)).fetchone() if row is None or row[9] is not None: raise ValueError("verification_invalid") if datetime.fromisoformat(row[8]) <= datetime.now(timezone.utc): raise ValueError("verification_expired") self.db.execute("UPDATE verifications SET consumed_at=? WHERE request_id=?", (datetime.now(timezone.utc).isoformat(), row[0])) return {"request_id":row[0],"registration_id":row[2],"email":row[3], "preferred_username":row[4],"client_id":row[5],"tenant":row[6],"display_name":row[7]} class SMTPProvider: def __init__(self, host: str, port: int, username: str, password: str, sender: str) -> None: self.host, self.port, self.username, self.password, self.sender = host, port, username, password, sender def send(self, recipient: str, subject: str, text: str) -> str: message = EmailMessage() message["From"], message["To"], message["Subject"] = self.sender, recipient, subject message.set_content(text) with smtplib.SMTP(self.host, self.port, timeout=10) as smtp: smtp.starttls() smtp.login(self.username, self.password) smtp.send_message(message) return message["Message-ID"] or f"smtp:{abs(hash((recipient, subject)))}" class TransactionalApplication: def __init__(self, store: SQLiteDeliveryStore, provider, bearer_token: str, portal_url: str) -> None: self.store, self.provider, self.token = store, provider, bearer_token self.portal_url = portal_url.rstrip("/") def __call__(self, environ, start_response): if environ.get("PATH_INFO") in ("/healthz", "/readyz"): return self._json(start_response, HTTPStatus.OK, {"status":"ok"}) path = environ.get("PATH_INFO") if path not in ("/v1/send", "/v1/registration-verifications", "/v1/registration-verifications/consume") or environ.get("REQUEST_METHOD") != "POST": return self._json(start_response, HTTPStatus.NOT_FOUND, {"error":"not_found"}) if not hmac.compare_digest(str(environ.get("HTTP_AUTHORIZATION", "")), f"Bearer {self.token}"): return self._json(start_response, HTTPStatus.UNAUTHORIZED, {"error":"unauthorized"}) try: length = int(environ.get("CONTENT_LENGTH") or 0) if length <= 0 or length > 128 * 1024: raise ValueError("invalid_size") payload = json.loads(environ["wsgi.input"].read(length)) if path == "/v1/registration-verifications": return self._request_verification(start_response, payload) if path == "/v1/registration-verifications/consume": return self._consume_verification(start_response, payload) event_id = str(payload["id"]) if environ.get("HTTP_IDEMPOTENCY_KEY") != event_id: raise ValueError("idempotency_key_mismatch") if payload.get("source") != "user-engine" or payload.get("type") not in ALLOWED_EVENTS: raise ValueError("template_not_allowed") recipient = str(payload.get("data", {}).get("primary_email", "")) if not _valid_address(recipient): raise ValueError("invalid_recipient") existing = self.store.reference(event_id) if existing: return self._json(start_response, HTTPStatus.OK, {"status":"duplicate","reference":existing}) invitation_id = str(payload.get("data", {}).get("invitation_id", "")) if not invitation_id: raise ValueError("invitation_id_required") reference = self.provider.send(recipient, "Your NetKingdom invitation", f"You have been invited. Continue securely at {self.portal_url}/invitations/{invitation_id}\n") self.store.record(event_id, reference) except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc: return self._json(start_response, HTTPStatus.BAD_REQUEST, {"error":str(exc)}) except (TimeoutError, OSError, smtplib.SMTPException): return self._json(start_response, HTTPStatus.SERVICE_UNAVAILABLE, {"error":"provider_unavailable","retryable":True}) return self._json(start_response, HTTPStatus.ACCEPTED, {"status":"accepted","reference":reference}) def _request_verification(self, start_response, payload): required = ("registration_id", "normalized_email", "preferred_username", "client_id", "tenant", "correlation_id") if any(not payload.get(key) for key in required) or not _valid_address(str(payload["normalized_email"])): raise ValueError("invalid_verification_request") handle = secrets.token_urlsafe(32) expires_at = datetime.now(timezone.utc) + timedelta(minutes=30) request_id = self.store.create_verification( payload, hashlib.sha256(handle.encode()).hexdigest(), expires_at.isoformat() ) self.provider.send( str(payload["normalized_email"]), "Verify your NetKingdom registration", f"Continue securely at {self.portal_url}/registration/verify?handle={handle}\nThis link expires in 30 minutes.\n", ) return self._json(start_response, HTTPStatus.ACCEPTED, {"request_id":request_id,"accepted":True}) def _consume_verification(self, start_response, payload): handle = str(payload.get("handle") or "") if len(handle) < 32: raise ValueError("verification_invalid") evidence = self.store.consume_verification(hashlib.sha256(handle.encode()).hexdigest()) return self._json(start_response, HTTPStatus.OK, { "purpose":"public-registration", "verification_id":f"fvr_{secrets.token_hex(12)}", **evidence, "source_system":"email-connect", "assurance":{"mailbox_control":True}, }) @staticmethod def _json(start_response, status, payload): body=json.dumps(payload).encode(); start_response(f"{status.value} {status.phrase}", [("Content-Type","application/json"),("Content-Length",str(len(body)))]) return [body] def _valid_address(value: str) -> bool: _, address = parseaddr(value) return address == value and "@" in address and "\n" not in address and "\r" not in address def main() -> None: provider = SMTPProvider(os.environ["EMAIL_CONNECT_SMTP_HOST"], int(os.environ.get("EMAIL_CONNECT_SMTP_PORT", "587")), os.environ["EMAIL_CONNECT_SMTP_USERNAME"], os.environ["EMAIL_CONNECT_SMTP_PASSWORD"], os.environ["EMAIL_CONNECT_SENDER"]) app = TransactionalApplication(SQLiteDeliveryStore(os.environ.get("EMAIL_CONNECT_DATABASE_PATH", "/data/email-connect.db")), provider, os.environ["EMAIL_CONNECT_INGEST_TOKEN"].strip(), os.environ["EMAIL_CONNECT_PORTAL_URL"]) with make_server("0.0.0.0", int(os.environ.get("EMAIL_CONNECT_HTTP_PORT", "8080")), app) as server: server.serve_forever()