Commit Graph

2182 Commits

Author SHA1 Message Date
fuomag9
9940bea058 Hide traffic and recent activity from non-admin users on overview page
The Traffic (24h) card and Recent Activity section were visible to
user/viewer roles even though they received empty data. Now both
sections are conditionally rendered only for admin users.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:28:05 +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
6745a043a8 Document user roles (viewer/user/admin) in README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:03:53 +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
b81c211da9 Fix forward auth security vulnerabilities found during pentest
- Fix broken rate limiting: add registerFailedAttempt/resetAttempts calls
- Remove raw session token from exchange table; generate fresh token at redemption
- Fix TOCTOU race: atomic UPDATE...WHERE used=false for exchange redemption
- Delete exchange records immediately after redemption
- Change bcrypt.compareSync to async bcrypt.compare to prevent event loop blocking
- Fix IP extraction: prefer x-real-ip, fall back to last x-forwarded-for entry
- Restrict redirect URI scheme to http/https only
- Add Origin header CSRF check on login and session-login endpoints
- Remove admin auto-access bypass from checkHostAccess (deny-by-default for all)
- Revoke forward auth sessions when user status changes away from active
- Validate portal domain against registered forward-auth hosts to prevent phishing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:01:10 +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
708b908679 Default new users to 'user' role instead of 'admin'
The bootstrap admin from ADMIN_USERNAME/ADMIN_PASSWORD is unaffected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:33:30 +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
a2b8d69aa6 Update README.md 2026-04-03 15:53:37 +02:00
fuomag9
9f4287b7fd Update funding information for GitHub and Ko-fi
Signed-off-by: fuomag9 <fuo@fuo.fi>
2026-04-03 14:37:13 +02:00
fuomag9
830e92127e Replace npm with bun in Dependabot config
Signed-off-by: fuomag9 <fuo@fuo.fi>
2026-04-03 13:50:48 +02:00
fuomag9
4b84f25c38 fix ts6 baseurl 2026-04-03 12:42:58 +02:00
fuomag9
8598bdd132 update packages 2026-04-03 12:40:24 +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
49b869f0ca fix: include WAF blocks in dashboard blocked counter
The Traffic (24h) card's "Blocked" percentage only counted
geo-blocks from trafficEvents. Now also queries wafEvents to
include WAF-blocked requests in the total.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:15:13 +02:00
fuomag9
71ea811ad1 update packages 2026-04-03 00:59:53 +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
ebc297190d fix: route geo-blocker log entries to access.log
The caddy-blocker plugin already emits "request blocked" log entries
for geo/IP blocks, but they were going to Caddy's default log (stdout)
instead of /logs/access.log because http.handlers.blocker was not in
the access log include list. The existing log parser and dashboard were
already wired up to count these — they just never received the data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 00:05:14 +02:00
fuomag9
6ce986f11f feat: add LocationRule to OpenAPI spec and fix response_headers example
- Add LocationRule schema with path and upstreams fields
- Add location_rules to ProxyHost and ProxyHostInput schemas
- Fix response_headers using concrete example instead of generic additionalProperties

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 15:01:50 +01: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
447dbcedde feat: add LocationRulesFields UI component and form wiring
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:51:08 +01:00
fuomag9
23e186a22e feat: emit location rule routes in Caddy config generation
Co-Authored-By: Claude Opus 4.6 (1M context) <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
f115f0cb13 update packages 2026-03-26 22:23:50 +01:00
fuomag9
b480c2cf5d chore: remove finding-ID prefixes from code comments
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:51:39 +01:00
fuomag9
4f2f090e2c fix: resolve lint errors from security changes
- Remove unused `/* global process */` in next.config.mjs
- Attach cause to rethrown error in secret.ts legacy key expiry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:41:19 +01:00
fuomag9
c537a59f36 fix: trustHost should be true when NEXTAUTH_URL is set
The H7 fix made trustHost default to false, which caused redirect loops
in environments where NEXTAUTH_URL is set (including Docker and tests).
When NEXTAUTH_URL is explicitly configured, the operator has declared
the canonical URL, making Host header validation unnecessary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:27:25 +01:00
fuomag9
debd0d98fc security: fix 17 vulnerabilities from comprehensive pentest
Fixes identified from full security audit covering auth, crypto,
injection, infrastructure, and configuration security.

Critical:
- C1: Fail-closed on unrecognized NODE_ENV (prevent DEV_SECRET in staging)
- C3: Validate API token expires_at (reject invalid dates that bypass expiry)

High:
- H1: Refresh JWT role from DB on each session (reflect demotions immediately)
- H2: Docker socket proxy for l4-port-manager (restrict API surface)
- H5: Block dangerous WAF custom directives (SecRuleEngine, SecAuditEngine)
- H7: Require explicit NEXTAUTH_TRUST_HOST instead of always trusting Host
- H8: Semantic validation of sync payload (block metadata SSRF, size limits)

Medium:
- M3: Rate limit password change current-password verification
- M5: Parameterized SQL in log/waf parsers (replace template literals)
- M6: Nonce-based CSP replacing unsafe-inline for script-src
- M9: Strip Caddy placeholders from rewrite path_prefix
- M10: Sanitize authentik outpostDomain (path traversal, placeholders)
- M14: Deny access on missing JWT role instead of defaulting to "user"

Low:
- L1: Require Origin header on mutating session-authenticated requests
- L4: Enforce password complexity on user password changes
- L5: Time-limited legacy SHA-256 key fallback (grace period until 2026-06-01)
- L6: Escape LIKE metacharacters in audit log search
- L7: Runtime-validate WAF excluded_rule_ids as positive integers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:14:44 +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
0acb430ebb more precision for readme ports 2026-03-25 21:53:51 +01:00
fuomag9
937e70d4f8 update packages 2026-03-25 19:56:05 +01:00
fuomag9
66f74b9928 ci: add l4-port-manager image to Docker build workflows
The l4-port-manager service had a Dockerfile but was missing from the
GitHub Actions build matrix, so it was never built or pushed to GHCR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 00:20:00 +01:00
fuomag9
39e9b9f0b9 Update README.md 2026-03-23 18:21:33 +01:00
fuomag9
c68dcf41ae feat: add feature badges for WAF, Geo, LB, mTLS, Authentik, Redirects, and Rewrite in proxy hosts table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:16:21 +01:00
fuomag9
25d49827e5 chore: upgrade Tailwind CSS to v4
- Replace @tailwind directives with @import "tailwindcss"
- Switch PostCSS plugin to @tailwindcss/postcss
- Add @theme inline block mapping CSS vars to v4 theme tokens
- Move accordion keyframes/animations to globals.css
- Remove tailwind.config.ts and tailwindcss-animate (no longer needed)
- Update components.json to clear config path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 09:53:09 +01:00
fuomag9
2db809e5d6 fix: change features grid from 4-column auto-fit to 3-column for clean 3x3 layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 09:42:56 +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
fce32318ce fix: remove unused imports and update dependencies
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 09:31:50 +01:00
fuomag9
62f5723f28 Update next-env.d.ts 2026-03-23 09:21:56 +01:00
fuomag9
df96441ac5 fix: replace screenshots with clean demo data
All screenshots now use completely fake data (*.example.com domains,
generic usernames, synthetic traffic). Analytics shows populated charts,
country heatmap, WAF events, protocol breakdown, and user agent stats.
No personal/real data in any screenshot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 09:20:20 +01:00