/** * Resets forgotten passwords. The client's `/login` page asks for a reset * link, which the email takes to the `/reset-password` page, whose token * names the reset. * * - `POST /api/password-resets` — public. Emails a reset link to the account * with that `email`, if any; always answers 200. * - `PUT /api/password-resets/:token` — public, with a reset token. Sets the * `newPassword`, which ends the user's sessions here and on the instances * they signed in to. * * @module */ import { Router } from "@oak/oak"; import { type APIResponse } from "../../shared/api.ts"; import { APIException } from "../lib/api-exception.ts"; import { fieldOf } from "../validators/user.ts"; import { requestPasswordReset, resetPassword, } from "../services/password-reset-service.ts"; import { notifyClients } from "../services/identity-provider-service.ts"; /** * Mounted by `api/main.ts` behind the error, Open Graph and CORS middleware. * An `APIException` thrown by a handler becomes the JSON error response there. */ const router: Router = new Router({ prefix: "/api/password-resets" }); // Answers the same whatever happens, so it can't tell which addresses have an // account. router.post("/", async (ctx) => { const body: unknown = await ctx.request.body.json(); const email = fieldOf(body, "email"); const trimmed = typeof email === "string" ? email.trim() : ""; if (trimmed) { await requestPasswordReset(trimmed).catch((err) => console.error("[password-resets]", err) ); } const responseBody: APIResponse = { success: true, data: null }; ctx.response.body = responseBody; }); router.put("/:token", async (ctx) => { const body: unknown = await ctx.request.body.json(); const newPassword = fieldOf(body, "newPassword"); if (typeof newPassword !== "string") { throw new APIException(400, "Invalid request"); } const userId = await resetPassword(ctx.params.token, newPassword); await notifyClients(userId, { type: "revoke" }); const responseBody: APIResponse = { success: true, data: null }; ctx.response.body = responseBody; }); export default router;