From 4c9bc668c7b89d5e86f3aa95d9ab0d9d9c7464e7 Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Mon, 16 Mar 2026 18:39:12 +0000 Subject: [PATCH] feat: Add password reset endpoints for super-admins - POST /api/admin/users/:userId/reset-password - POST /api/admin/reset-password-by-email --- src/routes/admin.ts | 69 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/routes/admin.ts b/src/routes/admin.ts index 7e1712b..990efd8 100644 --- a/src/routes/admin.ts +++ b/src/routes/admin.ts @@ -412,6 +412,75 @@ adminRouter.post("/organizations/:id/remove-free", requireSuperAdmin, async (ctx ctx.response.body = { message: `Freistellung aufgehoben, ${trialDays} Tage Trial gesetzt` }; }); +// Reset user password (Super Admin only) +adminRouter.post("/users/:userId/reset-password", requireSuperAdmin, async (ctx) => { + const userId = ctx.params.userId; + const body = await ctx.request.body.json(); + const { newPassword } = body; + + if (!newPassword || newPassword.length < 8) { + throw new AppError("Neues Passwort erforderlich (min. 8 Zeichen)", 400); + } + + // Check if user exists + const user = await queryOne<{ id: string; email: string }>( + `SELECT id, email FROM users WHERE id = $1`, + [userId] + ); + + if (!user) { + throw new AppError("Benutzer nicht gefunden", 404); + } + + // Hash and update password + const { hashPassword } = await import("../utils/auth.ts"); + const passwordHash = await hashPassword(newPassword); + + await execute( + `UPDATE users SET password_hash = $1 WHERE id = $2`, + [passwordHash, userId] + ); + + ctx.response.body = { + message: `Passwort für ${user.email} zurückgesetzt`, + userId: user.id + }; +}); + +// Reset password by email (Super Admin only) +adminRouter.post("/reset-password-by-email", requireSuperAdmin, async (ctx) => { + const body = await ctx.request.body.json(); + const { email, newPassword } = body; + + if (!email || !newPassword || newPassword.length < 8) { + throw new AppError("Email und neues Passwort erforderlich (min. 8 Zeichen)", 400); + } + + // Check if user exists + const user = await queryOne<{ id: string; email: string }>( + `SELECT id, email FROM users WHERE email = $1`, + [email] + ); + + if (!user) { + throw new AppError("Benutzer nicht gefunden", 404); + } + + // Hash and update password + const { hashPassword } = await import("../utils/auth.ts"); + const passwordHash = await hashPassword(newPassword); + + await execute( + `UPDATE users SET password_hash = $1 WHERE id = $2`, + [passwordHash, user.id] + ); + + ctx.response.body = { + message: `Passwort für ${user.email} zurückgesetzt`, + userId: user.id + }; +}); + // List all organizations with subscription info adminRouter.get("/subscriptions", requireSuperAdmin, async (ctx) => { const filter = ctx.request.url.searchParams.get("filter"); // all, trial, active, paused, expired