wiregui/TODO.md
Stefano Bertelli 5adb0c86ce
Some checks failed
CI / test (push) Failing after 2m4s
CI / release (push) Has been skipped
CI / docker (push) Has been skipped
feat: add E2E tests for device creation and account management
10 E2E tests using NiceGUI's User fixture:
- Device creation flow and name validation
- Password change (success, wrong current, mismatch, too short)
- API token creation, TOTP registration, invalid code rejection
- Account deletion with email confirmation

Tests live in tests/e2e/ with a separate conftest that loads the
NiceGUI testing plugin. CI runs unit and E2E tests as separate steps.
2026-03-30 22:26:15 -05:00

14 KiB

WireGUI Implementation TODO

Migration of Wirezone (Elixir/Phoenix) to Python/NiceGUI. Source: /home/stefanob/PycharmProjects/personal/wirezone

Test count: 174 (173 passing, 1 skipped) | Coverage: 35%


Phase 1: Foundation — Models, DB, Config

  • pyproject.toml with 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 entrypoint
  • compose.yml — PostgreSQL 17 + Valkey 8
  • wiregui/utils/time.pyutcnow() 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-jose
  • wiregui/auth/session.pyauthenticate_user() email/password verification
  • wiregui/auth/middleware.py — HTTP-level auth middleware (available for REST API)
  • wiregui/auth/seed.py — auto-create admin on first startup
  • wiregui/pages/login.py — login page with email/password form
  • wiregui/pages/home.py — authenticated home (redirects to /devices)
  • Auth guards via app.storage.user on each page
  • Logout clears storage and redirects

Phase 3: Device UI — User-Facing CRUD

  • wiregui/pages/layout.py — shared sidebar + header
  • wiregui/utils/network.py — IPv4/IPv6 allocation (random offset + scan)
  • wiregui/utils/crypto.py — WG keypair + PSK generation via wg CLI
  • wiregui/utils/wg_conf.py — WG client .conf builder
  • wiregui/pages/devices.py/devices list + create dialog + delete
  • /devices/{device_id} — detail page with stats and config flags
  • QR code generation + .conf download
  • 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_port
  • wiregui/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_all
  • wiregui/tasks/stats.py — poll WG stats every 60s, update DB
  • wiregui/tasks/reconcile.py — startup reconciliation (diff DB vs WG, add/remove)
  • config.pywg_enabled flag (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/rules CRUD (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 checks
  • wiregui/api/deps.py — get_db, get_current_api_user, require_admin
  • wiregui/schemas/ — Pydantic schemas: UserRead/Create/Update, DeviceRead/Create/Update, RuleRead/Create/Update, ConfigurationRead/Update
  • wiregui/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-all
  • wiregui/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

  • 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 /mfa if 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, respects disable_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
  • /account page — 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() calls navigator.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 on vpn_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/delete
  • wiregui/api/v0/rules.py (71%) — CRUD
  • wiregui/api/v0/devices.py (67%) — CRUD, permissions
  • wiregui/api/v0/configuration.py (61%) — get/update, auto-create
  • wiregui/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 mocked
  • wiregui/services/firewall.py (73%) — base tables, chains, rules, rebuild mocked
  • wiregui/services/events.py (80%) — device + rule events, rebuild chain
  • wiregui/services/email.py (89%) — send_email, magic link, no-smtp fallback
  • wiregui/services/wireguard.py — test ensure_interface, set_private_key, set_listen_port
  • wiregui/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 peer
  • wiregui/tasks/reconcile.py (84%) — add missing, remove orphaned, in-sync
  • wiregui/tasks/oidc_refresh.py (40%) — no connections, skip unknown provider
  • wiregui/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 config
  • wiregui/auth/webauthn.py (85%) — registration/authentication options
  • wiregui/auth/session.py (90%) — no-password, disabled, nonexistent user
  • wiregui/auth/saml.py (0%) — needs mock SAML IdP metadata + response parsing
  • wiregui/auth/webauthn.py — test verify_registration, verify_authentication with mock credential data

E2E page tests (via NiceGUI User fixture in tests/e2e/):

  • tests/e2e/test_devices.py (2 tests) — add device full flow, name validation
  • tests/e2e/test_account.py (8 tests) — change password (success/wrong/mismatch/short), create API token, TOTP registration + invalid code, account deletion
  • E2E tests for admin pages (users, devices, rules, settings)

Logging (done)

  • Loguru configured (wiregui/logging.py), no print statements
  • File logging to logs/ when WG_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_preference column (migration a3f1d8e92b01)
  • 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-4 container with text-h5 q-mb-md page title
  • All text-grey-7 / text-grey-8 replaced with dark-mode-safe text-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