diff --git a/apps/api/src/docker/mailserver.ts b/apps/api/src/docker/mailserver.ts index 4e150bd..6235e95 100644 --- a/apps/api/src/docker/mailserver.ts +++ b/apps/api/src/docker/mailserver.ts @@ -4,6 +4,18 @@ import { hostedMailboxSchema } from "../lib/validation"; import { runDocker } from "./runner"; const MAILDIR_FOLDER = "INBOX"; +const infrastructureErrorPatterns = [ + /cannot connect to the docker daemon/i, + /permission denied/i, + /no such container/i, + /is not running/i, + /executable file not found/i, + /no such file or directory/i, + /command not found/i +]; + +const looksLikeInfrastructureFailure = (output: string): boolean => + infrastructureErrorPatterns.some((pattern) => pattern.test(output)); const localPartRegex = /^[A-Z0-9.!#$%&'*+/=?^_`{|}~-]{1,64}$/i; @@ -29,6 +41,11 @@ export const authenticateMailbox = async (email: string, password: string): Prom { allowNonZero: true } ); + const combinedOutput = `${result.stdout}\n${result.stderr}`.trim(); + if (result.exitCode !== 0 && looksLikeInfrastructureFailure(combinedOutput)) { + throw new HttpError(502, "Mail authentication backend is unavailable."); + } + return result.exitCode === 0; }; @@ -85,4 +102,3 @@ export const removeForwardingAlias = async (sourceEmail: string, targetEmail: st }; export const getSupportedFolder = (): string => MAILDIR_FOLDER; - diff --git a/apps/api/src/lib/config.ts b/apps/api/src/lib/config.ts index 082cdb4..b3cea24 100644 --- a/apps/api/src/lib/config.ts +++ b/apps/api/src/lib/config.ts @@ -1,6 +1,10 @@ import { resolve } from "node:path"; import { z } from "zod"; +const normalizedEnv = Object.fromEntries( + Object.entries(Bun.env).map(([key, value]) => [key, value === "" ? undefined : value]) +); + const splitList = (value: string | undefined, fallback: string): string[] => (value ?? fallback) .split(",") @@ -35,7 +39,7 @@ const envSchema = z.object({ TOTP_ENCRYPTION_KEY: z.string().optional() }); -const raw = envSchema.parse(Bun.env); +const raw = envSchema.parse(normalizedEnv); if ((raw.APP_ADMIN_EMAIL && !raw.APP_ADMIN_PASSWORD) || (!raw.APP_ADMIN_EMAIL && raw.APP_ADMIN_PASSWORD)) { throw new Error("APP_ADMIN_EMAIL and APP_ADMIN_PASSWORD must be provided together."); @@ -69,4 +73,3 @@ export const config = { adminPassword: raw.APP_ADMIN_PASSWORD ?? null, totpEncryptionKey: raw.TOTP_ENCRYPTION_KEY ?? null } as const; -