Merge branch 'main' into dependabot/npm_and_yarn/aws-sdk-971def180c

This commit is contained in:
Robert Helmer 2024-02-12 14:13:18 -08:00 коммит произвёл GitHub
Родитель 2e910fe63f 1c16c4509c
Коммит 66aa48a4c0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
22 изменённых файлов: 120 добавлений и 61 удалений

Просмотреть файл

@ -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<SubscriberRow>).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<SubscriberRow>).breach_resolution;
token.subscriber = existingUser as unknown as SerializedSubscriber;
if (account.access_token && account.refresh_token) {
const updatedUser = await updateFxAData(
existingUser,

Просмотреть файл

@ -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,
) {

Просмотреть файл

@ -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);

Просмотреть файл

@ -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(

Просмотреть файл

@ -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<NextResponse> {
}
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<NextResponse> {
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<NextResponse> {
subscriber,
currentBreachResolution,
);
if (!updatedSubscriber) {
throw new Error("Could not retrieve updated subscriber data.");
}
return NextResponse.json({
success: true,

Просмотреть файл

@ -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,

Просмотреть файл

@ -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);

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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(

Просмотреть файл

@ -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;

Просмотреть файл

@ -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);

Просмотреть файл

@ -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);

Просмотреть файл

@ -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);

Просмотреть файл

@ -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`,

Просмотреть файл

@ -82,6 +82,9 @@ export interface Breach {
Title: string;
}
/**
* @deprecated Use {@see SubscriberRow} instead
*/
export interface Subscriber {
id: number;
primary_sha1: string;

Просмотреть файл

@ -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;

Просмотреть файл

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

Просмотреть файл

@ -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;

Просмотреть файл

@ -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({

Просмотреть файл

@ -65,6 +65,7 @@ async function getSubscriberById (id) {
/**
* @param {string} uid
* @returns {Promise<undefined | import("knex/types/tables").SubscriberRow & { email_addresses: Array<{ id: import("knex/types/tables").EmailAddressRow["id"]; email: import("knex/types/tables").EmailAddressRow["email"]; }> }>}
*/
// 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<undefined | import("knex/types/tables").SubscriberRow & { email_addresses: Array<{ id: import("knex/types/tables").EmailAddressRow["id"]; email: import("knex/types/tables").EmailAddressRow["email"]; }> }>}
* @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<import('knex/types/tables').SubscriberRow | null>} 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<object>} 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
*/

7
src/knex-tables.d.ts поставляемый
Просмотреть файл

@ -334,10 +334,9 @@ declare module "knex/types/tables" {
> &
Partial<Pick<SubscriberRow, SubscriberOptionalColumns>>,
// 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<Omit<SubscriberRow, "id" | "created_at" | "fxa_profile_json">> &
Pick<SubscriberRow, "updated_at"> & { fxa_profile_json: string | null }
// otherfields are optional, except updated_at:
Partial<Omit<SubscriberRow, "id" | "created_at">> &
Pick<SubscriberRow, "updated_at">
>;
email_addresses: Knex.CompositeTableType<

Просмотреть файл

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