From 58d8d99625a5416d44f6ae28c8fe8cb337990351 Mon Sep 17 00:00:00 2001 From: Randall Stillwell Date: Fri, 17 Apr 2026 14:37:55 -0500 Subject: [PATCH] Fix masked passwords being sent to test endpoints and saved to DB The settings GET endpoint masks emailImapPass and ftpPass for security. The test endpoints and save handler were using these masked values, causing IMAP/FTP test failures and potentially overwriting real passwords. Now the test endpoints fall back to the DB-stored password when they receive the masked value, and the save handler strips masked passwords from the payload. Made-with: Cursor --- src/app/(dashboard)/settings/upload-sources/page.tsx | 6 +++++- src/app/api/email-watch/test/route.ts | 10 +++++++++- src/app/api/ftp-watch/test/route.ts | 12 +++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/app/(dashboard)/settings/upload-sources/page.tsx b/src/app/(dashboard)/settings/upload-sources/page.tsx index 29ebf9b..54d6ba6 100644 --- a/src/app/(dashboard)/settings/upload-sources/page.tsx +++ b/src/app/(dashboard)/settings/upload-sources/page.tsx @@ -120,10 +120,14 @@ export default function UploadSourcesPage() { const handleSave = async () => { setSaving(true); try { + const payload = { ...settings } as Record; + if (payload.emailImapPass === "••••••••") delete payload.emailImapPass; + if (payload.ftpPass === "••••••••") delete payload.ftpPass; + const res = await fetch("/api/settings", { method: "PUT", headers: { "Content-Type": "application/json" }, - body: JSON.stringify(settings), + body: JSON.stringify(payload), }); if (!res.ok) throw new Error(); toast.success("Upload source settings saved"); diff --git a/src/app/api/email-watch/test/route.ts b/src/app/api/email-watch/test/route.ts index 8ce0ad5..862b4ac 100644 --- a/src/app/api/email-watch/test/route.ts +++ b/src/app/api/email-watch/test/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from "next/server"; import { testEmailConnection } from "@/lib/email-watcher"; import { requireApiAuth, handleApiError } from "@/lib/api-auth"; +import { prisma } from "@/lib/db"; + +const MASKED = "••••••••"; export async function POST(request: NextRequest) { try { @@ -10,9 +13,14 @@ export async function POST(request: NextRequest) { const host = String(body.host || ""); const port = parseInt(String(body.port)) || 993; const user = String(body.user || ""); - const pass = String(body.pass || ""); + let pass = String(body.pass || ""); const tls = body.tls !== false; + if (!pass || pass === MASKED) { + const settings = await prisma.appSettings.findUnique({ where: { id: "singleton" } }); + pass = settings?.emailImapPass || ""; + } + if (!host || !user || !pass) { return NextResponse.json( { error: "Host, username, and password are required" }, diff --git a/src/app/api/ftp-watch/test/route.ts b/src/app/api/ftp-watch/test/route.ts index 9376bdd..00bfef8 100644 --- a/src/app/api/ftp-watch/test/route.ts +++ b/src/app/api/ftp-watch/test/route.ts @@ -1,16 +1,26 @@ import { NextRequest, NextResponse } from "next/server"; import { testFtpConnection } from "@/lib/ftp-watcher"; import { requireApiAuth, handleApiError } from "@/lib/api-auth"; +import { prisma } from "@/lib/db"; + +const MASKED = "••••••••"; export async function POST(request: NextRequest) { try { await requireApiAuth(); const body = await request.json(); + + let pass = body.pass || ""; + if (!pass || pass === MASKED) { + const settings = await prisma.appSettings.findUnique({ where: { id: "singleton" } }); + pass = settings?.ftpPass || ""; + } + const result = await testFtpConnection({ host: body.host, port: body.port || 21, user: body.user, - pass: body.pass, + pass, tls: body.tls ?? true, incomingDir: body.incomingDir || "/incoming", });