The dashboard overview counted all proxy hosts without a cert as ACME
certs, ignoring wildcard deduplication. The certificates page only
deduplicated the current page of results (25 rows) but used a full
count(*) for the total, so hosts on other pages covered by wildcards
were never subtracted.
Now both pages fetch all ACME hosts, apply full deduplication (cert
wildcard coverage + ACME wildcard collapsing), then paginate/count
from the deduplicated result.
Also fixes a strict-mode violation in the E2E test where DataTable's
dual mobile/desktop rendering caused getByText to match two elements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous fix (92fa1cb) only collapsed subdomains when an explicit
managed/imported certificate had a wildcard. When all hosts use Caddy
Auto (no certificate assigned), the wildcard ACME host was not checked
against sibling subdomain hosts, so each showed as a separate entry.
Also adds a startup migration to rename the stored IONOS DNS credential
key from api_token to auth_api_token for users who configured IONOS
before ef62ef2.
Closes#110
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
better-auth's default username validator only allows [a-zA-Z0-9_.],
rejecting hyphens with a generic "invalid username or password" error.
Added a custom validator that also permits hyphens.
Closes#112
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a wildcard cert (e.g. *.domain.de) existed and a proxy host was created
for a subdomain (e.g. sub.domain.de) without explicitly linking it, the
certificates page showed it as a separate ACME entry. Now hosts covered by
an existing wildcard cert are attributed to that cert's "Used by" list instead.
Closes#110
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded Cloudflare DNS-01 with a data-driven provider registry
supporting 11 providers (Cloudflare, Route 53, DigitalOcean, Duck DNS,
Hetzner, Vultr, Porkbun, GoDaddy, Namecheap, OVH, Linode). Users can
configure multiple providers with encrypted credentials and select a
default. Per-certificate provider override is supported via providerOptions.
- Add src/lib/dns-providers.ts with provider definitions, credential
encrypt/decrypt, and Caddy config builder
- Change DnsProviderSettings to multi-provider format with default selection
- Auto-migrate legacy Cloudflare settings on startup (db.ts)
- Normalize old single-provider format on read (getDnsProviderSettings)
- Refactor buildTlsAutomation() to use provider registry
- Add GET /api/v1/dns-providers endpoint for provider discovery
- Add dns-provider settings group to REST API and instance sync
- Replace Cloudflare settings card with multi-provider UI (add/remove
providers, set default, dynamic credential forms)
- Add 10 DNS provider modules to Caddy Dockerfile
- Update OpenAPI spec, E2E tests, and unit test mocks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allow users to exclude specific paths from Authentik/CPM forward auth
protection. When excluded_paths is set, all paths require authentication
EXCEPT the excluded ones — useful for apps like Navidrome that need
/share/* and /rest/* to bypass auth.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Set workers: 1 to eliminate parallelism race conditions
- Fix groups test: use .first() for "0 members" assertion
- Fix access-control helper: match by name instead of generic "Delete List"
- Fix forward-auth-oauth: target Dex button specifically, handle /login in Dex URL
- Add comprehensive API security E2E tests (316 tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests every /api/v1/ endpoint (86 endpoints × 4 auth levels = 316 tests):
- Unauthenticated requests → 401 on all endpoints
- User role → 403 on admin-only endpoints
- Viewer role → 403 on admin-only endpoints
- Admin role → allowed on all endpoints
- Cross-user isolation: users cannot access other users' profiles
Uses Bearer API tokens (created directly in DB) to avoid
Better Auth rate limiting during test execution.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace 0.0.0.0/0 with RFC 5737 test ranges (198.51.100.0/24, etc.) in
persistence tests so saving geoblock rules to Caddy doesn't block real
traffic for concurrent test workers. The LAN Only preset save test uses
the API to verify saved values and immediately resets, minimizing the
window where block-all is active.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Radix UI Tabs and Accordion unmount inactive/closed content from the DOM
by default. This caused hidden form inputs to be missing from FormData on
submit — saving while on the Block tab wiped all Allow rules (and vice
versa), and saving with the advanced accordion collapsed wiped redirect
URL, trusted proxies, and response settings.
Fix by adding forceMount to TabsContent and AccordionContent so all form
fields remain in the DOM regardless of which tab/panel is visible.
Also adds E2E regression tests covering both scenarios plus the RFC1918
preset, with proper afterEach cleanup to prevent test interference with
concurrent workers.
Fixes#99
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verifies all Docker containers in the test stack are running and healthy,
including a restart-count check on the l4-port-manager to detect permission
errors or other crash-loop scenarios that previously went unnoticed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Intermittent failure — the default 5s wasn't enough when the page
loaded slowly during a long E2E run (227/228 passed).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- dashboard.spec.ts: anchor regex /^\d+\s+Proxy Hosts/ to not match
"L4 Proxy Hosts" sidebar link
- role-access.spec.ts: use exact: true for "Proxy Hosts" link
- users.spec.ts: match any user count (/\d+ users?/) since other test
suites create additional users
- groups.spec.ts: remove unused emptyText variable
- link-account.spec.ts: remove unused context parameter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- dashboard: Match stat cards via link role with count+label pattern
to avoid matching subtitle paragraph containing "certificates"
- role-access: Use Bun.password.hash (built-in bcrypt) instead of
bcryptjs which is not installed in the production container
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- api-docs: Don't rely on CDN-loaded Swagger UI class in test env
- dashboard: Use `p` locator for stat card labels to avoid matching nav
- groups: Scope add-member click to bordered container to avoid nav match
- link-account: Remove assertion on error= URL param (not always present)
- portal: Use exact:true for "Sign in" button (OAuth button also matches)
- role-access: Use ESM imports in bun -e script, use getByLabel for login
fields, increase waitForURL timeout, use exact button match
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allow non-admin users (user/viewer) to access / and /profile while
blocking admin-only pages. The dashboard layout now uses requireUser()
instead of requireAdmin(), and the sidebar filters nav items by role.
Non-admin users see a minimal welcome page without stat cards.
New test files (86 tests across 7 files):
- dashboard, users, groups, api-docs, portal, link-account specs
- role-access spec with full RBAC coverage for all 3 roles
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DataTable renders mobile (block md:hidden) and desktop (hidden md:block)
variants. .first() resolved to the hidden mobile element at desktop viewport.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add udp/ prefix to upstream dial addresses for UDP proxy hosts
(Caddy L4 requires udp/ prefix on both listen and dial for UDP)
- Fix TCP "disabled host" test to check data echo instead of connection
refusal (Docker port mapping always accepts TCP handshake)
- Add waitForTcpRoute before "both ports" test to handle re-enable timing
- Increase UDP route wait timeout to 30s for listener startup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix Caddy L4 config to use "udp/:PORT" listen syntax for UDP proxy hosts
(previously used bare ":PORT" which Caddy treated as TCP)
- Fix TCP unused port test to check data echo instead of connection refusal
(Docker port mapping accepts TCP handshake even without a Caddy listener)
- Fix mTLS import test to wait for sheet close and scope cert name to table
- Fix CA certificate generate test to scope name assertion to table
- Remaining L4 routing test failures are infrastructure issues with Docker
port forwarding and Caddy L4 UDP listener startup timing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New l4_proxy_hosts table and Drizzle migration (0015)
- Full CRUD model layer with validation, audit logging, and Caddy config
generation (buildL4Servers integrating into buildCaddyDocument)
- Server actions, paginated list page, create/edit/delete dialogs
- L4 port manager sidecar (docker/l4-port-manager) that auto-recreates
the caddy container when port mappings change via a trigger file
- Auto-detects Docker Compose project name from caddy container labels
- Supports both named-volume and bind-mount (COMPOSE_HOST_DIR) deployments
- getL4PortsStatus simplified: status file is sole source of truth,
trigger files deleted after processing to prevent stuck 'Waiting' banner
- Navigation entry added (CableIcon)
- Tests: unit (entrypoint.sh invariants + validation), integration (ports
lifecycle + caddy config), E2E (CRUD + functional routing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds two new UI-configurable Caddy patterns that previously required raw JSON:
- Per-path redirect rules (from/to/status) emitted as a subroute handler before
auth so .well-known paths work without login; supports full URLs, cross-domain
targets, and wildcard path patterns (e.g. /.well-known/*)
- Path prefix rewrite that prepends a segment to every request before proxying
(e.g. /recipes → upstream sees /recipes/original/path)
Config is stored in the existing meta JSON column (no schema migration). Includes
integration tests for meta serialization and E2E functional tests against a real
Caddy instance covering relative/absolute destinations, all 3xx status codes, and
various wildcard combinations. Adds traefik/whoami to the test stack to verify
rewritten paths actually reach the upstream.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- accept wildcard proxy host domains like *.example.com with validation and normalization
- make exact hosts win over overlapping wildcards in generated routes and TLS policies
- add unit coverage for host-pattern priority and wildcard domain handling
- add a single test:all entry point and clean up lint/typecheck issues so the suite runs cleanly
- run mobile layout Playwright checks under both chromium and mobile-iphone
- Create tests/e2e/mobile/mobile-layout.spec.ts with 8 tests covering
AppBar/hamburger visibility, drawer open/close, mobile card rendering,
PageHeader button layout, dialog width, card actions, and analytics overflow.
- Fix AnalyticsClient: make Autocomplete full-width on mobile, add
overflow:hidden to outer Stack to prevent body scrollWidth growth.
- Fix WorldMapInner: remove hard-coded minWidth:400 that caused 73px
horizontal overflow on 393px iPhone 15 viewport.
- Fix DashboardLayoutClient: add overflowX:hidden to main content area
to contain chart library elements that exceed viewport width.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>