From 62eef04a6bea86fe35ffdebd87910d1ea3ffdeaa Mon Sep 17 00:00:00 2001 From: florianfederspiel Date: Fri, 4 Sep 2026 07:47:43 +0200 Subject: [PATCH] =?UTF-8?q?KI-AGENT:=20Password-Reset=20gegen=20h=C3=A4nge?= =?UTF-8?q?nden=20Mailversand=20absichern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/routes/auth/auth.ts | 28 +++++++++++++++++++++++----- backend/src/utils/mailer.ts | 7 ++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/backend/src/routes/auth/auth.ts b/backend/src/routes/auth/auth.ts index 2537d6c..43ae572 100644 --- a/backend/src/routes/auth/auth.ts +++ b/backend/src/routes/auth/auth.ts @@ -6,7 +6,7 @@ import { sendMail } from "../../utils/mailer"; import { authUsers } from "../../../db/schema"; import { authTenantUsers } from "../../../db/schema"; import { tenants } from "../../../db/schema"; -import { eq } from "drizzle-orm"; +import { and, eq } from "drizzle-orm"; import { createAccessToken, issueRefreshToken, @@ -211,6 +211,8 @@ export default async function authRoutes(server: FastifyInstance) { .select({ id: authUsers.id, email: authUsers.email, + passwordHash: authUsers.passwordHash, + mustChangePassword: authUsers.must_change_password, }) .from(authUsers) .where(eq(authUsers.email, email.toLowerCase())) @@ -228,12 +230,11 @@ export default async function authRoutes(server: FastifyInstance) { .update(authUsers) .set({ passwordHash, - // @ts-ignore - mustChangePassword: true, + must_change_password: true, }) .where(eq(authUsers.id, user.id)); - await sendMail( + const mailResult = await sendMail( user.email, "FEDEO | Dein neues Passwort", ` @@ -241,9 +242,26 @@ export default async function authRoutes(server: FastifyInstance) {

Dein Passwort wurde zurückgesetzt.

Neues Passwort: ${plainPassword}

Bitte ändere es nach dem Login umgehend.

- ` + ` ); + if (!mailResult.success) { + // Ein fehlgeschlagener Mailversand darf das bisherige Passwort nicht + // unbrauchbar machen. Die Hash-Bedingung schützt parallele Resets. + await server.db + .update(authUsers) + .set({ + passwordHash: user.passwordHash, + must_change_password: user.mustChangePassword, + }) + .where(and( + eq(authUsers.id, user.id), + eq(authUsers.passwordHash, passwordHash), + )); + + return reply.code(502).send({ error: "Password reset email could not be sent" }); + } + return { success: true }; }); } diff --git a/backend/src/utils/mailer.ts b/backend/src/utils/mailer.ts index ce52288..90e38c7 100644 --- a/backend/src/utils/mailer.ts +++ b/backend/src/utils/mailer.ts @@ -7,6 +7,11 @@ export const initMailer = async () => { host: secrets.MAILER_SMTP_HOST, port: Number(secrets.MAILER_SMTP_PORT) || 587, secure: secrets.MAILER_SMTP_SSL === "true", // true für 465, false für andere Ports + // Ohne explizite Timeouts kann ein API-Request bei einem nicht erreichbaren + // SMTP-Server mehrere Minuten offen bleiben. + connectionTimeout: 10_000, + greetingTimeout: 10_000, + socketTimeout: 20_000, auth: { user: secrets.MAILER_SMTP_USER, pass: secrets.MAILER_SMTP_PASS, @@ -34,4 +39,4 @@ export async function sendMail( console.error("❌ Fehler beim Mailversand:", err) return { success: false, error: err } } -} \ No newline at end of file +}