Commit Graph

35 Commits

Author SHA1 Message Date
jubnl e629548a42 fix(tests): align tests to actual working code 2026-04-13 14:48:25 +02:00
jubnl ad27c5f6be fix: restore broken tests after prerelease workflow refactor
- Export __clearVersionCacheForTests() from adminService; call in
  versionNotification beforeEach to reset module-scoped cache between
  tests (VNOTIF-002..006 failed because VNOTIF-001 cached
  update_available:false, short-circuiting all subsequent test fetches)
- Seed appVersion:'2.9.10' in Navbar test authStore; appVersion moved
  from local useEffect state to authStore in last commit so the test
  render no longer fetches it independently (FE-COMP-NAVBAR-016)
- Add data-testid="weekend-days" to VacaySettings weekend-days
  container; use within() in tests to scope button count to that
  section, fixing false positives from the week-start buttons which
  share the same inline styles (FE-COMP-VACAYSETTINGS-003/004)
- Pass isPrerelease={true} in GitHubPanel FE-ADMIN-GH-007; component
  filters out prerelease releases when isPrerelease=false so the badge
  was never rendered (pre-existing, unrelated to last commit)
2026-04-12 17:19:24 +02:00
Maurice de157cb87b test: comprehensive Journey test suite — 89.5% new code coverage
Server (172 tests):
- journeyService unit tests (87 tests): CRUD, access control, sync, photos, contributors
- journeyShareService unit tests (20 tests): share links, token validation, public access
- journey integration tests (45 tests): all API routes, auth, permissions, edge cases
- Test helpers: journey factories, RESET_TABLES updated

Client (340+ tests):
- journeyStore tests (15 tests): all store actions and state management
- JourneyPage tests (20 tests): frontpage, create flow, suggestions, navigation
- JourneyDetailPage tests (94 tests): all sub-components, entry editor, settings,
  share links, contributors, gallery, map, trip linking
- JourneyPublicPage tests (18 tests): public view, tabs, restricted access
- JourneyBookPDF tests (6 tests): PDF generation
- BottomNav tests (9 tests): profile sheet, navigation
- PhotoLightbox tests (8 tests): keyboard nav, counter
- JourneyMap tests (12 tests): markers, polylines, zoom
- Component tests: moodConfig, stripMarkdown, MarkdownToolbar, JournalBody, MobileTopHeader
- DashboardPage tests (32 tests): spotlight card, quick actions, widget settings

SonarQube: exclude unused MemoriesPanel from coverage (dead code, moved to Journey)
2026-04-12 01:19:53 +02:00
jubnl 47d9cce936 fix(tests): update tests for granular auth toggles
- Add new fields to AppConfig type and buildAppConfig factory
- Update FE-PAGE-ADMIN-018: heading changed to "Authentication Methods"
- Update FE-PAGE-ADMIN-053: oidc_only toggle removed from OIDC panel
- Update FE-PAGE-LOGIN-007/017: mocks now include password_login/oidc_login
- Update ADMIN-SVC-049: updateOidcSettings no longer writes oidc_only
2026-04-11 20:33:51 +02:00
jubnl bfd2553d1e feat(auth): split OIDC_ONLY into granular auth toggles
Replaces the coarse oidc_only + allow_registration settings with four
independent toggles: password_login, password_registration, oidc_login,
oidc_registration. Each can be enabled/disabled individually in
Admin > Settings without affecting the others.

- Add resolveAuthToggles() in authService.ts as the central resolver;
  falls back to legacy oidc_only/allow_registration keys when new keys
  are absent (backward compat)
- OIDC_ONLY env var still works and overrides DB toggles for password_*,
  with a visual lock in the admin UI when active
- Server enforces lockout prevention: cannot disable all login methods
- oidc_login gate added to OIDC /login and /callback routes
- Remove oidc_only toggle from OIDC settings panel; replaced by the
  granular toggles in the Settings tab
