From 75f48591e90f8df3682880e34d4b3ef6680482b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Saraiva?= <53227802+Kisakay@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:53:02 +0200 Subject: [PATCH] . --- apps/api/src/docker/mailserver.ts | 18 +++++++++++++++++- apps/api/src/lib/config.ts | 7 +++++-- 2 files changed, 22 insertions(+), 3 deletions(-) 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; -