diff --git a/public/images/email/footer-bg-shapes.png b/public/images/email/footer-bg-shapes.png deleted file mode 100644 index 960fbd4777f..00000000000 Binary files a/public/images/email/footer-bg-shapes.png and /dev/null differ diff --git a/public/images/email/hero-bg-gradient.png b/public/images/email/hero-bg-gradient.png deleted file mode 100644 index 570700aa11a..00000000000 Binary files a/public/images/email/hero-bg-gradient.png and /dev/null differ 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/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/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/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, 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) { diff --git a/src/emails/components/EmailHero.tsx b/src/emails/components/EmailHero.tsx index 6065df99477..95e5c29c8d7 100644 --- a/src/emails/components/EmailHero.tsx +++ b/src/emails/components/EmailHero.tsx @@ -23,8 +23,8 @@ export const EmailHero = (props: Props) => { { - +

{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}; }; 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; };