From 3446c47e742640dd414b415a94000f6c48d240bc Mon Sep 17 00:00:00 2001 From: Kaitlyn Andres Date: Wed, 30 Oct 2024 12:34:36 -0400 Subject: [PATCH 1/4] Remove background images for footer and header (#5266) * Remove background images for footer and header * requests * remove images * remove text --- public/images/email/footer-bg-shapes.png | Bin 13786 -> 0 bytes public/images/email/hero-bg-gradient.png | Bin 846 -> 0 bytes src/emails/components/EmailHero.tsx | 4 ++-- src/emails/templates/EmailFooter.tsx | 2 +- src/emails/templates/HeaderStyles.tsx | 18 ++---------------- 5 files changed, 5 insertions(+), 19 deletions(-) delete mode 100644 public/images/email/footer-bg-shapes.png delete mode 100644 public/images/email/hero-bg-gradient.png diff --git a/public/images/email/footer-bg-shapes.png b/public/images/email/footer-bg-shapes.png deleted file mode 100644 index 960fbd4777fb80c03029b6aadf0f6e084b04282d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13786 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 { { - +

{props.heading}

diff --git a/src/emails/templates/EmailFooter.tsx b/src/emails/templates/EmailFooter.tsx index 05dfbd30bb3..566793f6d4b 100644 --- a/src/emails/templates/EmailFooter.tsx +++ b/src/emails/templates/EmailFooter.tsx @@ -120,7 +120,7 @@ export const RedesignedEmailFooter = (props: Props) => { diff --git a/src/emails/templates/HeaderStyles.tsx b/src/emails/templates/HeaderStyles.tsx index d225e0c7cfb..f4bc4e3d2fe 100644 --- a/src/emails/templates/HeaderStyles.tsx +++ b/src/emails/templates/HeaderStyles.tsx @@ -13,26 +13,12 @@ export const MetaTags = () => { }; export const HeaderStyles = () => { - const hideBgImageOnDarkMode = ` + const enforceLightMode = ` :root { color-scheme: light only; supported-color-schemes: light only; } - - .footer_background { - background-image: url(${process.env.SERVER_URL}/images/email/footer-bg-shapes.png); - background-position: center bottom; - background-repeat: no-repeat; - color: #000000 !important; - } - - .hero_background { - background-image: url(${process.env.SERVER_URL}/images/email/hero-bg-gradient.png); - background-repeat: repeat; - background-position-x: 0; - color: #000000 !important; - } `; - return {hideBgImageOnDarkMode}; + return {enforceLightMode}; }; From ce95d67dd997dc1ffd80753441566ba579899325 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 22 Oct 2024 16:08:34 +0200 Subject: [PATCH 2/4] Limit subscriber row fields in session object This limits the subscriber fields available in the session object to an allowlist of properties, rather than using a denylist. --- src/app/api/utils/auth.tsx | 59 +++++++++++++++++++------------------- src/next-auth.d.ts | 7 +++-- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/app/api/utils/auth.tsx b/src/app/api/utils/auth.tsx index 96b7a3db5ea..2ed27b8d9e6 100644 --- a/src/app/api/utils/auth.tsx +++ b/src/app/api/utils/auth.tsx @@ -4,7 +4,6 @@ import { NextRequest } from "next/server"; import { AuthOptions, Profile as FxaProfile, User } from "next-auth"; -import { SubscriberRow } from "knex/types/tables"; import { logger } from "../../functions/server/logging"; import { @@ -26,6 +25,7 @@ import { record } from "../../functions/server/glean"; import { renderEmail } from "../../../emails/renderEmail"; import { SignupReportEmail } from "../../../emails/templates/signupReport/SignupReportEmail"; import { getEnvVarsOrThrow } from "../../../envVars"; +import { sanitizeSubscriberRow } from "../../functions/server/sanitize"; const envVars = getEnvVarsOrThrow([ "OAUTH_AUTHORIZATION_URI", @@ -117,14 +117,11 @@ export const authOptions: AuthOptions = { ); if (subscriberFromDb) { - profile = subscriberFromDb.fxa_profile_json as FxaProfile; + const sanitizedSubscriber = sanitizeSubscriberRow(subscriberFromDb); + profile = sanitizedSubscriber.fxa_profile_json as FxaProfile; - // MNTOR-2599 The breach_resolution object can get pretty big, - // causing the session token cookie to balloon in size, - // eventually resulting in a 400 Bad Request due to headers being too large. - delete (subscriberFromDb as Partial).breach_resolution; token.subscriber = - subscriberFromDb as unknown as SerializedSubscriber; + sanitizedSubscriber as unknown as SerializedSubscriber; } } if (profile) { @@ -153,11 +150,9 @@ export const authOptions: AuthOptions = { const existingUser = await getSubscriberByFxaUid(profile.uid); if (existingUser) { - // MNTOR-2599 The breach_resolution object can get pretty big, - // causing the session token cookie to balloon in size, - // eventually resulting in a 400 Bad Request due to headers being too large. - delete (existingUser as Partial).breach_resolution; - token.subscriber = existingUser as unknown as SerializedSubscriber; + const sanitizedSubscriber = sanitizeSubscriberRow(existingUser); + token.subscriber = + sanitizedSubscriber as unknown as SerializedSubscriber; if (account.access_token && account.refresh_token) { const updatedUser = await updateFxAData( existingUser, @@ -166,13 +161,13 @@ export const authOptions: AuthOptions = { account.expires_at ?? 0, profile, ); - // MNTOR-2599 The breach_resolution object can get pretty big, - // causing the session token cookie to balloon in size, - // eventually resulting in a 400 Bad Request due to headers being too large. - delete (updatedUser as Partial).breach_resolution; - // Next-Auth implicitly converts `updatedUser` to a SerializedSubscriber, - // hence the type assertion: - token.subscriber = updatedUser as unknown as SerializedSubscriber; + if (updatedUser) { + const sanitizedUpdatedUser = sanitizeSubscriberRow(updatedUser); + // Next-Auth implicitly converts `updatedUser` to a SerializedSubscriber, + // hence the type assertion: + token.subscriber = + sanitizedUpdatedUser as unknown as SerializedSubscriber; + } } } else if (!existingUser && profile.email) { const verifiedSubscriber = await addSubscriber( @@ -183,10 +178,14 @@ export const authOptions: AuthOptions = { account.expires_at, profile, ); - // The date fields of `verifiedSubscriber` get converted to an ISO 8601 - // date string when serialised in the token, hence the type assertion: - token.subscriber = - verifiedSubscriber as unknown as SerializedSubscriber; + if (verifiedSubscriber) { + const sanitizedSubscriber = + sanitizeSubscriberRow(verifiedSubscriber); + // The date fields of `verifiedSubscriber` get converted to an ISO 8601 + // date string when serialised in the token, hence the type assertion: + token.subscriber = + sanitizedSubscriber as unknown as SerializedSubscriber; + } const allBreaches = await getBreaches(); const unsafeBreachesForEmail = await getBreachesForEmail( @@ -276,13 +275,13 @@ export const authOptions: AuthOptions = { Date.now() + responseTokens.expires_in * 1000, ); - // MNTOR-2599 The breach_resolution object can get pretty big, - // causing the session token cookie to balloon in size, - // eventually resulting in a 400 Bad Request due to headers being too large. - delete (updatedUser as Partial).breach_resolution; - // Next-Auth implicitly converts `updatedUser` to a SerializedSubscriber, - // hence the type assertion: - token.subscriber = updatedUser as unknown as SerializedSubscriber; + if (updatedUser) { + const sanitizedUpdatedUser = sanitizeSubscriberRow(updatedUser); + // Next-Auth implicitly converts `updatedUser` to a SerializedSubscriber, + // hence the type assertion: + token.subscriber = + sanitizedUpdatedUser as unknown as SerializedSubscriber; + } } catch (error) { logger.error("refresh_access_token", error); // The error property can be used client-side to handle the refresh token error diff --git a/src/next-auth.d.ts b/src/next-auth.d.ts index 00153ed7364..23c0fd8ee78 100644 --- a/src/next-auth.d.ts +++ b/src/next-auth.d.ts @@ -3,10 +3,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { DefaultSession } from "next-auth"; -import { SubscriberRow } from "knex/types/tables"; import { ISO8601DateString } from "./utils/parse"; +import { SanitizedSubscriberRow } from "./app/functions/server/sanitize"; -export type SerializedSubscriber = Omit & { +export type SerializedSubscriber = Omit< + SanitizedSubscriberRow, + "created_at" +> & { created_at: ISO8601DateString; }; From 78d25cf991744ee5527d9b532b625f9ebf17b649 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 23 Oct 2024 16:52:44 +0200 Subject: [PATCH 3/4] Make the primary email hash available It can be trivially calculated by anyone who has access to the email, so there's no need to keep it secret from the user, and the qa-customs page needs access to it. --- src/app/functions/server/sanitize.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/functions/server/sanitize.ts b/src/app/functions/server/sanitize.ts index 3fde76fd568..f83d5ba7c24 100644 --- a/src/app/functions/server/sanitize.ts +++ b/src/app/functions/server/sanitize.ts @@ -48,6 +48,7 @@ export type SanitizedSubscriberRow = SanitizationMarker & | "id" | "primary_email" | "primary_verified" + | "primary_sha1" | "created_at" | "updated_at" | "fx_newsletter" @@ -77,6 +78,7 @@ export function sanitizeSubscriberRow( id: subscriber.id, primary_email: subscriber.primary_email, primary_verified: subscriber.primary_verified, + primary_sha1: subscriber.primary_sha1, created_at: subscriber.created_at, updated_at: subscriber.updated_at, fx_newsletter: subscriber.fx_newsletter, From 2a212cbc84e4037a4cdf83c027fe80d85105929a Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 23 Oct 2024 17:03:43 +0200 Subject: [PATCH 4/4] Don't derive FxA auth code from session --- .../(authenticated)/admin/fxa/actions.tsx | 14 +++++++-- .../user/(dashboard)/settings/actions.ts | 30 +++++++++++++++++-- src/app/functions/server/applyCoupon.ts | 4 +-- src/app/functions/server/deleteAccount.ts | 5 +--- src/app/functions/server/user.ts | 10 +++++-- 5 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/actions.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/actions.tsx index afe06de11ac..dbb002176e8 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/actions.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/actions.tsx @@ -10,6 +10,7 @@ import { getServerSession } from "../../../../../functions/server/getServerSessi import { isAdmin } from "../../../../../api/utils/auth"; import { logger } from "@sentry/utils"; import { captureException } from "@sentry/node"; +import { getSubscriberByFxaUid } from "../../../../../../db/tables/subscribers"; export async function getAttachedClientsAction() { const session = await getServerSession(); @@ -17,14 +18,23 @@ export async function getAttachedClientsAction() { if ( !session?.user?.email || !isAdmin(session.user.email) || - process.env.APP_ENV === "production" + process.env.APP_ENV === "production" || + typeof session?.user?.subscriber?.fxa_uid !== "string" ) { return notFound(); } + const subscriber = await getSubscriberByFxaUid( + session.user.subscriber.fxa_uid, + ); + if (!subscriber) { + logger.error("admin_fxa_no_subscriber_found"); + return notFound(); + } + try { const attachedClients = await getAttachedClients( - session?.user.subscriber?.fxa_access_token ?? "", + subscriber.fxa_access_token ?? "", ); return attachedClients; } catch (error) { diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/actions.ts b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/actions.ts index ee268fbc96a..f0007b3c02c 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/actions.ts +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/actions.ts @@ -176,7 +176,20 @@ export async function onDeleteAccount() { }; } - await deleteAccount(session.user.subscriber); + const subscriber = await getSubscriberByFxaUid( + session.user.subscriber.fxa_uid, + ); + if (!subscriber) { + logger.error( + `Tried to delete an account with a session that could not be linked to a subscriber.`, + ); + return { + success: false, + error: "delete-account-with-invalid-session", + errorMessage: `User tried to delete their account, but we could not find it.`, + }; + } + await deleteAccount(subscriber); // Tell the front page to display an "account deleted" notification: cookies().set("justDeletedAccount", "justDeletedAccount", { @@ -202,7 +215,20 @@ export async function onApplyCouponCode() { }; } - const result = await applyCurrentCouponCode(session.user.subscriber); + const subscriber = await getSubscriberByFxaUid( + session.user.subscriber.fxa_uid, + ); + if (!subscriber) { + logger.error( + `Tried to apply a coupon code with a session that could not be linked to a subscriber.`, + ); + return { + success: false, + error: "apply-coupon-code-with-invalid-session", + errorMessage: `User tried to apply a coupon code, but we could not find their account.`, + }; + } + const result = await applyCurrentCouponCode(subscriber); return result; } diff --git a/src/app/functions/server/applyCoupon.ts b/src/app/functions/server/applyCoupon.ts index d337ac75ca7..9731976260a 100644 --- a/src/app/functions/server/applyCoupon.ts +++ b/src/app/functions/server/applyCoupon.ts @@ -11,9 +11,7 @@ import { } from "../../../db/tables/subscriber_coupons"; import { applyCoupon } from "../../../utils/fxa"; -export async function applyCurrentCouponCode( - subscriber: SubscriberRow | SerializedSubscriber, -) { +export async function applyCurrentCouponCode(subscriber: SubscriberRow) { logger.info("fxa_apply_coupon_code", { subscriber: subscriber.id, }); diff --git a/src/app/functions/server/deleteAccount.ts b/src/app/functions/server/deleteAccount.ts index 4ac9229b8f7..634fc122320 100644 --- a/src/app/functions/server/deleteAccount.ts +++ b/src/app/functions/server/deleteAccount.ts @@ -9,13 +9,10 @@ import { getOnerepProfileId, } from "../../../db/tables/subscribers"; import { deactivateProfile } from "./onerep"; -import { SerializedSubscriber } from "../../../next-auth"; import { deleteSubscription } from "../../../utils/fxa"; import { record } from "./glean"; -export async function deleteAccount( - subscriber: SubscriberRow | SerializedSubscriber, -) { +export async function deleteAccount(subscriber: SubscriberRow) { logger.info("fxa_delete_user", { subscriber: subscriber.id, }); diff --git a/src/app/functions/server/user.ts b/src/app/functions/server/user.ts index ee4bd38d12a..108ab78ccf3 100644 --- a/src/app/functions/server/user.ts +++ b/src/app/functions/server/user.ts @@ -4,10 +4,16 @@ import { Session } from "next-auth"; import { getBillingAndSubscriptions } from "../../../utils/fxa"; +import { getSubscriberByFxaUid } from "../../../db/tables/subscribers"; /* c8 ignore start */ export async function checkUserHasMonthlySubscription(user: Session["user"]) { - if (!user.subscriber?.fxa_access_token) { + if (!user.subscriber?.fxa_uid) { + console.error("FXA UID not set"); + return false; + } + const subscriber = await getSubscriberByFxaUid(user.subscriber.fxa_uid); + if (!subscriber || !subscriber.fxa_access_token) { console.error("FXA token not set"); return false; } @@ -18,7 +24,7 @@ export async function checkUserHasMonthlySubscription(user: Session["user"]) { } const billingAndSubscriptionInfo = await getBillingAndSubscriptions( - user.subscriber.fxa_access_token, + subscriber.fxa_access_token, ); if (billingAndSubscriptionInfo === null) {