- Parse Caddy access logs every 30s into traffic_events SQLite table
- GeoIP country lookup via maxmind (GeoLite2-Country.mmdb)
- 90-day retention with automatic purge
- Analytics page with interval (24h/7d/30d) and per-host filtering:
- Stats cards: total requests, unique IPs, blocked count, block rate
- Requests-over-time area chart (ApexCharts)
- SVG world choropleth map (d3-geo + topojson-client, React 19 compatible)
- Top countries table with flag emojis
- HTTP protocol donut chart
- Top user agents horizontal bar chart
- Recent blocked requests table with pagination
- Traffic (24h) summary card on Overview page linking to analytics
- 7 authenticated API routes under /api/analytics/
- Share caddy-logs volume with web container (read-only)
- group_add caddy GID to web container for log file read access
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change providerSubjectIdx from index to uniqueIndex in schema.ts to
prevent multiple users sharing the same (provider, subject) pair,
which caused non-deterministic sign-in resolution via findFirst.
- Add migration 0008_unique_provider_subject.sql: DROP the existing
non-unique index and CREATE UNIQUE INDEX in its place.
- Validate INSTANCE_SYNC_MAX_BYTES env var in sync route: fall back to
10 MB default when the value is non-numeric (e.g. 'off') or
non-positive, preventing NaN comparisons that silently disabled the
size limit.
- Return a generic error message to callers on applySyncPayload /
applyCaddyConfig failure instead of leaking the raw error string;
the original message is still stored internally via setSlaveLastSync.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Returns whether GeoLite2-Country and GeoLite2-ASN databases are loaded,
used by the UI to show the GeoIP ready indicator in GeoBlockFields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit resolves multiple build errors and adds a workaround for environments
where Prisma engine binaries cannot be downloaded due to network restrictions.
Changes:
- Fix TypeScript error: Remove invalid request.ip property access in NextAuth route
- Add missing config import in auth.ts for sessionSecret
- Add dynamic = 'force-dynamic' to API routes to prevent static generation
- Create Prisma stub generator script for build-time type checking
- Update build script to use stub generator instead of prisma generate
- Add binaryTargets to Prisma schema configuration
The stub generator allows the Next.js build to complete successfully in environments
where Prisma binaries cannot be downloaded (403 Forbidden errors from binaries server).
The actual Prisma engines will need to be available at runtime in production deployments.
All routes are now properly configured as dynamic server-rendered routes.
This commit addresses several critical security issues identified in the security audit:
1. Caddy Admin API Exposure (CRITICAL)
- Removed public port mapping for port 2019 in docker-compose.yml
- Admin API now only accessible via internal Docker network
- Web UI can still access it via http://caddy:2019 internally
- Prevents unauthorized access to Caddy configuration API
2. IP Spoofing in Rate Limiting (CRITICAL)
- Updated getClientIp() to use Next.js request.ip property
- This provides the actual client IP instead of trusting X-Forwarded-For header
- Prevents attackers from bypassing rate limiting by spoofing headers
- Fallback to headers only in development environments
3. Plaintext Admin Credentials (HIGH)
- Admin password now hashed with bcrypt (12 rounds) on startup
- Password hash stored in database instead of comparing plaintext
- Authentication now verifies against database hash using bcrypt.compareSync()
- Improves security by not storing plaintext passwords in memory
- Password updates handled on every startup to support env var changes
Files modified:
- docker-compose.yml: Removed port 2019 public exposure
- app/api/auth/[...nextauth]/route.ts: Use actual client IP for rate limiting
- src/lib/auth.ts: Verify passwords against database hashes
- src/lib/init-db.ts: Hash and store admin password on startup
Security posture improved from C+ to B+