Commit Graph

115 Commits

Author SHA1 Message Date
jubnl
ce8d498f2d fix: add independent rate limiter for MFA verification endpoints
TOTP brute-force is a realistic attack once a password is compromised:
with no independent throttle, an attacker shared the login budget (10
attempts) across /login, /register, and /mfa/verify-login, and
/mfa/enable had no rate limiting at all.

- Add a dedicated `mfaAttempts` store so MFA limits are tracked
  separately from login attempts
- Introduce `mfaLimiter` (5 attempts / 15 min) applied to both
  /mfa/verify-login and /mfa/enable
- Refactor `rateLimiter()` to accept an optional store parameter,
  keeping all existing call-sites unchanged
- Include mfaAttempts in the periodic cleanup interval
2026-04-01 07:58:29 +02:00
jubnl
b109c1340a fix: prevent ICS header injection in calendar export
Three vulnerabilities patched in the /export.ics route:

- esc() now handles bare \r and CRLF sequences — the previous regex only
  matched \n, leaving \r intact and allowing CRLF injection via \r\n
- reservation DESCRIPTION field was built from unescaped user data
  (type, confirmation_number, notes, airline, flight/train numbers,
  airports) and written raw into ICS output; now passed through esc()
- Content-Disposition filename used ICS escaping instead of HTTP header
  sanitization; replaced with a character allowlist to prevent " and
  \r\n injection into the response header
2026-04-01 07:58:18 +02:00
jubnl
6f5550dc50 fix: decouple at-rest encryption from JWT_SECRET, add JWT rotation
Introduces a dedicated ENCRYPTION_KEY for encrypting stored secrets
(API keys, MFA TOTP, SMTP password, OIDC client secret) so that
rotating the JWT signing secret no longer invalidates encrypted data,
and a compromised JWT_SECRET no longer exposes stored credentials.

- server/src/config.ts: add ENCRYPTION_KEY (auto-generated to
  data/.encryption_key if not set, same pattern as JWT_SECRET);
  switch JWT_SECRET to `export let` so updateJwtSecret() keeps the
  CJS module binding live for all importers without restart
- apiKeyCrypto.ts, mfaCrypto.ts: derive encryption keys from
  ENCRYPTION_KEY instead of JWT_SECRET
- admin POST /rotate-jwt-secret: generates a new 32-byte hex secret,
  persists it to data/.jwt_secret, updates the live in-process binding
  via updateJwtSecret(), and writes an audit log entry
- Admin panel (Settings → Danger Zone): "Rotate JWT Secret" button
  with a confirmation modal warning that all sessions will be
  invalidated; on success the acting admin is logged out immediately
- docker-compose.yml, .env.example, README, Helm chart (values.yaml,
  secret.yaml, deployment.yaml, NOTES.txt, README): document
  ENCRYPTION_KEY and its upgrade migration path
2026-04-01 07:57:55 +02:00
jubnl
dfdd473eca fix: validate uploaded backup DB before restore
Before swapping in a restored database, run PRAGMA integrity_check and
verify the five core TREK tables (users, trips, trip_members, places,
days) are present. This blocks restoring corrupt, empty, or unrelated
SQLite files that would otherwise crash the app immediately after swap,
and prevents a malicious admin from hot-swapping a crafted database with
forged users or permissions.
2026-04-01 07:57:42 +02:00
jubnl
b515880adb fix: encrypt Immich API key at rest using AES-256-GCM
Per-user Immich API keys were stored as plaintext in the users table,
giving any attacker with DB read access full control over each user's
Immich photo server. Keys are now encrypted on write with
maybe_encrypt_api_key() and decrypted at the point of use via a shared
getImmichCredentials() helper. A new migration (index 66) back-fills
encryption for any existing plaintext values on startup.
2026-04-01 07:57:29 +02:00
jubnl
78695b4e03 fix: replace JWT tokens in URL query params with short-lived ephemeral tokens
Addresses CWE-598: long-lived JWTs were exposed in WebSocket URLs, file
download links, and Immich asset proxy URLs, leaking into server logs,
browser history, and Referer headers.

- Add ephemeralTokens service: in-memory single-use tokens with per-purpose
  TTLs (ws=30s, download/immich=60s), max 10k entries, periodic cleanup
