From 8d1ce3c425fd867b8cac7e639bdc69b08489beab Mon Sep 17 00:00:00 2001 From: Maarten den Braber Date: Thu, 6 Aug 2026 11:36:32 +0200 Subject: [PATCH] Add bearer-token auth mode and optional session URL rewriting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two build-time flags, both inert unless set, so upstream behaviour is unchanged by default. NEXT_PUBLIC_JMAP_AUTH_MODE=bearer sends the login form's password field as `Authorization: Bearer ` instead of Basic. Needed for hosted JMAP providers that only accept bearer tokens — Fastmail answers Basic with 401 and `WWW-Authenticate: Bearer` regardless of credentials, so its API tokens cannot be used at all otherwise. JMAPClient.withBearer already existed; it was just unreachable without an OAuth or TOTP exchange. No onTokenRefresh is passed, which is correct for a static token: a 401 surfaces as an auth error rather than a refresh attempt. NEXT_PUBLIC_JMAP_REWRITE_SESSION_URLS=false disables rewriteSessionUrl. That rewrite forces every URL in the JMAP session resource onto JMAP_SERVER_URL's origin, which is right for Stalwart behind a reverse proxy advertising an internal hostname, but wrong for providers that serve blobs from a separate origin by design — Fastmail returns downloadUrl on www.fastmailusercontent.com, and rewriting it to api.fastmail.com breaks every attachment. Co-Authored-By: Claude Opus 5 (1M context) --- Dockerfile | 4 ++++ lib/jmap/client.ts | 1 + stores/auth-store.ts | 3 +++ 3 files changed, 8 insertions(+) diff --git a/Dockerfile b/Dockerfile index cd12db324..e440dee9d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,10 @@ ARG NEXT_PUBLIC_DEFAULT_LOCALE= ENV NEXT_PUBLIC_DEFAULT_LOCALE=$NEXT_PUBLIC_DEFAULT_LOCALE # Commit SHA shown in the About screen. .dockerignore excludes .git, so # `git rev-parse` inside the build can't find it - CI must pass it in. +ARG NEXT_PUBLIC_JMAP_AUTH_MODE= +ENV NEXT_PUBLIC_JMAP_AUTH_MODE=$NEXT_PUBLIC_JMAP_AUTH_MODE +ARG NEXT_PUBLIC_JMAP_REWRITE_SESSION_URLS= +ENV NEXT_PUBLIC_JMAP_REWRITE_SESSION_URLS=$NEXT_PUBLIC_JMAP_REWRITE_SESSION_URLS ARG GIT_COMMIT=unknown ENV GIT_COMMIT=$GIT_COMMIT RUN npx next build --webpack diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index d2655a774..8c0d1f87d 100644 --- a/lib/jmap/client.ts +++ b/lib/jmap/client.ts @@ -1019,6 +1019,7 @@ export class JMAPClient implements IJMAPClient { } private rewriteSessionUrl(url: string): string { + if (process.env.NEXT_PUBLIC_JMAP_REWRITE_SESSION_URLS === 'false') return url; try { const parsed = new URL(url); const server = new URL(this.serverUrl); diff --git a/stores/auth-store.ts b/stores/auth-store.ts index f302a0ccd..f88aabf31 100644 --- a/stores/auth-store.ts +++ b/stores/auth-store.ts @@ -644,6 +644,9 @@ export const useAuthStore = create()( client.enableTotpReauth(password, () => useTotpReauthStore.getState().requestTotp()); debug.log('auth', 'TOTP re-auth enabled (legacy basic-auth path)'); } + } else if (process.env.NEXT_PUBLIC_JMAP_AUTH_MODE === 'bearer') { + client = JMAPClient.withBearer(serverUrl, password, username); + await client.connect(); } else { client = new JMAPClient(serverUrl, username, password); await client.connect();