import { isNotificationCategory, CATEGORY_INFO } from "~/lib/notification-categories"; import { enforceRateLimit, requestIp } from "../limits"; import { canUnsubscribe, unsubscribe, userForUnsubscribeToken } from "./prefs"; import type { UnsubscribeTarget } from "./prefs"; export const UNSUBSCRIBE_PATH = "/email/unsubscribe"; const HTML_ESCAPES: Record = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }; function escapeHtml(value: string): string { return value.replace(/[&<>"']/g, (char) => HTML_ESCAPES[char] ?? char); } function parseTarget(value: string | null): UnsubscribeTarget | null { if (value === "participating" || value === "watching") return value; return value && isNotificationCategory(value) ? value : null; } function targetLabel(target: UnsubscribeTarget): string { if (target === "participating") return "participating and @mention"; if (target === "watching") return "watched repository"; return CATEGORY_INFO[target].label.toLowerCase(); } function page(body: string, status = 200): Response { return new Response( `Unsubscribe
${body}

Manage notification settings

`, { status, headers: { "content-type": "text/html; charset=utf-8", "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'", "referrer-policy": "no-referrer", }, }, ); } /** * GET shows a confirmation form so link scanners can't unsubscribe anyone; POST (the form or an RFC 8058 * one-click request from the mail client) turns email off for that category. The token is the credential. */ export async function handleUnsubscribeRoute(request: Request, url: URL): Promise { if (url.pathname !== UNSUBSCRIBE_PATH) return null; const token = url.searchParams.get("token") ?? ""; await enforceRateLimit("AUTH_LIMITER", `unsubscribe:${requestIp(request)}`); const target = parseTarget(url.searchParams.get("category")); if (target && !canUnsubscribe(target)) { return page( "

Not available

Security alerts can only be changed from your notification settings.

", 403, ); } const userId = token && target ? await userForUnsubscribeToken(token, target) : null; if (!userId || !target) return page("

Link not valid

This unsubscribe link is invalid or expired.

", 404); const label = escapeHtml(targetLabel(target)); if (request.method === "POST") { await unsubscribe(userId, target); return page(`

Unsubscribed

You will no longer receive ${label} emails.

`); } if (request.method !== "GET" && request.method !== "HEAD") return page("

Method not allowed

", 405); const action = `${UNSUBSCRIBE_PATH}?${new URLSearchParams({ token, category: target })}`; return page(`

Unsubscribe

Stop sending ${label} emails? Web notifications are not affected.

`); }