Commit Graph

472 Commits

Author SHA1 Message Date
jubnl bf23b2d2f2 fix(mcp): surface static token deprecation via server instructions
The deprecation warning was registered as an MCP prompt that clients
must explicitly fetch — it never fired automatically. Move it to the
ServerOptions.instructions field, which is returned in the initialize
response and automatically read by Claude and other MCP clients as
system context.
2026-04-10 02:18:06 +02:00
jubnl 7c0a0d5f39 security(oauth): harden OAuth 2.1/MCP implementation (Critical + High + Medium findings)
Address 14 security findings from internal review of the OAuth 2.1 + MCP layer:

Critical:
- C1: Scope-gate all MCP resources (trips, budget, packing, collab, atlas, vacay, etc.)
- C2: Wire token/session revocation into active MCP session lifecycle per (user, client_id)
- C3: Refresh-token replay detection via parent_token_id chain + cascade revoke on replay

High:
- H1: Validate PKCE code_challenge (43-char base64url) and code_verifier (43–128 chars) format
- H2: Rate-limit /oauth/token (30/min), /authorize/validate (30/min), /oauth/revoke (10/min)
- H3: Strip client metadata from unauthenticated /authorize/validate responses (oracle prevention)
- H4: Constant-time secret comparison via crypto.timingSafeEqual (prevents timing attacks)
- H5: Collapse all invalid_grant cases to a single generic message; log specifics server-side

Medium:
- M1: Set Cache-Control: no-store + Pragma: no-cache on token endpoint responses
- M2: Return 404 (not 200/403) on discovery + revoke endpoints when MCP addon is disabled
- M4: Audit-log all OAuth lifecycle events (create, consent, issue, refresh, revoke, replay)
- M5: Union consent scopes on re-authorization instead of replacing existing grants
- M7: Require httpOnly cookie auth (not Bearer JWT) on all state-mutating OAuth endpoints
- M8: Strict Bearer scheme check in MCP token verification

Refactoring:
- Extract MCP session management (sessions Map, revokeUserSessions, revokeUserSessionsForClient)
  into mcp/sessionManager.ts to break the circular dependency between oauthService and mcp/index
- Extract verifyJwtAndLoadUser helper in auth middleware, shared by authenticate and new
  requireCookieAuth middleware

Tests:
- Fix all existing integration tests broken by the security hardening (OAUTH-019 to OAUTH-032)
- Add 13 new integration tests covering M1, M2, H1, H3, H5, M5, M7, C3
- Add 14 new unit tests covering C2, C3, H1, H3, M5 behaviors in oauthService
2026-04-10 02:03:27 +02:00
jubnl 8212f3c023 feat(oauth): add trips:share scope and redesign consent screen
Introduce trips:share as a dedicated OAuth scope for managing public
share links, decoupled from trips:read and trips:write. Share link
tools (get/create/delete_share_link) now gate on canShareTrips()
instead of the generic read/write scopes. Scope added to both client
and server definitions with full test coverage.

Redesign the consent screen from a narrow single-column card
(max-w-sm) to a two-panel layout (max-w-2xl): app identity and
action buttons on the left, scrollable scope list on the right.
Responsive — stacks vertically on mobile.
2026-04-10 00:55:12 +02:00
Ben Haas 35d676e76e Add real-time autocomplete suggestions when typing in the place search
field, with Google Places Autocomplete API and Nominatim fallback.

  - Add POST /api/maps/autocomplete route and autocompletePlaces service
  - Add mapsApi.autocomplete client method
  - Add debounced autocomplete dropdown to PlaceFormModal with keyboard
    navigation (arrow keys, enter, escape) and mouse selection
  - Use place details API to populate form fields on suggestion selection
  - Derive location bias from existing trip places for better results
  - Extract Google Maps URL regex to shared constant
