From c96814db4cce74b03526f445ec072def2610fc28 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:19:26 +0200 Subject: [PATCH] fix: hide the lite server field once config.json sets jmapServerUrl #1087 --- lib/__tests__/lite-scripts.test.ts | 15 +++++++++++++-- scripts/lite/lib.mjs | 14 ++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/__tests__/lite-scripts.test.ts b/lib/__tests__/lite-scripts.test.ts index 8f8e7e958..f8b5bbc05 100644 --- a/lib/__tests__/lite-scripts.test.ts +++ b/lib/__tests__/lite-scripts.test.ts @@ -60,6 +60,7 @@ import { runPostbuild } from '../../scripts/lite/postbuild.mjs'; import { verifyExport } from '../../scripts/lite/verify.mjs'; import { stageContainer } from '../../scripts/lite/container.mjs'; import { LITE_PENDING_PATH_KEY as APP_LITE_PENDING_PATH_KEY } from '../lite'; +import { applyLiteConfig } from '../lite-config'; /** The scripts take `process.env`-shaped input; tests pass plain objects. */ function env(values: Record = {}): NodeJS.ProcessEnv { @@ -102,18 +103,28 @@ describe('lite build helpers', () => { it('derives config.json from the LITE_* inputs with safe defaults', () => { const empty = buildLiteConfig({}); expect(empty.jmapServerUrl).toBe(''); - expect(empty.allowCustomJmapEndpoint).toBe(true); + expect(empty).not.toHaveProperty('allowCustomJmapEndpoint'); + expect(applyLiteConfig(empty).allowCustomJmapEndpoint).toBe(true); expect(empty.rememberMeEnabled).toBe(true); expect(empty.demoMode).toBe(false); expect(empty.appName).toBe('Bulwark Webmail'); const fixed = buildLiteConfig({ LITE_JMAP_SERVER_URL: 'https://mail.example.com/', LITE_APP_NAME: 'Acme', LITE_DEMO_MODE: 'true', LITE_REMEMBER_ME: 'false' }); expect(fixed.jmapServerUrl).toBe('https://mail.example.com'); - expect(fixed.allowCustomJmapEndpoint).toBe(false); + expect(fixed).not.toHaveProperty('allowCustomJmapEndpoint'); + expect(applyLiteConfig(fixed).allowCustomJmapEndpoint).toBe(false); expect(fixed.appName).toBe('Acme'); expect(fixed.demoMode).toBe(true); expect(fixed.rememberMeEnabled).toBe(false); expect(buildLitePolicy().features).toEqual({ pluginsEnabled: false, sidebarAppsEnabled: false }); + expect(buildLiteConfig({ LITE_ALLOW_CUSTOM_ENDPOINT: 'true' }).allowCustomJmapEndpoint).toBe(true); + expect(buildLiteConfig({ LITE_ALLOW_CUSTOM_ENDPOINT: 'false' }).allowCustomJmapEndpoint).toBe(false); + }); + + it('hides the server field once a deployer fills in jmapServerUrl of the shipped config.json (#1087)', () => { + const shipped = buildLiteConfig({}); + const edited = { ...shipped, jmapServerUrl: 'https://mail.example.com' }; + expect(applyLiteConfig(edited).allowCustomJmapEndpoint).toBe(false); }); it('prefixes manifest and redirect paths with the base path', () => { diff --git a/scripts/lite/lib.mjs b/scripts/lite/lib.mjs index 303436abe..02d86badd 100644 --- a/scripts/lite/lib.mjs +++ b/scripts/lite/lib.mjs @@ -139,17 +139,25 @@ export function normalizeBasePath(raw) { * config.json contents from the LITE_* build inputs. The Stalwart target is * served by the mail server itself: an empty server URL means "this origin" * there, so the server field stays hidden unless a build asks for it. + * + * The static target leaves `allowCustomJmapEndpoint` out unless the build sets + * it: the app then derives it from `jmapServerUrl` at runtime. Writing the + * derived `true` of an empty server URL kept the server field on the login + * page after a deployer filled in `jmapServerUrl` and nothing else (#1087). */ export function buildLiteConfig(env = {}, { target = "static" } = {}) { const jmapServerUrl = (env.LITE_JMAP_SERVER_URL ?? "").trim().replace(/\/+$/, ""); const stalwart = target === "stalwart"; + const customEndpointSet = (env.LITE_ALLOW_CUSTOM_ENDPOINT ?? "") !== ""; return { _comment: stalwart ? "Bulwark Lite for Stalwart. Read-only inside the Application bundle: to change it, build your own zip (see LITE-README.md). An empty jmapServerUrl means the Stalwart server that serves this page." : "Bulwark Lite runtime configuration. Edit and re-upload; no rebuild needed. See LITE-README.md.", appName: (env.LITE_APP_NAME ?? "").trim() || "Bulwark Webmail", jmapServerUrl, - allowCustomJmapEndpoint: parseBool(env.LITE_ALLOW_CUSTOM_ENDPOINT, !stalwart && jmapServerUrl === ""), + ...(stalwart || customEndpointSet + ? { allowCustomJmapEndpoint: parseBool(env.LITE_ALLOW_CUSTOM_ENDPOINT, false) } + : {}), rememberMeEnabled: parseBool(env.LITE_REMEMBER_ME, true), demoMode: parseBool(env.LITE_DEMO_MODE, false), loginShowTotp: true, @@ -574,7 +582,9 @@ ${demoMode ? "Demo mode is ON: the login page offers a built-in demo account and 2. Edit \`config.json\`: - \`jmapServerUrl\`: your mail server, e.g. \`https://mail.example.com\`${jmapServerUrl ? ` (currently \`${jmapServerUrl}\`)` : ""}. - \`appName\`: the name shown in the tab and on the login page. - - \`allowCustomJmapEndpoint\`: \`true\` shows a server field on the login page. + - \`allowCustomJmapEndpoint\` (optional): \`true\` always shows a server field + on the login page, \`false\` never does. Left out, the field shows only + while \`jmapServerUrl\` is empty. - \`rememberMeEnabled\`: \`false\` hides "remember me" (sessions then end with the tab). Optional keys: \`demoMode\`, \`jmapServers\`, \`jmapServerAutoPickByDomain\`, the login logo/company/link keys and \`loginShow*\` toggles (same names as the Docker env vars, camelCased). 3. Allow the browser to talk to the mail server (CORS). In Stalwart: