- Add Manrope as primary UI font via Google Fonts (wiregui/pages/style.py) - Add dark/light/auto theme toggle in header, persisted to users.theme_preference - Alembic migration for theme_preference column - Redesign account page with card-based layout matching admin pages - Convert settings page from tabs to stacked cards - Replace all outline buttons with solid unelevated buttons - Fix dark mode: remove hardcoded bg-grey-1/text-grey-7, use theme-safe colors - Fix CI: add ca-certificates to release job for SSL cert verification - Add no-coauthor and commit conventions to CLAUDE.md
14 KiB
14 KiB
WireGUI Implementation TODO
Migration of Wirezone (Elixir/Phoenix) to Python/NiceGUI.
Source: /home/stefanob/PycharmProjects/personal/wirezone
Test count: 164 (163 passing, 1 skipped) | Coverage: 35%
Phase 1: Foundation — Models, DB, Config ✅
pyproject.tomlwith dependencies,uv sync- Package directory structure
wiregui/config.py— pydantic-settings (DB, Redis, WG, auth, SMTP, logging)wiregui/db.py— async engine, sessionmaker,init_db()wiregui/redis.py— Valkey connection pool- All 8 SQLModel models (User, Device, Rule, MFAMethod, OIDCConnection, ApiToken, ConnectivityCheck, Configuration)
- Alembic init + initial migration +
alembic upgrade head wiregui/main.py— app entrypointcompose.yml— PostgreSQL 17 + Valkey 8wiregui/utils/time.py—utcnow()helper for naive UTC timestamps
Phase 2: Auth System — Login + Sessions ✅
wiregui/auth/passwords.py— bcrypt hash/verify (direct bcrypt, not passlib)wiregui/auth/jwt.py— create/decode JWT via python-josewiregui/auth/session.py—authenticate_user()email/password verificationwiregui/auth/middleware.py— HTTP-level auth middleware (available for REST API)wiregui/auth/seed.py— auto-create admin on first startupwiregui/pages/login.py— login page with email/password formwiregui/pages/home.py— authenticated home (redirects to /devices)- Auth guards via
app.storage.useron each page - Logout clears storage and redirects
Phase 3: Device UI — User-Facing CRUD ✅
wiregui/pages/layout.py— shared sidebar + headerwiregui/utils/network.py— IPv4/IPv6 allocation (random offset + scan)wiregui/utils/crypto.py— WG keypair + PSK generation viawgCLIwiregui/utils/wg_conf.py— WG client.confbuilderwiregui/pages/devices.py—/deviceslist + create dialog + delete/devices/{device_id}— detail page with stats and config flags- QR code generation +
.confdownload - Full device create/edit form with all wirezone options (description, per-device config overrides, use_default_* toggles with bound inputs, better layout)
Phase 4: WireGuard Integration ✅
wiregui/services/wireguard.py— async subprocess: ensure_interface, add/remove_peer, get_peers, set_private_key, set_listen_portwiregui/services/events.py— event bridge: device CRUD → WG + firewall- Device create/delete in UI fires WG events
wiregui/tasks/__init__.py— background task registry + cancel_allwiregui/tasks/stats.py— poll WG stats every 60s, update DBwiregui/tasks/reconcile.py— startup reconciliation (diff DB vs WG, add/remove)config.py—wg_enabledflag (default False for dev)- Startup: ensure_interface → reconcile → stats_loop (when wg_enabled)
Phase 5: Firewall (nftables) ✅
wiregui/services/firewall.py— nft CLI: setup_base_tables, masquerade, per-user chains, jump rules, apply_rule, rebuild_all_rules- IPv4/IPv6 aware, TCP/UDP port range support
wiregui/pages/admin/rules.py—/admin/rulesCRUD (action, CIDR, protocol, port, user)- Events: on_rule_created/deleted, on_device_created adds jump rules
- Startup: setup_base_tables + setup_masquerade (when wg_enabled)
- Edit rule — edit dialog in admin rules page with all fields
- Full user chain rebuild on rule update/delete via
_rebuild_user_chain()in events.py
Phase 6: REST API (v0) ✅
wiregui/auth/api_token.py— token generation (random → sha256), Bearer resolution with expiry + disabled user checkswiregui/api/deps.py— get_db, get_current_api_user, require_adminwiregui/schemas/— Pydantic schemas: UserRead/Create/Update, DeviceRead/Create/Update, RuleRead/Create/Update, ConfigurationRead/Updatewiregui/api/v0/users.py— full CRUD (admin only)wiregui/api/v0/devices.py— full CRUD (owner or admin, triggers WG/firewall events)wiregui/api/v0/rules.py— full CRUD (admin only, triggers firewall events)wiregui/api/v0/configuration.py— GET/PUT (admin only, auto-creates singleton)- Mounted on NiceGUI app at
/api/v0
Phase 7: Admin UI ✅
/admin/users— table (email, role, devices, status, last sign-in, method, created), create (email/password/role), edit (email/role/password/disabled), delete with cascading cleanup (devices → WG events, rules)/admin/devices— all devices with user filter, full create form (owner, name, description, all use_default_* toggles with bound override inputs), full edit form, delete with WG events, config + QR on creation/admin/settings— 3 tabs:- Client Defaults (endpoint, DNS, allowed IPs, MTU, keepalive)
- Security (VPN session duration, local auth, unpriv device mgmt/config, OIDC auto-disable)
- Authentication (OIDC provider CRUD with table + dialog; SAML placeholder for Phase 8)
/admin/diagnostics— WG interface status, active peers, connectivity checks, system notifications with clear/clear-allwiregui/services/notifications.py— in-memory deque (capped at 100), add/clear/count/current- Header notification bell badge (admin only, links to diagnostics)
- TODO: SAML provider management in Authentication tab
Phase 8: Advanced Auth (MFA, OIDC, Magic Links, SAML) ✅
- TOTP MFA (
wiregui/auth/mfa.py) — secret generation, URI/QR, verification with clock drift tolerance - MFA challenge page (
/mfa) — 6-digit code entry, multi-method support, last-used tracking - Login page updated: checks for MFA methods after password auth, redirects to
/mfaif present - OIDC (
wiregui/auth/oidc.py) — provider registry from Configuration, authlib Starlette integration - OIDC routes (
/auth/oidc/{provider}+/auth/oidc/{provider}/callback) — auth code flow, user lookup/auto-create, refresh token storage in OIDCConnection - Login page shows OIDC provider buttons dynamically from config
- OIDC refresh task (
wiregui/tasks/oidc_refresh.py) — every 10min, refreshes all stored tokens, creates notifications on failure, respectsdisable_vpn_on_oidc_error - Magic links (
/auth/magic-link+/auth/magic/{user_id}/{token}) — request page, signed JWT with 15min expiry, email via aiosmtplib - Email service (
wiregui/services/email.py) — aiosmtplib send, magic link template /accountpage — 3 tabs: Profile (details + password change), Two-Factor Auth (TOTP registration with QR + verification, list/delete methods), API Tokens (create with configurable expiry, list, delete)- OIDC providers registered on startup from Configuration
- WebAuthn MFA (
wiregui/auth/webauthn.py) — registration/authentication options generation, response verification, credential storage - SAML (
wiregui/auth/saml.py+wiregui/pages/auth_saml.py) — SP-initiated SSO, metadata endpoint, ACS callback, IdP metadata parsing, attribute mapping - WebAuthn browser-side JS integration in account page —
ui.run_javascript()callsnavigator.credentials.create(), serializes response, server verifies and stores credential - SAML provider management UI in admin settings Authentication tab — table + add/delete dialog (config ID, label, XML metadata, sign requests/metadata/assertions/envelopes toggles, auto-create users)
Phase 9: Background Tasks & VPN Session Management
- Task scheduler (
wiregui/tasks/__init__.py) — register/cancel - Stats polling task (Phase 4)
- OIDC refresh task (Phase 8)
- VPN session expiry task (
wiregui/tasks/vpn_session.py) — every 60s, finds expired sessions based onvpn_session_duration+last_signed_in_at, removes WG peers, creates notifications - Connectivity check poller (
wiregui/tasks/connectivity.py) — fetches URL, stores result in DB, notification on failure - Live stats push —
ui.timer(30, ...)on/devices(table refresh),/devices/{id}(RX/TX/handshake/remote IP labels),/admin/devices(table refresh)
Phase 10: Polish, Testing & Deployment
Testing (partially done)
- pytest + pytest-asyncio setup, conftest with test DB
- test_models.py (10 tests), test_auth.py (8 tests), test_utils.py (6 tests), test_services.py (6 tests), test_firewall.py (7 tests)
- test_api.py (6 tests) — token generation, resolution, expiry, disabled user
- test_notifications.py (9 tests) — add, ordering, count, clear, max cap, to_dict
- test_admin.py (13 tests) — user CRUD, cascading deletes, config CRUD, OIDC providers, device overrides
- test_mfa.py (11 tests) — TOTP secret gen, URI, code verification (valid/invalid/wrong secret/empty), QR SVG, DB integration, multi-method
- test_magic_link.py (4 tests) — token creation/expiry/user mismatch, disabled user rejection
- test_account.py (8 tests) — password change flow, API token CRUD, OIDC connection CRUD, refresh token update
- test_integration_mfa.py (7 tests) — full TOTP registration flow, MFA blocks login, wrong code, multi-method, last-used tracking, delete allows bypass, disabled user
- test_integration_oidc.py (10 tests) — provider config loading, connection create/update, auto-create user, disabled user, refresh token, multi-provider
- test_tasks.py (6 tests) — VPN session expiry (expired/unlimited/no-config/disabled user), connectivity check (success/failure with notification)
- HTTP-level integration tests (OIDC redirect/callback flow with respx mocking)
Coverage gaps (35% overall — run uv run pytest --cov=wiregui --cov-report=term-missing --cov-branch)
100% covered: models, schemas, config, auth/passwords, auth/jwt, auth/mfa, auth/api_token, utils/crypto, utils/time, services/notifications
API routes (32-84% — partially covered via httpx TestClient):
wiregui/api/v0/users.py(84%) — list/get/create/update/deletewiregui/api/v0/rules.py(71%) — CRUDwiregui/api/v0/devices.py(67%) — CRUD, permissionswiregui/api/v0/configuration.py(61%) — get/update, auto-createwiregui/api/deps.py(32%) — test get_current_api_user with real Bearer header parsing, require_admin rejection
Services (62-89% covered):
wiregui/services/wireguard.py(62%) — add/remove/get peers mockedwiregui/services/firewall.py(73%) — base tables, chains, rules, rebuild mockedwiregui/services/events.py(80%) — device + rule events, rebuild chainwiregui/services/email.py(89%) — send_email, magic link, no-smtp fallbackwiregui/services/wireguard.py— test ensure_interface, set_private_key, set_listen_portwiregui/services/firewall.py— test _nft/_nft_batch error handling, add_device_jump_rule with only ipv4/ipv6
Tasks (40-84% covered):
wiregui/tasks/stats.py(77%) — update from peers, no-op, unmatched peerwiregui/tasks/reconcile.py(84%) — add missing, remove orphaned, in-syncwiregui/tasks/oidc_refresh.py(40%) — no connections, skip unknown providerwiregui/tasks/oidc_refresh.py— test successful refresh, failure with notification, disable_vpn_on_oidc_error
Auth modules (85-92% covered):
wiregui/auth/oidc.py(87%) — register providers, get_client, load from configwiregui/auth/webauthn.py(85%) — registration/authentication optionswiregui/auth/session.py(90%) — no-password, disabled, nonexistent userwiregui/auth/saml.py(0%) — needs mock SAML IdP metadata + response parsingwiregui/auth/webauthn.py— test verify_registration, verify_authentication with mock credential data
Pages (0% — requires E2E testing):
- Consider Playwright or NiceGUI's testing utilities for E2E page tests
Logging (done)
- Loguru configured (wiregui/logging.py), no print statements
- File logging to
logs/whenWG_LOG_TO_FILE=true
Deployment ✅
- Dockerfile (multi-stage python:3.13-slim)
- compose.prod.yml (bridge networking, NET_ADMIN, nftables)
- Health endpoint
GET /api/health - Forgejo CI: test → semver → Docker registry push
- First-run CLI setup command
- README.md
UI Polish & Styling
Global styling ✅
- Manrope font loaded from Google Fonts as primary UI font (
wiregui/pages/style.py) - Font applied on all pages (layout, login, MFA challenge)
- Dark/light/auto theme toggle in header — cycles with icon button
- Theme preference stored in
users.theme_preferencecolumn (migrationa3f1d8e92b01) - Theme persisted to DB and loaded into session on all login flows (password, MFA, magic link, OIDC, SAML)
Account page (/account) ✅
- Card-based layout matching admin pages (diagnostics, settings)
- Account Details:
ui.grid(columns=2)with bold labels, same as diagnostics - Change Password: inline card section (no modal), outlined inputs, validation
- Connected SSO Providers: always visible card with empty state
- API Tokens: table with status badges, inline create, copy-to-clipboard with green accent card
- MFA: methods table, inline TOTP registration (QR + verify), WebAuthn, empty state
- Danger Zone: red left border accent, typed email confirmation, disabled if only admin
Settings page (/admin/settings) ✅
- Converted from tabbed layout to stacked cards (Client Defaults, Security, Authentication)
Consistency pass ✅
- All buttons solid (
unelevated) — no outline buttons anywhere - All pages use
w-full p-4container withtext-h5 q-mb-mdpage title - All
text-grey-7/text-grey-8replaced with dark-mode-safetext-grey - Sidebar: removed hardcoded
bg-grey-1, uses theme-aware background - Card titles:
text-subtitle1 text-bold+ui.separator()everywhere
Remaining
- SSO Providers: add Status column, "Disconnect" action
- Admin pages (users, devices, rules): apply same card-based styling