Commit Graph

56 Commits

Author SHA1 Message Date
fuomag9
3a16d6e9b1 Replace next-auth with Better Auth, migrate DB columns to camelCase
- Replace next-auth v5 beta with better-auth v1.6.2 (stable releases)
- Add multi-provider OAuth support with admin UI configuration
- New oauthProviders table with encrypted secrets (AES-256-GCM)
- Env var bootstrap (OAUTH_*) syncs to DB, UI-created providers fully editable
- OAuth provider REST API: GET/POST/PUT/DELETE /api/v1/oauth-providers
- Settings page "Authentication Providers" section for admin management
- Account linking uses new accounts table (multi-provider per user)
- Username plugin for credentials sign-in (replaces email@localhost pattern)
- bcrypt password compatibility (existing hashes work)
- Database-backed sessions via Kysely adapter (bun:sqlite direct)
- Configurable rate limiting via AUTH_RATE_LIMIT_* env vars
- All DB columns migrated from snake_case to camelCase
- All TypeScript types/models migrated to camelCase properties
- Removed casing: "snake_case" from Drizzle config
- Callback URL format: {baseUrl}/api/auth/oauth2/callback/{providerId}
- package-lock.json removed and gitignored (using bun.lock)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:11:48 +02:00
fuomag9
f007f2df0c Use safe test-range CIDRs in geoblock E2E tests to prevent worker interference
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>
2026-04-11 19:16:49 +02:00
fuomag9
f9169c2ab2 Fix geo blocking form losing rules when switching tabs or collapsing accordion
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>
2026-04-11 16:34:31 +02:00
fuomag9
2dea2af20f Add E2E container health tests to catch crash-looping sidecars
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>
2026-04-10 13:26:17 +02:00
fuomag9
5d0b4837d8 Security hardening: fix SQL injection, WAF bypass, placeholder injection, and more
- C1: Replace all ClickHouse string interpolation with parameterized queries
  (query_params) to eliminate SQL injection in analytics endpoints
- C3: Strip Caddy placeholder patterns from redirect rules, protected paths,
  and Authentik auth endpoint to prevent config injection
- C4: Replace WAF custom directive blocklist with allowlist approach — only
  SecRule/SecAction/SecMarker/SecDefaultAction permitted; block ctl:ruleEngine
  and Include directives
- H2: Validate GCM authentication tag is exactly 16 bytes before decryption
- H3: Validate forward auth redirect URIs (scheme, no credentials) to prevent
  open redirects
- H4: Switch 11 analytics/WAF/geoip endpoints from session-only requireAdmin
  to requireApiAdmin supporting both Bearer token and session auth
- H5: Add input validation for instance-mode (whitelist) and sync-token
  (32-char minimum) in settings API
- M1: Add non-root user to l4-port-manager Dockerfile
- M5: Document Caddy admin API binding security rationale
- Document C2 (custom config injection) and H1 (SSRF via upstreams) as
  intentional admin features

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 12:13:50 +02:00
fuomag9
e1c97038d4 Migrate analytics from SQLite to ClickHouse
SQLite was too slow for analytical aggregations on traffic_events and
waf_events (millions of rows, GROUP BY, COUNT DISTINCT). ClickHouse is
a columnar OLAP database purpose-built for this workload.

- Add ClickHouse container to Docker Compose with health check
- Create src/lib/clickhouse/client.ts with singleton client, table DDL,
  insert helpers, and all analytics query functions
- Update log-parser.ts and waf-log-parser.ts to write to ClickHouse
- Remove purgeOldEntries — ClickHouse TTL handles 90-day retention
- Rewrite analytics-db.ts and waf-events.ts to query ClickHouse
- Remove trafficEvents/wafEvents from SQLite schema, add migration
- CLICKHOUSE_PASSWORD is required (no hardcoded default)
- Update .env.example, README, and test infrastructure

API response shapes are unchanged — no frontend modifications needed.
Parse state (file offsets) remains in SQLite.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 00:05:38 +02:00
fuomag9
833284efb1 Add forward auth E2E tests with Dex OIDC provider
- Add Dex OIDC provider to test Docker Compose stack with static test
  users (alice, bob) and pre-configured OAuth client
