Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MNTOR-1800: unsubscribe from free monthly report #4988

Merged
merged 44 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
73a361d
feat: add subscribers email pref db functions
mansaj Aug 22, 2024
61002cf
Merge branch 'MNTOR-1800' into MNTOR-1800-2
mansaj Aug 23, 2024
f212434
feat: util crud for email pref table
mansaj Aug 23, 2024
d34fc03
feat: migrate email pref script
mansaj Aug 23, 2024
3b14ba5
Merge branch 'MNTOR-1800' into MNTOR-1800-2
mansaj Aug 23, 2024
2b1cfe9
fix: change script to only insert when no record exists
mansaj Aug 23, 2024
8484972
fix: unsub emails util funcs
mansaj Aug 23, 2024
61a623e
fix: build
mansaj Aug 23, 2024
933ae40
feat: unsubscribe monthly report
mansaj Aug 23, 2024
eb9437d
Merge branch 'main' into MNTOR-1800-unsub-emails
mansaj Aug 26, 2024
f30ede0
Merge branch 'MNTOR-1800' into MNTOR-1800-2
mansaj Aug 27, 2024
a59d99b
fix: add db utils for email pref table
mansaj Aug 27, 2024
1d57bd5
Merge branch 'main' into MNTOR-1800-unsub-emails
mansaj Aug 27, 2024
63cc3ba
Merge branch 'MNTOR-1800-2' into MNTOR-1800-unsub-emails
mansaj Aug 27, 2024
9004c1a
Merge branch 'MNTOR-1800-unsub-emails' of https://github.com/mozilla/…
mansaj Aug 27, 2024
eeed72c
Merge pull request #4973 from mozilla/MNTOR-1800-unsub-emails
mansaj Aug 27, 2024
43a6990
fix: move unsub func to pref util file
mansaj Aug 27, 2024
6e73211
fix: unsubscribe email util func
mansaj Aug 27, 2024
3067d04
feat: set monthly report back to true whenever user sub to plus
mansaj Aug 28, 2024
6723260
Merge pull request #4992 from mozilla/MNTOR-3584
mansaj Aug 29, 2024
00f82f0
Merge branch 'MNTOR-1800' into MNTOR-1800-2
mansaj Aug 29, 2024
e3ad019
feat: storing randomly generated unsub token
mansaj Aug 29, 2024
6301849
fix: update API endpoint for monitor report to include the free reports
mansaj Aug 29, 2024
a6feab0
fix: guard against no unsub token
mansaj Aug 29, 2024
86b3f1a
fix: gurantee boolean value
mansaj Aug 30, 2024
3e37de5
fix: update should upsert too
mansaj Aug 30, 2024
77ab60c
Merge branch 'MNTOR-1800' into MNTOR-1800-2
mansaj Aug 30, 2024
9469811
fix: remove dup primary_email from new table
mansaj Aug 30, 2024
8d63a9b
fix: generate the link for the unsub page instead
mansaj Sep 3, 2024
1c99123
Only require token when unsubscribing
Vinnl Sep 4, 2024
7c1cff3
Merge branch 'MNTOR-1800' into MNTOR-1800-2
mansaj Sep 4, 2024
5080c64
Merge branch 'MNTOR-1800-2' of https://github.com/mozilla/blurts-serv…
mansaj Sep 4, 2024
ac8c751
Merge branch 'MNTOR-1800-2' into MNTOR-1800-token-only
mansaj Sep 4, 2024
5647bec
feat: unique index set
mansaj Sep 4, 2024
cf8e8df
fix: review comments
mansaj Sep 4, 2024
4cbb718
revert:
mansaj Sep 4, 2024
cf331ac
Merge pull request #5019 from mozilla/MNTOR-1800-token-only
mansaj Sep 4, 2024
99dc4b6
Merge branch 'MNTOR-1800-2' of https://github.com/mozilla/blurts-serv…
mansaj Sep 4, 2024
fdb191e
Merge branch 'MNTOR-1800' into MNTOR-1800-2
mansaj Sep 4, 2024
19b1643
fix: add getEmailPreferenceForPrimaryEmail back
mansaj Sep 5, 2024
028b3ce
fix: logs
mansaj Sep 5, 2024
60b2c44
Merge branch 'main' into MNTOR-1800-2
mansaj Sep 5, 2024
cbf0e3c
fix: setter
mansaj Sep 5, 2024
a26ecb9
chore: review comments
mansaj Sep 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/app/api/utils/email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ 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";
import {
addEmailPreferenceForSubscriber,
getEmailPreferenceForPrimaryEmail,
} from "../../../db/tables/subscriber_email_preferences";
import { SerializedSubscriber } from "../../../next-auth.js";

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

