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
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
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
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
- 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
- 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
- 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
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.
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.
- 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
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!
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)
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
- 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
- New display setting "Blur Booking Codes" (off by default)
- When enabled, confirmation codes are blurred across all views
(ReservationsPanel, DayDetailPanel, Transport detail modal)
- Hover or click reveals the code (click toggles on mobile)
- Settings page uses masonry two-column layout on desktop, single
column on mobile (<900px)
- Fix hardcoded admin page title to use i18n key
Upload a GPX file to automatically create places from waypoints.
Supports <wpt>, <rtept>, and <trkpt> elements with CDATA handling.
Handles lat/lon in any attribute order. Track-only files import
start and end points with the track name.
- New server endpoint POST /places/import/gpx
- Import GPX button in PlacesSidebar below Add Place
- i18n keys for DE and EN
Add UI controls for configuring auto-backup schedule with hour, day of
week, and day of month pickers. The hour picker respects the user's
12h/24h time format preference from settings.
Add TZ environment variable support via docker-compose so the container
runs in the configured timezone. The timezone is passed to node-cron for
accurate scheduling and exposed via the API so the UI displays it.
Fix SQLite UTC timestamp handling by appending Z suffix to all timestamps
sent to the client, ensuring proper timezone conversion in the browser.
Made-with: Cursor
Switch from ne_110m to ne_50m Natural Earth dataset so small countries
like Seychelles, Maldives, Monaco etc. are visible in the Atlas view
and visited countries status.
Transport reservations (flights, trains, buses, cars, cruises) now appear
directly in the day plan timeline based on their reservation date/time.
- Transport cards display inline with places and notes, sorted by time
- Click to open detail modal with all booking data and linked files
- Persistent positioning via new day_plan_position field on reservations
- Free drag & drop: places can be moved between/around transport entries
- Arrow reorder works on the full visual list including transports
- Timed places show confirmation popup when reorder breaks chronology
- Custom delete confirmation popup for reservations
- DB migration adds day_plan_position column to reservations table
- New batch endpoint PUT /reservations/positions for position updates
- i18n keys added for DE and EN
Audit log
- Add audit_log table (migration + schema) with index on created_at.
- Add auditLog service (writeAudit, getClientIp) and record events for backups
(create, restore, upload-restore, delete, auto-settings), admin actions
(users, OIDC, invites, system update, demo baseline, bag tracking, packing
template delete, addons), and auth (app settings, MFA enable/disable).
- Add GET /api/admin/audit-log with pagination; fix invite insert row id lookup.
- Add AuditLogPanel and Admin tab; adminApi.auditLog.
- Add admin.tabs.audit and admin.audit.* strings in all locale files.
Note: Rebase feature branches so new DB migrations stay after existing ones
(e.g. file_links) when merging upstream.
When registration is disabled, users with a valid invite link can now
register via OIDC/SSO. The invite token is passed from the login page
through the OIDC state, validated on callback, and used to bypass the
allow_registration check. Invite usage count is incremented after
successful registration.