- Add POST /api/auth/ws-token and POST /api/auth/resource-token endpoints
- WebSocket auth now consumes an ephemeral token instead of verifying the JWT
  directly from the URL; client fetches a fresh token before each connect
- File download ?token= query param now accepts ephemeral tokens; Bearer
  header path continues to accept JWTs for programmatic access
- Immich asset proxy replaces authFromQuery JWT injection with ephemeral token
  consumption
- Client: new getAuthUrl() utility, AuthedImg/ImmichImg components, and async
  onClick handlers replace the synchronous authUrl() pattern throughout
  FileManager, PlaceInspector, and MemoriesPanel
- Add OIDC_DISCOVERY_URL env var and oidc_discovery_url DB setting to allow
  overriding the auto-constructed discovery endpoint (required for Authentik
  and similar providers); exposed in the admin UI and .env.example
2026-04-01 07:57:14 +02:00
jubnl
0ee53e7b38 fix: prevent OIDC redirect URI construction from untrusted X-Forwarded-Host
The OIDC login route silently fell back to building the redirect URI from
X-Forwarded-Host/X-Forwarded-Proto when APP_URL was not configured. An
attacker could set X-Forwarded-Host: attacker.example.com to redirect the
authorization code to their own server after the user authenticates.

Remove the header-derived fallback entirely. If APP_URL is not set (via env
or the app_url DB setting), the OIDC login endpoint now returns a 500 error
rather than trusting attacker-controlled request headers. Document APP_URL
in .env.example as required for OIDC use.
2026-04-01 07:56:55 +02:00
jubnl
1b28bd96d4 fix: encrypt SMTP password at rest using AES-256-GCM
The smtp_pass setting was stored as plaintext in app_settings, exposing
SMTP credentials to anyone with database read access. Apply the same
encrypt_api_key/decrypt_api_key pattern already used for OIDC client
secrets and API keys. A new migration transparently re-encrypts any
existing plaintext value on startup; decrypt_api_key handles legacy
plaintext gracefully so in-flight reads remain safe during upgrade.
2026-04-01 07:56:43 +02:00
jubnl
bba50f038b fix: encrypt OIDC client secret at rest using AES-256-GCM
The oidc_client_secret was written to app_settings as plaintext,
unlike Maps and OpenWeather API keys which are protected with
apiKeyCrypto. An attacker with read access to the SQLite file
(e.g. via a backup download) could obtain the secret and
impersonate the application with the identity provider.

- Encrypt on write in PUT /api/admin/oidc via maybe_encrypt_api_key
- Decrypt on read in GET /api/admin/oidc and in getOidcConfig()
  (oidc.ts) before passing the secret to the OIDC client library
- Add a startup migration that encrypts any existing plaintext value
  already present in the database
2026-04-01 07:56:29 +02:00
jubnl
c9341eda3f fix: remove RCE vector from admin update endpoint.
The POST /api/admin/update endpoint ran git pull, npm install, and npm run build via execSync, potentially giving any compromised admin account full code execution on the host in case repository is compromised. TREK ships as a Docker image so runtime self-updating is unnecessary.
- Remove the /update route and child_process import from admin.ts
- Remove the installUpdate API client method
- Replace the live-update modal with an info-only panel showing docker pull instructions and a link to the GitHub release
- Drop the updating/updateResult state and handleInstallUpdate handler
2026-04-01 07:55:34 +02:00
Gérnyi Márk
1fbc19ad4f fix: add missing permission checks to file routes and map context menu
- Add checkPermission to 6 unprotected file endpoints (star, restore,
  permanent delete, empty trash, link, unlink)
- Gate map right-click place creation with place_edit permission
- Use file_upload permission for collab note file uploads
2026-03-31 23:45:11 +02:00
Gérnyi Márk
23edfe3dfc fix: harden permissions system after code review
- Gate permissions in /app-config behind optionalAuth so unauthenticated
  requests don't receive admin configuration