- Add forward-auth.spec.ts: credential-based forward auth flow tests
  (redirect, portal form, login, session cookie, forged cookie rejection)
- Add forward-auth-oauth.spec.ts: full OAuth forward auth flow tests
  including user-based access (allowed/denied), group-based access,
  access revocation, and credential login coexisting with OAuth
- Add waitForStatus helper for polling specific HTTP status codes
- Expand portal.spec.ts with OAuth button visibility, URI scheme
  rejection, and strict alert selector tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 21:10:28 +02:00
fuomag9
1ea6add989 Increase timeout for proxy hosts sort button visibility check
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>
2026-04-06 13:51:57 +02:00
fuomag9
2f12475ab0 Fix E2E test locator ambiguity and lint errors
- 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>
2026-04-06 09:59:11 +02:00
fuomag9
785cfb6cc5 Fix dashboard stat card selector and use Bun.password.hash for test users
- 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>
2026-04-06 01:04:09 +02:00
fuomag9
bc5658f164 Fix 7 E2E test failures from strict mode violations and environment issues
- 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>
2026-04-06 01:01:15 +02:00
fuomag9
7fe6b10788 Add E2E tests for untested pages and enforce role-based access control
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>
2026-04-06 00:58:22 +02:00
fuomag9
155268a180 Fix 19 ESLint unused-variable errors across source and test files
Remove unused imports, functions, and variables flagged by
@typescript-eslint/no-unused-vars and no-useless-assignment rules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:32:54 +02:00
fuomag9
0542ed56cb Fix mTLS fail-closed bypass when all certs for a CA are revoked
Two bugs caused mTLS to be silently disabled when all issued client
certificates for a CA were revoked:

1. New cert-based trust model (caddy.ts): When deriving CA IDs from
   trusted cert IDs, revoked certs were invisible (active-only query),
   causing derivedCaIds to be empty and the domain to be dropped from
   mTlsDomainMap entirely — no mTLS policy at all. Fix by falling back
   to a cert-ID-to-CA-ID lookup that includes revoked certs, keeping the
   domain in the map so it gets a fail-closed policy.

2. Legacy CA-based model (caddy-mtls.ts): buildClientAuthentication
   returned null when all certs were revoked, relying on Caddy's
   experimental "drop" TLS connection policy field which didn't work
   reliably. Fix by pinning to the CA cert itself as a trusted_leaf_certs
   entry — no client cert can hash-match a CA certificate (and presenting
   the CA cert would require its private key, already a full compromise).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:31:12 +02:00
fuomag9
881992b6cc Restrict analytics, GeoIP status, and OpenAPI spec to admin role
Pentest found that all 8 analytics API endpoints, the GeoIP status
endpoint, and the OpenAPI spec were accessible to any authenticated
user. Since the user role should only have access to forward auth
and self-service, these are now admin-only.

