diff --git a/services/webmail/next.config.ts b/services/webmail/next.config.ts index 00949be..82bd934 100644 --- a/services/webmail/next.config.ts +++ b/services/webmail/next.config.ts @@ -30,6 +30,12 @@ const nextConfig: NextConfig = { source: "/.well-known/mta-sts.txt", destination: `${PROTOCOLS_SERVICE_URL}/.well-known/mta-sts.txt`, }, + { + // Fetched by receiving mail providers evaluating a message, which + // hold no session and never will. + source: "/.well-known/bimi/default.svg", + destination: `${PROTOCOLS_SERVICE_URL}/.well-known/bimi/default.svg`, + }, { source: "/.well-known/security.txt", destination: `${PROTOCOLS_SERVICE_URL}/.well-known/security.txt`, diff --git a/services/webmail/src/app/api/v1/[...path]/route.ts b/services/webmail/src/app/api/v1/[...path]/route.ts index f56a176..6ccc971 100644 --- a/services/webmail/src/app/api/v1/[...path]/route.ts +++ b/services/webmail/src/app/api/v1/[...path]/route.ts @@ -28,7 +28,7 @@ const PROTOCOLS_SERVICE_URL = process.env.PROTOCOLS_SERVICE_URL ?? "http://proto * a session problem, and the reason to keep this list beside the services it * names. */ -const PROTOCOLS_ADMIN_AREAS = new Set(["dkim", "tls", "dns"]); +const PROTOCOLS_ADMIN_AREAS = new Set(["dkim", "tls", "dns", "bimi"]); function upstreamFor(path: string[]): string { if (path[0] === "mail") return PROTOCOLS_SERVICE_URL; diff --git a/services/webmail/src/wellknown-routing.test.ts b/services/webmail/src/wellknown-routing.test.ts index bd5e499..3d44f24 100644 --- a/services/webmail/src/wellknown-routing.test.ts +++ b/services/webmail/src/wellknown-routing.test.ts @@ -32,3 +32,30 @@ describe("the MTA-STS policy senders fetch", () => { expect(config).not.toMatch(/redirects\(\)/); }); }); + +/** + * Everything the protocols service owns has to be named twice: once where the + * route is registered, and once here where the proxy decides which upstream a + * request goes to. Adding it in only one place is silent - an admin route + * lands on the auth service and comes back "API endpoint not found", and a + * well-known document comes back as this app's own 404 page. + */ +describe("the routes the protocols service owns", () => { + const proxy = readFileSync( + resolve(process.cwd(), "src/app/api/v1/[...path]/route.ts"), + "utf8", + ); + + // Each of these is served by protocols because that process holds something + // the auth service does not. + it.each(["dkim", "tls", "dns", "bimi"])("routes /admin/%s to protocols", (area) => { + expect(proxy).toMatch(new RegExp(`PROTOCOLS_ADMIN_AREAS[\\s\\S]{0,120}"${area}"`)); + }); + + // Fetched by receiving mail providers evaluating a message. They hold no + // session, so this one has to be reachable without any. + it("routes the BIMI logo to the service that stores it", () => { + expect(config).toContain("/.well-known/bimi/default.svg"); + expect(config).toMatch(/bimi\/default\.svg[\s\S]{0,200}PROTOCOLS_SERVICE_URL/); + }); +});