- Fix trip_delete isMember parameter (was hardcoded false)
- Return skipped keys from savePermissions for admin visibility
- Add disabled prop to CustomSelect, use in BudgetPanel currency picker
- Fix CollabChat reaction handler returning false instead of void
- Pass canUploadFiles as prop to NoteFormModal instead of internal store read
- Make edit-only NoteFormModal props optional (onDeleteFile, note, tripId)
- Add missing trailing newlines to .gitignore and it.ts
2026-03-31 23:36:17 +02:00
Gérnyi Márk
c1bce755ca refactor: dedupe database requests 2026-03-31 23:36:15 +02:00
Gérnyi Márk
7d3b37a2a3 feat: add configurable permissions system with admin panel
Adds a full permissions management feature allowing admins to control
who can perform actions across the app (trip CRUD, files, places,
budget, packing, reservations, collab, members, share links).

- New server/src/services/permissions.ts: 16 configurable actions,
  in-memory cache, checkPermission() helper, backwards-compatible
  defaults matching upstream behaviour
- GET/PUT /admin/permissions endpoints; permissions loaded into
  app-config response so clients have them on startup
- checkPermission() applied to all mutating route handlers across
  10 server route files; getTripOwnerId() helper eliminates repeated
  inline DB queries; trips.ts and files.ts now reuse canAccessTrip()
  result to avoid redundant DB round-trips
- New client/src/store/permissionsStore.ts: Zustand store +
  useCanDo() hook; TripOwnerContext type accepts both Trip and
  DashboardTrip shapes without casting at call sites
- New client/src/components/Admin/PermissionsPanel.tsx: categorised
  UI with per-action dropdowns, customised badge, save/reset
- AdminPage, DashboardPage, FileManager, PlacesSidebar,
  TripMembersModal gated via useCanDo(); no prop drilling
- 46 perm.* translation keys added to all 12 language files
2026-03-31 23:36:15 +02:00
Maurice
3444e3f446 Merge branch 'perf-test' of https://github.com/jubnl/TREK into dev
# Conflicts:
#	client/src/components/Map/MapView.tsx
2026-03-31 23:10:02 +02:00
Maurice
9e3ac1e490 fix: increase max trip duration from 90 to 365 days 2026-03-31 22:58:27 +02:00
Maurice
36cd2feca5 fix: use Nominatim reverse geocoding for accurate country detection in atlas
Bounding boxes overlap for neighboring countries (e.g. Munich matched
Austria instead of Germany). Now uses Nominatim reverse geocoding with
in-memory cache as primary fallback, bounding boxes only as last resort.
2026-03-31 21:58:20 +02:00
Maurice
fbe3b5b17e Merge pull request #225 from andreibrebene/improvements/various-improvements
Improvements/various improvements
2026-03-31 21:40:26 +02:00
Maurice
10107ecf31 fix: require auth for file downloads, localize atlas search, use flag images
- Block direct access to /uploads/files (401), serve via authenticated
  /api/trips/:tripId/files/:id/download with JWT verification
- Client passes auth token as query parameter for direct links
- Atlas country search now uses Intl.DisplayNames (user language) instead
  of English GeoJSON names
- Atlas search results use flagcdn.com flag images instead of emoji
2026-03-31 21:38:16 +02:00
Andrei Brebene
7522f396e7 feat: configurable trip reminders, admin full access, and enhanced audit logging
- Add configurable trip reminder days (1, 3, 9 or custom up to 30) settable by trip owner
- Grant administrators full access to edit, archive, delete, view and list all trips
- Show trip owner email in audit logs and docker logs when admin edits/deletes another user's trip
- Show target user email in audit logs when admin edits or deletes a user account
- Use email instead of username in all notifications (Discord/Slack/email) to avoid ambiguity
- Grey out notification event toggles when no SMTP/webhook is configured
- Grey out trip reminder selector when notifications are disabled
- Skip local admin account creation when OIDC_ONLY=true with OIDC configured
- Conditional scheduler logging: show disabled reason or active reminder count
- Log per-owner reminder creation/update in docker logs
- Demote 401/403 HTTP errors to DEBUG log level to reduce noise
- Hide edit/archive/delete buttons for non-owner invited users on trip cards
- Fix literal "0" rendering on trip cards from SQLite numeric is_owner field
- Add missing translation keys across all 14 language files

Made-with: Cursor
2026-03-31 22:23:38 +03:00
Andrei Brebene
9b2f083e4b feat: notifications, audit logging, and admin improvements
- Add centralized notification service with webhook (Discord/Slack) and
  email (SMTP) support, triggered for trip invites, booking changes,
  collab messages, and trip reminders
