Conversation
…T reconnect Clients such as ChatGPT probe the flat well-known URL on every fresh discovery cycle (i.e. after a full disconnect/reconnect where cached OAuth state is cleared). The SDK's mcpAuthMetadataRouter only serves the path-based form /.well-known/oauth-protected-resource/mcp, so the flat probe returned 404. Without the resource metadata, ChatGPT fell back to the issuer URL as the resource parameter (https://…/ instead of https://…/mcp). The authorize handler then rejected it with invalid_target and redirected back to ChatGPT's callback with an error — showing the user the TREK home page instead of the consent form. Add an explicit GET handler for the flat URL that returns the same protected resource metadata, so the resource URI is discovered correctly on the first probe.
Service worker was intercepting /oauth/authorize navigate requests (not in denylist), serving index.html, and React Router's catch-all redirected to / instead of the SDK authorize handler. Helmet's default COOP: same-origin isolated the /oauth/consent popup from its cross-origin opener, making window.opener null and breaking the popup-based OAuth completion signal for ChatGPT and similar clients.
…ByteString crash Todo/trip names containing chars like → or € (and non-Latin-1 locale templates for Czech, Chinese, Russian, etc.) caused the Fetch API to throw when setting the ntfy Title header. Apply RFC 2047 base64 encoded-word encoding for any header value containing chars above U+00FF; ntfy decodes this automatically.
…uests Add Cloudflare WAF note to MCP-Setup and a full troubleshooting entry covering root cause (IP reputation + UA heuristics), free-plan limitation (disable Bot Fight Mode entirely, with explicit warning), and paid-plan WAF skip rule with the full expression syntax and path table for all MCP/OAuth/.well-known routes.
Behind Cloudflare Zero Trust or Pangolin, cross-origin auth redirects on /api/* calls surface as CORS errors (error.response === undefined) that the existing 401 interceptor never catches, leaving the PWA stuck with network-error toasts instead of re-authenticating. New connectivity module probes /api/health every 30s using fetch with cache:no-store and inspects Content-Type to reliably detect whether the server is reachable vs intercepted by an upstream proxy. axios interceptor changes: - On !error.response + navigator.onLine: run probeNow(); if the health probe also fails (proxy is intercepting all requests), trigger a guarded window.location.reload() so the edge proxy can intercept the top-level navigation and run its auth flow (covers CF Access and Pangolin 302 mode) - On error.response status 401 with text/html body: same reload path, covering Pangolin header-auth extended compatibility mode which returns 401+HTML instead of a 302 redirect. TREK own 401s are always JSON so there is no collision with the existing AUTH_REQUIRED branch. - sessionStorage flag prevents reload loops; cleared on any successful response so the guard resets after re-auth. /api/health excluded from SW NetworkFirst cache (vite.config.js regex) and Cache-Control: no-store added server-side so probes always hit the network and cannot be served stale from the 24h api-data cache. LoginPage caches last-known appConfig in localStorage so the SSO button renders in OIDC+UN/PW dual mode even when the config fetch is intercepted by the proxy. Auto-redirect to IdP skipped when config comes from cache to avoid redirect loops while the proxy is challenging. Fixes discussion #836.
…allenge WorkBox's NavigationRoute served the cached SPA shell on window.location.reload(), meaning Pangolin/CF Access never saw the navigation and the app was left stuck showing stale offline data. Unregistering the SW first lets the navigation reach the network so the upstream proxy can run its auth flow. Also rebuilds server/public with corrected sw.js (health excluded from NetworkFirst, /oauth/ and /.well-known/ added to NavigationRoute denylist).
Dockerfile and Proxmox community script both rebuild client/dist and copy it into server/public at build time — committed artifacts were never used. Replace with .gitkeep and add server/public/* to .gitignore.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
A series of fixes making TREK's MCP OAuth 2.1 server fully compatible with ChatGPT and other strict MCP clients, plus a crash fix for ntfy notifications with non-Latin-1 characters, documentation for Cloudflare bot blocking, PWA resilience behind upstream auth proxies (Cloudflare Zero Trust, Pangolin), mobile layout fixes for the Files tab and the Budget tab.
MCP OAuth 2.1 overhaul (
cbaf744)Comprehensive refactor of the OAuth layer to comply with strict client implementations:
oauthProvider.tsmoduleOAuthAuthorizePage.tsx) and public OAuth routes (oauth.ts)/oauth/authorizeto/oauth/consentso the SDK's own authorization handler can own/oauth/authorizewithout the service worker intercepting itoauth.test.ts) to cover the full RFC-compliant flowsFlat
/.well-known/oauth-protected-resourcefor ChatGPT (6943244)ChatGPT probes the flat well-known URL (
/.well-known/oauth-protected-resource) on every fresh discovery cycle. The SDK only served the path-suffixed form (/.well-known/oauth-protected-resource/mcp), returning 404 on the flat probe. Without the resource metadata, ChatGPT fell back to the issuer URL as theresourceparameter; the authorize handler rejected it withinvalid_target, redirecting back to ChatGPT's callback with an error and showing the user the TREK home page instead of the consent form.Added an explicit GET handler for the flat URL that returns the same protected resource metadata.
OAuth consent popup — SW denylist and COOP header (
f089c55)Two bugs were preventing the OAuth consent popup from working for cross-origin clients:
/oauth/authorizenavigation requests (not in its denylist), servingindex.html, and React Router's catch-all redirected to/instead of the SDK's authorization handlerCross-Origin-Opener-Policy: same-originisolated the consent popup from its cross-origin opener (ChatGPT), makingwindow.openernull and breaking the popup-based OAuth completion signalFixed by adding
/oauth/to the service worker denylist and settingCOOP: unsafe-noneon the/oauth/consentroute.ntfy RFC 2047 header encoding (
7b2928a)ntfy notification headers are transmitted as
ByteString; non-Latin-1 characters (emoji, accented characters, non-ASCII trip names) caused a crash when building the notification request. Non-Latin-1 header values are now encoded with RFC 2047 (=?utf-8?B?…?=).Cloudflare bot detection docs (
48c0f97)Added a troubleshooting entry and MCP-Setup callout documenting that Cloudflare's Bot Fight Mode blocks ChatGPT's MCP requests due to IP reputation and UA heuristics. Covers the free-plan workaround (disable Bot Fight Mode entirely, with an explicit warning about the security tradeoff) and a paid-plan WAF skip rule with the full Cloudflare expression syntax and a table of all affected paths (
/mcp,/oauth/*,/.well-known/*).PWA resilience behind upstream auth proxies — CF Zero Trust & Pangolin (
3ee4da9)When TREK is deployed behind Cloudflare Zero Trust or Pangolin, the PWA was silently getting stuck after the upstream auth cookie expired: XHR calls to
/api/*were intercepted and returned a cross-origin redirect, which the browser CORS-blocked — axios surfaced this as a generic network error that no interceptor caught, so the app just showed "network error" toasts without ever triggering re-authentication.client/src/sync/connectivity.tsmodule — probes/api/healthevery 30 s usingfetchwithcache: no-store. Inspects the response'sContent-Typeto distinguish a reachable TREK server (JSON) from a proxy auth wall (HTML or CORS error). ExposesisReachable()/probeNow()/onChange().!error.response + navigator.onLine: runsprobeNow(); if the health probe also fails (!isReachable()), both requests were blocked by the proxy →window.location.reload()so the edge proxy can intercept the top-level navigation and run its auth flow. Covers CF Access and standard Pangolin (302 mode).error.response.status === 401withContent-Type: text/html: covers Pangolin's header-auth extended compatibility mode, which returns a 401 + HTML redirect page instead of a 302. TREK's own 401 responses are alwaysapplication/json, so there is no collision with the existingAUTH_REQUIREDbranch.sessionStorageflag prevents reload loops; it is cleared on any successful API response so the guard resets after re-auth./api/healthexcluded from SW cache (vite.config.jsNetworkFirst regex) andCache-Control: no-storeadded server-side — ensures probes always hit the network and are never served stale from the 24 hapi-datacache.LoginPageappConfiglocalStorage cache — the SSO button was gated on a successfulGET /api/auth/app-config. When the proxy intercepted that fetch,appConfigstayednulland the SSO button never rendered in OIDC + UN/PW dual mode (reported edge case: users had to uninstall and reinstall the PWA to get the button back). The response is now cached inlocalStorageand used as fallback; auto-redirect to the IdP is skipped when config comes from cache to avoid redirect loops.Mobile Files tab — last file hidden under bottom navbar (
a0c10e3)The Files (
dateien) tab wrapper inTripPlannerPagewas missing thepaddingBottom: 'var(--bottom-nav-h)'reservation that every other scrollable tab already applies. On mobile the 84 px stickyBottomNavwas occluding the last file in the list. Added the padding to match the established pattern.Mobile Budget tab — no way to add a category (
0909abf)The Budget toolbar (currency selector, category-name input +
+button, CSV export) was wrapped inhidden md:flex, so on mobile only the "Budget" title rendered — users could not create budget categories on phones once at least one category already existed (the empty-state form was not gated, creating an asymmetry).Removed the desktop-only gate and added
max-md:!w-fulloverrides on the currency wrapper and category input/button wrapper so controls stack as full-width rows on narrow viewports. The CSV button collapses to icon-only belowsmto avoid crowding.Related Issue or Discussion
Closes #959
Addresses discussion #836
Type of Change
Checklist
devdevbranch, notmain(wiki-only PRs are exempt)