2026-04-09 15:19:49 -07:00
jubnl 41f1dd9ce5 fix(oauth): select ot.user_id instead of u.id in getUserByAccessToken
u.id was returned by SQLite as `id` but the code read `row.user_id`,
which was undefined. This caused all MCP calls to resolve userId as
undefined, making list_trips return empty and canAccessTrip deny all
access when authenticated via OAuth 2.1.
2026-04-09 23:59:11 +02:00
jubnl 5b44fe68b1 fix(mcp): narrow OAuth scope to allowed intersection instead of rejecting
When a client requests scopes it is not permitted for, silently drop
them rather than failing the entire authorization flow. The token is
issued with only the intersection of requested and allowed scopes.

Also fix /authorize/validate to always return HTTP 200 so the consent
page can surface the actual error_description instead of a generic
axios failure message.
2026-04-09 23:48:05 +02:00
jubnl 830f6c0706 feat(mcp): introduce OAuth 2.1 auth and enforce addon gating
OAuth 2.1 authentication for MCP:
- Add OAuth 2.1 authorization server with PKCE support (routes/oauth.ts)
- Add OAuth service for client CRUD, auth-code flow, and token management (services/oauthService.ts)
- Add typed scope definitions and enforcement helpers (mcp/scopes.ts)
- Add OAuth consent UI page (OAuthAuthorizePage.tsx)
- Add client-side scope labels and descriptions (api/oauthScopes.ts)
- Integrate OAuth token auth into MCP handler alongside existing static tokens
- All OAuth endpoints gated on `mcp` addon

