50 lines
2.0 KiB
TypeScript
50 lines
2.0 KiB
TypeScript
/**
|
|
* Turns a digest off from the link in its email, without signing in. Mail
|
|
* clients post here for a one-click unsubscribe (RFC 8058), and so does the
|
|
* client's `/unsubscribe` page.
|
|
*
|
|
* - `POST /api/unsubscribe?token=` — public, with a digest's unsubscribe
|
|
* token. Turns that digest off and answers its cadence.
|
|
* - `POST /api/users/unsubscribe?token=` — the same, at its v3 path, which the
|
|
* `List-Unsubscribe` header of every digest sent before v4 names. Those
|
|
* tokens never expire, so this path stays for good.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import { Router, type RouterMiddleware } from "@oak/oak";
|
|
|
|
import { type APIResponse } from "../../shared/api.ts";
|
|
import { APIException } from "../lib/api-exception.ts";
|
|
import { DIGEST_SETTING, type DigestCadence } from "../../shared/settings.ts";
|
|
import { verifyUnsubscribeToken } from "../lib/jwt.ts";
|
|
import { getUserById } from "../services/user-service.ts";
|
|
import { updateSettings } from "../services/settings-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();
|
|
|
|
// The body is ignored: a mail client's one-click post sends a form saying
|
|
// "List-Unsubscribe=One-Click", the client's page sends nothing.
|
|
const unsubscribe: RouterMiddleware<string> = async (ctx) => {
|
|
const token = ctx.request.url.searchParams.get("token") ?? "";
|
|
const payload = await verifyUnsubscribeToken(token);
|
|
if (!payload) throw new APIException(400, "Invalid unsubscribe link");
|
|
// Throws a 404 once the account is gone.
|
|
getUserById(payload.userId);
|
|
updateSettings(payload.userId, { [DIGEST_SETTING[payload.cadence]]: false });
|
|
const responseBody: APIResponse<{ cadence: DigestCadence }> = {
|
|
success: true,
|
|
data: { cadence: payload.cadence },
|
|
};
|
|
ctx.response.body = responseBody;
|
|
};
|
|
|
|
router.post("/api/unsubscribe", unsubscribe);
|
|
router.post("/api/users/unsubscribe", unsubscribe);
|
|
|
|
export default router;
|