Skip to content

Commit

Permalink
Merge pull request #4973 from mozilla/MNTOR-1800-unsub-emails
Browse files Browse the repository at this point in the history
MNTOR-1800: Part 3 - unsub emails endpoint and generate link util function
  • Loading branch information
mansaj authored Aug 27, 2024
2 parents a59d99b + 9004c1a commit eeed72c
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 50 deletions.
49 changes: 49 additions & 0 deletions src/app/api/utils/email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { VerifyEmailAddressEmail } from "../../../emails/templates/verifyEmailAd
import { sanitizeSubscriberRow } from "../../functions/server/sanitize";
import { getL10n } from "../../functions/l10n/serverComponents";
import { BadRequestError } from "../../../utils/error";
import { captureException } from "@sentry/node";
import crypto from "crypto";

export async function sendVerificationEmail(
user: SubscriberRow,
Expand Down Expand Up @@ -54,3 +56,50 @@ export async function sendVerificationEmail(
),
);
}

export function generateUnsubscribeLink(email: string) {
const secret = process.env.NEXTAUTH_SECRET;

try {
if (!secret) {
throw new Error(
"generateUnsubscribeLink: env var NEXTAUTH_SECRET is not set",
);
}

const key = secret + email;
const unsubToken = getSha2(key);
return `${process.env.SERVER_URL}/api/v1/unsubscribe-email?email=${email}&token=${unsubToken}`;
} catch (e) {
console.error("generate_unsubscribe_link", {
exception: e as string,
});
captureException(e);
return null;
}
}

export function verifyUnsubscribeToken(email: string, unsubToken: string) {
const secret = process.env.NEXTAUTH_SECRET;

try {
if (!secret) {
throw new Error(
"verifyUnsubscribeToken: env var NEXTAUTH_SECRET is not set",
);
}

const key = secret + email;
return unsubToken === getSha2(key);
} catch (e) {
console.error("verify_unsubscribe_token", {
exception: e as string,
});
captureException(e);
return false;
}
}

function getSha2(str: crypto.BinaryLike) {
return crypto.createHash("sha256").update(str).digest("hex");
}
48 changes: 48 additions & 0 deletions src/app/api/v1/user/unsubscribe-email/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { logger } from "../../../../functions/server/logging";
import { verifyUnsubscribeToken } from "../../../utils/email";
import { unsubscribeMonthlyMonitorReportForEmail } from "../../../../../db/tables/subscribers";

export async function GET(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
const email = searchParams.get("email");
const unsubToken = searchParams.get("token");

if (!email || !unsubToken) {
return NextResponse.json(
{
success: false,
message: "email and token are required url parameters.",
},
{ status: 400 },
);
}

const tokenVerified = verifyUnsubscribeToken(email, unsubToken);
if (tokenVerified) {
await unsubscribeMonthlyMonitorReportForEmail(email);
logger.debug("unsubscribe_email_success");
return NextResponse.json({ success: true }, { status: 200 });
} else {
logger.warn("unsubscribe_email_unauthorized_token", {
email,
unsubToken,
});
return NextResponse.json(
{ success: false, message: "Unauthorized unsubscribe token" },
{ status: 401 },
);
}
} catch (e) {
logger.error("unsubscribe_email", {
exception: e as string,
});
return NextResponse.json({ success: false }, { status: 500 });
}
}
2 changes: 1 addition & 1 deletion src/app/api/v1/user/update-comm-option/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export async function POST(req: NextRequest) {
await setAllEmailsToPrimary(subscriber, allEmailsToPrimary);
}
if (typeof monthlyMonitorReport === "boolean") {
await setMonthlyMonitorReport(subscriber, monthlyMonitorReport);
await setMonthlyMonitorReport(subscriber.id, monthlyMonitorReport);
}

return NextResponse.json({
Expand Down
Loading

0 comments on commit eeed72c

Please sign in to comment.