Addon gating across MCP tools, resources, and prompts:
- Add typed ADDON_IDS constant (server/src/addons.ts) replacing all string literals
- Gate budget tools and resources (trip-budget, per-person, settlement) on `budget` addon
- Gate packing tools and resources (trip-packing, trip-packing-bags, trip-todos) on `packing` addon
- Gate todos tools on `packing` addon (mirrors web UI Lists tab behavior)
- Expand atlas gate to cover full tool body (bucket-list + country tools no longer leak)
- Expand collab gate to cover full tool body (collab notes no longer leak)
- Gate packing-list and budget-overview MCP prompts on their respective addons
- Gate get_trip_summary sections per addon; blank packing/budget/collab_notes/todos when disabled
- Remove trip-files resource and files field from get_trip_summary
- Replace all isAddonEnabled('literal') calls with ADDON_IDS constants

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 22:25:58 +02:00
Maurice 0df90086bf feat: include day activities and notes in iCal export (#375)
Timed activities are exported as individual calendar events with
start/end times and location. Untimed activities and day notes are
grouped into an all-day summary event per day with a structured
description listing places and notes.
2026-04-09 20:11:42 +02:00
Maurice 5c0d819fc1 feat: drag-and-drop reorder for budget categories and items (#479)
Add reordering support for budget categories and line items within
categories. Changes persist via new DB table (budget_category_order)
and existing sort_order column. Live sync via WebSocket budget:reordered
event. Use Map instead of plain objects for category grouping to
preserve insertion order with numeric category names.
2026-04-09 19:21:43 +02:00
Julien G. 89c10ccedb Merge pull request #540 from mauriceboe/feat/mcp-enhancement
feat(mcp): extract all MCP tools into dedicated modules and add shared helpers and add missing tools
2026-04-09 18:29:38 +02:00
jubnl 91bde5cb5a feat(mcp): include full budget items and packing list in trip summary
Expand get_trip_summary to return full budget line items and full
packing list (with checked status) instead of totals/stats only.
Update tool description to accurately reflect all returned data
including todos, files, and collab poll/message counts.
2026-04-09 18:23:02 +02:00
jubnl 059a0a24c5 fix(mcp): remove all file tools and remove ability to delete notifications 2026-04-09 18:17:31 +02:00
Maurice 576ad85c08 fix: clear stale accommodation_id on reservation update (#522)
When a place/accommodation is deleted, existing reservations still
reference the now-gone accommodation_id. Validate that the linked
accommodation exists before updating; clear to null if it doesn't.
2026-04-09 18:11:07 +02:00
jubnl 63784d86a3 refactor(mcp): extract all MCP tools into dedicated modules and add shared helpers 2026-04-09 18:09:33 +02:00
jubnl a565f3c665 fix(mcp): add missing google place id on update_place tool 2026-04-09 13:51:00 +02:00
jubnl 78b465a815 fix(mcp): clean up import ordering, static imports, and annotation correctness
- Move safeBroadcast after all imports (was incorrectly placed between import blocks)
- Replace dynamic import of packingService in packing-list prompt with static import
- Fix reorder_day_assignments annotation from NON_IDEMPOTENT to WRITE (reordering is idempotent)
- Fix misleading osm_id description in update_place (removed "create-only" claim)
- Remove internal error detail leakage from MCP 500 responses
2026-04-09 12:59:27 +02:00
unknown 6aeec0ead1 fix: add osm_id to update_place 2026-04-09 12:45:12 +02:00
unknown 3ccafb9a7b fix(mcp): add missing fields to update_place and create_collab_note pinned support 2026-04-09 12:45:11 +02:00
unknown caa6b7ecca fix(mcp): safeBroadcast now calls broadcast correctly (was recursive call bug) 2026-04-09 12:45:11 +02:00
unknown 6883f2fdf9 fix(mcp): revert allowedOrigins to avoid SDK compatibility issues 2026-04-09 12:45:11 +02:00
unknown 4b0cda41cf fix(mcp): wrap broadcast calls in try-catch to prevent WebSocket errors crashing tools 2026-04-09 12:45:10 +02:00
unknown 1646caa66b fix(mcp): add error handling and logging to prevent silent crashes 2026-04-09 12:45:10 +02:00
unknown 39db61cc76 fix(mcp): add describe() to remaining z.enum fields for better tool descriptions 2026-04-09 12:45:10 +02:00
unknown 46449d374a fix(mcp): document assignment enum values in list_places description 2026-04-09 12:45:09 +02:00
unknown 978df648eb feat(mcp): add list_places assignment filter for orphan activities 2026-04-09 12:45:09 +02:00
unknown a012dffa22 MCP: add tool annotations, prompts, mimeType, and capabilities
- Add tool annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) to all 40+ tools
- Register 3 MCP prompts: trip-summary, packing-list, budget-overview
- Add explicit mimeType: application/json to all resource registrations
- Announce capabilities with listChanged on resources, tools, prompts
- Update server name to 'TREK MCP' in MCP initialization
2026-04-09 12:45:08 +02:00
Maurice 2d17ec60db fix: missing avatar URLs in notifications, admin panel, and budget
- Notifications: map raw avatar filename to /uploads/avatars/ URL in
  getNotifications, createNotification broadcasts, and respond handler
- Admin listUsers: include avatar field in SELECT and map to avatar_url
- Admin page: render actual avatar image instead of initial letter only
- Budget loadItemMembers: map avatar to avatar_url (fixed in prior commit)

Fixes #507
2026-04-08 18:17:08 +02:00
Maurice 525dc6ebd2 fix: budget member avatars lost after updating item fields
loadItemMembers was returning raw avatar field without mapping to
avatar_url, causing avatars to disappear when editing days/persons/etc.
2026-04-08 17:38:31 +02:00
Yannis Biasutti 0e3e6df1f0 Merge remote-tracking branch 'origin/dev' into feat/places-kmz-kml-import
# Conflicts:
#	server/tests/integration/places.test.ts
2026-04-07 21:07:42 +02:00
Marco Sadowski d9d389d090 feat: naver list import
Added Naver List Import in a similar style like the Google List Import. To keep the frontend clean I combined both list options.
2026-04-07 14:04:27 +02:00
Yannis Biasutti 81851d8367 refactor(i18n): rename importKmlKmz to importKeyholeMarkup across all locales 2026-04-06 22:26:22 +02:00
Yannis Biasutti aacfd24b58 refactor(places): merge KML/KMZ routes into single POST /import/map endpoint 2026-04-06 21:35:01 +02:00
Yannis Biasutti 8c8bd5bc37 refactor(server): consolidate KML import utilities 2026-04-06 21:27:10 +02:00
jubnl a2359dd769 fix: unrelated changes 2026-04-06 20:17:02 +02:00
jubnl b4922322ae test: expand test suite to 87.3% backend coverage
Add new integration test files covering previously untested routes:
- categories.test.ts — GET /api/categories
- oidc.test.ts — full OIDC login flow (callback, state, errors)
- settings.test.ts — GET/PUT /api/settings, bulk save
- tags.test.ts — CRUD for trip tags
- todo.test.ts — todo items CRUD and reorder

Add new unit test files covering service-layer logic:
- adminService.test.ts — user/invite management, packing templates, OIDC settings
- atlasService.test.ts — atlas search and place enrichment
- authServiceDb.test.ts — DB-backed auth helpers (login, register, MFA)
- backupService.test.ts — export/import/restore logic
- categoryService.test.ts — category CRUD
- dayService.test.ts — day management and accommodation helpers
- mapsService.test.ts — route/directions helpers
- oidcService.test.ts — OIDC state, auth code, role resolution, user upsert
- packingService.test.ts — packing item/bag/template operations
- placeService.test.ts — place CRUD and tag attachment
- settingsService.test.ts — settings get/set/bulk
- tagService.test.ts — tag CRUD
- todoService.test.ts — todo CRUD and reorder
- tripService.test.ts — trip CRUD, member management, archiving
- vacayService.test.ts — vacay integration helpers
- tripAccess.test.ts (middleware) — requireTripAccess middleware

Expand existing integration and unit test files with additional cases
across admin, atlas, auth, backup, collab, days, files, maps, memories
(Immich/Synology), notifications, places, reservations, share, vacay,
weather, auth middleware, ephemeral tokens, notification preferences,
permissions, SSRF guard, and WebSocket connection tests.

Update test helpers (factories.ts, test-db.ts) with new factory
functions and seed data required by the expanded suite.

Fix minor issues in server/src/routes/reservations.ts and
server/src/services/atlasService.ts surfaced by new test coverage.

Update sonar-project.properties to reflect new coverage thresholds.
2026-04-06 20:08:30 +02:00
Yannis Biasutti 5271463064 feat(server): add KML and KMZ place import pipeline 2026-04-06 18:31:47 +02:00
Maurice 66a057a070 fix(bookings): resolve date handling and file auth bugs
- Clear reservation_time fields when switching booking type to hotel (#459)
- Parse date-only reservation_end_time correctly on edit (#455)
- Show end date on booking cards for date-only values (#455)
- Add auth token to file download links in bookings (#454)
- Account for timezone offsets in flight time validation (#456)
2026-04-06 11:32:06 +02:00
jubnl b0dee4dafb feat(mcp): add MCP_MAX_SESSION_PER_USER env var and document it everywhere 2026-04-06 00:09:22 +02:00
jubnl e2be3ec191 fix(atlas): replace fuzzy region matching with exact name_en check
Bidirectional substring matching in isVisitedFeature caused unrelated
regions to be highlighted as visited (e.g. selecting Nordrhein-Westfalen
also marked Nord France due to "nord" being a substring match).

Replace the fuzzy loop with an additional exact check against the Natural
Earth name_en property to cover English-vs-native name mismatches.
Also fix Nominatim field priority to prefer state over county so
reverse-geocoded places resolve to the correct admin-1 level.

Adds integration tests ATLAS-009 through ATLAS-011 covering mark/unmark
region endpoints and user isolation.

Fixes #446
2026-04-05 23:38:34 +02:00
Maurice 5c57116a68 fix(dayplan): restore time-based auto-sort for places and free reorder for untimed
Timed places now auto-sort chronologically when a time is set.
Untimed places can be freely dragged between timed items.
Transports are inserted by time with per-day position override.
Fixes regression from multi-day spanning PR that removed timed/untimed split.
2026-04-05 23:26:35 +02:00
jubnl c8250256a7 fix(streaming): end response on client disconnect during asset pipe
When a client disconnects mid-stream, headers are already sent so the
catch block now calls response.end() before returning, preventing the
socket from being left open and crashing the server. Fixes #445.
2026-04-05 23:11:57 +02:00
Maurice 03757ed0af fix(dayplan): per-day transport positions for multi-day reservations
Reordering places on one day of a multi-day reservation no longer
affects the order on other days. Transport positions are now stored
per-day in a new reservation_day_positions table instead of a single
global day_plan_position on the reservation.
2026-04-05 23:02:42 +02:00
jubnl 9ebca725ae fix(CSP): Paths that end in / match any path they are a prefix of. 2026-04-05 22:20:40 +02:00
Julien G. 9b11abbf4a Merge pull request #434 from jerryhuangyu/feat/support-zh
feat(i18n): add Traditional Chinese (zh-TW) language support
2026-04-05 21:18:02 +02:00
Julien G. cc613771fa Merge pull request #437 from mauriceboe/feat/migrate-node-fetch-to-native
refactor(server): replace node-fetch with native fetch + undici, fix photo integrations
2026-04-05 21:15:03 +02:00
jubnl 5cc81ae4b0 refactor(server): replace node-fetch with native fetch + undici, fix photo integrations
Replace node-fetch v2 with Node 22's built-in fetch API across the entire server.
Add undici as an explicit dependency to provide the dispatcher API needed for
DNS pinning (SSRF rebinding prevention) in ssrfGuard.ts. All seven service files
that used a plain `import fetch from 'node-fetch'` are updated to use the global.
The ssrfGuard safeFetch/createPinnedAgent is rewritten as createPinnedDispatcher
using an undici Agent, with correct handling of the `all: true` lookup callback
required by Node 18+. The collabService dynamic require() and notifications agent
option are updated to use the dispatcher pattern. Test mocks are migrated from
vi.mock('node-fetch') to vi.stubGlobal('fetch'), and streaming test fixtures are
updated to use Web ReadableStream instead of Node Readable.

Fix several bugs in the Synology and Immich photo integrations:
- pipeAsset: guard against setting headers after stream has already started
- _getSynologySession: clear stale SID and re-login when decrypt_api_key returns null
  instead of propagating success(null) downstream
- _requestSynologyApi: return retrySession error (not stale session) on retry failure;
  also retry on error codes 106 (timeout) and 107 (duplicate login), not only 119
- searchSynologyPhotos: fix incorrect total field type (Synology list_item returns no
  total); hasMore correctly uses allItems.length === limit
- _splitPackedSynologyId: validate cache_key format before use; callers return 400
- getImmichCredentials / _getSynologyCredentials: treat null from decrypt_api_key as
  a missing-credentials condition rather than casting null to string
- Synology size param: enforce allowlist ['sm', 'm', 'xl'] per API documentation
2026-04-05 21:12:51 +02:00
Maurice 94b74f96a3 fix(ical): pad datetime to 15 chars for valid iCal DTSTART/DTEND format
Times like 09:00 were exported as YYYYMMDDTHHMM (13 chars) instead of
YYYYMMDDTHHMMSS (15 chars). Google Calendar couldn't parse the short
format and defaulted all events to 12:00 AM. Closes #432
2026-04-05 20:17:22 +02:00
Maurice 48bf149d01 feat(packing): item quantity, bag rename, multi-user bags, save as template
- Add quantity field to packing items (persisted, visible per item)
- Bags are now renamable (click to edit in sidebar)
- Bags support multiple user assignments with avatar display
- New packing_bag_members table for multi-user bag ownership
- Save current packing list as reusable template
- Add bag members API endpoint (PUT /bags/:bagId/members)
- Migration 74: quantity on packing_items, user_id on packing_bags, packing_bag_members table
2026-04-05 19:28:33 +02:00
Maurice 38206883ff feat(budget): bidirectional sync between reservations and budget items
- Link budget items to reservations via reservation_id column
- Update budget entry when reservation price changes (not create duplicate)
- Delete budget entry when reservation price is cleared
- Sync price back to reservation when edited in budget panel
- Lock budget item name when linked to a reservation
- Add migration 73 for reservation_id on budget_items
2026-04-05 18:16:02 +02:00
jerryhuangyu dd21074c27 feat: Add Traditional Chinese (zh-TW) translations support 2026-04-05 23:53:26 +08:00