From da354d1dd957cda210aa8affb07ec2ffbd86c78d Mon Sep 17 00:00:00 2001 From: Vincent Date: Mon, 12 Feb 2024 23:03:42 +0100 Subject: [PATCH 1/2] `getSubscriberByFxaUid` and `getSubscriberByEmail` can return undefined (#4201) * `getSubscriberByFxaUid` can return undefined This should only change type-level code, except where it adds exceptions to deal with no subscriber being found. This should help us detect when we run into an issue that causes us not to be able to load subscriber data. * Mark `getSubscriberByEmail` as deprecated We replaced its usage as much as possible in 90cb7b939d00fe0615b809c2ea96bd94afb4f872 and 9bbfc63baaac81e2c6d5e5a75bc60d9b0b465abb. * `getSubscriberByEmail` can also return `undefined` --------- Co-authored-by: Robert Helmer --- src/app/api/utils/auth.ts | 20 ++++++------- src/app/api/utils/email.ts | 4 +-- .../api/v1/admin/users/[primarySha1]/route.ts | 3 ++ src/app/api/v1/fxa-rp-events/route.ts | 28 ++++++++++++++++--- .../v1/user/breaches/bulk-resolve/route.ts | 18 ++++++++---- src/app/api/v1/user/breaches/route.ts | 24 ++++++++-------- src/app/api/v1/user/email/route.ts | 10 +++---- src/app/api/v1/user/remove-email/route.ts | 3 ++ src/app/api/v1/user/resend-email/route.ts | 3 ++ .../api/v1/user/update-comm-option/route.ts | 3 ++ .../api/v1/user/welcome-scan/create/route.ts | 3 ++ .../v1/user/welcome-scan/progress/route.ts | 3 ++ .../api/v1/user/welcome-scan/result/route.ts | 3 ++ .../user/breaches/breaches.d.ts | 3 ++ .../functions/server/changeSubscription.ts | 4 +-- .../functions/server/getSubscriberEmails.ts | 3 ++ src/app/functions/server/getUserBreaches.ts | 3 ++ src/db/tables/onerep_scans.ts | 9 ++++-- src/db/tables/subscribers.js | 15 ++++++---- src/knex-tables.d.ts | 7 ++--- src/utils/subscriberBreaches.ts | 4 +-- 21 files changed, 118 insertions(+), 55 deletions(-) diff --git a/src/app/api/utils/auth.ts b/src/app/api/utils/auth.ts index f56a195b0..836cb27dc 100644 --- a/src/app/api/utils/auth.ts +++ b/src/app/api/utils/auth.ts @@ -4,6 +4,7 @@ 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 AppConstants from "../../../appConstants.js"; @@ -72,20 +73,19 @@ export const authOptions: AuthOptions = { async jwt({ token, account, profile, trigger }) { if (trigger === "update") { // Refresh the user data from FxA, in case e.g. new subscriptions got added: - const subscriber = await getSubscriberByFxaUid( + const subscriberFromDb = await getSubscriberByFxaUid( token.subscriber?.fxa_uid ?? "", ); - profile = subscriber.fxa_profile_json as FxaProfile; - if (token.subscriber?.fxa_uid) { - const updatedSubscriberData = await getSubscriberByFxaUid( - token.subscriber.fxa_uid, - ); + if (subscriberFromDb) { + profile = subscriberFromDb.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 updatedSubscriberData.breach_resolution; - token.subscriber = updatedSubscriberData; + delete (subscriberFromDb as Partial).breach_resolution; + token.subscriber = + subscriberFromDb as unknown as SerializedSubscriber; } } if (profile) { @@ -117,8 +117,8 @@ export const authOptions: AuthOptions = { // 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.breach_resolution; - token.subscriber = existingUser; + delete (existingUser as Partial).breach_resolution; + token.subscriber = existingUser as unknown as SerializedSubscriber; if (account.access_token && account.refresh_token) { const updatedUser = await updateFxAData( existingUser, diff --git a/src/app/api/utils/email.ts b/src/app/api/utils/email.ts index 356ea335d..de96c43f7 100644 --- a/src/app/api/utils/email.ts +++ b/src/app/api/utils/email.ts @@ -3,15 +3,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { ReactLocalization } from "@fluent/react"; +import { SubscriberRow } from "knex/types/tables"; import { resetUnverifiedEmailAddress } from "../../../db/tables/emailAddresses.js"; import { sendEmail, getVerificationUrl } from "../../../utils/email"; import { getStringLookup } from "../../../utils/fluent.js"; import { getTemplate } from "../../../views/emails/email2022.js"; import { verifyPartial } from "../../../views/emails/emailVerify.js"; -import { Subscriber } from "../../deprecated/(authenticated)/user/breaches/breaches"; export async function sendVerificationEmail( - user: Subscriber, + user: SubscriberRow, emailId: number, l10n: ReactLocalization, ) { diff --git a/src/app/api/v1/admin/users/[primarySha1]/route.ts b/src/app/api/v1/admin/users/[primarySha1]/route.ts index 9445893db..a287d24e3 100644 --- a/src/app/api/v1/admin/users/[primarySha1]/route.ts +++ b/src/app/api/v1/admin/users/[primarySha1]/route.ts @@ -136,6 +136,9 @@ export async function PUT( const subscriber = await getSubscriberByEmail( subscriberRow.primary_email, ); + if (!subscriber) { + throw new Error("No subscriber found for given email."); + } const onerepProfileId = await getOnerepProfileId(subscriber.id); diff --git a/src/app/api/v1/fxa-rp-events/route.ts b/src/app/api/v1/fxa-rp-events/route.ts index 049897a47..f0f69f594 100644 --- a/src/app/api/v1/fxa-rp-events/route.ts +++ b/src/app/api/v1/fxa-rp-events/route.ts @@ -219,7 +219,10 @@ export async function POST(request: NextRequest) { }); // get current profiledata - const currentFxAProfile = subscriber?.fxa_profile_json || {}; + // Typed as `any` because `subscriber` used to be typed as `any`, and + // making that type more specific was enough work just by itself: + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const currentFxAProfile: any = subscriber?.fxa_profile_json; // merge new event into existing profile data for (const key in updatedProfileFromEvent) { @@ -249,8 +252,25 @@ export async function POST(request: NextRequest) { updateFromEvent, }); + const refreshToken = subscriber.fxa_refresh_token; + const accessToken = subscriber.fxa_access_token; + if (refreshToken === null || accessToken === null) { + logger.error("failed_changing_password", { + subscriber_id: subscriber.id, + fxa_refresh_token: subscriber.fxa_refresh_token, + fxa_access_token: subscriber.fxa_access_token, + }); + return NextResponse.json( + { success: false, message: "failed_changing_password" }, + { status: 500 }, + ); + } + // MNTOR-1932: Change password should revoke sessions - await revokeOAuthTokens(subscriber); + await revokeOAuthTokens({ + fxa_access_token: accessToken, + fxa_refresh_token: refreshToken, + }); break; } case FXA_SUBSCRIPTION_CHANGE_EVENT: { @@ -291,7 +311,7 @@ export async function POST(request: NextRequest) { captureException( new Error(`No OneRep profile Id found, subscriber: ${ - subscriber.id as string + subscriber.id }\n Event: ${event}\n updateFromEvent: ${JSON.stringify(updatedSubscriptionFromEvent)}`), @@ -351,7 +371,7 @@ export async function POST(request: NextRequest) { captureException( new Error(`No OneRep profile Id found, subscriber: ${ - subscriber.id as string + subscriber.id }\n Event: ${event}\n updateFromEvent: ${JSON.stringify( diff --git a/src/app/api/v1/user/breaches/bulk-resolve/route.ts b/src/app/api/v1/user/breaches/bulk-resolve/route.ts index 59e26a257..d09889d2a 100644 --- a/src/app/api/v1/user/breaches/bulk-resolve/route.ts +++ b/src/app/api/v1/user/breaches/bulk-resolve/route.ts @@ -6,10 +6,7 @@ import { NextRequest, NextResponse } from "next/server"; import { authOptions } from "../../../../utils/auth"; import { getServerSession } from "next-auth"; import { logger } from "../../../../../functions/server/logging"; -import { - BreachBulkResolutionRequest, - Subscriber, -} from "../../../../../deprecated/(authenticated)/user/breaches/breaches.js"; +import { BreachBulkResolutionRequest } from "../../../../../deprecated/(authenticated)/user/breaches/breaches.js"; import { getBreaches } from "../../../../../functions/server/getBreaches"; import { getAllEmailsAndBreaches } from "../../../../../../utils/breaches"; import { @@ -30,9 +27,12 @@ export async function PUT(req: NextRequest): Promise { } try { - const subscriber: Subscriber = await getSubscriberByFxaUid( + const subscriber = await getSubscriberByFxaUid( session.user.subscriber.fxa_uid, ); + if (!subscriber) { + throw new Error("No subscriber found for the current session."); + } const allBreaches = await getBreaches(); const { dataType: dataTypeToResolve }: BreachBulkResolutionRequest = await req.json(); @@ -42,7 +42,10 @@ export async function PUT(req: NextRequest): Promise { allBreaches, ); - const currentBreachResolution = subscriber.breach_resolution || {}; // get this from existing breach resolution if available + // Typed as `any` because `subscriber` used to be typed as `any`, and making + // that type more specific was enough work just by itself: + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const currentBreachResolution: any = subscriber.breach_resolution || {}; // get this from existing breach resolution if available for (const verifiedEmail of verifiedEmails) { const currentEmail = verifiedEmail.email; @@ -83,6 +86,9 @@ export async function PUT(req: NextRequest): Promise { subscriber, currentBreachResolution, ); + if (!updatedSubscriber) { + throw new Error("Could not retrieve updated subscriber data."); + } return NextResponse.json({ success: true, diff --git a/src/app/api/v1/user/breaches/route.ts b/src/app/api/v1/user/breaches/route.ts index 52f2718a1..63ebe6d9d 100644 --- a/src/app/api/v1/user/breaches/route.ts +++ b/src/app/api/v1/user/breaches/route.ts @@ -6,10 +6,7 @@ import { NextRequest, NextResponse } from "next/server"; import { getToken } from "next-auth/jwt"; import { logger } from "../../../../functions/server/logging"; -import { - BreachResolutionRequest, - Subscriber, -} from "../../../../deprecated/(authenticated)/user/breaches/breaches.js"; +import { BreachResolutionRequest } from "../../../../deprecated/(authenticated)/user/breaches/breaches.js"; import { getBreaches } from "../../../../functions/server/getBreaches"; import { getAllEmailsAndBreaches } from "../../../../../utils/breaches"; import { @@ -24,9 +21,7 @@ export async function GET(req: NextRequest) { if (typeof token?.subscriber?.fxa_uid === "string") { // Signed in try { - const subscriber: Subscriber = await getSubscriberByFxaUid( - token.subscriber?.fxa_uid, - ); + const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid); const allBreaches = await getBreaches(); const breaches = await getAllEmailsAndBreaches(subscriber, allBreaches); const successResponse = { @@ -48,9 +43,10 @@ export async function PUT(req: NextRequest) { const token = await getToken({ req }); if (typeof token?.subscriber?.fxa_uid === "string") { try { - const subscriber: Subscriber = await getSubscriberByFxaUid( - token.subscriber?.fxa_uid, - ); + const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid); + if (!subscriber) { + throw new Error("No subscriber found for current session."); + } const allBreaches = await getBreaches(); const j = await req.json(); const { @@ -125,7 +121,10 @@ export async function PUT(req: NextRequest) { // */ const currentBreachDataTypes = currentBreaches[0].DataClasses; // get this from existing breaches - const currentBreachResolution = subscriber.breach_resolution || {}; // get this from existing breach resolution if available + // Typed as `any` because `subscriber` used to be typed as `any`, and + // making that type more specific was enough work just by itself: + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const currentBreachResolution: any = subscriber.breach_resolution || {}; // get this from existing breach resolution if available const isResolved = resolutionsChecked.length === currentBreachDataTypes.length; currentBreachResolution[affectedEmail] = { @@ -146,6 +145,9 @@ export async function PUT(req: NextRequest) { subscriber, currentBreachResolution, ); + if (!updatedSubscriber) { + throw new Error("Could not retrieve updated subscriber data."); + } return NextResponse.json({ success: true, diff --git a/src/app/api/v1/user/email/route.ts b/src/app/api/v1/user/email/route.ts index cdc2a994e..a6f02291c 100644 --- a/src/app/api/v1/user/email/route.ts +++ b/src/app/api/v1/user/email/route.ts @@ -14,7 +14,6 @@ import { sendVerificationEmail } from "../../../utils/email"; import { validateEmailAddress } from "../../../../../utils/emailAddress"; import { getL10n } from "../../../../functions/server/l10n"; import { initEmail } from "../../../../../utils/email"; -import { Subscriber } from "../../../../deprecated/(authenticated)/user/breaches/breaches"; import { CONST_MAX_NUM_ADDRESSES } from "../../../../../constants"; interface EmailAddRequest { @@ -28,11 +27,10 @@ export async function POST(req: NextRequest) { if (typeof token?.subscriber?.fxa_uid === "string") { try { const body: EmailAddRequest = await req.json(); - const subscriber = (await getSubscriberByFxaUid( - token.subscriber?.fxa_uid, - )) as Subscriber & { - email_addresses: Array<{ id: number; email: string }>; - }; + const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid); + if (!subscriber) { + throw new Error("No subscriber found for current session."); + } const emailCount = 1 + (subscriber.email_addresses?.length ?? 0); // primary + verified + unverified emails const validatedEmail = validateEmailAddress(body.email); diff --git a/src/app/api/v1/user/remove-email/route.ts b/src/app/api/v1/user/remove-email/route.ts index 5ecb68878..8ede96a4b 100644 --- a/src/app/api/v1/user/remove-email/route.ts +++ b/src/app/api/v1/user/remove-email/route.ts @@ -29,6 +29,9 @@ export async function POST(req: NextRequest) { try { const { emailId }: EmailDeleteRequest = await req.json(); const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid); + if (!subscriber) { + throw new Error("No subscriber found for current session."); + } const existingEmail = await getEmailById(emailId); if (existingEmail?.subscriber_id !== subscriber.id) { diff --git a/src/app/api/v1/user/resend-email/route.ts b/src/app/api/v1/user/resend-email/route.ts index 25e44903e..6db44b14e 100644 --- a/src/app/api/v1/user/resend-email/route.ts +++ b/src/app/api/v1/user/resend-email/route.ts @@ -25,6 +25,9 @@ export async function POST(req: NextRequest) { try { const { emailId }: EmailResendRequest = await req.json(); const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid); + if (!subscriber) { + throw new Error("No subscriber found for current session."); + } const existingEmail = await getUserEmails(subscriber.id); const filteredEmail = existingEmail.filter( diff --git a/src/app/api/v1/user/update-comm-option/route.ts b/src/app/api/v1/user/update-comm-option/route.ts index 3cce725c9..9461bd324 100644 --- a/src/app/api/v1/user/update-comm-option/route.ts +++ b/src/app/api/v1/user/update-comm-option/route.ts @@ -26,6 +26,9 @@ export async function POST(req: NextRequest) { const { communicationOption }: EmailUpdateCommOptionRequest = await req.json(); const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid); + if (!subscriber) { + throw new Error("No subscriber found for current session."); + } // 0 = Send breach alerts to the corresponding affected emails. // 1 = Send all breach alerts to user's primary email address. const allEmailsToPrimary = Number(communicationOption) === 1 ?? false; diff --git a/src/app/api/v1/user/welcome-scan/create/route.ts b/src/app/api/v1/user/welcome-scan/create/route.ts index b3caabfb0..2d987af34 100644 --- a/src/app/api/v1/user/welcome-scan/create/route.ts +++ b/src/app/api/v1/user/welcome-scan/create/route.ts @@ -86,6 +86,9 @@ export async function POST( session.user.subscriber.fxa_uid, ); + if (!subscriber) { + throw new Error("No subscriber found for current session."); + } if (!subscriber.onerep_profile_id) { // Create OneRep profile const profileId = await createProfile(profileData); diff --git a/src/app/api/v1/user/welcome-scan/progress/route.ts b/src/app/api/v1/user/welcome-scan/progress/route.ts index 61754ec8d..229ac33b0 100644 --- a/src/app/api/v1/user/welcome-scan/progress/route.ts +++ b/src/app/api/v1/user/welcome-scan/progress/route.ts @@ -43,6 +43,9 @@ export async function GET( const subscriber = await getSubscriberByFxaUid( session.user.subscriber?.fxa_uid, ); + if (!subscriber) { + throw new Error("No subscriber found for current session."); + } const profileId = await getOnerepProfileId(subscriber.id); const latestScan = await getLatestOnerepScanResults(profileId); diff --git a/src/app/api/v1/user/welcome-scan/result/route.ts b/src/app/api/v1/user/welcome-scan/result/route.ts index 891ced606..b93329826 100644 --- a/src/app/api/v1/user/welcome-scan/result/route.ts +++ b/src/app/api/v1/user/welcome-scan/result/route.ts @@ -31,6 +31,9 @@ export async function GET() { const subscriber = await getSubscriberByFxaUid( session.user.subscriber?.fxa_uid, ); + if (!subscriber) { + throw new Error("No subscriber found for current session."); + } const profileId = await getOnerepProfileId(subscriber.id); const scanResults = await getLatestOnerepScanResults(profileId); diff --git a/src/app/deprecated/(authenticated)/user/breaches/breaches.d.ts b/src/app/deprecated/(authenticated)/user/breaches/breaches.d.ts index e2e2ebd94..713175003 100644 --- a/src/app/deprecated/(authenticated)/user/breaches/breaches.d.ts +++ b/src/app/deprecated/(authenticated)/user/breaches/breaches.d.ts @@ -82,6 +82,9 @@ export interface Breach { Title: string; } +/** + * @deprecated Use {@see SubscriberRow} instead + */ export interface Subscriber { id: number; primary_sha1: string; diff --git a/src/app/functions/server/changeSubscription.ts b/src/app/functions/server/changeSubscription.ts index e22b9dfe6..562290ac1 100644 --- a/src/app/functions/server/changeSubscription.ts +++ b/src/app/functions/server/changeSubscription.ts @@ -2,13 +2,13 @@ * 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 { Subscriber } from "../../deprecated/(authenticated)/user/breaches/breaches"; +import { SubscriberRow } from "knex/types/tables"; import { updateFxAProfileData } from "../../../db/tables/subscribers"; const MONITOR_PREMIUM_CAPABILITY = "monitor"; export async function changeSubscription( - subscriber: Subscriber, + subscriber: SubscriberRow, enabled: boolean, ) { const currentFxAProfile = subscriber?.fxa_profile_json as FxaProfile; diff --git a/src/app/functions/server/getSubscriberEmails.ts b/src/app/functions/server/getSubscriberEmails.ts index 1e56fca9b..6bddf1414 100644 --- a/src/app/functions/server/getSubscriberEmails.ts +++ b/src/app/functions/server/getSubscriberEmails.ts @@ -19,6 +19,9 @@ export async function getSubscriberEmails( } const emailArray: string[] = [user.email]; const subscriber = await getSubscriberByFxaUid(user.subscriber?.fxa_uid); + if (!subscriber) { + throw new Error("No subscriber found for current session."); + } (await getUserEmails(subscriber.id)).forEach((e) => emailArray.push(e.email)); return emailArray; } diff --git a/src/app/functions/server/getUserBreaches.ts b/src/app/functions/server/getUserBreaches.ts index c55bc8dc2..41a586bc2 100644 --- a/src/app/functions/server/getUserBreaches.ts +++ b/src/app/functions/server/getUserBreaches.ts @@ -103,6 +103,9 @@ export async function getSubscriberBreaches( throw new Error("No fxa_uid found in session"); } const subscriber = await getSubscriberByFxaUid(user.subscriber.fxa_uid); + if (!subscriber) { + throw new Error("No subscriber found for the given user data."); + } const allBreaches = await getBreaches(); const breachesData = await getSubBreaches(subscriber, allBreaches); return breachesData; diff --git a/src/db/tables/onerep_scans.ts b/src/db/tables/onerep_scans.ts index c096361bb..076c2a54c 100644 --- a/src/db/tables/onerep_scans.ts +++ b/src/db/tables/onerep_scans.ts @@ -6,8 +6,11 @@ import createDbConnection from "../connect.js"; import { logger } from "../../app/functions/server/logging"; import { ScanResult, Scan } from "../../app/functions/server/onerep.js"; -import { Subscriber } from "../../app/deprecated/(authenticated)/user/breaches/breaches.js"; -import { OnerepScanResultRow, OnerepScanRow } from "knex/types/tables"; +import { + OnerepScanResultRow, + OnerepScanRow, + SubscriberRow, +} from "knex/types/tables"; const knex = createDbConnection(); @@ -72,7 +75,7 @@ async function getLatestOnerepScanResults( } async function setOnerepProfileId( - subscriber: Subscriber, + subscriber: SubscriberRow, onerepProfileId: number, ) { await knex("subscribers").where("id", subscriber.id).update({ diff --git a/src/db/tables/subscribers.js b/src/db/tables/subscribers.js index b27b38252..c463d7b16 100644 --- a/src/db/tables/subscribers.js +++ b/src/db/tables/subscribers.js @@ -65,6 +65,7 @@ async function getSubscriberById (id) { /** * @param {string} uid + * @returns {Promise }>} */ // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy /* c8 ignore start */ @@ -79,6 +80,8 @@ async function getSubscriberByFxaUid (uid) { /** * @param {string} email + * @returns {Promise }>} + * @deprecated Use [[getSubscriberByFxAUid]] instead, as email identifiers are unstable (e.g. we've had issues with case-sensitivity). */ // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy /* c8 ignore start */ @@ -95,7 +98,7 @@ async function getSubscriberByEmail (email) { /** * Update primary email for subscriber * - * @param {import('../../app/deprecated/(authenticated)/user/breaches/breaches.js').Subscriber} subscriber + * @param {import("knex/types/tables").SubscriberRow} subscriber * @param {string} updatedEmail primary email to be updated to * @returns {Promise} updated subscriber */ @@ -179,8 +182,8 @@ async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, fxaPr /** * Update fxa_profile_json for subscriber * - * @param {import('../../app/deprecated/(authenticated)/user/breaches/breaches.js').Subscriber} subscriber knex object in DB - * @param {string} fxaProfileData from Firefox Account + * @param {import("knex/types/tables").SubscriberRow} subscriber knex object in DB + * @param {import("next-auth").Profile | string} fxaProfileData from Firefox Account * @returns {Promise} updated subscriber knex object in DB */ // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy @@ -188,6 +191,8 @@ async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, fxaPr async function updateFxAProfileData (subscriber, fxaProfileData) { await knex('subscribers').where('id', subscriber.id) .update({ + // @ts-ignore Our old code is inconsistent about passing in objects or serialised strings, + // which confuses the typings: fxa_profile_json: fxaProfileData, // @ts-ignore knex.fn.now() results in it being set to a date, // even if it's not typed as a JS date object: @@ -251,7 +256,7 @@ async function setBreachesLastShownNow (subscriber) { /* c8 ignore stop */ /** - * @param {import('../../app/deprecated/(authenticated)/user/breaches/breaches.js').Subscriber} subscriber + * @param {import("knex/types/tables").SubscriberRow} subscriber * @param {boolean} allEmailsToPrimary */ // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy @@ -299,7 +304,7 @@ async function setBreachesResolved (options) { * This column is meant to replace "breaches_resolved" column, which was used * for v1. * - * @param {import('../../app/deprecated/(authenticated)/user/breaches/breaches.js').Subscriber} user user object that contains the id of a user + * @param {import("knex/types/tables").SubscriberRow} user user object that contains the id of a user * @param {any} updatedBreachesResolution {emailId: [{breachId: {isResolved: bool, resolutionsChecked: [BreachType]}}, {}...]} * @returns subscriber */ diff --git a/src/knex-tables.d.ts b/src/knex-tables.d.ts index 6b9043a74..9accbc69e 100644 --- a/src/knex-tables.d.ts +++ b/src/knex-tables.d.ts @@ -334,10 +334,9 @@ declare module "knex/types/tables" { > & Partial>, // On updates, don't allow updating the ID and created date; all - // otherfields are optional, except updated_at. Also, fxa_profile_json - // takes the data as a serialised string: - Partial> & - Pick & { fxa_profile_json: string | null } + // otherfields are optional, except updated_at: + Partial> & + Pick >; email_addresses: Knex.CompositeTableType< diff --git a/src/utils/subscriberBreaches.ts b/src/utils/subscriberBreaches.ts index 289633aff..c3060d815 100644 --- a/src/utils/subscriberBreaches.ts +++ b/src/utils/subscriberBreaches.ts @@ -2,13 +2,13 @@ * 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 { SubscriberRow } from "knex/types/tables"; import { getUserEmails } from "../db/tables/emailAddresses.js"; import { HibpLikeDbBreach, getBreachesForEmail } from "./hibp.js"; import { getSha1 } from "./fxa.js"; import { Breach, HibpBreachDataTypes, - Subscriber, } from "../app/deprecated/(authenticated)/user/breaches/breaches.js"; import { parseIso8601Datetime } from "./parse.js"; import { @@ -59,7 +59,7 @@ function filterBreachDataTypes( * @param allBreaches */ export async function getSubBreaches( - subscriber: Subscriber, + subscriber: SubscriberRow, allBreaches: (Breach | HibpLikeDbBreach)[], ) { const uniqueBreaches: SubscriberBreachMap = {}; From 1c16c4509c8e5e2e0ca9c8f6fce1f98d40b63fba Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Mon, 12 Feb 2024 14:12:47 -0800 Subject: [PATCH 2/2] Encode GA4 optional values correctly (#4202) --- src/app/components/client/GoogleAnalyticsWorkaround.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/app/components/client/GoogleAnalyticsWorkaround.tsx b/src/app/components/client/GoogleAnalyticsWorkaround.tsx index ece9ba34f..3f1f405bb 100644 --- a/src/app/components/client/GoogleAnalyticsWorkaround.tsx +++ b/src/app/components/client/GoogleAnalyticsWorkaround.tsx @@ -86,11 +86,7 @@ export const GoogleAnalyticsWorkaround = ( ); }; -export const sendGAEvent = ( - type: "event", - eventName: string, - ...args: object[] -) => { +export const sendGAEvent = (type: "event", eventName: string, args: object) => { if (process.env.NODE_ENV === "test") { return; } @@ -101,7 +97,7 @@ export const sendGAEvent = ( } if (window[currDataLayerName]) { - window.gtag(type, eventName, { args }); + window.gtag(type, eventName, args); } else { console.warn( `@next/third-parties: GA dataLayer ${currDataLayerName} does not exist`,