- Webhook sends one message per event (group channel); email sends
  individually per trip member, excluding the actor
- Discord invite notifications now include the invited user's name
- Add LOG_LEVEL env var (info/debug) controlling console and file output
- INFO logs show user email, action, and IP for audit events; errors
  for HTTP requests
- DEBUG logs show every request with full body/query (passwords redacted),
  audit details, notification params, and webhook payloads
- Add persistent trek.log file logging with 10MB rotation (5 files)
  in /app/data/logs/
- Color-coded log levels in Docker console output
- Timestamps without timezone name (user sets TZ via Docker)
- Add Test Webhook and Save buttons to admin notification settings
- Move notification event toggles to admin panel
- Add daily trip reminder scheduler (9 AM, timezone-aware)
- Wire up booking create/update/delete and collab message notifications
- Add i18n keys for notification UI across all 13 languages

Made-with: Cursor
2026-03-31 22:23:23 +03:00
jubnl
9a949d7391 Performance on trip planner (Maybe ?) 2026-03-31 21:13:29 +02:00
Maurice
f7160e6dec Merge pull request #179 from shanelord01/audit/remediation-clean
Automated Security & Quality Audit via Claude Code
2026-03-31 20:53:48 +02:00
Maurice
6866644d0c Merge pull request #189 from M-Enderle/feat/gpx-full-route-import
feat(add-gpx-tracks): adds better gpx track views
2026-03-31 20:17:22 +02:00
Maurice
9de0c5b051 Merge remote-tracking branch 'origin/dev' into asteriskyg/main
# Conflicts:
#	server/src/routes/files.ts
2026-03-31 20:08:42 +02:00
Moritz Enderle
3aaa6e916b feat: adds better gpx track views 2026-03-31 10:29:49 +02:00
David Moll
990e804bd3 fix(server): encrypt api keys 2026-03-31 09:00:35 +02:00
Claude
c89ff8b551 fix: critical Immich SSRF and API key exposure vulnerabilities
- Add URL validation on Immich URL save to prevent SSRF attacks
  (blocks private IPs, metadata endpoints, non-HTTP protocols)
- Remove userId query parameter from asset proxy endpoints to prevent
  any authenticated user from accessing another user's Immich API key
  and photo library
- Add asset ID validation (alphanumeric only) to prevent path traversal
  in proxied Immich API URLs
- Update AUDIT_FINDINGS.md with Immich and admin route findings

https://claude.ai/code/session_01SoQKcF5Rz9Y8Nzo4PzkxY8
2026-03-31 00:34:06 +00:00
Claude
63232e56a3 fix: prevent OIDC token data leaking to logs, update audit findings
- Redact OIDC token exchange error logs to only include HTTP status
- Add additional findings from exhaustive server security scan to
  AUDIT_FINDINGS.md

https://claude.ai/code/session_01SoQKcF5Rz9Y8Nzo4PzkxY8
2026-03-31 00:34:04 +00:00
Claude
804c2586a9 fix: tighten CSP, fix API key exposure, improve error handling
- Remove 'unsafe-inline' from script-src CSP directive
- Restrict connectSrc and imgSrc to known external domains
- Move Google API key from URL query parameter to X-Goog-Api-Key header
- Sanitize error logging in production (no stack traces)
- Log file link errors instead of silently swallowing them

https://claude.ai/code/session_01SoQKcF5Rz9Y8Nzo4PzkxY8
2026-03-31 00:33:56 +00:00
Claude
fedd559fd6 fix: pin JWT algorithm to HS256 and harden token security
- Add { algorithms: ['HS256'] } to all jwt.verify() calls to prevent
  algorithm confusion attacks (including the 'none' algorithm)
- Add { algorithm: 'HS256' } to all jwt.sign() calls for consistency
- Reduce OIDC token payload to only { id } (was leaking username, email, role)
- Validate OIDC redirect URI against APP_URL env var when configured
- Add startup warning when JWT_SECRET is auto-generated