- Add 6 new resolveAuthToggles() unit tests; fix AUTH-DB-033 error
  message assertion
- Update OIDC_ONLY descriptions in README, docker-compose, Helm values,
  Unraid template, and .env.example to clarify override semantics

Closes #492
2026-04-11 20:21:36 +02:00
jubnl eff3fcfe10 test: update expected event_types count after adding synology_session_cleared 2026-04-11 18:44:40 +02:00
jubnl 7871c06059 feat: enhance Synology Photos integration with OTP, SSL skip, and better UX
- Fix endpoint path: users now provide full base URL (e.g. https://nas:5001/photo)
- Add OTP/2FA field for Synology login
- Add skip SSL verification option (DB column + checkbox UI)
- Add device ID (synology_did) column for session tracking
- Trigger in-app notification when Synology session is cleared
- Show disconnection banner in MemoriesPanel
- Add URL hint in provider settings
- Map Synology API error codes to human-readable messages
- Update i18n for all locales
2026-04-11 18:25:42 +02:00
jubnl 7a22d742ab test: add comprehensive coverage for OAuth scopes, MCP, and core services
Adds new and expanded test suites across client and server to cover the
OAuth 2.1 scope system, MCP session manager, collab service, unified
memories helpers, OIDC service, budget slice, and OAuth authorize page.
Also extends SonarQube coverage exclusions to include bootstrapping files
(migrations, scheduler, main.tsx, types.ts) that are not meaningfully
testable.
2026-04-11 14:08:09 +02:00
jubnl 1585c472c2 fix(test): bumb default limit to reflect implementation 2026-04-11 02:32:17 +02:00
jubnl 535c06bb3f feat(mcp): granular OAuth scopes and per-client rate limiting
- Split `media:read` into `geo:read` and `weather:read` scopes
- Add dedicated `atlas:read/write` scopes (previously under `places`)
- Add dedicated `todos:read/write` scopes (previously under `collab`)
- Rate limiting now keyed by userId+clientId instead of userId alone
- Bind MCP sessions to the OAuth client that created them
- Log MCP tool calls to audit log with clientId
- Invalidate all MCP sessions on addon state change
- Reduce session sweep interval from 10min to 1min
- Update all translations with new scope labels
2026-04-11 02:06:32 +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
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 f2908fdd65 test(mcp): add tests for OAuth 2.1, addon gating, and budget reorder
Covers OAuth integration flow, scope enforcement, addon-gated tool access,
oauthService unit tests, and budget reorder/permission/reservation-sync scenarios.
2026-04-09 23:12:59 +02:00
jubnl 059a0a24c5 fix(mcp): remove all file tools and remove ability to delete notifications 2026-04-09 18:17:31 +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
unknown 978df648eb feat(mcp): add list_places assignment filter for orphan activities 2026-04-09 12:45:09 +02:00
jubnl 781861f799 test: relax ReDoS timing thresholds for CI compatibility
MAPS-024 and MAPS-026 were asserting < 100ms on adversarial regex input,
which passed locally but flaked on CI runners (~150-170ms). These are not
cases of catastrophic backtracking — true ReDoS would take seconds, not
~150ms. Raise the threshold to 500ms to remain meaningful while being
reliable across environments.
2026-04-06 20:12:52 +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
jubnl 5bcadb3cc6 test: apply suite review improvements (01–11)
- Fix SEC-005: rewrite path traversal test to upload a real file, inject
  traversal filename into DB, and assert the download does not succeed
- Fix SEC-007: rename misleading test description to reflect it tests
  rejection of an invalid token, not acceptance of a valid one
- Delete health.test.ts: all 3 tests were exact duplicates of auth.test.ts
  and misc.test.ts
- Remove duplicate describe blocks from misc.test.ts: Categories endpoint
  (duplicate of categories.test.ts) and App config (duplicate of auth.test.ts)
- Remove TRIP-016 from trips.test.ts: weaker duplicate of TRIP-007 (no body
  assertion)
- Remove API Keys describe block from profile.test.ts: canonical copy lives
  in security.test.ts where it belongs
- Remove avatarUrl describe block from budgetService.test.ts: identical tests
  already exist in authService.test.ts; drop now-unused import
- Add DB verification to ASSIGN-007 and PACK-006 reorder tests: query
  day_assignments / packing_items after PUT reorder to confirm order changed
- Strengthen BUDGET-007/008/009: add member/payer setup and assert concrete
  values (total_paid, per-user balance, flow direction and amount)
- Remove 6 pointless Map-semantics tests from inAppNotificationActions.test.ts;
  keep only the two built-in registration checks
- Remove 5 passthrough tests from queryHelpers.test.ts; keep the 4 tests that
  cover actual flat-to-nested transformation logic
2026-04-06 20:08:13 +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
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
jubnl c6148ba4f2 fix(mfa): generate SVG QR code
Replace the rasterized 180px PNG QR code with a crisp 250px SVG
2026-04-05 17:15:19 +02:00
Maurice 9ee5d21c3a test(trips): update TRIP-002 for dateless trips and add day_count test
- TRIP-002 now expects null dates and 7 placeholder days instead of forced date window
- Add TRIP-002b to verify custom day_count creates correct number of days
2026-04-05 16:29:29 +02:00
jubnl 82f08360d7 fix(mcp): route search_place through mapsService to support Google Maps
The search_place MCP tool was hardcoding a direct Nominatim call, ignoring
any configured Google Maps API key and never returning google_place_id despite
the tool description advertising it. Replace the inline fetch with the existing
searchPlaces() service which already switches between Google and Nominatim.

Update unit tests to mock mapsService instead of global fetch, and add a
dedicated test case for the Google path returning google_place_id.

Closes #424
2026-04-05 15:38:19 +02:00
Maurice c274846275 fix(memories): fix deprecated immich route regressions from PR #336
- Fix createAlbumLink using old column name (immich_album_id → album_id)
- Fix deleteAlbumLink not removing associated photos (with owner check)
- Update integration tests for new schema (asset_id, album_id, provider)
2026-04-05 15:19:13 +02:00
jubnl 7b37d337c1 fix(security): address notification system security audit findings
- SSRF: guard sendWebhook() with checkSsrf() + createPinnedAgent() to block
  requests to loopback, link-local, private network, and cloud metadata endpoints
- XSS: escape subject, body, and ctaHref in buildEmailHtml() via escapeHtml()
  to prevent HTML injection through user-controlled params (actor, preview, etc.)
- Encrypt webhook URLs at rest: apply maybe_encrypt_api_key on save
  (settingsService for user URLs, authService for admin URL) and decrypt_api_key
  on read in getUserWebhookUrl() / getAdminWebhookUrl()
- Log failed channel dispatches: inspect Promise.allSettled() results and log
  rejections via logError instead of silently dropping them
- Log admin webhook failures: replace fire-and-forget .catch(() => {}) with
  .catch(err => logError(...)) and await the call
- Migration 69: guard against missing notification_preferences table on fresh installs
- Migration 70: drop the now-unused notification_preferences table
- Refactor: extract applyUserChannelPrefs() helper to deduplicate
  setPreferences / setAdminPreferences logic
- Tests: add SEC-016 (XSS, 5 cases) and SEC-017 (SSRF, 6 cases) test suites;
  mock ssrfGuard in notificationService tests
2026-04-05 03:36:50 +02:00
jubnl fc29c5f7d0 feat(notifications): add unified multi-channel notification system
Introduces a fully featured notification system with three delivery
channels (in-app, email, webhook), normalized per-user/per-event/
per-channel preferences, admin-scoped notifications, scheduled trip
reminders and version update alerts.

- New notificationService.send() as the single orchestration entry point
- In-app notifications with simple/boolean/navigate types and WebSocket push
- Per-user preference matrix with normalized notification_channel_preferences table
- Admin notification preferences stored globally in app_settings
- Migration 69 normalizes legacy notification_preferences table
- Scheduler hooks for daily trip reminders and version checks
- DevNotificationsPanel for testing in dev mode
- All new tests passing, covering dispatch, preferences, migration, boolean
  responses, resilience, and full API integration (NSVC, NPREF, INOTIF,
  MIGR, VNOTIF, NROUTE series)
 - Previous tests passing
2026-04-05 01:22:18 +02:00
jubnl 1bddb3c588 refactor(mcp): replace direct DB access with service layer calls
Replace all db.prepare() calls in mcp/index.ts, mcp/resources.ts, and
mcp/tools.ts with calls to the service layer. Add missing service functions:
- authService: isDemoUser, verifyMcpToken, verifyJwtToken
- adminService: isAddonEnabled
- atlasService: listVisitedCountries
- tripService: getTripSummary, listTrips with null archived param

Also fix getAssignmentWithPlace and formatAssignmentWithPlace to expose
place_id, assignment_time, and assignment_end_time at the top level, and
fix updateDay to correctly handle null title for clearing.

Add comprehensive unit and integration test suite for the MCP layer (821 tests all passing).
2026-04-04 18:12:53 +02:00
jubnl c4c3ea1e6d fix(immich): remove album photos on unlink
When unlinking an Immich album, photos synced from that album are now
deleted. A new `album_link_id` FK column on `trip_photos` tracks the
source album link at sync time; `deleteAlbumLink` deletes matching
photos before removing the link. Individually-added photos are
unaffected. The client now refreshes the photo grid after unlinking.

Adds integration tests IMMICH-020 through IMMICH-024.

Closes #398
2026-04-04 16:37:14 +02:00
jubnl 846db9d076 test(trips): assert exact start/end dates in TRIP-002
Replace not-null checks with exact date assertions mirroring the
route's defaulting logic (tomorrow + 7-day window).
2026-04-04 00:19:54 +02:00
jubnl a307d8d1c9 test(trips): update TRIP-002 to expect default 7-day window
Now that trips always default to a start+7 day window when no dates
are provided, the test expectation of null dates and zero dated days
is no longer valid.
2026-04-04 00:19:46 +02:00
Maurice 501bab0f69 test: update cookie test to match sameSite lax change 2026-04-03 14:42:48 +02:00
jubnl bb54fda6dc fix: collab note attachments broken (#343)
- Fix attachment URLs to use /api/trips/:id/files/:id/download instead
  of /uploads/files/... which was unconditionally blocked with 401
- Use getAuthUrl() with ephemeral tokens for displaying attachments and
  opening them in a new tab (images, PDFs, documents)
- Replace htmlFor/id label pattern with ref.current.click() for the
  file picker button in NoteFormModal — fixes file not being added to
  pending list on first note creation
- Add integration tests COLLAB-028 to COLLAB-031 covering URL format,
  listing URLs, ephemeral token download, and unauthenticated 401
2026-04-03 14:11:18 +02:00
Julien G. 905c7d460b Add comprehensive backend test suite (#339)
* add test suite, mostly covers integration testing, tests are only backend side

* workflow runs the correct script

* workflow runs the correct script

* workflow runs the correct script

* unit tests incoming

* Fix multer silent rejections and error handler info leak

- Revert cb(null, false) to cb(new Error(...)) in auth.ts, collab.ts,
  and files.ts so invalid uploads return an error instead of silently
  dropping the file
- Error handler in app.ts now always returns 500 / "Internal server
  error" instead of forwarding err.message to the client

* Use statusCode consistently for multer errors and error handler

- Error handler in app.ts reads err.statusCode to forward the correct
  HTTP status while keeping the response body generic
2026-04-03 13:17:53 +02:00