Complete Python/NiceGUI rewrite of the Wirezone (Elixir/Phoenix) VPN management platform. All 10 implementation phases delivered. Core stack: - NiceGUI reactive UI with SQLModel ORM on PostgreSQL (asyncpg) - Alembic migrations, Valkey/Redis cache, pydantic-settings config - WireGuard management via subprocess (wg/ip/nft CLIs) - 164 tests passing, 35% code coverage Features: - User/device/rule CRUD with admin and unprivileged roles - Full device config form with per-device WG overrides - WireGuard client config generation with QR codes - REST API (v0) with Bearer token auth for all resources - TOTP MFA with QR registration and challenge flow - OIDC SSO with authlib (provider registry, auto-create users) - Magic link passwordless sign-in via email - SAML SP-initiated SSO with IdP metadata parsing - WebAuthn/FIDO2 security key registration - nftables firewall with per-user chains and masquerade - Background tasks: WG stats polling, VPN session expiry, OIDC token refresh, WAN connectivity checks - Startup reconciliation (DB ↔ WireGuard state sync) - In-memory notification system with header badge - Admin UI: users, devices, rules, settings (3 tabs), diagnostics - Loguru logging with optional timestamped file output Deployment: - Multi-stage Dockerfile (python:3.13-slim) - Docker Compose prod stack (bridge networking, NET_ADMIN, nftables) - Forgejo CI: tests → semantic versioning → Docker registry push - Health endpoint at /api/health
51 lines
1.4 KiB
Python
51 lines
1.4 KiB
Python
"""Email sending via aiosmtplib for magic links and notifications."""
|
|
|
|
import aiosmtplib
|
|
from email.message import EmailMessage
|
|
|
|
from loguru import logger
|
|
|
|
from wiregui.config import get_settings
|
|
|
|
|
|
async def send_email(to: str, subject: str, body: str) -> bool:
|
|
"""Send an email via configured SMTP. Returns True on success."""
|
|
settings = get_settings()
|
|
|
|
if not settings.smtp_host:
|
|
logger.warning("SMTP not configured — email to {} not sent", to)
|
|
return False
|
|
|
|
msg = EmailMessage()
|
|
msg["From"] = settings.smtp_from
|
|
msg["To"] = to
|
|
msg["Subject"] = subject
|
|
msg.set_content(body)
|
|
|
|
try:
|
|
await aiosmtplib.send(
|
|
msg,
|
|
hostname=settings.smtp_host,
|
|
port=settings.smtp_port,
|
|
username=settings.smtp_user,
|
|
password=settings.smtp_password,
|
|
start_tls=True,
|
|
)
|
|
logger.info("Email sent to {}: {}", to, subject)
|
|
return True
|
|
except Exception as e:
|
|
logger.error("Failed to send email to {}: {}", to, e)
|
|
return False
|
|
|
|
|
|
async def send_magic_link(to: str, link: str) -> bool:
|
|
"""Send a magic link sign-in email."""
|
|
subject = "WireGUI — Sign in link"
|
|
body = f"""You requested a sign-in link for WireGUI.
|
|
|
|
Click here to sign in:
|
|
{link}
|
|
|
|
This link expires in 15 minutes. If you didn't request this, you can safely ignore this email.
|
|
"""
|
|
return await send_email(to, subject, body)
|