https://claude.ai/code/session_01SoQKcF5Rz9Y8Nzo4PzkxY8
2026-03-31 00:33:53 +00:00
Maurice
069fd99341 Merge branch 'pr-169'
# Conflicts:
#	client/src/i18n/translations/ar.ts
#	client/src/i18n/translations/br.ts
#	client/src/i18n/translations/cs.ts
#	client/src/i18n/translations/de.ts
#	client/src/i18n/translations/en.ts
#	client/src/i18n/translations/es.ts
#	client/src/i18n/translations/fr.ts
#	client/src/i18n/translations/hu.ts
#	client/src/i18n/translations/it.ts
#	client/src/i18n/translations/nl.ts
#	client/src/i18n/translations/ru.ts
#	client/src/i18n/translations/zh.ts
#	client/src/pages/SettingsPage.tsx
2026-03-30 23:46:32 +02:00
Fernando Bona
13580ea5fb Merge branch 'main' into feat/#155 2026-03-30 18:36:18 -03:00
Fernando Bona
aa5dd1abc6 Merge branch 'main' into fix/mfa-backup-codes 2026-03-30 18:27:46 -03:00
fgbona
de444bf770 fix(mfa-backup-codes): persist backup codes panel after enable and refresh
Keep MFA backup codes visible after enabling MFA by avoiding protected-route unmount during user reload (`loadUser({ silent: true })`) and restoring pending backup codes from sessionStorage until the user explicitly dismisses them.
2026-03-30 18:22:45 -03:00
Maurice
faebc62917 Merge branch 'pr-125'
# Conflicts:
#	client/src/api/client.ts
#	client/src/i18n/translations/ar.ts
#	client/src/i18n/translations/es.ts
#	client/src/i18n/translations/fr.ts
#	client/src/i18n/translations/nl.ts
#	client/src/i18n/translations/ru.ts
#	client/src/i18n/translations/zh.ts
#	client/src/pages/AdminPage.tsx
#	client/src/pages/SettingsPage.tsx
#	server/package.json
#	server/src/db/migrations.ts
#	server/src/index.ts
#	server/src/routes/admin.ts
2026-03-30 23:10:34 +02:00
Fernando Bona
41e572445c Merge branch 'main' into feat/#155 2026-03-30 17:52:07 -03:00
fgbona
66f5ea50c5 feat(require-mfa): #155 enforce MFA via admin policy toggle across app access
Add an admin-controlled `require_mfa` policy in App Settings and expose it via `/auth/app-config` so the client can enforce it globally. Users without MFA are redirected to Settings after login and blocked from protected API/WebSocket access until setup is completed, while preserving MFA setup endpoints and admin recovery paths. Also prevent enabling the policy unless the acting admin already has MFA enabled, and block MFA disable while the policy is active. Includes UI toggle in Admin > Settings, required-policy notice in Settings, client-side 403 `MFA_REQUIRED` handling, and i18n updates for all supported locales.
2026-03-30 17:42:40 -03:00
Maurice
b1138eb9db fix: shared page language redirect + skip TLS for self-signed certs — closes #163 #164
- Language change on public shared page no longer triggers API call / login redirect
- New "Skip TLS certificate check" toggle in Admin > SMTP settings
- Also configurable via SMTP_SKIP_TLS_VERIFY=true env var
2026-03-30 22:26:09 +02:00
Maurice
14ef2d4a4a Merge branch 'pr-117' into dev 2026-03-30 20:07:12 +02:00
Maurice
de859318fa feat: admin audit log — merged PR #118
Audit logging for admin actions, backups, auth events.
New AuditLogPanel in Admin tab with pagination.
Dockerfile security: run as non-root user.
i18n keys for all 9 languages.

Thanks @fgbona for the implementation!
2026-03-30 20:05:32 +02:00
Maurice
a314ba2b80 feat: public read-only share links with permissions — closes #79
Share links:
- Generate a public link in the trip share modal
- Choose what to share: Map & Plan, Bookings, Packing, Budget, Chat
- Permissions enforced server-side
- Delete link to revoke access instantly

Shared trip page (/shared/:token):
- Read-only view with TREK logo, cover image, trip details
- Tabbed navigation with Lucide icons (responsive on mobile)
- Interactive map with auto-fit bounds per day
- Day plan, Bookings, Packing, Budget, Chat views
- Language picker, TREK branding footer

