diff --git a/app/api/connector/capabilities/route.ts b/app/api/connector/capabilities/route.ts index 87de75fb9..4ff6fd1a1 100644 --- a/app/api/connector/capabilities/route.ts +++ b/app/api/connector/capabilities/route.ts @@ -15,7 +15,8 @@ import { TARGET_NAMES } from '@/lib/connector/registry'; * here, and the answer is narrower than the login page it sits next to. It * says nothing about accounts, domains or the mail server - only the product, * its version, the branded app name, and the target names, which are a - * compile-time constant. + * compile-time constant. The version follows loginShowVersion, like the login + * page (#896); the connector only needs the target names to warn. * * `appName` can be a customer's own branding, so the whole endpoint is gated * on `connectorEnabled` for deployments that would rather disclose nothing. @@ -36,7 +37,9 @@ export async function GET() { product: 'bulwark-webmail', appName: configManager.get('appName', '') || process.env.NEXT_PUBLIC_APP_NAME || 'Bulwark', - version: process.env.NEXT_PUBLIC_APP_VERSION || null, + version: configManager.get('loginShowVersion', true) + ? process.env.NEXT_PUBLIC_APP_VERSION || null + : null, connectorPath: '/connector', targets: TARGET_NAMES, }, diff --git a/lib/__tests__/instance-details-gates.test.ts b/lib/__tests__/instance-details-gates.test.ts index d40383c2d..85b3b96ac 100644 --- a/lib/__tests__/instance-details-gates.test.ts +++ b/lib/__tests__/instance-details-gates.test.ts @@ -1,5 +1,5 @@ // @vitest-environment node -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { NextRequest } from 'next/server'; // Endpoints answering anyone used to hand out what fingerprints an instance: @@ -56,6 +56,7 @@ import { GET as health } from '@/app/api/health/route'; import { GET as updateStatus } from '@/app/api/system/update-status/route'; import { GET as plugins } from '@/app/api/plugins/route'; import { GET as policy } from '@/app/api/admin/policy/route'; +import { GET as connectorCapabilities } from '@/app/api/connector/capabilities/route'; const req = (path: string) => new NextRequest(`https://mail.example${path}`); @@ -64,6 +65,10 @@ beforeEach(() => { config.values = {}; }); +afterEach(() => { + vi.unstubAllEnvs(); +}); + describe('endpoints for visitors who are not signed in', () => { it('health answers the liveness probe but no diagnostics', async () => { const body = await (await health(req('/api/health?detailed=true'))).json(); @@ -87,6 +92,17 @@ describe('endpoints for visitors who are not signed in', () => { expect((await (await updateStatus(req('/api/system/update-status'))).json()).status).toEqual(STATUS); }); + it('the connector probe follows loginShowVersion and still lists its targets', async () => { + vi.stubEnv('NEXT_PUBLIC_APP_VERSION', '1.9.0'); + expect((await (await connectorCapabilities()).json()).version).toBe('1.9.0'); + + config.values.loginShowVersion = false; + const hidden = await (await connectorCapabilities()).json(); + expect(hidden.version).toBeNull(); + expect(hidden.product).toBe('bulwark-webmail'); + expect(hidden.targets.length).toBeGreaterThan(0); + }); + it('the plugin list needs a session', async () => { expect((await plugins(req('/api/plugins'))).status).toBe(401); gate.signedIn = true;