KI-AGENT: Password-Reset gegen hängenden Mailversand absichern

This commit is contained in:
2026-09-04 07:47:43 +02:00
parent a298712973
commit 62eef04a6b
2 changed files with 29 additions and 6 deletions

View File

@@ -6,7 +6,7 @@ import { sendMail } from "../../utils/mailer";
import { authUsers } from "../../../db/schema"; import { authUsers } from "../../../db/schema";
import { authTenantUsers } from "../../../db/schema"; import { authTenantUsers } from "../../../db/schema";
import { tenants } from "../../../db/schema"; import { tenants } from "../../../db/schema";
import { eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { import {
createAccessToken, createAccessToken,
issueRefreshToken, issueRefreshToken,
@@ -211,6 +211,8 @@ export default async function authRoutes(server: FastifyInstance) {
.select({ .select({
id: authUsers.id, id: authUsers.id,
email: authUsers.email, email: authUsers.email,
passwordHash: authUsers.passwordHash,
mustChangePassword: authUsers.must_change_password,
}) })
.from(authUsers) .from(authUsers)
.where(eq(authUsers.email, email.toLowerCase())) .where(eq(authUsers.email, email.toLowerCase()))
@@ -228,12 +230,11 @@ export default async function authRoutes(server: FastifyInstance) {
.update(authUsers) .update(authUsers)
.set({ .set({
passwordHash, passwordHash,
// @ts-ignore must_change_password: true,
mustChangePassword: true,
}) })
.where(eq(authUsers.id, user.id)); .where(eq(authUsers.id, user.id));
await sendMail( const mailResult = await sendMail(
user.email, user.email,
"FEDEO | Dein neues Passwort", "FEDEO | Dein neues Passwort",
` `
@@ -241,9 +242,26 @@ export default async function authRoutes(server: FastifyInstance) {
<p>Dein Passwort wurde zurückgesetzt.</p> <p>Dein Passwort wurde zurückgesetzt.</p>
<p><strong>Neues Passwort:</strong> ${plainPassword}</p> <p><strong>Neues Passwort:</strong> ${plainPassword}</p>
<p>Bitte ändere es nach dem Login umgehend.</p> <p>Bitte ändere es nach dem Login umgehend.</p>
` `
); );
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 }; return { success: true };
}); });
} }

View File

@@ -7,6 +7,11 @@ export const initMailer = async () => {
host: secrets.MAILER_SMTP_HOST, host: secrets.MAILER_SMTP_HOST,
port: Number(secrets.MAILER_SMTP_PORT) || 587, port: Number(secrets.MAILER_SMTP_PORT) || 587,
secure: secrets.MAILER_SMTP_SSL === "true", // true für 465, false für andere Ports 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: { auth: {
user: secrets.MAILER_SMTP_USER, user: secrets.MAILER_SMTP_USER,
pass: secrets.MAILER_SMTP_PASS, pass: secrets.MAILER_SMTP_PASS,
@@ -34,4 +39,4 @@ export async function sendMail(
console.error("❌ Fehler beim Mailversand:", err) console.error("❌ Fehler beim Mailversand:", err)
return { success: false, error: err } return { success: false, error: err }
} }
} }