export async function generateUnsubscribeLinkForSubscriber(
mansaj marked this conversation as resolved.
Show resolved Hide resolved
subscriber: SerializedSubscriber,
) {
try {
const unsubToken = randomString();
const sub = await addEmailPreferenceForSubscriber(
subscriber.id,
{
unsubscribe_token: unsubToken,
},
["unsubscribe_token"],
);
return `${process.env.SERVER_URL}/api/v1/user/unsubscribe-email?email=${subscriber.primary_email}&token=${sub.unsubscribe_token}`;
mansaj marked this conversation as resolved.
Show resolved Hide resolved
} catch (e) {
console.error("generate_unsubscribe_link", {
exception: e as string,
});
captureException(e);
return null;
}
}

export async function verifyUnsubscribeToken(
email: string,
unsubToken: string,
) {
try {
const preference = await getEmailPreferenceForPrimaryEmail(email);
if (!preference || !preference.unsubscribe_token) {
return false;
}
return unsubToken === preference.unsubscribe_token;
} catch (e) {
console.error("verify_unsubscribe_token", {
exception: e as string,
});
captureException(e);
return false;
}
}

function randomString(length: number = 64) {
return crypto.randomBytes(length).toString("hex");
mansaj marked this conversation as resolved.
Show resolved Hide resolved
}
4 changes: 4 additions & 0 deletions src/app/api/v1/fxa-rp-events/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
updateFxAProfileData,
updatePrimaryEmail,
getOnerepProfileId,
setMonthlyMonitorReport,
} from "../../../../db/tables/subscribers.js";
import {
activateProfile,
Expand Down Expand Up @@ -295,6 +296,9 @@ export async function POST(request: NextRequest) {
// any problems with activation.
await changeSubscription(subscriber, true);

// Set monthly monitor report value back to true
await setMonthlyMonitorReport(subscriber.id, true);

// MNTOR-2103: if one rep profile id doesn't exist in the db, fail immediately
if (!oneRepProfileId) {
logger.error("onerep_profile_not_found", {
Expand Down
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/subscriber_email_preferences";

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 = await 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 });
}
}
13 changes: 11 additions & 2 deletions src/app/api/v1/user/update-comm-option/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { logger } from "../../../../functions/server/logging";
import {
getSubscriberByFxaUid,
setAllEmailsToPrimary,
setMonthlyMonitorReport,
isSubscriberPlus,
} from "../../../../../db/tables/subscribers";
import { updateEmailPreferenceForSubscriber } from "../../../../../db/tables/subscriber_email_preferences";

export type EmailUpdateCommTypeOfOptions = "null" | "affected" | "primary";

Expand Down Expand Up @@ -52,7 +53,15 @@ export async function POST(req: NextRequest) {
await setAllEmailsToPrimary(subscriber, allEmailsToPrimary);
}
if (typeof monthlyMonitorReport === "boolean") {
await setMonthlyMonitorReport(subscriber, monthlyMonitorReport);
const isFree = !(await isSubscriberPlus(subscriber.id));
const preference = isFree
? { monthly_monitor_report_free: monthlyMonitorReport }
: { monthly_monitor_report: monthlyMonitorReport };
await updateEmailPreferenceForSubscriber(
subscriber.id,
isFree,
preference,
);
}

return NextResponse.json({
Expand Down
Loading
Loading