Technical:
- share_tokens DB table with per-field permissions
- Public GET /shared/:token endpoint (no auth)
- Two-column share modal (max-w-5xl)
2026-03-30 18:02:53 +02:00
Maurice
0a0205fcf9 fix: ICS export — add DTSTAMP, fix time-only DTEND formatting 2026-03-30 17:14:06 +02:00
Maurice
d189d6d776 feat: email notifications, webhook support, ICS export — closes #110
Email Notifications:
- SMTP configuration in Admin > Settings (host, port, user, pass, from)
- App URL setting for email CTA links
- Webhook URL support (Discord, Slack, custom)
- Test email button with SMTP validation
- Beautiful HTML email template with TREK logo, slogan, red heart footer
- All notification texts translated in 8 languages (en/de/fr/es/nl/ru/zh/ar)
- Emails sent in each user's language preference

Notification Events:
- Trip invitation (member added)
- Booking created (new reservation)
- Vacay fusion invite
- Photos shared (Immich)
- Collab chat message
- Packing list category assignment

User Notification Preferences:
- Per-user toggle for each event type in Settings
- Addon-aware: Vacay/Collab/Photos toggles hidden when addon disabled
- Webhook opt-in per user

ICS Calendar Export:
- Download button next to PDF in day plan header
- Exports trip dates + all reservations with details
- Compatible with Google Calendar, Apple Calendar, Outlook

Technical:
- Nodemailer for SMTP
- notification_preferences DB table with per-event columns
- GET/PUT /auth/app-settings for admin config persistence
- POST /notifications/test-smtp for validation
- Dynamic imports for non-blocking notification sends
2026-03-30 17:07:33 +02:00
Maurice
262905e357 feat: import places from Google Maps URLs — closes #141
Paste a Google Maps URL into the place search bar to automatically
import name, coordinates, and address. No API key required.

Supported URL formats:
- Short URLs: maps.app.goo.gl/..., goo.gl/maps/...
- Full URLs: google.com/maps/place/.../@lat,lng
- Data params: !3dlat!4dlng embedded coordinates

Server resolves short URL redirects and extracts coordinates.
Reverse geocoding via Nominatim provides name and address.
2026-03-30 15:18:22 +02:00
Maurice
4a4643f33f feat: OIDC claim-based admin role assignment — closes #93
New environment variables:
- OIDC_ADMIN_CLAIM (default: "groups") — which claim to check
- OIDC_ADMIN_VALUE (e.g. "app-trek-admins") — value that grants admin

Admin role is resolved on every OIDC login:
- New users get admin if their claim matches
- Existing users have their role updated dynamically
- Removing a user from the group revokes admin on next login
- First user is always admin regardless of claims
- No config = previous behavior (first user admin, rest user)

Supports array claims (groups: ["a", "b"]) and string claims.
2026-03-30 15:12:27 +02:00
Maurice
a6a7edf0b2 feat: bucket list POIs with auto-search + optional dates — closes #105
- Bucket list now supports POIs (not just countries): add any place
  with auto-search via Google Places / Nominatim
- Optional target date (month/year) via CustomSelect dropdowns
- New target_date field on bucket_list table (DB migration)
- Server PUT route supports updating all fields
- Country bucket modal: date dropdowns default to empty
- CustomSelect: auto-opens upward when near bottom of viewport
- Search results open upward in the bucket add form
- i18n keys for DE and EN
2026-03-30 14:57:31 +02:00
Maurice
949d0967d2 feat: timezone support + granular backup schedule — closes #131
Based on PR #135 by @andreibrebene with adjustments:
- TZ environment variable for Docker timezone support
- Granular auto-backup schedule (hour, day of week, day of month)
- UTC timestamp fix for admin panel
- Server timezone exposed in app-config API
- Replaced native selects with CustomSelect for consistent UI
- Backup schedule UI with 12h/24h time format support

Thanks @andreibrebene for the implementation!
2026-03-30 14:02:27 +02:00
Maurice
7201380504 fix: paginate Immich photo search — no longer limited to 200 — closes #137
The Immich metadata search was hardcoded to size: 200. Now paginates
through all results (1000 per page, up to 20k photos max).
2026-03-30 13:36:04 +02:00
ASTERISK Kwon
ba87a7f876 fix: correct linksMap type annotation 2026-03-30 20:32:49 +09:00