- analytics/*: requireUser → requireAdmin
- geoip-status: requireUser → requireAdmin
- openapi.json: add requireApiAdmin + change Cache-Control to private
- analytics/api-docs pages: requireUser → requireAdmin (defense-in-depth)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:02:13 +02:00
fuomag9
94efaad5dd Add user management admin page with role, status, and profile editing
- New /users page with search, inline editing, role/status changes, and deletion
- Model: added updateUserRole, updateUserStatus, deleteUser functions
- API: PUT /api/v1/users/[id] now supports role and status fields, added DELETE
- Safety: cannot change own role/status or delete own account

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:40:10 +02:00
fuomag9
03c8f40417 Add forward auth portal — CPM as built-in IdP replacing Authentik
CPM can now act as its own forward auth provider for proxied sites.
Users authenticate at a login portal (credentials or OAuth) and Caddy
gates access via a verify subrequest, eliminating the need for external
IdPs like Authentik.

Key components:
- Forward auth flow: verify endpoint, exchange code callback, login portal
- User groups with membership management
- Per-proxy-host access control (users and/or groups)
- Caddy config generation for forward_auth handler + callback route
- OAuth and credential login on the portal page
- Admin UI: groups page, inline user/group assignment in proxy host form
- REST API: /api/v1/groups, /api/v1/forward-auth-sessions, per-host access
- Integration tests for groups and forward auth schema

Also fixes mTLS E2E test selectors broken by the RBAC refactor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:32:17 +02:00
fuomag9
277ae6e79c Add mTLS RBAC with path-based access control, role/cert trust model, and comprehensive tests
Implements full role-based access control for mTLS client certificates:
- Database: mtls_roles, mtls_certificate_roles, mtls_access_rules tables with migration
- Models: CRUD for roles, cert-role assignments, path-based access rules
- Caddy config: HTTP-layer RBAC enforcement via CEL fingerprint matching in subroutes
- New trust model: select individual certs or entire roles instead of CAs (derives CAs automatically)
- REST API: /api/v1/mtls-roles, cert assignments, proxy-host access rules endpoints
- UI: Roles management tab (card-based), cert/role trust picker, inline RBAC rule editor
- Fix: dialog autoclose bug after creating proxy host (key-based remount)
- Tests: 85 new tests (785 total) covering models, schema, RBAC route generation, leaf override, edge cases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:40:21 +02:00
fuomag9
b9a88c4330 fix: remove ACME cert scanning to eliminate caddy-data permission issue (#88)
Caddy's certmagic creates storage dirs with hardcoded 0700 permissions,
making the web container's supplementary group membership ineffective.
Rather than working around this with ACLs or chmod hacks, remove the
feature entirely — it was cosmetic (issuer/expiry display) for certs
that Caddy auto-manages anyway.

Also bump access list dropdown timeout from 5s to 10s to fix flaky E2E test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:34:18 +02:00
fuomag9
71502e4879 test: full pipeline integration test for geo-blocked request analytics
Tests the complete chain: raw Caddy log lines → collectBlockedSignatures
→ parseLine → INSERT into DB → all analytics queries (summary, countries,
timeline, blocked events list) return correct blocked counts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 00:13:05 +02:00
fuomag9
e26d7a2c3f feat: improve LocationRulesFields UI and add unit tests for buildLocationReverseProxy
- Replace textarea with per-upstream rows (protocol dropdown + address input),
  matching the existing UpstreamInput component pattern
- Export buildLocationReverseProxy for testing
- Add 14 unit tests covering: dial formatting, HTTPS/TLS transport,
  host header preservation, path sanitization, IPv6, mixed upstreams

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 14:51:08 +01:00
fuomag9
55f4ba4e80 fix: make location rules integration test honest about what it tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:51:08 +01:00
fuomag9
0f9bd04ec7 feat: add LocationRule type and model layer support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:51:08 +01:00
fuomag9
7a12ecf2fe fix: E2E select dropdown reliability and stale eslint-disable directives
- Fix Radix Select interaction in proxy host E2E helper: scroll trigger
  into view and wait for option visibility before clicking (fixes flaky
  access-control.spec.ts timeout)
- Apply same fix to certificate selector for consistency
- Remove stale eslint-disable directives from pre-existing test files
  (now covered by test-wide eslint config override)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:50:14 +01:00
fuomag9
d9806e84e6 fix: resolve lint and typecheck errors in API routes and tests
- Remove unused imports (users, and) from api-tokens model
- Fix password_hash destructure lint error in user routes
- Fix apiErrorResponse mock pattern in all 12 test files (use instanceof)
- Remove stale eslint-disable directives from test files
- Add eslint override for tests (no-explicit-any, no-require-imports)
- Fix unused vars in settings and tokens tests
- Fix unused tokenB in integration test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:33:47 +01:00
fuomag9
28f61082ce test: comprehensive API test coverage with full input variations
- OpenAPI endpoint: 5 tests (spec structure, paths, headers, schemas)
- Proxy hosts: POST/PUT/GET with all nested fields (authentik, load_balancer,
  dns_resolver, geoblock, waf, mtls, redirects, rewrite) + error cases
- L4 proxy hosts: TCP+TLS, UDP, matcher/protocol variations + error cases
- Certificates: managed with provider_options, imported with PEM fields
- Client certificates: all required fields, revoke with revoked_at
- Access lists: seed users, entry add with username/password + error cases
- Settings: GET+PUT for all 11 groups (was 3), full data shapes
- API auth: empty Bearer, CSRF session vs Bearer, apiErrorResponse variants
- API tokens integration: cross-user isolation, admin visibility, inactive user
- CA certificates: PUT/DELETE error cases

646 tests total (54 new)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:15:22 +01:00
fuomag9
de28478a42 feat: add comprehensive REST API with token auth, OpenAPI docs, and full test coverage
- API token model (SHA-256 hashed, debounced lastUsedAt) with Bearer auth
- Dual auth middleware (session + API token) in src/lib/api-auth.ts
- 23 REST endpoints under /api/v1/ covering all functionality:
  tokens, proxy-hosts, l4-proxy-hosts, certificates, ca-certificates,
  client-certificates, access-lists, settings, instances, users,
  audit-log, caddy/apply
- OpenAPI 3.1 spec at /api/v1/openapi.json with fully typed schemas
- Swagger UI docs page at /api-docs in the dashboard
- API token management integrated into the Profile page
- Fix: next build now works under Node.js (bun:sqlite aliased to better-sqlite3)
- 89 new API route unit tests + 11 integration tests (592 total)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 09:45:45 +01:00
fuomag9
a2fefb5a07 fix: use .last() for empty state check in L4 hosts E2E test
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>
2026-03-23 09:35:48 +01:00
fuomag9
452bb6eb78 fix: unskip all E2E tests, fix L4 empty state and analytics mobile overflow
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 08:30:25 +01:00
fuomag9
2d081372f0 fix: L4 UDP proxy routing and TCP disable/re-enable test reliability
- 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>
2026-03-23 08:10:02 +01:00
fuomag9
6b297a11ad fix: update mTLS E2E test selectors for shadcn UI components
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 07:53:50 +01:00
fuomag9
7261fa24d8 fix: add udp/ prefix to Caddy L4 UDP listen addresses and fix E2E test selectors
- 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>
2026-03-22 22:37:39 +01:00
fuomag9
9c60d11c2c feat: improve UI contrast, dark mode, dialog sizing, color coherence, and add table sorting
- Fix dialog scrollability (flex layout + max-h-[90dvh]) and increase L4 dialog to lg width
- Add styled enable card to L4 dialog matching proxy host pattern
- Unify section colors across proxy host and L4 dialogs (cyan=LB, emerald=DNS, violet=upstream DNS, rose=geo, amber=mTLS)
- Improve light mode contrast: muted-foreground oklch 0.552→0.502, remove opacity modifiers on secondary text
- Improve dark mode: boost muted-foreground to 0.85, increase border opacity 10%→16%, input 15%→20%
- Add bg-card to DataTable wrapper and bg-muted/40 to table headers for surface hierarchy
- Add semantic badge variants (success, warning, info, muted) and StatusChip dark mode fix
- Add server-side sortable columns to Proxy Hosts and L4 Proxy Hosts (name, upstream, status, protocol, listen)
- Add sortKey to DataTable Column type with clickable sort headers (ArrowUp/Down indicators, URL param driven)
- Fix E2E test selectors for shadcn UI (label associations, combobox roles, dropdown menus, mobile drawer)
- Add htmlFor/id to proxy host form fields and aria-labels to select triggers for accessibility
- Add sorting E2E tests for both proxy host pages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:17:56 +01:00
fuomag9
0f7676be14 fix: force linux/amd64 for tcp-echo in test compose (arm64 host compat)
cjimti/go-echo has no arm64 image; explicit platform lets Docker pull
and run it via Rosetta/qemu on Apple Silicon without unhealthy warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 00:45:57 +01:00
fuomag9
b5ef7aab7b fix: resolve all lint errors from L4 feature
- Remove unused Box/Collapse from L4PortsApplyBanner
- Remove unused Stack from RedirectsFields
- Remove unused updateL4ProxyHost import from validation test
- Add eslint-disable-next-line for require() in vi.hoisted() blocks
  (necessary pattern since vi.hoisted runs before ESM imports)
- Add file-level eslint-disable no-explicit-any for test files that
  intentionally pass invalid types to test validation logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 00:37:52 +01:00
fuomag9
00c9bff8b4 feat: instant banner refresh on L4 mutations + master-slave L4 sync
Banner (L4PortsApplyBanner):
- Accept refreshSignal prop; re-fetch /api/l4-ports when it changes
- Signal fires immediately after create/edit/delete/toggle in L4ProxyHostsClient
  without waiting for a page reload

Master-slave replication (instance-sync):
- Add l4ProxyHosts to SyncPayload.data (optional for backward compat
  with older master instances that don't include it)
- buildSyncPayload: query and include l4ProxyHosts, sanitize ownerUserId
- applySyncPayload: clear and re-insert l4ProxyHosts in transaction;
  call applyL4Ports() if port diff requires it so the slave's sidecar
  recreates caddy with the correct ports
- Sync route: add isL4ProxyHost validator; backfill missing field from
  old masters; validate array when present

Tests (25 new tests):
- instance-sync.test.ts: buildSyncPayload includes L4 data, sanitizes ownerUserId;
  applySyncPayload replaces L4 hosts, handles missing field, writes trigger
  when ports differ, skips trigger when ports already match
- l4-ports-apply-banner.test.ts: banner refreshSignal contract + client
  increments counter on all mutation paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 00:22:44 +01:00
fuomag9
3a4a4d51cf feat: add L4 (TCP/UDP) proxy host support via caddy-l4
- 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>
2026-03-22 00:11:16 +01:00
fuomag9
fc680d4171 fix: use bun:sqlite in production, better-sqlite3 as test-only devDep
Production (Docker): src/lib/db.ts now uses bun:sqlite + drizzle-orm/bun-sqlite.
No native addon compilation needed — bun:sqlite is a Bun built-in. The Dockerfile
drops all native build tools (python3, make, g++) and uses --ignore-scripts.

Tests (Vitest/Node.js): bun:sqlite is unavailable under Node.js, so:
- tests/helpers/db.ts keeps better-sqlite3 + drizzle-orm/better-sqlite3 for
  integration tests that need a real in-memory SQLite
- vitest.config.ts aliases bun:sqlite → a thin better-sqlite3 shim and
  drizzle-orm/bun-sqlite → drizzle-orm/better-sqlite3 for unit tests that
  transitively import src/lib/db.ts without executing any queries
- better-sqlite3 stays as a devDependency (test-only, not built in Docker)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:53:33 +01:00
fuomag9
4b5323a7bf feat: add structured redirects and path prefix rewrite for proxy hosts
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>
2026-03-19 17:53:33 +01:00
fuomag9
ea26c57859 add mTLS tests 2026-03-14 16:42:14 +01:00
fuomag9
73c90894b1 Handle wildcard proxy hosts and stabilize test coverage
- 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
2026-03-14 01:03:34 +01:00
fuomag9
27b7fadacc test: restrict mobile-iphone project to mobile tests only 2026-03-12 09:07:15 +01:00
fuomag9
6e8db4ec39 test: add mobile layout E2E tests for iPhone 15
- 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>
2026-03-12 09:04:58 +01:00
fuomag9
bfcc24eac0 test: add iPhone 15 Playwright project 2026-03-12 01:33:17 +01:00
fuomag9
26fcf8ca90 fix WAF silently dropping WebSocket upgrade requests
When allowWebsocket=true and WAF is enabled, the WAF handler sits first
in the handler chain and processes the initial HTTP upgrade request
(GET + Upgrade: websocket). If any rule matches, Coraza can block the
handshake before SecAuditEngine captures it — producing no log entry
and an unexplained connection failure from the client's perspective.

Fix: when allowWebsocket=true, prepend a phase:1 SecLang rule that
matches Upgrade: websocket (case-insensitive) and turns the rule engine
off for that transaction via ctl:ruleEngine=off. After the 101
Switching Protocols response the connection becomes a raw WebSocket
tunnel that the WAF cannot inspect anyway, so this bypass has no impact
on normal HTTP traffic through the same host.

The rule is inserted before OWASP CRS includes so it always fires first
regardless of which ruleset is loaded.

Add 9 unit tests in caddy-waf.test.ts covering: bypass present/absent,
phase:1 placement, case-insensitive regex, nolog/noauditlog flags,
ordering before CRS, and compatibility with custom directives.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 23:14:12 +01:00
fuomag9
d6df70ab5f fix WAF merge mode ignoring host.enabled=false, extract resolveEffectiveWaf
Bug: when a proxy host had per-host WAF explicitly disabled (enabled:false)
with waf_mode:"merge" (or no waf_mode set), resolveEffectiveWaf entered the
merge branch and returned enabled:true unconditionally, applying the global
WAF to a host the user had opted out of.

Fix: add `if (host.enabled === false) return null` at the top of the merge
branch. Explicit opt-out now takes precedence over the global setting
regardless of mode. The override mode already handled this correctly.

Also extract resolveEffectiveWaf from caddy.ts into caddy-waf.ts so it
can be unit tested. Add 12 new tests covering no-config fallback,
merge opt-out regression, merge settings combination, and override mode.

What runs without OWASP CRS: only SecRuleEngine + audit directives +
any custom_directives. The @coraza.conf-recommended and CRS includes
are gated behind load_owasp_crs (fixed in previous commit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 21:39:00 +01:00
fuomag9
b5bb668bd9 test: suppress console output noise in vitest
Add onConsoleLog: () => false to vitest config to silence expected
warn/error calls from production code when tests deliberately feed bad
input (e.g. parseJson with malformed JSON). Tests can still use
vi.spyOn(console, ...) to assert on console calls explicitly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 02:39:29 +01:00
fuomag9
f0825d6497 fix WAF crash when enabled without OWASP CRS, add regression tests
The WAF handler always prepended 'Include @coraza.conf-recommended' to the
SecLang directives regardless of load_owasp_crs. The @-prefixed paths only
resolve from the embedded coraza-coreruleset filesystem, which the Caddy
WAF plugin mounts only when load_owasp_crs=true. Without it Caddy fails:
  "failed to readfile: open @coraza.conf-recommended: no such file or directory"

Fix: gate all @-prefixed Include directives behind load_owasp_crs.

Also extract buildWafHandler from caddy.ts into caddy-waf.ts so it can be
unit tested in isolation, and add tests/unit/caddy-waf.test.ts (19 tests)
covering the regression, CRS include ordering, excluded rule IDs, and
handler structure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 01:19:10 +01:00
fuomag9
fd847e7eb5 fix mTLS cross-CA isolation bug, add instance-sync and mTLS tests
Extract pemToBase64Der and buildClientAuthentication from caddy.ts into
a new caddy-mtls.ts module, adding groupMtlsDomainsByCaSet to group mTLS
domains by their CA fingerprint before building TLS connection policies.

Previously all mTLS domains sharing a cert type (auto-managed, imported,
or managed) were grouped into a single policy, causing CA union: a client
cert from CA_B could authenticate against a host that only trusted CA_A.
The fix creates one policy per unique CA set, ensuring strict per-host
CA isolation across all three TLS policy code paths.

Also adds:
- tests/unit/caddy-mtls.test.ts (26 tests) covering pemToBase64Der,
  buildClientAuthentication, groupMtlsDomainsByCaSet, and cross-CA
  isolation regression tests
- tests/unit/instance-sync-env.test.ts (33 tests) for the five pure
  env-reading functions in instance-sync.ts
- tests/integration/instance-sync.test.ts (16 tests) for
  buildSyncPayload and applySyncPayload using an in-memory SQLite db
- Fix tests/helpers/db.ts to use a relative import for db/schema so it
  works inside vi.mock factory dynamic imports

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 18:32:52 +01:00
fuomag9
e5ba3e1ed9 refractor code to allow more tests 2026-03-07 16:53:36 +01:00