From b576bc98843e5f14b4a879ba22d48a5b8116a0d8 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Tue, 14 May 2024 10:32:37 -0700 Subject: [PATCH 001/137] ci: 1 click deploy --- .github/workflows/production_deploy.yml | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/production_deploy.yml diff --git a/.github/workflows/production_deploy.yml b/.github/workflows/production_deploy.yml new file mode 100644 index 00000000000..ab74e303aa2 --- /dev/null +++ b/.github/workflows/production_deploy.yml @@ -0,0 +1,40 @@ +name: Monitor 1-click Deployment +on: + workflow_dispatch: + inputs: + environment: + description: 'Environment to deploy to' + required: true + default: 'prod' + type: choice + options: + - stage + - prod + commitSha: + description: 'Commit Sha to deploy' + required: true + type: string +env: + DOCKER_IMAGE_NAME: mozilla/blurts-server +jobs: + pull_retag_push: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Pull Docker image + run: docker pull ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.commitSha }} + + - name: Retag image + run: docker tag ${{ env.DOCKER_IMAGE_NAME }}${{ inputs.commitSha }} ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.environment }}-${{ inputs.commitSha }} + + - name: Redeploy image + run: docker push ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.environment }}-${{ inputs.commitSha }} \ No newline at end of file From bccbb1e704c05050aeb0cc3c7dade28d03dfd136 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Tue, 28 May 2024 18:21:23 -0700 Subject: [PATCH 002/137] feat: mock HIBP --- package-lock.json | 61 +++++++++++++++++++ package.json | 1 + .../range/[hashPrefix]/route.ts | 38 ++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 src/app/api/mock/hibp/breachedaccount/range/[hashPrefix]/route.ts diff --git a/package-lock.json b/package-lock.json index c577a425a13..83980f3281d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,6 +93,7 @@ "jest-fail-on-console": "^3.3.0", "lint-staged": "^15.2.2", "mjml-browser": "^4.15.3", + "oauth2-mock-server": "^7.1.2", "prettier": "3.2.5", "react-intersection-observer": "^9.10.2", "sass": "^1.77.2", @@ -14130,6 +14131,24 @@ } ] }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, "node_modules/better-opn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", @@ -15380,6 +15399,19 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -24782,6 +24814,35 @@ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" }, + "node_modules/oauth2-mock-server": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/oauth2-mock-server/-/oauth2-mock-server-7.1.2.tgz", + "integrity": "sha512-xUg/YOTcMRe8W+q2jphecq1fB1BAjlAPbeeA9lvqwGaQSPJKxI2e8JUnDXHrrKGNJAVXQdHgE/9h4RpCtOfYOA==", + "dev": true, + "dependencies": { + "basic-auth": "^2.0.1", + "cors": "^2.8.5", + "express": "^4.18.2", + "is-plain-object": "^5.0.0", + "jose": "^5.3.0" + }, + "bin": { + "oauth2-mock-server": "dist/oauth2-mock-server.js" + }, + "engines": { + "node": "^18.12 || ^20 || ^22", + "yarn": "^1.15.2" + } + }, + "node_modules/oauth2-mock-server/node_modules/jose": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.3.0.tgz", + "integrity": "sha512-IChe9AtAE79ru084ow8jzkN2lNrG3Ntfiv65Cvj9uOCE2m5LNsdHG+9EbxWxAoWRF9TgDOqLN5jm08++owDVRg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", diff --git a/package.json b/package.json index f0129300603..01289299889 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "jest-fail-on-console": "^3.3.0", "lint-staged": "^15.2.2", "mjml-browser": "^4.15.3", + "oauth2-mock-server": "^7.1.2", "prettier": "3.2.5", "react-intersection-observer": "^9.10.2", "sass": "^1.77.2", diff --git a/src/app/api/mock/hibp/breachedaccount/range/[hashPrefix]/route.ts b/src/app/api/mock/hibp/breachedaccount/range/[hashPrefix]/route.ts new file mode 100644 index 00000000000..f2c8bae7e9b --- /dev/null +++ b/src/app/api/mock/hibp/breachedaccount/range/[hashPrefix]/route.ts @@ -0,0 +1,38 @@ +/* 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 { logger } from "../../../../../../functions/server/logging"; +import { getSha1 } from "../../../../../../../utils/fxa"; +import type { BinaryLike } from "crypto"; +type BreachedAccountResponse = { + hashSuffix: string; + websites: string[]; +}[]; + +export function GET() { + const { APP_ENV } = process.env; + + // Check if APP_ENV is set to production + if (APP_ENV === "production") { + return NextResponse.json( + { error: "Endpoint not available in production environment" }, + { status: 403 }, + ); + } + + // Mock data for test email, can be randomized + const userEmail = process.env.E2E_TEST_ACCOUNT_EMAIL; + const currentUserSha = getSha1(userEmail as BinaryLike); + logger.info("Mock endpoint: /breachedaccount/range/"); + + const data: BreachedAccountResponse = [ + { + hashSuffix: currentUserSha.slice(6).toUpperCase(), + websites: ["Adobe"], + }, + ]; + + return NextResponse.json(data); +} From a0d9df7d2d1b17d9b5d7b9e3cd78cf2b9994686c Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Thu, 13 Jun 2024 01:24:38 -0700 Subject: [PATCH 003/137] able to default to mock endpoint on failure locally. --- .env-dist | 2 + package.json | 1 - .../range/[hashPrefix]/route.ts | 14 +++-- src/app/api/mock/hibp/data/fakeBreaches.json | 8 +++ src/app/api/mock/hibp/mockConfigure/route.ts | 56 +++++++++++++++++++ src/utils/hibp.js | 17 +++++- 6 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 src/app/api/mock/hibp/data/fakeBreaches.json create mode 100644 src/app/api/mock/hibp/mockConfigure/route.ts diff --git a/.env-dist b/.env-dist index 0519ef4f161..5d88112333b 100755 --- a/.env-dist +++ b/.env-dist @@ -55,6 +55,8 @@ OAUTH_API_URI="https://api-accounts.stage.mozaws.net/v1" HIBP_RELOAD_BREACHES_TIMER=600 # HIBP API for range search and subscription HIBP_KANON_API_ROOT=https://api.haveibeenpwned.com +HIBP_KANON_API_ROOT_TRUE=https://api.haveibeenpwned.com +HIBP_KANON_API_ROOT_FAKE=http://localhost:6060/api/mock/hibp HIBP_KANON_API_TOKEN= HIBP_API_ROOT=https://haveibeenpwned.com/api/v2 HIBP_API_TOKEN= diff --git a/package.json b/package.json index 08b08c6d5da..b03b4839052 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,6 @@ "jest-fail-on-console": "^3.3.0", "lint-staged": "^15.2.5", "mjml-browser": "^4.15.3", - "oauth2-mock-server": "^7.1.2", "prettier": "3.2.5", "react-intersection-observer": "^9.10.2", "sass": "^1.77.4", diff --git a/src/app/api/mock/hibp/breachedaccount/range/[hashPrefix]/route.ts b/src/app/api/mock/hibp/breachedaccount/range/[hashPrefix]/route.ts index f2c8bae7e9b..190fb74c664 100644 --- a/src/app/api/mock/hibp/breachedaccount/range/[hashPrefix]/route.ts +++ b/src/app/api/mock/hibp/breachedaccount/range/[hashPrefix]/route.ts @@ -5,7 +5,9 @@ import { NextResponse } from "next/server"; import { logger } from "../../../../../../functions/server/logging"; import { getSha1 } from "../../../../../../../utils/fxa"; +import fakeBreaches from "../../../data/fakeBreaches.json"; import type { BinaryLike } from "crypto"; + type BreachedAccountResponse = { hashSuffix: string; websites: string[]; @@ -24,15 +26,15 @@ export function GET() { // Mock data for test email, can be randomized const userEmail = process.env.E2E_TEST_ACCOUNT_EMAIL; + //TODO: getServerSession doesn't work here for some reason const currentUserSha = getSha1(userEmail as BinaryLike); logger.info("Mock endpoint: /breachedaccount/range/"); - const data: BreachedAccountResponse = [ - { - hashSuffix: currentUserSha.slice(6).toUpperCase(), - websites: ["Adobe"], - }, - ]; + let data = fakeBreaches.data as BreachedAccountResponse; + data = data.map((elem) => ({ + ...elem, + hashSuffix: currentUserSha.slice(6).toUpperCase(), + })); return NextResponse.json(data); } diff --git a/src/app/api/mock/hibp/data/fakeBreaches.json b/src/app/api/mock/hibp/data/fakeBreaches.json new file mode 100644 index 00000000000..84c48aa96ae --- /dev/null +++ b/src/app/api/mock/hibp/data/fakeBreaches.json @@ -0,0 +1,8 @@ +{ + "data": [ + { + "hashSuffix": "", + "websites": ["Adobe", "AshleyMadison", "BTCE"] + } + ] +} \ No newline at end of file diff --git a/src/app/api/mock/hibp/mockConfigure/route.ts b/src/app/api/mock/hibp/mockConfigure/route.ts new file mode 100644 index 00000000000..6482c26b924 --- /dev/null +++ b/src/app/api/mock/hibp/mockConfigure/route.ts @@ -0,0 +1,56 @@ +/* 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 { NextRequest, NextResponse } from "next/server"; +import { logger } from "../../../../functions/server/logging"; + +export function GET() { + const { APP_ENV, HIBP_KANON_API_ROOT, HIBP_KANON_API_ROOT_TRUE } = + process.env; + if (APP_ENV === "production") { + logger.info("Attempt to access environment variable HIBP_KANON_API_ROOT"); + return NextResponse.json( + { error: "Endpoint not available in production environment" }, + { status: 403 }, + ); + } + return NextResponse.json({ + message: `HIBP endpoint is ${HIBP_KANON_API_ROOT === HIBP_KANON_API_ROOT_TRUE ? "REAL" : "FAKE"}`, + }); +} + +export async function PUT(req: NextRequest) { + //TODO: make new environemnt variable to use for real vs mock endpoint + + const { APP_ENV, HIBP_KANON_API_ROOT_TRUE, SERVER_URL } = process.env; + const reqJson = await req.json(); + const useMock = reqJson.useMock; + + // Check if APP_ENV is set to production + if (APP_ENV === "production") { + logger.info( + "Attempt to change environment variable HIBP_KANON_API_ROOT in production environment", + ); + return NextResponse.json( + { error: "Endpoint not available in production environment" }, + { status: 403 }, + ); + } + let msg = + "Environment variable HIBP_KANON_API_ROOT has been updated to use mock API"; + // Set the HIBP_KANON_API_ROOT environment variable + if (useMock) { + process.env.HIBP_KANON_API_ROOT = SERVER_URL + "/api/mock/hibp"; + } else { + process.env.HIBP_KANON_API_ROOT = HIBP_KANON_API_ROOT_TRUE; + msg = + "Environment variable HIBP_KANON_API_ROOT has been updated to use true API"; + } + logger.info(msg); + + // Return a success response + return NextResponse.json({ + message: msg, + }); +} diff --git a/src/utils/hibp.js b/src/utils/hibp.js index 60c0b2837c9..3ea51fef134 100644 --- a/src/utils/hibp.js +++ b/src/utils/hibp.js @@ -61,8 +61,21 @@ async function _throttledFetch (url, reqOptions, tryCount = 1) { throw new InternalServerError(`bad response: ${response.status}`) } } catch (err) { - console.error('_throttledFetch', { err }) - throw new InternalServerError(getMessage('error-hibp-connect')) + const mockUrl = (String(process.env.HIBP_KANON_API_ROOT_FAKE)) + url.match(/\/breachedaccount.*/); + + console.error('_throttledFetch - attempting to switch to mock endpoint', { err }) + const fakeEndpoint = process.env.HIBP_KANON_API_ROOT_FAKE + '/mockConfigure'; + await fetch(fakeEndpoint, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({'useMock': true}) + }) + const response = await fetch(mockUrl, reqOptions); + const resJson = await response.json(); + if (response.ok) return resJson; + throw new InternalServerError('Both real and mock endpoints FAILED'); } } /* c8 ignore stop */ From 695e1fcf5cf66ec4a4e516e642cb80f60f088642 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Thu, 13 Jun 2024 01:40:09 -0700 Subject: [PATCH 004/137] made it host-flexibile for HIBP fallback --- .env-dist | 2 +- src/app/api/mock/hibp/mockConfigure/route.ts | 20 ++++++++++++++++++-- src/utils/hibp.js | 10 ++++++++-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/.env-dist b/.env-dist index 5d88112333b..cabc1dfd242 100755 --- a/.env-dist +++ b/.env-dist @@ -56,7 +56,7 @@ HIBP_RELOAD_BREACHES_TIMER=600 # HIBP API for range search and subscription HIBP_KANON_API_ROOT=https://api.haveibeenpwned.com HIBP_KANON_API_ROOT_TRUE=https://api.haveibeenpwned.com -HIBP_KANON_API_ROOT_FAKE=http://localhost:6060/api/mock/hibp +HIBP_KANON_API_SUFFIX_FAKE=/api/mock/hibp HIBP_KANON_API_TOKEN= HIBP_API_ROOT=https://haveibeenpwned.com/api/v2 HIBP_API_TOKEN= diff --git a/src/app/api/mock/hibp/mockConfigure/route.ts b/src/app/api/mock/hibp/mockConfigure/route.ts index 6482c26b924..56e088c2011 100644 --- a/src/app/api/mock/hibp/mockConfigure/route.ts +++ b/src/app/api/mock/hibp/mockConfigure/route.ts @@ -23,7 +23,23 @@ export function GET() { export async function PUT(req: NextRequest) { //TODO: make new environemnt variable to use for real vs mock endpoint - const { APP_ENV, HIBP_KANON_API_ROOT_TRUE, SERVER_URL } = process.env; + const { + APP_ENV, + HIBP_KANON_API_ROOT_TRUE, + HIBP_KANON_API_SUFFIX_FAKE, + SERVER_URL, + } = process.env; + + if (HIBP_KANON_API_SUFFIX_FAKE === undefined || SERVER_URL === undefined) { + return NextResponse.json( + { + error: + "Server environment not configured correctly: suffix_fake or server_url is undefined", + }, + { status: 500 }, + ); + } + const reqJson = await req.json(); const useMock = reqJson.useMock; @@ -41,7 +57,7 @@ export async function PUT(req: NextRequest) { "Environment variable HIBP_KANON_API_ROOT has been updated to use mock API"; // Set the HIBP_KANON_API_ROOT environment variable if (useMock) { - process.env.HIBP_KANON_API_ROOT = SERVER_URL + "/api/mock/hibp"; + process.env.HIBP_KANON_API_ROOT = SERVER_URL + HIBP_KANON_API_SUFFIX_FAKE; } else { process.env.HIBP_KANON_API_ROOT = HIBP_KANON_API_ROOT_TRUE; msg = diff --git a/src/utils/hibp.js b/src/utils/hibp.js index 3ea51fef134..e689fe15ba3 100644 --- a/src/utils/hibp.js +++ b/src/utils/hibp.js @@ -61,10 +61,16 @@ async function _throttledFetch (url, reqOptions, tryCount = 1) { throw new InternalServerError(`bad response: ${response.status}`) } } catch (err) { - const mockUrl = (String(process.env.HIBP_KANON_API_ROOT_FAKE)) + url.match(/\/breachedaccount.*/); + const {SERVER_URL, HIBP_KANON_API_SUFFIX_FAKE} = process.env; + if (SERVER_URL === undefined || HIBP_KANON_API_SUFFIX_FAKE === undefined) { + throw new InternalServerError('Environemnt not configured correctly: Missing server_url or hibp_suffix_fake') + } + + const mockHost = String(SERVER_URL) + String(HIBP_KANON_API_SUFFIX_FAKE); + const mockUrl = mockHost + url.match(/\/breachedaccount.*/); console.error('_throttledFetch - attempting to switch to mock endpoint', { err }) - const fakeEndpoint = process.env.HIBP_KANON_API_ROOT_FAKE + '/mockConfigure'; + const fakeEndpoint = mockHost + '/mockConfigure'; await fetch(fakeEndpoint, { method: 'PUT', headers: { From e07af0e938b8162e72b059dd43bb4dd0e4faf405 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Thu, 13 Jun 2024 07:58:41 -0700 Subject: [PATCH 005/137] npx prettified fakeBraches.json --- src/app/api/mock/hibp/data/fakeBreaches.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/api/mock/hibp/data/fakeBreaches.json b/src/app/api/mock/hibp/data/fakeBreaches.json index 84c48aa96ae..d4d2731dbbb 100644 --- a/src/app/api/mock/hibp/data/fakeBreaches.json +++ b/src/app/api/mock/hibp/data/fakeBreaches.json @@ -1,8 +1,8 @@ { "data": [ - { - "hashSuffix": "", - "websites": ["Adobe", "AshleyMadison", "BTCE"] - } + { + "hashSuffix": "", + "websites": ["Adobe", "AshleyMadison", "BTCE"] + } ] -} \ No newline at end of file +} From c0b253a76cc6c041857e38d81ce2192337fb00e1 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Thu, 13 Jun 2024 08:48:06 -0700 Subject: [PATCH 006/137] made the code cleaner, and switch back to true endpoint after using falling back to mock --- src/utils/hibp.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/utils/hibp.js b/src/utils/hibp.js index e689fe15ba3..ecb294931d4 100644 --- a/src/utils/hibp.js +++ b/src/utils/hibp.js @@ -29,6 +29,21 @@ function _addStandardOptions (options = {}) { } /* c8 ignore stop */ +/** + * @param {boolean} doUse + */ +async function toggleMockEndpoint(doUse) { + const {SERVER_URL, HIBP_KANON_API_SUFFIX_FAKE} = process.env; + const fakeEndpoint = String(SERVER_URL) + String(HIBP_KANON_API_SUFFIX_FAKE) + '/mockConfigure'; + return await fetch(fakeEndpoint, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({'useMock': doUse}) + }) +} + /** * @param {string} url * @param {any | undefined} reqOptions @@ -70,15 +85,9 @@ async function _throttledFetch (url, reqOptions, tryCount = 1) { const mockUrl = mockHost + url.match(/\/breachedaccount.*/); console.error('_throttledFetch - attempting to switch to mock endpoint', { err }) - const fakeEndpoint = mockHost + '/mockConfigure'; - await fetch(fakeEndpoint, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({'useMock': true}) - }) + await toggleMockEndpoint(true); const response = await fetch(mockUrl, reqOptions); + await toggleMockEndpoint(false); const resJson = await response.json(); if (response.ok) return resJson; throw new InternalServerError('Both real and mock endpoints FAILED'); From 25b590b71e83199c3ed899d2e3bf12156da03a83 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Thu, 13 Jun 2024 09:03:16 -0700 Subject: [PATCH 007/137] got rid of second promise wait --- src/utils/hibp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/hibp.js b/src/utils/hibp.js index ecb294931d4..35a569bc24a 100644 --- a/src/utils/hibp.js +++ b/src/utils/hibp.js @@ -87,7 +87,7 @@ async function _throttledFetch (url, reqOptions, tryCount = 1) { console.error('_throttledFetch - attempting to switch to mock endpoint', { err }) await toggleMockEndpoint(true); const response = await fetch(mockUrl, reqOptions); - await toggleMockEndpoint(false); + toggleMockEndpoint(false); const resJson = await response.json(); if (response.ok) return resJson; throw new InternalServerError('Both real and mock endpoints FAILED'); From ab680afadce9e3eb6265e8fc3e1504d5414d7ea7 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Mon, 17 Jun 2024 14:22:35 -0700 Subject: [PATCH 008/137] removed other features, kept only the mock endpoint --- .env-dist | 2 - src/app/api/mock/hibp/breaches/route.ts | 45 ++++++++++++ .../api/mock/hibp/data/fakeAllBreaches.json | 52 ++++++++++++++ src/app/api/mock/hibp/mockConfigure/route.ts | 72 ------------------- src/utils/hibp.js | 32 +-------- 5 files changed, 99 insertions(+), 104 deletions(-) create mode 100644 src/app/api/mock/hibp/breaches/route.ts create mode 100644 src/app/api/mock/hibp/data/fakeAllBreaches.json delete mode 100644 src/app/api/mock/hibp/mockConfigure/route.ts diff --git a/.env-dist b/.env-dist index ca60b0f451c..7757f312ddd 100755 --- a/.env-dist +++ b/.env-dist @@ -55,8 +55,6 @@ OAUTH_API_URI="https://api-accounts.stage.mozaws.net/v1" HIBP_RELOAD_BREACHES_TIMER=600 # HIBP API for range search and subscription HIBP_KANON_API_ROOT=https://api.haveibeenpwned.com -HIBP_KANON_API_ROOT_TRUE=https://api.haveibeenpwned.com -HIBP_KANON_API_SUFFIX_FAKE=/api/mock/hibp HIBP_KANON_API_TOKEN= HIBP_API_ROOT=https://haveibeenpwned.com/api/v2 HIBP_API_TOKEN= diff --git a/src/app/api/mock/hibp/breaches/route.ts b/src/app/api/mock/hibp/breaches/route.ts new file mode 100644 index 00000000000..5c4230b773f --- /dev/null +++ b/src/app/api/mock/hibp/breaches/route.ts @@ -0,0 +1,45 @@ +/* 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 { logger } from "../../../../functions/server/logging"; +import fakeAllBreaches from "../data/fakeAllBreaches.json"; + +type BreachesListResponse = { + Id: number; + Name: string; + Title: string; + Domain: string; + BreachDate: string; + AddedDate: string; + ModifiedDate: string; + PwnCount: number; + Description: string; + LogoPath: string; + DataClasses: string[]; + IsVerified: boolean; + IsFabricated: boolean; + IsSensitive: boolean; + IsRetired: boolean; + IsSpamList: boolean; + IsMalware: boolean; + FaviconUrl: string | null; +}[]; + +export function GET() { + const { APP_ENV } = process.env; + + // Check if APP_ENV is set to production + if (APP_ENV === "production") { + return NextResponse.json( + { error: "Endpoint not available in production environment" }, + { status: 403 }, + ); + } + logger.info("Mock endpoint: /breaches"); + + const data = fakeAllBreaches.data as BreachesListResponse; + + return NextResponse.json(data); +} diff --git a/src/app/api/mock/hibp/data/fakeAllBreaches.json b/src/app/api/mock/hibp/data/fakeAllBreaches.json new file mode 100644 index 00000000000..42524b86c5a --- /dev/null +++ b/src/app/api/mock/hibp/data/fakeAllBreaches.json @@ -0,0 +1,52 @@ +{ + "data": [ + { + "Id": 1, + "Name": "000webhost", + "Title": "000webhost", + "Domain": "000webhost.com", + "BreachDate": "2015-03-01T08:00:00.000Z", + "AddedDate": "2015-10-26T23:35:45.000Z", + "ModifiedDate": "2017-12-10T21:44:27.000Z", + "PwnCount": 14936670, + "Description": "In approximately March 2015, the free web hosting provider 000webhost suffered a major data breach that exposed almost 15 million customer records. The data was sold and traded before 000webhost was alerted in October. The breach included names, email addresses and plain text passwords.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/000webhost.png", + "DataClasses": ["email-addresses", "ip-addresses", "names", "passwords"], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 2, + "Name": "123RF", + "Title": "123RF", + "Domain": "123rf.com", + "BreachDate": "2020-03-22T07:00:00.000Z", + "AddedDate": "2020-11-15T00:59:50.000Z", + "ModifiedDate": "2020-11-15T01:07:10.000Z", + "PwnCount": 8661578, + "Description": "In March 2020, the stock photo site 123RF suffered a data breach which impacted over 8 million subscribers and was subsequently sold online. The breach included email, IP and physical addresses, names, phone numbers and passwords stored as MD5 hashes. The data was provided to HIBP by dehashed.com.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/123RF.png", + "DataClasses": [ + "email-addresses", + "ip-addresses", + "names", + "passwords", + "phone-numbers", + "physical-addresses", + "usernames" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + } + ] +} diff --git a/src/app/api/mock/hibp/mockConfigure/route.ts b/src/app/api/mock/hibp/mockConfigure/route.ts deleted file mode 100644 index 56e088c2011..00000000000 --- a/src/app/api/mock/hibp/mockConfigure/route.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* 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 { NextRequest, NextResponse } from "next/server"; -import { logger } from "../../../../functions/server/logging"; - -export function GET() { - const { APP_ENV, HIBP_KANON_API_ROOT, HIBP_KANON_API_ROOT_TRUE } = - process.env; - if (APP_ENV === "production") { - logger.info("Attempt to access environment variable HIBP_KANON_API_ROOT"); - return NextResponse.json( - { error: "Endpoint not available in production environment" }, - { status: 403 }, - ); - } - return NextResponse.json({ - message: `HIBP endpoint is ${HIBP_KANON_API_ROOT === HIBP_KANON_API_ROOT_TRUE ? "REAL" : "FAKE"}`, - }); -} - -export async function PUT(req: NextRequest) { - //TODO: make new environemnt variable to use for real vs mock endpoint - - const { - APP_ENV, - HIBP_KANON_API_ROOT_TRUE, - HIBP_KANON_API_SUFFIX_FAKE, - SERVER_URL, - } = process.env; - - if (HIBP_KANON_API_SUFFIX_FAKE === undefined || SERVER_URL === undefined) { - return NextResponse.json( - { - error: - "Server environment not configured correctly: suffix_fake or server_url is undefined", - }, - { status: 500 }, - ); - } - - const reqJson = await req.json(); - const useMock = reqJson.useMock; - - // Check if APP_ENV is set to production - if (APP_ENV === "production") { - logger.info( - "Attempt to change environment variable HIBP_KANON_API_ROOT in production environment", - ); - return NextResponse.json( - { error: "Endpoint not available in production environment" }, - { status: 403 }, - ); - } - let msg = - "Environment variable HIBP_KANON_API_ROOT has been updated to use mock API"; - // Set the HIBP_KANON_API_ROOT environment variable - if (useMock) { - process.env.HIBP_KANON_API_ROOT = SERVER_URL + HIBP_KANON_API_SUFFIX_FAKE; - } else { - process.env.HIBP_KANON_API_ROOT = HIBP_KANON_API_ROOT_TRUE; - msg = - "Environment variable HIBP_KANON_API_ROOT has been updated to use true API"; - } - logger.info(msg); - - // Return a success response - return NextResponse.json({ - message: msg, - }); -} diff --git a/src/utils/hibp.js b/src/utils/hibp.js index 35a569bc24a..60c0b2837c9 100644 --- a/src/utils/hibp.js +++ b/src/utils/hibp.js @@ -29,21 +29,6 @@ function _addStandardOptions (options = {}) { } /* c8 ignore stop */ -/** - * @param {boolean} doUse - */ -async function toggleMockEndpoint(doUse) { - const {SERVER_URL, HIBP_KANON_API_SUFFIX_FAKE} = process.env; - const fakeEndpoint = String(SERVER_URL) + String(HIBP_KANON_API_SUFFIX_FAKE) + '/mockConfigure'; - return await fetch(fakeEndpoint, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({'useMock': doUse}) - }) -} - /** * @param {string} url * @param {any | undefined} reqOptions @@ -76,21 +61,8 @@ async function _throttledFetch (url, reqOptions, tryCount = 1) { throw new InternalServerError(`bad response: ${response.status}`) } } catch (err) { - const {SERVER_URL, HIBP_KANON_API_SUFFIX_FAKE} = process.env; - if (SERVER_URL === undefined || HIBP_KANON_API_SUFFIX_FAKE === undefined) { - throw new InternalServerError('Environemnt not configured correctly: Missing server_url or hibp_suffix_fake') - } - - const mockHost = String(SERVER_URL) + String(HIBP_KANON_API_SUFFIX_FAKE); - const mockUrl = mockHost + url.match(/\/breachedaccount.*/); - - console.error('_throttledFetch - attempting to switch to mock endpoint', { err }) - await toggleMockEndpoint(true); - const response = await fetch(mockUrl, reqOptions); - toggleMockEndpoint(false); - const resJson = await response.json(); - if (response.ok) return resJson; - throw new InternalServerError('Both real and mock endpoints FAILED'); + console.error('_throttledFetch', { err }) + throw new InternalServerError(getMessage('error-hibp-connect')) } } /* c8 ignore stop */ From 184cfcf0295db6b4c3462373b2692a41cbfc59d0 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Mon, 17 Jun 2024 14:27:41 -0700 Subject: [PATCH 009/137] made breaches list match the fake breaches for a user --- src/app/api/mock/hibp/data/fakeBreaches.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/mock/hibp/data/fakeBreaches.json b/src/app/api/mock/hibp/data/fakeBreaches.json index d4d2731dbbb..70dbadb067c 100644 --- a/src/app/api/mock/hibp/data/fakeBreaches.json +++ b/src/app/api/mock/hibp/data/fakeBreaches.json @@ -2,7 +2,7 @@ "data": [ { "hashSuffix": "", - "websites": ["Adobe", "AshleyMadison", "BTCE"] + "websites": ["000webhost", "123RF"] } ] } From dda9c226daa44910a52fb7cc16e0eb00aa4a734b Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Wed, 19 Jun 2024 17:09:21 -0700 Subject: [PATCH 010/137] file endpoint additions --- src/app/api/mock/onerep/config/config.ts | 6 ++ src/app/api/mock/onerep/data-brokers/route.ts | 4 + .../profiles/[profileId]/activate/route.ts | 4 + .../profiles/[profileId]/deactivate/route.ts | 4 + .../profiles/[profileId]/optout/route.ts | 4 + .../mock/onerep/profiles/[profileId]/route.ts | 33 +++++++ .../[profileId]/scans/[scanId]/route.ts | 4 + .../profiles/[profileId]/scans/route.ts | 4 + src/app/api/mock/onerep/profiles/route.ts | 86 +++++++++++++++++++ .../api/mock/onerep/stats/profiles/route.ts | 4 + 10 files changed, 153 insertions(+) create mode 100644 src/app/api/mock/onerep/config/config.ts create mode 100644 src/app/api/mock/onerep/data-brokers/route.ts create mode 100644 src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts create mode 100644 src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts create mode 100644 src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts create mode 100644 src/app/api/mock/onerep/profiles/[profileId]/route.ts create mode 100644 src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts create mode 100644 src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts create mode 100644 src/app/api/mock/onerep/profiles/route.ts create mode 100644 src/app/api/mock/onerep/stats/profiles/route.ts diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts new file mode 100644 index 00000000000..d8ff54ecdbf --- /dev/null +++ b/src/app/api/mock/onerep/config/config.ts @@ -0,0 +1,6 @@ +/* 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/. */ + +export const MOCK_PROFILE_ID = 777; +export const MOCK_TIME = "2024-06-19T01:37:02+0000"; diff --git a/src/app/api/mock/onerep/data-brokers/route.ts b/src/app/api/mock/onerep/data-brokers/route.ts new file mode 100644 index 00000000000..b7e0a1d5ef1 --- /dev/null +++ b/src/app/api/mock/onerep/data-brokers/route.ts @@ -0,0 +1,4 @@ +/* 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/. */ + diff --git a/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts new file mode 100644 index 00000000000..b7e0a1d5ef1 --- /dev/null +++ b/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts @@ -0,0 +1,4 @@ +/* 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/. */ + diff --git a/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts new file mode 100644 index 00000000000..b7e0a1d5ef1 --- /dev/null +++ b/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts @@ -0,0 +1,4 @@ +/* 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/. */ + diff --git a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts new file mode 100644 index 00000000000..b7e0a1d5ef1 --- /dev/null +++ b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts @@ -0,0 +1,4 @@ +/* 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/. */ + diff --git a/src/app/api/mock/onerep/profiles/[profileId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/route.ts new file mode 100644 index 00000000000..d3f92e90f31 --- /dev/null +++ b/src/app/api/mock/onerep/profiles/[profileId]/route.ts @@ -0,0 +1,33 @@ +/* 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 { NextApiRequest, NextApiResponse } from "next"; +import { ShowProfileResponse } from "../../../../../functions/server/onerep"; + +// Mocked profile data to simulate response +const mockProfileData: ShowProfileResponse = { + id: 0, + first_name: "", + last_name: "", + birth_date: "", + addresses: [{ state: "", city: "" }], + status: "", + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + url: `https://api.onerep.com/profiles/`, +}; + +// Mock endpoint to simulate fetching a profile by ID +export default function GET(req: NextApiRequest, res: NextApiResponse) { + // Extract profileId from query parameters or request body + const profileId: number = Number(req.query.profileId || req.body.profileId); + + // Simulate error if profileId is not provided or not valid + if (!profileId || isNaN(profileId)) { + res.status(400).json({ error: "Invalid profile ID" }); + return; + } + + res.status(200).json(mockProfileData); +} diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts new file mode 100644 index 00000000000..b7e0a1d5ef1 --- /dev/null +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts @@ -0,0 +1,4 @@ +/* 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/. */ + diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts new file mode 100644 index 00000000000..b7e0a1d5ef1 --- /dev/null +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -0,0 +1,4 @@ +/* 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/. */ + diff --git a/src/app/api/mock/onerep/profiles/route.ts b/src/app/api/mock/onerep/profiles/route.ts new file mode 100644 index 00000000000..2b2b219d021 --- /dev/null +++ b/src/app/api/mock/onerep/profiles/route.ts @@ -0,0 +1,86 @@ +/* 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 type { NextApiRequest, NextApiResponse } from "next"; +import { MOCK_PROFILE_ID, MOCK_TIME } from "../config/config.ts"; + +type RequestProfileData = { + first_name: string; + last_name: string; + middle_name: string; + birth_date: string; + addresses: Array<{ state: string; city: string }>; +}; + +type ResponseProfileData = { + id: number; + first_name: string; + last_name: string; + middle_name: string | null; + birth_date: string; + first_names: string[]; + middle_names: string[]; + last_names: string[]; + phone_numbers: string[]; + emails: string[]; + addresses: Array<{ + id: number; + profile_id: number; + state: string; + city: string; + address_line: string | null; + zip: string | null; + created_at: string; + updated_at: string; + url: string; + }>; + status: "active" | "inactive"; + created_at: string; + updated_at: string; + url: string; +}; + +// Mock API endpoint +export default function POST(req: NextApiRequest, res: NextApiResponse) { + //TODO: mock out all URLs + try { + const requestProfile: RequestProfileData = JSON.parse(req.body); + + // Mock response object + const responseProfile: ResponseProfileData = { + id: MOCK_PROFILE_ID, //Random Mock id + first_name: requestProfile.first_name, + last_name: requestProfile.last_name, + middle_name: requestProfile.middle_name, + birth_date: requestProfile.birth_date, + first_names: [], + middle_names: [], + last_names: [], + phone_numbers: [], + emails: [], + addresses: requestProfile.addresses.map((addr, index) => ({ + id: MOCK_PROFILE_ID + index, // Mocked IDs for addresses + profile_id: MOCK_PROFILE_ID, + state: addr.state, + city: addr.city, + address_line: null, + zip: null, + created_at: MOCK_TIME, + updated_at: MOCK_TIME, + url: `https://api.onerep.com/profiles/${MOCK_PROFILE_ID}/addresses/${MOCK_PROFILE_ID + index}`, + })), + status: "inactive", //assuming status is active + created_at: MOCK_TIME, + updated_at: MOCK_TIME, + url: `https://api.onerep.com/profiles/${MOCK_PROFILE_ID}`, + }; + + res.status(201).json(responseProfile); + } catch (error) { + console.error("Failed to process request:", error); + res + .status(400) + .json({ error: "Bad Request: Invalid JSON or incorrect data structure" }); + } +} diff --git a/src/app/api/mock/onerep/stats/profiles/route.ts b/src/app/api/mock/onerep/stats/profiles/route.ts new file mode 100644 index 00000000000..b7e0a1d5ef1 --- /dev/null +++ b/src/app/api/mock/onerep/stats/profiles/route.ts @@ -0,0 +1,4 @@ +/* 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/. */ + From 988997c80d8069af2a400ba781e4bc7341187861 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Tue, 25 Jun 2024 01:57:28 -0700 Subject: [PATCH 011/137] funtional commit - mocked accessed endpoints --- src/app/api/mock/onerep/config/config.ts | 10 +++ src/app/api/mock/onerep/config/userObject.ts | 39 ++++++++++ src/app/api/mock/onerep/data-brokers/route.ts | 6 ++ .../profiles/[profileId]/activate/route.ts | 15 ++++ .../profiles/[profileId]/deactivate/route.ts | 15 ++++ .../profiles/[profileId]/optout/route.ts | 15 ++++ .../mock/onerep/profiles/[profileId]/route.ts | 52 ++++++++----- .../[profileId]/scans/[scanId]/route.ts | 21 +++++ .../profiles/[profileId]/scans/route.ts | 67 ++++++++++++++++ src/app/api/mock/onerep/profiles/route.ts | 76 ++++++------------- src/app/api/mock/onerep/scan-results/route.ts | 65 ++++++++++++++++ .../api/mock/onerep/stats/profiles/route.ts | 16 ++++ src/app/functions/server/onerep.ts | 44 ++++++++--- src/db/tables/onerep_scans.ts | 1 + 14 files changed, 359 insertions(+), 83 deletions(-) create mode 100644 src/app/api/mock/onerep/config/userObject.ts create mode 100644 src/app/api/mock/onerep/scan-results/route.ts diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index d8ff54ecdbf..573ac048713 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -2,5 +2,15 @@ * 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 { StateAbbr } from "../../../../../utils/states"; + export const MOCK_PROFILE_ID = 777; +export const MOCK_SCAN_ID = 6972; export const MOCK_TIME = "2024-06-19T01:37:02+0000"; +export const MOCK_FIRSTNAME = "John"; +export const MOCK_LASTNAME = "Doe"; +export const MOCK_BIRTHDATE = "2000-01-01"; + +export const MOCK_ADDRESSES: [{ city: string; state: StateAbbr }] = [ + { city: "Berkeley", state: "CA" as StateAbbr }, +]; diff --git a/src/app/api/mock/onerep/config/userObject.ts b/src/app/api/mock/onerep/config/userObject.ts new file mode 100644 index 00000000000..58f284c32ea --- /dev/null +++ b/src/app/api/mock/onerep/config/userObject.ts @@ -0,0 +1,39 @@ +/* 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/. */ + +export type RequestProfileData = { + first_name: string; + last_name: string; + middle_name: string; + birth_date: string; + addresses: Array<{ state: string; city: string }>; +}; + +export type ResponseProfileData = { + id: number; + first_name: string; + last_name: string; + middle_name: string | null; + birth_date: string; + first_names: string[]; + middle_names: string[]; + last_names: string[]; + phone_numbers: string[]; + emails: string[]; + addresses: Array<{ + id: number; + profile_id: number; + state: string; + city: string; + address_line: string | null; + zip: string | null; + created_at: string; + updated_at: string; + url: string; + }>; + status: "active" | "inactive"; + created_at: string; + updated_at: string; + url: string; +}; diff --git a/src/app/api/mock/onerep/data-brokers/route.ts b/src/app/api/mock/onerep/data-brokers/route.ts index b7e0a1d5ef1..a047a740545 100644 --- a/src/app/api/mock/onerep/data-brokers/route.ts +++ b/src/app/api/mock/onerep/data-brokers/route.ts @@ -2,3 +2,9 @@ * 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"; + +//ONLY PART OF ADMIN - probably shouldn't mock? +export default function handler() { + return NextResponse.json({ error: "You've reached a mock endpoint" }); +} diff --git a/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts index b7e0a1d5ef1..dcae96793f8 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts @@ -2,3 +2,18 @@ * 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 { NextRequest, NextResponse } from "next/server"; + +export function PUT(req: NextRequest) { + const profileId: number = Number(req.url.match(/profiles\/([0-9]+)/)![1]); + + if (!profileId || isNaN(profileId)) { + return NextResponse.json({ error: "Invalid profile ID" }); + } + + //TODO: update the json file corresponding to this user + + return NextResponse.json({ + message: `Profile ${profileId} successfully activated`, + }); +} diff --git a/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts index b7e0a1d5ef1..964652c369d 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts @@ -2,3 +2,18 @@ * 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 { NextRequest, NextResponse } from "next/server"; + +export function PUT(req: NextRequest) { + const profileId: number = Number(req.url.match(/profiles\/([0-9]+)/)![1]); + + if (!profileId || isNaN(profileId)) { + return NextResponse.json({ error: "Invalid profile ID" }); + } + + //TODO: update the json file corresponding to this user + + return NextResponse.json({ + message: `Profile ${profileId} successfully deactivated`, + }); +} diff --git a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts index b7e0a1d5ef1..8a3bed0ecb6 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts @@ -2,3 +2,18 @@ * 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 { NextRequest, NextResponse } from "next/server"; + +export function PUT(req: NextRequest) { + const profileId: number = Number(req.url.match(/profiles\/([0-9]+)/)![1]); + + if (!profileId || isNaN(profileId)) { + return NextResponse.json({ error: "Invalid profile ID" }); + } + + //TODO: update the json file corresponding to this user + + return NextResponse.json({ + message: `Profile ${profileId} successfully opted out`, + }); +} diff --git a/src/app/api/mock/onerep/profiles/[profileId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/route.ts index d3f92e90f31..1eefb2c3b11 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/route.ts @@ -2,32 +2,46 @@ * 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 { NextApiRequest, NextApiResponse } from "next"; -import { ShowProfileResponse } from "../../../../../functions/server/onerep"; +import { + MOCK_TIME, + MOCK_FIRSTNAME, + MOCK_LASTNAME, + MOCK_BIRTHDATE, + MOCK_ADDRESSES, +} from "../../config/config.ts"; +import { ShowProfileResponse } from "../../../../../functions/server/onerep.ts"; +import { NextRequest, NextResponse } from "next/server"; // Mocked profile data to simulate response -const mockProfileData: ShowProfileResponse = { - id: 0, - first_name: "", - last_name: "", - birth_date: "", - addresses: [{ state: "", city: "" }], - status: "", - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - url: `https://api.onerep.com/profiles/`, -}; +//TODO: mock out the URL + +async function extractProfileId(req: NextRequest) { + const idFromBody: number = req.body !== null && (await req.json()).profileId; + if (idFromBody) return idFromBody; + const idFromUrl: number = Number(req.url.match(/profiles\/([0-9]+)/)![1]); + return idFromUrl; +} // Mock endpoint to simulate fetching a profile by ID -export default function GET(req: NextApiRequest, res: NextApiResponse) { +export async function GET(req: NextRequest) { // Extract profileId from query parameters or request body - const profileId: number = Number(req.query.profileId || req.body.profileId); + const profileId: number = await extractProfileId(req); - // Simulate error if profileId is not provided or not valid if (!profileId || isNaN(profileId)) { - res.status(400).json({ error: "Invalid profile ID" }); - return; + return NextResponse.json({ error: "Invalid profile ID" }, { status: 400 }); } - res.status(200).json(mockProfileData); + const mockProfileData: ShowProfileResponse = { + id: profileId, + first_name: MOCK_FIRSTNAME, + last_name: MOCK_LASTNAME, + birth_date: MOCK_BIRTHDATE, + addresses: MOCK_ADDRESSES, + status: "inactive", + created_at: MOCK_TIME, + updated_at: MOCK_TIME, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}`, + }; + + return NextResponse.json(mockProfileData); } diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts index b7e0a1d5ef1..7119f8e075d 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts @@ -2,3 +2,24 @@ * 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/. */ +// pages/api/profiles/[profileId]/scans/[scanId].ts + +import { MOCK_TIME } from "../../../../config/config"; +import { NextRequest, NextResponse } from "next/server"; + +export function GET(req: NextRequest) { + const profileId = Number(req.url.match(/profiles\/([0-9]+)/)![1]); + const scanId = Number(req.url.match(/scans\/([0-9]+)/)![1]); + + // Check for the availability of the scan + const responseData = { + id: scanId, + profile_id: profileId, + status: "finished", + reason: "manual", + created_at: MOCK_TIME, + updated_at: MOCK_TIME, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, + }; + return NextResponse.json(responseData); +} diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index b7e0a1d5ef1..17f0d44bb42 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -2,3 +2,70 @@ * 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 { MOCK_SCAN_ID, MOCK_TIME } from "../../../config/config"; +import { NextRequest, NextResponse } from "next/server"; + +//TODO: mock out the id field and url + +function extractProfileId(req: NextRequest) { + const idFromUrl = Number(req.url.match(/profiles\/([0-9]+)\/scans/)![1]); + return idFromUrl; +} + +export function POST(req: NextRequest) { + const profileId: number = extractProfileId(req); + if (!profileId || isNaN(profileId)) { + return NextResponse.json({ error: "Invalid profile ID" }); + } + + const mockResponse = { + id: MOCK_SCAN_ID, + profile_id: profileId, + status: "finished", + reason: "manual", + created_at: MOCK_TIME, + updated_at: MOCK_TIME, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${MOCK_SCAN_ID}`, + }; + + return NextResponse.json(mockResponse); +} + +export function GET(req: NextRequest) { + const profileId: number = extractProfileId(req); + + if (!profileId || isNaN(profileId)) { + return NextResponse.json({ error: "Invalid profile ID" }); + } + + //TODO: mock out ID here and urls + const responseData = { + data: [ + { + id: MOCK_SCAN_ID, + profile_id: profileId, + status: "finished", + reason: "manual", + created_at: MOCK_TIME, + updated_at: MOCK_TIME, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${MOCK_SCAN_ID}`, + }, + ], + links: { + first: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans?page=1`, + last: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans?page=1`, + prev: null, + next: null, + }, + meta: { + current_page: 1, + from: 1, + last_page: 1, + path: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans`, + per_page: 20, + to: 1, + total: 1, + }, + }; + return NextResponse.json(responseData); +} diff --git a/src/app/api/mock/onerep/profiles/route.ts b/src/app/api/mock/onerep/profiles/route.ts index 2b2b219d021..e60b7746660 100644 --- a/src/app/api/mock/onerep/profiles/route.ts +++ b/src/app/api/mock/onerep/profiles/route.ts @@ -2,54 +2,26 @@ * 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 type { NextApiRequest, NextApiResponse } from "next"; -import { MOCK_PROFILE_ID, MOCK_TIME } from "../config/config.ts"; - -type RequestProfileData = { - first_name: string; - last_name: string; - middle_name: string; - birth_date: string; - addresses: Array<{ state: string; city: string }>; -}; - -type ResponseProfileData = { - id: number; - first_name: string; - last_name: string; - middle_name: string | null; - birth_date: string; - first_names: string[]; - middle_names: string[]; - last_names: string[]; - phone_numbers: string[]; - emails: string[]; - addresses: Array<{ - id: number; - profile_id: number; - state: string; - city: string; - address_line: string | null; - zip: string | null; - created_at: string; - updated_at: string; - url: string; - }>; - status: "active" | "inactive"; - created_at: string; - updated_at: string; - url: string; -}; +import { MOCK_TIME } from "../config/config.ts"; +import { + ResponseProfileData, + RequestProfileData, +} from "../config/userObject.ts"; +import { NextRequest, NextResponse } from "next/server"; +import { randomInt } from "crypto"; // Mock API endpoint -export default function POST(req: NextApiRequest, res: NextApiResponse) { +export async function POST(req: NextRequest) { //TODO: mock out all URLs + const profileId = randomInt(1000, 10000); try { - const requestProfile: RequestProfileData = JSON.parse(req.body); - + if (req.body === null) { + return NextResponse.json({ error: "Invalid request - without body" }); + } + const requestProfile: RequestProfileData = await req.json(); // Mock response object const responseProfile: ResponseProfileData = { - id: MOCK_PROFILE_ID, //Random Mock id + id: profileId, first_name: requestProfile.first_name, last_name: requestProfile.last_name, middle_name: requestProfile.middle_name, @@ -60,27 +32,27 @@ export default function POST(req: NextApiRequest, res: NextApiResponse) { phone_numbers: [], emails: [], addresses: requestProfile.addresses.map((addr, index) => ({ - id: MOCK_PROFILE_ID + index, // Mocked IDs for addresses - profile_id: MOCK_PROFILE_ID, + id: profileId + index, // Mocked IDs for addresses + profile_id: profileId, state: addr.state, city: addr.city, address_line: null, zip: null, created_at: MOCK_TIME, updated_at: MOCK_TIME, - url: `https://api.onerep.com/profiles/${MOCK_PROFILE_ID}/addresses/${MOCK_PROFILE_ID + index}`, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/addresses/${profileId + index}`, })), - status: "inactive", //assuming status is active + status: "active", //assuming status is active created_at: MOCK_TIME, updated_at: MOCK_TIME, - url: `https://api.onerep.com/profiles/${MOCK_PROFILE_ID}`, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}`, }; - - res.status(201).json(responseProfile); + return NextResponse.json(responseProfile, { status: 201 }); } catch (error) { console.error("Failed to process request:", error); - res - .status(400) - .json({ error: "Bad Request: Invalid JSON or incorrect data structure" }); + return NextResponse.json( + { error: "Bad Request: Invalid JSON or incorrect data structure" }, + { status: 403 }, + ); } } diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts new file mode 100644 index 00000000000..9e2e21cba32 --- /dev/null +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -0,0 +1,65 @@ +/* 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 { + MOCK_FIRSTNAME, + MOCK_LASTNAME, + MOCK_TIME, + MOCK_PROFILE_ID, + MOCK_SCAN_ID, +} from "../config/config"; +import { NextRequest, NextResponse } from "next/server"; + +//TODO: mock out all URLS +export function GET(req: NextRequest) { + // const profileId = MOCK_PROFILE_ID + const page = req.url.match(/page=([0-9]+)/)![1] || "1"; + const perPage = req.url.match(/per_page=([0-9]+)/)![1] || "100"; + + const magicNum0 = 37680; + const magicNum1 = 23; + const howMany = 10; + + // TODO: mock put the indecies, id scan_id + const responseData = { + data: new Array(howMany).fill(null).map((_, index) => ({ + id: magicNum0 - index, + profile_id: MOCK_PROFILE_ID, + scan_id: MOCK_SCAN_ID, + status: "new", + first_name: MOCK_FIRSTNAME, + middle_name: null, + last_name: MOCK_LASTNAME, + age: null, + addresses: [], + phones: [], + emails: [], + relatives: [], + link: `https://example.com/link-to-databroker${index}`, + data_broker: `example${index}.com`, + data_broker_id: magicNum1 - index, + optout_attempts: 0, + created_at: MOCK_TIME, + updated_at: MOCK_TIME, + url: `${process.env.ONEREP_API_BASE}scan-results/${magicNum0 - index}`, + })), + links: { + first: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${MOCK_PROFILE_ID}&per_page=100&page=1`, + last: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${MOCK_PROFILE_ID}&per_page=100&page=1`, + prev: null, + next: null, + }, + meta: { + current_page: parseInt(page), + from: 1, + last_page: 1, + path: `${process.env.ONEREP_API_BASE}/scan-results`, + per_page: parseInt(perPage), + to: 10, + total: 10, + }, + }; + + return NextResponse.json(responseData); +} diff --git a/src/app/api/mock/onerep/stats/profiles/route.ts b/src/app/api/mock/onerep/stats/profiles/route.ts index b7e0a1d5ef1..ffd8f958068 100644 --- a/src/app/api/mock/onerep/stats/profiles/route.ts +++ b/src/app/api/mock/onerep/stats/profiles/route.ts @@ -2,3 +2,19 @@ * 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"; + +export function GET() { + const profileStats = { + created: 0, + deleted: 0, + activated: 0, + reactivated: 0, + deactivated: 0, + total_active: 0, + total_inactive: 0, + total: 0, + }; + + return NextResponse.json(profileStats); +} diff --git a/src/app/functions/server/onerep.ts b/src/app/functions/server/onerep.ts index be35d5379b8..db318d756db 100644 --- a/src/app/functions/server/onerep.ts +++ b/src/app/functions/server/onerep.ts @@ -38,7 +38,7 @@ export type ShowProfileResponse = CreateProfileRequest & { status: "active" | "inactive"; created_at: ISO8601DateString; updated_at: ISO8601DateString; - url: `https://api.onerep.com/profiles/${number}`; + url: `${string}/profiles/${number}`; }; export type CreateScanResponse = { id: number; @@ -142,7 +142,8 @@ export async function createProfile( }, ], }; - const response = await onerepFetch("/profiles", { + //TODO: add conditional check for '/' + const response = await onerepFetch("profiles", { method: "POST", body: JSON.stringify(requestBody), }); @@ -173,7 +174,8 @@ export async function createProfile( export async function getProfile( profileId: number, ): Promise { - const response: Response = await onerepFetch(`/profiles/${profileId}`, { + //TODO: add conditional check for '/' + const response: Response = await onerepFetch(`profiles/${profileId}`, { method: "GET", }); if (!response.ok) { @@ -190,8 +192,10 @@ export async function getProfile( } export async function activateProfile(profileId: number): Promise { + //TODO: add conditional check for '/' + const response: Response = await onerepFetch( - `/profiles/${profileId}/activate`, + `profiles/${profileId}/activate`, { method: "PUT", }, @@ -207,8 +211,10 @@ export async function activateProfile(profileId: number): Promise { } export async function deactivateProfile(profileId: number): Promise { + //TODO: add conditional check for '/' + const response: Response = await onerepFetch( - `/profiles/${profileId}/deactivate`, + `profiles/${profileId}/deactivate`, { method: "PUT", }, @@ -224,7 +230,9 @@ export async function deactivateProfile(profileId: number): Promise { } export async function optoutProfile(profileId: number): Promise { - const response = await onerepFetch(`/profiles/${profileId}/optout`, { + //TODO: add conditional check for '/' + + const response = await onerepFetch(`profiles/${profileId}/optout`, { method: "POST", }); if (!response.ok) { @@ -271,7 +279,9 @@ export async function createScan( /** * See https://docs.onerep.com/#operation/createScan */ - const response = await onerepFetch(`/profiles/${profileId}/scans`, { + //TODO: add conditional check for '/' + + const response = await onerepFetch(`profiles/${profileId}/scans`, { method: "POST", }); if (!response.ok) { @@ -296,8 +306,10 @@ export async function listScans( if (options.per_page) { queryParams.set("per_page", options.per_page.toString()); } + //TODO: add conditional check for '/' + const response: Response = await onerepFetch( - `/profiles/${profileId}/scans?` + queryParams.toString(), + `profiles/${profileId}/scans?` + queryParams.toString(), { method: "GET", }, @@ -338,8 +350,10 @@ export async function listScanResults( queryParams.append("status[]", status); }); } + //TODO: add conditional check for '/' + const response: Response = await onerepFetch( - "/scan-results/?" + queryParams.toString(), + "scan-results/?" + queryParams.toString(), { method: "GET", }, @@ -390,7 +404,9 @@ export async function getScanDetails( profileId: number, scanId: number, ): Promise { - const response = await onerepFetch(`/profiles/${profileId}/scans/${scanId}`, { + //TODO: add conditional check for '/' + + const response = await onerepFetch(`profiles/${profileId}/scans/${scanId}`, { method: "GET", }); if (!response.ok) { @@ -413,9 +429,11 @@ export async function getAllScanResults( } export async function getAllDataBrokers() { + //TODO: add conditional check for '/' + return fetchAllPages(async (page: number) => { const response = await onerepFetch( - "/data-brokers?per_page=100&page=" + page.toString(), + "data-brokers?per_page=100&page=" + page.toString(), ); const data: OneRepResponse< Array<{ @@ -467,8 +485,10 @@ export async function getProfilesStats( if (profileStatsCache.has(queryParamsString)) return profileStatsCache.get(queryParamsString); + //TODO: add conditional check for '/' + const response: Response = await onerepFetch( - `/stats/profiles?${queryParamsString}`, + `stats/profiles?${queryParamsString}`, { method: "GET", }, diff --git a/src/db/tables/onerep_scans.ts b/src/db/tables/onerep_scans.ts index dd31911fde1..26b66ef3a68 100644 --- a/src/db/tables/onerep_scans.ts +++ b/src/db/tables/onerep_scans.ts @@ -195,6 +195,7 @@ async function setOnerepScan( }) .onConflict("onerep_scan_id") .merge({ + onerep_profile_id: onerepProfileId, onerep_scan_status: onerepScanStatus, updated_at: knex.fn.now(), }); From fb2d028883feac7d6b4778bb7001b464ce01654a Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Tue, 25 Jun 2024 13:37:03 -0700 Subject: [PATCH 012/137] fixed the path overwriting issue, added responsive dynamic configuration of mock data --- src/app/api/mock/onerep/config/config.ts | 15 +++--- .../mock/onerep/profiles/[profileId]/route.ts | 22 ++++---- .../[profileId]/scans/[scanId]/route.ts | 6 +-- .../profiles/[profileId]/scans/route.ts | 18 +++---- src/app/api/mock/onerep/profiles/route.ts | 11 ++-- src/app/api/mock/onerep/scan-results/route.ts | 38 +++++++------- src/app/functions/server/onerep.ts | 52 ++++++++----------- src/db/tables/onerep_scans.ts | 8 +++ 8 files changed, 84 insertions(+), 86 deletions(-) diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index 573ac048713..ed4a3cfb041 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -4,13 +4,14 @@ import { StateAbbr } from "../../../../../utils/states"; -export const MOCK_PROFILE_ID = 777; -export const MOCK_SCAN_ID = 6972; -export const MOCK_TIME = "2024-06-19T01:37:02+0000"; -export const MOCK_FIRSTNAME = "John"; -export const MOCK_LASTNAME = "Doe"; -export const MOCK_BIRTHDATE = "2000-01-01"; +export const MOCK_ONEREP_PROFILE_ID = 777; +export const MOCK_ONEREP_SCAN_ID = 129837123; +export const MOCK_ONEREP_TIME = "2024-06-19T01:37:02+0000"; +export const MOCK_ONEREP_FIRSTNAME = "John"; +export const MOCK_ONEREP_LASTNAME = "Doe"; +export const MOCK_ONEREP_BIRTHDATE = "2000-01-01"; +export const MOCK_ONEREP_EMAIL = "JohnDoe@JohnDoe.com"; -export const MOCK_ADDRESSES: [{ city: string; state: StateAbbr }] = [ +export const MOCK_ONEREP_ADDRESSES: [{ city: string; state: StateAbbr }] = [ { city: "Berkeley", state: "CA" as StateAbbr }, ]; diff --git a/src/app/api/mock/onerep/profiles/[profileId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/route.ts index 1eefb2c3b11..aa3641f3a6a 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/route.ts @@ -3,11 +3,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { - MOCK_TIME, - MOCK_FIRSTNAME, - MOCK_LASTNAME, - MOCK_BIRTHDATE, - MOCK_ADDRESSES, + MOCK_ONEREP_TIME, + MOCK_ONEREP_FIRSTNAME, + MOCK_ONEREP_LASTNAME, + MOCK_ONEREP_BIRTHDATE, + MOCK_ONEREP_ADDRESSES, } from "../../config/config.ts"; import { ShowProfileResponse } from "../../../../../functions/server/onerep.ts"; import { NextRequest, NextResponse } from "next/server"; @@ -33,13 +33,13 @@ export async function GET(req: NextRequest) { const mockProfileData: ShowProfileResponse = { id: profileId, - first_name: MOCK_FIRSTNAME, - last_name: MOCK_LASTNAME, - birth_date: MOCK_BIRTHDATE, - addresses: MOCK_ADDRESSES, + first_name: MOCK_ONEREP_FIRSTNAME, + last_name: MOCK_ONEREP_LASTNAME, + birth_date: MOCK_ONEREP_BIRTHDATE, + addresses: MOCK_ONEREP_ADDRESSES, status: "inactive", - created_at: MOCK_TIME, - updated_at: MOCK_TIME, + created_at: MOCK_ONEREP_TIME, + updated_at: MOCK_ONEREP_TIME, url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}`, }; diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts index 7119f8e075d..cd122e41556 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts @@ -4,7 +4,7 @@ // pages/api/profiles/[profileId]/scans/[scanId].ts -import { MOCK_TIME } from "../../../../config/config"; +import { MOCK_ONEREP_TIME } from "../../../../config/config"; import { NextRequest, NextResponse } from "next/server"; export function GET(req: NextRequest) { @@ -17,8 +17,8 @@ export function GET(req: NextRequest) { profile_id: profileId, status: "finished", reason: "manual", - created_at: MOCK_TIME, - updated_at: MOCK_TIME, + created_at: MOCK_ONEREP_TIME, + updated_at: MOCK_ONEREP_TIME, url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, }; return NextResponse.json(responseData); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index 17f0d44bb42..7c799c3acef 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -2,7 +2,7 @@ * 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 { MOCK_SCAN_ID, MOCK_TIME } from "../../../config/config"; +import { MOCK_ONEREP_SCAN_ID, MOCK_ONEREP_TIME } from "../../../config/config"; import { NextRequest, NextResponse } from "next/server"; //TODO: mock out the id field and url @@ -19,13 +19,13 @@ export function POST(req: NextRequest) { } const mockResponse = { - id: MOCK_SCAN_ID, + id: MOCK_ONEREP_SCAN_ID, profile_id: profileId, status: "finished", reason: "manual", - created_at: MOCK_TIME, - updated_at: MOCK_TIME, - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${MOCK_SCAN_ID}`, + created_at: MOCK_ONEREP_TIME, + updated_at: MOCK_ONEREP_TIME, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${MOCK_ONEREP_SCAN_ID}`, }; return NextResponse.json(mockResponse); @@ -42,13 +42,13 @@ export function GET(req: NextRequest) { const responseData = { data: [ { - id: MOCK_SCAN_ID, + id: MOCK_ONEREP_SCAN_ID, profile_id: profileId, status: "finished", reason: "manual", - created_at: MOCK_TIME, - updated_at: MOCK_TIME, - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${MOCK_SCAN_ID}`, + created_at: MOCK_ONEREP_TIME, + updated_at: MOCK_ONEREP_TIME, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${MOCK_ONEREP_SCAN_ID}`, }, ], links: { diff --git a/src/app/api/mock/onerep/profiles/route.ts b/src/app/api/mock/onerep/profiles/route.ts index e60b7746660..2ddc00fd31d 100644 --- a/src/app/api/mock/onerep/profiles/route.ts +++ b/src/app/api/mock/onerep/profiles/route.ts @@ -2,7 +2,7 @@ * 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 { MOCK_TIME } from "../config/config.ts"; +import { MOCK_ONEREP_TIME } from "../config/config.ts"; import { ResponseProfileData, RequestProfileData, @@ -12,7 +12,6 @@ import { randomInt } from "crypto"; // Mock API endpoint export async function POST(req: NextRequest) { - //TODO: mock out all URLs const profileId = randomInt(1000, 10000); try { if (req.body === null) { @@ -38,13 +37,13 @@ export async function POST(req: NextRequest) { city: addr.city, address_line: null, zip: null, - created_at: MOCK_TIME, - updated_at: MOCK_TIME, + created_at: MOCK_ONEREP_TIME, + updated_at: MOCK_ONEREP_TIME, url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/addresses/${profileId + index}`, })), status: "active", //assuming status is active - created_at: MOCK_TIME, - updated_at: MOCK_TIME, + created_at: MOCK_ONEREP_TIME, + updated_at: MOCK_ONEREP_TIME, url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}`, }; return NextResponse.json(responseProfile, { status: 201 }); diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts index 9e2e21cba32..d8c7938fcb8 100644 --- a/src/app/api/mock/onerep/scan-results/route.ts +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -3,50 +3,50 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { - MOCK_FIRSTNAME, - MOCK_LASTNAME, - MOCK_TIME, - MOCK_PROFILE_ID, - MOCK_SCAN_ID, + MOCK_ONEREP_FIRSTNAME, + MOCK_ONEREP_LASTNAME, + MOCK_ONEREP_TIME, + MOCK_ONEREP_SCAN_ID, + MOCK_ONEREP_EMAIL, } from "../config/config"; import { NextRequest, NextResponse } from "next/server"; -//TODO: mock out all URLS export function GET(req: NextRequest) { - // const profileId = MOCK_PROFILE_ID const page = req.url.match(/page=([0-9]+)/)![1] || "1"; const perPage = req.url.match(/per_page=([0-9]+)/)![1] || "100"; + const profileId = req.url.match(/profile_id......=([0-9]+)&/)![1]; + const magicNum0 = 37680; const magicNum1 = 23; - const howMany = 10; + const howMany = 5; // TODO: mock put the indecies, id scan_id const responseData = { data: new Array(howMany).fill(null).map((_, index) => ({ id: magicNum0 - index, - profile_id: MOCK_PROFILE_ID, - scan_id: MOCK_SCAN_ID, + profile_id: profileId, + scan_id: MOCK_ONEREP_SCAN_ID, status: "new", - first_name: MOCK_FIRSTNAME, + first_name: MOCK_ONEREP_FIRSTNAME, middle_name: null, - last_name: MOCK_LASTNAME, + last_name: MOCK_ONEREP_LASTNAME, age: null, addresses: [], phones: [], - emails: [], + emails: [MOCK_ONEREP_EMAIL], relatives: [], - link: `https://example.com/link-to-databroker${index}`, - data_broker: `example${index}.com`, + link: `https://mockexample.com/link-to-databroker${index}`, + data_broker: `mockexample${index}.com`, data_broker_id: magicNum1 - index, optout_attempts: 0, - created_at: MOCK_TIME, - updated_at: MOCK_TIME, + created_at: MOCK_ONEREP_TIME, + updated_at: MOCK_ONEREP_TIME, url: `${process.env.ONEREP_API_BASE}scan-results/${magicNum0 - index}`, })), links: { - first: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${MOCK_PROFILE_ID}&per_page=100&page=1`, - last: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${MOCK_PROFILE_ID}&per_page=100&page=1`, + first: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, + last: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, prev: null, next: null, }, diff --git a/src/app/functions/server/onerep.ts b/src/app/functions/server/onerep.ts index db318d756db..af7fa53b86a 100644 --- a/src/app/functions/server/onerep.ts +++ b/src/app/functions/server/onerep.ts @@ -118,6 +118,16 @@ async function onerepFetch( if (!onerepApiKey) { throw new Error("ONEREP_API_KEY env var not set"); } + + //If mock, remove the first slash so that it doesn't overwrite the path + if ( + onerepApiBase.includes("localhost") && + path.length > 1 && + path[0] === "/" + ) { + path = path.substring(1); + } + const url = new URL(path, onerepApiBase); const headers = new Headers(options.headers); headers.set("Authorization", `Bearer ${onerepApiKey}`); @@ -142,8 +152,7 @@ export async function createProfile( }, ], }; - //TODO: add conditional check for '/' - const response = await onerepFetch("profiles", { + const response = await onerepFetch("/profiles", { method: "POST", body: JSON.stringify(requestBody), }); @@ -174,8 +183,7 @@ export async function createProfile( export async function getProfile( profileId: number, ): Promise { - //TODO: add conditional check for '/' - const response: Response = await onerepFetch(`profiles/${profileId}`, { + const response: Response = await onerepFetch(`/profiles/${profileId}`, { method: "GET", }); if (!response.ok) { @@ -192,10 +200,8 @@ export async function getProfile( } export async function activateProfile(profileId: number): Promise { - //TODO: add conditional check for '/' - const response: Response = await onerepFetch( - `profiles/${profileId}/activate`, + `/profiles/${profileId}/activate`, { method: "PUT", }, @@ -211,10 +217,8 @@ export async function activateProfile(profileId: number): Promise { } export async function deactivateProfile(profileId: number): Promise { - //TODO: add conditional check for '/' - const response: Response = await onerepFetch( - `profiles/${profileId}/deactivate`, + `/profiles/${profileId}/deactivate`, { method: "PUT", }, @@ -230,9 +234,7 @@ export async function deactivateProfile(profileId: number): Promise { } export async function optoutProfile(profileId: number): Promise { - //TODO: add conditional check for '/' - - const response = await onerepFetch(`profiles/${profileId}/optout`, { + const response = await onerepFetch(`/profiles/${profileId}/optout`, { method: "POST", }); if (!response.ok) { @@ -279,9 +281,7 @@ export async function createScan( /** * See https://docs.onerep.com/#operation/createScan */ - //TODO: add conditional check for '/' - - const response = await onerepFetch(`profiles/${profileId}/scans`, { + const response = await onerepFetch(`/profiles/${profileId}/scans`, { method: "POST", }); if (!response.ok) { @@ -306,10 +306,8 @@ export async function listScans( if (options.per_page) { queryParams.set("per_page", options.per_page.toString()); } - //TODO: add conditional check for '/' - const response: Response = await onerepFetch( - `profiles/${profileId}/scans?` + queryParams.toString(), + `/profiles/${profileId}/scans?` + queryParams.toString(), { method: "GET", }, @@ -350,10 +348,8 @@ export async function listScanResults( queryParams.append("status[]", status); }); } - //TODO: add conditional check for '/' - const response: Response = await onerepFetch( - "scan-results/?" + queryParams.toString(), + "/scan-results/?" + queryParams.toString(), { method: "GET", }, @@ -404,9 +400,7 @@ export async function getScanDetails( profileId: number, scanId: number, ): Promise { - //TODO: add conditional check for '/' - - const response = await onerepFetch(`profiles/${profileId}/scans/${scanId}`, { + const response = await onerepFetch(`/profiles/${profileId}/scans/${scanId}`, { method: "GET", }); if (!response.ok) { @@ -429,11 +423,9 @@ export async function getAllScanResults( } export async function getAllDataBrokers() { - //TODO: add conditional check for '/' - return fetchAllPages(async (page: number) => { const response = await onerepFetch( - "data-brokers?per_page=100&page=" + page.toString(), + "/data-brokers?per_page=100&page=" + page.toString(), ); const data: OneRepResponse< Array<{ @@ -485,10 +477,8 @@ export async function getProfilesStats( if (profileStatsCache.has(queryParamsString)) return profileStatsCache.get(queryParamsString); - //TODO: add conditional check for '/' - const response: Response = await onerepFetch( - `stats/profiles?${queryParamsString}`, + `/stats/profiles?${queryParamsString}`, { method: "GET", }, diff --git a/src/db/tables/onerep_scans.ts b/src/db/tables/onerep_scans.ts index 26b66ef3a68..d385567bf82 100644 --- a/src/db/tables/onerep_scans.ts +++ b/src/db/tables/onerep_scans.ts @@ -4,6 +4,7 @@ import createDbConnection from "../connect.js"; import { logger } from "../../app/functions/server/logging"; +import { MOCK_ONEREP_SCAN_ID } from "../../app/api/mock/onerep/config/config.ts"; import { ScanResult, @@ -240,6 +241,13 @@ async function addOnerepScanResults( }); if (scanResultsMap.length > 0) { + //Delete previous records to allow dynamic mock data configuration. + if (process.env.ONEREP_API_BASE!.includes("localhost")) { + await knex("onerep_scan_results") + .where("onerep_scan_id", MOCK_ONEREP_SCAN_ID) + .del(); + } + await knex("onerep_scan_results") .insert(scanResultsMap) .onConflict("onerep_scan_result_id") From 2a4e5045901e1c998d35cf8ab771476e550c3070 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Tue, 25 Jun 2024 23:33:33 -0700 Subject: [PATCH 013/137] moved mock user data into a separate file --- src/app/api/mock/onerep/config/config.ts | 48 +++++++++++++----- src/app/api/mock/onerep/config/mockUser.json | 12 +++++ src/app/api/mock/onerep/config/userObject.ts | 39 --------------- src/app/api/mock/onerep/data-brokers/route.ts | 14 +++++- .../mock/onerep/profiles/[profileId]/route.ts | 12 ++--- src/app/api/mock/onerep/profiles/route.ts | 49 +++++++++++++++---- 6 files changed, 108 insertions(+), 66 deletions(-) create mode 100644 src/app/api/mock/onerep/config/mockUser.json delete mode 100644 src/app/api/mock/onerep/config/userObject.ts diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index ed4a3cfb041..e0a718efab0 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -3,15 +3,41 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { StateAbbr } from "../../../../../utils/states"; +import MockUser from "./mockUser.json"; -export const MOCK_ONEREP_PROFILE_ID = 777; -export const MOCK_ONEREP_SCAN_ID = 129837123; -export const MOCK_ONEREP_TIME = "2024-06-19T01:37:02+0000"; -export const MOCK_ONEREP_FIRSTNAME = "John"; -export const MOCK_ONEREP_LASTNAME = "Doe"; -export const MOCK_ONEREP_BIRTHDATE = "2000-01-01"; -export const MOCK_ONEREP_EMAIL = "JohnDoe@JohnDoe.com"; - -export const MOCK_ONEREP_ADDRESSES: [{ city: string; state: StateAbbr }] = [ - { city: "Berkeley", state: "CA" as StateAbbr }, -]; +export function MOCK_ONEREP_PROFILE_ID() { + return MockUser.PROFILE_ID; +} + +export function MOCK_ONEREP_SCAN_ID() { + return MockUser.SCAN_ID; +} + +export function MOCK_ONEREP_TIME() { + return MockUser.TIME; +} + +export function MOCK_ONEREP_FIRSTNAME() { + return MockUser.FIRSTNAME; +} + +export function MOCK_ONEREP_LASTNAME() { + return MockUser.LASTNAME; +} + +export function MOCK_ONEREP_BIRTHDATE() { + return MockUser.BIRTHDATE; +} + +export function MOCK_ONEREP_EMAIL() { + return MockUser.EMAIL; +} + +export function MOCK_ONEREP_ADDRESSES() { + type typeOfAddr = [{ city: string; state: StateAbbr }]; + + return MockUser.ADDRESSES.map((address) => ({ + city: address.city, + state: address.state as StateAbbr, + })) as typeOfAddr; +} diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json new file mode 100644 index 00000000000..d835457579b --- /dev/null +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -0,0 +1,12 @@ +{ + "PROFILE_ID": 777, + "SCAN_ID": 129837123, + "TIME": "2024-06-19T01:37:02+0000", + "FIRSTNAME": "John", + "LASTNAME": "Doe", + "BIRTHDATE": "2000-01-01", + "EMAIL": "JohnDoe@JohnDoe.com", + "ADDRESSES": [ + { "city": "Berkeley", "state": "CA" } + ] +} diff --git a/src/app/api/mock/onerep/config/userObject.ts b/src/app/api/mock/onerep/config/userObject.ts deleted file mode 100644 index 58f284c32ea..00000000000 --- a/src/app/api/mock/onerep/config/userObject.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* 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/. */ - -export type RequestProfileData = { - first_name: string; - last_name: string; - middle_name: string; - birth_date: string; - addresses: Array<{ state: string; city: string }>; -}; - -export type ResponseProfileData = { - id: number; - first_name: string; - last_name: string; - middle_name: string | null; - birth_date: string; - first_names: string[]; - middle_names: string[]; - last_names: string[]; - phone_numbers: string[]; - emails: string[]; - addresses: Array<{ - id: number; - profile_id: number; - state: string; - city: string; - address_line: string | null; - zip: string | null; - created_at: string; - updated_at: string; - url: string; - }>; - status: "active" | "inactive"; - created_at: string; - updated_at: string; - url: string; -}; diff --git a/src/app/api/mock/onerep/data-brokers/route.ts b/src/app/api/mock/onerep/data-brokers/route.ts index a047a740545..7adced07d32 100644 --- a/src/app/api/mock/onerep/data-brokers/route.ts +++ b/src/app/api/mock/onerep/data-brokers/route.ts @@ -5,6 +5,18 @@ import { NextResponse } from "next/server"; //ONLY PART OF ADMIN - probably shouldn't mock? -export default function handler() { +export function GET() { + return NextResponse.json({ error: "You've reached a mock endpoint" }); +} + +export function POST() { + return NextResponse.json({ error: "You've reached a mock endpoint" }); +} + +export function PUT() { + return NextResponse.json({ error: "You've reached a mock endpoint" }); +} + +export function DELETE() { return NextResponse.json({ error: "You've reached a mock endpoint" }); } diff --git a/src/app/api/mock/onerep/profiles/[profileId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/route.ts index aa3641f3a6a..48a501142b1 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/route.ts @@ -33,13 +33,13 @@ export async function GET(req: NextRequest) { const mockProfileData: ShowProfileResponse = { id: profileId, - first_name: MOCK_ONEREP_FIRSTNAME, - last_name: MOCK_ONEREP_LASTNAME, - birth_date: MOCK_ONEREP_BIRTHDATE, - addresses: MOCK_ONEREP_ADDRESSES, + first_name: MOCK_ONEREP_FIRSTNAME(), + last_name: MOCK_ONEREP_LASTNAME(), + birth_date: MOCK_ONEREP_BIRTHDATE(), + addresses: MOCK_ONEREP_ADDRESSES(), status: "inactive", - created_at: MOCK_ONEREP_TIME, - updated_at: MOCK_ONEREP_TIME, + created_at: MOCK_ONEREP_TIME(), + updated_at: MOCK_ONEREP_TIME(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}`, }; diff --git a/src/app/api/mock/onerep/profiles/route.ts b/src/app/api/mock/onerep/profiles/route.ts index 2ddc00fd31d..02d61906c56 100644 --- a/src/app/api/mock/onerep/profiles/route.ts +++ b/src/app/api/mock/onerep/profiles/route.ts @@ -3,14 +3,45 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { MOCK_ONEREP_TIME } from "../config/config.ts"; -import { - ResponseProfileData, - RequestProfileData, -} from "../config/userObject.ts"; import { NextRequest, NextResponse } from "next/server"; import { randomInt } from "crypto"; -// Mock API endpoint +export type RequestProfileData = { + first_name: string; + last_name: string; + middle_name: string; + birth_date: string; + addresses: Array<{ state: string; city: string }>; +}; + +type ResponseProfileData = { + id: number; + first_name: string; + last_name: string; + middle_name: string | null; + birth_date: string; + first_names: string[]; + middle_names: string[]; + last_names: string[]; + phone_numbers: string[]; + emails: string[]; + addresses: Array<{ + id: number; + profile_id: number; + state: string; + city: string; + address_line: string | null; + zip: string | null; + created_at: string; + updated_at: string; + url: string; + }>; + status: "active" | "inactive"; + created_at: string; + updated_at: string; + url: string; +}; + export async function POST(req: NextRequest) { const profileId = randomInt(1000, 10000); try { @@ -37,13 +68,13 @@ export async function POST(req: NextRequest) { city: addr.city, address_line: null, zip: null, - created_at: MOCK_ONEREP_TIME, - updated_at: MOCK_ONEREP_TIME, + created_at: MOCK_ONEREP_TIME(), + updated_at: MOCK_ONEREP_TIME(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/addresses/${profileId + index}`, })), status: "active", //assuming status is active - created_at: MOCK_ONEREP_TIME, - updated_at: MOCK_ONEREP_TIME, + created_at: MOCK_ONEREP_TIME(), + updated_at: MOCK_ONEREP_TIME(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}`, }; return NextResponse.json(responseProfile, { status: 201 }); From 0b1d9e5971dc523b6ff7c63c59ea69b59c5c4bae Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Tue, 25 Jun 2024 23:48:23 -0700 Subject: [PATCH 014/137] fixed some code quality and substitued constants with function calls --- src/app/api/mock/onerep/config/config.ts | 4 ++++ src/app/api/mock/onerep/config/mockUser.json | 5 ++--- .../mock/onerep/profiles/[profileId]/route.ts | 3 ++- .../profiles/[profileId]/scans/[scanId]/route.ts | 4 ++-- .../onerep/profiles/[profileId]/scans/route.ts | 16 ++++++++-------- src/app/api/mock/onerep/profiles/route.ts | 4 ++-- src/app/api/mock/onerep/scan-results/route.ts | 14 +++++++------- src/db/tables/onerep_scans.ts | 2 +- 8 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index e0a718efab0..987bb110534 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -33,6 +33,10 @@ export function MOCK_ONEREP_EMAIL() { return MockUser.EMAIL; } +export function MOCK_ONEREP_STATUS() { + return MockUser.STATUS as "active" | "inactive"; +} + export function MOCK_ONEREP_ADDRESSES() { type typeOfAddr = [{ city: string; state: StateAbbr }]; diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index d835457579b..d7705432b5d 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -6,7 +6,6 @@ "LASTNAME": "Doe", "BIRTHDATE": "2000-01-01", "EMAIL": "JohnDoe@JohnDoe.com", - "ADDRESSES": [ - { "city": "Berkeley", "state": "CA" } - ] + "STATUS": "active", + "ADDRESSES": [{ "city": "Berkeley", "state": "CA" }] } diff --git a/src/app/api/mock/onerep/profiles/[profileId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/route.ts index 48a501142b1..ea28493e2ba 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/route.ts @@ -8,6 +8,7 @@ import { MOCK_ONEREP_LASTNAME, MOCK_ONEREP_BIRTHDATE, MOCK_ONEREP_ADDRESSES, + MOCK_ONEREP_STATUS, } from "../../config/config.ts"; import { ShowProfileResponse } from "../../../../../functions/server/onerep.ts"; import { NextRequest, NextResponse } from "next/server"; @@ -37,7 +38,7 @@ export async function GET(req: NextRequest) { last_name: MOCK_ONEREP_LASTNAME(), birth_date: MOCK_ONEREP_BIRTHDATE(), addresses: MOCK_ONEREP_ADDRESSES(), - status: "inactive", + status: MOCK_ONEREP_STATUS(), created_at: MOCK_ONEREP_TIME(), updated_at: MOCK_ONEREP_TIME(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}`, diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts index cd122e41556..14d56c3b2ae 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts @@ -17,8 +17,8 @@ export function GET(req: NextRequest) { profile_id: profileId, status: "finished", reason: "manual", - created_at: MOCK_ONEREP_TIME, - updated_at: MOCK_ONEREP_TIME, + created_at: MOCK_ONEREP_TIME(), + updated_at: MOCK_ONEREP_TIME(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, }; return NextResponse.json(responseData); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index 7c799c3acef..c5c2a934b39 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -19,13 +19,13 @@ export function POST(req: NextRequest) { } const mockResponse = { - id: MOCK_ONEREP_SCAN_ID, + id: MOCK_ONEREP_SCAN_ID(), profile_id: profileId, status: "finished", reason: "manual", - created_at: MOCK_ONEREP_TIME, - updated_at: MOCK_ONEREP_TIME, - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${MOCK_ONEREP_SCAN_ID}`, + created_at: MOCK_ONEREP_TIME(), + updated_at: MOCK_ONEREP_TIME(), + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${MOCK_ONEREP_SCAN_ID()}`, }; return NextResponse.json(mockResponse); @@ -42,13 +42,13 @@ export function GET(req: NextRequest) { const responseData = { data: [ { - id: MOCK_ONEREP_SCAN_ID, + id: MOCK_ONEREP_SCAN_ID(), profile_id: profileId, status: "finished", reason: "manual", - created_at: MOCK_ONEREP_TIME, - updated_at: MOCK_ONEREP_TIME, - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${MOCK_ONEREP_SCAN_ID}`, + created_at: MOCK_ONEREP_TIME(), + updated_at: MOCK_ONEREP_TIME(), + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${MOCK_ONEREP_SCAN_ID()}`, }, ], links: { diff --git a/src/app/api/mock/onerep/profiles/route.ts b/src/app/api/mock/onerep/profiles/route.ts index 02d61906c56..a5180d11320 100644 --- a/src/app/api/mock/onerep/profiles/route.ts +++ b/src/app/api/mock/onerep/profiles/route.ts @@ -2,7 +2,7 @@ * 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 { MOCK_ONEREP_TIME } from "../config/config.ts"; +import { MOCK_ONEREP_STATUS, MOCK_ONEREP_TIME } from "../config/config.ts"; import { NextRequest, NextResponse } from "next/server"; import { randomInt } from "crypto"; @@ -72,7 +72,7 @@ export async function POST(req: NextRequest) { updated_at: MOCK_ONEREP_TIME(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/addresses/${profileId + index}`, })), - status: "active", //assuming status is active + status: MOCK_ONEREP_STATUS(), created_at: MOCK_ONEREP_TIME(), updated_at: MOCK_ONEREP_TIME(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}`, diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts index d8c7938fcb8..9655b1f32e3 100644 --- a/src/app/api/mock/onerep/scan-results/route.ts +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -19,29 +19,29 @@ export function GET(req: NextRequest) { const magicNum0 = 37680; const magicNum1 = 23; - const howMany = 5; + const howMany = 10; // TODO: mock put the indecies, id scan_id const responseData = { data: new Array(howMany).fill(null).map((_, index) => ({ id: magicNum0 - index, profile_id: profileId, - scan_id: MOCK_ONEREP_SCAN_ID, + scan_id: MOCK_ONEREP_SCAN_ID(), status: "new", - first_name: MOCK_ONEREP_FIRSTNAME, + first_name: MOCK_ONEREP_FIRSTNAME(), middle_name: null, - last_name: MOCK_ONEREP_LASTNAME, + last_name: MOCK_ONEREP_LASTNAME(), age: null, addresses: [], phones: [], - emails: [MOCK_ONEREP_EMAIL], + emails: [MOCK_ONEREP_EMAIL()], relatives: [], link: `https://mockexample.com/link-to-databroker${index}`, data_broker: `mockexample${index}.com`, data_broker_id: magicNum1 - index, optout_attempts: 0, - created_at: MOCK_ONEREP_TIME, - updated_at: MOCK_ONEREP_TIME, + created_at: MOCK_ONEREP_TIME(), + updated_at: MOCK_ONEREP_TIME(), url: `${process.env.ONEREP_API_BASE}scan-results/${magicNum0 - index}`, })), links: { diff --git a/src/db/tables/onerep_scans.ts b/src/db/tables/onerep_scans.ts index d385567bf82..0e61241f126 100644 --- a/src/db/tables/onerep_scans.ts +++ b/src/db/tables/onerep_scans.ts @@ -244,7 +244,7 @@ async function addOnerepScanResults( //Delete previous records to allow dynamic mock data configuration. if (process.env.ONEREP_API_BASE!.includes("localhost")) { await knex("onerep_scan_results") - .where("onerep_scan_id", MOCK_ONEREP_SCAN_ID) + .where("onerep_scan_id", MOCK_ONEREP_SCAN_ID()) .del(); } From 29b6f0e5830f8937a8e7186db5c5a2966dff13e4 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Wed, 26 Jun 2024 02:48:09 -0700 Subject: [PATCH 015/137] Allowed for data to be configured dynamically using an endpoint --- src/app/api/mock/onerep/config/config.ts | 103 ++++++++++++++++++ src/app/api/mock/onerep/config/mockUser.json | 15 ++- src/app/api/mock/onerep/config/route.ts | 71 ++++++++++++ src/app/api/mock/onerep/config/test.json | 25 +++++ src/app/api/mock/onerep/scan-results/route.ts | 54 +-------- 5 files changed, 214 insertions(+), 54 deletions(-) create mode 100644 src/app/api/mock/onerep/config/route.ts create mode 100644 src/app/api/mock/onerep/config/test.json diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index 987bb110534..eae5296af05 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -5,6 +5,28 @@ import { StateAbbr } from "../../../../../utils/states"; import MockUser from "./mockUser.json"; +interface Broker { + id: number; + profile_id: string; + scan_id: number; + status: string; + first_name: string; + middle_name?: string | null; + last_name: string; + age?: number | null; + addresses: object[]; + phones: object[]; + emails: object[]; + relatives: object[]; + link: string; + data_broker: string; + data_broker_id: number; + optout_attempts: number; + created_at: string; + updated_at: string; + url: string; +} + export function MOCK_ONEREP_PROFILE_ID() { return MockUser.PROFILE_ID; } @@ -45,3 +67,84 @@ export function MOCK_ONEREP_ADDRESSES() { state: address.state as StateAbbr, })) as typeOfAddr; } + +const DEFAULT_NUMBER_BREACHES = 10; +const magicNum0 = 37680; +const magicNum1 = 23; + +export function MOCK_ONEREP_BROKERS( + profileId: string, + page: string, + perPage: string, +) { + const mockResponseData = MockUser.BROKERS_LIST; + + const mockLinks = { + first: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, + last: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, + prev: null, + next: null, + }; + + const mockMeta = { + current_page: parseInt(page), + from: 1, + last_page: 1, + path: `${process.env.ONEREP_API_BASE}/scan-results`, + per_page: parseInt(perPage), + to: 10, + total: 10, + }; + + if (mockResponseData.valid) { + const response: { + data: Broker[]; + links: typeof mockLinks; + meta: typeof mockMeta; + } = { + data: [], + links: mockLinks, + meta: mockMeta, + }; + + if (mockResponseData.data.length > 0) { + response.data = mockResponseData.data.map((broker) => { + return { + ...(broker as Broker), + profile_id: profileId, + scan_id: MOCK_ONEREP_SCAN_ID(), + }; + }); + } + + return mockResponseData; + } + + const responseData = { + data: new Array(DEFAULT_NUMBER_BREACHES).fill(null).map((_, index) => ({ + id: magicNum0 - index, + profile_id: profileId, + scan_id: MOCK_ONEREP_SCAN_ID(), + status: "new", + first_name: MOCK_ONEREP_FIRSTNAME(), + middle_name: null, + last_name: MOCK_ONEREP_LASTNAME(), + age: null, + addresses: [], + phones: [], + emails: [MOCK_ONEREP_EMAIL()], + relatives: [], + link: `https://mockexample.com/link-to-databroker${index}`, + data_broker: `mockexample${index}.com`, + data_broker_id: magicNum1 - index, + optout_attempts: 0, + created_at: MOCK_ONEREP_TIME(), + updated_at: MOCK_ONEREP_TIME(), + url: `${process.env.ONEREP_API_BASE}scan-results/${magicNum0 - index}`, + })), + links: mockLinks, + meta: mockMeta, + }; + + return responseData; +} diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index d7705432b5d..6aba83b3656 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -7,5 +7,16 @@ "BIRTHDATE": "2000-01-01", "EMAIL": "JohnDoe@JohnDoe.com", "STATUS": "active", - "ADDRESSES": [{ "city": "Berkeley", "state": "CA" }] -} + "ADDRESSES": [ + { + "city": "Berkeley", + "state": "CA" + } + ], + "BROKERS_LIST": { + "data": [], + "links": {}, + "meta": {}, + "valid": false + } +} \ No newline at end of file diff --git a/src/app/api/mock/onerep/config/route.ts b/src/app/api/mock/onerep/config/route.ts new file mode 100644 index 00000000000..7d933d4725c --- /dev/null +++ b/src/app/api/mock/onerep/config/route.ts @@ -0,0 +1,71 @@ +/* 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 { NextRequest, NextResponse } from "next/server"; +import fs from "fs"; +import path from "path"; + +/* + +Example fetch, where obj conforms to Broker interface in config.ts + +fetch('/api/mock/onerep/config', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + brokers: obj, + erase: true + }) +}) +.then(response => response.json()).then(data => console.log(data)) + + +*/ + +export async function POST(req: NextRequest) { + // Define the path to the JSON file + const jsonFilePath = path.join( + process.cwd(), + "./src/app/api/mock/onerep/config/mockUser.json", + ); + + // Read the current JSON from the file + const fileData = fs.readFileSync(jsonFilePath, "utf8"); + const jsonData = JSON.parse(fileData); + + try { + const newData = await req.json(); + const erase = newData.erase; + + if (erase) { + jsonData.BROKERS_LIST.data = []; + jsonData.BROKERS_LIST.valid = false; + } else { + if (!newData.brokers || !newData.brokers.data) { + return NextResponse.json( + { error: "Bad Request: Data format is unexpected!" }, + { status: 400 }, + ); + } + + jsonData.BROKERS_LIST.data = newData.brokers.data; + jsonData.BROKERS_LIST.valid = true; + } + + fs.writeFileSync(jsonFilePath, JSON.stringify(jsonData, null, 2), "utf8"); + + return NextResponse.json( + { message: "JSON data has been successfully updated." }, + { status: 200 }, + ); + } catch (error) { + console.error("Mock endpoint OneRep: Failed to update JSON:", error); + return NextResponse.json( + { message: "Failed to update JSON data." }, + { status: 500 }, + ); + } +} diff --git a/src/app/api/mock/onerep/config/test.json b/src/app/api/mock/onerep/config/test.json new file mode 100644 index 00000000000..c9306b2f191 --- /dev/null +++ b/src/app/api/mock/onerep/config/test.json @@ -0,0 +1,25 @@ +{ + "data": [ + { + "id": 45000, + "profile_id": 0, + "scan_id": 0, + "status": "new", + "first_name": "John", + "middle_name": null, + "last_name": "Doe", + "age": null, + "addresses": [], + "phones": [], + "emails": [], + "relatives": [], + "link": "https://test.com/link-to-databroker0", + "data_broker": "test0.com", + "data_broker_id": 23, + "optout_attempts": 0, + "created_at": "2024-06-19T01:37:02+0000", + "updated_at": "2024-06-19T01:37:02+0000", + "url": "http://localhost:6060/api/mock/onerep/scan-results/45000" + } + ] +} diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts index 9655b1f32e3..3f2c76310ed 100644 --- a/src/app/api/mock/onerep/scan-results/route.ts +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -2,13 +2,7 @@ * 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 { - MOCK_ONEREP_FIRSTNAME, - MOCK_ONEREP_LASTNAME, - MOCK_ONEREP_TIME, - MOCK_ONEREP_SCAN_ID, - MOCK_ONEREP_EMAIL, -} from "../config/config"; +import { MOCK_ONEREP_BROKERS } from "../config/config"; import { NextRequest, NextResponse } from "next/server"; export function GET(req: NextRequest) { @@ -17,49 +11,5 @@ export function GET(req: NextRequest) { const profileId = req.url.match(/profile_id......=([0-9]+)&/)![1]; - const magicNum0 = 37680; - const magicNum1 = 23; - const howMany = 10; - - // TODO: mock put the indecies, id scan_id - const responseData = { - data: new Array(howMany).fill(null).map((_, index) => ({ - id: magicNum0 - index, - profile_id: profileId, - scan_id: MOCK_ONEREP_SCAN_ID(), - status: "new", - first_name: MOCK_ONEREP_FIRSTNAME(), - middle_name: null, - last_name: MOCK_ONEREP_LASTNAME(), - age: null, - addresses: [], - phones: [], - emails: [MOCK_ONEREP_EMAIL()], - relatives: [], - link: `https://mockexample.com/link-to-databroker${index}`, - data_broker: `mockexample${index}.com`, - data_broker_id: magicNum1 - index, - optout_attempts: 0, - created_at: MOCK_ONEREP_TIME(), - updated_at: MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}scan-results/${magicNum0 - index}`, - })), - links: { - first: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, - last: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, - prev: null, - next: null, - }, - meta: { - current_page: parseInt(page), - from: 1, - last_page: 1, - path: `${process.env.ONEREP_API_BASE}/scan-results`, - per_page: parseInt(perPage), - to: 10, - total: 10, - }, - }; - - return NextResponse.json(responseData); + return NextResponse.json(MOCK_ONEREP_BROKERS(profileId, page, perPage)); } From 8ec080df8ca94e60d1cdfed8b7a0da8767df7bb5 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Wed, 26 Jun 2024 02:50:23 -0700 Subject: [PATCH 016/137] lint fail fixed --- src/app/api/mock/onerep/config/mockUser.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index 6aba83b3656..cbd4dec28e0 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -19,4 +19,4 @@ "meta": {}, "valid": false } -} \ No newline at end of file +} From f3e9b37b2daffa4bf72d84846a25718a991dc60c Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Wed, 26 Jun 2024 16:52:29 -0700 Subject: [PATCH 017/137] most tests pass when using mock onerep endpoint --- src/app/functions/server/getRelevantGuidedSteps.ts | 1 + src/e2e/pages/purchasePage.ts | 2 +- src/e2e/utils/helpers.ts | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/functions/server/getRelevantGuidedSteps.ts b/src/app/functions/server/getRelevantGuidedSteps.ts index fb43517a2fa..93ed0ae3f2b 100644 --- a/src/app/functions/server/getRelevantGuidedSteps.ts +++ b/src/app/functions/server/getRelevantGuidedSteps.ts @@ -70,6 +70,7 @@ export type StepLinkWithStatus = (typeof stepLinks)[number] & { }; export function isGuidedResolutionInProgress(stepId: StepLink["id"]) { + if (stepId === "Scan" || stepId === "Done") return false; //this fixes a lint check const inProgressStepIds = stepLinks .filter((step) => step.id !== "Scan" && step.id !== "Done") .map(({ id }) => id); diff --git a/src/e2e/pages/purchasePage.ts b/src/e2e/pages/purchasePage.ts index e1ead13b02e..4b0c52de122 100644 --- a/src/e2e/pages/purchasePage.ts +++ b/src/e2e/pages/purchasePage.ts @@ -121,7 +121,7 @@ export class PurchasePage { (await this.planDetails.textContent()) as string, ); expect(planDetails).toContain( - `${process.env.E2E_TEST_ENV === "prod" ? "yearly" : "every 2 months"}`, + `${process.env.E2E_TEST_ENV!.match(/prod|local/) ? "yearly" : "every 2 months"}`, ); } diff --git a/src/e2e/utils/helpers.ts b/src/e2e/utils/helpers.ts index 0d398436dd0..6257d254367 100644 --- a/src/e2e/utils/helpers.ts +++ b/src/e2e/utils/helpers.ts @@ -170,7 +170,9 @@ export const clickOnATagCheckDomain = async ( page: Page, ) => { if (typeof host === "string") - host = new RegExp(escapeRegExp(host.replace(/^(https?:\/\/)/, ""))); + host = new RegExp( + escapeRegExp(host.replace(/^(https?:\/\/)/, "").replace(/:\d+$/, "")), + ); if (typeof path === "string") path = new RegExp(".*" + path + ".*"); const href = await aTag.getAttribute("href"); From 71e5ddf1f29be53761541c0796318daf532f9843 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Fri, 28 Jun 2024 15:57:14 -0700 Subject: [PATCH 018/137] passes all tests except for those in purchase spec --- .../api/mock/hibp/data/fakeAllBreaches.json | 32 +++++++++++++++- src/app/api/mock/hibp/data/fakeBreaches.json | 2 +- .../search}/[hashPrefix]/route.ts | 6 ++- src/app/api/mock/onerep/config/config.ts | 38 ++++++++++++++----- src/app/api/mock/onerep/config/mockUser.json | 4 +- src/app/api/mock/onerep/config/route.ts | 2 + src/app/api/mock/onerep/config/test.json | 4 +- .../profiles/[profileId]/optout/route.ts | 2 +- .../[profileId]/scans/[scanId]/route.ts | 1 - .../profiles/[profileId]/scans/route.ts | 25 ++++++++---- src/db/tables/onerep_scans.ts | 37 ++++++++++++++---- src/e2e/specs/auth.spec.ts | 5 +-- src/e2e/specs/dashboard.spec.ts | 21 +++++----- src/e2e/specs/landing.spec.ts | 20 ++-------- 14 files changed, 135 insertions(+), 64 deletions(-) rename src/app/api/mock/hibp/{breachedaccount/range => range/search}/[hashPrefix]/route.ts (90%) diff --git a/src/app/api/mock/hibp/data/fakeAllBreaches.json b/src/app/api/mock/hibp/data/fakeAllBreaches.json index 42524b86c5a..44aa91f5f2c 100644 --- a/src/app/api/mock/hibp/data/fakeAllBreaches.json +++ b/src/app/api/mock/hibp/data/fakeAllBreaches.json @@ -47,6 +47,36 @@ "IsSpamList": false, "IsMalware": false, "FaviconUrl": null - } + }, + { + "Id": 95, + "Name": "Bonobos", + "Title": "Bonobos", + "Domain": "bonobos.com", + "BreachDate": "2020-08-14T07:00:00.000Z", + "AddedDate": "2021-01-31T00:09:25.000Z", + "ModifiedDate": "2021-01-31T00:12:58.000Z", + "PwnCount": 2811929, + "Description": "In August 2020, the clothing store Bonobos suffered a data breach that exposed almost 70GB of data containing 2.8 million unique email addresses. The breach also exposed names, physical and IP addresses, phone numbers, order histories and passwords stored as salted SHA-512 hashes, including historical passwords. The breach also exposed partial credit card data including card type, the name on the card, expiry date and the last 4 digits of the card. The data was provided to HIBP by dehashed.com.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/Bonobos.png", + "DataClasses": [ + "email-addresses", + "historical-passwords", + "ip-addresses", + "names", + "partial-credit-card-data", + "passwords", + "phone-numbers", + "physical-addresses", + "purchases" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + } ] } diff --git a/src/app/api/mock/hibp/data/fakeBreaches.json b/src/app/api/mock/hibp/data/fakeBreaches.json index 70dbadb067c..65d091e067a 100644 --- a/src/app/api/mock/hibp/data/fakeBreaches.json +++ b/src/app/api/mock/hibp/data/fakeBreaches.json @@ -2,7 +2,7 @@ "data": [ { "hashSuffix": "", - "websites": ["000webhost", "123RF"] + "websites": ["000webhost", "123RF", "Bonobos"] } ] } diff --git a/src/app/api/mock/hibp/breachedaccount/range/[hashPrefix]/route.ts b/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts similarity index 90% rename from src/app/api/mock/hibp/breachedaccount/range/[hashPrefix]/route.ts rename to src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts index 190fb74c664..e386a13ed4a 100644 --- a/src/app/api/mock/hibp/breachedaccount/range/[hashPrefix]/route.ts +++ b/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts @@ -28,7 +28,7 @@ export function GET() { const userEmail = process.env.E2E_TEST_ACCOUNT_EMAIL; //TODO: getServerSession doesn't work here for some reason const currentUserSha = getSha1(userEmail as BinaryLike); - logger.info("Mock endpoint: /breachedaccount/range/"); + logger.info("Mock endpoint: /range/search/"); let data = fakeBreaches.data as BreachedAccountResponse; @@ -36,5 +36,7 @@ export function GET() { ...elem, hashSuffix: currentUserSha.slice(6).toUpperCase(), })); - return NextResponse.json(data); + const res = NextResponse.json(data); + console.log("opa1", data); + return res; } diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index eae5296af05..228cfb54329 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -2,8 +2,10 @@ * 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 { randomInt } from "crypto"; import { StateAbbr } from "../../../../../utils/states"; import MockUser from "./mockUser.json"; +// import { getLatestOnerepScanResults } from "../../../../../db/tables/onerep_scans"; interface Broker { id: number; @@ -31,8 +33,8 @@ export function MOCK_ONEREP_PROFILE_ID() { return MockUser.PROFILE_ID; } -export function MOCK_ONEREP_SCAN_ID() { - return MockUser.SCAN_ID; +export function MOCK_ONEREP_MAGIC_NUM_0() { + return MockUser.MAGIC_NUM_0; } export function MOCK_ONEREP_TIME() { @@ -68,9 +70,22 @@ export function MOCK_ONEREP_ADDRESSES() { })) as typeOfAddr; } -const DEFAULT_NUMBER_BREACHES = 10; -const magicNum0 = 37680; -const magicNum1 = 23; +const DEFAULT_NUMBER_BREACHES = 5; +const MAGIC_NUM_brokerId = 78127; +const MAGIC_NUM_1 = 24623; +const MAGIC_NUM_2 = 2161; + +export function MOCK_ONEREP_MAGIC_NUM_1() { + return MAGIC_NUM_1; +} + +export function MOCK_ONEREP_MAGIC_NUM_2() { + return MAGIC_NUM_2; +} + +function getScanId(profileId: string) { + return (Number(profileId) * MAGIC_NUM_1) % MAGIC_NUM_2; +} export function MOCK_ONEREP_BROKERS( profileId: string, @@ -78,6 +93,7 @@ export function MOCK_ONEREP_BROKERS( perPage: string, ) { const mockResponseData = MockUser.BROKERS_LIST; + const latestScanId = getScanId(profileId); const mockLinks = { first: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, @@ -112,7 +128,7 @@ export function MOCK_ONEREP_BROKERS( return { ...(broker as Broker), profile_id: profileId, - scan_id: MOCK_ONEREP_SCAN_ID(), + scan_id: latestScanId, }; }); } @@ -120,11 +136,13 @@ export function MOCK_ONEREP_BROKERS( return mockResponseData; } + const idStart = latestScanId * MOCK_ONEREP_MAGIC_NUM_0(); + const responseData = { data: new Array(DEFAULT_NUMBER_BREACHES).fill(null).map((_, index) => ({ - id: magicNum0 - index, + id: idStart - index, profile_id: profileId, - scan_id: MOCK_ONEREP_SCAN_ID(), + scan_id: latestScanId, status: "new", first_name: MOCK_ONEREP_FIRSTNAME(), middle_name: null, @@ -136,11 +154,11 @@ export function MOCK_ONEREP_BROKERS( relatives: [], link: `https://mockexample.com/link-to-databroker${index}`, data_broker: `mockexample${index}.com`, - data_broker_id: magicNum1 - index, + data_broker_id: MAGIC_NUM_brokerId - index, optout_attempts: 0, created_at: MOCK_ONEREP_TIME(), updated_at: MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}scan-results/${magicNum0 - index}`, + url: `${process.env.ONEREP_API_BASE}scan-results/${idStart - index}`, })), links: mockLinks, meta: mockMeta, diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index cbd4dec28e0..ece3a3a67eb 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -1,6 +1,6 @@ { "PROFILE_ID": 777, - "SCAN_ID": 129837123, + "MAGIC_NUM_0": 2914, "TIME": "2024-06-19T01:37:02+0000", "FIRSTNAME": "John", "LASTNAME": "Doe", @@ -19,4 +19,4 @@ "meta": {}, "valid": false } -} +} \ No newline at end of file diff --git a/src/app/api/mock/onerep/config/route.ts b/src/app/api/mock/onerep/config/route.ts index 7d933d4725c..969ed75a812 100644 --- a/src/app/api/mock/onerep/config/route.ts +++ b/src/app/api/mock/onerep/config/route.ts @@ -5,6 +5,7 @@ import { NextRequest, NextResponse } from "next/server"; import fs from "fs"; import path from "path"; +import { randomInt } from "crypto"; /* @@ -43,6 +44,7 @@ export async function POST(req: NextRequest) { if (erase) { jsonData.BROKERS_LIST.data = []; jsonData.BROKERS_LIST.valid = false; + jsonData.MAGIC_NUM_0 = randomInt(1000, 100000); } else { if (!newData.brokers || !newData.brokers.data) { return NextResponse.json( diff --git a/src/app/api/mock/onerep/config/test.json b/src/app/api/mock/onerep/config/test.json index c9306b2f191..c8d3af447e7 100644 --- a/src/app/api/mock/onerep/config/test.json +++ b/src/app/api/mock/onerep/config/test.json @@ -2,8 +2,8 @@ "data": [ { "id": 45000, - "profile_id": 0, - "scan_id": 0, + "profile_id": -1, + "scan_id": -1, "status": "new", "first_name": "John", "middle_name": null, diff --git a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts index 8a3bed0ecb6..611cf44c0bc 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts @@ -4,7 +4,7 @@ import { NextRequest, NextResponse } from "next/server"; -export function PUT(req: NextRequest) { +export function POST(req: NextRequest) { const profileId: number = Number(req.url.match(/profiles\/([0-9]+)/)![1]); if (!profileId || isNaN(profileId)) { diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts index 14d56c3b2ae..228088ec1b0 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts @@ -11,7 +11,6 @@ export function GET(req: NextRequest) { const profileId = Number(req.url.match(/profiles\/([0-9]+)/)![1]); const scanId = Number(req.url.match(/scans\/([0-9]+)/)![1]); - // Check for the availability of the scan const responseData = { id: scanId, profile_id: profileId, diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index c5c2a934b39..d085e0fc107 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -2,30 +2,38 @@ * 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 { MOCK_ONEREP_SCAN_ID, MOCK_ONEREP_TIME } from "../../../config/config"; import { NextRequest, NextResponse } from "next/server"; - -//TODO: mock out the id field and url +import { + MOCK_ONEREP_TIME, + MOCK_ONEREP_MAGIC_NUM_1, + MOCK_ONEREP_MAGIC_NUM_2, +} from "../../../config/config"; function extractProfileId(req: NextRequest) { const idFromUrl = Number(req.url.match(/profiles\/([0-9]+)\/scans/)![1]); return idFromUrl; } +function getScanId(profileId: number) { + return (profileId * MOCK_ONEREP_MAGIC_NUM_1()) % MOCK_ONEREP_MAGIC_NUM_2(); +} + export function POST(req: NextRequest) { const profileId: number = extractProfileId(req); if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); } + const scanId = getScanId(profileId); + const mockResponse = { - id: MOCK_ONEREP_SCAN_ID(), + id: scanId, profile_id: profileId, status: "finished", reason: "manual", created_at: MOCK_ONEREP_TIME(), updated_at: MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${MOCK_ONEREP_SCAN_ID()}`, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, }; return NextResponse.json(mockResponse); @@ -38,17 +46,18 @@ export function GET(req: NextRequest) { return NextResponse.json({ error: "Invalid profile ID" }); } - //TODO: mock out ID here and urls + const scandId = getScanId(profileId); + const responseData = { data: [ { - id: MOCK_ONEREP_SCAN_ID(), + id: scandId, profile_id: profileId, status: "finished", reason: "manual", created_at: MOCK_ONEREP_TIME(), updated_at: MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${MOCK_ONEREP_SCAN_ID()}`, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scandId}`, }, ], links: { diff --git a/src/db/tables/onerep_scans.ts b/src/db/tables/onerep_scans.ts index 0e61241f126..deee6736e78 100644 --- a/src/db/tables/onerep_scans.ts +++ b/src/db/tables/onerep_scans.ts @@ -4,7 +4,6 @@ import createDbConnection from "../connect.js"; import { logger } from "../../app/functions/server/logging"; -import { MOCK_ONEREP_SCAN_ID } from "../../app/api/mock/onerep/config/config.ts"; import { ScanResult, @@ -206,7 +205,7 @@ async function addOnerepScanResults( onerepProfileId: number, onerepScanResults: Array, ) { - const scanResultsMap = onerepScanResults.map((scanResult) => ({ + let scanResultsMap = onerepScanResults.map((scanResult) => ({ onerep_scan_result_id: scanResult.id, onerep_scan_id: scanResult.scan_id, link: scanResult.link, @@ -241,11 +240,35 @@ async function addOnerepScanResults( }); if (scanResultsMap.length > 0) { - //Delete previous records to allow dynamic mock data configuration. - if (process.env.ONEREP_API_BASE!.includes("localhost")) { - await knex("onerep_scan_results") - .where("onerep_scan_id", MOCK_ONEREP_SCAN_ID()) - .del(); + const scan_id = scanResultsMap[0].onerep_scan_id; + + // Delete previous records to allow dynamic mock data configuration, maintaining the 'manually_resolved' field. + if (!process.env.ONEREP_API_BASE!.includes("mozilla.api.onerep.com")) { + const existingRecords = await knex("onerep_scan_results") + .where("onerep_scan_id", scan_id) + .select("onerep_scan_result_id", "manually_resolved"); + + const resolvedStatusMap = new Map(); + existingRecords.forEach((record) => { + resolvedStatusMap.set( + record.onerep_scan_result_id, + record.manually_resolved, + ); + }); + + await knex("onerep_scan_results").where("onerep_scan_id", scan_id).del(); + + scanResultsMap = scanResultsMap.map((item) => { + if (resolvedStatusMap.has(item.onerep_scan_result_id)) { + return { + ...item, + manually_resolved: resolvedStatusMap.get( + item.onerep_scan_result_id, + ), + }; + } + return item; + }); } await knex("onerep_scan_results") diff --git a/src/e2e/specs/auth.spec.ts b/src/e2e/specs/auth.spec.ts index 92959b012d7..a820b367893 100644 --- a/src/e2e/specs/auth.spec.ts +++ b/src/e2e/specs/auth.spec.ts @@ -27,10 +27,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Authentication flow verification @s await authPage.signUp(randomEmail, page); // assert successful login - const successUrl = - process.env.E2E_TEST_ENV === "local" - ? "/user/dashboard" - : "/user/welcome"; + const successUrl = "/user/welcome"; expect(page.url()).toBe(`${process.env.E2E_TEST_BASE_URL}${successUrl}`); await testInfo.attach( diff --git a/src/e2e/specs/dashboard.spec.ts b/src/e2e/specs/dashboard.spec.ts index a72dba31d56..d2709338a7f 100644 --- a/src/e2e/specs/dashboard.spec.ts +++ b/src/e2e/specs/dashboard.spec.ts @@ -10,7 +10,6 @@ import { removeUnicodeChars, clickOnATagCheckDomain, escapeRegExp, - forceLoginAs, } from "../utils/helpers.js"; // bypass login @@ -426,12 +425,14 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Breaches Scan, const count = await dashboardPage.allExposures.count(); // Fix first exposure await dashboardPage.markAsFixed.click(); - + // await new Promise(resolve => setTimeout(resolve, 10000)); for (let i = 1; i < count; i++) { const exposure = dashboardPage.allExposures.nth(i); await exposure.click(); + // await new Promise(resolve => setTimeout(resolve, 2000)); if (await dashboardPage.markAsFixed.isVisible()) { + // await new Promise(resolve => setTimeout(resolve, 2000)); await dashboardPage.markAsFixed.click(); } } @@ -448,6 +449,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Breaches Scan, await dashboardPage.fixedTab.click(); const fixedExposures = await dashboardPage.numFixed.textContent(); expect(fixedExposures as string).toMatch(initialExposuresCount); + // expect(false).toBeTruthy(); }); }); @@ -720,13 +722,14 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Navigation`, ( }); test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Data Breaches`, () => { - test.beforeEach(async ({ landingPage, page, authPage }) => { - const emailToUse = process.env - .E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED as string; - const pwdToUse = process.env.E2E_TEST_ACCOUNT_PASSWORD as string; - expect(emailToUse).not.toBeUndefined(); - expect(pwdToUse).not.toBeUndefined(); - await forceLoginAs(emailToUse, pwdToUse, page, landingPage, authPage); + test.beforeEach(async ({ dashboardPage, page }) => { + await dashboardPage.open(); + + try { + await checkAuthState(page); + } catch { + console.log("[E2E_LOG] - No fxa auth required, proceeding..."); + } }); test("Verify that the High risk data breaches step is displayed correctly", async ({ diff --git a/src/e2e/specs/landing.spec.ts b/src/e2e/specs/landing.spec.ts index acb202d4656..58d462c36b6 100644 --- a/src/e2e/specs/landing.spec.ts +++ b/src/e2e/specs/landing.spec.ts @@ -309,14 +309,8 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali await authPage.enterPassword(); // verify dashboard redirect - const successUrl = - process.env.E2E_TEST_BASE_URL + - `${ - process.env.E2E_TEST_ENV === "local" - ? "/user/welcome" - : "/user/dashboard" - }`; - expect(page.url()).toBe(successUrl); + const successUrl = process.env.E2E_TEST_BASE_URL + "/user/dashboard"; + expect(page.url()).toContain(successUrl); }); test('Verify the "Start free monitoring" button UI and functionality with existing account', async ({ @@ -336,13 +330,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali await authPage.enterPassword(); // verify dashboard redirect - const successUrl = - process.env.E2E_TEST_BASE_URL + - `${ - process.env.E2E_TEST_ENV === "local" - ? "/user/welcome" - : "/user/dashboard" - }`; - expect(page.url()).toBe(successUrl); + const successUrl = process.env.E2E_TEST_BASE_URL + "/user/dashboard"; + expect(page.url()).toContain(successUrl); }); }); From d6eb4ed150be1d0c9fd73726a167ca52ac2c3257 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Fri, 28 Jun 2024 16:59:41 -0700 Subject: [PATCH 019/137] added production checks --- src/app/api/mock/hibp/breaches/route.ts | 11 +++-------- .../mock/hibp/range/search/[hashPrefix]/route.ts | 13 +++---------- src/app/api/mock/onerep/config/config.ts | 2 +- src/app/api/mock/onerep/config/mockUser.json | 2 +- src/app/api/mock/onerep/config/route.ts | 13 ++++++++++++- .../profiles/[profileId]/activate/route.ts | 6 ++++-- .../profiles/[profileId]/deactivate/route.ts | 6 ++++-- .../onerep/profiles/[profileId]/optout/route.ts | 6 ++++-- .../mock/onerep/profiles/[profileId]/route.ts | 5 ++++- .../profiles/[profileId]/scans/[scanId]/route.ts | 6 ++++-- .../onerep/profiles/[profileId]/scans/route.ts | 7 +++++++ src/app/api/mock/onerep/profiles/route.ts | 4 ++++ src/app/api/mock/onerep/scan-results/route.ts | 4 ++++ src/app/api/mock/onerep/stats/profiles/route.ts | 4 ++++ src/app/api/mock/utils/errorThrower.ts | 16 ++++++++++++++++ src/app/functions/server/onerep.ts | 2 +- src/db/tables/onerep_scans.ts | 2 +- src/e2e/specs/dashboard.spec.ts | 4 ---- 18 files changed, 77 insertions(+), 36 deletions(-) create mode 100644 src/app/api/mock/utils/errorThrower.ts diff --git a/src/app/api/mock/hibp/breaches/route.ts b/src/app/api/mock/hibp/breaches/route.ts index 5c4230b773f..91ed1e2c7ea 100644 --- a/src/app/api/mock/hibp/breaches/route.ts +++ b/src/app/api/mock/hibp/breaches/route.ts @@ -5,6 +5,7 @@ import { NextResponse } from "next/server"; import { logger } from "../../../../functions/server/logging"; import fakeAllBreaches from "../data/fakeAllBreaches.json"; +import { errorIfProduction } from "../../utils/errorThrower"; type BreachesListResponse = { Id: number; @@ -28,15 +29,9 @@ type BreachesListResponse = { }[]; export function GET() { - const { APP_ENV } = process.env; + const prodError = errorIfProduction(); + if (prodError) return prodError; - // Check if APP_ENV is set to production - if (APP_ENV === "production") { - return NextResponse.json( - { error: "Endpoint not available in production environment" }, - { status: 403 }, - ); - } logger.info("Mock endpoint: /breaches"); const data = fakeAllBreaches.data as BreachesListResponse; diff --git a/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts b/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts index e386a13ed4a..795a27a381f 100644 --- a/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts +++ b/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts @@ -7,6 +7,7 @@ import { logger } from "../../../../../../functions/server/logging"; import { getSha1 } from "../../../../../../../utils/fxa"; import fakeBreaches from "../../../data/fakeBreaches.json"; import type { BinaryLike } from "crypto"; +import { errorIfProduction } from "../../../../utils/errorThrower"; type BreachedAccountResponse = { hashSuffix: string; @@ -14,15 +15,8 @@ type BreachedAccountResponse = { }[]; export function GET() { - const { APP_ENV } = process.env; - - // Check if APP_ENV is set to production - if (APP_ENV === "production") { - return NextResponse.json( - { error: "Endpoint not available in production environment" }, - { status: 403 }, - ); - } + const prodError = errorIfProduction(); + if (prodError) return prodError; // Mock data for test email, can be randomized const userEmail = process.env.E2E_TEST_ACCOUNT_EMAIL; @@ -37,6 +31,5 @@ export function GET() { hashSuffix: currentUserSha.slice(6).toUpperCase(), })); const res = NextResponse.json(data); - console.log("opa1", data); return res; } diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index 228cfb54329..d6a9a152e99 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -70,7 +70,7 @@ export function MOCK_ONEREP_ADDRESSES() { })) as typeOfAddr; } -const DEFAULT_NUMBER_BREACHES = 5; +const DEFAULT_NUMBER_BREACHES = 10; const MAGIC_NUM_brokerId = 78127; const MAGIC_NUM_1 = 24623; const MAGIC_NUM_2 = 2161; diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index ece3a3a67eb..f39086e0d1b 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -1,6 +1,6 @@ { "PROFILE_ID": 777, - "MAGIC_NUM_0": 2914, + "MAGIC_NUM_0": 29010, "TIME": "2024-06-19T01:37:02+0000", "FIRSTNAME": "John", "LASTNAME": "Doe", diff --git a/src/app/api/mock/onerep/config/route.ts b/src/app/api/mock/onerep/config/route.ts index 969ed75a812..b5cf3a03465 100644 --- a/src/app/api/mock/onerep/config/route.ts +++ b/src/app/api/mock/onerep/config/route.ts @@ -6,10 +6,12 @@ import { NextRequest, NextResponse } from "next/server"; import fs from "fs"; import path from "path"; import { randomInt } from "crypto"; +import { logger } from "../../../../functions/server/logging"; /* Example fetch, where obj conforms to Broker interface in config.ts +Set erase to true if you want to use default response, or false if obj fetch('/api/mock/onerep/config', { method: 'POST', @@ -26,7 +28,16 @@ fetch('/api/mock/onerep/config', { */ -export async function POST(req: NextRequest) { +export function POST() { + logger.info(`Attempted to access ${updateJsonFile.name}`); + + return NextResponse.json( + { error: "Endpoint not available yet" }, + { status: 403 }, + ); +} + +async function updateJsonFile(req: NextRequest) { // Define the path to the JSON file const jsonFilePath = path.join( process.cwd(), diff --git a/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts index dcae96793f8..d2be762a3ee 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts @@ -3,16 +3,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextRequest, NextResponse } from "next/server"; +import { errorIfProduction } from "../../../../utils/errorThrower"; export function PUT(req: NextRequest) { + const prodError = errorIfProduction(); + if (prodError) return prodError; + const profileId: number = Number(req.url.match(/profiles\/([0-9]+)/)![1]); if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); } - //TODO: update the json file corresponding to this user - return NextResponse.json({ message: `Profile ${profileId} successfully activated`, }); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts index 964652c369d..ef8fee39f97 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts @@ -3,16 +3,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextRequest, NextResponse } from "next/server"; +import { errorIfProduction } from "../../../../utils/errorThrower"; export function PUT(req: NextRequest) { + const prodError = errorIfProduction(); + if (prodError) return prodError; + const profileId: number = Number(req.url.match(/profiles\/([0-9]+)/)![1]); if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); } - //TODO: update the json file corresponding to this user - return NextResponse.json({ message: `Profile ${profileId} successfully deactivated`, }); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts index 611cf44c0bc..0a74866019c 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts @@ -3,16 +3,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextRequest, NextResponse } from "next/server"; +import { errorIfProduction } from "../../../../utils/errorThrower"; export function POST(req: NextRequest) { + const prodError = errorIfProduction(); + if (prodError) return prodError; + const profileId: number = Number(req.url.match(/profiles\/([0-9]+)/)![1]); if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); } - //TODO: update the json file corresponding to this user - return NextResponse.json({ message: `Profile ${profileId} successfully opted out`, }); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/route.ts index ea28493e2ba..be0ca13f989 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/route.ts @@ -12,9 +12,9 @@ import { } from "../../config/config.ts"; import { ShowProfileResponse } from "../../../../../functions/server/onerep.ts"; import { NextRequest, NextResponse } from "next/server"; +import { errorIfProduction } from "../../../utils/errorThrower.ts"; // Mocked profile data to simulate response -//TODO: mock out the URL async function extractProfileId(req: NextRequest) { const idFromBody: number = req.body !== null && (await req.json()).profileId; @@ -25,6 +25,9 @@ async function extractProfileId(req: NextRequest) { // Mock endpoint to simulate fetching a profile by ID export async function GET(req: NextRequest) { + const prodError = errorIfProduction(); + if (prodError) return prodError; + // Extract profileId from query parameters or request body const profileId: number = await extractProfileId(req); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts index 228088ec1b0..d42bd82caa2 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts @@ -2,12 +2,14 @@ * 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/. */ -// pages/api/profiles/[profileId]/scans/[scanId].ts - +import { errorIfProduction } from "../../../../../utils/errorThrower"; import { MOCK_ONEREP_TIME } from "../../../../config/config"; import { NextRequest, NextResponse } from "next/server"; export function GET(req: NextRequest) { + const prodError = errorIfProduction(); + if (prodError) return prodError; + const profileId = Number(req.url.match(/profiles\/([0-9]+)/)![1]); const scanId = Number(req.url.match(/scans\/([0-9]+)/)![1]); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index d085e0fc107..fbd32e7244b 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -8,6 +8,7 @@ import { MOCK_ONEREP_MAGIC_NUM_1, MOCK_ONEREP_MAGIC_NUM_2, } from "../../../config/config"; +import { errorIfProduction } from "../../../../utils/errorThrower"; function extractProfileId(req: NextRequest) { const idFromUrl = Number(req.url.match(/profiles\/([0-9]+)\/scans/)![1]); @@ -19,6 +20,9 @@ function getScanId(profileId: number) { } export function POST(req: NextRequest) { + const prodError = errorIfProduction(); + if (prodError) return prodError; + const profileId: number = extractProfileId(req); if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); @@ -40,6 +44,9 @@ export function POST(req: NextRequest) { } export function GET(req: NextRequest) { + const prodError = errorIfProduction(); + if (prodError) return prodError; + const profileId: number = extractProfileId(req); if (!profileId || isNaN(profileId)) { diff --git a/src/app/api/mock/onerep/profiles/route.ts b/src/app/api/mock/onerep/profiles/route.ts index a5180d11320..98f2b4d8a54 100644 --- a/src/app/api/mock/onerep/profiles/route.ts +++ b/src/app/api/mock/onerep/profiles/route.ts @@ -5,6 +5,7 @@ import { MOCK_ONEREP_STATUS, MOCK_ONEREP_TIME } from "../config/config.ts"; import { NextRequest, NextResponse } from "next/server"; import { randomInt } from "crypto"; +import { errorIfProduction } from "../../utils/errorThrower.ts"; export type RequestProfileData = { first_name: string; @@ -43,6 +44,9 @@ type ResponseProfileData = { }; export async function POST(req: NextRequest) { + const prodError = errorIfProduction(); + if (prodError) return prodError; + const profileId = randomInt(1000, 10000); try { if (req.body === null) { diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts index 3f2c76310ed..07b4bab3c43 100644 --- a/src/app/api/mock/onerep/scan-results/route.ts +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -2,10 +2,14 @@ * 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 { errorIfProduction } from "../../utils/errorThrower"; import { MOCK_ONEREP_BROKERS } from "../config/config"; import { NextRequest, NextResponse } from "next/server"; export function GET(req: NextRequest) { + const prodError = errorIfProduction(); + if (prodError) return prodError; + const page = req.url.match(/page=([0-9]+)/)![1] || "1"; const perPage = req.url.match(/per_page=([0-9]+)/)![1] || "100"; diff --git a/src/app/api/mock/onerep/stats/profiles/route.ts b/src/app/api/mock/onerep/stats/profiles/route.ts index ffd8f958068..fbad9a4e337 100644 --- a/src/app/api/mock/onerep/stats/profiles/route.ts +++ b/src/app/api/mock/onerep/stats/profiles/route.ts @@ -3,8 +3,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextResponse } from "next/server"; +import { errorIfProduction } from "../../../utils/errorThrower"; export function GET() { + const prodError = errorIfProduction(); + if (prodError) return prodError; + const profileStats = { created: 0, deleted: 0, diff --git a/src/app/api/mock/utils/errorThrower.ts b/src/app/api/mock/utils/errorThrower.ts new file mode 100644 index 00000000000..9471aa108e9 --- /dev/null +++ b/src/app/api/mock/utils/errorThrower.ts @@ -0,0 +1,16 @@ +/* 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"; + +export function errorIfProduction() { + //checks that the environment isnt prod + if (process.env.APP_ENV === "production") { + return NextResponse.json( + { error: "Endpoint not available in production environment" }, + { status: 403 }, + ); + } + return null; +} diff --git a/src/app/functions/server/onerep.ts b/src/app/functions/server/onerep.ts index af7fa53b86a..491ad6e3ba7 100644 --- a/src/app/functions/server/onerep.ts +++ b/src/app/functions/server/onerep.ts @@ -121,7 +121,7 @@ async function onerepFetch( //If mock, remove the first slash so that it doesn't overwrite the path if ( - onerepApiBase.includes("localhost") && + onerepApiBase.includes("/api/mock") && path.length > 1 && path[0] === "/" ) { diff --git a/src/db/tables/onerep_scans.ts b/src/db/tables/onerep_scans.ts index deee6736e78..04913d616cd 100644 --- a/src/db/tables/onerep_scans.ts +++ b/src/db/tables/onerep_scans.ts @@ -243,7 +243,7 @@ async function addOnerepScanResults( const scan_id = scanResultsMap[0].onerep_scan_id; // Delete previous records to allow dynamic mock data configuration, maintaining the 'manually_resolved' field. - if (!process.env.ONEREP_API_BASE!.includes("mozilla.api.onerep.com")) { + if (process.env.ONEREP_API_BASE!.includes("/api/mock")) { const existingRecords = await knex("onerep_scan_results") .where("onerep_scan_id", scan_id) .select("onerep_scan_result_id", "manually_resolved"); diff --git a/src/e2e/specs/dashboard.spec.ts b/src/e2e/specs/dashboard.spec.ts index d2709338a7f..bc1b0df3eb0 100644 --- a/src/e2e/specs/dashboard.spec.ts +++ b/src/e2e/specs/dashboard.spec.ts @@ -425,14 +425,11 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Breaches Scan, const count = await dashboardPage.allExposures.count(); // Fix first exposure await dashboardPage.markAsFixed.click(); - // await new Promise(resolve => setTimeout(resolve, 10000)); for (let i = 1; i < count; i++) { const exposure = dashboardPage.allExposures.nth(i); await exposure.click(); - // await new Promise(resolve => setTimeout(resolve, 2000)); if (await dashboardPage.markAsFixed.isVisible()) { - // await new Promise(resolve => setTimeout(resolve, 2000)); await dashboardPage.markAsFixed.click(); } } @@ -449,7 +446,6 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Breaches Scan, await dashboardPage.fixedTab.click(); const fixedExposures = await dashboardPage.numFixed.textContent(); expect(fixedExposures as string).toMatch(initialExposuresCount); - // expect(false).toBeTruthy(); }); }); From 842b229efe83d474457dc38d138974f2d0a53a42 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Fri, 28 Jun 2024 17:04:01 -0700 Subject: [PATCH 020/137] fixed lint errors --- src/app/api/mock/hibp/data/fakeAllBreaches.json | 2 +- src/app/api/mock/onerep/config/mockUser.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/api/mock/hibp/data/fakeAllBreaches.json b/src/app/api/mock/hibp/data/fakeAllBreaches.json index 44aa91f5f2c..ec9063f09f6 100644 --- a/src/app/api/mock/hibp/data/fakeAllBreaches.json +++ b/src/app/api/mock/hibp/data/fakeAllBreaches.json @@ -77,6 +77,6 @@ "IsSpamList": false, "IsMalware": false, "FaviconUrl": null - } + } ] } diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index f39086e0d1b..65375f66e67 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -19,4 +19,4 @@ "meta": {}, "valid": false } -} \ No newline at end of file +} From 9d368c0fb1c68802f795b40df06a42f0fd11ac5b Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Fri, 28 Jun 2024 17:42:23 -0700 Subject: [PATCH 021/137] reverting smoke test --- src/e2e/specs/auth.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/e2e/specs/auth.spec.ts b/src/e2e/specs/auth.spec.ts index a820b367893..92959b012d7 100644 --- a/src/e2e/specs/auth.spec.ts +++ b/src/e2e/specs/auth.spec.ts @@ -27,7 +27,10 @@ test.describe(`${process.env.E2E_TEST_ENV} - Authentication flow verification @s await authPage.signUp(randomEmail, page); // assert successful login - const successUrl = "/user/welcome"; + const successUrl = + process.env.E2E_TEST_ENV === "local" + ? "/user/dashboard" + : "/user/welcome"; expect(page.url()).toBe(`${process.env.E2E_TEST_BASE_URL}${successUrl}`); await testInfo.attach( From 29af535c1c357efc39c2f4b20c2cf43d5f120d40 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Sat, 29 Jun 2024 03:18:02 -0700 Subject: [PATCH 022/137] cleaned up query params and dynamic routing --- src/app/api/mock/onerep/config/mockUser.json | 4 ++-- .../profiles/[profileId]/activate/route.ts | 7 +++++-- .../profiles/[profileId]/deactivate/route.ts | 7 +++++-- .../profiles/[profileId]/optout/route.ts | 7 +++++-- .../mock/onerep/profiles/[profileId]/route.ts | 17 +++++------------ .../[profileId]/scans/[scanId]/route.ts | 10 +++++++--- .../onerep/profiles/[profileId]/scans/route.ts | 18 +++++++++--------- src/app/api/mock/onerep/scan-results/route.ts | 13 ++++++++++--- 8 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index 65375f66e67..b7e3b3ebbe5 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -1,6 +1,6 @@ { "PROFILE_ID": 777, - "MAGIC_NUM_0": 29010, + "MAGIC_NUM_0": 83597, "TIME": "2024-06-19T01:37:02+0000", "FIRSTNAME": "John", "LASTNAME": "Doe", @@ -19,4 +19,4 @@ "meta": {}, "valid": false } -} +} \ No newline at end of file diff --git a/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts index d2be762a3ee..74b07b24eef 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts @@ -5,11 +5,14 @@ import { NextRequest, NextResponse } from "next/server"; import { errorIfProduction } from "../../../../utils/errorThrower"; -export function PUT(req: NextRequest) { +export function PUT( + _: NextRequest, + { params }: { params: { profileId: string } }, +) { const prodError = errorIfProduction(); if (prodError) return prodError; - const profileId: number = Number(req.url.match(/profiles\/([0-9]+)/)![1]); + const profileId: number = Number(params.profileId); if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts index ef8fee39f97..815143010bb 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts @@ -5,11 +5,14 @@ import { NextRequest, NextResponse } from "next/server"; import { errorIfProduction } from "../../../../utils/errorThrower"; -export function PUT(req: NextRequest) { +export function PUT( + _: NextRequest, + { params }: { params: { profileId: string } }, +) { const prodError = errorIfProduction(); if (prodError) return prodError; - const profileId: number = Number(req.url.match(/profiles\/([0-9]+)/)![1]); + const profileId: number = Number(params.profileId); if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts index 0a74866019c..5ea5e3c37ff 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts @@ -5,11 +5,14 @@ import { NextRequest, NextResponse } from "next/server"; import { errorIfProduction } from "../../../../utils/errorThrower"; -export function POST(req: NextRequest) { +export function POST( + _: NextRequest, + { params }: { params: { profileId: string } }, +) { const prodError = errorIfProduction(); if (prodError) return prodError; - const profileId: number = Number(req.url.match(/profiles\/([0-9]+)/)![1]); + const profileId: number = Number(params.profileId); if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/route.ts index be0ca13f989..49cb8e3aaad 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/route.ts @@ -14,22 +14,15 @@ import { ShowProfileResponse } from "../../../../../functions/server/onerep.ts"; import { NextRequest, NextResponse } from "next/server"; import { errorIfProduction } from "../../../utils/errorThrower.ts"; -// Mocked profile data to simulate response - -async function extractProfileId(req: NextRequest) { - const idFromBody: number = req.body !== null && (await req.json()).profileId; - if (idFromBody) return idFromBody; - const idFromUrl: number = Number(req.url.match(/profiles\/([0-9]+)/)![1]); - return idFromUrl; -} - // Mock endpoint to simulate fetching a profile by ID -export async function GET(req: NextRequest) { +export function GET( + _: NextRequest, + { params }: { params: { profileId: string } }, +) { const prodError = errorIfProduction(); if (prodError) return prodError; - // Extract profileId from query parameters or request body - const profileId: number = await extractProfileId(req); + const profileId: number = Number(params.profileId); if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }, { status: 400 }); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts index d42bd82caa2..7297aabcab4 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts @@ -6,12 +6,15 @@ import { errorIfProduction } from "../../../../../utils/errorThrower"; import { MOCK_ONEREP_TIME } from "../../../../config/config"; import { NextRequest, NextResponse } from "next/server"; -export function GET(req: NextRequest) { +export function GET( + _: NextRequest, + { params }: { params: { profileId: string; scandId: string } }, +) { const prodError = errorIfProduction(); if (prodError) return prodError; - const profileId = Number(req.url.match(/profiles\/([0-9]+)/)![1]); - const scanId = Number(req.url.match(/scans\/([0-9]+)/)![1]); + const profileId: number = Number(params.profileId); + const scanId: number = Number(params.scandId); const responseData = { id: scanId, @@ -22,5 +25,6 @@ export function GET(req: NextRequest) { updated_at: MOCK_ONEREP_TIME(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, }; + return NextResponse.json(responseData); } diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index fbd32e7244b..4bd1265e0a1 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -10,20 +10,15 @@ import { } from "../../../config/config"; import { errorIfProduction } from "../../../../utils/errorThrower"; -function extractProfileId(req: NextRequest) { - const idFromUrl = Number(req.url.match(/profiles\/([0-9]+)\/scans/)![1]); - return idFromUrl; -} - function getScanId(profileId: number) { return (profileId * MOCK_ONEREP_MAGIC_NUM_1()) % MOCK_ONEREP_MAGIC_NUM_2(); } -export function POST(req: NextRequest) { +export function POST({ params }: { params: { profileId: number } }) { const prodError = errorIfProduction(); if (prodError) return prodError; - const profileId: number = extractProfileId(req); + const profileId: number = params.profileId; if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); } @@ -43,11 +38,16 @@ export function POST(req: NextRequest) { return NextResponse.json(mockResponse); } -export function GET(req: NextRequest) { +export function GET( + _: NextRequest, + { params }: { params: { profileId: string } }, +) { const prodError = errorIfProduction(); if (prodError) return prodError; - const profileId: number = extractProfileId(req); + // Extract profileId from query parameters or request body + // const profileId: number = await extractProfileId(req) + const profileId: number = Number(params.profileId); if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts index 07b4bab3c43..f9d9f1eeefe 100644 --- a/src/app/api/mock/onerep/scan-results/route.ts +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -10,10 +10,17 @@ export function GET(req: NextRequest) { const prodError = errorIfProduction(); if (prodError) return prodError; - const page = req.url.match(/page=([0-9]+)/)![1] || "1"; - const perPage = req.url.match(/per_page=([0-9]+)/)![1] || "100"; + const page = req.nextUrl.searchParams.get("page") || "1"; + const perPage = req.nextUrl.searchParams.get("per_page") || "100"; - const profileId = req.url.match(/profile_id......=([0-9]+)&/)![1]; + const profileId = req.nextUrl.searchParams.get("profile_id[]"); + + if (profileId === null) { + return NextResponse.json( + { error: "No 'profile_id' provided!" }, + { status: 400 }, + ); + } return NextResponse.json(MOCK_ONEREP_BROKERS(profileId, page, perPage)); } From fa16c41486cd1715c40bce737167d2f8530453bb Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Sat, 29 Jun 2024 03:23:21 -0700 Subject: [PATCH 023/137] fixed lint errors and corrected a POST endpoint --- src/app/api/mock/onerep/config/mockUser.json | 2 +- src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index b7e3b3ebbe5..e757d2af1b1 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -19,4 +19,4 @@ "meta": {}, "valid": false } -} \ No newline at end of file +} diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index 4bd1265e0a1..3a1a2a6e424 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -14,7 +14,10 @@ function getScanId(profileId: number) { return (profileId * MOCK_ONEREP_MAGIC_NUM_1()) % MOCK_ONEREP_MAGIC_NUM_2(); } -export function POST({ params }: { params: { profileId: number } }) { +export function POST( + _: NextRequest, + { params }: { params: { profileId: number } }, +) { const prodError = errorIfProduction(); if (prodError) return prodError; From f3378b0ceeda89f6c318f9b7078ded78219fb18e Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Sat, 29 Jun 2024 18:44:13 -0700 Subject: [PATCH 024/137] added form input --- .../admin/mock-config/onerep/ConfigPage.tsx | 170 ++++++++++++++++++ .../admin/mock-config/onerep/page.tsx | 23 +++ src/app/api/mock/onerep/config/config.ts | 49 +++-- src/app/api/mock/onerep/config/test.json | 3 +- .../profiles/[profileId]/scans/route.ts | 14 +- src/app/api/mock/onerep/scan-results/route.ts | 4 +- 6 files changed, 223 insertions(+), 40 deletions(-) create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/page.tsx diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx new file mode 100644 index 00000000000..39a6d091379 --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx @@ -0,0 +1,170 @@ +/* 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/. */ + +"use client"; + +import React, { useState } from "react"; +import { + MOCK_ONEREP_DATABROKER_ID_START, + MOCK_ONEREP_ID_START, + MOCK_ONEREP_SCAN_ID, + MOCK_ONEREP_STATUS, + MOCK_ONEREP_TIME, +} from "../../../../../../api/mock/onerep/config/config"; + +interface Broker { + id: number; + profile_id: number; + scan_id: number; + status: string; + first_name: string; + middle_name?: string | null; + last_name: string; + age?: number | null; + addresses: object[]; + phones: object[]; + emails: object[]; + relatives: object[]; + link: string; + data_broker: string; + data_broker_id: number; + optout_attempts: number; + created_at: string; + updated_at: string; +} + +const ConfigPage = () => { + const [brokers, setBrokers] = useState([]); + + // Initialize a base broker template to reset form fields + const baseBroker = { + id: -1, + profile_id: -1, + scan_id: -1, + status: MOCK_ONEREP_STATUS(), + first_name: "", + middle_name: null, + last_name: "", + age: null, + addresses: [], + phones: [], + emails: [], + relatives: [], + link: "", + data_broker: "", + data_broker_id: -1, + optout_attempts: 0, + created_at: MOCK_ONEREP_TIME(), + updated_at: MOCK_ONEREP_TIME(), + }; + + // Temporary state to hold form input for a new broker + const [newBroker, setNewBroker] = useState(baseBroker); + + const handleChange = (e: React.ChangeEvent) => { + setNewBroker({ ...newBroker, [e.target.name]: e.target.value }); + }; + + const handleAddBroker = (event: React.FormEvent) => { + event.preventDefault(); + + const profileId = Number(newBroker.profile_id); + const scandId = MOCK_ONEREP_SCAN_ID(profileId); + const brokerIdStart = MOCK_ONEREP_DATABROKER_ID_START(profileId); + const idStart = MOCK_ONEREP_ID_START(profileId); + + setBrokers([ + ...brokers, + { + ...newBroker, + id: idStart - brokers.length, + scan_id: scandId, + data_broker_id: brokerIdStart - brokers.length, + profile_id: profileId, + }, + ]); + setNewBroker(baseBroker); // Reset form fields + }; + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + try { + const response = brokers; + console.log("Response:", response); + alert("Brokers submitted successfully!"); + } catch (error) { + console.error("Error submitting brokers:", error); + alert("Failed to submit brokers."); + } + }; + + return ( +
+
+ + + + + + +
+
+

Brokers List

+
    + {brokers.map((broker) => ( +
  • + {broker.first_name} {broker.last_name} +
  • + ))} +
+
+ +
+ ); +}; + +export default ConfigPage; diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/page.tsx new file mode 100644 index 00000000000..3062648f0ff --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/page.tsx @@ -0,0 +1,23 @@ +/* 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 { getServerSession } from "../../../../../../functions/server/getServerSession"; +import { notFound } from "next/navigation"; +import { isAdmin } from "../../../../../../api/utils/auth"; +import ConfigPage from "./ConfigPage"; + +export default async function DevPage() { + const session = await getServerSession(); + + if ( + !session?.user?.email || + (!isAdmin(session.user.email) && + session.user.email !== process.env.E2E_TEST_ACCOUNT_EMAIL) || + process.env.APP_ENV !== "local" + ) { + return notFound(); + } + + return ; +} diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index d6a9a152e99..b7373b15507 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -9,7 +9,7 @@ import MockUser from "./mockUser.json"; interface Broker { id: number; - profile_id: string; + profile_id: number; scan_id: number; status: string; first_name: string; @@ -26,15 +26,27 @@ interface Broker { optout_attempts: number; created_at: string; updated_at: string; - url: string; } +const DEFAULT_NUMBER_BREACHES = 10; +const MAGIC_NUM_1 = 24623; +const MAGIC_NUM_2 = 2161; +const MAGIC_NUM_3 = 1013; + export function MOCK_ONEREP_PROFILE_ID() { return MockUser.PROFILE_ID; } -export function MOCK_ONEREP_MAGIC_NUM_0() { - return MockUser.MAGIC_NUM_0; +export function MOCK_ONEREP_SCAN_ID(profileId: number) { + return (profileId * MAGIC_NUM_1) % MAGIC_NUM_2; +} + +export function MOCK_ONEREP_DATABROKER_ID_START(profileId: number) { + return MockUser.MAGIC_NUM_0 * MOCK_ONEREP_SCAN_ID(profileId); +} + +export function MOCK_ONEREP_ID_START(profileId: number) { + return MOCK_ONEREP_DATABROKER_ID_START(profileId) % MAGIC_NUM_3; } export function MOCK_ONEREP_TIME() { @@ -70,30 +82,13 @@ export function MOCK_ONEREP_ADDRESSES() { })) as typeOfAddr; } -const DEFAULT_NUMBER_BREACHES = 10; -const MAGIC_NUM_brokerId = 78127; -const MAGIC_NUM_1 = 24623; -const MAGIC_NUM_2 = 2161; - -export function MOCK_ONEREP_MAGIC_NUM_1() { - return MAGIC_NUM_1; -} - -export function MOCK_ONEREP_MAGIC_NUM_2() { - return MAGIC_NUM_2; -} - -function getScanId(profileId: string) { - return (Number(profileId) * MAGIC_NUM_1) % MAGIC_NUM_2; -} - export function MOCK_ONEREP_BROKERS( - profileId: string, + profileId: number, page: string, perPage: string, ) { const mockResponseData = MockUser.BROKERS_LIST; - const latestScanId = getScanId(profileId); + const latestScanId = MOCK_ONEREP_SCAN_ID(profileId); const mockLinks = { first: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, @@ -136,7 +131,10 @@ export function MOCK_ONEREP_BROKERS( return mockResponseData; } - const idStart = latestScanId * MOCK_ONEREP_MAGIC_NUM_0(); + const idStart = MOCK_ONEREP_ID_START(profileId); + const idStartDataBroker = MOCK_ONEREP_DATABROKER_ID_START(profileId); + + //TODO: update array creation to be of type broker const responseData = { data: new Array(DEFAULT_NUMBER_BREACHES).fill(null).map((_, index) => ({ @@ -154,11 +152,10 @@ export function MOCK_ONEREP_BROKERS( relatives: [], link: `https://mockexample.com/link-to-databroker${index}`, data_broker: `mockexample${index}.com`, - data_broker_id: MAGIC_NUM_brokerId - index, + data_broker_id: idStartDataBroker - index, optout_attempts: 0, created_at: MOCK_ONEREP_TIME(), updated_at: MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}scan-results/${idStart - index}`, })), links: mockLinks, meta: mockMeta, diff --git a/src/app/api/mock/onerep/config/test.json b/src/app/api/mock/onerep/config/test.json index c8d3af447e7..f808d276593 100644 --- a/src/app/api/mock/onerep/config/test.json +++ b/src/app/api/mock/onerep/config/test.json @@ -18,8 +18,7 @@ "data_broker_id": 23, "optout_attempts": 0, "created_at": "2024-06-19T01:37:02+0000", - "updated_at": "2024-06-19T01:37:02+0000", - "url": "http://localhost:6060/api/mock/onerep/scan-results/45000" + "updated_at": "2024-06-19T01:37:02+0000" } ] } diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index 3a1a2a6e424..cbe6f1d755f 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -3,17 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextRequest, NextResponse } from "next/server"; -import { - MOCK_ONEREP_TIME, - MOCK_ONEREP_MAGIC_NUM_1, - MOCK_ONEREP_MAGIC_NUM_2, -} from "../../../config/config"; +import { MOCK_ONEREP_TIME, MOCK_ONEREP_SCAN_ID } from "../../../config/config"; import { errorIfProduction } from "../../../../utils/errorThrower"; -function getScanId(profileId: number) { - return (profileId * MOCK_ONEREP_MAGIC_NUM_1()) % MOCK_ONEREP_MAGIC_NUM_2(); -} - export function POST( _: NextRequest, { params }: { params: { profileId: number } }, @@ -26,7 +18,7 @@ export function POST( return NextResponse.json({ error: "Invalid profile ID" }); } - const scanId = getScanId(profileId); + const scanId = MOCK_ONEREP_SCAN_ID(profileId); const mockResponse = { id: scanId, @@ -56,7 +48,7 @@ export function GET( return NextResponse.json({ error: "Invalid profile ID" }); } - const scandId = getScanId(profileId); + const scandId = MOCK_ONEREP_SCAN_ID(profileId); const responseData = { data: [ diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts index f9d9f1eeefe..de3fc6a03e0 100644 --- a/src/app/api/mock/onerep/scan-results/route.ts +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -22,5 +22,7 @@ export function GET(req: NextRequest) { ); } - return NextResponse.json(MOCK_ONEREP_BROKERS(profileId, page, perPage)); + return NextResponse.json( + MOCK_ONEREP_BROKERS(Number(profileId), page, perPage), + ); } From 0a4e15bacae7812c109710dbf9ced3f82f051f76 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Sat, 29 Jun 2024 19:03:45 -0700 Subject: [PATCH 025/137] substituted direct magic numbers access to wrapper methods --- src/app/api/mock/onerep/config/config.ts | 49 +++++++++---------- .../profiles/[profileId]/activate/route.ts | 4 +- .../profiles/[profileId]/deactivate/route.ts | 6 +-- .../profiles/[profileId]/optout/route.ts | 4 +- .../[profileId]/scans/[scanId]/route.ts | 6 +-- .../profiles/[profileId]/scans/route.ts | 21 +++----- src/app/api/mock/onerep/scan-results/route.ts | 4 +- 7 files changed, 42 insertions(+), 52 deletions(-) diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index d6a9a152e99..b7373b15507 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -9,7 +9,7 @@ import MockUser from "./mockUser.json"; interface Broker { id: number; - profile_id: string; + profile_id: number; scan_id: number; status: string; first_name: string; @@ -26,15 +26,27 @@ interface Broker { optout_attempts: number; created_at: string; updated_at: string; - url: string; } +const DEFAULT_NUMBER_BREACHES = 10; +const MAGIC_NUM_1 = 24623; +const MAGIC_NUM_2 = 2161; +const MAGIC_NUM_3 = 1013; + export function MOCK_ONEREP_PROFILE_ID() { return MockUser.PROFILE_ID; } -export function MOCK_ONEREP_MAGIC_NUM_0() { - return MockUser.MAGIC_NUM_0; +export function MOCK_ONEREP_SCAN_ID(profileId: number) { + return (profileId * MAGIC_NUM_1) % MAGIC_NUM_2; +} + +export function MOCK_ONEREP_DATABROKER_ID_START(profileId: number) { + return MockUser.MAGIC_NUM_0 * MOCK_ONEREP_SCAN_ID(profileId); +} + +export function MOCK_ONEREP_ID_START(profileId: number) { + return MOCK_ONEREP_DATABROKER_ID_START(profileId) % MAGIC_NUM_3; } export function MOCK_ONEREP_TIME() { @@ -70,30 +82,13 @@ export function MOCK_ONEREP_ADDRESSES() { })) as typeOfAddr; } -const DEFAULT_NUMBER_BREACHES = 10; -const MAGIC_NUM_brokerId = 78127; -const MAGIC_NUM_1 = 24623; -const MAGIC_NUM_2 = 2161; - -export function MOCK_ONEREP_MAGIC_NUM_1() { - return MAGIC_NUM_1; -} - -export function MOCK_ONEREP_MAGIC_NUM_2() { - return MAGIC_NUM_2; -} - -function getScanId(profileId: string) { - return (Number(profileId) * MAGIC_NUM_1) % MAGIC_NUM_2; -} - export function MOCK_ONEREP_BROKERS( - profileId: string, + profileId: number, page: string, perPage: string, ) { const mockResponseData = MockUser.BROKERS_LIST; - const latestScanId = getScanId(profileId); + const latestScanId = MOCK_ONEREP_SCAN_ID(profileId); const mockLinks = { first: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, @@ -136,7 +131,10 @@ export function MOCK_ONEREP_BROKERS( return mockResponseData; } - const idStart = latestScanId * MOCK_ONEREP_MAGIC_NUM_0(); + const idStart = MOCK_ONEREP_ID_START(profileId); + const idStartDataBroker = MOCK_ONEREP_DATABROKER_ID_START(profileId); + + //TODO: update array creation to be of type broker const responseData = { data: new Array(DEFAULT_NUMBER_BREACHES).fill(null).map((_, index) => ({ @@ -154,11 +152,10 @@ export function MOCK_ONEREP_BROKERS( relatives: [], link: `https://mockexample.com/link-to-databroker${index}`, data_broker: `mockexample${index}.com`, - data_broker_id: MAGIC_NUM_brokerId - index, + data_broker_id: idStartDataBroker - index, optout_attempts: 0, created_at: MOCK_ONEREP_TIME(), updated_at: MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}scan-results/${idStart - index}`, })), links: mockLinks, meta: mockMeta, diff --git a/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts index 74b07b24eef..3bedaef54e5 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts @@ -7,12 +7,12 @@ import { errorIfProduction } from "../../../../utils/errorThrower"; export function PUT( _: NextRequest, - { params }: { params: { profileId: string } }, + { params }: { params: { profileId: number } }, ) { const prodError = errorIfProduction(); if (prodError) return prodError; - const profileId: number = Number(params.profileId); + const profileId: number = params.profileId; if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts index 815143010bb..a4dc0e0def8 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts @@ -7,15 +7,15 @@ import { errorIfProduction } from "../../../../utils/errorThrower"; export function PUT( _: NextRequest, - { params }: { params: { profileId: string } }, + { params }: { params: { profileId: number } }, ) { const prodError = errorIfProduction(); if (prodError) return prodError; - const profileId: number = Number(params.profileId); + const profileId: number = params.profileId; if (!profileId || isNaN(profileId)) { - return NextResponse.json({ error: "Invalid profile ID" }); + return NextResponse.json({ status: 400, error: "Invalid profile ID" }); } return NextResponse.json({ diff --git a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts index 5ea5e3c37ff..b28fb2d8885 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts @@ -7,12 +7,12 @@ import { errorIfProduction } from "../../../../utils/errorThrower"; export function POST( _: NextRequest, - { params }: { params: { profileId: string } }, + { params }: { params: { profileId: number } }, ) { const prodError = errorIfProduction(); if (prodError) return prodError; - const profileId: number = Number(params.profileId); + const profileId: number = params.profileId; if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts index 7297aabcab4..7864613bc77 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts @@ -8,13 +8,13 @@ import { NextRequest, NextResponse } from "next/server"; export function GET( _: NextRequest, - { params }: { params: { profileId: string; scandId: string } }, + { params }: { params: { profileId: number; scanId: number } }, ) { const prodError = errorIfProduction(); if (prodError) return prodError; - const profileId: number = Number(params.profileId); - const scanId: number = Number(params.scandId); + const profileId: number = params.profileId; + const scanId: number = params.scanId; const responseData = { id: scanId, diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index 3a1a2a6e424..1ad2ccc6b22 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -3,17 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextRequest, NextResponse } from "next/server"; -import { - MOCK_ONEREP_TIME, - MOCK_ONEREP_MAGIC_NUM_1, - MOCK_ONEREP_MAGIC_NUM_2, -} from "../../../config/config"; +import { MOCK_ONEREP_SCAN_ID, MOCK_ONEREP_TIME } from "../../../config/config"; import { errorIfProduction } from "../../../../utils/errorThrower"; -function getScanId(profileId: number) { - return (profileId * MOCK_ONEREP_MAGIC_NUM_1()) % MOCK_ONEREP_MAGIC_NUM_2(); -} - export function POST( _: NextRequest, { params }: { params: { profileId: number } }, @@ -22,11 +14,12 @@ export function POST( if (prodError) return prodError; const profileId: number = params.profileId; + if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); } - const scanId = getScanId(profileId); + const scanId = MOCK_ONEREP_SCAN_ID(profileId); const mockResponse = { id: scanId, @@ -43,20 +36,18 @@ export function POST( export function GET( _: NextRequest, - { params }: { params: { profileId: string } }, + { params }: { params: { profileId: number } }, ) { const prodError = errorIfProduction(); if (prodError) return prodError; - // Extract profileId from query parameters or request body - // const profileId: number = await extractProfileId(req) - const profileId: number = Number(params.profileId); + const profileId: number = params.profileId; if (!profileId || isNaN(profileId)) { return NextResponse.json({ error: "Invalid profile ID" }); } - const scandId = getScanId(profileId); + const scandId = MOCK_ONEREP_SCAN_ID(profileId); const responseData = { data: [ diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts index f9d9f1eeefe..de3fc6a03e0 100644 --- a/src/app/api/mock/onerep/scan-results/route.ts +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -22,5 +22,7 @@ export function GET(req: NextRequest) { ); } - return NextResponse.json(MOCK_ONEREP_BROKERS(profileId, page, perPage)); + return NextResponse.json( + MOCK_ONEREP_BROKERS(Number(profileId), page, perPage), + ); } From 06b25ca8b128355487947410e0d21114c5c5a6b2 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Sat, 29 Jun 2024 21:46:28 -0700 Subject: [PATCH 026/137] mock endpoint config - basic version --- .../mock-config/onerep/ConfigPage.module.scss | 68 +++++++ .../admin/mock-config/onerep/ConfigPage.tsx | 177 +++++++++--------- src/app/api/mock/onerep/config/config.ts | 12 +- src/app/api/mock/onerep/config/mockUser.json | 4 +- src/app/api/mock/onerep/config/route.ts | 45 ++--- src/app/api/mock/onerep/config/test.json | 2 +- src/db/tables/onerep_scans.ts | 60 +++--- 7 files changed, 222 insertions(+), 146 deletions(-) create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.module.scss diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.module.scss b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.module.scss new file mode 100644 index 00000000000..c93a2ed6ef3 --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.module.scss @@ -0,0 +1,68 @@ +@import "../../../../../../tokens"; + +.wrapper { + display: flex; + flex-direction: column; + gap: $spacing-2xl; + background-color: $color-grey-05; + height: 100%; + padding: $layout-lg $layout-2xl; + + @media screen and (max-width: $screen-lg) { + padding: $spacing-lg; + } +} + +.header { + font: $text-title-xs; + font-weight: normal; + + b { + font-weight: bold; + } +} + +.form { + display: flex; + flex-direction: column; + gap: $spacing-2xl; + + .userPicker { + flex: 1 0 auto; + align-items: center; + display: flex; + flex-wrap: wrap; + gap: $spacing-2xl; + min-height: $layout-2xl; + + label { + display: flex; + flex-direction: column; + gap: $spacing-sm; + min-width: 50%; + } + + input { + padding: $spacing-sm; + font: $text-body-md; + } + } + + .actions { + display: flex; + flex-wrap: wrap; + gap: $spacing-xl; + + button { + flex: 1 1 25%; + } + } +} + +.status { + background-color: $color-yellow-05; + border: 2px solid $color-yellow-20; + border-radius: $border-radius-sm; + padding: $spacing-md $spacing-lg; + font: $text-body-md; +} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx index 39a6d091379..6f7e2902031 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx @@ -9,48 +9,30 @@ import { MOCK_ONEREP_DATABROKER_ID_START, MOCK_ONEREP_ID_START, MOCK_ONEREP_SCAN_ID, - MOCK_ONEREP_STATUS, MOCK_ONEREP_TIME, + Broker, } from "../../../../../../api/mock/onerep/config/config"; - -interface Broker { - id: number; - profile_id: number; - scan_id: number; - status: string; - first_name: string; - middle_name?: string | null; - last_name: string; - age?: number | null; - addresses: object[]; - phones: object[]; - emails: object[]; - relatives: object[]; - link: string; - data_broker: string; - data_broker_id: number; - optout_attempts: number; - created_at: string; - updated_at: string; -} +import { useSession } from "next-auth/react"; +import styles from "./ConfigPage.module.scss"; const ConfigPage = () => { const [brokers, setBrokers] = useState([]); + const session = useSession(); // Initialize a base broker template to reset form fields const baseBroker = { id: -1, profile_id: -1, scan_id: -1, - status: MOCK_ONEREP_STATUS(), - first_name: "", + status: "new", + first_name: "Johnny", middle_name: null, - last_name: "", + last_name: "Doe", age: null, - addresses: [], - phones: [], - emails: [], - relatives: [], + addresses: ["address0"], + phones: ["phone0"], + emails: ["email0"], + relatives: ["relative0"], link: "", data_broker: "", data_broker_id: -1, @@ -90,66 +72,88 @@ const ConfigPage = () => { const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); try { - const response = brokers; - console.log("Response:", response); - alert("Brokers submitted successfully!"); + void makeConfigRequest(false); + alert("Brokers list update request submitted successfully!"); } catch (error) { console.error("Error submitting brokers:", error); alert("Failed to submit brokers."); } }; + const makeConfigRequest = async (erase: boolean) => { + return fetch("/api/mock/onerep/config", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: session.data?.user.email, + brokers: brokers, + erase: erase, + }), + }).then(async (resp) => + console.log("Response from endpoint config:", await resp.json()), + ); + }; + return ( -
-
- - - - - +
+
+ Changing the default response for OneRep mock endpoint. +
+ +
+ + + + + +
@@ -162,8 +166,13 @@ const ConfigPage = () => { ))}
- -
+ + + ); }; diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index b7373b15507..216488247d2 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -7,7 +7,7 @@ import { StateAbbr } from "../../../../../utils/states"; import MockUser from "./mockUser.json"; // import { getLatestOnerepScanResults } from "../../../../../db/tables/onerep_scans"; -interface Broker { +export interface Broker { id: number; profile_id: number; scan_id: number; @@ -16,10 +16,10 @@ interface Broker { middle_name?: string | null; last_name: string; age?: number | null; - addresses: object[]; - phones: object[]; - emails: object[]; - relatives: object[]; + addresses: string[]; + phones: string[]; + emails: string[]; + relatives: string[]; link: string; data_broker: string; data_broker_id: number; @@ -128,7 +128,7 @@ export function MOCK_ONEREP_BROKERS( }); } - return mockResponseData; + return response; } const idStart = MOCK_ONEREP_ID_START(profileId); diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index e757d2af1b1..2fc1db3a690 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -1,6 +1,6 @@ { "PROFILE_ID": 777, - "MAGIC_NUM_0": 83597, + "MAGIC_NUM_0": 85136, "TIME": "2024-06-19T01:37:02+0000", "FIRSTNAME": "John", "LASTNAME": "Doe", @@ -19,4 +19,4 @@ "meta": {}, "valid": false } -} +} \ No newline at end of file diff --git a/src/app/api/mock/onerep/config/route.ts b/src/app/api/mock/onerep/config/route.ts index b5cf3a03465..3bb56ebf10b 100644 --- a/src/app/api/mock/onerep/config/route.ts +++ b/src/app/api/mock/onerep/config/route.ts @@ -3,10 +3,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextRequest, NextResponse } from "next/server"; +import { randomInt } from "crypto"; +// import { logger } from "../../../../functions/server/logging"; +import { isAdmin } from "../../../utils/auth"; import fs from "fs"; import path from "path"; -import { randomInt } from "crypto"; -import { logger } from "../../../../functions/server/logging"; +import { Broker } from "./config"; /* @@ -14,7 +16,7 @@ Example fetch, where obj conforms to Broker interface in config.ts Set erase to true if you want to use default response, or false if obj fetch('/api/mock/onerep/config', { - method: 'POST', + method: 'PUT', headers: { 'Content-Type': 'application/json', }, @@ -25,19 +27,28 @@ fetch('/api/mock/onerep/config', { }) .then(response => response.json()).then(data => console.log(data)) - */ -export function POST() { - logger.info(`Attempted to access ${updateJsonFile.name}`); +type onerepConfigReq = { + email: string; + erase?: boolean; + brokers: [Broker]; +}; - return NextResponse.json( - { error: "Endpoint not available yet" }, - { status: 403 }, - ); +export async function PUT(req: NextRequest) { + const data = await req.json(); + const { email, erase = false, brokers } = data as onerepConfigReq; + + if (!isAdmin(email)) { + return NextResponse.json( + { error: "Unauthorized to access the endpoint" }, + { status: 401 }, + ); + } + return updateJsonFile(erase, brokers); } -async function updateJsonFile(req: NextRequest) { +function updateJsonFile(erase: boolean, brokers: [Broker]) { // Define the path to the JSON file const jsonFilePath = path.join( process.cwd(), @@ -49,22 +60,12 @@ async function updateJsonFile(req: NextRequest) { const jsonData = JSON.parse(fileData); try { - const newData = await req.json(); - const erase = newData.erase; - if (erase) { jsonData.BROKERS_LIST.data = []; jsonData.BROKERS_LIST.valid = false; jsonData.MAGIC_NUM_0 = randomInt(1000, 100000); } else { - if (!newData.brokers || !newData.brokers.data) { - return NextResponse.json( - { error: "Bad Request: Data format is unexpected!" }, - { status: 400 }, - ); - } - - jsonData.BROKERS_LIST.data = newData.brokers.data; + jsonData.BROKERS_LIST.data = brokers; jsonData.BROKERS_LIST.valid = true; } diff --git a/src/app/api/mock/onerep/config/test.json b/src/app/api/mock/onerep/config/test.json index f808d276593..31d485d35c4 100644 --- a/src/app/api/mock/onerep/config/test.json +++ b/src/app/api/mock/onerep/config/test.json @@ -1,5 +1,5 @@ { - "data": [ + "brokers": [ { "id": 45000, "profile_id": -1, diff --git a/src/db/tables/onerep_scans.ts b/src/db/tables/onerep_scans.ts index 04913d616cd..a3490cef99c 100644 --- a/src/db/tables/onerep_scans.ts +++ b/src/db/tables/onerep_scans.ts @@ -16,6 +16,7 @@ import { SubscriberRow, } from "knex/types/tables"; import { RemovalStatus } from "../../app/functions/universal/scanResult.js"; +import { MOCK_ONEREP_SCAN_ID } from "../../app/api/mock/onerep/config/config.ts"; const knex = createDbConnection(); @@ -239,38 +240,35 @@ async function addOnerepScanResults( }), }); - if (scanResultsMap.length > 0) { - const scan_id = scanResultsMap[0].onerep_scan_id; - - // Delete previous records to allow dynamic mock data configuration, maintaining the 'manually_resolved' field. - if (process.env.ONEREP_API_BASE!.includes("/api/mock")) { - const existingRecords = await knex("onerep_scan_results") - .where("onerep_scan_id", scan_id) - .select("onerep_scan_result_id", "manually_resolved"); - - const resolvedStatusMap = new Map(); - existingRecords.forEach((record) => { - resolvedStatusMap.set( - record.onerep_scan_result_id, - record.manually_resolved, - ); - }); - - await knex("onerep_scan_results").where("onerep_scan_id", scan_id).del(); - - scanResultsMap = scanResultsMap.map((item) => { - if (resolvedStatusMap.has(item.onerep_scan_result_id)) { - return { - ...item, - manually_resolved: resolvedStatusMap.get( - item.onerep_scan_result_id, - ), - }; - } - return item; - }); - } + // Delete previous records to allow dynamic mock data configuration, maintaining the 'manually_resolved' field. + if (process.env.ONEREP_API_BASE!.includes("/api/mock")) { + const scan_id = MOCK_ONEREP_SCAN_ID(onerepProfileId); + const existingRecords = await knex("onerep_scan_results") + .where("onerep_scan_id", scan_id) + .select("onerep_scan_result_id", "manually_resolved"); + + const resolvedStatusMap = new Map(); + existingRecords.forEach((record) => { + resolvedStatusMap.set( + record.onerep_scan_result_id, + record.manually_resolved, + ); + }); + + await knex("onerep_scan_results").where("onerep_scan_id", scan_id).del(); + scanResultsMap = scanResultsMap.map((item) => { + if (resolvedStatusMap.has(item.onerep_scan_result_id)) { + return { + ...item, + manually_resolved: resolvedStatusMap.get(item.onerep_scan_result_id), + }; + } + return item; + }); + } + + if (scanResultsMap.length > 0) { await knex("onerep_scan_results") .insert(scanResultsMap) .onConflict("onerep_scan_result_id") From cf0ebf6980eb3b02e214d982d32d5d552eab6e4a Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Sat, 29 Jun 2024 22:38:10 -0700 Subject: [PATCH 027/137] added more type checks and proper defaults --- .../admin/mock-config/onerep/ConfigPage.tsx | 18 +++-- src/app/api/mock/onerep/config/config.ts | 67 ++++++++++--------- src/app/api/mock/onerep/config/mockUser.json | 15 +++-- src/app/api/mock/onerep/config/route.ts | 6 +- src/db/tables/onerep_scans.ts | 5 +- 5 files changed, 66 insertions(+), 45 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx index 6f7e2902031..1350164f66d 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx @@ -11,6 +11,12 @@ import { MOCK_ONEREP_SCAN_ID, MOCK_ONEREP_TIME, Broker, + MOCK_ONEREP_FIRSTNAME, + MOCK_ONEREP_LASTNAME, + MOCK_ONEREP_ADDRESSES, + MOCK_ONEREP_EMAILS, + MOCK_ONEREP_RELATIVES, + MOCK_ONEREP_PHONES, } from "../../../../../../api/mock/onerep/config/config"; import { useSession } from "next-auth/react"; import styles from "./ConfigPage.module.scss"; @@ -25,14 +31,14 @@ const ConfigPage = () => { profile_id: -1, scan_id: -1, status: "new", - first_name: "Johnny", + first_name: MOCK_ONEREP_FIRSTNAME(), middle_name: null, - last_name: "Doe", + last_name: MOCK_ONEREP_LASTNAME(), age: null, - addresses: ["address0"], - phones: ["phone0"], - emails: ["email0"], - relatives: ["relative0"], + addresses: [MOCK_ONEREP_ADDRESSES()], + phones: MOCK_ONEREP_PHONES(), + emails: MOCK_ONEREP_EMAILS(), + relatives: MOCK_ONEREP_RELATIVES(), link: "", data_broker: "", data_broker_id: -1, diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index 216488247d2..8a9d15956ab 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -16,7 +16,7 @@ export interface Broker { middle_name?: string | null; last_name: string; age?: number | null; - addresses: string[]; + addresses: object[]; phones: string[]; emails: string[]; relatives: string[]; @@ -33,10 +33,6 @@ const MAGIC_NUM_1 = 24623; const MAGIC_NUM_2 = 2161; const MAGIC_NUM_3 = 1013; -export function MOCK_ONEREP_PROFILE_ID() { - return MockUser.PROFILE_ID; -} - export function MOCK_ONEREP_SCAN_ID(profileId: number) { return (profileId * MAGIC_NUM_1) % MAGIC_NUM_2; } @@ -65,8 +61,16 @@ export function MOCK_ONEREP_BIRTHDATE() { return MockUser.BIRTHDATE; } -export function MOCK_ONEREP_EMAIL() { - return MockUser.EMAIL; +export function MOCK_ONEREP_EMAILS() { + return MockUser.EMAILS; +} + +export function MOCK_ONEREP_PHONES() { + return MockUser.PHONES; +} + +export function MOCK_ONEREP_RELATIVES() { + return MockUser.RELATIVES; } export function MOCK_ONEREP_STATUS() { @@ -88,7 +92,7 @@ export function MOCK_ONEREP_BROKERS( perPage: string, ) { const mockResponseData = MockUser.BROKERS_LIST; - const latestScanId = MOCK_ONEREP_SCAN_ID(profileId); + const scanId = MOCK_ONEREP_SCAN_ID(profileId); const mockLinks = { first: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, @@ -123,7 +127,7 @@ export function MOCK_ONEREP_BROKERS( return { ...(broker as Broker), profile_id: profileId, - scan_id: latestScanId, + scan_id: scanId, }; }); } @@ -134,29 +138,30 @@ export function MOCK_ONEREP_BROKERS( const idStart = MOCK_ONEREP_ID_START(profileId); const idStartDataBroker = MOCK_ONEREP_DATABROKER_ID_START(profileId); - //TODO: update array creation to be of type broker - const responseData = { - data: new Array(DEFAULT_NUMBER_BREACHES).fill(null).map((_, index) => ({ - id: idStart - index, - profile_id: profileId, - scan_id: latestScanId, - status: "new", - first_name: MOCK_ONEREP_FIRSTNAME(), - middle_name: null, - last_name: MOCK_ONEREP_LASTNAME(), - age: null, - addresses: [], - phones: [], - emails: [MOCK_ONEREP_EMAIL()], - relatives: [], - link: `https://mockexample.com/link-to-databroker${index}`, - data_broker: `mockexample${index}.com`, - data_broker_id: idStartDataBroker - index, - optout_attempts: 0, - created_at: MOCK_ONEREP_TIME(), - updated_at: MOCK_ONEREP_TIME(), - })), + data: new Array(DEFAULT_NUMBER_BREACHES).fill(null).map( + (_, index) => + ({ + id: idStart - index, + profile_id: profileId, + scan_id: scanId, + status: "new", + first_name: MOCK_ONEREP_FIRSTNAME(), + middle_name: null, + last_name: MOCK_ONEREP_LASTNAME(), + age: null, + addresses: [MOCK_ONEREP_ADDRESSES()], + phones: MOCK_ONEREP_PHONES(), + emails: MOCK_ONEREP_EMAILS(), + relatives: MOCK_ONEREP_RELATIVES(), + link: `https://mockexample.com/link-to-databroker${index}`, + data_broker: `mockexample${index}.com`, + data_broker_id: idStartDataBroker - index, + optout_attempts: 0, + created_at: MOCK_ONEREP_TIME(), + updated_at: MOCK_ONEREP_TIME(), + }) as Broker, + ), links: mockLinks, meta: mockMeta, }; diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index 2fc1db3a690..7d8345ccdf5 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -1,11 +1,12 @@ { - "PROFILE_ID": 777, - "MAGIC_NUM_0": 85136, + "MAGIC_NUM_0": 77013, "TIME": "2024-06-19T01:37:02+0000", "FIRSTNAME": "John", "LASTNAME": "Doe", "BIRTHDATE": "2000-01-01", - "EMAIL": "JohnDoe@JohnDoe.com", + "EMAILS": [ + "JohnDoe@JohnDoe.com" + ], "STATUS": "active", "ADDRESSES": [ { @@ -13,10 +14,16 @@ "state": "CA" } ], + "PHONES": [ + "00000000" + ], + "RELATIVES": [ + "Bob Doe" + ], "BROKERS_LIST": { "data": [], "links": {}, "meta": {}, "valid": false } -} \ No newline at end of file +} diff --git a/src/app/api/mock/onerep/config/route.ts b/src/app/api/mock/onerep/config/route.ts index 3bb56ebf10b..af981ff3116 100644 --- a/src/app/api/mock/onerep/config/route.ts +++ b/src/app/api/mock/onerep/config/route.ts @@ -69,7 +69,11 @@ function updateJsonFile(erase: boolean, brokers: [Broker]) { jsonData.BROKERS_LIST.valid = true; } - fs.writeFileSync(jsonFilePath, JSON.stringify(jsonData, null, 2), "utf8"); + fs.writeFileSync( + jsonFilePath, + JSON.stringify(jsonData, null, 2) + "\n", + "utf8", + ); return NextResponse.json( { message: "JSON data has been successfully updated." }, diff --git a/src/db/tables/onerep_scans.ts b/src/db/tables/onerep_scans.ts index a3490cef99c..b6988f71481 100644 --- a/src/db/tables/onerep_scans.ts +++ b/src/db/tables/onerep_scans.ts @@ -255,8 +255,6 @@ async function addOnerepScanResults( ); }); - await knex("onerep_scan_results").where("onerep_scan_id", scan_id).del(); - scanResultsMap = scanResultsMap.map((item) => { if (resolvedStatusMap.has(item.onerep_scan_result_id)) { return { @@ -266,8 +264,9 @@ async function addOnerepScanResults( } return item; }); - } + await knex("onerep_scan_results").where("onerep_scan_id", scan_id).del(); + } if (scanResultsMap.length > 0) { await knex("onerep_scan_results") .insert(scanResultsMap) From 9fed1645c9d5784ee59fdaf20dfa7c0272634c06 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Sun, 30 Jun 2024 01:08:24 -0700 Subject: [PATCH 028/137] functional OneRep config page, with styling --- .../mock-config/onerep/ConfigPage.module.scss | 130 ++++++++--- .../admin/mock-config/onerep/ConfigPage.tsx | 208 +++++++++++------- src/app/api/mock/onerep/config/config.ts | 2 +- src/app/api/mock/onerep/config/mockUser.json | 2 +- 4 files changed, 237 insertions(+), 105 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.module.scss b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.module.scss index c93a2ed6ef3..674fe11cb8a 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.module.scss +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.module.scss @@ -1,68 +1,140 @@ @import "../../../../../../tokens"; .wrapper { - display: flex; - flex-direction: column; - gap: $spacing-2xl; - background-color: $color-grey-05; + display: grid; + grid-template-rows: 120px min-content; + gap: $spacing-md; height: 100%; - padding: $layout-lg $layout-2xl; - - @media screen and (max-width: $screen-lg) { - padding: $spacing-lg; - } + padding: $layout-sm; + background-color: $color-grey-05; + align-items: center; + justify-content: center; } .header { font: $text-title-xs; font-weight: normal; - + margin: auto; + width: 100%; b { font-weight: bold; } + text-align: center; +} + +.formAndListWrapper { + margin-top: 40px; + display: grid; + grid-template-rows: 1fr; + grid-template-columns: 3fr 2fr; + justify-content: center; +} + +.formWrapper { + display: grid; + grid-template-rows: 50px 4fr 100px; + justify-content: center; + h2 { + text-align: center; + } +} + +.listButtonsWrapper { + display: grid; + grid-template-rows: auto 100px; + justify-content: center; +} + +.listContainer { + max-height: 250px; + overflow-y: auto; +} + +.buttonsWrapper { + height: auto; + display: grid; + grid-template-columns: 1fr 1fr; +} + +.listWrapper { + height: min-content; + display: grid; + grid-template-rows: 50px auto; + justify-content: center; + align-items: flex-start; + + p { + color: #858585; + } +} + +.button { + display: block; + margin: auto; + max-height: 100px; + max-width: 100px; + margin-top: 10px; + margin-bottom: 10px; +} + +.buttonUnderList { + margin-left: 10px; + margin-right: 10px; +} + +@keyframes shake { + 0% { + transform: translateX(0); + } + 25% { + transform: translateX(-5px); + } + 75% { + transform: translateX(5px); + } + 100% { + transform: translateX(0); + } +} + +.error { + border-color: red; /* Example: Highlight border in red for error */ + animation: shake 0.5s ease-in-out; /* Example animation */ } .form { display: flex; flex-direction: column; - gap: $spacing-2xl; + gap: $spacing-sm; + align-items: center; + width: min-content; .userPicker { flex: 1 0 auto; align-items: center; display: flex; flex-wrap: wrap; - gap: $spacing-2xl; - min-height: $layout-2xl; + gap: $spacing-md; + min-height: $layout-md; label { display: flex; flex-direction: column; gap: $spacing-sm; min-width: 50%; + width: 350px; } input { padding: $spacing-sm; font: $text-body-md; - } - } - - .actions { - display: flex; - flex-wrap: wrap; - gap: $spacing-xl; - - button { - flex: 1 1 25%; + transition: border-color 0.3s ease; } } } -.status { - background-color: $color-yellow-05; - border: 2px solid $color-yellow-20; - border-radius: $border-radius-sm; - padding: $spacing-md $spacing-lg; - font: $text-body-md; +.listItem:hover { + background-color: #f0f0f0; /* Light grey background on hover */ + cursor: pointer; /* Change cursor to indicate clickable item */ + text-decoration: line-through; } diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx index 1350164f66d..f702b6a0248 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx @@ -25,6 +25,12 @@ const ConfigPage = () => { const [brokers, setBrokers] = useState([]); const session = useSession(); + const [errors, setErrors] = useState({ + profile_id: false, + data_broker: false, + link: false, + }); + // Initialize a base broker template to reset form fields const baseBroker = { id: -1, @@ -35,7 +41,7 @@ const ConfigPage = () => { middle_name: null, last_name: MOCK_ONEREP_LASTNAME(), age: null, - addresses: [MOCK_ONEREP_ADDRESSES()], + addresses: MOCK_ONEREP_ADDRESSES(), phones: MOCK_ONEREP_PHONES(), emails: MOCK_ONEREP_EMAILS(), relatives: MOCK_ONEREP_RELATIVES(), @@ -57,6 +63,34 @@ const ConfigPage = () => { const handleAddBroker = (event: React.FormEvent) => { event.preventDefault(); + let hasError = false; + + if (newBroker.profile_id < 0) { + setErrors({ ...errors, profile_id: true }); + hasError = true; + } else { + setErrors({ ...errors, profile_id: false }); + } + + if (newBroker.data_broker.length === 0) { + setErrors({ ...errors, data_broker: true }); + hasError = true; + } else { + setErrors({ ...errors, data_broker: false }); + } + + let linkString = ""; + try { + const urlObj = new URL(newBroker.link); + linkString = urlObj.href; + setErrors({ ...errors, link: false }); + } catch { + setErrors({ ...errors, link: true }); + hasError = true; + } + + if (hasError) return; + const profileId = Number(newBroker.profile_id); const scandId = MOCK_ONEREP_SCAN_ID(profileId); const brokerIdStart = MOCK_ONEREP_DATABROKER_ID_START(profileId); @@ -66,12 +100,14 @@ const ConfigPage = () => { ...brokers, { ...newBroker, + link: linkString, id: idStart - brokers.length, scan_id: scandId, data_broker_id: brokerIdStart - brokers.length, profile_id: profileId, }, ]); + setNewBroker(baseBroker); // Reset form fields }; @@ -79,15 +115,18 @@ const ConfigPage = () => { event.preventDefault(); try { void makeConfigRequest(false); - alert("Brokers list update request submitted successfully!"); } catch (error) { console.error("Error submitting brokers:", error); alert("Failed to submit brokers."); } }; - const makeConfigRequest = async (erase: boolean) => { - return fetch("/api/mock/onerep/config", { + const handleDeleteBroker = (id: number) => { + setBrokers(brokers.filter((broker) => broker.id !== id)); + }; + + const makeConfigRequest = (erase: boolean) => { + void fetch("/api/mock/onerep/config", { method: "PUT", headers: { "Content-Type": "application/json", @@ -100,84 +139,105 @@ const ConfigPage = () => { }).then(async (resp) => console.log("Response from endpoint config:", await resp.json()), ); + alert("Brokers list update request submitted successfully!"); }; return (
- Changing the default response for OneRep mock endpoint. +
+ Changing the default response for OneRep mock endpoint +
+
-
-
- - - - - + +
+ +

Input Broker Element

+
+
+ + + + + +
+
+ + +
+
+

Brokers List

+
    + {brokers.length !== 0 ? ( + brokers.map((broker) => ( +
  • handleDeleteBroker(broker.id)} + className={styles.listItem} + > + {"{"} {broker.profile_id}, {broker.data_broker},{" "} + {broker.link} {"}"} +
  • + )) + ) : ( +

    empty...

    + )} +
+
+ +
+ + + +
- - -
-

Brokers List

-
    - {brokers.map((broker) => ( -
  • - {broker.first_name} {broker.last_name} -
  • - ))} -
- -
); }; diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index 8a9d15956ab..ba326edc421 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -150,7 +150,7 @@ export function MOCK_ONEREP_BROKERS( middle_name: null, last_name: MOCK_ONEREP_LASTNAME(), age: null, - addresses: [MOCK_ONEREP_ADDRESSES()], + addresses: MOCK_ONEREP_ADDRESSES(), phones: MOCK_ONEREP_PHONES(), emails: MOCK_ONEREP_EMAILS(), relatives: MOCK_ONEREP_RELATIVES(), diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index 7d8345ccdf5..01b7876d109 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -1,5 +1,5 @@ { - "MAGIC_NUM_0": 77013, + "MAGIC_NUM_0": 40776, "TIME": "2024-06-19T01:37:02+0000", "FIRSTNAME": "John", "LASTNAME": "Doe", From c98d0b237137506c13132fdba57479f34901571c Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Sun, 30 Jun 2024 01:12:45 -0700 Subject: [PATCH 029/137] lint error --- src/app/api/mock/onerep/config/mockUser.json | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index 01b7876d109..4d0f1aba4a9 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -4,9 +4,7 @@ "FIRSTNAME": "John", "LASTNAME": "Doe", "BIRTHDATE": "2000-01-01", - "EMAILS": [ - "JohnDoe@JohnDoe.com" - ], + "EMAILS": ["JohnDoe@JohnDoe.com"], "STATUS": "active", "ADDRESSES": [ { @@ -14,12 +12,8 @@ "state": "CA" } ], - "PHONES": [ - "00000000" - ], - "RELATIVES": [ - "Bob Doe" - ], + "PHONES": ["00000000"], + "RELATIVES": ["Bob Doe"], "BROKERS_LIST": { "data": [], "links": {}, From f8e6b52ea3adbee2aadcfef42d24d10e5a29bf46 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Mon, 1 Jul 2024 02:21:02 -0700 Subject: [PATCH 030/137] added a configuration endpoint and page for HIBP --- .../{onerep => }/ConfigPage.module.scss | 72 +++++++++- .../admin/mock-config/hibp/breachesLookup.tsx | 56 ++++++++ .../admin/mock-config/hibp/hibpConfig.tsx | 135 ++++++++++++++++++ .../admin/mock-config/hibp/page.tsx | 31 ++++ .../{ConfigPage.tsx => onerepConfig.tsx} | 4 +- .../admin/mock-config/onerep/page.tsx | 2 +- src/app/api/mock/hibp/breaches/route.ts | 35 ++--- src/app/api/mock/hibp/config/defaults.ts | 16 +++ src/app/api/mock/hibp/config/route.ts | 74 ++++++++++ src/app/api/mock/hibp/data/fakeBreaches.json | 8 -- ...eAllBreaches.json => mockAllBreaches.json} | 9 +- src/app/api/mock/hibp/data/mockBreaches.json | 12 ++ .../hibp/range/search/[hashPrefix]/route.ts | 22 +-- src/app/api/mock/onerep/config/mockUser.json | 14 +- src/app/api/mock/onerep/config/route.ts | 32 ++--- src/app/api/mock/utils/errorThrower.ts | 14 +- 16 files changed, 451 insertions(+), 85 deletions(-) rename src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/{onerep => }/ConfigPage.module.scss (67%) create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/breachesLookup.tsx create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/hibpConfig.tsx create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/page.tsx rename src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/{ConfigPage.tsx => onerepConfig.tsx} (99%) create mode 100644 src/app/api/mock/hibp/config/defaults.ts create mode 100644 src/app/api/mock/hibp/config/route.ts delete mode 100644 src/app/api/mock/hibp/data/fakeBreaches.json rename src/app/api/mock/hibp/data/{fakeAllBreaches.json => mockAllBreaches.json} (96%) create mode 100644 src/app/api/mock/hibp/data/mockBreaches.json diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.module.scss b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/ConfigPage.module.scss similarity index 67% rename from src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.module.scss rename to src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/ConfigPage.module.scss index 674fe11cb8a..e5fe8ca3772 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.module.scss +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/ConfigPage.module.scss @@ -1,4 +1,4 @@ -@import "../../../../../../tokens"; +@import "../../../../../tokens"; .wrapper { display: grid; @@ -11,6 +11,11 @@ justify-content: center; } +.wrapperHibp { + grid-template-rows: 120px min-content 100px; + justify-content: center; +} + .header { font: $text-title-xs; font-weight: normal; @@ -30,13 +35,24 @@ justify-content: center; } +.hibpFormAndListWrapper { + grid-template-columns: 1fr 1fr; +} + .formWrapper { display: grid; grid-template-rows: 50px 4fr 100px; justify-content: center; - h2 { - text-align: center; - } +} + +.h2 { + text-align: center; +} + +.allBreachesWrapper { + display: grid; + grid-template-rows: 50px 4fr; + justify-content: center; } .listButtonsWrapper { @@ -46,25 +62,50 @@ } .listContainer { - max-height: 250px; + max-height: 330px; overflow-y: auto; } +.searchBox { + background-color: white; + border: 1px solid rgb(139, 139, 139); + border-radius: 3px; + height: 240px; + width: 100%; +} + .buttonsWrapper { height: auto; display: grid; grid-template-columns: 1fr 1fr; } +.buttonsStandalone { + height: 100px; + width: 100%; + display: flex; + justify-content: center; +} + .listWrapper { height: min-content; display: grid; grid-template-rows: 50px auto; justify-content: center; align-items: flex-start; +} +.hibpSelectedBoxWrapper { + width: 100%; + grid-template-columns: 100%; + .listContainer { + margin-left: 10px; + margin-right: 10px; + margin-top: 8px; + height: 300px; + } p { - color: #858585; + margin-left: 10px; } } @@ -82,6 +123,11 @@ margin-right: 10px; } +.buttonsHibp { + margin: 10px; + justify-self: center; +} + @keyframes shake { 0% { transform: translateX(0); @@ -138,3 +184,17 @@ cursor: pointer; /* Change cursor to indicate clickable item */ text-decoration: line-through; } + +.allBreachesItem:hover { + background-color: #f0f0f0; /* Light grey background on hover */ + cursor: pointer; /* Change cursor to indicate clickable item */ +} + +.selectedBox { + margin: 0; +} + +.breachName { + color: black; + margin-left: 10px; +} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/breachesLookup.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/breachesLookup.tsx new file mode 100644 index 00000000000..eb392347514 --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/breachesLookup.tsx @@ -0,0 +1,56 @@ +/* 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 { getBreaches } from "../../../../../../functions/server/getBreaches.ts"; +import styles from "../ConfigPage.module.scss"; +import { Breach } from "../../../../../../functions/universal/breach.ts"; +import { HibpLikeDbBreach } from "../../../../../../../utils/hibp.js"; + +interface Props { + allBreaches: (Breach | HibpLikeDbBreach)[]; + filterWord?: string; + onClickFunc?: (elem: Breach | HibpLikeDbBreach) => void; + additionalStyles?: string; +} + +export default function BreachesLookup(props: Props) { + // const allBreaches = (await getBreaches()); + + const { + allBreaches, + filterWord = "", + onClickFunc = () => {}, + additionalStyles = "", + } = props; + + const getElementData = (breach: Breach | HibpLikeDbBreach) => { + const name = breach.Name; + const month = (breach.AddedDate as Date).toLocaleString("en-US", { + month: "long", + }); + const year = (breach.AddedDate as Date).getFullYear(); + return `${name} - ${month}, ${year}`; + }; + + return ( +
+ {allBreaches + .filter( + (elem) => + elem.Name.includes(filterWord) || + elem.Title.includes(filterWord) || + String(elem.AddedDate).includes(filterWord), + ) + .map((elem, index) => ( +

onClickFunc(elem)} + key={index} + > + {getElementData(elem)} +

+ ))} +
+ ); +} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/hibpConfig.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/hibpConfig.tsx new file mode 100644 index 00000000000..1d5b9f68627 --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/hibpConfig.tsx @@ -0,0 +1,135 @@ +/* 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/. */ + +"use client"; + +import React, { useState } from "react"; +import { useSession } from "next-auth/react"; +import BreachesLookup from "./breachesLookup.tsx"; +import styles from "../ConfigPage.module.scss"; +import { Breach } from "../../../../../../functions/universal/breach.ts"; +import { HibpLikeDbBreach } from "../../../../../../../utils/hibp.js"; + +interface Props { + allBreaches: (Breach | HibpLikeDbBreach)[]; +} + +const ConfigPage = (props: Props) => { + const [breaches, setBreaches] = useState<(Breach | HibpLikeDbBreach)[]>([]); + const [breachSearch, setBreachSearch] = useState(); + const session = useSession(); + + const handleChange = (e: React.ChangeEvent) => { + setBreachSearch(e.target.value); + }; + + const handleAddBreach = (breach: Breach | HibpLikeDbBreach) => { + setBreaches([...breaches, breach]); + }; + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + try { + void makeConfigRequest(false); + } catch (error) { + console.error("Error submitting brokers:", error); + alert("Failed to submit brokers."); + } + }; + + const handleDeleteBreach = (breach: Breach | HibpLikeDbBreach) => { + setBreaches(breaches.filter((elem) => elem !== breach)); + }; + + const makeConfigRequest = (erase: boolean) => { + const breachesNames = breaches.map((elem) => elem.Name); + void fetch("/api/mock/hibp/config", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: session.data?.user.email, + breachesNames: breachesNames, + erase: erase, + }), + }).then(async (resp) => + console.log("Response from endpoint config:", await resp.json()), + ); + alert("Brokers list update request submitted successfully!"); + }; + + return ( +
+
+
+ Changing the default response for HIBP mock endpoint +
+
+
+ +
+
+

Select Breach Element

+
+
+ +
+ +
+
+
+
+
+

Added breaches List

+

Added breaches:

+
+ +
+
+
+
+ + +
+
+ ); +}; + +export default ConfigPage; diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/page.tsx new file mode 100644 index 00000000000..00256f22821 --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/page.tsx @@ -0,0 +1,31 @@ +/* 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 { getServerSession } from "../../../../../../functions/server/getServerSession"; +import { notFound } from "next/navigation"; +import { isAdmin } from "../../../../../../api/utils/auth"; +import ConfigPage from "./hibpConfig"; +import { getBreaches } from "../../../../../../functions/server/getBreaches.ts"; + +export default async function DevPage() { + const session = await getServerSession(); + const allBreaches = (await getBreaches()).filter( + (breach) => + !breach.IsRetired && + !breach.IsSpamList && + !breach.IsFabricated && + breach.IsVerified && + breach.Domain !== "", + ); + + if ( + !session?.user?.email || + !isAdmin(session.user.email) || + process.env.APP_ENV !== "local" + ) { + return notFound(); + } + + return ; +} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/onerepConfig.tsx similarity index 99% rename from src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx rename to src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/onerepConfig.tsx index f702b6a0248..a964a10a832 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/ConfigPage.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/onerepConfig.tsx @@ -10,16 +10,16 @@ import { MOCK_ONEREP_ID_START, MOCK_ONEREP_SCAN_ID, MOCK_ONEREP_TIME, - Broker, MOCK_ONEREP_FIRSTNAME, MOCK_ONEREP_LASTNAME, MOCK_ONEREP_ADDRESSES, MOCK_ONEREP_EMAILS, MOCK_ONEREP_RELATIVES, MOCK_ONEREP_PHONES, + Broker, } from "../../../../../../api/mock/onerep/config/config"; import { useSession } from "next-auth/react"; -import styles from "./ConfigPage.module.scss"; +import styles from "../ConfigPage.module.scss"; const ConfigPage = () => { const [brokers, setBrokers] = useState([]); diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/page.tsx index 3062648f0ff..f33e057bccf 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/page.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/page.tsx @@ -5,7 +5,7 @@ import { getServerSession } from "../../../../../../functions/server/getServerSession"; import { notFound } from "next/navigation"; import { isAdmin } from "../../../../../../api/utils/auth"; -import ConfigPage from "./ConfigPage"; +import ConfigPage from "./onerepConfig"; export default async function DevPage() { const session = await getServerSession(); diff --git a/src/app/api/mock/hibp/breaches/route.ts b/src/app/api/mock/hibp/breaches/route.ts index 91ed1e2c7ea..66ae78f47c7 100644 --- a/src/app/api/mock/hibp/breaches/route.ts +++ b/src/app/api/mock/hibp/breaches/route.ts @@ -4,37 +4,24 @@ import { NextResponse } from "next/server"; import { logger } from "../../../../functions/server/logging"; -import fakeAllBreaches from "../data/fakeAllBreaches.json"; +import mockAllBreaches from "../data/mockAllBreaches.json"; import { errorIfProduction } from "../../utils/errorThrower"; +import { getBreaches } from "../../../../functions/server/getBreaches"; +import { Breach } from "../../../../functions/universal/breach"; +import { HibpLikeDbBreach } from "../../../../../utils/hibp"; -type BreachesListResponse = { - Id: number; - Name: string; - Title: string; - Domain: string; - BreachDate: string; - AddedDate: string; - ModifiedDate: string; - PwnCount: number; - Description: string; - LogoPath: string; - DataClasses: string[]; - IsVerified: boolean; - IsFabricated: boolean; - IsSensitive: boolean; - IsRetired: boolean; - IsSpamList: boolean; - IsMalware: boolean; - FaviconUrl: string | null; -}[]; +type BreachesListResponse = (Breach | HibpLikeDbBreach)[]; -export function GET() { +export async function GET() { const prodError = errorIfProduction(); if (prodError) return prodError; logger.info("Mock endpoint: /breaches"); - const data = fakeAllBreaches.data as BreachesListResponse; + let allBreaches = await getBreaches(); + if (allBreaches.length === 0) { + allBreaches = mockAllBreaches.data as BreachesListResponse; + } - return NextResponse.json(data); + return NextResponse.json(allBreaches); } diff --git a/src/app/api/mock/hibp/config/defaults.ts b/src/app/api/mock/hibp/config/defaults.ts new file mode 100644 index 00000000000..41a53ba4557 --- /dev/null +++ b/src/app/api/mock/hibp/config/defaults.ts @@ -0,0 +1,16 @@ +/* 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 { BinaryLike } from "crypto"; +import { getSha1 } from "../../../../../utils/fxa"; + +export function MOCK_HIBP_DEFAULT_BREACHES_NAMES() { + return ["000webhost", "123RF", "Bonobos"]; +} + +export function MOCK_HIBP_COMPUTE_SHA1(email: string) { + return getSha1(email as BinaryLike) + .slice(6) + .toUpperCase(); +} diff --git a/src/app/api/mock/hibp/config/route.ts b/src/app/api/mock/hibp/config/route.ts new file mode 100644 index 00000000000..c82db14675d --- /dev/null +++ b/src/app/api/mock/hibp/config/route.ts @@ -0,0 +1,74 @@ +/* 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 { NextRequest, NextResponse } from "next/server"; +// import { logger } from "../../../../functions/server/logging"; +import { isAdmin } from "../../../utils/auth"; +import fs from "fs"; +import path from "path"; +import { MOCK_HIBP_DEFAULT_BREACHES_NAMES } from "./defaults"; +import { errorIfProduction, errorIfStage } from "../../utils/errorThrower"; + +type hibpConfigReq = { + email: string; + breachesNames: string[]; + erase?: boolean; +}; + +export async function PUT(req: NextRequest) { + const checks = errorIfStage() || errorIfProduction(); + if (checks !== null) return checks; + + const data = await req.json(); + const { email, erase = false, breachesNames } = data as hibpConfigReq; + + if (!isAdmin(email)) { + return NextResponse.json( + { error: "Mock endpoint HIBP: Unauthorized to access the endpoint" }, + { status: 401 }, + ); + } + return updateJsonFile(erase, breachesNames); +} + +function updateJsonFile(erase: boolean, breachesNames: string[]) { + // Define the path to the JSON file + const jsonFilePath = path.join( + process.cwd(), + "./src/app/api/mock/hibp/data/mockBreaches.json", + ); + + // Read the current JSON from the file + const fileData = fs.readFileSync(jsonFilePath, "utf8"); + const jsonData = JSON.parse(fileData); + + try { + jsonData.data = [ + { + hashSuffix: "", + websites: erase ? MOCK_HIBP_DEFAULT_BREACHES_NAMES() : breachesNames, + }, + ]; + + fs.writeFileSync( + jsonFilePath, + JSON.stringify(jsonData, null, 2) + "\n", + "utf8", + ); + + return NextResponse.json( + { + message: + "Mock endpoint HIBP: default JSON data has been successfully updated.", + }, + { status: 200 }, + ); + } catch (error) { + console.error("Mock endpoint HIBP: Failed to update JSON:", error); + return NextResponse.json( + { message: "Mock endpoint HIBP: Failed to update JSON data." }, + { status: 500 }, + ); + } +} diff --git a/src/app/api/mock/hibp/data/fakeBreaches.json b/src/app/api/mock/hibp/data/fakeBreaches.json deleted file mode 100644 index 65d091e067a..00000000000 --- a/src/app/api/mock/hibp/data/fakeBreaches.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "data": [ - { - "hashSuffix": "", - "websites": ["000webhost", "123RF", "Bonobos"] - } - ] -} diff --git a/src/app/api/mock/hibp/data/fakeAllBreaches.json b/src/app/api/mock/hibp/data/mockAllBreaches.json similarity index 96% rename from src/app/api/mock/hibp/data/fakeAllBreaches.json rename to src/app/api/mock/hibp/data/mockAllBreaches.json index ec9063f09f6..95a44733b68 100644 --- a/src/app/api/mock/hibp/data/fakeAllBreaches.json +++ b/src/app/api/mock/hibp/data/mockAllBreaches.json @@ -17,8 +17,7 @@ "IsSensitive": false, "IsRetired": false, "IsSpamList": false, - "IsMalware": false, - "FaviconUrl": null + "IsMalware": false }, { "Id": 2, @@ -45,8 +44,7 @@ "IsSensitive": false, "IsRetired": false, "IsSpamList": false, - "IsMalware": false, - "FaviconUrl": null + "IsMalware": false }, { "Id": 95, @@ -75,8 +73,7 @@ "IsSensitive": false, "IsRetired": false, "IsSpamList": false, - "IsMalware": false, - "FaviconUrl": null + "IsMalware": false } ] } diff --git a/src/app/api/mock/hibp/data/mockBreaches.json b/src/app/api/mock/hibp/data/mockBreaches.json new file mode 100644 index 00000000000..4b7b27a7387 --- /dev/null +++ b/src/app/api/mock/hibp/data/mockBreaches.json @@ -0,0 +1,12 @@ +{ + "data": [ + { + "hashSuffix": "", + "websites": [ + "000webhost", + "123RF", + "Bonobos" + ] + } + ] +} diff --git a/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts b/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts index 795a27a381f..043c1addb2a 100644 --- a/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts +++ b/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts @@ -4,10 +4,9 @@ import { NextResponse } from "next/server"; import { logger } from "../../../../../../functions/server/logging"; -import { getSha1 } from "../../../../../../../utils/fxa"; -import fakeBreaches from "../../../data/fakeBreaches.json"; -import type { BinaryLike } from "crypto"; +import mockBreaches from "../../../data/mockBreaches.json"; import { errorIfProduction } from "../../../../utils/errorThrower"; +import { MOCK_HIBP_COMPUTE_SHA1 } from "../../../config/defaults"; type BreachedAccountResponse = { hashSuffix: string; @@ -18,17 +17,20 @@ export function GET() { const prodError = errorIfProduction(); if (prodError) return prodError; - // Mock data for test email, can be randomized - const userEmail = process.env.E2E_TEST_ACCOUNT_EMAIL; - //TODO: getServerSession doesn't work here for some reason - const currentUserSha = getSha1(userEmail as BinaryLike); - logger.info("Mock endpoint: /range/search/"); + logger.info("HIBP Mock endpoint: /range/search/"); + if (process.env.E2E_TEST_ACCOUNT_EMAIL === undefined) { + return NextResponse.json({ + error: 500, + message: "HIBP Mock endpoint: E2E test account isn't set up!", + }); + } - let data = fakeBreaches.data as BreachedAccountResponse; + const sha1Sliced = MOCK_HIBP_COMPUTE_SHA1(process.env.E2E_TEST_ACCOUNT_EMAIL); + let data = mockBreaches.data as BreachedAccountResponse; data = data.map((elem) => ({ ...elem, - hashSuffix: currentUserSha.slice(6).toUpperCase(), + hashSuffix: sha1Sliced, })); const res = NextResponse.json(data); return res; diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index 4d0f1aba4a9..e10d7abed1c 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -1,10 +1,12 @@ { - "MAGIC_NUM_0": 40776, + "MAGIC_NUM_0": 18744, "TIME": "2024-06-19T01:37:02+0000", "FIRSTNAME": "John", "LASTNAME": "Doe", "BIRTHDATE": "2000-01-01", - "EMAILS": ["JohnDoe@JohnDoe.com"], + "EMAILS": [ + "JohnDoe@JohnDoe.com" + ], "STATUS": "active", "ADDRESSES": [ { @@ -12,8 +14,12 @@ "state": "CA" } ], - "PHONES": ["00000000"], - "RELATIVES": ["Bob Doe"], + "PHONES": [ + "00000000" + ], + "RELATIVES": [ + "Bob Doe" + ], "BROKERS_LIST": { "data": [], "links": {}, diff --git a/src/app/api/mock/onerep/config/route.ts b/src/app/api/mock/onerep/config/route.ts index af981ff3116..fa4cbb93062 100644 --- a/src/app/api/mock/onerep/config/route.ts +++ b/src/app/api/mock/onerep/config/route.ts @@ -9,25 +9,7 @@ import { isAdmin } from "../../../utils/auth"; import fs from "fs"; import path from "path"; import { Broker } from "./config"; - -/* - -Example fetch, where obj conforms to Broker interface in config.ts -Set erase to true if you want to use default response, or false if obj - -fetch('/api/mock/onerep/config', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - brokers: obj, - erase: true - }) -}) -.then(response => response.json()).then(data => console.log(data)) - -*/ +import { errorIfProduction, errorIfStage } from "../../utils/errorThrower"; type onerepConfigReq = { email: string; @@ -36,12 +18,15 @@ type onerepConfigReq = { }; export async function PUT(req: NextRequest) { + const checks = errorIfStage() || errorIfProduction(); + if (checks !== null) return checks; + const data = await req.json(); const { email, erase = false, brokers } = data as onerepConfigReq; if (!isAdmin(email)) { return NextResponse.json( - { error: "Unauthorized to access the endpoint" }, + { error: "Mock endpoint OneRep: Unauthorized to access the endpoint" }, { status: 401 }, ); } @@ -76,13 +61,16 @@ function updateJsonFile(erase: boolean, brokers: [Broker]) { ); return NextResponse.json( - { message: "JSON data has been successfully updated." }, + { + message: + "Mock endpoint OneRep: JSON data has been successfully updated.", + }, { status: 200 }, ); } catch (error) { console.error("Mock endpoint OneRep: Failed to update JSON:", error); return NextResponse.json( - { message: "Failed to update JSON data." }, + { message: "Mock endpoint OneRep: Failed to update JSON data." }, { status: 500 }, ); } diff --git a/src/app/api/mock/utils/errorThrower.ts b/src/app/api/mock/utils/errorThrower.ts index 9471aa108e9..791283cc495 100644 --- a/src/app/api/mock/utils/errorThrower.ts +++ b/src/app/api/mock/utils/errorThrower.ts @@ -5,8 +5,18 @@ import { NextResponse } from "next/server"; export function errorIfProduction() { - //checks that the environment isnt prod - if (process.env.APP_ENV === "production") { + //checks that the environment isnt production + return errorIfEnv("production"); +} + +export function errorIfStage() { + //checks that the environment isnt stage + return errorIfEnv("stage"); +} + +export function errorIfEnv(which: string) { + //checks that the environment isnt 'which' + if (process.env.APP_ENV === which) { return NextResponse.json( { error: "Endpoint not available in production environment" }, { status: 403 }, From a24965454e5bbb87388f594da2d40adfc55a0ff5 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Mon, 1 Jul 2024 02:24:22 -0700 Subject: [PATCH 031/137] reset package-lock --- package-lock.json | 522 ++++++++++++++++++++-------------------------- 1 file changed, 227 insertions(+), 295 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac4b7cbf67e..d4e9e0a91cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "1.0.0", "license": "MPL-2.0", "dependencies": { - "@aws-sdk/client-s3": "^3.606.0", - "@aws-sdk/lib-storage": "^3.606.0", + "@aws-sdk/client-s3": "^3.598.0", + "@aws-sdk/lib-storage": "^3.598.0", "@fluent/bundle": "^0.18.0", "@fluent/langneg": "^0.7.0", "@fluent/react": "^0.15.2", @@ -31,14 +31,14 @@ "canvas-confetti": "^1.9.3", "dotenv": "^16.4.5", "eslint-config-next": "^14.2.4", - "jsdom": "^24.1.0", + "jsdom": "^24.0.0", "jsonwebtoken": "^9.0.2", "jwk-to-pem": "^2.0.5", "knex": "^3.1.0", "mjml": "^4.15.3", "next": "^14.2.4", "next-auth": "^4.24.7", - "nodemailer": "^6.9.14", + "nodemailer": "^6.9.13", "pg": "^8.11.5", "react": "^18.2.0", "react-aria": "^3.33.1", @@ -93,14 +93,13 @@ "jest-fail-on-console": "^3.3.0", "lint-staged": "^15.2.5", "mjml-browser": "^4.15.3", - "oauth2-mock-server": "^7.1.2", "prettier": "3.2.5", "react-intersection-observer": "^9.10.2", "sass": "^1.77.4", "storybook": "^8.1.10", "stylelint": "^16.6.1", "stylelint-config-recommended-scss": "^14.0.0", - "stylelint-scss": "^6.3.2", + "stylelint-scss": "^6.3.1", "ts-jest": "^29.1.5", "tsx": "^4.15.6", "typescript": "^5.4.5", @@ -336,17 +335,17 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.606.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.606.0.tgz", - "integrity": "sha512-IGM/E8kVk/NY/kZwLdmGRsX1QYtuPljoNutM5kBRdtGahQL5VwVAve5PElPUArcsTkfTyW+LfXpznDeeHxMCcA==", + "version": "3.598.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.598.0.tgz", + "integrity": "sha512-UMxftsgF6j1vzm4Qd9vQJHs2he1NQCWWV8esZfmNFq23OpUC2BPMxkqi13ZQ9tnTAZUNs7yFT/x4Zsi/wpRZEw==", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.606.0", - "@aws-sdk/client-sts": "3.606.0", + "@aws-sdk/client-sso-oidc": "3.598.0", + "@aws-sdk/client-sts": "3.598.0", "@aws-sdk/core": "3.598.0", - "@aws-sdk/credential-provider-node": "3.600.0", + "@aws-sdk/credential-provider-node": "3.598.0", "@aws-sdk/middleware-bucket-endpoint": "3.598.0", "@aws-sdk/middleware-expect-continue": "3.598.0", "@aws-sdk/middleware-flexible-checksums": "3.598.0", @@ -452,14 +451,15 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.606.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.606.0.tgz", - "integrity": "sha512-gL1FHPS6hwgMNS/A+Qh5bUyHOeRVOqdb7c6+i+9gR3wtGvt2lvoSm8w5DhS08Xiiacz2AqYRDEapp0xuyCrbBQ==", + "version": "3.598.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.598.0.tgz", + "integrity": "sha512-jfdH1pAO9Tt8Nkta/JJLoUnwl7jaRdxToQTJfUtE+o3+0JP5sA4LfC2rBkJSWcU5BdAA+kyOs5Lv776DlN04Vg==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sts": "3.598.0", "@aws-sdk/core": "3.598.0", - "@aws-sdk/credential-provider-node": "3.600.0", + "@aws-sdk/credential-provider-node": "3.598.0", "@aws-sdk/middleware-host-header": "3.598.0", "@aws-sdk/middleware-logger": "3.598.0", "@aws-sdk/middleware-recursion-detection": "3.598.0", @@ -498,21 +498,18 @@ }, "engines": { "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.606.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.606.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.606.0.tgz", - "integrity": "sha512-b11mAhjrkm3MMiAPoMGcmd6vsaz2120lg8rHG/NZCo9vB1K6Kc7WP+a1Q05TRMseer2egTtpWJfn44aVO97VqA==", + "version": "3.598.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.598.0.tgz", + "integrity": "sha512-bXhz/cHL0iB9UH9IFtMaJJf4F8mV+HzncETCRFzZ9SyUMt5rP9j8A7VZknqGYSx/6mI8SsB1XJQkWSbhn6FiSQ==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.606.0", + "@aws-sdk/client-sso-oidc": "3.598.0", "@aws-sdk/core": "3.598.0", - "@aws-sdk/credential-provider-node": "3.600.0", + "@aws-sdk/credential-provider-node": "3.598.0", "@aws-sdk/middleware-host-header": "3.598.0", "@aws-sdk/middleware-logger": "3.598.0", "@aws-sdk/middleware-recursion-detection": "3.598.0", @@ -628,9 +625,9 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.600.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.600.0.tgz", - "integrity": "sha512-1pC7MPMYD45J7yFjA90SxpR0yaSvy+yZiq23aXhAPZLYgJBAxHLu0s0mDCk/piWGPh8+UGur5K0bVdx4B1D5hw==", + "version": "3.598.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.598.0.tgz", + "integrity": "sha512-sXTlqL5I/awlF9Dg2MQ17SfrEaABVnsj2mf4jF5qQrIRhfbvQOIYdEqdy8Rn1AWlJMz/N450SGzc0XJ5owxxqw==", "dependencies": { "@aws-sdk/credential-provider-env": "3.598.0", "@aws-sdk/credential-provider-http": "3.598.0", @@ -699,9 +696,9 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.606.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.606.0.tgz", - "integrity": "sha512-SagGtmyU0Zyjq5ZA+EvccqPrJTrRNAHXE/rjk9Z2ENqtTYI2pK451sD5kLQqgNIAHNf/6U1TX7hnYz1wkInLUQ==", + "version": "3.598.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.598.0.tgz", + "integrity": "sha512-la1ZY8MHH6oGUZ6nocl+2ebGNhkzgE15dB5iC0ZPHjfW0aNEfcrF2crGVxnkJQFv0LeDPQN26drajlmLnq86UA==", "dependencies": { "@smithy/abort-controller": "^3.0.1", "@smithy/middleware-endpoint": "^3.0.2", @@ -715,7 +712,7 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-s3": "^3.606.0" + "@aws-sdk/client-s3": "^3.598.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { @@ -8663,11 +8660,11 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", - "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.0.1.tgz", + "integrity": "sha512-Jb7jg4E+C+uvrUQi+h9kbILY6ts6fglKZzseMCHlH9ayq+1f5QdpYf8MV/xppuiN6DAMJAmwGz53GwP3213dmA==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -8692,14 +8689,14 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.4.tgz", - "integrity": "sha512-VwiOk7TwXoE7NlNguV/aPq1hFH72tqkHCw8eWXbr2xHspRyyv9DLpLXhq+Ieje+NwoqXrY0xyQjPXdOE6cGcHA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.2.tgz", + "integrity": "sha512-wUyG6ezpp2sWAvfqmSYTROwFUmJqKV78GLf55WODrosBcT0BAMd9bOLO4HRhynWBgAobPml2cF9ZOdgCe00r+g==", "dependencies": { - "@smithy/node-config-provider": "^3.1.3", - "@smithy/types": "^3.3.0", + "@smithy/node-config-provider": "^3.1.1", + "@smithy/types": "^3.1.0", "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", + "@smithy/util-middleware": "^3.0.1", "tslib": "^2.6.2" }, "engines": { @@ -8707,17 +8704,17 @@ } }, "node_modules/@smithy/core": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.2.4.tgz", - "integrity": "sha512-qdY3LpMOUyLM/gfjjMQZui+UTNS7kBRDWlvyIhVOql5dn2J3isk9qUTBtQ1CbDH8MTugHis1zu3h4rH+Qmmh4g==", - "dependencies": { - "@smithy/middleware-endpoint": "^3.0.4", - "@smithy/middleware-retry": "^3.0.7", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.5", - "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.2.2.tgz", + "integrity": "sha512-bxZr4ZTqS6hMSQGYdcsfFQTFU0MO2xKLbkqZMSRDM+ruQ0nY00lFJUeLhXe7fqohSEd1y5wKu1Ap0bVJPzpmHg==", + "dependencies": { + "@smithy/middleware-endpoint": "^3.0.2", + "@smithy/middleware-retry": "^3.0.5", + "@smithy/middleware-serde": "^3.0.1", + "@smithy/protocol-http": "^4.0.1", + "@smithy/smithy-client": "^3.1.3", + "@smithy/types": "^3.1.0", + "@smithy/util-middleware": "^3.0.1", "tslib": "^2.6.2" }, "engines": { @@ -8725,14 +8722,14 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.1.3.tgz", - "integrity": "sha512-U1Yrv6hx/mRK6k8AncuI6jLUx9rn0VVSd9NPEX6pyYFBfkSkChOc/n4zUb8alHUVg83TbI4OdZVo1X0Zfj3ijA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.1.1.tgz", + "integrity": "sha512-htndP0LwHdE3R3Nam9ZyVWhwPYOmD4xCL79kqvNxy8u/bv0huuy574CSiRY4cvEICgimv8jlVfLeZ7zZqbnB2g==", "dependencies": { - "@smithy/node-config-provider": "^3.1.3", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", + "@smithy/node-config-provider": "^3.1.1", + "@smithy/property-provider": "^3.1.1", + "@smithy/types": "^3.1.0", + "@smithy/url-parser": "^3.0.1", "tslib": "^2.6.2" }, "engines": { @@ -8802,13 +8799,13 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.0.tgz", - "integrity": "sha512-vFvDxMrc6sO5Atec8PaISckMcAwsCrRhYxwUylg97bRT2KZoumOF7qk5+6EVUtuM1IG9AJV5aqXnHln9ZdXHpg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.0.3.tgz", + "integrity": "sha512-31x2MokxJL/u5U/BdElvVRotOGjUcOOvI2pb5TZ02umBLw+vVHImiLn+khbN0SblaFXNRzPoGrKwXylNjV3skw==", "dependencies": { - "@smithy/protocol-http": "^4.0.3", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/protocol-http": "^4.0.1", + "@smithy/querystring-builder": "^3.0.1", + "@smithy/types": "^3.1.0", "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" } @@ -8825,11 +8822,11 @@ } }, "node_modules/@smithy/hash-node": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", - "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.1.tgz", + "integrity": "sha512-w2ncjgk2EYO2+WhAsSQA8owzoOSY7IL1qVytlwpnL1pFGWTjIoIh5nROkEKXY51unB63bMGZqDiVoXaFbyKDlg==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.1.0", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -8852,11 +8849,11 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", - "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.1.tgz", + "integrity": "sha512-RSNF/32BKygXKKMyS7koyuAq1rcdW5p5c4EFa77QenBFze9As+JiRnV9OWBh2cB/ejGZalEZjvIrMLHwJl7aGA==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, @@ -8882,12 +8879,12 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.3.tgz", - "integrity": "sha512-Dbz2bzexReYIQDWMr+gZhpwBetNXzbhnEMhYKA6urqmojO14CsXjnsoPYO8UL/xxcawn8ZsuVU61ElkLSltIUQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.1.tgz", + "integrity": "sha512-6QdK/VbrCfXD5/QolE2W/ok6VqxD+SM28Ds8iSlEHXZwv4buLsvWyvoEEy0322K/g5uFgPzBmZjGqesTmPL+yQ==", "dependencies": { - "@smithy/protocol-http": "^4.0.3", - "@smithy/types": "^3.3.0", + "@smithy/protocol-http": "^4.0.1", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -8895,16 +8892,16 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.0.4.tgz", - "integrity": "sha512-whUJMEPwl3ANIbXjBXZVdJNgfV2ZU8ayln7xUM47rXL2txuenI7jQ/VFFwCzy5lCmXScjp6zYtptW5Evud8e9g==", - "dependencies": { - "@smithy/middleware-serde": "^3.0.3", - "@smithy/node-config-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-middleware": "^3.0.3", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.0.2.tgz", + "integrity": "sha512-gWEaGYB3Bei17Oiy/F2IlUPpBazNXImytoOdJ1xbrUOaJKAOiUhx8/4FOnYLLJHdAwa9PlvJ2ULda2f/Dnwi9w==", + "dependencies": { + "@smithy/middleware-serde": "^3.0.1", + "@smithy/node-config-provider": "^3.1.1", + "@smithy/shared-ini-file-loader": "^3.1.1", + "@smithy/types": "^3.1.0", + "@smithy/url-parser": "^3.0.1", + "@smithy/util-middleware": "^3.0.1", "tslib": "^2.6.2" }, "engines": { @@ -8912,17 +8909,17 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.7.tgz", - "integrity": "sha512-f5q7Y09G+2h5ivkSx5CHvlAT4qRR3jBFEsfXyQ9nFNiWQlr8c48blnu5cmbTQ+p1xmIO14UXzKoF8d7Tm0Gsjw==", - "dependencies": { - "@smithy/node-config-provider": "^3.1.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/service-error-classification": "^3.0.3", - "@smithy/smithy-client": "^3.1.5", - "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.5.tgz", + "integrity": "sha512-nKAmmea9Wm0d94obPqVgjxW2zzaNemxcTzjgd17LhGKI23D66UQKI5gpoWDsnE+R4tfuZe9dCcw8gmTVEwFpRA==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.1", + "@smithy/protocol-http": "^4.0.1", + "@smithy/service-error-classification": "^3.0.1", + "@smithy/smithy-client": "^3.1.3", + "@smithy/types": "^3.1.0", + "@smithy/util-middleware": "^3.0.1", + "@smithy/util-retry": "^3.0.1", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -8931,11 +8928,11 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", - "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.1.tgz", + "integrity": "sha512-ak6H/ZRN05r5+SR0/IUc5zOSyh2qp3HReg1KkrnaSLXmncy9lwOjNqybX4L4x55/e5mtVDn1uf/gQ6bw5neJPw==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -8943,11 +8940,11 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", - "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.1.tgz", + "integrity": "sha512-fS5uT//y1SlBdkzIvgmWQ9FufwMXrHSSbuR25ygMy1CRDIZkcBMoF4oTMYNfR9kBlVBcVzlv7joFdNrFuQirPA==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -8955,13 +8952,13 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.3.tgz", - "integrity": "sha512-rxdpAZczzholz6CYZxtqDu/aKTxATD5DAUDVj7HoEulq+pDSQVWzbg0btZDlxeFfa6bb2b5tUvgdX5+k8jUqcg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.1.tgz", + "integrity": "sha512-z5G7+ysL4yUtMghUd2zrLkecu0mTfnYlt5dR76g/HsFqf7evFazwiZP1ag2EJenGxNBDwDM5g8nm11NPogiUVA==", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.3", - "@smithy/types": "^3.3.0", + "@smithy/property-provider": "^3.1.1", + "@smithy/shared-ini-file-loader": "^3.1.1", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -8969,14 +8966,14 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.1.tgz", - "integrity": "sha512-L71NLyPeP450r2J/mfu1jMc//Z1YnqJt2eSNw7uhiItaONnBLDA68J5jgxq8+MBDsYnFwNAIc7dBG1ImiWBiwg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.0.1.tgz", + "integrity": "sha512-hlBI6MuREA4o1wBMEt+QNhUzoDtFFvwR6ecufimlx9D79jPybE/r8kNorphXOi91PgSO9S2fxRjcKCLk7Jw8zA==", "dependencies": { - "@smithy/abort-controller": "^3.1.1", - "@smithy/protocol-http": "^4.0.3", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/abort-controller": "^3.0.1", + "@smithy/protocol-http": "^4.0.1", + "@smithy/querystring-builder": "^3.0.1", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -8984,11 +8981,11 @@ } }, "node_modules/@smithy/property-provider": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", - "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.1.tgz", + "integrity": "sha512-YknOMZcQkB5on+MU0DvbToCmT2YPtTETMXW0D3+/Iln7ezT+Zm1GMHhCW1dOH/X/+LkkQD9aXEoCX/B10s4Xdw==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -8996,11 +8993,11 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.0.3.tgz", - "integrity": "sha512-x5jmrCWwQlx+Zv4jAtc33ijJ+vqqYN+c/ZkrnpvEe/uDas7AT7A/4Rc2CdfxgWv4WFGmEqODIrrUToPN6DDkGw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.0.1.tgz", + "integrity": "sha512-eBhm9zwcFPEazc654c0BEWtxYAzrw+OhoSf5pkwKzfftWKXRoqEhwOE2Pvn30v0iAdo7Mfsfb6pi1NnZlGCMpg==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -9008,11 +9005,11 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", - "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.1.tgz", + "integrity": "sha512-vKitpnG/2KOMVlx3x1S3FkBH075EROG3wcrcDaNerQNh8yuqnSL23btCD2UyX4i4lpPzNW6VFdxbn2Z25b/g5Q==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.1.0", "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" }, @@ -9021,11 +9018,11 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", - "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.1.tgz", + "integrity": "sha512-Qt8DMC05lVS8NcQx94lfVbZSX+2Ym7032b/JR8AlboAa/D669kPzqb35dkjkvAG6+NWmUchef3ENtrD6F+5n8Q==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -9033,22 +9030,22 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", - "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.1.tgz", + "integrity": "sha512-ubFUvIePjDCyIzZ+pLETqNC6KXJ/fc6g+/baqel7Zf6kJI/kZKgjwkCI7zbUhoUuOZ/4eA/87YasVu40b/B4bA==", "dependencies": { - "@smithy/types": "^3.3.0" + "@smithy/types": "^3.1.0" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.3.tgz", - "integrity": "sha512-Z8Y3+08vgoDgl4HENqNnnzSISAaGrF2RoKupoC47u2wiMp+Z8P/8mDh1CL8+8ujfi2U5naNvopSBmP/BUj8b5w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.1.tgz", + "integrity": "sha512-nD6tXIX2126/P9e3wqRY1bm9dTtPZwRDyjVOd18G28o+1UOG+kOVgUwujE795HslSuPlEgqzsH5sgNP1hDjj9g==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -9073,15 +9070,15 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.5.tgz", - "integrity": "sha512-x9bL9Mx2CT2P1OiUlHM+ZNpbVU6TgT32f9CmTRzqIHA7M4vYrROCWEoC3o4xHNJASoGd4Opos3cXYPgh+/m4Ww==", - "dependencies": { - "@smithy/middleware-endpoint": "^3.0.4", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.0.5", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.3.tgz", + "integrity": "sha512-YVz+akpR5lIIRPJfhE4sqoHYwMys6/33vsFvDof+71FCwa4jkVfMpzKv9TKrG/EDb5TV+YtjdXkwywdqlUOQXA==", + "dependencies": { + "@smithy/middleware-endpoint": "^3.0.2", + "@smithy/middleware-stack": "^3.0.1", + "@smithy/protocol-http": "^4.0.1", + "@smithy/types": "^3.1.0", + "@smithy/util-stream": "^3.0.3", "tslib": "^2.6.2" }, "engines": { @@ -9089,9 +9086,9 @@ } }, "node_modules/@smithy/types": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", - "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.1.0.tgz", + "integrity": "sha512-qi4SeCVOUPjhSSZrxxB/mB8DrmuSFUcJnD9KXjuP+7C3LV/KFV4kpuUSH3OHDZgQB9TEH/1sO/Fq/5HyaK9MPw==", "dependencies": { "tslib": "^2.6.2" }, @@ -9100,12 +9097,12 @@ } }, "node_modules/@smithy/url-parser": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", - "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.1.tgz", + "integrity": "sha512-G140IlNFlzYWVCedC4E2d6NycM1dCUbe5CnsGW1hmGt4hYKiGOw0v7lVru9WAn5T2w09QEjl4fOESWjGmCvVmg==", "dependencies": { - "@smithy/querystring-parser": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/querystring-parser": "^3.0.1", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, @@ -9165,13 +9162,13 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.7.tgz", - "integrity": "sha512-Q2txLyvQyGfmjsaDbVV7Sg8psefpFcrnlGapDzXGFRPFKRBeEg6OvFK8FljqjeHSaCZ6/UuzQExUPqBR/2qlDA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.5.tgz", + "integrity": "sha512-VZkJ+bXCHcNSMhX8EReGyFcc/Err94YGqeEKbbxkVz2TgKlacsoplpi+kxOMVbQq/tq9sQx5ajBKG+nl2GNuxw==", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.1.5", - "@smithy/types": "^3.3.0", + "@smithy/property-provider": "^3.1.1", + "@smithy/smithy-client": "^3.1.3", + "@smithy/types": "^3.1.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -9180,16 +9177,16 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.7.tgz", - "integrity": "sha512-F4Qcj1fG6MGi2BSWCslfsMSwllws/WzYONBGtLybyY+halAcXdWhcew+mej8M5SKd5hqPYp4f7b+ABQEaeytgg==", - "dependencies": { - "@smithy/config-resolver": "^3.0.4", - "@smithy/credential-provider-imds": "^3.1.3", - "@smithy/node-config-provider": "^3.1.3", - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.1.5", - "@smithy/types": "^3.3.0", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.5.tgz", + "integrity": "sha512-jy19cFQA0k4f8VUDFsZVBey3rmI8EuXCw/xh/abdiq6S1qdwdfZ5coviuyYd//LPszf2yWIYkLpvmLF9qbhLGg==", + "dependencies": { + "@smithy/config-resolver": "^3.0.2", + "@smithy/credential-provider-imds": "^3.1.1", + "@smithy/node-config-provider": "^3.1.1", + "@smithy/property-provider": "^3.1.1", + "@smithy/smithy-client": "^3.1.3", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -9197,12 +9194,12 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.4.tgz", - "integrity": "sha512-ZAtNf+vXAsgzgRutDDiklU09ZzZiiV/nATyqde4Um4priTmasDH+eLpp3tspL0hS2dEootyFMhu1Y6Y+tzpWBQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.2.tgz", + "integrity": "sha512-4zFOcBFQvifd2LSD4a1dKvfIWWwh4sWNtS3oZ7mpob/qPPmJseqKB148iT+hWCDsG//TmI+8vjYPgZdvnkYlTg==", "dependencies": { - "@smithy/node-config-provider": "^3.1.3", - "@smithy/types": "^3.3.0", + "@smithy/node-config-provider": "^3.1.1", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -9221,11 +9218,11 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", - "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.1.tgz", + "integrity": "sha512-WRODCQtUsO7vIvfrdxS8RFPeLKcewYtaCglZsBsedIKSUGIIvMlZT5oh+pCe72I+1L+OjnZuqRNpN2LKhWA4KQ==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -9233,12 +9230,12 @@ } }, "node_modules/@smithy/util-retry": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", - "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.1.tgz", + "integrity": "sha512-5lRtYm+8fNFEUTdqZXg5M4ppVp40rMIJfR1TpbHAhKQgPIDpWT+iYMaqgnwEbtpi9U1smyUOPv5Sg+M1neOBgw==", "dependencies": { - "@smithy/service-error-classification": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/service-error-classification": "^3.0.1", + "@smithy/types": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -9246,13 +9243,13 @@ } }, "node_modules/@smithy/util-stream": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.0.5.tgz", - "integrity": "sha512-xC3L5PKMAT/Bh8fmHNXP9sdQ4+4aKVUU3EEJ2CF/lLk7R+wtMJM+v/1B4en7jO++Wa5spGzFDBCl0QxgbUc5Ug==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.0.3.tgz", + "integrity": "sha512-ztOvXkXKJromRHNzvrLEW/vvTQPnxPBRHA0gR0QX61LnHDgrm4TBT4EQNpWwwHCD1N0nnEL5bEkzo2dt2t34Kg==", "dependencies": { - "@smithy/fetch-http-handler": "^3.2.0", - "@smithy/node-http-handler": "^3.1.1", - "@smithy/types": "^3.3.0", + "@smithy/fetch-http-handler": "^3.0.3", + "@smithy/node-http-handler": "^3.0.1", + "@smithy/types": "^3.1.0", "@smithy/util-base64": "^3.0.0", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-hex-encoding": "^3.0.0", @@ -14313,24 +14310,6 @@ } ] }, - "node_modules/basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "dev": true, - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/basic-auth/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "node_modules/better-opn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", @@ -15572,19 +15551,6 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -19774,9 +19740,9 @@ } }, "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -19786,9 +19752,9 @@ } }, "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", "dependencies": { "debug": "^4.3.4" }, @@ -22522,30 +22488,30 @@ } }, "node_modules/jsdom": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz", - "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.0.0.tgz", + "integrity": "sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==", "dependencies": { "cssstyle": "^4.0.1", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.10", + "nwsapi": "^2.2.7", "parse5": "^7.1.2", - "rrweb-cssom": "^0.7.0", + "rrweb-cssom": "^0.6.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.4", + "tough-cookie": "^4.1.3", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0", - "ws": "^8.17.0", + "ws": "^8.16.0", "xml-name-validator": "^5.0.0" }, "engines": { @@ -22561,9 +22527,9 @@ } }, "node_modules/jsdom/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", "dependencies": { "debug": "^4.3.4" }, @@ -22572,9 +22538,9 @@ } }, "node_modules/jsdom/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -22583,11 +22549,6 @@ "node": ">= 14" } }, - "node_modules/jsdom/node_modules/rrweb-cssom": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", - "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==" - }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -24707,9 +24668,9 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/nodemailer": { - "version": "6.9.14", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", - "integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==", + "version": "6.9.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz", + "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==", "engines": { "node": ">=6.0.0" } @@ -24781,9 +24742,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", - "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==" + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==" }, "node_modules/nypm": { "version": "0.3.8", @@ -24943,35 +24904,6 @@ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" }, - "node_modules/oauth2-mock-server": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/oauth2-mock-server/-/oauth2-mock-server-7.1.2.tgz", - "integrity": "sha512-xUg/YOTcMRe8W+q2jphecq1fB1BAjlAPbeeA9lvqwGaQSPJKxI2e8JUnDXHrrKGNJAVXQdHgE/9h4RpCtOfYOA==", - "dev": true, - "dependencies": { - "basic-auth": "^2.0.1", - "cors": "^2.8.5", - "express": "^4.18.2", - "is-plain-object": "^5.0.0", - "jose": "^5.3.0" - }, - "bin": { - "oauth2-mock-server": "dist/oauth2-mock-server.js" - }, - "engines": { - "node": "^18.12 || ^20 || ^22", - "yarn": "^1.15.2" - } - }, - "node_modules/oauth2-mock-server/node_modules/jose": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.3.0.tgz", - "integrity": "sha512-IChe9AtAE79ru084ow8jzkN2lNrG3Ntfiv65Cvj9uOCE2m5LNsdHG+9EbxWxAoWRF9TgDOqLN5jm08++owDVRg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -28590,9 +28522,9 @@ } }, "node_modules/stylelint-scss": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.3.2.tgz", - "integrity": "sha512-pNk9mXOVKkQtd+SROPC9io8ISSgX+tOVPhFdBE+LaKQnJMLdWPbGKAGYv4Wmf/RrnOjkutunNTN9kKMhkdE5qA==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.3.1.tgz", + "integrity": "sha512-w/czBoWUZxJNk5fBRPODcXSN4qcPv3WHjTSSpFovVY+TE3MZTMR0yRlbmaDYrm8tTWHvpwQAuEBZ0lk2wwkboQ==", "dev": true, "dependencies": { "known-css-properties": "^0.31.0", @@ -29349,9 +29281,9 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", From cb8e013a6eea68051c3b92c6f1f282b6f2e8be67 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Mon, 1 Jul 2024 02:25:31 -0700 Subject: [PATCH 032/137] lint --- src/app/api/mock/hibp/data/mockBreaches.json | 6 +----- src/app/api/mock/onerep/config/mockUser.json | 12 +++--------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/app/api/mock/hibp/data/mockBreaches.json b/src/app/api/mock/hibp/data/mockBreaches.json index 4b7b27a7387..65d091e067a 100644 --- a/src/app/api/mock/hibp/data/mockBreaches.json +++ b/src/app/api/mock/hibp/data/mockBreaches.json @@ -2,11 +2,7 @@ "data": [ { "hashSuffix": "", - "websites": [ - "000webhost", - "123RF", - "Bonobos" - ] + "websites": ["000webhost", "123RF", "Bonobos"] } ] } diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index e10d7abed1c..ffe3e3325f9 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -4,9 +4,7 @@ "FIRSTNAME": "John", "LASTNAME": "Doe", "BIRTHDATE": "2000-01-01", - "EMAILS": [ - "JohnDoe@JohnDoe.com" - ], + "EMAILS": ["JohnDoe@JohnDoe.com"], "STATUS": "active", "ADDRESSES": [ { @@ -14,12 +12,8 @@ "state": "CA" } ], - "PHONES": [ - "00000000" - ], - "RELATIVES": [ - "Bob Doe" - ], + "PHONES": ["00000000"], + "RELATIVES": ["Bob Doe"], "BROKERS_LIST": { "data": [], "links": {}, From 21a6bc5d038dbf7f2bbaa0d1b048c675aadf80f4 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Mon, 1 Jul 2024 02:33:29 -0700 Subject: [PATCH 033/137] package-lock --- package-lock.json | 461 +++++++++++++++++++++++----------------------- 1 file changed, 234 insertions(+), 227 deletions(-) diff --git a/package-lock.json b/package-lock.json index d4e9e0a91cd..96c0343a4f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "1.0.0", "license": "MPL-2.0", "dependencies": { - "@aws-sdk/client-s3": "^3.598.0", - "@aws-sdk/lib-storage": "^3.598.0", + "@aws-sdk/client-s3": "^3.606.0", + "@aws-sdk/lib-storage": "^3.606.0", "@fluent/bundle": "^0.18.0", "@fluent/langneg": "^0.7.0", "@fluent/react": "^0.15.2", @@ -31,14 +31,14 @@ "canvas-confetti": "^1.9.3", "dotenv": "^16.4.5", "eslint-config-next": "^14.2.4", - "jsdom": "^24.0.0", + "jsdom": "^24.1.0", "jsonwebtoken": "^9.0.2", "jwk-to-pem": "^2.0.5", "knex": "^3.1.0", "mjml": "^4.15.3", "next": "^14.2.4", "next-auth": "^4.24.7", - "nodemailer": "^6.9.13", + "nodemailer": "^6.9.14", "pg": "^8.11.5", "react": "^18.2.0", "react-aria": "^3.33.1", @@ -99,7 +99,7 @@ "storybook": "^8.1.10", "stylelint": "^16.6.1", "stylelint-config-recommended-scss": "^14.0.0", - "stylelint-scss": "^6.3.1", + "stylelint-scss": "^6.3.2", "ts-jest": "^29.1.5", "tsx": "^4.15.6", "typescript": "^5.4.5", @@ -335,17 +335,17 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.598.0.tgz", - "integrity": "sha512-UMxftsgF6j1vzm4Qd9vQJHs2he1NQCWWV8esZfmNFq23OpUC2BPMxkqi13ZQ9tnTAZUNs7yFT/x4Zsi/wpRZEw==", + "version": "3.606.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.606.0.tgz", + "integrity": "sha512-IGM/E8kVk/NY/kZwLdmGRsX1QYtuPljoNutM5kBRdtGahQL5VwVAve5PElPUArcsTkfTyW+LfXpznDeeHxMCcA==", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.598.0", - "@aws-sdk/client-sts": "3.598.0", + "@aws-sdk/client-sso-oidc": "3.606.0", + "@aws-sdk/client-sts": "3.606.0", "@aws-sdk/core": "3.598.0", - "@aws-sdk/credential-provider-node": "3.598.0", + "@aws-sdk/credential-provider-node": "3.600.0", "@aws-sdk/middleware-bucket-endpoint": "3.598.0", "@aws-sdk/middleware-expect-continue": "3.598.0", "@aws-sdk/middleware-flexible-checksums": "3.598.0", @@ -451,15 +451,14 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.598.0.tgz", - "integrity": "sha512-jfdH1pAO9Tt8Nkta/JJLoUnwl7jaRdxToQTJfUtE+o3+0JP5sA4LfC2rBkJSWcU5BdAA+kyOs5Lv776DlN04Vg==", + "version": "3.606.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.606.0.tgz", + "integrity": "sha512-gL1FHPS6hwgMNS/A+Qh5bUyHOeRVOqdb7c6+i+9gR3wtGvt2lvoSm8w5DhS08Xiiacz2AqYRDEapp0xuyCrbBQ==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sts": "3.598.0", "@aws-sdk/core": "3.598.0", - "@aws-sdk/credential-provider-node": "3.598.0", + "@aws-sdk/credential-provider-node": "3.600.0", "@aws-sdk/middleware-host-header": "3.598.0", "@aws-sdk/middleware-logger": "3.598.0", "@aws-sdk/middleware-recursion-detection": "3.598.0", @@ -498,18 +497,21 @@ }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.606.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.598.0.tgz", - "integrity": "sha512-bXhz/cHL0iB9UH9IFtMaJJf4F8mV+HzncETCRFzZ9SyUMt5rP9j8A7VZknqGYSx/6mI8SsB1XJQkWSbhn6FiSQ==", + "version": "3.606.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.606.0.tgz", + "integrity": "sha512-b11mAhjrkm3MMiAPoMGcmd6vsaz2120lg8rHG/NZCo9vB1K6Kc7WP+a1Q05TRMseer2egTtpWJfn44aVO97VqA==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.598.0", + "@aws-sdk/client-sso-oidc": "3.606.0", "@aws-sdk/core": "3.598.0", - "@aws-sdk/credential-provider-node": "3.598.0", + "@aws-sdk/credential-provider-node": "3.600.0", "@aws-sdk/middleware-host-header": "3.598.0", "@aws-sdk/middleware-logger": "3.598.0", "@aws-sdk/middleware-recursion-detection": "3.598.0", @@ -625,9 +627,9 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.598.0.tgz", - "integrity": "sha512-sXTlqL5I/awlF9Dg2MQ17SfrEaABVnsj2mf4jF5qQrIRhfbvQOIYdEqdy8Rn1AWlJMz/N450SGzc0XJ5owxxqw==", + "version": "3.600.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.600.0.tgz", + "integrity": "sha512-1pC7MPMYD45J7yFjA90SxpR0yaSvy+yZiq23aXhAPZLYgJBAxHLu0s0mDCk/piWGPh8+UGur5K0bVdx4B1D5hw==", "dependencies": { "@aws-sdk/credential-provider-env": "3.598.0", "@aws-sdk/credential-provider-http": "3.598.0", @@ -696,9 +698,9 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.598.0.tgz", - "integrity": "sha512-la1ZY8MHH6oGUZ6nocl+2ebGNhkzgE15dB5iC0ZPHjfW0aNEfcrF2crGVxnkJQFv0LeDPQN26drajlmLnq86UA==", + "version": "3.606.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.606.0.tgz", + "integrity": "sha512-SagGtmyU0Zyjq5ZA+EvccqPrJTrRNAHXE/rjk9Z2ENqtTYI2pK451sD5kLQqgNIAHNf/6U1TX7hnYz1wkInLUQ==", "dependencies": { "@smithy/abort-controller": "^3.0.1", "@smithy/middleware-endpoint": "^3.0.2", @@ -712,7 +714,7 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-s3": "^3.598.0" + "@aws-sdk/client-s3": "^3.606.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { @@ -8660,11 +8662,11 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.0.1.tgz", - "integrity": "sha512-Jb7jg4E+C+uvrUQi+h9kbILY6ts6fglKZzseMCHlH9ayq+1f5QdpYf8MV/xppuiN6DAMJAmwGz53GwP3213dmA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -8689,14 +8691,14 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.2.tgz", - "integrity": "sha512-wUyG6ezpp2sWAvfqmSYTROwFUmJqKV78GLf55WODrosBcT0BAMd9bOLO4HRhynWBgAobPml2cF9ZOdgCe00r+g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.4.tgz", + "integrity": "sha512-VwiOk7TwXoE7NlNguV/aPq1hFH72tqkHCw8eWXbr2xHspRyyv9DLpLXhq+Ieje+NwoqXrY0xyQjPXdOE6cGcHA==", "dependencies": { - "@smithy/node-config-provider": "^3.1.1", - "@smithy/types": "^3.1.0", + "@smithy/node-config-provider": "^3.1.3", + "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.1", + "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { @@ -8704,17 +8706,17 @@ } }, "node_modules/@smithy/core": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.2.2.tgz", - "integrity": "sha512-bxZr4ZTqS6hMSQGYdcsfFQTFU0MO2xKLbkqZMSRDM+ruQ0nY00lFJUeLhXe7fqohSEd1y5wKu1Ap0bVJPzpmHg==", - "dependencies": { - "@smithy/middleware-endpoint": "^3.0.2", - "@smithy/middleware-retry": "^3.0.5", - "@smithy/middleware-serde": "^3.0.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/smithy-client": "^3.1.3", - "@smithy/types": "^3.1.0", - "@smithy/util-middleware": "^3.0.1", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.2.4.tgz", + "integrity": "sha512-qdY3LpMOUyLM/gfjjMQZui+UTNS7kBRDWlvyIhVOql5dn2J3isk9qUTBtQ1CbDH8MTugHis1zu3h4rH+Qmmh4g==", + "dependencies": { + "@smithy/middleware-endpoint": "^3.0.4", + "@smithy/middleware-retry": "^3.0.7", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/protocol-http": "^4.0.3", + "@smithy/smithy-client": "^3.1.5", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { @@ -8722,14 +8724,14 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.1.1.tgz", - "integrity": "sha512-htndP0LwHdE3R3Nam9ZyVWhwPYOmD4xCL79kqvNxy8u/bv0huuy574CSiRY4cvEICgimv8jlVfLeZ7zZqbnB2g==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.1.3.tgz", + "integrity": "sha512-U1Yrv6hx/mRK6k8AncuI6jLUx9rn0VVSd9NPEX6pyYFBfkSkChOc/n4zUb8alHUVg83TbI4OdZVo1X0Zfj3ijA==", "dependencies": { - "@smithy/node-config-provider": "^3.1.1", - "@smithy/property-provider": "^3.1.1", - "@smithy/types": "^3.1.0", - "@smithy/url-parser": "^3.0.1", + "@smithy/node-config-provider": "^3.1.3", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", "tslib": "^2.6.2" }, "engines": { @@ -8799,13 +8801,13 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.0.3.tgz", - "integrity": "sha512-31x2MokxJL/u5U/BdElvVRotOGjUcOOvI2pb5TZ02umBLw+vVHImiLn+khbN0SblaFXNRzPoGrKwXylNjV3skw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.0.tgz", + "integrity": "sha512-vFvDxMrc6sO5Atec8PaISckMcAwsCrRhYxwUylg97bRT2KZoumOF7qk5+6EVUtuM1IG9AJV5aqXnHln9ZdXHpg==", "dependencies": { - "@smithy/protocol-http": "^4.0.1", - "@smithy/querystring-builder": "^3.0.1", - "@smithy/types": "^3.1.0", + "@smithy/protocol-http": "^4.0.3", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" } @@ -8822,11 +8824,11 @@ } }, "node_modules/@smithy/hash-node": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.1.tgz", - "integrity": "sha512-w2ncjgk2EYO2+WhAsSQA8owzoOSY7IL1qVytlwpnL1pFGWTjIoIh5nROkEKXY51unB63bMGZqDiVoXaFbyKDlg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", + "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -8849,11 +8851,11 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.1.tgz", - "integrity": "sha512-RSNF/32BKygXKKMyS7koyuAq1rcdW5p5c4EFa77QenBFze9As+JiRnV9OWBh2cB/ejGZalEZjvIrMLHwJl7aGA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", + "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, @@ -8879,12 +8881,12 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.1.tgz", - "integrity": "sha512-6QdK/VbrCfXD5/QolE2W/ok6VqxD+SM28Ds8iSlEHXZwv4buLsvWyvoEEy0322K/g5uFgPzBmZjGqesTmPL+yQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.3.tgz", + "integrity": "sha512-Dbz2bzexReYIQDWMr+gZhpwBetNXzbhnEMhYKA6urqmojO14CsXjnsoPYO8UL/xxcawn8ZsuVU61ElkLSltIUQ==", "dependencies": { - "@smithy/protocol-http": "^4.0.1", - "@smithy/types": "^3.1.0", + "@smithy/protocol-http": "^4.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -8892,16 +8894,16 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.0.2.tgz", - "integrity": "sha512-gWEaGYB3Bei17Oiy/F2IlUPpBazNXImytoOdJ1xbrUOaJKAOiUhx8/4FOnYLLJHdAwa9PlvJ2ULda2f/Dnwi9w==", - "dependencies": { - "@smithy/middleware-serde": "^3.0.1", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/shared-ini-file-loader": "^3.1.1", - "@smithy/types": "^3.1.0", - "@smithy/url-parser": "^3.0.1", - "@smithy/util-middleware": "^3.0.1", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.0.4.tgz", + "integrity": "sha512-whUJMEPwl3ANIbXjBXZVdJNgfV2ZU8ayln7xUM47rXL2txuenI7jQ/VFFwCzy5lCmXScjp6zYtptW5Evud8e9g==", + "dependencies": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.3", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { @@ -8909,17 +8911,17 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.5.tgz", - "integrity": "sha512-nKAmmea9Wm0d94obPqVgjxW2zzaNemxcTzjgd17LhGKI23D66UQKI5gpoWDsnE+R4tfuZe9dCcw8gmTVEwFpRA==", - "dependencies": { - "@smithy/node-config-provider": "^3.1.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/service-error-classification": "^3.0.1", - "@smithy/smithy-client": "^3.1.3", - "@smithy/types": "^3.1.0", - "@smithy/util-middleware": "^3.0.1", - "@smithy/util-retry": "^3.0.1", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.7.tgz", + "integrity": "sha512-f5q7Y09G+2h5ivkSx5CHvlAT4qRR3jBFEsfXyQ9nFNiWQlr8c48blnu5cmbTQ+p1xmIO14UXzKoF8d7Tm0Gsjw==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.3", + "@smithy/protocol-http": "^4.0.3", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/smithy-client": "^3.1.5", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -8928,11 +8930,11 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.1.tgz", - "integrity": "sha512-ak6H/ZRN05r5+SR0/IUc5zOSyh2qp3HReg1KkrnaSLXmncy9lwOjNqybX4L4x55/e5mtVDn1uf/gQ6bw5neJPw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -8940,11 +8942,11 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.1.tgz", - "integrity": "sha512-fS5uT//y1SlBdkzIvgmWQ9FufwMXrHSSbuR25ygMy1CRDIZkcBMoF4oTMYNfR9kBlVBcVzlv7joFdNrFuQirPA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -8952,13 +8954,13 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.1.tgz", - "integrity": "sha512-z5G7+ysL4yUtMghUd2zrLkecu0mTfnYlt5dR76g/HsFqf7evFazwiZP1ag2EJenGxNBDwDM5g8nm11NPogiUVA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.3.tgz", + "integrity": "sha512-rxdpAZczzholz6CYZxtqDu/aKTxATD5DAUDVj7HoEulq+pDSQVWzbg0btZDlxeFfa6bb2b5tUvgdX5+k8jUqcg==", "dependencies": { - "@smithy/property-provider": "^3.1.1", - "@smithy/shared-ini-file-loader": "^3.1.1", - "@smithy/types": "^3.1.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -8966,14 +8968,14 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.0.1.tgz", - "integrity": "sha512-hlBI6MuREA4o1wBMEt+QNhUzoDtFFvwR6ecufimlx9D79jPybE/r8kNorphXOi91PgSO9S2fxRjcKCLk7Jw8zA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.1.tgz", + "integrity": "sha512-L71NLyPeP450r2J/mfu1jMc//Z1YnqJt2eSNw7uhiItaONnBLDA68J5jgxq8+MBDsYnFwNAIc7dBG1ImiWBiwg==", "dependencies": { - "@smithy/abort-controller": "^3.0.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/querystring-builder": "^3.0.1", - "@smithy/types": "^3.1.0", + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.0.3", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -8981,11 +8983,11 @@ } }, "node_modules/@smithy/property-provider": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.1.tgz", - "integrity": "sha512-YknOMZcQkB5on+MU0DvbToCmT2YPtTETMXW0D3+/Iln7ezT+Zm1GMHhCW1dOH/X/+LkkQD9aXEoCX/B10s4Xdw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", + "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -8993,11 +8995,11 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.0.1.tgz", - "integrity": "sha512-eBhm9zwcFPEazc654c0BEWtxYAzrw+OhoSf5pkwKzfftWKXRoqEhwOE2Pvn30v0iAdo7Mfsfb6pi1NnZlGCMpg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.0.3.tgz", + "integrity": "sha512-x5jmrCWwQlx+Zv4jAtc33ijJ+vqqYN+c/ZkrnpvEe/uDas7AT7A/4Rc2CdfxgWv4WFGmEqODIrrUToPN6DDkGw==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -9005,11 +9007,11 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.1.tgz", - "integrity": "sha512-vKitpnG/2KOMVlx3x1S3FkBH075EROG3wcrcDaNerQNh8yuqnSL23btCD2UyX4i4lpPzNW6VFdxbn2Z25b/g5Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" }, @@ -9018,11 +9020,11 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.1.tgz", - "integrity": "sha512-Qt8DMC05lVS8NcQx94lfVbZSX+2Ym7032b/JR8AlboAa/D669kPzqb35dkjkvAG6+NWmUchef3ENtrD6F+5n8Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -9030,22 +9032,22 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.1.tgz", - "integrity": "sha512-ubFUvIePjDCyIzZ+pLETqNC6KXJ/fc6g+/baqel7Zf6kJI/kZKgjwkCI7zbUhoUuOZ/4eA/87YasVu40b/B4bA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", + "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", "dependencies": { - "@smithy/types": "^3.1.0" + "@smithy/types": "^3.3.0" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.1.tgz", - "integrity": "sha512-nD6tXIX2126/P9e3wqRY1bm9dTtPZwRDyjVOd18G28o+1UOG+kOVgUwujE795HslSuPlEgqzsH5sgNP1hDjj9g==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.3.tgz", + "integrity": "sha512-Z8Y3+08vgoDgl4HENqNnnzSISAaGrF2RoKupoC47u2wiMp+Z8P/8mDh1CL8+8ujfi2U5naNvopSBmP/BUj8b5w==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -9070,15 +9072,15 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.3.tgz", - "integrity": "sha512-YVz+akpR5lIIRPJfhE4sqoHYwMys6/33vsFvDof+71FCwa4jkVfMpzKv9TKrG/EDb5TV+YtjdXkwywdqlUOQXA==", - "dependencies": { - "@smithy/middleware-endpoint": "^3.0.2", - "@smithy/middleware-stack": "^3.0.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/types": "^3.1.0", - "@smithy/util-stream": "^3.0.3", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.5.tgz", + "integrity": "sha512-x9bL9Mx2CT2P1OiUlHM+ZNpbVU6TgT32f9CmTRzqIHA7M4vYrROCWEoC3o4xHNJASoGd4Opos3cXYPgh+/m4Ww==", + "dependencies": { + "@smithy/middleware-endpoint": "^3.0.4", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.0.5", "tslib": "^2.6.2" }, "engines": { @@ -9086,9 +9088,9 @@ } }, "node_modules/@smithy/types": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.1.0.tgz", - "integrity": "sha512-qi4SeCVOUPjhSSZrxxB/mB8DrmuSFUcJnD9KXjuP+7C3LV/KFV4kpuUSH3OHDZgQB9TEH/1sO/Fq/5HyaK9MPw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "dependencies": { "tslib": "^2.6.2" }, @@ -9097,12 +9099,12 @@ } }, "node_modules/@smithy/url-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.1.tgz", - "integrity": "sha512-G140IlNFlzYWVCedC4E2d6NycM1dCUbe5CnsGW1hmGt4hYKiGOw0v7lVru9WAn5T2w09QEjl4fOESWjGmCvVmg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", "dependencies": { - "@smithy/querystring-parser": "^3.0.1", - "@smithy/types": "^3.1.0", + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, @@ -9162,13 +9164,13 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.5.tgz", - "integrity": "sha512-VZkJ+bXCHcNSMhX8EReGyFcc/Err94YGqeEKbbxkVz2TgKlacsoplpi+kxOMVbQq/tq9sQx5ajBKG+nl2GNuxw==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.7.tgz", + "integrity": "sha512-Q2txLyvQyGfmjsaDbVV7Sg8psefpFcrnlGapDzXGFRPFKRBeEg6OvFK8FljqjeHSaCZ6/UuzQExUPqBR/2qlDA==", "dependencies": { - "@smithy/property-provider": "^3.1.1", - "@smithy/smithy-client": "^3.1.3", - "@smithy/types": "^3.1.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.1.5", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -9177,16 +9179,16 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.5.tgz", - "integrity": "sha512-jy19cFQA0k4f8VUDFsZVBey3rmI8EuXCw/xh/abdiq6S1qdwdfZ5coviuyYd//LPszf2yWIYkLpvmLF9qbhLGg==", - "dependencies": { - "@smithy/config-resolver": "^3.0.2", - "@smithy/credential-provider-imds": "^3.1.1", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/property-provider": "^3.1.1", - "@smithy/smithy-client": "^3.1.3", - "@smithy/types": "^3.1.0", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.7.tgz", + "integrity": "sha512-F4Qcj1fG6MGi2BSWCslfsMSwllws/WzYONBGtLybyY+halAcXdWhcew+mej8M5SKd5hqPYp4f7b+ABQEaeytgg==", + "dependencies": { + "@smithy/config-resolver": "^3.0.4", + "@smithy/credential-provider-imds": "^3.1.3", + "@smithy/node-config-provider": "^3.1.3", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.1.5", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -9194,12 +9196,12 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.2.tgz", - "integrity": "sha512-4zFOcBFQvifd2LSD4a1dKvfIWWwh4sWNtS3oZ7mpob/qPPmJseqKB148iT+hWCDsG//TmI+8vjYPgZdvnkYlTg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.4.tgz", + "integrity": "sha512-ZAtNf+vXAsgzgRutDDiklU09ZzZiiV/nATyqde4Um4priTmasDH+eLpp3tspL0hS2dEootyFMhu1Y6Y+tzpWBQ==", "dependencies": { - "@smithy/node-config-provider": "^3.1.1", - "@smithy/types": "^3.1.0", + "@smithy/node-config-provider": "^3.1.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -9218,11 +9220,11 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.1.tgz", - "integrity": "sha512-WRODCQtUsO7vIvfrdxS8RFPeLKcewYtaCglZsBsedIKSUGIIvMlZT5oh+pCe72I+1L+OjnZuqRNpN2LKhWA4KQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -9230,12 +9232,12 @@ } }, "node_modules/@smithy/util-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.1.tgz", - "integrity": "sha512-5lRtYm+8fNFEUTdqZXg5M4ppVp40rMIJfR1TpbHAhKQgPIDpWT+iYMaqgnwEbtpi9U1smyUOPv5Sg+M1neOBgw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", + "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", "dependencies": { - "@smithy/service-error-classification": "^3.0.1", - "@smithy/types": "^3.1.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -9243,13 +9245,13 @@ } }, "node_modules/@smithy/util-stream": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.0.3.tgz", - "integrity": "sha512-ztOvXkXKJromRHNzvrLEW/vvTQPnxPBRHA0gR0QX61LnHDgrm4TBT4EQNpWwwHCD1N0nnEL5bEkzo2dt2t34Kg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.0.5.tgz", + "integrity": "sha512-xC3L5PKMAT/Bh8fmHNXP9sdQ4+4aKVUU3EEJ2CF/lLk7R+wtMJM+v/1B4en7jO++Wa5spGzFDBCl0QxgbUc5Ug==", "dependencies": { - "@smithy/fetch-http-handler": "^3.0.3", - "@smithy/node-http-handler": "^3.0.1", - "@smithy/types": "^3.1.0", + "@smithy/fetch-http-handler": "^3.2.0", + "@smithy/node-http-handler": "^3.1.1", + "@smithy/types": "^3.3.0", "@smithy/util-base64": "^3.0.0", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-hex-encoding": "^3.0.0", @@ -19740,9 +19742,9 @@ } }, "node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -19752,9 +19754,9 @@ } }, "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dependencies": { "debug": "^4.3.4" }, @@ -22488,30 +22490,30 @@ } }, "node_modules/jsdom": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.0.0.tgz", - "integrity": "sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz", + "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==", "dependencies": { "cssstyle": "^4.0.1", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.4", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.7", + "nwsapi": "^2.2.10", "parse5": "^7.1.2", - "rrweb-cssom": "^0.6.0", + "rrweb-cssom": "^0.7.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.3", + "tough-cookie": "^4.1.4", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0", - "ws": "^8.16.0", + "ws": "^8.17.0", "xml-name-validator": "^5.0.0" }, "engines": { @@ -22527,9 +22529,9 @@ } }, "node_modules/jsdom/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dependencies": { "debug": "^4.3.4" }, @@ -22538,9 +22540,9 @@ } }, "node_modules/jsdom/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -22549,6 +22551,11 @@ "node": ">= 14" } }, + "node_modules/jsdom/node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==" + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -24668,9 +24675,9 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/nodemailer": { - "version": "6.9.13", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz", - "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==", + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", + "integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==", "engines": { "node": ">=6.0.0" } @@ -24742,9 +24749,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", - "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==" + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", + "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==" }, "node_modules/nypm": { "version": "0.3.8", @@ -28522,9 +28529,9 @@ } }, "node_modules/stylelint-scss": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.3.1.tgz", - "integrity": "sha512-w/czBoWUZxJNk5fBRPODcXSN4qcPv3WHjTSSpFovVY+TE3MZTMR0yRlbmaDYrm8tTWHvpwQAuEBZ0lk2wwkboQ==", + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.3.2.tgz", + "integrity": "sha512-pNk9mXOVKkQtd+SROPC9io8ISSgX+tOVPhFdBE+LaKQnJMLdWPbGKAGYv4Wmf/RrnOjkutunNTN9kKMhkdE5qA==", "dev": true, "dependencies": { "known-css-properties": "^0.31.0", @@ -29281,9 +29288,9 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", From 839a9da8aed61153804da22da1bb27fa0d6f48b5 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Mon, 1 Jul 2024 02:44:43 -0700 Subject: [PATCH 034/137] slightly modified hibp/breaches --- src/app/api/mock/hibp/breaches/route.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/app/api/mock/hibp/breaches/route.ts b/src/app/api/mock/hibp/breaches/route.ts index 66ae78f47c7..624da2230f7 100644 --- a/src/app/api/mock/hibp/breaches/route.ts +++ b/src/app/api/mock/hibp/breaches/route.ts @@ -18,10 +18,19 @@ export async function GET() { logger.info("Mock endpoint: /breaches"); - let allBreaches = await getBreaches(); - if (allBreaches.length === 0) { - allBreaches = mockAllBreaches.data as BreachesListResponse; - } + // This fails when creating a PR + // let allBreaches = await getBreaches(); + // if (allBreaches.length === 0) { + // allBreaches = mockAllBreaches.data as BreachesListResponse; + // } - return NextResponse.json(allBreaches); + try { + let allBreaches = await getBreaches(); + if (allBreaches.length === 0) { + allBreaches = mockAllBreaches.data as BreachesListResponse; + } + return NextResponse.json(allBreaches); + } catch { + return NextResponse.json(mockAllBreaches.data as BreachesListResponse); + } } From feaf567e086b382c649ee7b7af7d62e75f53ce4a Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Mon, 1 Jul 2024 03:45:51 -0700 Subject: [PATCH 035/137] removed database retrieval from mock hibp endpoint --- src/app/api/mock/hibp/breaches/route.ts | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/app/api/mock/hibp/breaches/route.ts b/src/app/api/mock/hibp/breaches/route.ts index 624da2230f7..5219ec5c24c 100644 --- a/src/app/api/mock/hibp/breaches/route.ts +++ b/src/app/api/mock/hibp/breaches/route.ts @@ -6,31 +6,16 @@ import { NextResponse } from "next/server"; import { logger } from "../../../../functions/server/logging"; import mockAllBreaches from "../data/mockAllBreaches.json"; import { errorIfProduction } from "../../utils/errorThrower"; -import { getBreaches } from "../../../../functions/server/getBreaches"; import { Breach } from "../../../../functions/universal/breach"; import { HibpLikeDbBreach } from "../../../../../utils/hibp"; type BreachesListResponse = (Breach | HibpLikeDbBreach)[]; -export async function GET() { +export function GET() { const prodError = errorIfProduction(); if (prodError) return prodError; logger.info("Mock endpoint: /breaches"); - // This fails when creating a PR - // let allBreaches = await getBreaches(); - // if (allBreaches.length === 0) { - // allBreaches = mockAllBreaches.data as BreachesListResponse; - // } - - try { - let allBreaches = await getBreaches(); - if (allBreaches.length === 0) { - allBreaches = mockAllBreaches.data as BreachesListResponse; - } - return NextResponse.json(allBreaches); - } catch { - return NextResponse.json(mockAllBreaches.data as BreachesListResponse); - } + return NextResponse.json(mockAllBreaches.data as BreachesListResponse); } From f693e1aa52c04ae6b9a220cf988c0c15b1652488 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Mon, 1 Jul 2024 08:58:33 -0700 Subject: [PATCH 036/137] Merged with main + minor error log change --- src/app/api/mock/utils/errorThrower.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/mock/utils/errorThrower.ts b/src/app/api/mock/utils/errorThrower.ts index 791283cc495..17dd10db12c 100644 --- a/src/app/api/mock/utils/errorThrower.ts +++ b/src/app/api/mock/utils/errorThrower.ts @@ -18,7 +18,7 @@ export function errorIfEnv(which: string) { //checks that the environment isnt 'which' if (process.env.APP_ENV === which) { return NextResponse.json( - { error: "Endpoint not available in production environment" }, + { error: `Endpoint not available in ${which} environment` }, { status: 403 }, ); } From 9401fedc9ba13dd88367cdfe7389de50bd19a1da Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Mon, 1 Jul 2024 17:17:09 -0700 Subject: [PATCH 037/137] reverted e2e changes --- .../server/getRelevantGuidedSteps.ts | 1 - src/e2e/pages/purchasePage.ts | 2 +- src/e2e/specs/dashboard.spec.ts | 17 ++++++++-------- src/e2e/specs/landing.spec.ts | 20 +++++++++++++++---- src/e2e/utils/helpers.ts | 4 +--- 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/app/functions/server/getRelevantGuidedSteps.ts b/src/app/functions/server/getRelevantGuidedSteps.ts index 93ed0ae3f2b..fb43517a2fa 100644 --- a/src/app/functions/server/getRelevantGuidedSteps.ts +++ b/src/app/functions/server/getRelevantGuidedSteps.ts @@ -70,7 +70,6 @@ export type StepLinkWithStatus = (typeof stepLinks)[number] & { }; export function isGuidedResolutionInProgress(stepId: StepLink["id"]) { - if (stepId === "Scan" || stepId === "Done") return false; //this fixes a lint check const inProgressStepIds = stepLinks .filter((step) => step.id !== "Scan" && step.id !== "Done") .map(({ id }) => id); diff --git a/src/e2e/pages/purchasePage.ts b/src/e2e/pages/purchasePage.ts index 4b0c52de122..e1ead13b02e 100644 --- a/src/e2e/pages/purchasePage.ts +++ b/src/e2e/pages/purchasePage.ts @@ -121,7 +121,7 @@ export class PurchasePage { (await this.planDetails.textContent()) as string, ); expect(planDetails).toContain( - `${process.env.E2E_TEST_ENV!.match(/prod|local/) ? "yearly" : "every 2 months"}`, + `${process.env.E2E_TEST_ENV === "prod" ? "yearly" : "every 2 months"}`, ); } diff --git a/src/e2e/specs/dashboard.spec.ts b/src/e2e/specs/dashboard.spec.ts index bc1b0df3eb0..a72dba31d56 100644 --- a/src/e2e/specs/dashboard.spec.ts +++ b/src/e2e/specs/dashboard.spec.ts @@ -10,6 +10,7 @@ import { removeUnicodeChars, clickOnATagCheckDomain, escapeRegExp, + forceLoginAs, } from "../utils/helpers.js"; // bypass login @@ -425,6 +426,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Breaches Scan, const count = await dashboardPage.allExposures.count(); // Fix first exposure await dashboardPage.markAsFixed.click(); + for (let i = 1; i < count; i++) { const exposure = dashboardPage.allExposures.nth(i); await exposure.click(); @@ -718,14 +720,13 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Navigation`, ( }); test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Data Breaches`, () => { - test.beforeEach(async ({ dashboardPage, page }) => { - await dashboardPage.open(); - - try { - await checkAuthState(page); - } catch { - console.log("[E2E_LOG] - No fxa auth required, proceeding..."); - } + test.beforeEach(async ({ landingPage, page, authPage }) => { + const emailToUse = process.env + .E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED as string; + const pwdToUse = process.env.E2E_TEST_ACCOUNT_PASSWORD as string; + expect(emailToUse).not.toBeUndefined(); + expect(pwdToUse).not.toBeUndefined(); + await forceLoginAs(emailToUse, pwdToUse, page, landingPage, authPage); }); test("Verify that the High risk data breaches step is displayed correctly", async ({ diff --git a/src/e2e/specs/landing.spec.ts b/src/e2e/specs/landing.spec.ts index 58d462c36b6..acb202d4656 100644 --- a/src/e2e/specs/landing.spec.ts +++ b/src/e2e/specs/landing.spec.ts @@ -309,8 +309,14 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali await authPage.enterPassword(); // verify dashboard redirect - const successUrl = process.env.E2E_TEST_BASE_URL + "/user/dashboard"; - expect(page.url()).toContain(successUrl); + const successUrl = + process.env.E2E_TEST_BASE_URL + + `${ + process.env.E2E_TEST_ENV === "local" + ? "/user/welcome" + : "/user/dashboard" + }`; + expect(page.url()).toBe(successUrl); }); test('Verify the "Start free monitoring" button UI and functionality with existing account', async ({ @@ -330,7 +336,13 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali await authPage.enterPassword(); // verify dashboard redirect - const successUrl = process.env.E2E_TEST_BASE_URL + "/user/dashboard"; - expect(page.url()).toContain(successUrl); + const successUrl = + process.env.E2E_TEST_BASE_URL + + `${ + process.env.E2E_TEST_ENV === "local" + ? "/user/welcome" + : "/user/dashboard" + }`; + expect(page.url()).toBe(successUrl); }); }); diff --git a/src/e2e/utils/helpers.ts b/src/e2e/utils/helpers.ts index 6257d254367..0d398436dd0 100644 --- a/src/e2e/utils/helpers.ts +++ b/src/e2e/utils/helpers.ts @@ -170,9 +170,7 @@ export const clickOnATagCheckDomain = async ( page: Page, ) => { if (typeof host === "string") - host = new RegExp( - escapeRegExp(host.replace(/^(https?:\/\/)/, "").replace(/:\d+$/, "")), - ); + host = new RegExp(escapeRegExp(host.replace(/^(https?:\/\/)/, ""))); if (typeof path === "string") path = new RegExp(".*" + path + ".*"); const href = await aTag.getAttribute("href"); From d6678f1190c58528fa186ab59246dae99db6e8c8 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Wed, 3 Jul 2024 14:49:36 -0700 Subject: [PATCH 038/137] feat: add refresh token logic to session management --- src/app/api/utils/auth.ts | 44 +++++++++++++++++++++++++++-- src/db/tables/emailAddresses.js | 5 ++-- src/db/tables/subscribers.js | 49 ++++++++++++++++++++++++++++++++- src/next-auth.d.ts | 1 + src/utils/fxa.js | 36 ++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 5 deletions(-) diff --git a/src/app/api/utils/auth.ts b/src/app/api/utils/auth.ts index 0f75f3a7780..4878193d09e 100644 --- a/src/app/api/utils/auth.ts +++ b/src/app/api/utils/auth.ts @@ -12,11 +12,13 @@ import { getSubscriberByFxaUid, updateFxAData, incrementSignInCountForEligibleFreeUser, + getFxATokens, + updateFxATokens, } from "../../../db/tables/subscribers.js"; import { addSubscriber } from "../../../db/tables/emailAddresses.js"; import { getBreaches } from "../../functions/server/getBreaches"; import { getBreachesForEmail } from "../../../utils/hibp.js"; -import { getSha1 } from "../../../utils/fxa.js"; +import { getSha1, refreshOauthTokens } from "../../../utils/fxa.js"; import { getEmailCtaDashboardHref, initEmail, @@ -130,6 +132,7 @@ export const authOptions: AuthOptions = { existingUser, account.access_token, account.refresh_token, + account.expires_at || 0, JSON.stringify(profile), ); // MNTOR-2599 The breach_resolution object can get pretty big, @@ -144,6 +147,7 @@ export const authOptions: AuthOptions = { profile.locale, account.access_token, account.refresh_token, + account.expires_at, JSON.stringify(profile), ); // The date fields of `verifiedSubscriber` get converted to an ISO 8601 @@ -209,7 +213,7 @@ export const authOptions: AuthOptions = { } return token; }, - session({ session, token }) { + async session({ session, token }) { if (token.fxa) { session.user.fxa = { locale: token.fxa.locale, @@ -222,7 +226,43 @@ export const authOptions: AuthOptions = { } if (token.subscriber) { session.user.subscriber = token.subscriber; + + // refresh token + const dbFxATokens = await getFxATokens(token.subscriber.id); + if ( + !dbFxATokens.fxa_session_expiry || + dbFxATokens.fxa_session_expiry.getTime() < Date.now() + ) { + // If the access token has expired, try to refresh it + if (!dbFxATokens.fxa_refresh_token) { + logger.error("no_fxa_refresh_token", { dbFxATokens }); + session.error = "RefreshAccessTokenError"; + return session; + } + try { + const responseTokens = await refreshOauthTokens( + dbFxATokens.fxa_refresh_token, + ); + const updatedUser = await updateFxATokens( + token.subscriber, + responseTokens.access_token, + responseTokens.refresh_token, + 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.breach_resolution; + token.subscriber = updatedUser; + } catch (error) { + logger.error("refresh_access_token", error); + // The error property can be used client-side to handle the refresh token error + session.error = "RefreshAccessTokenError"; + } + } } + return session; }, }, diff --git a/src/db/tables/emailAddresses.js b/src/db/tables/emailAddresses.js index f58ba88f12b..684fd792bfc 100644 --- a/src/db/tables/emailAddresses.js +++ b/src/db/tables/emailAddresses.js @@ -218,18 +218,19 @@ async function _addEmailHash (sha1, email, signupLanguage, verified = false) { * @param {string} signupLanguage from Accept-Language * @param {string | null} fxaAccessToken from Firefox Account Oauth * @param {string | null} fxaRefreshToken from Firefox Account Oauth + * @param {number} sessionExpiredAt from Firefox Account Oauth * @param {string | null} fxaProfileData from Firefox Account * @returns {Promise} subscriber knex object added to DB */ // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy /* c8 ignore start */ -async function addSubscriber (email, signupLanguage, fxaAccessToken = null, fxaRefreshToken = null, fxaProfileData = null) { +async function addSubscriber (email, signupLanguage, fxaAccessToken = null, fxaRefreshToken = null, sessionExpiredAt = 0, fxaProfileData = null) { const lowerCaseEmail = email.toLowerCase() const emailHash = await _addEmailHash(getSha1(lowerCaseEmail), lowerCaseEmail, signupLanguage, true) const verified = await _verifySubscriber(emailHash) const verifiedSubscriber = Array.isArray(verified) ? verified[0] : null if (fxaRefreshToken || fxaProfileData) { - return updateFxAData(verifiedSubscriber, fxaAccessToken, fxaRefreshToken, fxaProfileData) + return updateFxAData(verifiedSubscriber, fxaAccessToken, fxaRefreshToken, sessionExpiredAt, fxaProfileData) } return verifiedSubscriber } diff --git a/src/db/tables/subscribers.js b/src/db/tables/subscribers.js index 30c081d119c..d98075b9ffc 100644 --- a/src/db/tables/subscribers.js +++ b/src/db/tables/subscribers.js @@ -121,12 +121,13 @@ async function updatePrimaryEmail (subscriber, updatedEmail) { * @param {any} subscriber knex object in DB * @param {string | null} fxaAccessToken from Firefox Account Oauth * @param {string | null} fxaRefreshToken from Firefox Account Oauth + * @param {number} sessionExpiredAt from Firefox Account Oauth * @param {any} 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 /* c8 ignore start */ -async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, fxaProfileData) { +async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, sessionExpiredAt, fxaProfileData) { const fxaUID = JSON.parse(fxaProfileData).uid const updated = await knex('subscribers') .where('id', '=', subscriber.id) @@ -134,6 +135,7 @@ async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, fxaPr fxa_uid: fxaUID, fxa_access_token: fxaAccessToken, fxa_refresh_token: fxaRefreshToken, + fxa_session_expiry: new Date(sessionExpiredAt), 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: @@ -148,6 +150,49 @@ async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, fxaPr } /* c8 ignore stop */ +/** + * Update fxa tokens for subscriber + * + * @param {any} subscriber knex object in DB + * @param {string | null} fxaAccessToken from Firefox Account Oauth + * @param {string | null} fxaRefreshToken from Firefox Account Oauth + * @param {number} sessionExpiredAt from Firefox Account Oauth + * @returns {Promise} updated subscriber knex object in DB + */ +// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy +/* c8 ignore start */ +async function updateFxATokens (subscriber, fxaAccessToken, fxaRefreshToken, sessionExpiredAt) { + const updated = await knex('subscribers') + .where('id', '=', subscriber.id) + .update({ + fxa_access_token: fxaAccessToken, + fxa_refresh_token: fxaRefreshToken, + fxa_session_expiry: new Date(sessionExpiredAt), + // @ts-ignore knex.fn.now() results in it being set to a date, + // even if it's not typed as a JS date object: + updated_at: knex.fn.now(), + }) + .returning('*') + const updatedSubscriber = Array.isArray(updated) ? updated[0] : null + return updatedSubscriber +} +/* c8 ignore stop */ + +/** + * Get fxa tokens and expiry for subscriber + * + * @param {number} subscriberId + */ +// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy +/* c8 ignore start */ +async function getFxATokens (subscriberId) { + const res = await knex('subscribers') + .select('fxa_access_token', 'fxa_refresh_token', 'fxa_session_expiry') + .where('id', subscriberId) + return res?.[0] ?? null +} +/* c8 ignore stop */ + /** * Update fxa_profile_json for subscriber * @@ -626,6 +671,8 @@ export { getSubscribersWithUnresolvedBreachesCount, updatePrimaryEmail, updateFxAData, + updateFxATokens, + getFxATokens, updateFxAProfileData, setAllEmailsToPrimary, setMonthlyMonitorReport, diff --git a/src/next-auth.d.ts b/src/next-auth.d.ts index ec4330a4e51..00153ed7364 100644 --- a/src/next-auth.d.ts +++ b/src/next-auth.d.ts @@ -42,6 +42,7 @@ declare module "next-auth" { /** Session data available after deserialising the JWT */ interface Session { + error?: "RefreshAccessTokenError"; user: { fxa?: { /** The value of the Accept-Language header when the user signed up for their Firefox Account */ diff --git a/src/utils/fxa.js b/src/utils/fxa.js index 8962b4f3e79..7b90400f41d 100644 --- a/src/utils/fxa.js +++ b/src/utils/fxa.js @@ -73,6 +73,41 @@ async function revokeOAuthTokens(subscriber) { await destroyOAuthToken({ token: subscriber.fxa_refresh_token, token_type_hint: "refresh_token" }) } +/** + * @param {string} refreshToken + * @returns {Promise<{access_token: string, refresh_token: string, expires_in: number}>} + */ +// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy +/* c8 ignore start */ +async function refreshOauthTokens(refreshToken) { + const subscriptionIdUrl = `${AppConstants.OAUTH_ACCOUNT_URI}/oauth/token` + try { + const postResp = await fetch(subscriptionIdUrl, { + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + client_id: AppConstants.OAUTH_CLIENT_ID, + client_secret: AppConstants.OAUTH_CLIENT_SECRET, + grant_type: "refresh_token", + refresh_token: refreshToken, + ttl: 604800, // request 7 days ttl + }), + method: "POST", + + }) + + const responseTokens = await postResp.json(); + if (!postResp.ok) throw responseTokens; + return responseTokens + + } catch (e) { + if (e instanceof Error) { + console.error('refresh_fxa_access_token', { stack: e.stack }) + } + throw e + } +} +/* c8 ignore stop */ + /** * @param {string} bearerToken * @returns {Promise | null>} @@ -232,6 +267,7 @@ function getSha1(email) { } export { + refreshOauthTokens, destroyOAuthToken, revokeOAuthTokens, getSha1, From f07115cabad28767803e9d2b581c5e3c53507ffd Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Wed, 3 Jul 2024 16:08:39 -0700 Subject: [PATCH 039/137] temporary additions --- src/app/api/mock/hibp/breaches/route.ts | 2 +- src/app/api/mock/hibp/config/route.ts | 4 +-- src/app/api/mock/hibp/data/mockBreaches.json | 2 +- .../hibp/range/search/[hashPrefix]/route.ts | 2 +- src/app/api/mock/onerep/config/route.ts | 4 +-- .../profiles/[profileId]/activate/route.ts | 2 +- .../profiles/[profileId]/deactivate/route.ts | 2 +- .../profiles/[profileId]/optout/route.ts | 2 +- .../mock/onerep/profiles/[profileId]/route.ts | 2 +- .../[profileId]/scans/[scanId]/route.ts | 2 +- .../profiles/[profileId]/scans/route.ts | 2 +- src/app/api/mock/onerep/profiles/route.ts | 2 +- src/app/api/mock/onerep/scan-results/route.ts | 2 +- .../api/mock/onerep/stats/profiles/route.ts | 2 +- src/app/api/{mock => }/utils/errorThrower.ts | 18 ++++++++--- .../server/refreshStoredScanResults.ts | 2 ++ src/app/functions/universal/mock.ts | 13 ++++++++ src/db/tables/onerep_scans.ts | 31 +------------------ src/e2e/specs/dashboard.spec.ts | 4 +++ src/utils/hibp.js | 13 ++++++++ 20 files changed, 62 insertions(+), 51 deletions(-) rename src/app/api/{mock => }/utils/errorThrower.ts (54%) create mode 100644 src/app/functions/universal/mock.ts diff --git a/src/app/api/mock/hibp/breaches/route.ts b/src/app/api/mock/hibp/breaches/route.ts index 5219ec5c24c..4c1a33d4e83 100644 --- a/src/app/api/mock/hibp/breaches/route.ts +++ b/src/app/api/mock/hibp/breaches/route.ts @@ -5,7 +5,7 @@ import { NextResponse } from "next/server"; import { logger } from "../../../../functions/server/logging"; import mockAllBreaches from "../data/mockAllBreaches.json"; -import { errorIfProduction } from "../../utils/errorThrower"; +import { errorIfProduction } from "../../../utils/errorThrower"; import { Breach } from "../../../../functions/universal/breach"; import { HibpLikeDbBreach } from "../../../../../utils/hibp"; diff --git a/src/app/api/mock/hibp/config/route.ts b/src/app/api/mock/hibp/config/route.ts index c82db14675d..bd722390961 100644 --- a/src/app/api/mock/hibp/config/route.ts +++ b/src/app/api/mock/hibp/config/route.ts @@ -8,7 +8,7 @@ import { isAdmin } from "../../../utils/auth"; import fs from "fs"; import path from "path"; import { MOCK_HIBP_DEFAULT_BREACHES_NAMES } from "./defaults"; -import { errorIfProduction, errorIfStage } from "../../utils/errorThrower"; +import { errorIfNotLocal } from "../../../utils/errorThrower"; type hibpConfigReq = { email: string; @@ -17,7 +17,7 @@ type hibpConfigReq = { }; export async function PUT(req: NextRequest) { - const checks = errorIfStage() || errorIfProduction(); + const checks = errorIfNotLocal(); if (checks !== null) return checks; const data = await req.json(); diff --git a/src/app/api/mock/hibp/data/mockBreaches.json b/src/app/api/mock/hibp/data/mockBreaches.json index 65d091e067a..9f9b4b078d6 100644 --- a/src/app/api/mock/hibp/data/mockBreaches.json +++ b/src/app/api/mock/hibp/data/mockBreaches.json @@ -2,7 +2,7 @@ "data": [ { "hashSuffix": "", - "websites": ["000webhost", "123RF", "Bonobos"] + "websites": ["000webhost", "123RF", "Bonobos", "Deezer"] } ] } diff --git a/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts b/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts index 043c1addb2a..6abb3b60a47 100644 --- a/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts +++ b/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts @@ -5,7 +5,7 @@ import { NextResponse } from "next/server"; import { logger } from "../../../../../../functions/server/logging"; import mockBreaches from "../../../data/mockBreaches.json"; -import { errorIfProduction } from "../../../../utils/errorThrower"; +import { errorIfProduction } from "../../../../../utils/errorThrower"; import { MOCK_HIBP_COMPUTE_SHA1 } from "../../../config/defaults"; type BreachedAccountResponse = { diff --git a/src/app/api/mock/onerep/config/route.ts b/src/app/api/mock/onerep/config/route.ts index fa4cbb93062..28c16cb4c01 100644 --- a/src/app/api/mock/onerep/config/route.ts +++ b/src/app/api/mock/onerep/config/route.ts @@ -9,7 +9,7 @@ import { isAdmin } from "../../../utils/auth"; import fs from "fs"; import path from "path"; import { Broker } from "./config"; -import { errorIfProduction, errorIfStage } from "../../utils/errorThrower"; +import { errorIfNotLocal } from "../../../utils/errorThrower"; type onerepConfigReq = { email: string; @@ -18,7 +18,7 @@ type onerepConfigReq = { }; export async function PUT(req: NextRequest) { - const checks = errorIfStage() || errorIfProduction(); + const checks = errorIfNotLocal(); if (checks !== null) return checks; const data = await req.json(); diff --git a/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts index 3bedaef54e5..18d4be68e07 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/activate/route.ts @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextRequest, NextResponse } from "next/server"; -import { errorIfProduction } from "../../../../utils/errorThrower"; +import { errorIfProduction } from "../../../../../utils/errorThrower"; export function PUT( _: NextRequest, diff --git a/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts index a4dc0e0def8..ffaefd43c05 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/deactivate/route.ts @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextRequest, NextResponse } from "next/server"; -import { errorIfProduction } from "../../../../utils/errorThrower"; +import { errorIfProduction } from "../../../../../utils/errorThrower"; export function PUT( _: NextRequest, diff --git a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts index b28fb2d8885..f005bec2ba1 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/optout/route.ts @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextRequest, NextResponse } from "next/server"; -import { errorIfProduction } from "../../../../utils/errorThrower"; +import { errorIfProduction } from "../../../../../utils/errorThrower"; export function POST( _: NextRequest, diff --git a/src/app/api/mock/onerep/profiles/[profileId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/route.ts index 49cb8e3aaad..084bd8d7ea3 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/route.ts @@ -12,7 +12,7 @@ import { } from "../../config/config.ts"; import { ShowProfileResponse } from "../../../../../functions/server/onerep.ts"; import { NextRequest, NextResponse } from "next/server"; -import { errorIfProduction } from "../../../utils/errorThrower.ts"; +import { errorIfProduction } from "../../../../utils/errorThrower.ts"; // Mock endpoint to simulate fetching a profile by ID export function GET( diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts index 7864613bc77..c1437e21bb2 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts @@ -2,7 +2,7 @@ * 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 { errorIfProduction } from "../../../../../utils/errorThrower"; +import { errorIfProduction } from "../../../../../../utils/errorThrower"; import { MOCK_ONEREP_TIME } from "../../../../config/config"; import { NextRequest, NextResponse } from "next/server"; diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index 1ad2ccc6b22..22bf1e39add 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -4,7 +4,7 @@ import { NextRequest, NextResponse } from "next/server"; import { MOCK_ONEREP_SCAN_ID, MOCK_ONEREP_TIME } from "../../../config/config"; -import { errorIfProduction } from "../../../../utils/errorThrower"; +import { errorIfProduction } from "../../../../../utils/errorThrower"; export function POST( _: NextRequest, diff --git a/src/app/api/mock/onerep/profiles/route.ts b/src/app/api/mock/onerep/profiles/route.ts index 98f2b4d8a54..d938ac05e0f 100644 --- a/src/app/api/mock/onerep/profiles/route.ts +++ b/src/app/api/mock/onerep/profiles/route.ts @@ -5,7 +5,7 @@ import { MOCK_ONEREP_STATUS, MOCK_ONEREP_TIME } from "../config/config.ts"; import { NextRequest, NextResponse } from "next/server"; import { randomInt } from "crypto"; -import { errorIfProduction } from "../../utils/errorThrower.ts"; +import { errorIfProduction } from "../../../utils/errorThrower.ts"; export type RequestProfileData = { first_name: string; diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts index de3fc6a03e0..6f5c35fc463 100644 --- a/src/app/api/mock/onerep/scan-results/route.ts +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -2,7 +2,7 @@ * 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 { errorIfProduction } from "../../utils/errorThrower"; +import { errorIfProduction } from "../../../utils/errorThrower"; import { MOCK_ONEREP_BROKERS } from "../config/config"; import { NextRequest, NextResponse } from "next/server"; diff --git a/src/app/api/mock/onerep/stats/profiles/route.ts b/src/app/api/mock/onerep/stats/profiles/route.ts index fbad9a4e337..69f96adc185 100644 --- a/src/app/api/mock/onerep/stats/profiles/route.ts +++ b/src/app/api/mock/onerep/stats/profiles/route.ts @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextResponse } from "next/server"; -import { errorIfProduction } from "../../../utils/errorThrower"; +import { errorIfProduction } from "../../../../utils/errorThrower"; export function GET() { const prodError = errorIfProduction(); diff --git a/src/app/api/mock/utils/errorThrower.ts b/src/app/api/utils/errorThrower.ts similarity index 54% rename from src/app/api/mock/utils/errorThrower.ts rename to src/app/api/utils/errorThrower.ts index 17dd10db12c..11d0175da6f 100644 --- a/src/app/api/mock/utils/errorThrower.ts +++ b/src/app/api/utils/errorThrower.ts @@ -6,17 +6,25 @@ import { NextResponse } from "next/server"; export function errorIfProduction() { //checks that the environment isnt production - return errorIfEnv("production"); + return errorIfEnvCond("production", false); } export function errorIfStage() { //checks that the environment isnt stage - return errorIfEnv("stage"); + return errorIfEnvCond("stage", false); } -export function errorIfEnv(which: string) { - //checks that the environment isnt 'which' - if (process.env.APP_ENV === which) { +export function errorIfNotLocal() { + return errorIfEnvCond("local", true); +} + +export function errorIfNotENv(which: string) { + return errorIfEnvCond(which, true); +} + +export function errorIfEnvCond(which: string, isEqualToWhich: boolean) { + //checks that the app environment satisfies the 'isEqualToWhich' condition with 'which' + if (isEqualToWhich !== (process.env.APP_ENV === which)) { return NextResponse.json( { error: `Endpoint not available in ${which} environment` }, { status: 403 }, diff --git a/src/app/functions/server/refreshStoredScanResults.ts b/src/app/functions/server/refreshStoredScanResults.ts index cdc8dae0992..4fa82466fc3 100644 --- a/src/app/functions/server/refreshStoredScanResults.ts +++ b/src/app/functions/server/refreshStoredScanResults.ts @@ -9,6 +9,7 @@ import { } from "../../../db/tables/onerep_scans"; import { getAllScanResults, listScans } from "./onerep"; import { logger } from "./logging"; +import { isUsingMockEndpoint } from "../universal/mock"; /** * Attempt to fetch the current scan results from the provider. @@ -19,6 +20,7 @@ import { logger } from "./logging"; * @param onerepProfileId {number} OneRep Profile ID to refresh. */ export async function refreshStoredScanResults(onerepProfileId: number) { + if (isUsingMockEndpoint()) return; try { const remoteScans = (await listScans(onerepProfileId)).data; const localScans = await getAllScansForProfile(onerepProfileId); diff --git a/src/app/functions/universal/mock.ts b/src/app/functions/universal/mock.ts new file mode 100644 index 00000000000..aca3b3ffb6c --- /dev/null +++ b/src/app/functions/universal/mock.ts @@ -0,0 +1,13 @@ +/* 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/. */ + +export function isUsingMockHIBPEndpoint() { + return process.env.HIBP_KANON_API_ROOT?.includes("api/mock") as boolean; +} + +export function isUsingMockONEREPEndpoint() { + return process.env.ONEREP?.includes("api/mock") as boolean; +} + +export const ONEREP_API_BASE = process.env.ONEREP_API_BASE; diff --git a/src/db/tables/onerep_scans.ts b/src/db/tables/onerep_scans.ts index b6988f71481..dd31911fde1 100644 --- a/src/db/tables/onerep_scans.ts +++ b/src/db/tables/onerep_scans.ts @@ -16,7 +16,6 @@ import { SubscriberRow, } from "knex/types/tables"; import { RemovalStatus } from "../../app/functions/universal/scanResult.js"; -import { MOCK_ONEREP_SCAN_ID } from "../../app/api/mock/onerep/config/config.ts"; const knex = createDbConnection(); @@ -196,7 +195,6 @@ async function setOnerepScan( }) .onConflict("onerep_scan_id") .merge({ - onerep_profile_id: onerepProfileId, onerep_scan_status: onerepScanStatus, updated_at: knex.fn.now(), }); @@ -206,7 +204,7 @@ async function addOnerepScanResults( onerepProfileId: number, onerepScanResults: Array, ) { - let scanResultsMap = onerepScanResults.map((scanResult) => ({ + const scanResultsMap = onerepScanResults.map((scanResult) => ({ onerep_scan_result_id: scanResult.id, onerep_scan_id: scanResult.scan_id, link: scanResult.link, @@ -240,33 +238,6 @@ async function addOnerepScanResults( }), }); - // Delete previous records to allow dynamic mock data configuration, maintaining the 'manually_resolved' field. - if (process.env.ONEREP_API_BASE!.includes("/api/mock")) { - const scan_id = MOCK_ONEREP_SCAN_ID(onerepProfileId); - const existingRecords = await knex("onerep_scan_results") - .where("onerep_scan_id", scan_id) - .select("onerep_scan_result_id", "manually_resolved"); - - const resolvedStatusMap = new Map(); - existingRecords.forEach((record) => { - resolvedStatusMap.set( - record.onerep_scan_result_id, - record.manually_resolved, - ); - }); - - scanResultsMap = scanResultsMap.map((item) => { - if (resolvedStatusMap.has(item.onerep_scan_result_id)) { - return { - ...item, - manually_resolved: resolvedStatusMap.get(item.onerep_scan_result_id), - }; - } - return item; - }); - - await knex("onerep_scan_results").where("onerep_scan_id", scan_id).del(); - } if (scanResultsMap.length > 0) { await knex("onerep_scan_results") .insert(scanResultsMap) diff --git a/src/e2e/specs/dashboard.spec.ts b/src/e2e/specs/dashboard.spec.ts index a72dba31d56..c3012d07c03 100644 --- a/src/e2e/specs/dashboard.spec.ts +++ b/src/e2e/specs/dashboard.spec.ts @@ -334,6 +334,10 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Content`, () = } } }); + + // test("Verify that the header looks correct for zero breaches and zero exposures", async ({ + // // + // })) }); test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Payment`, () => { diff --git a/src/utils/hibp.js b/src/utils/hibp.js index dabe608da74..7e22061a7d9 100644 --- a/src/utils/hibp.js +++ b/src/utils/hibp.js @@ -2,12 +2,14 @@ * 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 { boolean } from 'fast-check' import AppConstants from '../appConstants.js' import { getAllBreaches, upsertBreaches, knex } from '../db/tables/breaches.js' import { InternalServerError } from '../utils/error.js' import { getMessage } from '../utils/fluent.js' const { HIBP_THROTTLE_MAX_TRIES, HIBP_THROTTLE_DELAY, HIBP_API_ROOT, HIBP_KANON_API_ROOT, HIBP_KANON_API_TOKEN } = AppConstants + // TODO: fix hardcode const HIBP_USER_AGENT = 'monitor/1.0.0' // When HIBP "re-names" a breach, it keeps its old 'Name' value but gets a new 'Title' @@ -17,6 +19,7 @@ const RENAMED_BREACHES_MAP = { covve: 'db8151dd' } + // TODO: Add unit test when changing this code: /* c8 ignore start */ function _addStandardOptions(options = {}) { @@ -70,6 +73,9 @@ async function _throttledFetch(url, reqOptions, tryCount = 1) { } /* c8 ignore stop */ + +const isUsingMockEndpoint = String(process.env.HIBP_KANON_API_ROOT).includes("api/mock/"); + /** * @param {string} path * @param options @@ -295,6 +301,13 @@ async function getBreachesForEmail(sha1, allBreaches, includeSensitive = false, console.error("failed_kAnonReq_call: no response or empty response") return [] } + if (isUsingMockEndpoint) { + let mockDataBreaches = response[0]; + return allBreaches.filter(breach => mockDataBreaches.websites.includes(breach.Name)).sort((a, b) => { + // @ts-ignore TODO: Turn dates into a number + return new Date(b.AddedDate) - new Date(a.AddedDate) + }) + } // Parse response body, format: // [ // {"hashSuffix":,"websites":[,...]}, From 527206cb3fbc123deeffddd582961219fdc084af Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Thu, 4 Jul 2024 00:53:38 -0700 Subject: [PATCH 040/137] created an endpoint for data deletion --- .../admin/mock-config/ConfigPage.module.scss | 200 -------------- .../admin/mock-config/hibp/breachesLookup.tsx | 56 ---- .../admin/mock-config/hibp/hibpConfig.tsx | 135 ---------- .../admin/mock-config/hibp/page.tsx | 31 --- .../admin/mock-config/onerep/onerepConfig.tsx | 245 ------------------ .../admin/mock-config/onerep/page.tsx | 23 -- src/app/api/mock/onerep/config/config.ts | 55 ++-- src/app/api/mock/onerep/config/route.ts | 28 +- .../api/mock/onerep/deleteTestData/route.ts | 49 ++++ src/app/api/mock/onerep/profiles/route.ts | 1 + src/app/api/mock/onerep/scan-results/route.ts | 2 +- src/app/api/utils/errorThrower.ts | 16 ++ .../server/refreshStoredScanResults.ts | 2 - src/db/tables/onerep_scans.ts | 34 +++ 14 files changed, 156 insertions(+), 721 deletions(-) delete mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/ConfigPage.module.scss delete mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/breachesLookup.tsx delete mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/hibpConfig.tsx delete mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/page.tsx delete mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/onerepConfig.tsx delete mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/page.tsx create mode 100644 src/app/api/mock/onerep/deleteTestData/route.ts diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/ConfigPage.module.scss b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/ConfigPage.module.scss deleted file mode 100644 index e5fe8ca3772..00000000000 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/ConfigPage.module.scss +++ /dev/null @@ -1,200 +0,0 @@ -@import "../../../../../tokens"; - -.wrapper { - display: grid; - grid-template-rows: 120px min-content; - gap: $spacing-md; - height: 100%; - padding: $layout-sm; - background-color: $color-grey-05; - align-items: center; - justify-content: center; -} - -.wrapperHibp { - grid-template-rows: 120px min-content 100px; - justify-content: center; -} - -.header { - font: $text-title-xs; - font-weight: normal; - margin: auto; - width: 100%; - b { - font-weight: bold; - } - text-align: center; -} - -.formAndListWrapper { - margin-top: 40px; - display: grid; - grid-template-rows: 1fr; - grid-template-columns: 3fr 2fr; - justify-content: center; -} - -.hibpFormAndListWrapper { - grid-template-columns: 1fr 1fr; -} - -.formWrapper { - display: grid; - grid-template-rows: 50px 4fr 100px; - justify-content: center; -} - -.h2 { - text-align: center; -} - -.allBreachesWrapper { - display: grid; - grid-template-rows: 50px 4fr; - justify-content: center; -} - -.listButtonsWrapper { - display: grid; - grid-template-rows: auto 100px; - justify-content: center; -} - -.listContainer { - max-height: 330px; - overflow-y: auto; -} - -.searchBox { - background-color: white; - border: 1px solid rgb(139, 139, 139); - border-radius: 3px; - height: 240px; - width: 100%; -} - -.buttonsWrapper { - height: auto; - display: grid; - grid-template-columns: 1fr 1fr; -} - -.buttonsStandalone { - height: 100px; - width: 100%; - display: flex; - justify-content: center; -} - -.listWrapper { - height: min-content; - display: grid; - grid-template-rows: 50px auto; - justify-content: center; - align-items: flex-start; -} - -.hibpSelectedBoxWrapper { - width: 100%; - grid-template-columns: 100%; - .listContainer { - margin-left: 10px; - margin-right: 10px; - margin-top: 8px; - height: 300px; - } - p { - margin-left: 10px; - } -} - -.button { - display: block; - margin: auto; - max-height: 100px; - max-width: 100px; - margin-top: 10px; - margin-bottom: 10px; -} - -.buttonUnderList { - margin-left: 10px; - margin-right: 10px; -} - -.buttonsHibp { - margin: 10px; - justify-self: center; -} - -@keyframes shake { - 0% { - transform: translateX(0); - } - 25% { - transform: translateX(-5px); - } - 75% { - transform: translateX(5px); - } - 100% { - transform: translateX(0); - } -} - -.error { - border-color: red; /* Example: Highlight border in red for error */ - animation: shake 0.5s ease-in-out; /* Example animation */ -} - -.form { - display: flex; - flex-direction: column; - gap: $spacing-sm; - align-items: center; - width: min-content; - - .userPicker { - flex: 1 0 auto; - align-items: center; - display: flex; - flex-wrap: wrap; - gap: $spacing-md; - min-height: $layout-md; - - label { - display: flex; - flex-direction: column; - gap: $spacing-sm; - min-width: 50%; - width: 350px; - } - - input { - padding: $spacing-sm; - font: $text-body-md; - transition: border-color 0.3s ease; - } - } -} - -.listItem:hover { - background-color: #f0f0f0; /* Light grey background on hover */ - cursor: pointer; /* Change cursor to indicate clickable item */ - text-decoration: line-through; -} - -.allBreachesItem:hover { - background-color: #f0f0f0; /* Light grey background on hover */ - cursor: pointer; /* Change cursor to indicate clickable item */ -} - -.selectedBox { - margin: 0; -} - -.breachName { - color: black; - margin-left: 10px; -} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/breachesLookup.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/breachesLookup.tsx deleted file mode 100644 index eb392347514..00000000000 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/breachesLookup.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* 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 { getBreaches } from "../../../../../../functions/server/getBreaches.ts"; -import styles from "../ConfigPage.module.scss"; -import { Breach } from "../../../../../../functions/universal/breach.ts"; -import { HibpLikeDbBreach } from "../../../../../../../utils/hibp.js"; - -interface Props { - allBreaches: (Breach | HibpLikeDbBreach)[]; - filterWord?: string; - onClickFunc?: (elem: Breach | HibpLikeDbBreach) => void; - additionalStyles?: string; -} - -export default function BreachesLookup(props: Props) { - // const allBreaches = (await getBreaches()); - - const { - allBreaches, - filterWord = "", - onClickFunc = () => {}, - additionalStyles = "", - } = props; - - const getElementData = (breach: Breach | HibpLikeDbBreach) => { - const name = breach.Name; - const month = (breach.AddedDate as Date).toLocaleString("en-US", { - month: "long", - }); - const year = (breach.AddedDate as Date).getFullYear(); - return `${name} - ${month}, ${year}`; - }; - - return ( -
- {allBreaches - .filter( - (elem) => - elem.Name.includes(filterWord) || - elem.Title.includes(filterWord) || - String(elem.AddedDate).includes(filterWord), - ) - .map((elem, index) => ( -

onClickFunc(elem)} - key={index} - > - {getElementData(elem)} -

- ))} -
- ); -} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/hibpConfig.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/hibpConfig.tsx deleted file mode 100644 index 1d5b9f68627..00000000000 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/hibpConfig.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* 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/. */ - -"use client"; - -import React, { useState } from "react"; -import { useSession } from "next-auth/react"; -import BreachesLookup from "./breachesLookup.tsx"; -import styles from "../ConfigPage.module.scss"; -import { Breach } from "../../../../../../functions/universal/breach.ts"; -import { HibpLikeDbBreach } from "../../../../../../../utils/hibp.js"; - -interface Props { - allBreaches: (Breach | HibpLikeDbBreach)[]; -} - -const ConfigPage = (props: Props) => { - const [breaches, setBreaches] = useState<(Breach | HibpLikeDbBreach)[]>([]); - const [breachSearch, setBreachSearch] = useState(); - const session = useSession(); - - const handleChange = (e: React.ChangeEvent) => { - setBreachSearch(e.target.value); - }; - - const handleAddBreach = (breach: Breach | HibpLikeDbBreach) => { - setBreaches([...breaches, breach]); - }; - - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - try { - void makeConfigRequest(false); - } catch (error) { - console.error("Error submitting brokers:", error); - alert("Failed to submit brokers."); - } - }; - - const handleDeleteBreach = (breach: Breach | HibpLikeDbBreach) => { - setBreaches(breaches.filter((elem) => elem !== breach)); - }; - - const makeConfigRequest = (erase: boolean) => { - const breachesNames = breaches.map((elem) => elem.Name); - void fetch("/api/mock/hibp/config", { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - email: session.data?.user.email, - breachesNames: breachesNames, - erase: erase, - }), - }).then(async (resp) => - console.log("Response from endpoint config:", await resp.json()), - ); - alert("Brokers list update request submitted successfully!"); - }; - - return ( -
-
-
- Changing the default response for HIBP mock endpoint -
-
-
- -
-
-

Select Breach Element

-
-
- -
- -
-
-
-
-
-

Added breaches List

-

Added breaches:

-
- -
-
-
-
- - -
-
- ); -}; - -export default ConfigPage; diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/page.tsx deleted file mode 100644 index 00256f22821..00000000000 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/hibp/page.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* 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 { getServerSession } from "../../../../../../functions/server/getServerSession"; -import { notFound } from "next/navigation"; -import { isAdmin } from "../../../../../../api/utils/auth"; -import ConfigPage from "./hibpConfig"; -import { getBreaches } from "../../../../../../functions/server/getBreaches.ts"; - -export default async function DevPage() { - const session = await getServerSession(); - const allBreaches = (await getBreaches()).filter( - (breach) => - !breach.IsRetired && - !breach.IsSpamList && - !breach.IsFabricated && - breach.IsVerified && - breach.Domain !== "", - ); - - if ( - !session?.user?.email || - !isAdmin(session.user.email) || - process.env.APP_ENV !== "local" - ) { - return notFound(); - } - - return ; -} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/onerepConfig.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/onerepConfig.tsx deleted file mode 100644 index a964a10a832..00000000000 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/onerepConfig.tsx +++ /dev/null @@ -1,245 +0,0 @@ -/* 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/. */ - -"use client"; - -import React, { useState } from "react"; -import { - MOCK_ONEREP_DATABROKER_ID_START, - MOCK_ONEREP_ID_START, - MOCK_ONEREP_SCAN_ID, - MOCK_ONEREP_TIME, - MOCK_ONEREP_FIRSTNAME, - MOCK_ONEREP_LASTNAME, - MOCK_ONEREP_ADDRESSES, - MOCK_ONEREP_EMAILS, - MOCK_ONEREP_RELATIVES, - MOCK_ONEREP_PHONES, - Broker, -} from "../../../../../../api/mock/onerep/config/config"; -import { useSession } from "next-auth/react"; -import styles from "../ConfigPage.module.scss"; - -const ConfigPage = () => { - const [brokers, setBrokers] = useState([]); - const session = useSession(); - - const [errors, setErrors] = useState({ - profile_id: false, - data_broker: false, - link: false, - }); - - // Initialize a base broker template to reset form fields - const baseBroker = { - id: -1, - profile_id: -1, - scan_id: -1, - status: "new", - first_name: MOCK_ONEREP_FIRSTNAME(), - middle_name: null, - last_name: MOCK_ONEREP_LASTNAME(), - age: null, - addresses: MOCK_ONEREP_ADDRESSES(), - phones: MOCK_ONEREP_PHONES(), - emails: MOCK_ONEREP_EMAILS(), - relatives: MOCK_ONEREP_RELATIVES(), - link: "", - data_broker: "", - data_broker_id: -1, - optout_attempts: 0, - created_at: MOCK_ONEREP_TIME(), - updated_at: MOCK_ONEREP_TIME(), - }; - - // Temporary state to hold form input for a new broker - const [newBroker, setNewBroker] = useState(baseBroker); - - const handleChange = (e: React.ChangeEvent) => { - setNewBroker({ ...newBroker, [e.target.name]: e.target.value }); - }; - - const handleAddBroker = (event: React.FormEvent) => { - event.preventDefault(); - - let hasError = false; - - if (newBroker.profile_id < 0) { - setErrors({ ...errors, profile_id: true }); - hasError = true; - } else { - setErrors({ ...errors, profile_id: false }); - } - - if (newBroker.data_broker.length === 0) { - setErrors({ ...errors, data_broker: true }); - hasError = true; - } else { - setErrors({ ...errors, data_broker: false }); - } - - let linkString = ""; - try { - const urlObj = new URL(newBroker.link); - linkString = urlObj.href; - setErrors({ ...errors, link: false }); - } catch { - setErrors({ ...errors, link: true }); - hasError = true; - } - - if (hasError) return; - - const profileId = Number(newBroker.profile_id); - const scandId = MOCK_ONEREP_SCAN_ID(profileId); - const brokerIdStart = MOCK_ONEREP_DATABROKER_ID_START(profileId); - const idStart = MOCK_ONEREP_ID_START(profileId); - - setBrokers([ - ...brokers, - { - ...newBroker, - link: linkString, - id: idStart - brokers.length, - scan_id: scandId, - data_broker_id: brokerIdStart - brokers.length, - profile_id: profileId, - }, - ]); - - setNewBroker(baseBroker); // Reset form fields - }; - - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - try { - void makeConfigRequest(false); - } catch (error) { - console.error("Error submitting brokers:", error); - alert("Failed to submit brokers."); - } - }; - - const handleDeleteBroker = (id: number) => { - setBrokers(brokers.filter((broker) => broker.id !== id)); - }; - - const makeConfigRequest = (erase: boolean) => { - void fetch("/api/mock/onerep/config", { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - email: session.data?.user.email, - brokers: brokers, - erase: erase, - }), - }).then(async (resp) => - console.log("Response from endpoint config:", await resp.json()), - ); - alert("Brokers list update request submitted successfully!"); - }; - - return ( -
-
-
- Changing the default response for OneRep mock endpoint -
-
-
- -
-
-

Input Broker Element

-
-
- - - - - -
-
- -
-
-
-

Brokers List

-
    - {brokers.length !== 0 ? ( - brokers.map((broker) => ( -
  • handleDeleteBroker(broker.id)} - className={styles.listItem} - > - {"{"} {broker.profile_id}, {broker.data_broker},{" "} - {broker.link} {"}"} -
  • - )) - ) : ( -

    empty...

    - )} -
-
- -
- - - -
-
-
-
- ); -}; - -export default ConfigPage; diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/page.tsx deleted file mode 100644 index f33e057bccf..00000000000 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/mock-config/onerep/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* 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 { getServerSession } from "../../../../../../functions/server/getServerSession"; -import { notFound } from "next/navigation"; -import { isAdmin } from "../../../../../../api/utils/auth"; -import ConfigPage from "./onerepConfig"; - -export default async function DevPage() { - const session = await getServerSession(); - - if ( - !session?.user?.email || - (!isAdmin(session.user.email) && - session.user.email !== process.env.E2E_TEST_ACCOUNT_EMAIL) || - process.env.APP_ENV !== "local" - ) { - return notFound(); - } - - return ; -} diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index ba326edc421..264f009f6b8 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -2,10 +2,8 @@ * 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 { randomInt } from "crypto"; import { StateAbbr } from "../../../../../utils/states"; import MockUser from "./mockUser.json"; -// import { getLatestOnerepScanResults } from "../../../../../db/tables/onerep_scans"; export interface Broker { id: number; @@ -86,30 +84,51 @@ export function MOCK_ONEREP_ADDRESSES() { })) as typeOfAddr; } +export function MOCK_ONEREP_OBJECT_META(page: number | string = 1) { + if (typeof page === "string") page = parseInt(page); + return { + current_page: page, + from: 1, + last_page: 1, + path: `${process.env.ONEREP_API_BASE}/scan-results`, + per_page: page, + to: 10, + total: 10, + }; +} + +export function MOCK_ONEREP_OBJECT_LINKS( + profileId: number | string, + page: number | string = 100, + perPage: number | string = 100, +) { + if (typeof profileId === "string") profileId = parseInt(profileId); + if (typeof page === "string") page = parseInt(page); + if (typeof perPage === "string") perPage = parseInt(perPage); + + return { + first: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, + last: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, + prev: null, + next: null, + }; +} + export function MOCK_ONEREP_BROKERS( profileId: number, page: string, perPage: string, + numberOfBrokers: number = DEFAULT_NUMBER_BREACHES, ) { const mockResponseData = MockUser.BROKERS_LIST; + //TODO-mock: change the scan_id creation (make higher) const scanId = MOCK_ONEREP_SCAN_ID(profileId); - const mockLinks = { - first: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, - last: `${process.env.ONEREP_API_BASE}/scan-results?profile_id%5B0%5D=${profileId}&per_page=${perPage}&page=${page}`, - prev: null, - next: null, - }; + const mockMeta = MOCK_ONEREP_OBJECT_META(page); + const mockLinks = MOCK_ONEREP_OBJECT_LINKS(profileId, page, perPage); - const mockMeta = { - current_page: parseInt(page), - from: 1, - last_page: 1, - path: `${process.env.ONEREP_API_BASE}/scan-results`, - per_page: parseInt(perPage), - to: 10, - total: 10, - }; + //TODO-mock: based on email, select data response + //TODO-mock: change the mechanism (.valid) if (mockResponseData.valid) { const response: { @@ -139,7 +158,7 @@ export function MOCK_ONEREP_BROKERS( const idStartDataBroker = MOCK_ONEREP_DATABROKER_ID_START(profileId); const responseData = { - data: new Array(DEFAULT_NUMBER_BREACHES).fill(null).map( + data: new Array(numberOfBrokers).fill(null).map( (_, index) => ({ id: idStart - index, diff --git a/src/app/api/mock/onerep/config/route.ts b/src/app/api/mock/onerep/config/route.ts index 28c16cb4c01..4d4a3560b70 100644 --- a/src/app/api/mock/onerep/config/route.ts +++ b/src/app/api/mock/onerep/config/route.ts @@ -4,20 +4,27 @@ import { NextRequest, NextResponse } from "next/server"; import { randomInt } from "crypto"; -// import { logger } from "../../../../functions/server/logging"; -import { isAdmin } from "../../../utils/auth"; +import { logger } from "../../../../functions/server/logging"; import fs from "fs"; import path from "path"; import { Broker } from "./config"; -import { errorIfNotLocal } from "../../../utils/errorThrower"; +// import { errorIfNotLocal } from "../../../utils/errorThrower"; -type onerepConfigReq = { - email: string; - erase?: boolean; - brokers: [Broker]; -}; +// type onerepConfigReq = { +// email: string; +// erase?: boolean; +// brokers: [Broker]; +// }; -export async function PUT(req: NextRequest) { +export function PUT(req: NextRequest) { + logger.info( + `Mock OneRep endpoint: Attempted to access ${updateJsonFile.name}. (${req.bodyUsed})`, + ); + return NextResponse.json( + { error: "Endpoint not available yet" }, + { status: 403 }, + ); + /* const checks = errorIfNotLocal(); if (checks !== null) return checks; @@ -30,7 +37,8 @@ export async function PUT(req: NextRequest) { { status: 401 }, ); } - return updateJsonFile(erase, brokers); + return updateJsonFile(erase, brokers); + */ } function updateJsonFile(erase: boolean, brokers: [Broker]) { diff --git a/src/app/api/mock/onerep/deleteTestData/route.ts b/src/app/api/mock/onerep/deleteTestData/route.ts new file mode 100644 index 00000000000..4ce7fea5e3e --- /dev/null +++ b/src/app/api/mock/onerep/deleteTestData/route.ts @@ -0,0 +1,49 @@ +/* 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 { getServerSession } from "../../../../functions/server/getServerSession"; +import { + errorIfProduction, + internalServerError, + unauthError, +} from "../../../utils/errorThrower"; +import { getOnerepProfileId } from "../../../../../db/tables/subscribers"; +import { + deleteScanResultsForProfile, + deleteSomeScansForProfile, +} from "../../../../../db/tables/onerep_scans"; +import { logger } from "../../../../functions/server/logging"; + +function isTestEmail(email: string) { + if (!email) return false; + const testEmailKeys = Object.keys(process.env).filter((key) => + key.startsWith("E2E_TEST_ACCOUNT_EMAIL"), + ); + const testEmails = testEmailKeys.map((key) => process.env[key]); + return testEmails.includes(email); +} + +export async function GET() { + const prodError = errorIfProduction(); + if (prodError) return prodError; + + const session = await getServerSession(); + const email = session?.user.email; + const subscriberId = session?.user.subscriber?.id; + if (!session || !email || !isTestEmail(email) || !subscriberId) + return unauthError(); + + const onerepProfileId = await getOnerepProfileId(subscriberId); + if (!onerepProfileId) + return internalServerError("Unable to fetch OneRep profile ID"); + await deleteScanResultsForProfile(onerepProfileId); + await deleteSomeScansForProfile(onerepProfileId, 1); + logger.info("Mock OneRep endpoint: attempted to delete all but 1 scans"); + + return NextResponse.json( + { message: "Requested reached successfully" }, + { status: 200 }, + ); +} diff --git a/src/app/api/mock/onerep/profiles/route.ts b/src/app/api/mock/onerep/profiles/route.ts index d938ac05e0f..3bf53161625 100644 --- a/src/app/api/mock/onerep/profiles/route.ts +++ b/src/app/api/mock/onerep/profiles/route.ts @@ -47,6 +47,7 @@ export async function POST(req: NextRequest) { const prodError = errorIfProduction(); if (prodError) return prodError; + //TODO-mock: makes this considerably higher const profileId = randomInt(1000, 10000); try { if (req.body === null) { diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts index 6f5c35fc463..099c217c36d 100644 --- a/src/app/api/mock/onerep/scan-results/route.ts +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -23,6 +23,6 @@ export function GET(req: NextRequest) { } return NextResponse.json( - MOCK_ONEREP_BROKERS(Number(profileId), page, perPage), + MOCK_ONEREP_BROKERS(Number(profileId), page, perPage, 10), ); } diff --git a/src/app/api/utils/errorThrower.ts b/src/app/api/utils/errorThrower.ts index 11d0175da6f..1fbcbbc34ea 100644 --- a/src/app/api/utils/errorThrower.ts +++ b/src/app/api/utils/errorThrower.ts @@ -32,3 +32,19 @@ export function errorIfEnvCond(which: string, isEqualToWhich: boolean) { } return null; } + +export function unauthError() { + return NextResponse.json( + { error: "Unauthorized to access the endpoint" }, + { status: 401 }, + ); +} + +export function internalServerError( + description: string = "something went wrong!", +) { + return NextResponse.json( + { error: `Internal server error: ${description}` }, + { status: 401 }, + ); +} diff --git a/src/app/functions/server/refreshStoredScanResults.ts b/src/app/functions/server/refreshStoredScanResults.ts index 4fa82466fc3..cdc8dae0992 100644 --- a/src/app/functions/server/refreshStoredScanResults.ts +++ b/src/app/functions/server/refreshStoredScanResults.ts @@ -9,7 +9,6 @@ import { } from "../../../db/tables/onerep_scans"; import { getAllScanResults, listScans } from "./onerep"; import { logger } from "./logging"; -import { isUsingMockEndpoint } from "../universal/mock"; /** * Attempt to fetch the current scan results from the provider. @@ -20,7 +19,6 @@ import { isUsingMockEndpoint } from "../universal/mock"; * @param onerepProfileId {number} OneRep Profile ID to refresh. */ export async function refreshStoredScanResults(onerepProfileId: number) { - if (isUsingMockEndpoint()) return; try { const remoteScans = (await listScans(onerepProfileId)).data; const localScans = await getAllScansForProfile(onerepProfileId); diff --git a/src/db/tables/onerep_scans.ts b/src/db/tables/onerep_scans.ts index dd31911fde1..7b809860853 100644 --- a/src/db/tables/onerep_scans.ts +++ b/src/db/tables/onerep_scans.ts @@ -330,6 +330,39 @@ async function deleteScanResultsForProfile( .where("onerep_profile_id", onerepProfileId); } +async function deleteSomeScansForProfile( + onerepProfileId: number, + leaveOutAtMost: number = 0, +) { + if (leaveOutAtMost < 0) { + logger.info(`Attempted deleting ${leaveOutAtMost} rows, None were.`); + return; + } + const countResult = await knex("onerep_scans") + .where("onerep_profile_id", onerepProfileId) + .count("onerep_profile_id", { as: "total" }); + + const totalRows = countResult[0].total as number; + + const toDelete = Math.max(totalRows - leaveOutAtMost, 0); + + if (toDelete > 0) { + await knex("onerep_scans") + .where("onerep_profile_id", onerepProfileId) + .orderBy("onerep_scan_id", "desc") + .limit(toDelete) + .del(); + + logger.info( + `Deleted ${toDelete} rows for profileId ${onerepProfileId}, left ${Math.min(leaveOutAtMost, totalRows)} rows.`, + ); + } else { + logger.info( + "No rows need to be deleted, or the conditions do not allow deletion.", + ); + } +} + export { getAllScansForProfile, getLatestScanForProfileByReason, @@ -346,4 +379,5 @@ export { getScansCountForProfile, deleteScansForProfile, deleteScanResultsForProfile, + deleteSomeScansForProfile, }; From 1f539f68fa4a75aae59f2a88896c074cf37e0a75 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Sat, 6 Jul 2024 18:24:37 -0700 Subject: [PATCH 041/137] implemented different mock results retrieval based on email, and a test data deletion page. --- src/app/api/mock/hibp/config/defaults.ts | 17 ++- src/app/api/mock/hibp/config/route.ts | 46 +++--- .../api/mock/hibp/data/mockAllBreaches.json | 29 ++++ src/app/api/mock/hibp/data/mockBreaches.json | 9 +- .../hibp/range/search/[hashPrefix]/route.ts | 30 ++-- .../route.ts | 0 src/app/api/mock/onerep/config/config.ts | 132 ++++++++++-------- src/app/api/mock/onerep/config/mockUser.json | 32 ++++- .../profiles/[profileId]/scans/route.ts | 26 ++-- src/app/api/mock/onerep/profiles/route.ts | 9 +- src/app/api/mock/onerep/scan-results/route.ts | 4 +- src/app/api/utils/mockUtils.ts | 31 ++++ src/app/functions/server/onerep.ts | 7 + src/app/functions/universal/mock.ts | 2 +- src/db/tables/onerep_scans.ts | 14 ++ src/utils/hibp.js | 11 +- 16 files changed, 257 insertions(+), 142 deletions(-) rename src/app/api/mock/onerep/{deleteTestData => clearTestData}/route.ts (100%) create mode 100644 src/app/api/utils/mockUtils.ts diff --git a/src/app/api/mock/hibp/config/defaults.ts b/src/app/api/mock/hibp/config/defaults.ts index 41a53ba4557..963c9ce1877 100644 --- a/src/app/api/mock/hibp/config/defaults.ts +++ b/src/app/api/mock/hibp/config/defaults.ts @@ -2,15 +2,14 @@ * 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 { BinaryLike } from "crypto"; -import { getSha1 } from "../../../../../utils/fxa"; +import mockBreaches from "../data/mockBreaches.json"; +import { hashToEmailKeyMap } from "../../../utils/mockUtils"; -export function MOCK_HIBP_DEFAULT_BREACHES_NAMES() { - return ["000webhost", "123RF", "Bonobos"]; +export interface BreachMap { + [key: string]: string[]; } -export function MOCK_HIBP_COMPUTE_SHA1(email: string) { - return getSha1(email as BinaryLike) - .slice(6) - .toUpperCase(); -} +export const getBreachesForHash = (hash: string) => { + const key = hashToEmailKeyMap[hash] || "default"; + return (mockBreaches as BreachMap)[key]; +}; diff --git a/src/app/api/mock/hibp/config/route.ts b/src/app/api/mock/hibp/config/route.ts index bd722390961..c0889ce21a0 100644 --- a/src/app/api/mock/hibp/config/route.ts +++ b/src/app/api/mock/hibp/config/route.ts @@ -3,33 +3,41 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextRequest, NextResponse } from "next/server"; -// import { logger } from "../../../../functions/server/logging"; -import { isAdmin } from "../../../utils/auth"; +// import { isAdmin } from "../../../utils/auth"; import fs from "fs"; import path from "path"; -import { MOCK_HIBP_DEFAULT_BREACHES_NAMES } from "./defaults"; import { errorIfNotLocal } from "../../../utils/errorThrower"; +import mockBreaches from "../data/mockBreaches.json"; +import { logger } from "../../../../functions/server/logging"; -type hibpConfigReq = { - email: string; - breachesNames: string[]; - erase?: boolean; -}; +// type hibpConfigReq = { +// email: string; +// breachesNames: string[]; +// erase?: boolean; +// }; -export async function PUT(req: NextRequest) { +export function PUT(req: NextRequest) { const checks = errorIfNotLocal(); if (checks !== null) return checks; - const data = await req.json(); - const { email, erase = false, breachesNames } = data as hibpConfigReq; + logger.info( + `Mock OneRep endpoint: Attempted to access ${updateJsonFile.name}. (${req.bodyUsed})`, + ); + return NextResponse.json( + { error: "Endpoint not available yet" }, + { status: 403 }, + ); - if (!isAdmin(email)) { - return NextResponse.json( - { error: "Mock endpoint HIBP: Unauthorized to access the endpoint" }, - { status: 401 }, - ); - } - return updateJsonFile(erase, breachesNames); + // const data = await req.json(); + // const { email, erase = false, breachesNames } = data as hibpConfigReq; + + // if (!isAdmin(email)) { + // return NextResponse.json( + // { error: "Mock endpoint HIBP: Unauthorized to access the endpoint" }, + // { status: 401 }, + // ); + // } + // return updateJsonFile(erase, breachesNames); } function updateJsonFile(erase: boolean, breachesNames: string[]) { @@ -47,7 +55,7 @@ function updateJsonFile(erase: boolean, breachesNames: string[]) { jsonData.data = [ { hashSuffix: "", - websites: erase ? MOCK_HIBP_DEFAULT_BREACHES_NAMES() : breachesNames, + websites: erase ? mockBreaches.default : breachesNames, }, ]; diff --git a/src/app/api/mock/hibp/data/mockAllBreaches.json b/src/app/api/mock/hibp/data/mockAllBreaches.json index 95a44733b68..e7bd9c32d4d 100644 --- a/src/app/api/mock/hibp/data/mockAllBreaches.json +++ b/src/app/api/mock/hibp/data/mockAllBreaches.json @@ -74,6 +74,35 @@ "IsRetired": false, "IsSpamList": false, "IsMalware": false + }, + { + "Id": 174, + "Name": "Deezer", + "Title": "Deezer", + "Domain": "deezer.com", + "BreachDate": "2019-04-22T07:00:00.000Z", + "AddedDate": "2023-01-02T03:10:50.000Z", + "ModifiedDate": "2023-01-02T03:10:50.000Z", + "PwnCount": 229037936, + "Description": "In late 2022, the music streaming service Deezer disclosed a data breach that impacted over 240M customers. The breach dated back to a mid-2019 backup exposed by a 3rd party partner which was subsequently sold and then broadly redistributed on a popular hacking forum. Impacted data included 229M unique email addresses, IP addresses, names, usernames, genders, DoBs and the geographic location of the customer.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/Deezer.png", + "DataClasses": [ + "dates-of-birth", + "email-addresses", + "genders", + "geographic-locations", + "ip-addresses", + "names", + "spoken-languages", + "usernames" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null } ] } diff --git a/src/app/api/mock/hibp/data/mockBreaches.json b/src/app/api/mock/hibp/data/mockBreaches.json index 9f9b4b078d6..ec107cd28ac 100644 --- a/src/app/api/mock/hibp/data/mockBreaches.json +++ b/src/app/api/mock/hibp/data/mockBreaches.json @@ -1,8 +1,5 @@ { - "data": [ - { - "hashSuffix": "", - "websites": ["000webhost", "123RF", "Bonobos", "Deezer"] - } - ] + "default": ["000webhost", "123RF", "Bonobos"], + "E2E_TEST_ACCOUNT_EMAIL": ["000webhost", "123RF", "Bonobos", "Deezer"], + "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES": [] } diff --git a/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts b/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts index 6abb3b60a47..3956d8a2230 100644 --- a/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts +++ b/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts @@ -2,36 +2,34 @@ * 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 { NextRequest, NextResponse } from "next/server"; import { logger } from "../../../../../../functions/server/logging"; -import mockBreaches from "../../../data/mockBreaches.json"; import { errorIfProduction } from "../../../../../utils/errorThrower"; -import { MOCK_HIBP_COMPUTE_SHA1 } from "../../../config/defaults"; +import { getBreachesForHash } from "../../../config/defaults"; type BreachedAccountResponse = { hashSuffix: string; websites: string[]; }[]; -export function GET() { +export function GET( + _: NextRequest, + { params }: { params: { hashPrefix: string } }, +) { const prodError = errorIfProduction(); if (prodError) return prodError; logger.info("HIBP Mock endpoint: /range/search/"); - if (process.env.E2E_TEST_ACCOUNT_EMAIL === undefined) { - return NextResponse.json({ - error: 500, - message: "HIBP Mock endpoint: E2E test account isn't set up!", - }); - } - const sha1Sliced = MOCK_HIBP_COMPUTE_SHA1(process.env.E2E_TEST_ACCOUNT_EMAIL); - let data = mockBreaches.data as BreachedAccountResponse; + const hashPrefix = params.hashPrefix; + const breachesList = getBreachesForHash(hashPrefix); - data = data.map((elem) => ({ - ...elem, - hashSuffix: sha1Sliced, - })); + const data: BreachedAccountResponse = [ + { + hashSuffix: "", //hibp.js ignores hashSuffix if a mock endpoint is used. + websites: breachesList, + }, + ]; const res = NextResponse.json(data); return res; } diff --git a/src/app/api/mock/onerep/deleteTestData/route.ts b/src/app/api/mock/onerep/clearTestData/route.ts similarity index 100% rename from src/app/api/mock/onerep/deleteTestData/route.ts rename to src/app/api/mock/onerep/clearTestData/route.ts diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index 264f009f6b8..04f4e4013d9 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -2,8 +2,10 @@ * 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 { BinaryLike, createHash } from "crypto"; import { StateAbbr } from "../../../../../utils/states"; import MockUser from "./mockUser.json"; +import { computeSha1First6, hashToEmailKeyMap } from "../../../utils/mockUtils"; export interface Broker { id: number; @@ -26,21 +28,53 @@ export interface Broker { updated_at: string; } -const DEFAULT_NUMBER_BREACHES = 10; +export interface BrokerOptionals { + id?: number; + profile_id?: number; + scan_id?: number; + status?: string; + first_name?: string; + middle_name?: string | null; + last_name?: string; + age?: number | null; + addresses?: object[]; + phones?: string[]; + emails?: string[]; + relatives?: string[]; + link?: string; + data_broker?: string; + data_broker_id?: number; + optout_attempts?: number; + created_at?: string; + updated_at?: string; +} + +export interface BrokerMap { + [key: string]: BrokerOptionals[]; +} + const MAGIC_NUM_1 = 24623; const MAGIC_NUM_2 = 2161; -const MAGIC_NUM_3 = 1013; +export const profileIdLeftBound = 2 ** 28; +export const profileIdRightBound = 2 ** 32 - 1; + +function hasher(plaintext: number | string) { + if (typeof plaintext === "number") plaintext = String(plaintext); + const rawHash = createHash("sha1").update(plaintext as BinaryLike); + const last4BytesHex = rawHash.digest("hex").slice(-7); + return parseInt(last4BytesHex, 16); +} export function MOCK_ONEREP_SCAN_ID(profileId: number) { - return (profileId * MAGIC_NUM_1) % MAGIC_NUM_2; + return hasher(profileId); } export function MOCK_ONEREP_DATABROKER_ID_START(profileId: number) { - return MockUser.MAGIC_NUM_0 * MOCK_ONEREP_SCAN_ID(profileId); + return hasher(profileId * MAGIC_NUM_1); } export function MOCK_ONEREP_ID_START(profileId: number) { - return MOCK_ONEREP_DATABROKER_ID_START(profileId) % MAGIC_NUM_3; + return hasher(profileId * MAGIC_NUM_2); } export function MOCK_ONEREP_TIME() { @@ -99,7 +133,7 @@ export function MOCK_ONEREP_OBJECT_META(page: number | string = 1) { export function MOCK_ONEREP_OBJECT_LINKS( profileId: number | string, - page: number | string = 100, + page: number | string = 1, perPage: number | string = 100, ) { if (typeof profileId === "string") profileId = parseInt(profileId); @@ -118,69 +152,47 @@ export function MOCK_ONEREP_BROKERS( profileId: number, page: string, perPage: string, - numberOfBrokers: number = DEFAULT_NUMBER_BREACHES, + email: string, ) { - const mockResponseData = MockUser.BROKERS_LIST; - //TODO-mock: change the scan_id creation (make higher) const scanId = MOCK_ONEREP_SCAN_ID(profileId); - const mockMeta = MOCK_ONEREP_OBJECT_META(page); const mockLinks = MOCK_ONEREP_OBJECT_LINKS(profileId, page, perPage); - //TODO-mock: based on email, select data response - //TODO-mock: change the mechanism (.valid) - - if (mockResponseData.valid) { - const response: { - data: Broker[]; - links: typeof mockLinks; - meta: typeof mockMeta; - } = { - data: [], - links: mockLinks, - meta: mockMeta, - }; - - if (mockResponseData.data.length > 0) { - response.data = mockResponseData.data.map((broker) => { - return { - ...(broker as Broker), - profile_id: profileId, - scan_id: scanId, - }; - }); - } - - return response; - } - const idStart = MOCK_ONEREP_ID_START(profileId); const idStartDataBroker = MOCK_ONEREP_DATABROKER_ID_START(profileId); + const emailHash = computeSha1First6(email); + const brokersListMap = MockUser.BROKERS_LIST as BrokerMap; + const datasetKey = hashToEmailKeyMap[emailHash] || "default"; + const brokersList = brokersListMap[datasetKey]; + + const res = brokersList.map( + (elem: BrokerOptionals, index: number) => + ({ + id: idStart - index, + profile_id: profileId, + scan_id: scanId, + status: elem["status"] || "new", + first_name: elem["first_name"] || MOCK_ONEREP_FIRSTNAME(), + middle_name: elem["middle_name"] || null, + last_name: elem["last_name"] || MOCK_ONEREP_LASTNAME(), + age: elem["age"] || null, + addresses: elem["addresses"] || MOCK_ONEREP_ADDRESSES(), + phones: elem["phones"] || MOCK_ONEREP_PHONES(), + emails: elem["emails"] || MOCK_ONEREP_EMAILS(), + relatives: elem["relatives"] || MOCK_ONEREP_RELATIVES(), + link: + elem["link"] || `https://mockexample.com/link-to-databroker${index}`, + data_broker: elem["data_broker"] || `mockexample${index}.com`, + data_broker_id: idStartDataBroker - index, + optout_attempts: elem["optout_attempts"] || 0, + created_at: elem["created_at"] || MOCK_ONEREP_TIME(), + updated_at: elem["updated_at"] || MOCK_ONEREP_TIME(), + }) as Broker, + ); + const responseData = { - data: new Array(numberOfBrokers).fill(null).map( - (_, index) => - ({ - id: idStart - index, - profile_id: profileId, - scan_id: scanId, - status: "new", - first_name: MOCK_ONEREP_FIRSTNAME(), - middle_name: null, - last_name: MOCK_ONEREP_LASTNAME(), - age: null, - addresses: MOCK_ONEREP_ADDRESSES(), - phones: MOCK_ONEREP_PHONES(), - emails: MOCK_ONEREP_EMAILS(), - relatives: MOCK_ONEREP_RELATIVES(), - link: `https://mockexample.com/link-to-databroker${index}`, - data_broker: `mockexample${index}.com`, - data_broker_id: idStartDataBroker - index, - optout_attempts: 0, - created_at: MOCK_ONEREP_TIME(), - updated_at: MOCK_ONEREP_TIME(), - }) as Broker, - ), + data: res, links: mockLinks, meta: mockMeta, }; diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index ffe3e3325f9..7bf204ee782 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -8,16 +8,38 @@ "STATUS": "active", "ADDRESSES": [ { + "zip": "93386", "city": "Berkeley", - "state": "CA" + "state": "CA", + "street": "Von Meadows" } ], "PHONES": ["00000000"], "RELATIVES": ["Bob Doe"], "BROKERS_LIST": { - "data": [], - "links": {}, - "meta": {}, - "valid": false + "default": [{}, {}, {}, {}, {}], + "E2E_TEST_ACCOUNT_EMAIL": [ + { + "addresses": [ + { + "zip": 77777, + "city": "Hogwarts", + "state": "CA" + } + ], + "phones": ["00000000", "00000001"], + "link": "https://mockexample.com/link-to-databroker-e2e-test-0", + "data_broker": "mockexample-e2e-test-0.com" + }, + { + "relatives": ["Jon Jones"], + "link": "https://mockexample.com/link-to-databroker-e2e-test-1", + "data_broker": "mockexample-e2e-test-1.com" + } + ], + "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES": [] + }, + "SCANS_LIST": { + "default": {} } } diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index 22bf1e39add..ce654955f41 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -3,7 +3,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextRequest, NextResponse } from "next/server"; -import { MOCK_ONEREP_SCAN_ID, MOCK_ONEREP_TIME } from "../../../config/config"; +import { + MOCK_ONEREP_OBJECT_LINKS, + MOCK_ONEREP_OBJECT_META, + MOCK_ONEREP_SCAN_ID, + MOCK_ONEREP_TIME, +} from "../../../config/config"; import { errorIfProduction } from "../../../../../utils/errorThrower"; export function POST( @@ -48,6 +53,8 @@ export function GET( } const scandId = MOCK_ONEREP_SCAN_ID(profileId); + const links = MOCK_ONEREP_OBJECT_LINKS(profileId); + const meta = MOCK_ONEREP_OBJECT_META(profileId); const responseData = { data: [ @@ -61,21 +68,8 @@ export function GET( url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scandId}`, }, ], - links: { - first: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans?page=1`, - last: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans?page=1`, - prev: null, - next: null, - }, - meta: { - current_page: 1, - from: 1, - last_page: 1, - path: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans`, - per_page: 20, - to: 1, - total: 1, - }, + links: links, + meta: meta, }; return NextResponse.json(responseData); } diff --git a/src/app/api/mock/onerep/profiles/route.ts b/src/app/api/mock/onerep/profiles/route.ts index 3bf53161625..5ac3b619c9e 100644 --- a/src/app/api/mock/onerep/profiles/route.ts +++ b/src/app/api/mock/onerep/profiles/route.ts @@ -2,7 +2,12 @@ * 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 { MOCK_ONEREP_STATUS, MOCK_ONEREP_TIME } from "../config/config.ts"; +import { + MOCK_ONEREP_STATUS, + MOCK_ONEREP_TIME, + profileIdLeftBound, + profileIdRightBound, +} from "../config/config.ts"; import { NextRequest, NextResponse } from "next/server"; import { randomInt } from "crypto"; import { errorIfProduction } from "../../../utils/errorThrower.ts"; @@ -48,7 +53,7 @@ export async function POST(req: NextRequest) { if (prodError) return prodError; //TODO-mock: makes this considerably higher - const profileId = randomInt(1000, 10000); + const profileId = randomInt(profileIdLeftBound, profileIdRightBound); try { if (req.body === null) { return NextResponse.json({ error: "Invalid request - without body" }); diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts index 099c217c36d..93a38373199 100644 --- a/src/app/api/mock/onerep/scan-results/route.ts +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -12,8 +12,8 @@ export function GET(req: NextRequest) { const page = req.nextUrl.searchParams.get("page") || "1"; const perPage = req.nextUrl.searchParams.get("per_page") || "100"; - const profileId = req.nextUrl.searchParams.get("profile_id[]"); + const email = req.nextUrl.searchParams.get("email") || ""; if (profileId === null) { return NextResponse.json( @@ -23,6 +23,6 @@ export function GET(req: NextRequest) { } return NextResponse.json( - MOCK_ONEREP_BROKERS(Number(profileId), page, perPage, 10), + MOCK_ONEREP_BROKERS(Number(profileId), page, perPage, email), ); } diff --git a/src/app/api/utils/mockUtils.ts b/src/app/api/utils/mockUtils.ts new file mode 100644 index 00000000000..f042ee45d36 --- /dev/null +++ b/src/app/api/utils/mockUtils.ts @@ -0,0 +1,31 @@ +/* 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 { BinaryLike } from "crypto"; +import { getSha1 } from "../../../utils/fxa"; +import { logger } from "../../functions/server/logging"; + +export function computeSha1First6(email: string) { + return getSha1(email as BinaryLike) + .slice(0, 6) + .toUpperCase(); +} + +export const allE2ETestEmailKeys = (() => { + return Object.keys(process.env).filter( + (key) => + key.startsWith("E2E_TEST_ACCOUNT_EMAIL") && + (process.env[key] !== undefined || + (false && + logger.info(`Warning: ${key} is not set up! Ignoring it...`))), + ); +})(); + +export const hashToEmailKeyMap = (() => { + const mapping: { [key: string]: string } = {}; + allE2ETestEmailKeys.forEach((key) => { + mapping[computeSha1First6(process.env[key] as string)] = key; + }); + return mapping; +})(); diff --git a/src/app/functions/server/onerep.ts b/src/app/functions/server/onerep.ts index 491ad6e3ba7..ddb8eb9edaf 100644 --- a/src/app/functions/server/onerep.ts +++ b/src/app/functions/server/onerep.ts @@ -10,11 +10,13 @@ import { } from "../../../utils/parse.js"; import { StateAbbr } from "../../../utils/states.js"; import { + getEmailForProfile, getLatestOnerepScanResults, getLatestScanForProfileByReason, } from "../../../db/tables/onerep_scans"; import { RemovalStatus } from "../universal/scanResult.js"; import { logger } from "./logging"; +import { isUsingMockONEREPEndpoint } from "../universal/mock.ts"; export const monthlyScansQuota = parseInt( (process.env.MONTHLY_SCANS_QUOTA as string) ?? "0", @@ -331,8 +333,13 @@ export async function listScanResults( status: RemovalStatus; }> = {}, ): Promise { + let mockEmail = ""; + if (isUsingMockONEREPEndpoint()) { + mockEmail = (await getEmailForProfile(profileId)) || mockEmail; + } const queryParams = new URLSearchParams({ "profile_id[]": profileId.toString(), + email: mockEmail, }); if (options.page) { queryParams.set("page", options.page.toString()); diff --git a/src/app/functions/universal/mock.ts b/src/app/functions/universal/mock.ts index aca3b3ffb6c..9b610658505 100644 --- a/src/app/functions/universal/mock.ts +++ b/src/app/functions/universal/mock.ts @@ -7,7 +7,7 @@ export function isUsingMockHIBPEndpoint() { } export function isUsingMockONEREPEndpoint() { - return process.env.ONEREP?.includes("api/mock") as boolean; + return process.env.ONEREP_API_BASE?.includes("api/mock") as boolean; } export const ONEREP_API_BASE = process.env.ONEREP_API_BASE; diff --git a/src/db/tables/onerep_scans.ts b/src/db/tables/onerep_scans.ts index 7b809860853..74480c41c8f 100644 --- a/src/db/tables/onerep_scans.ts +++ b/src/db/tables/onerep_scans.ts @@ -363,6 +363,19 @@ async function deleteSomeScansForProfile( } } +async function getEmailForProfile(onerepProfileId: number) { + const result = await knex("subscribers") + .select("primary_email") + .where("onerep_profile_id", onerepProfileId) + .first(); + + if (result) { + return result.primary_email; + } else { + return undefined; + } +} + export { getAllScansForProfile, getLatestScanForProfileByReason, @@ -380,4 +393,5 @@ export { deleteScansForProfile, deleteScanResultsForProfile, deleteSomeScansForProfile, + getEmailForProfile, }; diff --git a/src/utils/hibp.js b/src/utils/hibp.js index 7e22061a7d9..c018cf6fe8e 100644 --- a/src/utils/hibp.js +++ b/src/utils/hibp.js @@ -2,11 +2,11 @@ * 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 { boolean } from 'fast-check' import AppConstants from '../appConstants.js' import { getAllBreaches, upsertBreaches, knex } from '../db/tables/breaches.js' import { InternalServerError } from '../utils/error.js' import { getMessage } from '../utils/fluent.js' +import { isUsingMockHIBPEndpoint } from '../app/functions/universal/mock.ts' const { HIBP_THROTTLE_MAX_TRIES, HIBP_THROTTLE_DELAY, HIBP_API_ROOT, HIBP_KANON_API_ROOT, HIBP_KANON_API_TOKEN } = AppConstants @@ -73,9 +73,6 @@ async function _throttledFetch(url, reqOptions, tryCount = 1) { } /* c8 ignore stop */ - -const isUsingMockEndpoint = String(process.env.HIBP_KANON_API_ROOT).includes("api/mock/"); - /** * @param {string} path * @param options @@ -110,6 +107,7 @@ async function kAnonReq(path, options = {}) { }, ...options } + const reqOptions = _addStandardOptions(options) try { return await _throttledFetch(url, reqOptions) @@ -301,12 +299,13 @@ async function getBreachesForEmail(sha1, allBreaches, includeSensitive = false, console.error("failed_kAnonReq_call: no response or empty response") return [] } - if (isUsingMockEndpoint) { + if (isUsingMockHIBPEndpoint()) { let mockDataBreaches = response[0]; - return allBreaches.filter(breach => mockDataBreaches.websites.includes(breach.Name)).sort((a, b) => { + const res = allBreaches.filter(breach => mockDataBreaches.websites.includes(breach.Name)).sort((a, b) => { // @ts-ignore TODO: Turn dates into a number return new Date(b.AddedDate) - new Date(a.AddedDate) }) + return res; } // Parse response body, format: // [ From a59c011d1845e9fc2d589cd20d72c513319c5197 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Sun, 7 Jul 2024 21:22:52 -0700 Subject: [PATCH 042/137] Changed the clear endpoint, provided better defaults --- .../mock/{onerep => }/clearTestData/route.ts | 18 +- .../api/mock/hibp/data/mockAllBreaches.json | 534 ++++++++++++++ src/app/api/mock/hibp/data/mockBreaches.json | 53 +- src/app/api/mock/onerep/config/mockUser.json | 694 +++++++++++++++++- src/app/api/mock/onerep/config/test.json | 24 - src/app/api/utils/mockUtils.ts | 6 +- src/db/tables/subscribers.js | 9 + src/e2e/specs/dashboard.spec.ts | 4 - src/e2e/utils/helpers.ts | 7 + 9 files changed, 1306 insertions(+), 43 deletions(-) rename src/app/api/mock/{onerep => }/clearTestData/route.ts (74%) delete mode 100644 src/app/api/mock/onerep/config/test.json diff --git a/src/app/api/mock/onerep/clearTestData/route.ts b/src/app/api/mock/clearTestData/route.ts similarity index 74% rename from src/app/api/mock/onerep/clearTestData/route.ts rename to src/app/api/mock/clearTestData/route.ts index 4ce7fea5e3e..3d6b5add05d 100644 --- a/src/app/api/mock/onerep/clearTestData/route.ts +++ b/src/app/api/mock/clearTestData/route.ts @@ -3,18 +3,21 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { NextResponse } from "next/server"; -import { getServerSession } from "../../../../functions/server/getServerSession"; +import { getServerSession } from "../../../functions/server/getServerSession"; import { errorIfProduction, internalServerError, unauthError, -} from "../../../utils/errorThrower"; -import { getOnerepProfileId } from "../../../../../db/tables/subscribers"; +} from "../../utils/errorThrower"; +import { + getOnerepProfileId, + unresolveAllBreaches, +} from "../../../../db/tables/subscribers"; import { deleteScanResultsForProfile, deleteSomeScansForProfile, -} from "../../../../../db/tables/onerep_scans"; -import { logger } from "../../../../functions/server/logging"; +} from "../../../../db/tables/onerep_scans"; +import { logger } from "../../../functions/server/logging"; function isTestEmail(email: string) { if (!email) return false; @@ -40,7 +43,10 @@ export async function GET() { return internalServerError("Unable to fetch OneRep profile ID"); await deleteScanResultsForProfile(onerepProfileId); await deleteSomeScansForProfile(onerepProfileId, 1); - logger.info("Mock OneRep endpoint: attempted to delete all but 1 scans"); + await unresolveAllBreaches(onerepProfileId); + logger.info( + "Mock OneRep endpoint: attempted to delete all but 1 scans, attempted to unresolve all breaches", + ); return NextResponse.json( { message: "Requested reached successfully" }, diff --git a/src/app/api/mock/hibp/data/mockAllBreaches.json b/src/app/api/mock/hibp/data/mockAllBreaches.json index e7bd9c32d4d..13bbc4ab361 100644 --- a/src/app/api/mock/hibp/data/mockAllBreaches.json +++ b/src/app/api/mock/hibp/data/mockAllBreaches.json @@ -103,6 +103,540 @@ "IsSpamList": false, "IsMalware": false, "FaviconUrl": null + }, + { + "Id": 294, + "Name": "GSMHosting", + "Title": "GSM Hosting", + "Domain": "gsmhosting.com", + "BreachDate": "2016-08-01T07:00:00.000Z", + "AddedDate": "2024-03-27T06:23:58.000Z", + "ModifiedDate": "2024-03-27T06:29:15.000Z", + "PwnCount": 2607440, + "Description": "In August 2016, breached data from the vBulletin forum for GSM-Hosting appeared for sale alongside dozens of other hacked services. The breach impacted 2.6M users of the service and included email and IP addresses, usernames and salted MD5 password hashes.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/GSMHosting.png", + "DataClasses": [ + "email-addresses", + "ip-addresses", + "passwords", + "usernames" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 22, + "Name": "Adobe", + "Title": "Adobe", + "Domain": "adobe.com", + "BreachDate": "2013-10-04T07:00:00.000Z", + "AddedDate": "2013-12-04T00:00:00.000Z", + "ModifiedDate": "2022-05-15T23:52:49.000Z", + "PwnCount": 152445165, + "Description": "In October 2013, 153 million Adobe accounts were breached with each containing an internal ID, username, email, encrypted password and a password hint in plain text. The password cryptography was poorly done and many were quickly resolved back to plain text. The unencrypted hints also disclosed much about the passwords adding further to the risk that hundreds of millions of Adobe customers already faced.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/Adobe.png", + "DataClasses": [ + "email-addresses", + "password-hints", + "passwords", + "usernames" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 25, + "Name": "AdultFriendFinder2016", + "Title": "Adult FriendFinder (2016)", + "Domain": "adultfriendfinder.com", + "BreachDate": "2016-10-16T07:00:00.000Z", + "AddedDate": "2020-02-06T23:53:53.000Z", + "ModifiedDate": "2020-02-07T01:11:28.000Z", + "PwnCount": 169746810, + "Description": "In October 2016, the adult entertainment company Friend Finder Networks suffered a massive data breach. The incident impacted multiple separate online assets owned by the company, the largest of which was the Adult FriendFinder website alleged to be \"the world's largest sex & swinger community\". Exposed data included usernames, passwords stored as SHA-1 hashes and 170 million unique email addresses. This incident is separate to the 2015 data breach Adult FriendFinder also suffered. The data was provided to HIBP by dehashed.com.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/AdultFriendFinder.png", + "DataClasses": [ + "email-addresses", + "passwords", + "spoken-languages", + "usernames" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": true, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 37, + "Name": "AndroidForums", + "Title": "Android Forums", + "Domain": "androidforums.com", + "BreachDate": "2011-10-30T07:00:00.000Z", + "AddedDate": "2015-12-20T06:47:19.000Z", + "ModifiedDate": "2020-08-07T23:29:01.000Z", + "PwnCount": 745355, + "Description": "In October 2011, the Android Forums website was hacked and 745k user accounts were subsequently leaked publicly. The compromised data included email addresses, user birth dates and passwords stored as a salted MD5 hash.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/AndroidForums.png", + "DataClasses": [ + "dates-of-birth", + "email-addresses", + "homepage-urls", + "instant-messenger-identities", + "ip-addresses", + "passwords" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 50, + "Name": "ArmorGames", + "Title": "Armor Games", + "Domain": "armorgames.com", + "BreachDate": "2019-01-01T08:00:00.000Z", + "AddedDate": "2019-07-20T06:03:31.000Z", + "ModifiedDate": "2021-04-07T06:28:50.000Z", + "PwnCount": 10604307, + "Description": "In January 2019, the game portal website Armor Games suffered a data breach. A total of 10.6 million email addresses were impacted by the breach which also exposed usernames, IP addresses, birthdays of administrator accounts and passwords stored as salted SHA-1 hashes. The data was provided to HIBP by a source who requested it be attributed to \"JimScott.Sec@protonmail.com\".", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/ArmorGames.png", + "DataClasses": [ + "bios", + "dates-of-birth", + "email-addresses", + "genders", + "geographic-locations", + "ip-addresses", + "passwords", + "usernames" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 54, + "Name": "AshleyMadison", + "Title": "Ashley Madison", + "Domain": "ashleymadison.com", + "BreachDate": "2015-07-19T07:00:00.000Z", + "AddedDate": "2015-08-18T20:55:05.000Z", + "ModifiedDate": "2015-08-18T20:55:05.000Z", + "PwnCount": 30811934, + "Description": "In July 2015, the infidelity website Ashley Madison suffered a serious data breach. The attackers threatened Ashley Madison with the full disclosure of the breach unless the service was shut down. One month later, the database was dumped including more than 30M unique email addresses. This breach has been classed as \"sensitive\" and is not publicly searchable, although individuals may discover if they've been impacted by registering for notifications. Read about this approach in detail.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/AshleyMadison.png", + "DataClasses": [ + "dates-of-birth", + "email-addresses", + "ethnicities", + "genders", + "names", + "passwords", + "payment-histories", + "phone-numbers", + "physical-addresses", + "security-questions-and-answers", + "sexual-orientations", + "usernames", + "website-activity" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": true, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 119, + "Name": "CashCrate", + "Title": "CashCrate", + "Domain": "cashcrate.com", + "BreachDate": "2016-11-17T08:00:00.000Z", + "AddedDate": "2018-04-20T21:40:38.000Z", + "ModifiedDate": "2018-04-20T21:40:38.000Z", + "PwnCount": 6844490, + "Description": "In June 2017, news broke that CashCrate had suffered a data breach exposing 6.8 million records. The breach of the cash-for-surveys site dated back to November 2016 and exposed names, physical addresses, email addresses and passwords stored in plain text for older accounts along with weak MD5 hashes for newer ones.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/CashCrate.png", + "DataClasses": [ + "email-addresses", + "names", + "passwords", + "physical-addresses" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 125, + "Name": "Chegg", + "Title": "Chegg", + "Domain": "chegg.com", + "BreachDate": "2018-04-28T07:00:00.000Z", + "AddedDate": "2019-08-16T07:24:58.000Z", + "ModifiedDate": "2024-04-27T05:34:09.000Z", + "PwnCount": 39721127, + "Description": "In April 2018, the textbook rental service Chegg suffered a data breach that impacted 40 million subscribers. The exposed data included email addresses, usernames, names and passwords stored as unsalted MD5 hashes. A small number of records also contained physical address or phone number. The data was provided to HIBP by a source who requested it be attributed to \"JimScott.Sec@protonmail.com\".", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/Chegg.png", + "DataClasses": [ + "email-addresses", + "names", + "passwords", + "phone-numbers", + "physical-addresses", + "usernames" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 192, + "Name": "Dropbox", + "Title": "Dropbox", + "Domain": "dropbox.com", + "BreachDate": "2012-07-01T07:00:00.000Z", + "AddedDate": "2016-08-31T00:19:19.000Z", + "ModifiedDate": "2016-08-31T00:19:19.000Z", + "PwnCount": 68648009, + "Description": "In mid-2012, Dropbox suffered a data breach which exposed the stored credentials of tens of millions of their customers. In August 2016, they forced password resets for customers they believed may be at risk. A large volume of data totalling over 68 million records was subsequently traded online and included email addresses and salted hashes of passwords (half of them SHA1, half of them bcrypt).", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/Dropbox.png", + "DataClasses": ["email-addresses", "passwords"], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 205, + "Name": "Edmodo", + "Title": "Edmodo", + "Domain": "edmodo.com", + "BreachDate": "2017-05-11T07:00:00.000Z", + "AddedDate": "2017-06-01T05:59:24.000Z", + "ModifiedDate": "2017-06-01T05:59:24.000Z", + "PwnCount": 43423561, + "Description": "In May 2017, the education platform Edmodo was hacked resulting in the exposure of 77 million records comprised of over 43 million unique customer email addresses. The data was consequently published to a popular hacking forum and made freely available. The records in the breach included usernames, email addresses and bcrypt hashes of passwords.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/Edmodo.png", + "DataClasses": ["email-addresses", "passwords", "usernames"], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 227, + "Name": "Evony", + "Title": "Evony", + "Domain": "evony.com", + "BreachDate": "2016-06-01T07:00:00.000Z", + "AddedDate": "2017-03-25T23:43:45.000Z", + "ModifiedDate": "2017-03-25T23:43:45.000Z", + "PwnCount": 29396116, + "Description": "In June 2016, the online multiplayer game Evony was hacked and over 29 million unique accounts were exposed. The attack led to the exposure of usernames, email and IP addresses and MD5 hashes of passwords (without salt).", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/Evony.png", + "DataClasses": [ + "email-addresses", + "ip-addresses", + "passwords", + "usernames" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 294, + "Name": "GSMHosting", + "Title": "GSM Hosting", + "Domain": "gsmhosting.com", + "BreachDate": "2016-08-01T07:00:00.000Z", + "AddedDate": "2024-03-27T06:23:58.000Z", + "ModifiedDate": "2024-03-27T06:29:15.000Z", + "PwnCount": 2607440, + "Description": "In August 2016, breached data from the vBulletin forum for GSM-Hosting appeared for sale alongside dozens of other hacked services. The breach impacted 2.6M users of the service and included email and IP addresses, usernames and salted MD5 password hashes.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/GSMHosting.png", + "DataClasses": [ + "email-addresses", + "ip-addresses", + "passwords", + "usernames" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 311, + "Name": "HeroesOfNewerth", + "Title": "Heroes of Newerth", + "Domain": "heroesofnewerth.com", + "BreachDate": "2012-12-17T08:00:00.000Z", + "AddedDate": "2016-01-24T16:27:23.000Z", + "ModifiedDate": "2016-01-24T16:27:23.000Z", + "PwnCount": 8089103, + "Description": "In December 2012, the multiplayer online battle arena game known as Heroes of Newerth was hacked and over 8 million accounts extracted from the system. The compromised data included usernames, email addresses and passwords.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/HeroesOfNewerth.png", + "DataClasses": ["email-addresses", "passwords", "usernames"], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 304, + "Name": "HauteLook", + "Title": "HauteLook", + "Domain": "hautelook.com", + "BreachDate": "2018-08-07T07:00:00.000Z", + "AddedDate": "2019-03-21T21:57:32.000Z", + "ModifiedDate": "2019-03-21T21:57:32.000Z", + "PwnCount": 28510459, + "Description": "In mid-2018, the fashion shopping site HauteLook was among a raft of sites that were breached and their data then sold in early-2019. The data included over 28 million unique email addresses alongside names, genders, dates of birth and passwords stored as bcrypt hashes. The data was provided to HIBP by dehashed.com.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/HauteLook.png", + "DataClasses": [ + "dates-of-birth", + "email-addresses", + "genders", + "geographic-locations", + "names", + "passwords" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 332, + "Name": "iMesh", + "Title": "iMesh", + "Domain": "imesh.com", + "BreachDate": "2013-09-22T07:00:00.000Z", + "AddedDate": "2016-07-02T05:42:13.000Z", + "ModifiedDate": "2016-07-02T05:42:13.000Z", + "PwnCount": 49467477, + "Description": "In September 2013, the media and file sharing client known as iMesh was hacked and approximately 50M accounts were exposed. The data was later put up for sale on a dark market website in mid-2016 and included email and IP addresses, usernames and salted MD5 hashes.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/iMesh.png", + "DataClasses": [ + "email-addresses", + "ip-addresses", + "passwords", + "usernames" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 385, + "Name": "LinkedIn", + "Title": "LinkedIn", + "Domain": "linkedin.com", + "BreachDate": "2012-05-05T07:00:00.000Z", + "AddedDate": "2016-05-21T21:35:40.000Z", + "ModifiedDate": "2016-05-21T21:35:40.000Z", + "PwnCount": 164611595, + "Description": "In May 2016, LinkedIn had 164 million email addresses and passwords exposed. Originally hacked in 2012, the data remained out of sight until being offered for sale on a dark market site 4 years later. The passwords in the breach were stored as SHA1 hashes without salt, the vast majority of which were quickly cracked in the days following the release of the data.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/LinkedIn.png", + "DataClasses": ["email-addresses", "passwords"], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 460, + "Name": "MySpace", + "Title": "MySpace", + "Domain": "myspace.com", + "BreachDate": "2008-07-01T07:00:00.000Z", + "AddedDate": "2016-05-31T00:12:29.000Z", + "ModifiedDate": "2016-05-31T00:12:29.000Z", + "PwnCount": 359420698, + "Description": "In approximately 2008, MySpace suffered a data breach that exposed almost 360 million accounts. In May 2016 the data was offered up for sale on the \"Real Deal\" dark market website and included email addresses, usernames and SHA1 hashes of the first 10 characters of the password converted to lowercase and stored without a salt. The exact breach date is unknown, but analysis of the data suggests it was 8 years before being made public.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/MySpace.png", + "DataClasses": ["email-addresses", "passwords", "usernames"], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 464, + "Name": "NaughtyAmerica", + "Title": "Naughty America", + "Domain": "naughtyamerica.com", + "BreachDate": "2016-03-14T07:00:00.000Z", + "AddedDate": "2016-04-24T06:14:42.000Z", + "ModifiedDate": "2016-04-24T06:14:42.000Z", + "PwnCount": 1398630, + "Description": "In March 2016, the adult website Naughty America was hacked and the data consequently sold online. The breach included data from numerous systems with various personal identity attributes, the largest of which had passwords stored as easily crackable MD5 hashes. There were 1.4 million unique email addresses in the breach.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/NaughtyAmerica.png", + "DataClasses": [ + "dates-of-birth", + "email-addresses", + "ip-addresses", + "passwords", + "usernames", + "website-activity" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": true, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 706, + "Name": "VerificationsIO", + "Title": "Verifications.io", + "Domain": "verifications.io", + "BreachDate": "2019-02-25T08:00:00.000Z", + "AddedDate": "2019-03-09T19:29:54.000Z", + "ModifiedDate": "2019-03-09T20:49:51.000Z", + "PwnCount": 763117241, + "Description": "In February 2019, the email address validation service verifications.io suffered a data breach. Discovered by Bob Diachenko and Vinny Troia, the breach was due to the data being stored in a MongoDB instance left publicly facing without a password and resulted in 763 million unique email addresses being exposed. Many records within the data also included additional personal attributes such as names, phone numbers, IP addresses, dates of birth and genders. No passwords were included in the data. The Verifications.io website went offline during the disclosure process, although an archived copy remains viewable.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/VerificationsIO.png", + "DataClasses": [ + "dates-of-birth", + "email-addresses", + "employers", + "genders", + "geographic-locations", + "ip-addresses", + "job-titles", + "names", + "phone-numbers", + "physical-addresses" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 725, + "Name": "Wattpad", + "Title": "Wattpad", + "Domain": "wattpad.com", + "BreachDate": "2020-06-29T07:00:00.000Z", + "AddedDate": "2020-07-19T22:49:19.000Z", + "ModifiedDate": "2020-07-19T22:49:19.000Z", + "PwnCount": 268765495, + "Description": "In June 2020, the user-generated stories website Wattpad suffered a huge data breach that exposed almost 270 million records. The data was initially sold then published on a public hacking forum where it was broadly shared. The incident exposed extensive personal information including names and usernames, email and IP addresses, genders, birth dates and passwords stored as bcrypt hashes.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/Wattpad.png", + "DataClasses": [ + "bios", + "dates-of-birth", + "email-addresses", + "genders", + "geographic-locations", + "ip-addresses", + "names", + "passwords", + "social-media-profiles", + "user-website-urls", + "usernames" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null + }, + { + "Id": 771, + "Name": "Zynga", + "Title": "Zynga", + "Domain": "zynga.com", + "BreachDate": "2019-09-01T07:00:00.000Z", + "AddedDate": "2019-12-19T04:54:45.000Z", + "ModifiedDate": "2020-01-11T00:41:51.000Z", + "PwnCount": 172869660, + "Description": "In September 2019, game developer Zynga (the creator of Words with Friends) suffered a data breach. The incident exposed 173M unique email addresses alongside usernames and passwords stored as salted SHA-1 hashes. The data was provided to HIBP by dehashed.com.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/Zynga.png", + "DataClasses": [ + "email-addresses", + "passwords", + "phone-numbers", + "usernames" + ], + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsSpamList": false, + "IsMalware": false, + "FaviconUrl": null } ] } diff --git a/src/app/api/mock/hibp/data/mockBreaches.json b/src/app/api/mock/hibp/data/mockBreaches.json index ec107cd28ac..84eab05a99e 100644 --- a/src/app/api/mock/hibp/data/mockBreaches.json +++ b/src/app/api/mock/hibp/data/mockBreaches.json @@ -1,5 +1,52 @@ { - "default": ["000webhost", "123RF", "Bonobos"], - "E2E_TEST_ACCOUNT_EMAIL": ["000webhost", "123RF", "Bonobos", "Deezer"], - "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES": [] + "default": [ + "GSMHosting", + "Wattpad", + "AdultFriendFinder2016", + "Zynga", + "Chegg", + "ArmorGames", + "HauteLook", + "VerificationsIO", + "CashCrate", + "Edmodo", + "Evony", + "Dropbox", + "iMesh", + "MySpace", + "LinkedIn", + "NaughtyAmerica", + "HeroesOfNewerth", + "AndroidForums", + "AshleyMadison", + "Adobe" + ], + "E2E_TEST_ACCOUNT_EMAIL": [ + "GSMHosting", + "Wattpad", + "AdultFriendFinder2016", + "Zynga", + "Chegg", + "ArmorGames", + "HauteLook", + "VerificationsIO", + "CashCrate", + "Edmodo", + "Evony", + "Dropbox", + "iMesh", + "MySpace", + "LinkedIn", + "NaughtyAmerica", + "HeroesOfNewerth", + "AndroidForums", + "AshleyMadison", + "Adobe" + ], + "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES": [], + "E2E_TEST_ACCOUNT_EMAIL_UNUSED": [ + "000webhost", + "AndroidForums", + "AshleyMadison" + ] } diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index 7bf204ee782..424efe94ec7 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -17,8 +17,354 @@ "PHONES": ["00000000"], "RELATIVES": ["Bob Doe"], "BROKERS_LIST": { - "default": [{}, {}, {}, {}, {}], - "E2E_TEST_ACCOUNT_EMAIL": [ + "default": [ + { + "age": 28, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://arivify.com", + "data_broker": "arivify.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 40, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://beenverified.com", + "data_broker": "beenverified.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 50, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://instantcheckmate.com", + "data_broker": "instantcheckmate.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 30, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://open-public-records.com", + "data_broker": "open-public-records.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 45, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://peoplefinders.com", + "data_broker": "peoplefinders.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 55, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://peoplesmart.com", + "data_broker": "peoplesmart.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 60, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://privateeye.com", + "data_broker": "privateeye.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 65, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://smartbackgroundchecks.com", + "data_broker": "smartbackgroundchecks.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 70, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://truthfinder.com", + "data_broker": "truthfinder.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 75, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://ussearch.com", + "data_broker": "ussearch.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ], + "E2E_TEST_ACCOUNT_EMAIL_UNUSED": [ + {}, + {}, + {}, + {}, + {}, { "addresses": [ { @@ -37,7 +383,349 @@ "data_broker": "mockexample-e2e-test-1.com" } ], - "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES": [] + "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES": [], + "E2E_TEST_ACCOUNT_EMAIL": [ + { + "age": 28, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://arivify.com", + "data_broker": "arivify.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 40, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://beenverified.com", + "data_broker": "beenverified.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 50, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://instantcheckmate.com", + "data_broker": "instantcheckmate.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 30, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://open-public-records.com", + "data_broker": "open-public-records.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 45, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://peoplefinders.com", + "data_broker": "peoplefinders.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 55, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://peoplesmart.com", + "data_broker": "peoplesmart.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 60, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://privateeye.com", + "data_broker": "privateeye.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 65, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://smartbackgroundchecks.com", + "data_broker": "smartbackgroundchecks.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 70, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://truthfinder.com", + "data_broker": "truthfinder.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + }, + { + "age": 75, + "addresses": [ + { + "street": "456 Oak St", + "city": "Othertown", + "state": "TX", + "zip": "67890" + }, + { + "street": "1 World Way", + "city": "Los Angeles", + "state": "CA", + "zip": "90045" + }, + { + "street": "University Avenue and, Oxford St", + "city": "Berkeley", + "state": "CA", + "zip": "94720" + } + ], + "phones": ["555-5678", "69-420", "314159"], + "emails": [ + "islam.makhachev@example.com", + "Jon.Jones@example.com", + "khabib.nurmagomedov@dag.com" + ], + "relatives": ["John Smith", "Bob Smith", "Amy Smith"], + "link": "https://ussearch.com", + "data_broker": "ussearch.com", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ] }, "SCANS_LIST": { "default": {} diff --git a/src/app/api/mock/onerep/config/test.json b/src/app/api/mock/onerep/config/test.json deleted file mode 100644 index 31d485d35c4..00000000000 --- a/src/app/api/mock/onerep/config/test.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "brokers": [ - { - "id": 45000, - "profile_id": -1, - "scan_id": -1, - "status": "new", - "first_name": "John", - "middle_name": null, - "last_name": "Doe", - "age": null, - "addresses": [], - "phones": [], - "emails": [], - "relatives": [], - "link": "https://test.com/link-to-databroker0", - "data_broker": "test0.com", - "data_broker_id": 23, - "optout_attempts": 0, - "created_at": "2024-06-19T01:37:02+0000", - "updated_at": "2024-06-19T01:37:02+0000" - } - ] -} diff --git a/src/app/api/utils/mockUtils.ts b/src/app/api/utils/mockUtils.ts index f042ee45d36..341e837e001 100644 --- a/src/app/api/utils/mockUtils.ts +++ b/src/app/api/utils/mockUtils.ts @@ -16,9 +16,9 @@ export const allE2ETestEmailKeys = (() => { return Object.keys(process.env).filter( (key) => key.startsWith("E2E_TEST_ACCOUNT_EMAIL") && - (process.env[key] !== undefined || - (false && - logger.info(`Warning: ${key} is not set up! Ignoring it...`))), + (process.env[key] || + (logger.info(`Warning: ${key} is not set up! Ignoring it...`) && + false)), ); })(); diff --git a/src/db/tables/subscribers.js b/src/db/tables/subscribers.js index 30c081d119c..1b60303b53a 100644 --- a/src/db/tables/subscribers.js +++ b/src/db/tables/subscribers.js @@ -615,6 +615,14 @@ async function getSignInCount (subscriberId) { } /* c8 ignore stop */ +/** + * @param {number} oneRepProfileId + */ +async function unresolveAllBreaches(oneRepProfileId) { + const currentDate = new Date(); + await knex('subscribers').where('onerep_profile_id', oneRepProfileId).update({'breach_resolution': null, 'updated_at': currentDate}); +} + export { getOnerepProfileId, getSubscribersByHashes, @@ -642,5 +650,6 @@ export { deleteOnerepProfileId, incrementSignInCountForEligibleFreeUser, getSignInCount, + unresolveAllBreaches, knex as knexSubscribers } diff --git a/src/e2e/specs/dashboard.spec.ts b/src/e2e/specs/dashboard.spec.ts index c3012d07c03..a72dba31d56 100644 --- a/src/e2e/specs/dashboard.spec.ts +++ b/src/e2e/specs/dashboard.spec.ts @@ -334,10 +334,6 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Content`, () = } } }); - - // test("Verify that the header looks correct for zero breaches and zero exposures", async ({ - // // - // })) }); test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Payment`, () => { diff --git a/src/e2e/utils/helpers.ts b/src/e2e/utils/helpers.ts index 0d398436dd0..37fd10681ec 100644 --- a/src/e2e/utils/helpers.ts +++ b/src/e2e/utils/helpers.ts @@ -222,3 +222,10 @@ export const forceLoginAs = async ( await page.waitForURL("**/user/dashboard"); await expect(page).toHaveURL(/.*\/user\/dashboard.*/); }; + +export const clearTestData = async (page: Page) => { + const url = new URL(page.url()); + await page.goto(url.host + "/api/mock/clearTestData"); + await page.waitForURL(/.*\/api\/mock\/clearTestData.*/); + await page.goBack(); +}; From 1cf540d4979e93322d516d67542ae57059a675c6 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Sun, 7 Jul 2024 22:12:51 -0700 Subject: [PATCH 043/137] Allowed customizable mock scan result --- src/app/api/mock/onerep/config/config.ts | 4 +- src/app/api/mock/onerep/config/mockUser.json | 53 ++++++++-------- .../mock/onerep/profiles/[profileId]/route.ts | 4 +- .../profiles/[profileId]/scans/route.ts | 61 +++++++++++++++---- src/app/api/mock/onerep/profiles/route.ts | 4 +- src/db/tables/subscribers.js | 2 + 6 files changed, 85 insertions(+), 43 deletions(-) diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index 04f4e4013d9..1e14576c8ae 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -56,7 +56,7 @@ export interface BrokerMap { const MAGIC_NUM_1 = 24623; const MAGIC_NUM_2 = 2161; export const profileIdLeftBound = 2 ** 28; -export const profileIdRightBound = 2 ** 32 - 1; +export const profileIdRightBound = 2 ** 31 - 1; function hasher(plaintext: number | string) { if (typeof plaintext === "number") plaintext = String(plaintext); @@ -105,7 +105,7 @@ export function MOCK_ONEREP_RELATIVES() { return MockUser.RELATIVES; } -export function MOCK_ONEREP_STATUS() { +export function MOCK_ONEREP_PROFILE_STATUS() { return MockUser.STATUS as "active" | "inactive"; } diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/config/mockUser.json index 424efe94ec7..07e2a061a6b 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/config/mockUser.json @@ -359,30 +359,6 @@ "updated_at": "2024-01-01T00:00:00Z" } ], - "E2E_TEST_ACCOUNT_EMAIL_UNUSED": [ - {}, - {}, - {}, - {}, - {}, - { - "addresses": [ - { - "zip": 77777, - "city": "Hogwarts", - "state": "CA" - } - ], - "phones": ["00000000", "00000001"], - "link": "https://mockexample.com/link-to-databroker-e2e-test-0", - "data_broker": "mockexample-e2e-test-0.com" - }, - { - "relatives": ["Jon Jones"], - "link": "https://mockexample.com/link-to-databroker-e2e-test-1", - "data_broker": "mockexample-e2e-test-1.com" - } - ], "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES": [], "E2E_TEST_ACCOUNT_EMAIL": [ { @@ -725,9 +701,36 @@ "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } + ], + "E2E_TEST_ACCOUNT_EMAIL_UNUSED": [ + {}, + {}, + {}, + {}, + {}, + { + "addresses": [ + { + "zip": 77777, + "city": "Hogwarts", + "state": "CA" + } + ], + "phones": ["00000000", "00000001"], + "link": "https://mockexample.com/link-to-databroker-e2e-test-0", + "data_broker": "mockexample-e2e-test-0.com" + }, + { + "relatives": ["Jon Jones"], + "link": "https://mockexample.com/link-to-databroker-e2e-test-1", + "data_broker": "mockexample-e2e-test-1.com" + } ] }, "SCANS_LIST": { - "default": {} + "default": [{}], + "E2E_TEST_ACCOUNT_EMAIL": [{}], + "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES": [{}], + "E2E_TEST_ACCOUNT_EMAIL_UNUSED": [{}] } } diff --git a/src/app/api/mock/onerep/profiles/[profileId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/route.ts index 084bd8d7ea3..464361a26f0 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/route.ts @@ -8,7 +8,7 @@ import { MOCK_ONEREP_LASTNAME, MOCK_ONEREP_BIRTHDATE, MOCK_ONEREP_ADDRESSES, - MOCK_ONEREP_STATUS, + MOCK_ONEREP_PROFILE_STATUS, } from "../../config/config.ts"; import { ShowProfileResponse } from "../../../../../functions/server/onerep.ts"; import { NextRequest, NextResponse } from "next/server"; @@ -34,7 +34,7 @@ export function GET( last_name: MOCK_ONEREP_LASTNAME(), birth_date: MOCK_ONEREP_BIRTHDATE(), addresses: MOCK_ONEREP_ADDRESSES(), - status: MOCK_ONEREP_STATUS(), + status: MOCK_ONEREP_PROFILE_STATUS(), created_at: MOCK_ONEREP_TIME(), updated_at: MOCK_ONEREP_TIME(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}`, diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index ce654955f41..55ac7634100 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -10,6 +10,35 @@ import { MOCK_ONEREP_TIME, } from "../../../config/config"; import { errorIfProduction } from "../../../../../utils/errorThrower"; +import { getEmailForProfile } from "../../../../../../../db/tables/onerep_scans"; +import { + computeSha1First6, + hashToEmailKeyMap, +} from "../../../../../utils/mockUtils"; +import mockUser from "../../../config/mockUser.json"; + +interface ScansMap { + [key: string]: MockScanOptionals[]; +} + +interface MockScan { + id: number; + profile_id: number; + status: string; + reason: string; + created_at: string; + updated_at: string; + url: string; +} + +interface MockScanOptionals { + status?: string; + reason?: string; + created_at?: string; + updated_at?: string; +} + +const mockScans = mockUser.SCANS_LIST as ScansMap; export function POST( _: NextRequest, @@ -39,7 +68,7 @@ export function POST( return NextResponse.json(mockResponse); } -export function GET( +export async function GET( _: NextRequest, { params }: { params: { profileId: number } }, ) { @@ -56,18 +85,26 @@ export function GET( const links = MOCK_ONEREP_OBJECT_LINKS(profileId); const meta = MOCK_ONEREP_OBJECT_META(profileId); + const email = await getEmailForProfile(profileId); + if (!email) + return NextResponse.json({ error: "No email for this ID is found" }); + const emailHash = computeSha1First6(email); + const dataKey = hashToEmailKeyMap[emailHash] || "default"; + const data = mockScans[dataKey]; + const responseData = { - data: [ - { - id: scandId, - profile_id: profileId, - status: "finished", - reason: "manual", - created_at: MOCK_ONEREP_TIME(), - updated_at: MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scandId}`, - }, - ], + data: data.map( + (scan) => + ({ + id: scandId, + profile_id: profileId, + status: scan.status || "finished", + reason: scan.reason || "manual", + created_at: scan.created_at || MOCK_ONEREP_TIME(), + updated_at: scan.updated_at || MOCK_ONEREP_TIME(), + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scandId}`, + }) as MockScan, + ), links: links, meta: meta, }; diff --git a/src/app/api/mock/onerep/profiles/route.ts b/src/app/api/mock/onerep/profiles/route.ts index 5ac3b619c9e..8a4f0951492 100644 --- a/src/app/api/mock/onerep/profiles/route.ts +++ b/src/app/api/mock/onerep/profiles/route.ts @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { - MOCK_ONEREP_STATUS, + MOCK_ONEREP_PROFILE_STATUS, MOCK_ONEREP_TIME, profileIdLeftBound, profileIdRightBound, @@ -82,7 +82,7 @@ export async function POST(req: NextRequest) { updated_at: MOCK_ONEREP_TIME(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/addresses/${profileId + index}`, })), - status: MOCK_ONEREP_STATUS(), + status: MOCK_ONEREP_PROFILE_STATUS(), created_at: MOCK_ONEREP_TIME(), updated_at: MOCK_ONEREP_TIME(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}`, diff --git a/src/db/tables/subscribers.js b/src/db/tables/subscribers.js index 1b60303b53a..de6fe91bac3 100644 --- a/src/db/tables/subscribers.js +++ b/src/db/tables/subscribers.js @@ -615,6 +615,7 @@ async function getSignInCount (subscriberId) { } /* c8 ignore stop */ +/* c8 ignore start */ /** * @param {number} oneRepProfileId */ @@ -622,6 +623,7 @@ async function unresolveAllBreaches(oneRepProfileId) { const currentDate = new Date(); await knex('subscribers').where('onerep_profile_id', oneRepProfileId).update({'breach_resolution': null, 'updated_at': currentDate}); } +/* c8 ignore stop */ export { getOnerepProfileId, From 4f1c830d40e52d5f4b034223589a5b68a914365d Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Mon, 8 Jul 2024 03:24:20 -0700 Subject: [PATCH 044/137] added support for 'exposures_started' e2e account, chnaged a locator --- src/app/api/mock/hibp/breaches/route.ts | 2 +- src/app/api/mock/hibp/config/defaults.ts | 2 +- src/app/api/mock/hibp/config/route.ts | 2 +- .../{data => mockData}/mockAllBreaches.json | 19 +++++++++++++++ .../hibp/{data => mockData}/mockBreaches.json | 24 ++----------------- src/app/api/mock/onerep/config/config.ts | 2 +- .../onerep/{config => mockData}/mockUser.json | 4 +++- .../profiles/[profileId]/scans/route.ts | 2 +- src/e2e/pages/authPage.ts | 4 +++- 9 files changed, 32 insertions(+), 29 deletions(-) rename src/app/api/mock/hibp/{data => mockData}/mockAllBreaches.json (95%) rename src/app/api/mock/hibp/{data => mockData}/mockBreaches.json (59%) rename src/app/api/mock/onerep/{config => mockData}/mockUser.json (99%) diff --git a/src/app/api/mock/hibp/breaches/route.ts b/src/app/api/mock/hibp/breaches/route.ts index 4c1a33d4e83..b48830276ea 100644 --- a/src/app/api/mock/hibp/breaches/route.ts +++ b/src/app/api/mock/hibp/breaches/route.ts @@ -4,7 +4,7 @@ import { NextResponse } from "next/server"; import { logger } from "../../../../functions/server/logging"; -import mockAllBreaches from "../data/mockAllBreaches.json"; +import mockAllBreaches from "../mockData/mockAllBreaches.json"; import { errorIfProduction } from "../../../utils/errorThrower"; import { Breach } from "../../../../functions/universal/breach"; import { HibpLikeDbBreach } from "../../../../../utils/hibp"; diff --git a/src/app/api/mock/hibp/config/defaults.ts b/src/app/api/mock/hibp/config/defaults.ts index 963c9ce1877..86ec3073c66 100644 --- a/src/app/api/mock/hibp/config/defaults.ts +++ b/src/app/api/mock/hibp/config/defaults.ts @@ -2,7 +2,7 @@ * 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 mockBreaches from "../data/mockBreaches.json"; +import mockBreaches from "../mockData/mockBreaches.json"; import { hashToEmailKeyMap } from "../../../utils/mockUtils"; export interface BreachMap { diff --git a/src/app/api/mock/hibp/config/route.ts b/src/app/api/mock/hibp/config/route.ts index c0889ce21a0..bf0fb0c182d 100644 --- a/src/app/api/mock/hibp/config/route.ts +++ b/src/app/api/mock/hibp/config/route.ts @@ -7,7 +7,7 @@ import { NextRequest, NextResponse } from "next/server"; import fs from "fs"; import path from "path"; import { errorIfNotLocal } from "../../../utils/errorThrower"; -import mockBreaches from "../data/mockBreaches.json"; +import mockBreaches from "../mockData/mockBreaches.json"; import { logger } from "../../../../functions/server/logging"; // type hibpConfigReq = { diff --git a/src/app/api/mock/hibp/data/mockAllBreaches.json b/src/app/api/mock/hibp/mockData/mockAllBreaches.json similarity index 95% rename from src/app/api/mock/hibp/data/mockAllBreaches.json rename to src/app/api/mock/hibp/mockData/mockAllBreaches.json index 13bbc4ab361..f1d45328a02 100644 --- a/src/app/api/mock/hibp/data/mockAllBreaches.json +++ b/src/app/api/mock/hibp/mockData/mockAllBreaches.json @@ -637,6 +637,25 @@ "IsSpamList": false, "IsMalware": false, "FaviconUrl": null + }, + { + "Id": 695, + "Name": "UC", + "Title": "University of California", + "Domain": "universityofcalifornia.edu", + "BreachDate": "2020-12-24", + "AddedDate": "2021-06-20 00:44:34-07", + "ModifiedDate": "2021-07-03 18:03:54-07", + "PwnCount": 547422, + "Description": "In December 2020, the University of California suffered a data breach due to vulnerability in in a third-party provider, Accellion. The breach exposed extensive personal data on both students and staff including 547 thousand unique email addresses, names, dates of birth, genders, social security numbers, ethnicities and other academic related data attributes. Further analysis is available in Exploring the Impact of the UC Data Breach. The data was provided to HIBP courtesy of Cyril Gorlla.", + "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/UC.png", + "DataClasses": "{dates-of-birth,education-levels,email-addresses,ethnicities,genders,job-titles,names,phone-numbers,physical-addresses,social-security-numbers}", + "IsVerified": true, + "IsFabricated": false, + "IsSensitive": false, + "IsRetired": false, + "IsMalware": false, + "FaviconUrl": false } ] } diff --git a/src/app/api/mock/hibp/data/mockBreaches.json b/src/app/api/mock/hibp/mockData/mockBreaches.json similarity index 59% rename from src/app/api/mock/hibp/data/mockBreaches.json rename to src/app/api/mock/hibp/mockData/mockBreaches.json index 84eab05a99e..a773f3d670f 100644 --- a/src/app/api/mock/hibp/data/mockBreaches.json +++ b/src/app/api/mock/hibp/mockData/mockBreaches.json @@ -1,26 +1,6 @@ { - "default": [ - "GSMHosting", - "Wattpad", - "AdultFriendFinder2016", - "Zynga", - "Chegg", - "ArmorGames", - "HauteLook", - "VerificationsIO", - "CashCrate", - "Edmodo", - "Evony", - "Dropbox", - "iMesh", - "MySpace", - "LinkedIn", - "NaughtyAmerica", - "HeroesOfNewerth", - "AndroidForums", - "AshleyMadison", - "Adobe" - ], + "default": [], + "E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED": ["UC"], "E2E_TEST_ACCOUNT_EMAIL": [ "GSMHosting", "Wattpad", diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index 1e14576c8ae..8b5a0399b3d 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -4,7 +4,7 @@ import { BinaryLike, createHash } from "crypto"; import { StateAbbr } from "../../../../../utils/states"; -import MockUser from "./mockUser.json"; +import MockUser from "../mockData/mockUser.json"; import { computeSha1First6, hashToEmailKeyMap } from "../../../utils/mockUtils"; export interface Broker { diff --git a/src/app/api/mock/onerep/config/mockUser.json b/src/app/api/mock/onerep/mockData/mockUser.json similarity index 99% rename from src/app/api/mock/onerep/config/mockUser.json rename to src/app/api/mock/onerep/mockData/mockUser.json index 07e2a061a6b..690292f9bac 100644 --- a/src/app/api/mock/onerep/config/mockUser.json +++ b/src/app/api/mock/onerep/mockData/mockUser.json @@ -702,6 +702,7 @@ "updated_at": "2024-01-01T00:00:00Z" } ], + "E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED": [{}, {}, {}], "E2E_TEST_ACCOUNT_EMAIL_UNUSED": [ {}, {}, @@ -731,6 +732,7 @@ "default": [{}], "E2E_TEST_ACCOUNT_EMAIL": [{}], "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES": [{}], - "E2E_TEST_ACCOUNT_EMAIL_UNUSED": [{}] + "E2E_TEST_ACCOUNT_EMAIL_UNUSED": [{}], + "E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED": [{}] } } diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index 55ac7634100..3f4664de4f5 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -15,7 +15,7 @@ import { computeSha1First6, hashToEmailKeyMap, } from "../../../../../utils/mockUtils"; -import mockUser from "../../../config/mockUser.json"; +import mockUser from "../../../mockData/mockUser.json"; interface ScansMap { [key: string]: MockScanOptionals[]; diff --git a/src/e2e/pages/authPage.ts b/src/e2e/pages/authPage.ts index 05e27a67939..09622fa9131 100644 --- a/src/e2e/pages/authPage.ts +++ b/src/e2e/pages/authPage.ts @@ -23,7 +23,9 @@ export class AuthPage { this.ageInputField = page.getByLabel("How old are you?"); this.continueButton = page.locator('[type="submit"]').first(); this.verifyCodeInputField = page.locator("div.card input"); - this.useDifferentEmailButton = page.locator("#use-different"); + this.useDifferentEmailButton = page.locator( + 'text="Use a different account"', + ); } async continue({ waitForURL = "**/*" }) { From ebe19edc4bd44caf4d063f7869e58bea9c0abb9d Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Tue, 9 Jul 2024 14:54:43 +0200 Subject: [PATCH 045/137] fix: Handle variable default string values --- src/scripts/build/nimbusTypes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scripts/build/nimbusTypes.js b/src/scripts/build/nimbusTypes.js index 6f4d1fcf073..22e1ef6aa51 100644 --- a/src/scripts/build/nimbusTypes.js +++ b/src/scripts/build/nimbusTypes.js @@ -82,7 +82,8 @@ function getFallbackObject(nimbusConfig) { nimbusConfig.features[featureId].variables, ); const variableFallbackDefs = variableNames.map((variableName) => { - return ` "${variableName}": ${nimbusConfig.features[featureId].variables[variableName].default},\n`; + const variableValue = nimbusConfig.features[featureId].variables[variableName].default; + return ` "${variableName}": ${typeof variableValue === "string" ? JSON.stringify(variableValue) : variableValue},\n`; }); return ` "${featureId}": {\n${variableFallbackDefs.join("")} },\n`; From a5f00ed0b95811dfc2e2ecbf6c62cc9260670094 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Tue, 9 Jul 2024 14:56:02 +0200 Subject: [PATCH 046/137] feat: Add Nimbus landing-page-free-scan-cta experiment --- config/nimbus.yaml | 21 +++++++++++++++++++++ src/scripts/build/nimbusTypes.js | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/config/nimbus.yaml b/config/nimbus.yaml index 3ec6fdacf02..f5d0cbb53bf 100644 --- a/config/nimbus.yaml +++ b/config/nimbus.yaml @@ -61,3 +61,24 @@ features: value: { "enabled": false } - channel: production value: { "enabled": false } + landing-page-free-scan-cta: + description: Landing page free scan CTA + variables: + variant: + description: The CTA variant to show + type: FreeScanCtaType + default: ctaWithEmailInput + defaults: + - channel: local + value: { "variant": ctaOnly } + - channel: staging + value: { "variant": ctaWithEmailInput } + - channel: production + value: { "variant": ctaWithEmailInput } +enums: + FreeScanCtaType: + description: An enum of free scan CTA types + variants: + ctaWithEmailInput: Show a CTA button with an optional email input + ctaOnly: Only show a CTA button with the default label + ctaOnlyAlternativeLabel: Only show a CTA button with an alternative label diff --git a/src/scripts/build/nimbusTypes.js b/src/scripts/build/nimbusTypes.js index 22e1ef6aa51..57109ce1a8a 100644 --- a/src/scripts/build/nimbusTypes.js +++ b/src/scripts/build/nimbusTypes.js @@ -83,7 +83,7 @@ function getFallbackObject(nimbusConfig) { ); const variableFallbackDefs = variableNames.map((variableName) => { const variableValue = nimbusConfig.features[featureId].variables[variableName].default; - return ` "${variableName}": ${typeof variableValue === "string" ? JSON.stringify(variableValue) : variableValue},\n`; + return ` "${variableName}": ${typeof variableValue === "string" ? `"${variableValue}"` : variableValue},\n`; }); return ` "${featureId}": {\n${variableFallbackDefs.join("")} },\n`; From 1ff474db83b2285beb651f5c3273a832e66354a3 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Tue, 9 Jul 2024 15:25:05 +0200 Subject: [PATCH 047/137] chore: Export Nimbus enum types --- src/scripts/build/nimbusTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/build/nimbusTypes.js b/src/scripts/build/nimbusTypes.js index 57109ce1a8a..966ef635d35 100644 --- a/src/scripts/build/nimbusTypes.js +++ b/src/scripts/build/nimbusTypes.js @@ -180,7 +180,7 @@ function getTypeAliases(nimbusConfig) { const unionOfStrings = Object.keys(nimbusConfig.enums[typeAlias].variants) .map((variant) => `"${variant}"`) .join(" | "); - return `type ${typeAlias} = ${unionOfStrings};`; + return `export type ${typeAlias} = ${unionOfStrings};`; }); return ( From 4d6ce093af00656294621467e0855a289597cdd1 Mon Sep 17 00:00:00 2001 From: mansaj Date: Tue, 9 Jul 2024 09:08:25 -0700 Subject: [PATCH 048/137] fix: expires instead of expired --- src/db/tables/emailAddresses.js | 6 +++--- src/db/tables/subscribers.js | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/db/tables/emailAddresses.js b/src/db/tables/emailAddresses.js index 684fd792bfc..23fc6c3a027 100644 --- a/src/db/tables/emailAddresses.js +++ b/src/db/tables/emailAddresses.js @@ -218,19 +218,19 @@ async function _addEmailHash (sha1, email, signupLanguage, verified = false) { * @param {string} signupLanguage from Accept-Language * @param {string | null} fxaAccessToken from Firefox Account Oauth * @param {string | null} fxaRefreshToken from Firefox Account Oauth - * @param {number} sessionExpiredAt from Firefox Account Oauth + * @param {number} sessionExpiresAt from Firefox Account Oauth * @param {string | null} fxaProfileData from Firefox Account * @returns {Promise} subscriber knex object added to DB */ // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy /* c8 ignore start */ -async function addSubscriber (email, signupLanguage, fxaAccessToken = null, fxaRefreshToken = null, sessionExpiredAt = 0, fxaProfileData = null) { +async function addSubscriber (email, signupLanguage, fxaAccessToken = null, fxaRefreshToken = null, sessionExpiresAt = 0, fxaProfileData = null) { const lowerCaseEmail = email.toLowerCase() const emailHash = await _addEmailHash(getSha1(lowerCaseEmail), lowerCaseEmail, signupLanguage, true) const verified = await _verifySubscriber(emailHash) const verifiedSubscriber = Array.isArray(verified) ? verified[0] : null if (fxaRefreshToken || fxaProfileData) { - return updateFxAData(verifiedSubscriber, fxaAccessToken, fxaRefreshToken, sessionExpiredAt, fxaProfileData) + return updateFxAData(verifiedSubscriber, fxaAccessToken, fxaRefreshToken, sessionExpiresAt, fxaProfileData) } return verifiedSubscriber } diff --git a/src/db/tables/subscribers.js b/src/db/tables/subscribers.js index d98075b9ffc..b3aa25a91e7 100644 --- a/src/db/tables/subscribers.js +++ b/src/db/tables/subscribers.js @@ -121,13 +121,13 @@ async function updatePrimaryEmail (subscriber, updatedEmail) { * @param {any} subscriber knex object in DB * @param {string | null} fxaAccessToken from Firefox Account Oauth * @param {string | null} fxaRefreshToken from Firefox Account Oauth - * @param {number} sessionExpiredAt from Firefox Account Oauth + * @param {number} sessionExpiresAt from Firefox Account Oauth * @param {any} 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 /* c8 ignore start */ -async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, sessionExpiredAt, fxaProfileData) { +async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, sessionExpiresAt, fxaProfileData) { const fxaUID = JSON.parse(fxaProfileData).uid const updated = await knex('subscribers') .where('id', '=', subscriber.id) @@ -135,7 +135,7 @@ async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, sessi fxa_uid: fxaUID, fxa_access_token: fxaAccessToken, fxa_refresh_token: fxaRefreshToken, - fxa_session_expiry: new Date(sessionExpiredAt), + fxa_session_expiry: new Date(sessionExpiresAt), 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: @@ -156,18 +156,18 @@ async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, sessi * @param {any} subscriber knex object in DB * @param {string | null} fxaAccessToken from Firefox Account Oauth * @param {string | null} fxaRefreshToken from Firefox Account Oauth - * @param {number} sessionExpiredAt from Firefox Account Oauth + * @param {number} sessionExpiresAt from Firefox Account Oauth * @returns {Promise} updated subscriber knex object in DB */ // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy /* c8 ignore start */ -async function updateFxATokens (subscriber, fxaAccessToken, fxaRefreshToken, sessionExpiredAt) { +async function updateFxATokens (subscriber, fxaAccessToken, fxaRefreshToken, sessionExpiresAt) { const updated = await knex('subscribers') .where('id', '=', subscriber.id) .update({ fxa_access_token: fxaAccessToken, fxa_refresh_token: fxaRefreshToken, - fxa_session_expiry: new Date(sessionExpiredAt), + fxa_session_expiry: new Date(sessionExpiresAt), // @ts-ignore knex.fn.now() results in it being set to a date, // even if it's not typed as a JS date object: updated_at: knex.fn.now(), From 5d0873835cfa3ae03ec5efbc0f2daf04d239e8ac Mon Sep 17 00:00:00 2001 From: mansaj Date: Tue, 9 Jul 2024 09:10:05 -0700 Subject: [PATCH 049/137] fix: spelling of OAuth --- src/app/api/utils/auth.ts | 4 ++-- src/utils/fxa.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/api/utils/auth.ts b/src/app/api/utils/auth.ts index 4878193d09e..d86cd59af21 100644 --- a/src/app/api/utils/auth.ts +++ b/src/app/api/utils/auth.ts @@ -18,7 +18,7 @@ import { import { addSubscriber } from "../../../db/tables/emailAddresses.js"; import { getBreaches } from "../../functions/server/getBreaches"; import { getBreachesForEmail } from "../../../utils/hibp.js"; -import { getSha1, refreshOauthTokens } from "../../../utils/fxa.js"; +import { getSha1, refreshOAuthTokens } from "../../../utils/fxa.js"; import { getEmailCtaDashboardHref, initEmail, @@ -240,7 +240,7 @@ export const authOptions: AuthOptions = { return session; } try { - const responseTokens = await refreshOauthTokens( + const responseTokens = await refreshOAuthTokens( dbFxATokens.fxa_refresh_token, ); const updatedUser = await updateFxATokens( diff --git a/src/utils/fxa.js b/src/utils/fxa.js index 22bab3ceee7..e70b5800bab 100644 --- a/src/utils/fxa.js +++ b/src/utils/fxa.js @@ -79,7 +79,7 @@ async function revokeOAuthTokens(subscriber) { */ // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy /* c8 ignore start */ -async function refreshOauthTokens(refreshToken) { +async function refreshOAuthTokens(refreshToken) { const subscriptionIdUrl = `${AppConstants.OAUTH_ACCOUNT_URI}/oauth/token` try { const postResp = await fetch(subscriptionIdUrl, { @@ -284,7 +284,7 @@ function getSha1(email) { } export { - refreshOauthTokens, + refreshOAuthTokens, destroyOAuthToken, revokeOAuthTokens, getSha1, From 5894d5e79308df78607837be2956ab80be1af994 Mon Sep 17 00:00:00 2001 From: mansaj Date: Tue, 9 Jul 2024 09:19:16 -0700 Subject: [PATCH 050/137] Apply suggestions from code review Co-authored-by: Vincent --- src/app/api/utils/auth.ts | 2 +- src/db/tables/subscribers.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/api/utils/auth.ts b/src/app/api/utils/auth.ts index d86cd59af21..fd560c5d466 100644 --- a/src/app/api/utils/auth.ts +++ b/src/app/api/utils/auth.ts @@ -132,7 +132,7 @@ export const authOptions: AuthOptions = { existingUser, account.access_token, account.refresh_token, - account.expires_at || 0, + account.expires_at ?? 0, JSON.stringify(profile), ); // MNTOR-2599 The breach_resolution object can get pretty big, diff --git a/src/db/tables/subscribers.js b/src/db/tables/subscribers.js index b3aa25a91e7..72cac75c4f6 100644 --- a/src/db/tables/subscribers.js +++ b/src/db/tables/subscribers.js @@ -187,7 +187,7 @@ async function updateFxATokens (subscriber, fxaAccessToken, fxaRefreshToken, ses /* c8 ignore start */ async function getFxATokens (subscriberId) { const res = await knex('subscribers') - .select('fxa_access_token', 'fxa_refresh_token', 'fxa_session_expiry') + .first('fxa_access_token', 'fxa_refresh_token', 'fxa_session_expiry') .where('id', subscriberId) return res?.[0] ?? null } From cf043d23ba52d3c1890b1eef2304d4f4b4511295 Mon Sep 17 00:00:00 2001 From: mansaj Date: Tue, 9 Jul 2024 09:20:31 -0700 Subject: [PATCH 051/137] fix: first instead of select --- src/db/tables/subscribers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/tables/subscribers.js b/src/db/tables/subscribers.js index 72cac75c4f6..95f7e1cc71c 100644 --- a/src/db/tables/subscribers.js +++ b/src/db/tables/subscribers.js @@ -189,7 +189,7 @@ async function getFxATokens (subscriberId) { const res = await knex('subscribers') .first('fxa_access_token', 'fxa_refresh_token', 'fxa_session_expiry') .where('id', subscriberId) - return res?.[0] ?? null + return res ?? null } /* c8 ignore stop */ From 112cacd1aa5fadb4394766bba993446f456d7dcb Mon Sep 17 00:00:00 2001 From: mansaj Date: Tue, 9 Jul 2024 09:30:04 -0700 Subject: [PATCH 052/137] fix: review comment --- src/db/tables/subscribers.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/db/tables/subscribers.js b/src/db/tables/subscribers.js index 95f7e1cc71c..d09c82e5b5b 100644 --- a/src/db/tables/subscribers.js +++ b/src/db/tables/subscribers.js @@ -162,7 +162,7 @@ async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, sessi // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy /* c8 ignore start */ async function updateFxATokens (subscriber, fxaAccessToken, fxaRefreshToken, sessionExpiresAt) { - const updated = await knex('subscribers') + return await knex('subscribers') .where('id', '=', subscriber.id) .update({ fxa_access_token: fxaAccessToken, @@ -173,8 +173,7 @@ async function updateFxATokens (subscriber, fxaAccessToken, fxaRefreshToken, ses updated_at: knex.fn.now(), }) .returning('*') - const updatedSubscriber = Array.isArray(updated) ? updated[0] : null - return updatedSubscriber + .first() || null; } /* c8 ignore stop */ From 04a001da395bef018f247c9e4c8e0d1fc9fae2ac Mon Sep 17 00:00:00 2001 From: mansaj Date: Tue, 9 Jul 2024 09:33:12 -0700 Subject: [PATCH 053/137] fix: potential null case --- src/app/api/utils/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/api/utils/auth.ts b/src/app/api/utils/auth.ts index fd560c5d466..e89ba0b19d5 100644 --- a/src/app/api/utils/auth.ts +++ b/src/app/api/utils/auth.ts @@ -230,11 +230,11 @@ export const authOptions: AuthOptions = { // refresh token const dbFxATokens = await getFxATokens(token.subscriber.id); if ( - !dbFxATokens.fxa_session_expiry || + !dbFxATokens?.fxa_session_expiry || dbFxATokens.fxa_session_expiry.getTime() < Date.now() ) { // If the access token has expired, try to refresh it - if (!dbFxATokens.fxa_refresh_token) { + if (!dbFxATokens?.fxa_refresh_token) { logger.error("no_fxa_refresh_token", { dbFxATokens }); session.error = "RefreshAccessTokenError"; return session; From 63623171949181881447ddff3b46cbc4d1e646a9 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Tue, 9 Jul 2024 19:05:21 +0200 Subject: [PATCH 054/137] feat: Add free scan CTA experiment variants to landing page --- config/nimbus.yaml | 8 +- locales/en/landing-all.ftl | 1 + .../(redesign)/(public)/FreeScanCta.tsx | 99 +++++++++++++++++++ .../(redesign)/(public)/LandingView.tsx | 22 +++-- .../(redesign)/(public)/SignUpForm.tsx | 36 ++++--- .../(redesign)/(public)/page.tsx | 11 +++ src/scripts/build/nimbusTypes.js | 2 +- 7 files changed, 154 insertions(+), 25 deletions(-) create mode 100644 src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx diff --git a/config/nimbus.yaml b/config/nimbus.yaml index f5d0cbb53bf..a1c060e703c 100644 --- a/config/nimbus.yaml +++ b/config/nimbus.yaml @@ -67,18 +67,18 @@ features: variant: description: The CTA variant to show type: FreeScanCtaType - default: ctaWithEmailInput + default: ctaWithEmail defaults: - channel: local value: { "variant": ctaOnly } - channel: staging - value: { "variant": ctaWithEmailInput } + value: { "variant": ctaWithEmail } - channel: production - value: { "variant": ctaWithEmailInput } + value: { "variant": ctaWithEmail } enums: FreeScanCtaType: description: An enum of free scan CTA types variants: - ctaWithEmailInput: Show a CTA button with an optional email input + ctaWithEmail: Show a CTA button with an optional email input ctaOnly: Only show a CTA button with the default label ctaOnlyAlternativeLabel: Only show a CTA button with an alternative label diff --git a/locales/en/landing-all.ftl b/locales/en/landing-all.ftl index 78d21c9968e..581cb3dd94e 100644 --- a/locales/en/landing-all.ftl +++ b/locales/en/landing-all.ftl @@ -10,6 +10,7 @@ landing-all-hero-lead = We scan data breaches to see if your data has been leake landing-all-hero-emailform-input-placeholder = yourname@example.com landing-all-hero-emailform-input-label = Enter your email address to check for data breach exposures. landing-all-hero-emailform-submit-label = Get free scan +landing-all-hero-emailform-submit-sign-in-label = Sign in to get free scan # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = exposures diff --git a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx new file mode 100644 index 00000000000..7be1473ecc2 --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx @@ -0,0 +1,99 @@ +/* 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/. */ + +"use client"; + +import { signIn } from "next-auth/react"; +import { useCookies } from "react-cookie"; +import { SignUpForm, Props as SignUpFormProps } from "./SignUpForm"; +import { TelemetryButton } from "../../../components/client/TelemetryButton"; +import { modifyAttributionsForUrlSearchParams } from "../../../functions/universal/attributions"; +import { ExperimentData } from "../../../../telemetry/generated/nimbus/experiments"; +import { useL10n } from "../../../hooks/l10n"; + +export type Props = { + eligibleForPremium: boolean; + signUpCallbackUrl: string; + isHero?: boolean; + eventId: { + cta: string; + field?: string; + }; + scanLimitReached: boolean; + placeholder?: string; +}; + +export function getAttributionSearchParams({ + cookies, + emailInput, +}: { + cookies: { + attributionsFirstTouch?: string; + }; + emailInput?: string; +}) { + const attributionSearchParams = modifyAttributionsForUrlSearchParams( + new URLSearchParams(cookies.attributionsFirstTouch), + { + entrypoint: "monitor.mozilla.org-monitor-product-page", + form_type: "button", + ...(emailInput && { email: emailInput }), + }, + { + utm_source: "product", + utm_medium: "monitor", + utm_campaign: "get_free_scan", + }, + ); + + return attributionSearchParams.toString(); +} + +export const FreeScanCta = ( + props: SignUpFormProps & { + experimentData: ExperimentData; + }, +) => { + const l10n = useL10n(); + const [cookies] = useCookies(["attributionsFirstTouch"]); + const freeScanCtaVariant = + props.experimentData["landing-page-free-scan-cta"].variant; + if (freeScanCtaVariant === "ctaWithEmail") { + return ( + + ); + } + + return ( + { + void signIn( + "fxa", + { callbackUrl: props.signUpCallbackUrl }, + getAttributionSearchParams({ cookies }), + ); + }} + > + {l10n.getString( + freeScanCtaVariant === "ctaOnly" + ? "landing-all-hero-emailform-submit-label" + : "landing-all-hero-emailform-submit-sign-in-label", + )} + + ); +}; diff --git a/src/app/(proper_react)/(redesign)/(public)/LandingView.tsx b/src/app/(proper_react)/(redesign)/(public)/LandingView.tsx index 4c162b90403..82ec8d4bff5 100644 --- a/src/app/(proper_react)/(redesign)/(public)/LandingView.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/LandingView.tsx @@ -4,7 +4,6 @@ import styles from "./LandingView.module.scss"; import { HeroImageAll, HeroImagePremium } from "./HeroImage"; -import { SignUpForm } from "./SignUpForm"; import { ExtendedReactLocalization } from "../../../functions/l10n"; import { PlansTable } from "./PlansTable"; import { useId } from "react"; @@ -29,6 +28,8 @@ import { ScanLimit } from "./ScanLimit"; import { FaqSection } from "./Faq"; import { FeatureFlagName } from "../../../../db/tables/featureFlags"; import { AccountDeletionNotification } from "./AccountDeletionNotification"; +import { ExperimentData } from "../../../../telemetry/generated/nimbus/experiments"; +import { FreeScanCta } from "./FreeScanCta"; export type Props = { eligibleForPremium: boolean; @@ -36,6 +37,7 @@ export type Props = { countryCode: string; scanLimitReached: boolean; enabledFlags: FeatureFlagName[]; + experimentData: ExperimentData; }; export const View = (props: Props) => { @@ -57,7 +59,7 @@ export const View = (props: Props) => { {props.eligibleForPremium && props.scanLimitReached ? ( ) : ( - { cta: "clicked_get_scan_header", field: "entered_email_address_header", }} + experimentData={props.experimentData} /> )} @@ -129,7 +132,7 @@ export const View = (props: Props) => { }, )}

- { cta: "clicked_get_scan_second", field: "entered_email_address_second", }} + experimentData={props.experimentData} />
@@ -175,7 +179,7 @@ export const View = (props: Props) => { "landing-all-value-prop-info-at-risk-description", )}

- { cta: "clicked_get_scan_third", field: "entered_email_address_third", }} + experimentData={props.experimentData} />
@@ -195,7 +200,7 @@ export const View = (props: Props) => {

{props.l10n.getString("landing-all-get-started")}

- { field: "entered_email_address_fourth", }} scanLimitReached={props.scanLimitReached} + experimentData={props.experimentData} />
@@ -238,7 +244,7 @@ export const View = (props: Props) => {

{props.l10n.getString("landing-all-take-back-data")}

- { field: "entered_email_address_last", }} scanLimitReached={props.scanLimitReached} + experimentData={props.experimentData} />
@@ -352,13 +359,14 @@ const Plans = (props: Props) => {
{props.l10n.getString("landing-premium-waitlist-section-pt-2")} - )} diff --git a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx index 40dbc1ad6ed..c0d5dec0dcc 100644 --- a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx @@ -27,21 +27,21 @@ export type Props = { placeholder?: string; }; -export const SignUpForm = (props: Props) => { - const emailInputId = useId(); - const l10n = useL10n(); - const [emailInput, setEmailInput] = useState(""); - const record = useTelemetry(); - const [cookies] = useCookies(["attributionsFirstTouch"]); - let attributionSearchParams = new URLSearchParams( - cookies.attributionsFirstTouch, - ); - attributionSearchParams = modifyAttributionsForUrlSearchParams( - attributionSearchParams, +export function getAttributionSearchParams({ + cookies, + emailInput, +}: { + cookies: { + attributionsFirstTouch?: string; + }; + emailInput?: string; +}) { + const attributionSearchParams = modifyAttributionsForUrlSearchParams( + new URLSearchParams(cookies.attributionsFirstTouch), { entrypoint: "monitor.mozilla.org-monitor-product-page", - email: emailInput, form_type: "button", + ...(emailInput && { email: emailInput }), }, { utm_source: "product", @@ -50,12 +50,22 @@ export const SignUpForm = (props: Props) => { }, ); + return attributionSearchParams.toString(); +} + +export const SignUpForm = (props: Props) => { + const emailInputId = useId(); + const l10n = useL10n(); + const [emailInput, setEmailInput] = useState(""); + const record = useTelemetry(); + const [cookies] = useCookies(["attributionsFirstTouch"]); + const onSubmit: FormEventHandler = (event) => { event.preventDefault(); void signIn( "fxa", { callbackUrl: props.signUpCallbackUrl }, - attributionSearchParams.toString(), + getAttributionSearchParams({ cookies, emailInput }), ); }; diff --git a/src/app/(proper_react)/(redesign)/(public)/page.tsx b/src/app/(proper_react)/(redesign)/(public)/page.tsx index 3e303ca8d59..a7f99bfb6a8 100644 --- a/src/app/(proper_react)/(redesign)/(public)/page.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/page.tsx @@ -15,6 +15,9 @@ import { getEnabledFeatureFlags } from "../../../../db/tables/featureFlags"; import { getL10n } from "../../../functions/l10n/serverComponents"; import { View } from "./LandingView"; import { CONST_DAY_MILLISECONDS } from "../../../../constants"; +import { getExperimentationId } from "../../../functions/server/getExperimentationId"; +import { getExperiments } from "../../../functions/server/getExperiments"; +import { getLocale } from "../../../functions/universal/getLocale"; export default async function Page() { const session = await getServerSession(); @@ -25,6 +28,13 @@ export default async function Page() { const countryCode = getCountryCode(headers()); const eligibleForPremium = isEligibleForPremium(countryCode); + const experimentationId = getExperimentationId(session?.user ?? null); + const experimentData = await getExperiments({ + experimentationId, + countryCode, + locale: getLocale(getL10n()), + }); + // request the profile stats for the last 30 days const profileStats = await getProfilesStats( new Date(Date.now() - 30 * CONST_DAY_MILLISECONDS), @@ -40,6 +50,7 @@ export default async function Page() { countryCode={countryCode} scanLimitReached={scanLimitReached} enabledFlags={enabledFlags} + experimentData={experimentData} /> ); } diff --git a/src/scripts/build/nimbusTypes.js b/src/scripts/build/nimbusTypes.js index 966ef635d35..57109ce1a8a 100644 --- a/src/scripts/build/nimbusTypes.js +++ b/src/scripts/build/nimbusTypes.js @@ -180,7 +180,7 @@ function getTypeAliases(nimbusConfig) { const unionOfStrings = Object.keys(nimbusConfig.enums[typeAlias].variants) .map((variant) => `"${variant}"`) .join(" | "); - return `export type ${typeAlias} = ${unionOfStrings};`; + return `type ${typeAlias} = ${unionOfStrings};`; }); return ( From 7ceb8a4cdc39db4b6a6a480d5e66fe7e7080ad49 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Wed, 10 Jul 2024 12:40:05 +0200 Subject: [PATCH 055/137] chore: Add enabled property to landing-page-free-scan-cta experiment --- config/nimbus.yaml | 19 ++++++++-- .../(redesign)/(public)/FreeScanCta.tsx | 33 ++++++++++++----- .../(public)/LandingView.stories.tsx | 3 +- .../(redesign)/(public)/SignUpForm.tsx | 36 +++++-------------- 4 files changed, 51 insertions(+), 40 deletions(-) diff --git a/config/nimbus.yaml b/config/nimbus.yaml index a1c060e703c..3dabef11043 100644 --- a/config/nimbus.yaml +++ b/config/nimbus.yaml @@ -64,17 +64,30 @@ features: landing-page-free-scan-cta: description: Landing page free scan CTA variables: + enabled: + description: If the feature is enabled + type: Boolean + default: false variant: description: The CTA variant to show type: FreeScanCtaType default: ctaWithEmail defaults: - channel: local - value: { "variant": ctaOnly } + value: { + "enabled": true, + "variant": ctaOnly, + } - channel: staging - value: { "variant": ctaWithEmail } + value: { + "enabled": false, + "variant": ctaWithEmail, + } - channel: production - value: { "variant": ctaWithEmail } + value: { + "enabled": false, + "variant": ctaWithEmail, + } enums: FreeScanCtaType: description: An enum of free scan CTA types diff --git a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx index 7be1473ecc2..97343888c26 100644 --- a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx @@ -11,6 +11,7 @@ import { TelemetryButton } from "../../../components/client/TelemetryButton"; import { modifyAttributionsForUrlSearchParams } from "../../../functions/universal/attributions"; import { ExperimentData } from "../../../../telemetry/generated/nimbus/experiments"; import { useL10n } from "../../../hooks/l10n"; +import { WaitlistCta } from "./ScanLimit"; export type Props = { eligibleForPremium: boolean; @@ -27,10 +28,12 @@ export type Props = { export function getAttributionSearchParams({ cookies, emailInput, + experimentData, }: { cookies: { attributionsFirstTouch?: string; }; + experimentData: ExperimentData; emailInput?: string; }) { const attributionSearchParams = modifyAttributionsForUrlSearchParams( @@ -38,7 +41,13 @@ export function getAttributionSearchParams({ { entrypoint: "monitor.mozilla.org-monitor-product-page", form_type: "button", + service: "monitor", ...(emailInput && { email: emailInput }), + ...(experimentData["landing-page-free-scan-cta"].enabled && { + entrypoint_experiment: "landing-page-free-scan-cta", + entrypoint_variation: + experimentData["landing-page-free-scan-cta"].variant, + }), }, { utm_source: "product", @@ -57,40 +66,48 @@ export const FreeScanCta = ( ) => { const l10n = useL10n(); const [cookies] = useCookies(["attributionsFirstTouch"]); - const freeScanCtaVariant = - props.experimentData["landing-page-free-scan-cta"].variant; - if (freeScanCtaVariant === "ctaWithEmail") { + if ( + !props.experimentData["landing-page-free-scan-cta"].enabled || + props.experimentData["landing-page-free-scan-cta"].variant === + "ctaWithEmail" + ) { return ( ); } - return ( + return props.scanLimitReached ? ( + + ) : ( { void signIn( "fxa", { callbackUrl: props.signUpCallbackUrl }, - getAttributionSearchParams({ cookies }), + getAttributionSearchParams({ + cookies, + experimentData: props.experimentData, + }), ); }} > {l10n.getString( - freeScanCtaVariant === "ctaOnly" + props.experimentData["landing-page-free-scan-cta"].variant === "ctaOnly" ? "landing-all-hero-emailform-submit-label" : "landing-all-hero-emailform-submit-sign-in-label", )} diff --git a/src/app/(proper_react)/(redesign)/(public)/LandingView.stories.tsx b/src/app/(proper_react)/(redesign)/(public)/LandingView.stories.tsx index 9598250e8f4..bf23a758e36 100644 --- a/src/app/(proper_react)/(redesign)/(public)/LandingView.stories.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/LandingView.stories.tsx @@ -6,12 +6,13 @@ import type { Meta, StoryObj } from "@storybook/react"; import { View, Props as ViewProps } from "./LandingView"; import { getL10n } from "../../../functions/l10n/storybookAndJest"; import { PublicShell } from "./PublicShell"; +import { defaultExperimentData } from "../../../../telemetry/generated/nimbus/experiments"; const meta: Meta = { title: "Pages/Public/Landing page", component: (props: ViewProps) => ( - + ), args: { diff --git a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx index c0d5dec0dcc..6bab95c4e9f 100644 --- a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx @@ -13,7 +13,8 @@ import { useTelemetry } from "../../../hooks/useTelemetry"; import { VisuallyHidden } from "../../../components/server/VisuallyHidden"; import { WaitlistCta } from "./ScanLimit"; import { useCookies } from "react-cookie"; -import { modifyAttributionsForUrlSearchParams } from "../../../functions/universal/attributions"; +import { getAttributionSearchParams } from "./FreeScanCta"; +import { ExperimentData } from "../../../../telemetry/generated/nimbus/experiments"; export type Props = { eligibleForPremium: boolean; @@ -24,35 +25,10 @@ export type Props = { field?: string; }; scanLimitReached: boolean; + experimentData: ExperimentData; placeholder?: string; }; -export function getAttributionSearchParams({ - cookies, - emailInput, -}: { - cookies: { - attributionsFirstTouch?: string; - }; - emailInput?: string; -}) { - const attributionSearchParams = modifyAttributionsForUrlSearchParams( - new URLSearchParams(cookies.attributionsFirstTouch), - { - entrypoint: "monitor.mozilla.org-monitor-product-page", - form_type: "button", - ...(emailInput && { email: emailInput }), - }, - { - utm_source: "product", - utm_medium: "monitor", - utm_campaign: "get_free_scan", - }, - ); - - return attributionSearchParams.toString(); -} - export const SignUpForm = (props: Props) => { const emailInputId = useId(); const l10n = useL10n(); @@ -65,7 +41,11 @@ export const SignUpForm = (props: Props) => { void signIn( "fxa", { callbackUrl: props.signUpCallbackUrl }, - getAttributionSearchParams({ cookies, emailInput }), + getAttributionSearchParams({ + cookies, + emailInput, + experimentData: props.experimentData, + }), ); }; From ce656e9fce5dcb8cfcab38d90cf90d0c71d944c9 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Wed, 10 Jul 2024 13:04:57 +0200 Subject: [PATCH 056/137] fix: Make experimentData prop optional for SignUpForm --- .../(redesign)/(public)/FreeScanCta.tsx | 13 +++++++------ .../(redesign)/(public)/SignUpForm.tsx | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx index 97343888c26..f387269302a 100644 --- a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx @@ -33,7 +33,7 @@ export function getAttributionSearchParams({ cookies: { attributionsFirstTouch?: string; }; - experimentData: ExperimentData; + experimentData?: ExperimentData; emailInput?: string; }) { const attributionSearchParams = modifyAttributionsForUrlSearchParams( @@ -43,11 +43,12 @@ export function getAttributionSearchParams({ form_type: "button", service: "monitor", ...(emailInput && { email: emailInput }), - ...(experimentData["landing-page-free-scan-cta"].enabled && { - entrypoint_experiment: "landing-page-free-scan-cta", - entrypoint_variation: - experimentData["landing-page-free-scan-cta"].variant, - }), + ...(experimentData && + experimentData["landing-page-free-scan-cta"].enabled && { + entrypoint_experiment: "landing-page-free-scan-cta", + entrypoint_variation: + experimentData["landing-page-free-scan-cta"].variant, + }), }, { utm_source: "product", diff --git a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx index 6bab95c4e9f..d46a9264310 100644 --- a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx @@ -25,7 +25,7 @@ export type Props = { field?: string; }; scanLimitReached: boolean; - experimentData: ExperimentData; + experimentData?: ExperimentData; placeholder?: string; }; From ec8c46213374f453d108027eb476ff6c09dac749 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Wed, 10 Jul 2024 14:17:21 +0200 Subject: [PATCH 057/137] chore: Add unit tests for the free scan CTA experiment --- .../(public)/LandingView.stories.tsx | 5 +- .../(redesign)/(public)/LandingView.test.tsx | 155 ++++++++++++++++++ 2 files changed, 159 insertions(+), 1 deletion(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/LandingView.stories.tsx b/src/app/(proper_react)/(redesign)/(public)/LandingView.stories.tsx index bf23a758e36..8e815df0933 100644 --- a/src/app/(proper_react)/(redesign)/(public)/LandingView.stories.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/LandingView.stories.tsx @@ -12,7 +12,10 @@ const meta: Meta = { title: "Pages/Public/Landing page", component: (props: ViewProps) => ( - + ), args: { diff --git a/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx b/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx index 54b05cf7832..6b6a2caa4f7 100644 --- a/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx @@ -26,6 +26,7 @@ import Meta, { LandingUsScanLimit, } from "./LandingView.stories"; import { deleteAllCookies } from "../../../functions/client/deleteAllCookies"; +import { defaultExperimentData } from "../../../../telemetry/generated/nimbus/experiments"; jest.mock("next-auth/react", () => { return { @@ -833,6 +834,160 @@ describe("When Premium is available", () => { }); }); +describe("Free scan CTA experiment", () => { + it("shows the CTA button with email input if the experiment disabled", () => { + const ComposedDashboard = composeStory(LandingUs, Meta); + render( + , + ); + + const inputField = screen.getAllByLabelText( + "Enter your email address to check for data breach exposures and sites selling your info.", + ); + expect(inputField[0]).toBeInTheDocument(); + + const submitButton = screen.getAllByRole("button", { + name: "Get free scan", + }); + expect(submitButton[0]).toBeInTheDocument(); + }); + + it("shows the CTA button with email input for the variant `ctaWithEmail` if the experiment is enabled", () => { + const ComposedDashboard = composeStory(LandingUs, Meta); + render( + , + ); + + const inputField = screen.queryAllByLabelText( + "Enter your email address to check for data breach exposures and sites selling your info.", + ); + expect(inputField[0]).toBeInTheDocument(); + + const submitButton = screen.getAllByRole("button", { + name: "Get free scan", + }); + expect(submitButton[0]).toBeInTheDocument(); + }); + + it("shows the CTA button only for the variant `ctaOnly` if the experiment is enabled", () => { + const ComposedDashboard = composeStory(LandingUs, Meta); + render( + , + ); + + const inputField = screen.queryAllByLabelText( + "Enter your email address to check for data breach exposures and sites selling your info.", + ); + expect(inputField[0]).toBeUndefined(); + + const submitButton = screen.getAllByRole("button", { + name: "Get free scan", + }); + expect(submitButton[0]).toBeInTheDocument(); + }); + + it("shows the CTA button only with an alternative label for the variant `ctaOnlyAlternativeLabel` if the experiment is enabled", () => { + const ComposedDashboard = composeStory(LandingUs, Meta); + render( + , + ); + + const inputField = screen.queryAllByLabelText( + "Enter your email address to check for data breach exposures and sites selling your info.", + ); + expect(inputField[0]).toBeUndefined(); + + const submitButton = screen.getAllByRole("button", { + name: "Sign in to get free scan", + }); + expect(submitButton[0]).toBeInTheDocument(); + }); + + it("shows the waitlist CTA when the scan limit is reached", () => { + const ComposedDashboard = composeStory(LandingUsScanLimit, Meta); + render( + , + ); + const waitlistCta = screen.getAllByRole("link", { + name: "Join waitlist", + }); + expect(waitlistCta[0]).toBeInTheDocument(); + }); + + it("sends telemetry for the different experiment variants", async () => { + const mockedRecord = useTelemetry(); + const user = userEvent.setup(); + const ComposedDashboard = composeStory(LandingUs, Meta); + render( + , + ); + + // jsdom will complain about not being able to navigate to a different page + // after clicking the link; suppress that error, as it's not relevant to the + // test: + jest.spyOn(console, "error").mockImplementation(() => undefined); + + const submitButton = screen.getAllByRole("button", { + name: "Get free scan", + }); + await user.click(submitButton[0]); + + expect(mockedRecord).toHaveBeenCalledWith( + "ctaButton", + "click", + expect.objectContaining({ button_id: "clicked_get_scan_header-ctaOnly" }), + ); + }); +}); + it("does not show a confirmaton message if the user has just deleted their account", () => { document.cookie = "justDeletedAccount=justDeletedAccount; max-age=0"; From fb9c75728e7f621112187185be19024afc057d2e Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Wed, 10 Jul 2024 16:01:38 +0200 Subject: [PATCH 058/137] chore: Set URL search param service to use public OAUTH_CLIENT_ID --- src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx index f387269302a..50ec4dd15c6 100644 --- a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx @@ -41,7 +41,7 @@ export function getAttributionSearchParams({ { entrypoint: "monitor.mozilla.org-monitor-product-page", form_type: "button", - service: "monitor", + service: process.env.OAUTH_CLIENT_ID ?? "", ...(emailInput && { email: emailInput }), ...(experimentData && experimentData["landing-page-free-scan-cta"].enabled && { From 5bb2107f9c4e16a17cf3f9bdc88cb207037178db Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Wed, 10 Jul 2024 17:35:39 +0200 Subject: [PATCH 059/137] fix: Get free scan experiment CTA layout --- .../(redesign)/(public)/FreeScanCta.tsx | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx index 50ec4dd15c6..5f859ba388d 100644 --- a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx @@ -87,31 +87,34 @@ export const FreeScanCta = ( return props.scanLimitReached ? ( ) : ( - { - void signIn( - "fxa", - { callbackUrl: props.signUpCallbackUrl }, - getAttributionSearchParams({ - cookies, - experimentData: props.experimentData, - }), - ); - }} - > - {l10n.getString( - props.experimentData["landing-page-free-scan-cta"].variant === "ctaOnly" - ? "landing-all-hero-emailform-submit-label" - : "landing-all-hero-emailform-submit-sign-in-label", - )} - +
+ { + void signIn( + "fxa", + { callbackUrl: props.signUpCallbackUrl }, + getAttributionSearchParams({ + cookies, + experimentData: props.experimentData, + }), + ); + }} + > + {l10n.getString( + props.experimentData["landing-page-free-scan-cta"].variant === + "ctaOnly" + ? "landing-all-hero-emailform-submit-label" + : "landing-all-hero-emailform-submit-sign-in-label", + )} + +
); }; From dd93f7e55f07653170ae8cd6a676569e95f90ed9 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Thu, 11 Jul 2024 16:13:47 +0200 Subject: [PATCH 060/137] chore: Infer that env.OAUTH_CLIENT_ID is a string --- src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx index 5f859ba388d..c6a13f897fa 100644 --- a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx @@ -41,7 +41,7 @@ export function getAttributionSearchParams({ { entrypoint: "monitor.mozilla.org-monitor-product-page", form_type: "button", - service: process.env.OAUTH_CLIENT_ID ?? "", + service: process.env.OAUTH_CLIENT_ID as string, ...(emailInput && { email: emailInput }), ...(experimentData && experimentData["landing-page-free-scan-cta"].enabled && { From 4310dba76903930307cdede6f5b828edf670caa7 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Thu, 11 Jul 2024 18:48:28 +0200 Subject: [PATCH 061/137] fix: Add previewMode to getExperiments --- src/app/(proper_react)/(redesign)/(public)/page.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/page.tsx b/src/app/(proper_react)/(redesign)/(public)/page.tsx index a7f99bfb6a8..64c7bc3541f 100644 --- a/src/app/(proper_react)/(redesign)/(public)/page.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/page.tsx @@ -19,7 +19,13 @@ import { getExperimentationId } from "../../../functions/server/getExperimentati import { getExperiments } from "../../../functions/server/getExperiments"; import { getLocale } from "../../../functions/universal/getLocale"; -export default async function Page() { +type Props = { + searchParams: { + nimbus_web_preview?: string; + }; +}; + +export default async function Page({ searchParams }: Props) { const session = await getServerSession(); if (typeof session?.user.subscriber?.fxa_uid === "string") { return redirect("/user/dashboard"); @@ -33,6 +39,7 @@ export default async function Page() { experimentationId, countryCode, locale: getLocale(getL10n()), + previewMode: searchParams.nimbus_web_preview === "true", }); // request the profile stats for the last 30 days From e443167697795359c8935900e539ec8a397dc20c Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Thu, 11 Jul 2024 22:09:54 +0200 Subject: [PATCH 062/137] feat: Add element view telemetry --- package-lock.json | 9 ++--- package.json | 2 +- .../(redesign)/(public)/LandingView.tsx | 5 +++ .../(redesign)/(public)/SignUpForm.tsx | 13 ++++++- src/app/components/client/PageLoadEvent.tsx | 4 ++- src/app/hooks/useTelemetry.ts | 8 +++-- src/app/hooks/useViewTelemetry.ts | 32 +++++++++++++++++ src/telemetry/metrics.yaml | 34 +++++++++++++++++++ 8 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 src/app/hooks/useViewTelemetry.ts diff --git a/package-lock.json b/package-lock.json index c5812133fe1..aef691d5c51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -94,7 +94,7 @@ "lint-staged": "^15.2.5", "mjml-browser": "^4.15.3", "prettier": "3.3.2", - "react-intersection-observer": "^9.10.3", + "react-intersection-observer": "^9.13.0", "sass": "^1.77.6", "storybook": "^8.1.11", "stylelint": "^16.6.1", @@ -26699,10 +26699,11 @@ "dev": true }, "node_modules/react-intersection-observer": { - "version": "9.10.3", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.10.3.tgz", - "integrity": "sha512-9NYfKwPZRovB6QJee7fDg0zz/SyYrqXtn5xTZU0vwLtLVBtfu9aZt1pVmr825REE49VPDZ7Lm5SNHjJBOTZHpA==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.0.tgz", + "integrity": "sha512-y0UvBfjDiXqC8h0EWccyaj4dVBWMxgEx0t5RGNzQsvkfvZwugnKwxpu70StY4ivzYuMajavwUDjH4LJyIki9Lw==", "dev": true, + "license": "MIT", "peerDependencies": { "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/package.json b/package.json index 4dad71cb813..d7b257a612c 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "lint-staged": "^15.2.5", "mjml-browser": "^4.15.3", "prettier": "3.3.2", - "react-intersection-observer": "^9.10.3", + "react-intersection-observer": "^9.13.0", "sass": "^1.77.6", "storybook": "^8.1.11", "stylelint": "^16.6.1", diff --git a/src/app/(proper_react)/(redesign)/(public)/LandingView.tsx b/src/app/(proper_react)/(redesign)/(public)/LandingView.tsx index 4c162b90403..4a815645cfc 100644 --- a/src/app/(proper_react)/(redesign)/(public)/LandingView.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/LandingView.tsx @@ -65,6 +65,7 @@ export const View = (props: Props) => { eventId={{ cta: "clicked_get_scan_header", field: "entered_email_address_header", + view: "viewed_get_scan_header", }} /> )} @@ -136,6 +137,7 @@ export const View = (props: Props) => { eventId={{ cta: "clicked_get_scan_second", field: "entered_email_address_second", + view: "viewed_get_scan_second", }} /> @@ -182,6 +184,7 @@ export const View = (props: Props) => { eventId={{ cta: "clicked_get_scan_third", field: "entered_email_address_third", + view: "viewed_get_scan_third", }} /> @@ -201,6 +204,7 @@ export const View = (props: Props) => { eventId={{ cta: "clicked_get_scan_fourth", field: "entered_email_address_fourth", + view: "viewed_get_scan_fourth", }} scanLimitReached={props.scanLimitReached} /> @@ -244,6 +248,7 @@ export const View = (props: Props) => { eventId={{ cta: "clicked_get_scan_last", field: "entered_email_address_last", + view: "viewed_get_scan_last", }} scanLimitReached={props.scanLimitReached} /> diff --git a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx index 40dbc1ad6ed..c3047a10652 100644 --- a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx @@ -10,6 +10,7 @@ import { useL10n } from "../../../hooks/l10n"; import { Button } from "../../../components/client/Button"; import styles from "./SignUpForm.module.scss"; import { useTelemetry } from "../../../hooks/useTelemetry"; +import { useViewTelemetry } from "../../../hooks/useViewTelemetry"; import { VisuallyHidden } from "../../../components/server/VisuallyHidden"; import { WaitlistCta } from "./ScanLimit"; import { useCookies } from "react-cookie"; @@ -22,6 +23,7 @@ export type Props = { eventId: { cta: string; field?: string; + view?: string; }; scanLimitReached: boolean; placeholder?: string; @@ -32,6 +34,15 @@ export const SignUpForm = (props: Props) => { const l10n = useL10n(); const [emailInput, setEmailInput] = useState(""); const record = useTelemetry(); + const { view } = props.eventId; + const refViewTelemetry = useViewTelemetry( + { + element_id: view, + }, + { + skip: typeof view === "undefined", + }, + ); const [cookies] = useCookies(["attributionsFirstTouch"]); let attributionSearchParams = new URLSearchParams( cookies.attributionsFirstTouch, @@ -72,7 +83,7 @@ export const SignUpForm = (props: Props) => { return props.scanLimitReached ? ( ) : ( -
+ { ]); const pathname = usePathname(); - const recordTelemetry = useTelemetry(props.experimentationId); + const recordTelemetry = useTelemetry({ + experimentationId: props.experimentationId, + }); if ( props.experimentationId.startsWith("guest") && diff --git a/src/app/hooks/useTelemetry.ts b/src/app/hooks/useTelemetry.ts index 28cb014be1d..f880534fa40 100644 --- a/src/app/hooks/useTelemetry.ts +++ b/src/app/hooks/useTelemetry.ts @@ -17,9 +17,13 @@ const TelemetryPlatforms = { Ga: "ga", } as const; -export const useTelemetry = (experimentationId?: string) => { +export type TelemetryArgs = { + experimentationId?: string; +}; + +export const useTelemetry = (args?: TelemetryArgs) => { const path = usePathname(); - const recordGlean = useGlean(experimentationId); + const recordGlean = useGlean(args?.experimentationId); const { Glean, Ga } = TelemetryPlatforms; const recordTelemetry = < diff --git a/src/app/hooks/useViewTelemetry.ts b/src/app/hooks/useViewTelemetry.ts new file mode 100644 index 00000000000..49c8528c5c8 --- /dev/null +++ b/src/app/hooks/useViewTelemetry.ts @@ -0,0 +1,32 @@ +/* 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 { IntersectionOptions, useInView } from "react-intersection-observer"; +import { TelemetryArgs, useTelemetry } from "./useTelemetry"; +import { GleanMetricMap } from "../../telemetry/generated/_map"; + +export function useViewTelemetry< + EventModule extends "view", + EventName extends keyof GleanMetricMap[EventModule], +>( + args: TelemetryArgs & GleanMetricMap[EventModule][EventName], + options?: IntersectionOptions, +) { + const { experimentationId, ...telemetryArgs } = args; + const recordTelemetry = useTelemetry({ experimentationId }); + const { ref } = useInView({ + skip: Object.keys(telemetryArgs).length === 0, + threshold: 1, + triggerOnce: true, + ...options, + onChange: (inView) => { + if (!inView) { + return; + } + recordTelemetry("view", "enter", telemetryArgs); + }, + }); + + return ref; +} diff --git a/src/telemetry/metrics.yaml b/src/telemetry/metrics.yaml index 71c2dfca2d0..b4e52c276c9 100644 --- a/src/telemetry/metrics.yaml +++ b/src/telemetry/metrics.yaml @@ -396,6 +396,40 @@ collapse: description: Which tier of plan the user is on [Free, Plus] type: string +view: + enter: + type: event + description: | + A DOM element entered the viewport. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1823766 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1823766 + data_sensitivity: + - interaction + notification_emails: + - rhelmer@mozilla.com + expires: never + extra_keys: + path: + description: The path of the page. + type: string + user_id: + description: Mozilla accounts user ID. + type: string + session_id: + description: An ID that allows us to track “sessions” of the user experience within the product. + type: string + flow_id: + description: A randomly generated unique identifier for following user flows within the FxA system. + type: string + element_id: + description: The ID of the element that entered the viewport, or some way to identify where on the viewport the element is located. + type: string + plan_tier: + description: Which tier of plan the user is on [Free, Plus] + type: string + cta_button: click: type: event From 10716eaddbc91f2ed2cc879a9d5fcefe4d4dc5c4 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Thu, 11 Jul 2024 17:15:30 -0700 Subject: [PATCH 063/137] fix: cannot use first() on update() --- src/db/tables/subscribers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/db/tables/subscribers.js b/src/db/tables/subscribers.js index d09c82e5b5b..c742a3c7dea 100644 --- a/src/db/tables/subscribers.js +++ b/src/db/tables/subscribers.js @@ -162,7 +162,7 @@ async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, sessi // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy /* c8 ignore start */ async function updateFxATokens (subscriber, fxaAccessToken, fxaRefreshToken, sessionExpiresAt) { - return await knex('subscribers') + const updateResp = await knex('subscribers') .where('id', '=', subscriber.id) .update({ fxa_access_token: fxaAccessToken, @@ -172,8 +172,8 @@ async function updateFxATokens (subscriber, fxaAccessToken, fxaRefreshToken, ses // even if it's not typed as a JS date object: updated_at: knex.fn.now(), }) - .returning('*') - .first() || null; + .returning('*'); + return (Array.isArray(updateResp) && updateResp.length > 0) ? updateResp[0] : null; } /* c8 ignore stop */ From 65671aca5d355c8b7ed977a8b83397c1f3464e10 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Thu, 11 Jul 2024 18:25:50 -0700 Subject: [PATCH 064/137] mntor-3140,3141 --- .env | 3 +- .env.local.example | 3 +- .github/workflows/e2e_cron.yml | 3 +- .github/workflows/e2e_pr.yml | 3 +- .../(authenticated)/e2e/ResetButton.tsx | 45 ++++++ .../(redesign)/(authenticated)/e2e/page.tsx | 38 +++++ .../api/mock/hibp/mockData/mockBreaches.json | 3 +- src/app/api/mock/onerep/config/config.ts | 6 +- .../api/mock/onerep/mockData/mockUser.json | 8 +- .../profiles/[profileId]/scans/route.ts | 46 +++--- .../{clearTestData => resetTestData}/route.ts | 25 ++-- src/app/api/utils/mockUtils.ts | 9 ++ src/app/functions/server/onerep.ts | 4 +- src/app/functions/universal/mock.ts | 2 +- src/e2e/pages/dashBoardPage.ts | 3 + src/e2e/specs/dashboard.spec.ts | 140 +++++++++++++++++- src/e2e/utils/helpers.ts | 29 +++- src/utils/hibp.js | 3 +- 18 files changed, 314 insertions(+), 59 deletions(-) create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/e2e/ResetButton.tsx create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/e2e/page.tsx rename src/app/api/mock/{clearTestData => resetTestData}/route.ts (72%) diff --git a/.env b/.env index e7538055d1d..06a5b7c2685 100755 --- a/.env +++ b/.env @@ -111,7 +111,8 @@ E2E_TEST_BASE_URL= E2E_TEST_ACCOUNT_EMAIL= E2E_TEST_ACCOUNT_PASSWORD= -E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES= +E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS= +E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS= E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED= E2E_TEST_PAYPAL_LOGIN = diff --git a/.env.local.example b/.env.local.example index 74190266b0b..503bdb0a524 100644 --- a/.env.local.example +++ b/.env.local.example @@ -26,7 +26,8 @@ DATABASE_URL="postgres://blurts:blurts@localhost:5432/blurts" # Set these to be able to run end-to-end tests locally E2E_TEST_ACCOUNT_EMAIL= E2E_TEST_ACCOUNT_PASSWORD= -E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES= +E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS= +E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS= E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED= E2E_TEST_PAYPAL_LOGIN = E2E_TEST_PAYPAL_PASSWORD = diff --git a/.github/workflows/e2e_cron.yml b/.github/workflows/e2e_cron.yml index 1e4b93b7d8e..d8f1b39e412 100644 --- a/.github/workflows/e2e_cron.yml +++ b/.github/workflows/e2e_cron.yml @@ -52,7 +52,8 @@ jobs: E2E_TEST_BASE_URL: ${{ secrets.E2E_TEST_BASE_URL }} E2E_TEST_ACCOUNT_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL }} E2E_TEST_ACCOUNT_PASSWORD: ${{ secrets.E2E_TEST_ACCOUNT_PASSWORD }} - E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES }} + E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS }} + E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS: : ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS }} E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED }} E2E_TEST_PAYPAL_LOGIN: ${{ secrets.E2E_TEST_PAYPAL_LOGIN }} E2E_TEST_PAYPAL_PASSWORD: ${{ secrets.E2E_TEST_PAYPAL_PASSWORD }} diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 8dca4b381d7..386e62767e2 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -66,7 +66,8 @@ jobs: E2E_TEST_ENV: ${{ inputs.environment != null && inputs.environment || 'local' }} E2E_TEST_BASE_URL: ${{ secrets.E2E_TEST_BASE_URL }} E2E_TEST_ACCOUNT_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL }} - E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES }} + E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS }} + E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS: : ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS }} E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED }} E2E_TEST_ACCOUNT_PASSWORD: ${{ secrets.E2E_TEST_ACCOUNT_PASSWORD }} E2E_TEST_PAYPAL_LOGIN: ${{ secrets.E2E_TEST_PAYPAL_LOGIN }} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/e2e/ResetButton.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/e2e/ResetButton.tsx new file mode 100644 index 00000000000..f1e6a8c4ce7 --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/e2e/ResetButton.tsx @@ -0,0 +1,45 @@ +/* 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/. */ + +"use client"; + +import { useState } from "react"; + +interface clearProps { + hibp: boolean; + onerep: boolean; +} + +const ResetButton = (props: clearProps) => { + const [isResolved, setIsResolved] = useState(false); + + const { hibp, onerep } = props; + + const makeClearReq = async () => { + const param1 = `${hibp ? "hibp=true" : ""}`; + const param2 = `${onerep ? "onerep=true" : ""}`; + let delim = ""; + if (param1 && param2) delim = "&"; + const params = param1 + delim + param2; + return await fetch(`/api/mock/resetTestData?` + params); + }; + + const handleClick = () => { + void makeClearReq().then((res) => { + console.log( + `Request to clear data reached! Status: ${res.status}, ${res.ok ? "ok" : "not ok."}`, + ); + setIsResolved(true); + }); + }; + + return ( +
+ + {isResolved &&
Request was successful!
} +
+ ); +}; + +export default ResetButton; diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/e2e/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/e2e/page.tsx new file mode 100644 index 00000000000..84d96349291 --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/e2e/page.tsx @@ -0,0 +1,38 @@ +/* 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 { isTestEmail } from "../../../../api/utils/mockUtils"; +import { getServerSession } from "../../../../functions/server/getServerSession"; +import NotFound from "../../../../not-found"; +import ResetButton from "./ResetButton"; + +const DynamicConfig = async ({ + searchParams, +}: { + searchParams?: { [key: string]: string | string[] | undefined }; +}) => { + const session = await getServerSession(); + + if (!session) { + throw new Error("Session not found"); + } + + const showNotFound = !isTestEmail(session.user.email); + + // Check 'hibp' and 'onerep' in the searchParams object and compare to 'true' as string + const hibp = (searchParams?.hibp || "false") === "true"; + const onerep = (searchParams?.onerep || "false") === "true"; + + return ( +
+ {showNotFound ? ( + + ) : ( + + )} +
+ ); +}; + +export default DynamicConfig; diff --git a/src/app/api/mock/hibp/mockData/mockBreaches.json b/src/app/api/mock/hibp/mockData/mockBreaches.json index a773f3d670f..c46b5bf6540 100644 --- a/src/app/api/mock/hibp/mockData/mockBreaches.json +++ b/src/app/api/mock/hibp/mockData/mockBreaches.json @@ -23,7 +23,8 @@ "AshleyMadison", "Adobe" ], - "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES": [], + "E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS": ["Adobe", "LinkedIn"], + "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS": [], "E2E_TEST_ACCOUNT_EMAIL_UNUSED": [ "000webhost", "AndroidForums", diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index 8b5a0399b3d..0d3638ce610 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -6,6 +6,7 @@ import { BinaryLike, createHash } from "crypto"; import { StateAbbr } from "../../../../../utils/states"; import MockUser from "../mockData/mockUser.json"; import { computeSha1First6, hashToEmailKeyMap } from "../../../utils/mockUtils"; +import { getLatestOnerepScan } from "../../../../../db/tables/onerep_scans"; export interface Broker { id: number; @@ -148,13 +149,14 @@ export function MOCK_ONEREP_OBJECT_LINKS( }; } -export function MOCK_ONEREP_BROKERS( +export async function MOCK_ONEREP_BROKERS( profileId: number, page: string, perPage: string, email: string, ) { - const scanId = MOCK_ONEREP_SCAN_ID(profileId); + let scanId = (await getLatestOnerepScan(profileId))?.onerep_scan_id; + if (!scanId) scanId = MOCK_ONEREP_SCAN_ID(profileId); const mockMeta = MOCK_ONEREP_OBJECT_META(page); const mockLinks = MOCK_ONEREP_OBJECT_LINKS(profileId, page, perPage); diff --git a/src/app/api/mock/onerep/mockData/mockUser.json b/src/app/api/mock/onerep/mockData/mockUser.json index 690292f9bac..7b4dc487a71 100644 --- a/src/app/api/mock/onerep/mockData/mockUser.json +++ b/src/app/api/mock/onerep/mockData/mockUser.json @@ -359,7 +359,8 @@ "updated_at": "2024-01-01T00:00:00Z" } ], - "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES": [], + "E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS": [], + "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS": [], "E2E_TEST_ACCOUNT_EMAIL": [ { "age": 28, @@ -731,8 +732,9 @@ "SCANS_LIST": { "default": [{}], "E2E_TEST_ACCOUNT_EMAIL": [{}], - "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES": [{}], "E2E_TEST_ACCOUNT_EMAIL_UNUSED": [{}], - "E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED": [{}] + "E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED": [{}], + "E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS": [{}], + "E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS": [{}] } } diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index 3f4664de4f5..35a843d771b 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -10,7 +10,10 @@ import { MOCK_ONEREP_TIME, } from "../../../config/config"; import { errorIfProduction } from "../../../../../utils/errorThrower"; -import { getEmailForProfile } from "../../../../../../../db/tables/onerep_scans"; +import { + getEmailForProfile, + getLatestOnerepScan, +} from "../../../../../../../db/tables/onerep_scans"; import { computeSha1First6, hashToEmailKeyMap, @@ -40,7 +43,7 @@ interface MockScanOptionals { const mockScans = mockUser.SCANS_LIST as ScansMap; -export function POST( +export async function POST( _: NextRequest, { params }: { params: { profileId: number } }, ) { @@ -53,17 +56,20 @@ export function POST( return NextResponse.json({ error: "Invalid profile ID" }); } - const scanId = MOCK_ONEREP_SCAN_ID(profileId); + const latestScan = await getLatestOnerepScan(profileId); + if (latestScan) return NextResponse.json([latestScan]); + + const mockScanId = MOCK_ONEREP_SCAN_ID(profileId); const mockResponse = { - id: scanId, + id: mockScanId, profile_id: profileId, status: "finished", reason: "manual", created_at: MOCK_ONEREP_TIME(), updated_at: MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, - }; + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${mockScanId}`, + } as MockScan; return NextResponse.json(mockResponse); } @@ -92,19 +98,23 @@ export async function GET( const dataKey = hashToEmailKeyMap[emailHash] || "default"; const data = mockScans[dataKey]; + const latestScan = await getLatestOnerepScan(profileId); + const responseData = { - data: data.map( - (scan) => - ({ - id: scandId, - profile_id: profileId, - status: scan.status || "finished", - reason: scan.reason || "manual", - created_at: scan.created_at || MOCK_ONEREP_TIME(), - updated_at: scan.updated_at || MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scandId}`, - }) as MockScan, - ), + data: latestScan + ? [latestScan] + : data.map( + (scan) => + ({ + id: scandId, + profile_id: profileId, + status: scan.status || "finished", + reason: scan.reason || "manual", + created_at: scan.created_at || MOCK_ONEREP_TIME(), + updated_at: scan.updated_at || MOCK_ONEREP_TIME(), + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scandId}`, + }) as MockScan, + ), links: links, meta: meta, }; diff --git a/src/app/api/mock/clearTestData/route.ts b/src/app/api/mock/resetTestData/route.ts similarity index 72% rename from src/app/api/mock/clearTestData/route.ts rename to src/app/api/mock/resetTestData/route.ts index 3d6b5add05d..89ef9eeec7a 100644 --- a/src/app/api/mock/clearTestData/route.ts +++ b/src/app/api/mock/resetTestData/route.ts @@ -2,7 +2,7 @@ * 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 { NextRequest, NextResponse } from "next/server"; import { getServerSession } from "../../../functions/server/getServerSession"; import { errorIfProduction, @@ -18,17 +18,9 @@ import { deleteSomeScansForProfile, } from "../../../../db/tables/onerep_scans"; import { logger } from "../../../functions/server/logging"; +import { isTestEmail } from "../../utils/mockUtils"; -function isTestEmail(email: string) { - if (!email) return false; - const testEmailKeys = Object.keys(process.env).filter((key) => - key.startsWith("E2E_TEST_ACCOUNT_EMAIL"), - ); - const testEmails = testEmailKeys.map((key) => process.env[key]); - return testEmails.includes(email); -} - -export async function GET() { +export async function GET(req: NextRequest) { const prodError = errorIfProduction(); if (prodError) return prodError; @@ -38,12 +30,17 @@ export async function GET() { if (!session || !email || !isTestEmail(email) || !subscriberId) return unauthError(); + const hibp = req.nextUrl.searchParams.get("hibp") === "true"; + const onerep = req.nextUrl.searchParams.get("onerep") === "true"; + const onerepProfileId = await getOnerepProfileId(subscriberId); if (!onerepProfileId) return internalServerError("Unable to fetch OneRep profile ID"); - await deleteScanResultsForProfile(onerepProfileId); - await deleteSomeScansForProfile(onerepProfileId, 1); - await unresolveAllBreaches(onerepProfileId); + if (onerep) { + await deleteScanResultsForProfile(onerepProfileId); + await deleteSomeScansForProfile(onerepProfileId, 1); + } + if (hibp) await unresolveAllBreaches(onerepProfileId); logger.info( "Mock OneRep endpoint: attempted to delete all but 1 scans, attempted to unresolve all breaches", ); diff --git a/src/app/api/utils/mockUtils.ts b/src/app/api/utils/mockUtils.ts index 341e837e001..69c19b93396 100644 --- a/src/app/api/utils/mockUtils.ts +++ b/src/app/api/utils/mockUtils.ts @@ -29,3 +29,12 @@ export const hashToEmailKeyMap = (() => { }); return mapping; })(); + +export function isTestEmail(email: string) { + if (!email) return false; + const testEmailKeys = Object.keys(process.env).filter((key) => + key.startsWith("E2E_TEST_ACCOUNT_EMAIL"), + ); + const testEmails = testEmailKeys.map((key) => process.env[key]); + return testEmails.includes(email); +} diff --git a/src/app/functions/server/onerep.ts b/src/app/functions/server/onerep.ts index ddb8eb9edaf..f7cc8f1b80a 100644 --- a/src/app/functions/server/onerep.ts +++ b/src/app/functions/server/onerep.ts @@ -16,7 +16,7 @@ import { } from "../../../db/tables/onerep_scans"; import { RemovalStatus } from "../universal/scanResult.js"; import { logger } from "./logging"; -import { isUsingMockONEREPEndpoint } from "../universal/mock.ts"; +import { isUsingMockONEREPndpoint } from "../universal/mock.ts"; export const monthlyScansQuota = parseInt( (process.env.MONTHLY_SCANS_QUOTA as string) ?? "0", @@ -334,7 +334,7 @@ export async function listScanResults( }> = {}, ): Promise { let mockEmail = ""; - if (isUsingMockONEREPEndpoint()) { + if (isUsingMockONEREPndpoint()) { mockEmail = (await getEmailForProfile(profileId)) || mockEmail; } const queryParams = new URLSearchParams({ diff --git a/src/app/functions/universal/mock.ts b/src/app/functions/universal/mock.ts index 9b610658505..46db7685223 100644 --- a/src/app/functions/universal/mock.ts +++ b/src/app/functions/universal/mock.ts @@ -6,7 +6,7 @@ export function isUsingMockHIBPEndpoint() { return process.env.HIBP_KANON_API_ROOT?.includes("api/mock") as boolean; } -export function isUsingMockONEREPEndpoint() { +export function isUsingMockONEREPndpoint() { return process.env.ONEREP_API_BASE?.includes("api/mock") as boolean; } diff --git a/src/e2e/pages/dashBoardPage.ts b/src/e2e/pages/dashBoardPage.ts index 25f5939df65..3c54c4750e0 100644 --- a/src/e2e/pages/dashBoardPage.ts +++ b/src/e2e/pages/dashBoardPage.ts @@ -82,6 +82,7 @@ export class DashboardPage { readonly overviewCard: Locator; readonly overviewCardSummary: Locator; readonly overviewCardFindings: Locator; + readonly chartSvgExposuresCount: Locator; readonly upsellScreenButton: Locator; readonly urlRegex: RegExp; @@ -249,6 +250,8 @@ export class DashboardPage { this.overviewCardFindings = page.locator( "[aria-label='Dashboard summary'] > div > h3", ); + this.chartSvgExposuresCount = + this.overviewCard.locator("figure > div > svg"); //regex this.urlRegex = /\/dashboard\/(fixed|action-needed)\/?/; diff --git a/src/e2e/specs/dashboard.spec.ts b/src/e2e/specs/dashboard.spec.ts index a72dba31d56..57ddf04184e 100644 --- a/src/e2e/specs/dashboard.spec.ts +++ b/src/e2e/specs/dashboard.spec.ts @@ -11,7 +11,12 @@ import { clickOnATagCheckDomain, escapeRegExp, forceLoginAs, + resetTestData, } from "../utils/helpers.js"; +import { + isUsingMockHIBPEndpoint, + isUsingMockONEREPndpoint, +} from "../../app/functions/universal/mock.js"; // bypass login test.use({ storageState: "./e2e/storageState.json" }); @@ -720,20 +725,24 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Navigation`, ( }); test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Data Breaches`, () => { - test.beforeEach(async ({ landingPage, page, authPage }) => { - const emailToUse = process.env - .E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED as string; - const pwdToUse = process.env.E2E_TEST_ACCOUNT_PASSWORD as string; - expect(emailToUse).not.toBeUndefined(); - expect(pwdToUse).not.toBeUndefined(); - await forceLoginAs(emailToUse, pwdToUse, page, landingPage, authPage); - }); + test.use({ storageState: { cookies: [], origins: [] } }); test("Verify that the High risk data breaches step is displayed correctly", async ({ dashboardPage, dataBrokersPage, page, + landingPage, + authPage, }) => { + const emailToUse = process.env.E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED!; + const pwdToUse = process.env.E2E_TEST_ACCOUNT_PASSWORD!; + expect(emailToUse).not.toBeUndefined(); + expect(pwdToUse).not.toBeUndefined(); + await forceLoginAs(emailToUse, pwdToUse, page, landingPage, authPage); + + if (isUsingMockHIBPEndpoint()) await resetTestData(page, true, false); + if (isUsingMockONEREPndpoint()) await resetTestData(page, false, true); + test.info().annotations.push({ type: "testrail", description: @@ -755,4 +764,119 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Data Breaches` highRiskDataBreachLi.locator("div").getByText("High risk data breaches"), ).toBeVisible(); }); + + test("Verify that the dashboard is displayed correctly for users with no scan results and no breaches", async ({ + dashboardPage, + page, + authPage, + landingPage, + }) => { + if (!isUsingMockHIBPEndpoint() || !isUsingMockONEREPndpoint()) return; + + test.info().annotations.push({ + type: "testrail", + description: + "https://testrail.stage.mozaws.net/index.php?/cases/view/2463610", + }); + + const emailToUse = + process.env.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS!; + const pwdToUse = process.env.E2E_TEST_ACCOUNT_PASSWORD!; + expect(emailToUse).not.toBeUndefined(); + expect(pwdToUse).not.toBeUndefined(); + await forceLoginAs(emailToUse, pwdToUse, page, landingPage, authPage); + await resetTestData(page, true, true); + await dashboardPage.open(); + + await expect(dashboardPage.overviewCard).toBeVisible(); + const textArea = dashboardPage.overviewCard.locator("section"); + await expect(textArea.getByText(/No exposures found/)).toBeVisible(); + await expect( + textArea.getByText( + /Great news! We searched all known data breaches and .\d+. data broker sites that sell personal info and found no exposures\./, + ), + ).toBeVisible(); + await expect(textArea.getByRole("button")).toBeVisible(); + expect(await dashboardPage.overviewCard.locator("svg").count()).toBe(5); + expect(await dashboardPage.overviewCard.locator("circle").count()).toBe(4); + await expect( + dashboardPage.chartSvgExposuresCount.getByText("Exposures"), + ).toBeVisible(); + await expect( + dashboardPage.chartSvgExposuresCount.getByText("0"), + ).toBeVisible(); + + await expect(dashboardPage.exposuresHeading).toBeVisible(); + expect(await dashboardPage.exposuresHeading.textContent()).toBe( + "View all sites where your info is exposed", + ); + + const noExpFoundMsg = page + .locator("div > strong") + .getByText("No exposures found"); + await expect(noExpFoundMsg).toBeVisible(); + }); + + test("Verify that the dashboard is displayed correctly for users with no scan results and with data breaches", async ({ + dashboardPage, + page, + authPage, + landingPage, + }) => { + if (!isUsingMockHIBPEndpoint() || !isUsingMockONEREPndpoint()) return; + + test.info().annotations.push({ + type: "testrail", + description: + "https://testrail.stage.mozaws.net/index.php?/cases/view/2463611", + }); + + const emailToUse = process.env.E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS!; + const pwdToUse = process.env.E2E_TEST_ACCOUNT_PASSWORD!; + expect(emailToUse).not.toBeUndefined(); + expect(pwdToUse).not.toBeUndefined(); + await forceLoginAs(emailToUse, pwdToUse, page, landingPage, authPage); + await resetTestData(page, true, true); + await dashboardPage.open(); + + // Assertions for the overview card + await expect(dashboardPage.overviewCard).toBeVisible(); + await expect( + page.getByText( + /You still have .\d+. exposures left to fix. Keep going and protect yourself\. We.ll guide you step-by-step\./, + ), + ).toBeVisible(); + await expect(dashboardPage.upsellScreenButton).toBeVisible(); + + // Chart reflecting results + await expect( + dashboardPage.chartSvgExposuresCount.getByText("Exposures"), + ).toBeVisible(); + await expect( + dashboardPage.chartSvgExposuresCount.getByText(/\d+/), + ).toBeVisible(); + + // Text above exposures list + await expect( + page.getByText("View all sites where your info is exposed"), + ).toBeVisible(); + await expect( + page.getByText( + /We found your information exposed .\d+. times over .\d+. data breaches and .0. data broker sites that are selling your personal info\./, + ), + ).toBeVisible(); + + // Exposures list + const exposureList = page.locator('[class*="exposureList"]'); + await expect(exposureList).toBeVisible(); + expect(await exposureList.locator("li").count()).toBeGreaterThan(0); + + // Click the "Let's keep going" button and check the redirection + await dashboardPage.upsellScreenButton.click(); + await page.waitForURL(/.*\/user\/dashboard\/fix.*/); + const dataBrokerFixed = page + .locator('[class*="FixNavigation"][class*="isCompleted"]') + .getByText("Data broker profiles"); + await expect(dataBrokerFixed).toBeVisible(); + }); }); diff --git a/src/e2e/utils/helpers.ts b/src/e2e/utils/helpers.ts index 37fd10681ec..96be1583a08 100644 --- a/src/e2e/utils/helpers.ts +++ b/src/e2e/utils/helpers.ts @@ -223,9 +223,30 @@ export const forceLoginAs = async ( await expect(page).toHaveURL(/.*\/user\/dashboard.*/); }; -export const clearTestData = async (page: Page) => { - const url = new URL(page.url()); - await page.goto(url.host + "/api/mock/clearTestData"); - await page.waitForURL(/.*\/api\/mock\/clearTestData.*/); +export const resetTestData = async ( + page: Page, + hibp: boolean, + onerep: boolean, +) => { + const baseUrl = process.env.SERVER_URL!; + const param1 = `${hibp ? "hibp=true" : ""}`; + const param2 = `${onerep ? "onerep=true" : ""}`; + let delim = ""; + if (param1 && param2) delim = "&"; + const params = param1 + delim + param2; + await page.goto(baseUrl + "/e2e?" + params); + await page.waitForURL(/.*\/e2e*/); + + const clearDataButton = page.locator("button", { hasText: "Clear Data" }); + await expect(clearDataButton).toBeVisible(); + await clearDataButton.click(); + + const selectorQuery = '//div[contains(text(), "Request was successful")]'; + + await page.waitForSelector(selectorQuery); + + const successMessage = page.locator(selectorQuery); + await expect(successMessage).toBeVisible(); + await page.goBack(); }; diff --git a/src/utils/hibp.js b/src/utils/hibp.js index c018cf6fe8e..4f7b7ce461e 100644 --- a/src/utils/hibp.js +++ b/src/utils/hibp.js @@ -301,11 +301,10 @@ async function getBreachesForEmail(sha1, allBreaches, includeSensitive = false, } if (isUsingMockHIBPEndpoint()) { let mockDataBreaches = response[0]; - const res = allBreaches.filter(breach => mockDataBreaches.websites.includes(breach.Name)).sort((a, b) => { + return allBreaches.filter(breach => mockDataBreaches.websites.includes(breach.Name)).sort((a, b) => { // @ts-ignore TODO: Turn dates into a number return new Date(b.AddedDate) - new Date(a.AddedDate) }) - return res; } // Parse response body, format: // [ From f5df18d9917eef498ee729879c68a8cf2f65628c Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Thu, 11 Jul 2024 23:11:31 -0700 Subject: [PATCH 065/137] marked eligible tests as smoke and changed the smoke job env. --- .github/workflows/e2e_pr.yml | 5 ++- src/app/api/mock/onerep/config/config.ts | 3 +- .../[profileId]/scans/[scanId]/route.ts | 32 +++++++++++++------ .../profiles/[profileId]/scans/route.ts | 17 +++++++--- src/app/api/mock/onerep/scan-results/route.ts | 11 +++++-- src/e2e/pages/purchasePage.ts | 4 +-- src/e2e/specs/auth.spec.ts | 5 +-- src/e2e/specs/breachResolution.spec.ts | 2 +- src/e2e/specs/dashboard.spec.ts | 14 ++++---- src/e2e/specs/landing.spec.ts | 22 +++---------- src/e2e/specs/settings.spec.ts | 2 +- src/e2e/utils/helpers.ts | 7 +++- 12 files changed, 70 insertions(+), 54 deletions(-) diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 386e62767e2..6b5470d4a6f 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -63,7 +63,7 @@ jobs: run: npm run e2e:smoke timeout-minutes: 10 env: - E2E_TEST_ENV: ${{ inputs.environment != null && inputs.environment || 'local' }} + E2E_TEST_ENV: 'local' E2E_TEST_BASE_URL: ${{ secrets.E2E_TEST_BASE_URL }} E2E_TEST_ACCOUNT_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL }} E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS }} @@ -75,11 +75,14 @@ jobs: ADMINS: ${{ secrets.ADMINS }} OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }} ONEREP_API_KEY: ${{ secrets.ONEREP_API_KEY }} + ONEREP_API_BASE: ${{ secrets.ONEREP_API_BASE }} NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }} DATABASE_URL: postgres://postgres:postgres@localhost:5432/blurts HIBP_KANON_API_TOKEN: ${{ secrets.HIBP_KANON_API_TOKEN }} HIBP_API_TOKEN: ${{ secrets.HIBP_API_TOKEN }} + HIBP_KANON_API_ROOT: ${{ secrets.HIBP_KANON_API_ROOT }} + HIBP_API_ROOT: ${{ secrets.HIBP_API_ROOT }} # Our tests are currently set up to expect accounts to act like # old user accounts, so let's pretend they all are: BROKER_SCAN_RELEASE_DATE: "3000-12-31" diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index 0d3638ce610..dcd1d219032 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -159,7 +159,6 @@ export async function MOCK_ONEREP_BROKERS( if (!scanId) scanId = MOCK_ONEREP_SCAN_ID(profileId); const mockMeta = MOCK_ONEREP_OBJECT_META(page); const mockLinks = MOCK_ONEREP_OBJECT_LINKS(profileId, page, perPage); - const idStart = MOCK_ONEREP_ID_START(profileId); const idStartDataBroker = MOCK_ONEREP_DATABROKER_ID_START(profileId); @@ -195,8 +194,8 @@ export async function MOCK_ONEREP_BROKERS( const responseData = { data: res, - links: mockLinks, meta: mockMeta, + links: mockLinks, }; return responseData; diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts index c1437e21bb2..721df9193ec 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts @@ -2,11 +2,12 @@ * 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 { getLatestOnerepScan } from "../../../../../../../../db/tables/onerep_scans"; import { errorIfProduction } from "../../../../../../utils/errorThrower"; import { MOCK_ONEREP_TIME } from "../../../../config/config"; import { NextRequest, NextResponse } from "next/server"; -export function GET( +export async function GET( _: NextRequest, { params }: { params: { profileId: number; scanId: number } }, ) { @@ -16,15 +17,26 @@ export function GET( const profileId: number = params.profileId; const scanId: number = params.scanId; - const responseData = { - id: scanId, - profile_id: profileId, - status: "finished", - reason: "manual", - created_at: MOCK_ONEREP_TIME(), - updated_at: MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, - }; + const latestScan = await getLatestOnerepScan(profileId); + + const responseData = latestScan + ? { + id: latestScan.id, + profileId: latestScan.onerep_profile_id, + status: latestScan.onerep_scan_status, + created_at: latestScan.created_at, + updated_at: latestScan.updated_at, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, + } + : { + id: scanId, + profile_id: profileId, + status: "finished", + reason: "manual", + created_at: MOCK_ONEREP_TIME(), + updated_at: MOCK_ONEREP_TIME(), + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, + }; return NextResponse.json(responseData); } diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index 35a843d771b..eb1928e7878 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -87,7 +87,7 @@ export async function GET( return NextResponse.json({ error: "Invalid profile ID" }); } - const scandId = MOCK_ONEREP_SCAN_ID(profileId); + const scanId = MOCK_ONEREP_SCAN_ID(profileId); const links = MOCK_ONEREP_OBJECT_LINKS(profileId); const meta = MOCK_ONEREP_OBJECT_META(profileId); @@ -102,17 +102,26 @@ export async function GET( const responseData = { data: latestScan - ? [latestScan] + ? [ + { + id: latestScan.onerep_scan_id, + profileId: latestScan.onerep_profile_id, + status: latestScan.onerep_scan_status, + created_at: latestScan.created_at, + updated_at: latestScan.updated_at, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${latestScan.onerep_scan_id}`, + }, + ] : data.map( (scan) => ({ - id: scandId, + id: scanId, profile_id: profileId, status: scan.status || "finished", reason: scan.reason || "manual", created_at: scan.created_at || MOCK_ONEREP_TIME(), updated_at: scan.updated_at || MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scandId}`, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, }) as MockScan, ), links: links, diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts index 93a38373199..1774819ed98 100644 --- a/src/app/api/mock/onerep/scan-results/route.ts +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -6,7 +6,7 @@ import { errorIfProduction } from "../../../utils/errorThrower"; import { MOCK_ONEREP_BROKERS } from "../config/config"; import { NextRequest, NextResponse } from "next/server"; -export function GET(req: NextRequest) { +export async function GET(req: NextRequest) { const prodError = errorIfProduction(); if (prodError) return prodError; @@ -22,7 +22,12 @@ export function GET(req: NextRequest) { ); } - return NextResponse.json( - MOCK_ONEREP_BROKERS(Number(profileId), page, perPage, email), + const res = await MOCK_ONEREP_BROKERS( + Number(profileId), + page, + perPage, + email, ); + + return NextResponse.json(res); } diff --git a/src/e2e/pages/purchasePage.ts b/src/e2e/pages/purchasePage.ts index e1ead13b02e..4e1899953c1 100644 --- a/src/e2e/pages/purchasePage.ts +++ b/src/e2e/pages/purchasePage.ts @@ -120,9 +120,7 @@ export class PurchasePage { const planDetails = removeUnicodeChars( (await this.planDetails.textContent()) as string, ); - expect(planDetails).toContain( - `${process.env.E2E_TEST_ENV === "prod" ? "yearly" : "every 2 months"}`, - ); + expect(planDetails).toContain("yearly"); } async gotoPurchaseFromDashboard( diff --git a/src/e2e/specs/auth.spec.ts b/src/e2e/specs/auth.spec.ts index 92959b012d7..a820b367893 100644 --- a/src/e2e/specs/auth.spec.ts +++ b/src/e2e/specs/auth.spec.ts @@ -27,10 +27,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Authentication flow verification @s await authPage.signUp(randomEmail, page); // assert successful login - const successUrl = - process.env.E2E_TEST_ENV === "local" - ? "/user/dashboard" - : "/user/welcome"; + const successUrl = "/user/welcome"; expect(page.url()).toBe(`${process.env.E2E_TEST_BASE_URL}${successUrl}`); await testInfo.attach( diff --git a/src/e2e/specs/breachResolution.spec.ts b/src/e2e/specs/breachResolution.spec.ts index e17370ed457..11dadc4d928 100644 --- a/src/e2e/specs/breachResolution.spec.ts +++ b/src/e2e/specs/breachResolution.spec.ts @@ -6,7 +6,7 @@ import { test, expect } from "../fixtures/basePage.js"; // bypass login test.use({ storageState: "./e2e/storageState.json" }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Headers`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Headers @smoke`, () => { test("Verify that the site header is displayed correctly for signed in users", async ({ dataBreachPage, }) => { diff --git a/src/e2e/specs/dashboard.spec.ts b/src/e2e/specs/dashboard.spec.ts index 57ddf04184e..59956149779 100644 --- a/src/e2e/specs/dashboard.spec.ts +++ b/src/e2e/specs/dashboard.spec.ts @@ -261,7 +261,7 @@ test.describe.skip( }, ); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Content`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Content @smoke`, () => { test.beforeEach(async ({ dashboardPage, page }) => { await dashboardPage.open(); @@ -341,7 +341,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Content`, () = }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Payment`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Payment`, () => { test.beforeEach(async ({ dashboardPage, page }) => { await dashboardPage.open(); @@ -378,7 +378,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Payment`, () }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Breaches Scan, Continuous Protection, Data Profile Actions`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Breaches Scan, Continuous Protection, Data Profile Actions @smoke`, () => { test.use({ storageState: { cookies: [], origins: [] } }); test.beforeEach(async ({ landingPage, page, authPage, welcomePage }) => { @@ -456,7 +456,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Breaches Scan, }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Overview Card`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Overview Card @smoke`, () => { test.beforeEach(async ({ dashboardPage, page }) => { await dashboardPage.open(); @@ -609,7 +609,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Overview Card` }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Footer`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Footer @smoke`, () => { test.beforeEach(async ({ dashboardPage, page }) => { await dashboardPage.open(); try { @@ -673,7 +673,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Footer`, () => }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Navigation`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Navigation @smoke`, () => { test.beforeEach(async ({ dashboardPage, page }) => { await dashboardPage.open(); @@ -724,7 +724,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Navigation`, ( }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Data Breaches`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Data Breaches @smoke`, () => { test.use({ storageState: { cookies: [], origins: [] } }); test("Verify that the High risk data breaches step is displayed correctly", async ({ diff --git a/src/e2e/specs/landing.spec.ts b/src/e2e/specs/landing.spec.ts index acb202d4656..141dfd12707 100644 --- a/src/e2e/specs/landing.spec.ts +++ b/src/e2e/specs/landing.spec.ts @@ -9,7 +9,7 @@ import { } from "../utils/helpers.js"; test.describe.configure({ mode: "parallel" }); -test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page content`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page content @smoke`, () => { test.beforeEach(async ({ landingPage }) => { await landingPage.open(); }); @@ -211,7 +211,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page content`, ( }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionality - without existing Account`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionality - without existing Account @smoke`, () => { test.beforeEach(async ({ landingPage }) => { await landingPage.open(); }); @@ -281,7 +281,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionality - with existing account`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionality - with existing account @smoke`, () => { test.beforeEach(async ({ landingPage }) => { await landingPage.open(); }); @@ -309,13 +309,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali await authPage.enterPassword(); // verify dashboard redirect - const successUrl = - process.env.E2E_TEST_BASE_URL + - `${ - process.env.E2E_TEST_ENV === "local" - ? "/user/welcome" - : "/user/dashboard" - }`; + const successUrl = process.env.E2E_TEST_BASE_URL + "/user/dashboard"; expect(page.url()).toBe(successUrl); }); @@ -336,13 +330,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali await authPage.enterPassword(); // verify dashboard redirect - const successUrl = - process.env.E2E_TEST_BASE_URL + - `${ - process.env.E2E_TEST_ENV === "local" - ? "/user/welcome" - : "/user/dashboard" - }`; + const successUrl = process.env.E2E_TEST_BASE_URL + "/user/dashboard"; expect(page.url()).toBe(successUrl); }); }); diff --git a/src/e2e/specs/settings.spec.ts b/src/e2e/specs/settings.spec.ts index e86390f5616..6377a26f83c 100644 --- a/src/e2e/specs/settings.spec.ts +++ b/src/e2e/specs/settings.spec.ts @@ -6,7 +6,7 @@ import { test, expect } from "../fixtures/basePage.js"; // bypass login test.use({ storageState: "./e2e/storageState.json" }); -test.describe(`${process.env.E2E_TEST_ENV} Settings Page`, () => { +test.describe(`${process.env.E2E_TEST_ENV} Settings Page @smoke`, () => { test("Verify settings page loads", async ({ settingsPage }) => { // should go directly to data breach page await settingsPage.open(); diff --git a/src/e2e/utils/helpers.ts b/src/e2e/utils/helpers.ts index 96be1583a08..f49f90da57d 100644 --- a/src/e2e/utils/helpers.ts +++ b/src/e2e/utils/helpers.ts @@ -170,7 +170,9 @@ export const clickOnATagCheckDomain = async ( page: Page, ) => { if (typeof host === "string") - host = new RegExp(escapeRegExp(host.replace(/^(https?:\/\/)/, ""))); + host = new RegExp( + escapeRegExp(host.replace(/^(https?:\/\/)/, "").replace(/:\d+$/, "")), + ); if (typeof path === "string") path = new RegExp(".*" + path + ".*"); const href = await aTag.getAttribute("href"); @@ -206,6 +208,9 @@ export const forceLoginAs = async ( await route.abort(); }); await page.context().clearCookies(); + await page + .context() + .addInitScript({ content: "window.localStorage.clear()" }); await landingPage.open(); await landingPage.goToSignIn(); let visible = true; From 5deb435ab86462d473c70410f40a7bb704df4230 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Thu, 11 Jul 2024 23:18:14 -0700 Subject: [PATCH 066/137] small .yaml adjustment --- .github/workflows/e2e_pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 6b5470d4a6f..070a86f0e66 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -67,7 +67,7 @@ jobs: E2E_TEST_BASE_URL: ${{ secrets.E2E_TEST_BASE_URL }} E2E_TEST_ACCOUNT_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL }} E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS }} - E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS: : ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS }} + E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS }} E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED }} E2E_TEST_ACCOUNT_PASSWORD: ${{ secrets.E2E_TEST_ACCOUNT_PASSWORD }} E2E_TEST_PAYPAL_LOGIN: ${{ secrets.E2E_TEST_PAYPAL_LOGIN }} From dd9b54dd6d3ee3ec8525d1c69617fb6a4e02f536 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 12 Jul 2024 11:18:12 +0200 Subject: [PATCH 067/137] chore: Make UTM param form_type variable --- src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx index c6a13f897fa..a9b466da21a 100644 --- a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx @@ -33,14 +33,14 @@ export function getAttributionSearchParams({ cookies: { attributionsFirstTouch?: string; }; - experimentData?: ExperimentData; emailInput?: string; + experimentData?: ExperimentData; }) { const attributionSearchParams = modifyAttributionsForUrlSearchParams( new URLSearchParams(cookies.attributionsFirstTouch), { entrypoint: "monitor.mozilla.org-monitor-product-page", - form_type: "button", + form_type: typeof emailInput === "string" ? "email" : "button", service: process.env.OAUTH_CLIENT_ID as string, ...(emailInput && { email: emailInput }), ...(experimentData && From 64ac5c5eb3ae97b99a4e94a94925f24f733ceca6 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 12 Jul 2024 12:04:08 +0200 Subject: [PATCH 068/137] chore: Add unit test for free scan sign in URL --- .../(redesign)/(public)/LandingView.test.tsx | 62 ++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx b/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx index 6b6a2caa4f7..5e3302a04de 100644 --- a/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx @@ -849,9 +849,7 @@ describe("Free scan CTA experiment", () => { />, ); - const inputField = screen.getAllByLabelText( - "Enter your email address to check for data breach exposures and sites selling your info.", - ); + const inputField = screen.getAllByTestId("signup-form-input"); expect(inputField[0]).toBeInTheDocument(); const submitButton = screen.getAllByRole("button", { @@ -874,9 +872,7 @@ describe("Free scan CTA experiment", () => { />, ); - const inputField = screen.queryAllByLabelText( - "Enter your email address to check for data breach exposures and sites selling your info.", - ); + const inputField = screen.getAllByTestId("signup-form-input"); expect(inputField[0]).toBeInTheDocument(); const submitButton = screen.getAllByRole("button", { @@ -899,10 +895,8 @@ describe("Free scan CTA experiment", () => { />, ); - const inputField = screen.queryAllByLabelText( - "Enter your email address to check for data breach exposures and sites selling your info.", - ); - expect(inputField[0]).toBeUndefined(); + const inputField = screen.queryAllByTestId("signup-form-input"); + expect(inputField.length).toBe(0); const submitButton = screen.getAllByRole("button", { name: "Get free scan", @@ -924,10 +918,8 @@ describe("Free scan CTA experiment", () => { />, ); - const inputField = screen.queryAllByLabelText( - "Enter your email address to check for data breach exposures and sites selling your info.", - ); - expect(inputField[0]).toBeUndefined(); + const inputField = screen.queryAllByTestId("signup-form-input"); + expect(inputField.length).toBe(0); const submitButton = screen.getAllByRole("button", { name: "Sign in to get free scan", @@ -986,6 +978,48 @@ describe("Free scan CTA experiment", () => { expect.objectContaining({ button_id: "clicked_get_scan_header-ctaOnly" }), ); }); + + it("passes the expected URL to the identity provider", async () => { + const user = userEvent.setup(); + const ComposedDashboard = composeStory(LandingUs, Meta); + render( + , + ); + + const inputField = screen.getAllByTestId("signup-form-input"); + await user.type(inputField[0], "mail@example.com"); + + const submitButton = screen.getAllByRole("button", { + name: "Get free scan", + }); + await user.click(submitButton[0]); + + expect(signIn).toHaveBeenCalledWith( + "fxa", + expect.any(Object), + expect.stringContaining( + [ + "entrypoint=monitor.mozilla.org-monitor-product-page", + "form_type=email", + "service=edd29a80019d61a1", + "email=mail%40example.com", + "entrypoint_experiment=landing-page-free-scan-cta", + "entrypoint_variation=ctaWithEmail", + "utm_source=product", + "utm_medium=monitor", + "utm_campaign=get_free_scan", + ].join("&"), + ), + ); + }); }); it("does not show a confirmaton message if the user has just deleted their account", () => { From 53a62e817694ff90cc06e6e71f910832985e9729 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 12 Jul 2024 12:20:35 +0200 Subject: [PATCH 069/137] chore: Revert react-intersection-observer upgrade --- package-lock.json | 9 ++++----- package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index aef691d5c51..c5812133fe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -94,7 +94,7 @@ "lint-staged": "^15.2.5", "mjml-browser": "^4.15.3", "prettier": "3.3.2", - "react-intersection-observer": "^9.13.0", + "react-intersection-observer": "^9.10.3", "sass": "^1.77.6", "storybook": "^8.1.11", "stylelint": "^16.6.1", @@ -26699,11 +26699,10 @@ "dev": true }, "node_modules/react-intersection-observer": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.0.tgz", - "integrity": "sha512-y0UvBfjDiXqC8h0EWccyaj4dVBWMxgEx0t5RGNzQsvkfvZwugnKwxpu70StY4ivzYuMajavwUDjH4LJyIki9Lw==", + "version": "9.10.3", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.10.3.tgz", + "integrity": "sha512-9NYfKwPZRovB6QJee7fDg0zz/SyYrqXtn5xTZU0vwLtLVBtfu9aZt1pVmr825REE49VPDZ7Lm5SNHjJBOTZHpA==", "dev": true, - "license": "MIT", "peerDependencies": { "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/package.json b/package.json index d7b257a612c..4dad71cb813 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "lint-staged": "^15.2.5", "mjml-browser": "^4.15.3", "prettier": "3.3.2", - "react-intersection-observer": "^9.13.0", + "react-intersection-observer": "^9.10.3", "sass": "^1.77.6", "storybook": "^8.1.11", "stylelint": "^16.6.1", From d6972e13af89a43f4a3f3c38de6589c1d6dd3b15 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 12 Jul 2024 12:39:13 +0200 Subject: [PATCH 070/137] fix: Args passed to useTelemetry --- .../(authenticated)/user/(dashboard)/dashboard/View.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/View.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/View.tsx index 801d04cba3d..52337c82436 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/View.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/View.tsx @@ -87,7 +87,9 @@ export type TabData = { export const View = (props: Props) => { const l10n = useL10n(); - const recordTelemetry = useTelemetry(props.experimentationId); + const recordTelemetry = useTelemetry({ + experimentationId: props.experimentationId, + }); const countryCode = useContext(CountryCodeContext); const pathname = usePathname(); From f0adc6349e60c4939f7ce6f98cfbb53c37102865 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Fri, 12 Jul 2024 15:03:22 -0700 Subject: [PATCH 071/137] feat: log out for dashboard and settings when session expires --- .../(dashboard)/dashboard/[[...slug]]/page.tsx | 2 +- .../user/(dashboard)/settings/page.tsx | 5 +++-- src/app/auth/logout/page.tsx | 18 ++++++++++++++++++ src/app/functions/server/applyCoupon.ts | 14 ++++++++------ src/app/functions/server/checkSession.ts | 3 +++ src/utils/fxa.js | 13 ++++++------- 6 files changed, 39 insertions(+), 16 deletions(-) create mode 100644 src/app/auth/logout/page.tsx diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx index 81e3b826df3..074db109a32 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx @@ -57,7 +57,7 @@ type Props = { export default async function DashboardPage({ params, searchParams }: Props) { const session = await getServerSession(); if (!checkSession(session) || !session?.user?.subscriber?.id) { - return redirect("/"); + return redirect("/auth/logout"); } const { slug } = params; diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/page.tsx index 15e0b2e5383..c914761d664 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/page.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/page.tsx @@ -24,6 +24,7 @@ import { getLocale } from "../../../../../../functions/universal/getLocale"; import { getCountryCode } from "../../../../../../functions/server/getCountryCode"; import { getSubscriberById } from "../../../../../../../db/tables/subscribers"; import { checkUserHasYearlySubscription } from "../../../../../../functions/universal/user"; +import { checkSession } from "../../../../../../functions/server/checkSession"; type Props = { searchParams: { @@ -35,8 +36,8 @@ export default async function SettingsPage({ searchParams }: Props) { const session = await getServerSession(); console.debug(searchParams); - if (!session?.user?.subscriber?.id) { - return redirect("/"); + if (!session?.user?.subscriber?.id || !checkSession(session)) { + return redirect("/auth/logout"); } const emailAddresses = await getUserEmails(session.user.subscriber.id); diff --git a/src/app/auth/logout/page.tsx b/src/app/auth/logout/page.tsx new file mode 100644 index 00000000000..d1bf7bda1a7 --- /dev/null +++ b/src/app/auth/logout/page.tsx @@ -0,0 +1,18 @@ +/* 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/. */ + +"use client"; +import { signOut } from "next-auth/react"; +import { useEffect } from "react"; + +export default function LogoutPage() { + useEffect(() => { + void signOut({ + redirect: true, + callbackUrl: "/", + }); + }, []); + + return <>; +} diff --git a/src/app/functions/server/applyCoupon.ts b/src/app/functions/server/applyCoupon.ts index 3ac308b257b..4e71d06448b 100644 --- a/src/app/functions/server/applyCoupon.ts +++ b/src/app/functions/server/applyCoupon.ts @@ -67,8 +67,7 @@ export async function checkCurrentCouponCode( const currentCouponCode = process.env.CURRENT_COUPON_CODE_ID; if (!currentCouponCode) { logger.error( - "fxa_check_coupon_failed", - "Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID", + "fxa_check_coupon_failed: Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID", ); return { success: false, @@ -80,10 +79,13 @@ export async function checkCurrentCouponCode( success: await checkCouponForSubscriber(subscriber.id, currentCouponCode), }; } catch (ex) { - logger.error("fxa_check_coupon_failed", { - subscriber_id: subscriber.id, - exception: ex, - }); + logger.error( + "fxa_check_coupon_failed", + JSON.stringify({ + subscriber_id: subscriber.id, + exception: ex, + }), + ); return { success: false, }; diff --git a/src/app/functions/server/checkSession.ts b/src/app/functions/server/checkSession.ts index 7dbdfd4c2fb..dddf5b37a55 100644 --- a/src/app/functions/server/checkSession.ts +++ b/src/app/functions/server/checkSession.ts @@ -18,6 +18,9 @@ export function checkSession(session: Session | null) { } else if (!session.user.subscriber.id) { logger.warn("no_subscriber_id_in_session", { session }); return false; + } else if (session.error === "RefreshAccessTokenError") { + console.error("Refreshing access token failed... require login again"); + return false; } else { return true; } diff --git a/src/utils/fxa.js b/src/utils/fxa.js index e70b5800bab..74285b216f0 100644 --- a/src/utils/fxa.js +++ b/src/utils/fxa.js @@ -123,13 +123,12 @@ async function getSubscriptions(bearerToken) { Authorization: `Bearer ${bearerToken}` } }) + const resp = getResp.json() + if (!getResp.ok) throw resp; - if (!getResp.ok) { - throw new InternalServerError(`bad response: ${getResp.status}`) - } else { - console.info(`get_fxa_subscriptions: success`) - return await getResp.json() - } + console.info(`get_fxa_subscriptions: success`) + return resp; + } catch (e) { if (e instanceof Error) { console.error('get_fxa_subscriptions', { stack: e.stack }) @@ -258,7 +257,7 @@ async function applyCoupon(bearerToken, couponCodeId) { }) if (!response.ok) { const errMsg = await response.text() - console.info(`apply_coupon: failed - ${errMsg}`) + console.error(`apply_coupon: failed - ${errMsg}`) throw new Error(`apply_coupon: failed - ${errMsg}`) } else { console.info(`apply_coupon: success - ${JSON.stringify(await response.json())}`) From ad857b952c088b04b2e83c13f7fdd55b779366aa Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Fri, 12 Jul 2024 16:45:32 -0700 Subject: [PATCH 072/137] fix: smoke test --- .github/workflows/e2e_pr.yml | 4 ++-- src/e2e/specs/auth.spec.ts | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 8dca4b381d7..0fc84ff8db2 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -63,8 +63,8 @@ jobs: run: npm run e2e:smoke timeout-minutes: 10 env: - E2E_TEST_ENV: ${{ inputs.environment != null && inputs.environment || 'local' }} - E2E_TEST_BASE_URL: ${{ secrets.E2E_TEST_BASE_URL }} + E2E_TEST_ENV: local + E2E_TEST_BASE_URL: http://localhost:6060 E2E_TEST_ACCOUNT_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL }} E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES }} E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED }} diff --git a/src/e2e/specs/auth.spec.ts b/src/e2e/specs/auth.spec.ts index 92959b012d7..1e648b3d4ab 100644 --- a/src/e2e/specs/auth.spec.ts +++ b/src/e2e/specs/auth.spec.ts @@ -26,12 +26,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Authentication flow verification @s const randomEmail = `${Date.now()}_tstact@restmail.net`; await authPage.signUp(randomEmail, page); - // assert successful login - const successUrl = - process.env.E2E_TEST_ENV === "local" - ? "/user/dashboard" - : "/user/welcome"; - expect(page.url()).toBe(`${process.env.E2E_TEST_BASE_URL}${successUrl}`); + expect(page.url()).toBe(`${process.env.E2E_TEST_BASE_URL}/user/welcome`); await testInfo.attach( `${process.env.E2E_TEST_ENV}-signup-monitor-dashboard.png`, From 2fe6aff7b38e36750fb48eb2bbfee403722f438a Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Sat, 13 Jul 2024 03:04:36 -0700 Subject: [PATCH 073/137] changed workflows endpoints and a test file --- .github/workflows/e2e_pr.yml | 8 ++++---- src/e2e/utils/helpers.ts | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 070a86f0e66..684dbd1338f 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -64,7 +64,7 @@ jobs: timeout-minutes: 10 env: E2E_TEST_ENV: 'local' - E2E_TEST_BASE_URL: ${{ secrets.E2E_TEST_BASE_URL }} + E2E_TEST_BASE_URL: http://localhost:6060 E2E_TEST_ACCOUNT_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL }} E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS }} E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS }} @@ -75,14 +75,14 @@ jobs: ADMINS: ${{ secrets.ADMINS }} OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }} ONEREP_API_KEY: ${{ secrets.ONEREP_API_KEY }} - ONEREP_API_BASE: ${{ secrets.ONEREP_API_BASE }} + ONEREP_API_BASE: 'http://localhost:6060/api/mock/onerep/' NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }} DATABASE_URL: postgres://postgres:postgres@localhost:5432/blurts HIBP_KANON_API_TOKEN: ${{ secrets.HIBP_KANON_API_TOKEN }} HIBP_API_TOKEN: ${{ secrets.HIBP_API_TOKEN }} - HIBP_KANON_API_ROOT: ${{ secrets.HIBP_KANON_API_ROOT }} - HIBP_API_ROOT: ${{ secrets.HIBP_API_ROOT }} + HIBP_KANON_API_ROOT: 'http://localhost:6060/api/mock/hibp' + HIBP_API_ROOT: 'http://localhost:6060/api/mock/hibp' # Our tests are currently set up to expect accounts to act like # old user accounts, so let's pretend they all are: BROKER_SCAN_RELEASE_DATE: "3000-12-31" diff --git a/src/e2e/utils/helpers.ts b/src/e2e/utils/helpers.ts index f49f90da57d..412336f2306 100644 --- a/src/e2e/utils/helpers.ts +++ b/src/e2e/utils/helpers.ts @@ -224,8 +224,9 @@ export const forceLoginAs = async ( await page.waitForURL(/^(?!.*signin).*/); } await authPage.signIn(email, password); - await page.waitForURL("**/user/dashboard"); - await expect(page).toHaveURL(/.*\/user\/dashboard.*/); + const dashboardRegex = /.*\/user\/dashboard.*/; + if (dashboardRegex.test(page.url())) return; + await page.waitForURL(dashboardRegex); }; export const resetTestData = async ( From 96f98723e600006d80fa1d20e77ac6e2cd115bfd Mon Sep 17 00:00:00 2001 From: Vincent Date: Mon, 15 Jul 2024 14:22:45 +0200 Subject: [PATCH 074/137] Remove ts-jest, which we're not using (yet?) It can enable type-checking of your tests, but we haven't set that up. --- package-lock.json | 103 ++-------------------------------------------- package.json | 1 - 2 files changed, 3 insertions(+), 101 deletions(-) diff --git a/package-lock.json b/package-lock.json index c51bbb0f3de..3a89ec6e4bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -100,7 +100,6 @@ "stylelint": "^16.7.0", "stylelint-config-recommended-scss": "^14.1.0", "stylelint-scss": "^6.3.2", - "ts-jest": "^29.1.5", "tsx": "^4.16.0", "typescript": "^5.5.3", "yaml": "^2.4.5" @@ -12935,18 +12934,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -21060,12 +21047,6 @@ "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", "integrity": "sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ==" }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -21365,7 +21346,9 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -26893,86 +26876,6 @@ "node": ">=6.10" } }, - "node_modules/ts-jest": { - "version": "29.1.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.5.tgz", - "integrity": "sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", diff --git a/package.json b/package.json index cb328d9126a..5633cbe4892 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,6 @@ "stylelint": "^16.7.0", "stylelint-config-recommended-scss": "^14.1.0", "stylelint-scss": "^6.3.2", - "ts-jest": "^29.1.5", "tsx": "^4.16.0", "typescript": "^5.5.3", "yaml": "^2.4.5" From ce4fe1f48a453ac3646731cc7887d72025ec32fa Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Mon, 15 Jul 2024 07:24:10 -0700 Subject: [PATCH 075/137] Allow overriding Nimbus features locally, and clone before modifying experiment data (#4790) * fix: allow overriding experiments locally, and clone feature data before modifying Co-authored-by: Vincent --- src/app/functions/server/getExperiments.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/app/functions/server/getExperiments.ts b/src/app/functions/server/getExperiments.ts index a8508ff2236..ec2180be19b 100644 --- a/src/app/functions/server/getExperiments.ts +++ b/src/app/functions/server/getExperiments.ts @@ -28,9 +28,23 @@ export async function getExperiments(params: { countryCode: string; previewMode: boolean; }): Promise { + // TODO MNTOR-3380 - until Cirrus implements preview mode, set all Nimbus features to `true` for QA purposes. + if (params.previewMode === true) { + // Clone the `localExperimentData` object so we don't modify the exported data structure. + const overriddenExperimentData = Object.fromEntries( + Object.entries(localExperimentData).map( + ([experimentId, experimentConfig]) => { + return [experimentId, { ...experimentConfig, enabled: true }]; + }, + ), + ) as ExperimentData; + return overriddenExperimentData; + } + if (["local"].includes(process.env.APP_ENV ?? "local")) { return localExperimentData; } + const serverUrl = process.env.NIMBUS_SIDECAR_URL; if (!serverUrl) { throw new Error("env var NIMBUS_SIDECAR_URL not set"); @@ -54,13 +68,6 @@ export async function getExperiments(params: { const experimentData = await response.json(); - if (params.previewMode === true) { - // TODO Until Cirrus ADR lands https://github.com/mozilla/experimenter/pull/10902, set all experiments to `true`. - // After this ADR is implemented, this will just pass `preview_mode` to Cirrus instead. - for (const experiment of experimentData) { - experiment["enabled"] = true; - } - } return (experimentData as ExperimentData) ?? defaultExperimentData; } catch (ex) { logger.error(`Could not connect to Cirrus on ${serverUrl}`, ex); From deddd519182b2096612d127fa79c0f9b7590bfe9 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Mon, 15 Jul 2024 16:29:17 +0200 Subject: [PATCH 076/137] chore: Get email input by label text instead of test ID --- .../(redesign)/(public)/LandingView.test.tsx | 98 ++++++++++--------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx b/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx index 5e3302a04de..7ff24561267 100644 --- a/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx @@ -63,7 +63,7 @@ describe("When Premium is not available", () => { render(); const inputField = screen.getAllByLabelText( - "Enter your email address to check for data breach exposures.", + "Enter your email address to check for data breach exposures." ); await user.type(inputField[0], "mail@example.com"); @@ -76,7 +76,7 @@ describe("When Premium is not available", () => { expect(signIn).toHaveBeenCalledWith( "fxa", expect.any(Object), - expect.stringContaining(`email=mail%40example.com`), + expect.stringContaining(`email=mail%40example.com`) ); }); @@ -138,7 +138,7 @@ describe("When Premium is not available", () => { "click", expect.objectContaining({ button_id: "sign_in", - }), + }) ); }); @@ -146,7 +146,7 @@ describe("When Premium is not available", () => { const ComposedDashboard = composeStory(LandingNonUs, Meta); render(); const quote = screen.getByText( - "Data breaches happen every 11 minutes, exposing your private information — but don’t worry, we can help.", + "Data breaches happen every 11 minutes, exposing your private information — but don’t worry, we can help." ); expect(quote).toBeInTheDocument(); }); @@ -156,7 +156,7 @@ describe("When Premium is not available", () => { render(); const scanningForExposuresIllustration = screen.getByTestId( - "scanning-for-exposures-image", + "scanning-for-exposures-image" ); expect(scanningForExposuresIllustration).toBeInTheDocument(); }); @@ -179,11 +179,11 @@ describe("When Premium is not available", () => { const ComposedDashboard = composeStory(LandingNonUsDe, Meta); render(); const scanningForExposuresIllustration = screen.getByTestId( - "scanning-for-exposures-image", + "scanning-for-exposures-image" ); expect(scanningForExposuresIllustration).toHaveAttribute( "data-country-code", - "de", + "de" ); }); @@ -191,11 +191,11 @@ describe("When Premium is not available", () => { const ComposedDashboard = composeStory(LandingNonUsFr, Meta); render(); const scanningForExposuresIllustration = screen.getByTestId( - "scanning-for-exposures-image", + "scanning-for-exposures-image" ); expect(scanningForExposuresIllustration).toHaveAttribute( "data-country-code", - "fr", + "fr" ); }); }); @@ -380,13 +380,13 @@ describe("When Premium is available", () => { const priceRegex = /\$(.+?)\/mo./; const yearlyPriceEl = getByText(pricingTable, priceRegex); const yearlyPrice = Number.parseFloat( - yearlyPriceEl.textContent!.split("$")[1].split("/")[0], + yearlyPriceEl.textContent!.split("$")[1].split("/")[0] ); const monthlyToggle = getByRole(pricingTable, "radio", { name: "Monthly" }); await user.click(monthlyToggle); const monthlyPriceEl = getByText(pricingTable, priceRegex); const monthlyPrice = Number.parseFloat( - monthlyPriceEl.textContent!.split("$")[1].split("/")[0], + monthlyPriceEl.textContent!.split("$")[1].split("/")[0] ); expect(savingsEl.textContent).not.toMatch("."); @@ -398,8 +398,8 @@ describe("When Premium is available", () => { // Replace the special characters Fluent inserts around variables: .replace("⁨", "") .replace("⁩", ""), - 10, - ), + 10 + ) ).toBeLessThanOrEqual(((monthlyPrice - yearlyPrice) * 100) / monthlyPrice); }); @@ -475,7 +475,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "selected_monthly_plan", - }), + }) ); await user.click(yearlyToggle); @@ -484,7 +484,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "selected_yearly_plan", - }), + }) ); }); @@ -505,7 +505,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "clicked_free_pricing_grid", - }), + }) ); }); @@ -534,7 +534,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "purchase_yearly_landing_page", - }), + }) ); await user.click(monthlyToggle); @@ -544,7 +544,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "purchase_monthly_landing_page", - }), + }) ); }); @@ -565,7 +565,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "selected_monthly_plan", - }), + }) ); await user.click(yearlyToggle); @@ -574,7 +574,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "selected_yearly_plan", - }), + }) ); }); @@ -604,7 +604,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "purchase_yearly_landing_page", - }), + }) ); await user.click(monthlyToggle); @@ -614,7 +614,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "purchase_monthly_landing_page", - }), + }) ); }); @@ -636,7 +636,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "clicked_free_pricing_grid", - }), + }) ); }); @@ -644,7 +644,7 @@ describe("When Premium is available", () => { const ComposedDashboard = composeStory(LandingUs, Meta); render(); const quote = screen.getByText( - "There’s a $240 billion industry of data brokers selling your private information for profit. It’s time to take back your privacy.", + "There’s a $240 billion industry of data brokers selling your private information for profit. It’s time to take back your privacy." ); expect(quote).toBeInTheDocument(); }); @@ -662,7 +662,7 @@ describe("When Premium is available", () => { render(); const limitDescription = screen.getByText( - "We’ve reached the maximum scans for the month. Enter your email to get on our waitlist.", + "We’ve reached the maximum scans for the month. Enter your email to get on our waitlist." ); expect(limitDescription).toBeInTheDocument(); }); @@ -683,7 +683,7 @@ describe("When Premium is available", () => { expect(waitlistCta[0]).toHaveAttribute( "href", - "https://www.mozilla.org/products/monitor/waitlist-scan/", + "https://www.mozilla.org/products/monitor/waitlist-scan/" ); }); @@ -697,7 +697,7 @@ describe("When Premium is available", () => { await user.click(faqQuestion); const faqAnswer = screen.getByText( "Certain websites are in the business of collecting and selling people’s personal information without their consent, which is unfortunately legal in the US.", - { exact: false }, + { exact: false } ); expect(faqAnswer).toHaveAttribute("aria-hidden", "false"); await user.click(faqQuestion); @@ -715,7 +715,7 @@ describe("When Premium is available", () => { await user.click(faqQuestion1); const faqAnswer1 = screen.getByText( "Certain websites are in the business of collecting and selling people’s personal information without their consent, which is unfortunately legal in the US.", - { exact: false }, + { exact: false } ); expect(faqAnswer1).toHaveAttribute("aria-hidden", "false"); const faqQuestion2 = screen.getByRole("button", { @@ -760,7 +760,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ link_id: "navbar_how_it_works", - }), + }) ); }); @@ -783,7 +783,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ link_id: "navbar_pricing", - }), + }) ); }); @@ -806,7 +806,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ link_id: "navbar_faqs", - }), + }) ); }); @@ -829,7 +829,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ link_id: "navbar_breaches", - }), + }) ); }); }); @@ -846,10 +846,12 @@ describe("Free scan CTA experiment", () => { enabled: false, }, }} - />, + /> ); - const inputField = screen.getAllByTestId("signup-form-input"); + const inputField = screen.getAllByLabelText( + "Enter your email address to check for data breach exposures and sites selling your info." + ); expect(inputField[0]).toBeInTheDocument(); const submitButton = screen.getAllByRole("button", { @@ -869,10 +871,12 @@ describe("Free scan CTA experiment", () => { variant: "ctaWithEmail", }, }} - />, + /> ); - const inputField = screen.getAllByTestId("signup-form-input"); + const inputField = screen.getAllByLabelText( + "Enter your email address to check for data breach exposures and sites selling your info." + ); expect(inputField[0]).toBeInTheDocument(); const submitButton = screen.getAllByRole("button", { @@ -892,7 +896,7 @@ describe("Free scan CTA experiment", () => { variant: "ctaOnly", }, }} - />, + /> ); const inputField = screen.queryAllByTestId("signup-form-input"); @@ -915,7 +919,7 @@ describe("Free scan CTA experiment", () => { variant: "ctaOnlyAlternativeLabel", }, }} - />, + /> ); const inputField = screen.queryAllByTestId("signup-form-input"); @@ -938,7 +942,7 @@ describe("Free scan CTA experiment", () => { variant: "ctaOnly", }, }} - />, + /> ); const waitlistCta = screen.getAllByRole("link", { name: "Join waitlist", @@ -959,7 +963,7 @@ describe("Free scan CTA experiment", () => { variant: "ctaOnly", }, }} - />, + /> ); // jsdom will complain about not being able to navigate to a different page @@ -975,7 +979,7 @@ describe("Free scan CTA experiment", () => { expect(mockedRecord).toHaveBeenCalledWith( "ctaButton", "click", - expect.objectContaining({ button_id: "clicked_get_scan_header-ctaOnly" }), + expect.objectContaining({ button_id: "clicked_get_scan_header-ctaOnly" }) ); }); @@ -991,10 +995,12 @@ describe("Free scan CTA experiment", () => { variant: "ctaWithEmail", }, }} - />, + /> ); - const inputField = screen.getAllByTestId("signup-form-input"); + const inputField = screen.getAllByLabelText( + "Enter your email address to check for data breach exposures and sites selling your info." + ); await user.type(inputField[0], "mail@example.com"); const submitButton = screen.getAllByRole("button", { @@ -1016,8 +1022,8 @@ describe("Free scan CTA experiment", () => { "utm_source=product", "utm_medium=monitor", "utm_campaign=get_free_scan", - ].join("&"), - ), + ].join("&") + ) ); }); }); @@ -1041,7 +1047,7 @@ it("shows a confirmaton message if the user has just deleted their account", () const alert = screen.getByRole("alert"); const confirmationMessage = within(alert).getByText( - "Your ⁨Monitor⁩ account is now deleted.", + "Your ⁨Monitor⁩ account is now deleted." ); expect(alert).toBeInTheDocument(); From 6a9d2dde9921a81a3e193546804c0824cb9efd55 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Mon, 15 Jul 2024 16:30:40 +0200 Subject: [PATCH 077/137] chore: Use more specific experiment variant description --- config/nimbus.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/nimbus.yaml b/config/nimbus.yaml index 04ac2c579b7..5cffc3ff3f2 100644 --- a/config/nimbus.yaml +++ b/config/nimbus.yaml @@ -79,5 +79,5 @@ enums: description: An enum of free scan CTA types variants: ctaWithEmail: Show a CTA button with an optional email input - ctaOnly: Only show a CTA button with the default label - ctaOnlyAlternativeLabel: Only show a CTA button with an alternative label + ctaOnly: Only show a CTA button with the label “Get free scan” + ctaOnlyAlternativeLabel: Only show a CTA button with the label “Sign in to get free scan” From ef2400cd580eb4f27291dcac4978cfd88eca90df Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Mon, 15 Jul 2024 16:36:22 +0200 Subject: [PATCH 078/137] fix: Linter --- .../(redesign)/(public)/LandingView.test.tsx | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx b/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx index 7ff24561267..96c793c49b8 100644 --- a/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx @@ -63,7 +63,7 @@ describe("When Premium is not available", () => { render(); const inputField = screen.getAllByLabelText( - "Enter your email address to check for data breach exposures." + "Enter your email address to check for data breach exposures.", ); await user.type(inputField[0], "mail@example.com"); @@ -76,7 +76,7 @@ describe("When Premium is not available", () => { expect(signIn).toHaveBeenCalledWith( "fxa", expect.any(Object), - expect.stringContaining(`email=mail%40example.com`) + expect.stringContaining(`email=mail%40example.com`), ); }); @@ -138,7 +138,7 @@ describe("When Premium is not available", () => { "click", expect.objectContaining({ button_id: "sign_in", - }) + }), ); }); @@ -146,7 +146,7 @@ describe("When Premium is not available", () => { const ComposedDashboard = composeStory(LandingNonUs, Meta); render(); const quote = screen.getByText( - "Data breaches happen every 11 minutes, exposing your private information — but don’t worry, we can help." + "Data breaches happen every 11 minutes, exposing your private information — but don’t worry, we can help.", ); expect(quote).toBeInTheDocument(); }); @@ -156,7 +156,7 @@ describe("When Premium is not available", () => { render(); const scanningForExposuresIllustration = screen.getByTestId( - "scanning-for-exposures-image" + "scanning-for-exposures-image", ); expect(scanningForExposuresIllustration).toBeInTheDocument(); }); @@ -179,11 +179,11 @@ describe("When Premium is not available", () => { const ComposedDashboard = composeStory(LandingNonUsDe, Meta); render(); const scanningForExposuresIllustration = screen.getByTestId( - "scanning-for-exposures-image" + "scanning-for-exposures-image", ); expect(scanningForExposuresIllustration).toHaveAttribute( "data-country-code", - "de" + "de", ); }); @@ -191,11 +191,11 @@ describe("When Premium is not available", () => { const ComposedDashboard = composeStory(LandingNonUsFr, Meta); render(); const scanningForExposuresIllustration = screen.getByTestId( - "scanning-for-exposures-image" + "scanning-for-exposures-image", ); expect(scanningForExposuresIllustration).toHaveAttribute( "data-country-code", - "fr" + "fr", ); }); }); @@ -380,13 +380,13 @@ describe("When Premium is available", () => { const priceRegex = /\$(.+?)\/mo./; const yearlyPriceEl = getByText(pricingTable, priceRegex); const yearlyPrice = Number.parseFloat( - yearlyPriceEl.textContent!.split("$")[1].split("/")[0] + yearlyPriceEl.textContent!.split("$")[1].split("/")[0], ); const monthlyToggle = getByRole(pricingTable, "radio", { name: "Monthly" }); await user.click(monthlyToggle); const monthlyPriceEl = getByText(pricingTable, priceRegex); const monthlyPrice = Number.parseFloat( - monthlyPriceEl.textContent!.split("$")[1].split("/")[0] + monthlyPriceEl.textContent!.split("$")[1].split("/")[0], ); expect(savingsEl.textContent).not.toMatch("."); @@ -398,8 +398,8 @@ describe("When Premium is available", () => { // Replace the special characters Fluent inserts around variables: .replace("⁨", "") .replace("⁩", ""), - 10 - ) + 10, + ), ).toBeLessThanOrEqual(((monthlyPrice - yearlyPrice) * 100) / monthlyPrice); }); @@ -475,7 +475,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "selected_monthly_plan", - }) + }), ); await user.click(yearlyToggle); @@ -484,7 +484,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "selected_yearly_plan", - }) + }), ); }); @@ -505,7 +505,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "clicked_free_pricing_grid", - }) + }), ); }); @@ -534,7 +534,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "purchase_yearly_landing_page", - }) + }), ); await user.click(monthlyToggle); @@ -544,7 +544,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "purchase_monthly_landing_page", - }) + }), ); }); @@ -565,7 +565,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "selected_monthly_plan", - }) + }), ); await user.click(yearlyToggle); @@ -574,7 +574,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "selected_yearly_plan", - }) + }), ); }); @@ -604,7 +604,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "purchase_yearly_landing_page", - }) + }), ); await user.click(monthlyToggle); @@ -614,7 +614,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "purchase_monthly_landing_page", - }) + }), ); }); @@ -636,7 +636,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ button_id: "clicked_free_pricing_grid", - }) + }), ); }); @@ -644,7 +644,7 @@ describe("When Premium is available", () => { const ComposedDashboard = composeStory(LandingUs, Meta); render(); const quote = screen.getByText( - "There’s a $240 billion industry of data brokers selling your private information for profit. It’s time to take back your privacy." + "There’s a $240 billion industry of data brokers selling your private information for profit. It’s time to take back your privacy.", ); expect(quote).toBeInTheDocument(); }); @@ -662,7 +662,7 @@ describe("When Premium is available", () => { render(); const limitDescription = screen.getByText( - "We’ve reached the maximum scans for the month. Enter your email to get on our waitlist." + "We’ve reached the maximum scans for the month. Enter your email to get on our waitlist.", ); expect(limitDescription).toBeInTheDocument(); }); @@ -683,7 +683,7 @@ describe("When Premium is available", () => { expect(waitlistCta[0]).toHaveAttribute( "href", - "https://www.mozilla.org/products/monitor/waitlist-scan/" + "https://www.mozilla.org/products/monitor/waitlist-scan/", ); }); @@ -697,7 +697,7 @@ describe("When Premium is available", () => { await user.click(faqQuestion); const faqAnswer = screen.getByText( "Certain websites are in the business of collecting and selling people’s personal information without their consent, which is unfortunately legal in the US.", - { exact: false } + { exact: false }, ); expect(faqAnswer).toHaveAttribute("aria-hidden", "false"); await user.click(faqQuestion); @@ -715,7 +715,7 @@ describe("When Premium is available", () => { await user.click(faqQuestion1); const faqAnswer1 = screen.getByText( "Certain websites are in the business of collecting and selling people’s personal information without their consent, which is unfortunately legal in the US.", - { exact: false } + { exact: false }, ); expect(faqAnswer1).toHaveAttribute("aria-hidden", "false"); const faqQuestion2 = screen.getByRole("button", { @@ -760,7 +760,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ link_id: "navbar_how_it_works", - }) + }), ); }); @@ -783,7 +783,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ link_id: "navbar_pricing", - }) + }), ); }); @@ -806,7 +806,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ link_id: "navbar_faqs", - }) + }), ); }); @@ -829,7 +829,7 @@ describe("When Premium is available", () => { "click", expect.objectContaining({ link_id: "navbar_breaches", - }) + }), ); }); }); @@ -846,11 +846,11 @@ describe("Free scan CTA experiment", () => { enabled: false, }, }} - /> + />, ); const inputField = screen.getAllByLabelText( - "Enter your email address to check for data breach exposures and sites selling your info." + "Enter your email address to check for data breach exposures and sites selling your info.", ); expect(inputField[0]).toBeInTheDocument(); @@ -871,11 +871,11 @@ describe("Free scan CTA experiment", () => { variant: "ctaWithEmail", }, }} - /> + />, ); const inputField = screen.getAllByLabelText( - "Enter your email address to check for data breach exposures and sites selling your info." + "Enter your email address to check for data breach exposures and sites selling your info.", ); expect(inputField[0]).toBeInTheDocument(); @@ -896,7 +896,7 @@ describe("Free scan CTA experiment", () => { variant: "ctaOnly", }, }} - /> + />, ); const inputField = screen.queryAllByTestId("signup-form-input"); @@ -919,7 +919,7 @@ describe("Free scan CTA experiment", () => { variant: "ctaOnlyAlternativeLabel", }, }} - /> + />, ); const inputField = screen.queryAllByTestId("signup-form-input"); @@ -942,7 +942,7 @@ describe("Free scan CTA experiment", () => { variant: "ctaOnly", }, }} - /> + />, ); const waitlistCta = screen.getAllByRole("link", { name: "Join waitlist", @@ -963,7 +963,7 @@ describe("Free scan CTA experiment", () => { variant: "ctaOnly", }, }} - /> + />, ); // jsdom will complain about not being able to navigate to a different page @@ -979,7 +979,7 @@ describe("Free scan CTA experiment", () => { expect(mockedRecord).toHaveBeenCalledWith( "ctaButton", "click", - expect.objectContaining({ button_id: "clicked_get_scan_header-ctaOnly" }) + expect.objectContaining({ button_id: "clicked_get_scan_header-ctaOnly" }), ); }); @@ -995,11 +995,11 @@ describe("Free scan CTA experiment", () => { variant: "ctaWithEmail", }, }} - /> + />, ); const inputField = screen.getAllByLabelText( - "Enter your email address to check for data breach exposures and sites selling your info." + "Enter your email address to check for data breach exposures and sites selling your info.", ); await user.type(inputField[0], "mail@example.com"); @@ -1022,8 +1022,8 @@ describe("Free scan CTA experiment", () => { "utm_source=product", "utm_medium=monitor", "utm_campaign=get_free_scan", - ].join("&") - ) + ].join("&"), + ), ); }); }); @@ -1047,7 +1047,7 @@ it("shows a confirmaton message if the user has just deleted their account", () const alert = screen.getByRole("alert"); const confirmationMessage = within(alert).getByText( - "Your ⁨Monitor⁩ account is now deleted." + "Your ⁨Monitor⁩ account is now deleted.", ); expect(alert).toBeInTheDocument(); From e3e3d3aea802b27a58d9c3b53fe448219023fd1d Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Mon, 15 Jul 2024 17:28:59 +0200 Subject: [PATCH 079/137] fix: Invalid FML in nimbus.yaml --- config/nimbus.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/config/nimbus.yaml b/config/nimbus.yaml index 5cffc3ff3f2..a2583065915 100644 --- a/config/nimbus.yaml +++ b/config/nimbus.yaml @@ -78,6 +78,9 @@ enums: FreeScanCtaType: description: An enum of free scan CTA types variants: - ctaWithEmail: Show a CTA button with an optional email input - ctaOnly: Only show a CTA button with the label “Get free scan” - ctaOnlyAlternativeLabel: Only show a CTA button with the label “Sign in to get free scan” + ctaWithEmail: + description: Show a CTA button with an optional email input + ctaOnly: + description: Only show a CTA button with the label “Get free scan” + ctaOnlyAlternativeLabel: + description: Only show a CTA button with the label “Sign in to get free scan” From ab179724ca18377a0eb0e0a05b15a0522709de2c Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Mon, 15 Jul 2024 13:19:32 -0700 Subject: [PATCH 080/137] fix: update secret --- .github/workflows/e2e_pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 0fc84ff8db2..e55014dc4c6 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -72,7 +72,7 @@ jobs: E2E_TEST_PAYPAL_LOGIN: ${{ secrets.E2E_TEST_PAYPAL_LOGIN }} E2E_TEST_PAYPAL_PASSWORD: ${{ secrets.E2E_TEST_PAYPAL_PASSWORD }} ADMINS: ${{ secrets.ADMINS }} - OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }} + OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET_LOCAL }} ONEREP_API_KEY: ${{ secrets.ONEREP_API_KEY }} NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }} From d8f7bb3da41e74ce5a7535187e7fc2a0a93584d3 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Mon, 15 Jul 2024 13:24:32 -0700 Subject: [PATCH 081/137] fix: overwrite account uri --- .github/workflows/e2e_pr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index e55014dc4c6..53d92d28e91 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -73,6 +73,7 @@ jobs: E2E_TEST_PAYPAL_PASSWORD: ${{ secrets.E2E_TEST_PAYPAL_PASSWORD }} ADMINS: ${{ secrets.ADMINS }} OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET_LOCAL }} + OAUTH_ACCOUNT_URI: ${{ secrets.OAUTH_ACCOUNT_URI }} ONEREP_API_KEY: ${{ secrets.ONEREP_API_KEY }} NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }} From 517cbadd9f39d29108ed443a5bf4c647de1d9ed0 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Mon, 15 Jul 2024 13:52:56 -0700 Subject: [PATCH 082/137] fix: revert --- src/e2e/specs/auth.spec.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/e2e/specs/auth.spec.ts b/src/e2e/specs/auth.spec.ts index 1e648b3d4ab..92959b012d7 100644 --- a/src/e2e/specs/auth.spec.ts +++ b/src/e2e/specs/auth.spec.ts @@ -26,7 +26,12 @@ test.describe(`${process.env.E2E_TEST_ENV} - Authentication flow verification @s const randomEmail = `${Date.now()}_tstact@restmail.net`; await authPage.signUp(randomEmail, page); - expect(page.url()).toBe(`${process.env.E2E_TEST_BASE_URL}/user/welcome`); + // assert successful login + const successUrl = + process.env.E2E_TEST_ENV === "local" + ? "/user/dashboard" + : "/user/welcome"; + expect(page.url()).toBe(`${process.env.E2E_TEST_BASE_URL}${successUrl}`); await testInfo.attach( `${process.env.E2E_TEST_ENV}-signup-monitor-dashboard.png`, From 366866d5c096cf3a22c7d948d12c71c2dd2ce565 Mon Sep 17 00:00:00 2001 From: mukhamediyar Date: Mon, 15 Jul 2024 16:23:29 -0700 Subject: [PATCH 083/137] Update mockAllBreaches.json --- src/app/api/mock/hibp/mockData/mockAllBreaches.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/app/api/mock/hibp/mockData/mockAllBreaches.json b/src/app/api/mock/hibp/mockData/mockAllBreaches.json index f1d45328a02..0adfe61d0a1 100644 --- a/src/app/api/mock/hibp/mockData/mockAllBreaches.json +++ b/src/app/api/mock/hibp/mockData/mockAllBreaches.json @@ -649,7 +649,18 @@ "PwnCount": 547422, "Description": "In December 2020, the University of California suffered a data breach due to vulnerability in in a third-party provider, Accellion. The breach exposed extensive personal data on both students and staff including 547 thousand unique email addresses, names, dates of birth, genders, social security numbers, ethnicities and other academic related data attributes. Further analysis is available in Exploring the Impact of the UC Data Breach. The data was provided to HIBP courtesy of Cyril Gorlla.", "LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/UC.png", - "DataClasses": "{dates-of-birth,education-levels,email-addresses,ethnicities,genders,job-titles,names,phone-numbers,physical-addresses,social-security-numbers}", + "DataClasses": [ + "dates-of-birth", + "education-levels", + "email-addresses", + "ethnicities", + "genders", + "job-titles", + "names", + "phone-numbers", + "physical-addresses", + "social-security-numbers" + ], "IsVerified": true, "IsFabricated": false, "IsSensitive": false, From bfdd3d5e2c7b7e593f862fe2e0fb8c4a53878e85 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Mon, 15 Jul 2024 16:29:38 -0700 Subject: [PATCH 084/137] revert: fix --- src/app/functions/server/applyCoupon.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/app/functions/server/applyCoupon.ts b/src/app/functions/server/applyCoupon.ts index 4e71d06448b..9d204944a48 100644 --- a/src/app/functions/server/applyCoupon.ts +++ b/src/app/functions/server/applyCoupon.ts @@ -79,13 +79,10 @@ export async function checkCurrentCouponCode( success: await checkCouponForSubscriber(subscriber.id, currentCouponCode), }; } catch (ex) { - logger.error( - "fxa_check_coupon_failed", - JSON.stringify({ - subscriber_id: subscriber.id, - exception: ex, - }), - ); + logger.error("fxa_check_coupon_failed", { + subscriber_id: subscriber.id, + exception: ex, + }); return { success: false, }; From 777640a2168fc0cdef8a1b45ff7d21583bf8b618 Mon Sep 17 00:00:00 2001 From: mukhamediyar Date: Mon, 15 Jul 2024 22:27:42 -0700 Subject: [PATCH 085/137] Update e2e_pr.yml --- .github/workflows/e2e_pr.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 684dbd1338f..be46a264836 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -82,7 +82,6 @@ jobs: HIBP_KANON_API_TOKEN: ${{ secrets.HIBP_KANON_API_TOKEN }} HIBP_API_TOKEN: ${{ secrets.HIBP_API_TOKEN }} HIBP_KANON_API_ROOT: 'http://localhost:6060/api/mock/hibp' - HIBP_API_ROOT: 'http://localhost:6060/api/mock/hibp' # Our tests are currently set up to expect accounts to act like # old user accounts, so let's pretend they all are: BROKER_SCAN_RELEASE_DATE: "3000-12-31" From 48e65012f12785248ae8a386c1e5df5e4609efa7 Mon Sep 17 00:00:00 2001 From: mukhamediyar Date: Mon, 15 Jul 2024 22:40:37 -0700 Subject: [PATCH 086/137] increased time limit --- .github/workflows/e2e_pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index be46a264836..3a1ca48ddfd 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -61,7 +61,7 @@ jobs: - name: Run Playwright tests if: github.actor != 'dependabot[bot]' run: npm run e2e:smoke - timeout-minutes: 10 + timeout-minutes: 30 env: E2E_TEST_ENV: 'local' E2E_TEST_BASE_URL: http://localhost:6060 From 780b61f0e329d4e9e13c46c0cd0c1abe138046a1 Mon Sep 17 00:00:00 2001 From: Kaitlyn Andres Date: Tue, 16 Jul 2024 07:34:28 -0400 Subject: [PATCH 087/137] Fix FAQ alignment issue (#4795) --- src/app/(proper_react)/(redesign)/(public)/Faq.module.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/Faq.module.scss b/src/app/(proper_react)/(redesign)/(public)/Faq.module.scss index 6a219cc6413..5d2b823c641 100644 --- a/src/app/(proper_react)/(redesign)/(public)/Faq.module.scss +++ b/src/app/(proper_react)/(redesign)/(public)/Faq.module.scss @@ -42,7 +42,8 @@ align-items: center; font: $text-body-lg; cursor: pointer; - text-align: center; + text-align: start; + svg { color: $color-blue-60; width: 12px; From a8e0e087c38d863e6db14927cdf8a18f7c96b302 Mon Sep 17 00:00:00 2001 From: mozilla-pontoon Date: Tue, 16 Jul 2024 12:01:29 +0000 Subject: [PATCH 088/137] Import translations from l10n repository (2024-07-16) --- locales/nn-NO/dashboard.ftl | 13 +++++++++++-- locales/nn-NO/landing-all.ftl | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/locales/nn-NO/dashboard.ftl b/locales/nn-NO/dashboard.ftl index f1510a71839..00ec27ef268 100644 --- a/locales/nn-NO/dashboard.ftl +++ b/locales/nn-NO/dashboard.ftl @@ -22,8 +22,10 @@ exposure-chart-legend-heading-nr = Antal # Variables: # $nr (number) - Number of a particular type of exposure found for the user exposure-chart-legend-value-nr = { $nr }× +exposure-chart-caption = Dette diagrammet viser kor mange gongar informasjonen din er aktivt eksponert. exposure-chart-returning-user-upgrade-prompt-cta = Start ei gratis skanning modal-cta-ok = OK +modal-cta-got-it = Eg forstår open-modal-alt = Opne modal close-modal-alt = Lat att modal open-tooltip-alt = Opne verktøytips @@ -32,6 +34,7 @@ progress-card-manually-fixed-headline = Manuelt løyst dashboard-tab-label-action-needed = Handling påkravd dashboard-tab-label-fixed = Løyst dashboard-exposures-all-fixed-label = Alt løyst her! +dashboard-exposures-area-headline = Sjå alle nettstadar der informasjonen din er eksponert # This is the label on a button that opens a popover menu, which shows a menu to adjust filters for the listed exposures. dashboard-exposures-filter = Filter dashboard-exposures-filter-company = Firma @@ -54,8 +57,14 @@ dashboard-top-banner-your-data-is-protected-cta = Sjå kva som er løyst dashboard-top-banner-lets-keep-protecting-title = La oss halde fram med å verne dataa dine dashboard-top-banner-protect-your-data-cta = La oss løyse det dashboard-no-exposures-label = Fann ingen eksponeringar +# Variables: +# $exposures_resolved_num is the number of exposures the user has resolved. +dashboard-top-banner-non-us-your-data-is-protected-description = + { $exposures_resolved_num -> + [one] Bra jobba, eksponeringa av dine data er løyst! Vi vil halde fram med overvakinga, og vi vil varsle deg om eventuelle nye eksponeringar. + *[other] Bra jobba, alle { $exposures_resolved_num } eksponeringar av dine data er fiksa! Vi vil halde fram med overvakinga, og vi vil varsle deg om eventuelle nye eksponeringar. + } dashboard-top-banner-monitor-more-cta = Overvak fleire e-postadresser -# About Exposure Statuses Modal +# About Exposure Indicators Modal -modal-exposure-status-title = Om eksponeringsstatusar diff --git a/locales/nn-NO/landing-all.ftl b/locales/nn-NO/landing-all.ftl index b88e13cd9ce..3774ab1e62a 100644 --- a/locales/nn-NO/landing-all.ftl +++ b/locales/nn-NO/landing-all.ftl @@ -3,7 +3,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } -landing-all-hero-title = Finn ut kvar din informasjon er eksponert — og ta han tilbake +landing-all-hero-title = Finn ut kvar informasjonen din er eksponert — og ta han tilbake landing-all-hero-lead = Vi undersøkjer datalekkasjar for å sjå om dine data har lekt og gir deg rettleiing om korleis du skal fikse det. landing-all-hero-emailform-input-placeholder = dittnamn@doeme.no landing-all-hero-emailform-input-label = Skriv inn e-postadressa di for å søkje etter datalekkasje-eksponeringar. From 14614a857b53c33038efd04cc299449ebd91e58d Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Tue, 16 Jul 2024 08:55:10 -0700 Subject: [PATCH 089/137] fix: review comments --- src/app/functions/server/applyCoupon.ts | 7 ++++--- src/app/functions/server/checkSession.ts | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/app/functions/server/applyCoupon.ts b/src/app/functions/server/applyCoupon.ts index 9d204944a48..d337ac75ca7 100644 --- a/src/app/functions/server/applyCoupon.ts +++ b/src/app/functions/server/applyCoupon.ts @@ -66,9 +66,10 @@ export async function checkCurrentCouponCode( const currentCouponCode = process.env.CURRENT_COUPON_CODE_ID; if (!currentCouponCode) { - logger.error( - "fxa_check_coupon_failed: Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID", - ); + logger.error("fxa_check_coupon_failed", { + exception: + "Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID", + }); return { success: false, }; diff --git a/src/app/functions/server/checkSession.ts b/src/app/functions/server/checkSession.ts index dddf5b37a55..a60d553c2b9 100644 --- a/src/app/functions/server/checkSession.ts +++ b/src/app/functions/server/checkSession.ts @@ -19,7 +19,9 @@ export function checkSession(session: Session | null) { logger.warn("no_subscriber_id_in_session", { session }); return false; } else if (session.error === "RefreshAccessTokenError") { - console.error("Refreshing access token failed... require login again"); + logger.error("refresh_access_token_failed", { + exception: "Refreshing access token failed... require login again", + }); return false; } else { return true; From bcf5d451553015c8fc8f2cfb4024539036c53f6a Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Tue, 16 Jul 2024 10:20:48 -0700 Subject: [PATCH 090/137] fix: generic naming --- .github/workflows/production_deploy.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/production_deploy.yml b/.github/workflows/production_deploy.yml index ab74e303aa2..3e29b551b4b 100644 --- a/.github/workflows/production_deploy.yml +++ b/.github/workflows/production_deploy.yml @@ -10,8 +10,8 @@ on: options: - stage - prod - commitSha: - description: 'Commit Sha to deploy' + originalImageTag: + description: 'The original image tag that has been deployed to dockerhub' required: true type: string env: @@ -31,10 +31,10 @@ jobs: password: ${{ secrets.DOCKER_PASSWORD }} - name: Pull Docker image - run: docker pull ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.commitSha }} + run: docker pull ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.originalImageTag }} - name: Retag image - run: docker tag ${{ env.DOCKER_IMAGE_NAME }}${{ inputs.commitSha }} ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.environment }}-${{ inputs.commitSha }} + run: docker tag ${{ env.DOCKER_IMAGE_NAME }}${{ inputs.originalImageTag }} ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.environment }}-${{ inputs.originalImageTag }} - name: Redeploy image - run: docker push ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.environment }}-${{ inputs.commitSha }} \ No newline at end of file + run: docker push ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.environment }}-${{ inputs.originalImageTag }} \ No newline at end of file From 5817568cf402ca2c943279b33188c9ba0a6623d3 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Tue, 16 Jul 2024 23:09:18 -0700 Subject: [PATCH 091/137] only mocks --- .github/workflows/e2e_cron.yml | 3 +- .github/workflows/e2e_pr.yml | 11 +- .../(authenticated)/e2e/ResetButton.tsx | 45 ----- .../(redesign)/(authenticated)/e2e/page.tsx | 38 ----- src/e2e/pages/dashBoardPage.ts | 3 - src/e2e/pages/purchasePage.ts | 4 +- src/e2e/specs/auth.spec.ts | 5 +- src/e2e/specs/breachResolution.spec.ts | 2 +- src/e2e/specs/dashboard.spec.ts | 154 ++---------------- src/e2e/specs/landing.spec.ts | 22 ++- src/e2e/specs/settings.spec.ts | 2 +- src/e2e/utils/helpers.ts | 40 +---- 12 files changed, 49 insertions(+), 280 deletions(-) delete mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/e2e/ResetButton.tsx delete mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/e2e/page.tsx diff --git a/.github/workflows/e2e_cron.yml b/.github/workflows/e2e_cron.yml index d8f1b39e412..1e4b93b7d8e 100644 --- a/.github/workflows/e2e_cron.yml +++ b/.github/workflows/e2e_cron.yml @@ -52,8 +52,7 @@ jobs: E2E_TEST_BASE_URL: ${{ secrets.E2E_TEST_BASE_URL }} E2E_TEST_ACCOUNT_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL }} E2E_TEST_ACCOUNT_PASSWORD: ${{ secrets.E2E_TEST_ACCOUNT_PASSWORD }} - E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS }} - E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS: : ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS }} + E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES }} E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED }} E2E_TEST_PAYPAL_LOGIN: ${{ secrets.E2E_TEST_PAYPAL_LOGIN }} E2E_TEST_PAYPAL_PASSWORD: ${{ secrets.E2E_TEST_PAYPAL_PASSWORD }} diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 3a1ca48ddfd..8dca4b381d7 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -61,13 +61,12 @@ jobs: - name: Run Playwright tests if: github.actor != 'dependabot[bot]' run: npm run e2e:smoke - timeout-minutes: 30 + timeout-minutes: 10 env: - E2E_TEST_ENV: 'local' - E2E_TEST_BASE_URL: http://localhost:6060 + E2E_TEST_ENV: ${{ inputs.environment != null && inputs.environment || 'local' }} + E2E_TEST_BASE_URL: ${{ secrets.E2E_TEST_BASE_URL }} E2E_TEST_ACCOUNT_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL }} - E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS }} - E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS }} + E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES }} E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED }} E2E_TEST_ACCOUNT_PASSWORD: ${{ secrets.E2E_TEST_ACCOUNT_PASSWORD }} E2E_TEST_PAYPAL_LOGIN: ${{ secrets.E2E_TEST_PAYPAL_LOGIN }} @@ -75,13 +74,11 @@ jobs: ADMINS: ${{ secrets.ADMINS }} OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }} ONEREP_API_KEY: ${{ secrets.ONEREP_API_KEY }} - ONEREP_API_BASE: 'http://localhost:6060/api/mock/onerep/' NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }} DATABASE_URL: postgres://postgres:postgres@localhost:5432/blurts HIBP_KANON_API_TOKEN: ${{ secrets.HIBP_KANON_API_TOKEN }} HIBP_API_TOKEN: ${{ secrets.HIBP_API_TOKEN }} - HIBP_KANON_API_ROOT: 'http://localhost:6060/api/mock/hibp' # Our tests are currently set up to expect accounts to act like # old user accounts, so let's pretend they all are: BROKER_SCAN_RELEASE_DATE: "3000-12-31" diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/e2e/ResetButton.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/e2e/ResetButton.tsx deleted file mode 100644 index f1e6a8c4ce7..00000000000 --- a/src/app/(proper_react)/(redesign)/(authenticated)/e2e/ResetButton.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* 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/. */ - -"use client"; - -import { useState } from "react"; - -interface clearProps { - hibp: boolean; - onerep: boolean; -} - -const ResetButton = (props: clearProps) => { - const [isResolved, setIsResolved] = useState(false); - - const { hibp, onerep } = props; - - const makeClearReq = async () => { - const param1 = `${hibp ? "hibp=true" : ""}`; - const param2 = `${onerep ? "onerep=true" : ""}`; - let delim = ""; - if (param1 && param2) delim = "&"; - const params = param1 + delim + param2; - return await fetch(`/api/mock/resetTestData?` + params); - }; - - const handleClick = () => { - void makeClearReq().then((res) => { - console.log( - `Request to clear data reached! Status: ${res.status}, ${res.ok ? "ok" : "not ok."}`, - ); - setIsResolved(true); - }); - }; - - return ( -
- - {isResolved &&
Request was successful!
} -
- ); -}; - -export default ResetButton; diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/e2e/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/e2e/page.tsx deleted file mode 100644 index 84d96349291..00000000000 --- a/src/app/(proper_react)/(redesign)/(authenticated)/e2e/page.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* 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 { isTestEmail } from "../../../../api/utils/mockUtils"; -import { getServerSession } from "../../../../functions/server/getServerSession"; -import NotFound from "../../../../not-found"; -import ResetButton from "./ResetButton"; - -const DynamicConfig = async ({ - searchParams, -}: { - searchParams?: { [key: string]: string | string[] | undefined }; -}) => { - const session = await getServerSession(); - - if (!session) { - throw new Error("Session not found"); - } - - const showNotFound = !isTestEmail(session.user.email); - - // Check 'hibp' and 'onerep' in the searchParams object and compare to 'true' as string - const hibp = (searchParams?.hibp || "false") === "true"; - const onerep = (searchParams?.onerep || "false") === "true"; - - return ( -
- {showNotFound ? ( - - ) : ( - - )} -
- ); -}; - -export default DynamicConfig; diff --git a/src/e2e/pages/dashBoardPage.ts b/src/e2e/pages/dashBoardPage.ts index 3c54c4750e0..25f5939df65 100644 --- a/src/e2e/pages/dashBoardPage.ts +++ b/src/e2e/pages/dashBoardPage.ts @@ -82,7 +82,6 @@ export class DashboardPage { readonly overviewCard: Locator; readonly overviewCardSummary: Locator; readonly overviewCardFindings: Locator; - readonly chartSvgExposuresCount: Locator; readonly upsellScreenButton: Locator; readonly urlRegex: RegExp; @@ -250,8 +249,6 @@ export class DashboardPage { this.overviewCardFindings = page.locator( "[aria-label='Dashboard summary'] > div > h3", ); - this.chartSvgExposuresCount = - this.overviewCard.locator("figure > div > svg"); //regex this.urlRegex = /\/dashboard\/(fixed|action-needed)\/?/; diff --git a/src/e2e/pages/purchasePage.ts b/src/e2e/pages/purchasePage.ts index 4e1899953c1..e1ead13b02e 100644 --- a/src/e2e/pages/purchasePage.ts +++ b/src/e2e/pages/purchasePage.ts @@ -120,7 +120,9 @@ export class PurchasePage { const planDetails = removeUnicodeChars( (await this.planDetails.textContent()) as string, ); - expect(planDetails).toContain("yearly"); + expect(planDetails).toContain( + `${process.env.E2E_TEST_ENV === "prod" ? "yearly" : "every 2 months"}`, + ); } async gotoPurchaseFromDashboard( diff --git a/src/e2e/specs/auth.spec.ts b/src/e2e/specs/auth.spec.ts index a820b367893..92959b012d7 100644 --- a/src/e2e/specs/auth.spec.ts +++ b/src/e2e/specs/auth.spec.ts @@ -27,7 +27,10 @@ test.describe(`${process.env.E2E_TEST_ENV} - Authentication flow verification @s await authPage.signUp(randomEmail, page); // assert successful login - const successUrl = "/user/welcome"; + const successUrl = + process.env.E2E_TEST_ENV === "local" + ? "/user/dashboard" + : "/user/welcome"; expect(page.url()).toBe(`${process.env.E2E_TEST_BASE_URL}${successUrl}`); await testInfo.attach( diff --git a/src/e2e/specs/breachResolution.spec.ts b/src/e2e/specs/breachResolution.spec.ts index 11dadc4d928..e17370ed457 100644 --- a/src/e2e/specs/breachResolution.spec.ts +++ b/src/e2e/specs/breachResolution.spec.ts @@ -6,7 +6,7 @@ import { test, expect } from "../fixtures/basePage.js"; // bypass login test.use({ storageState: "./e2e/storageState.json" }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Headers @smoke`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Headers`, () => { test("Verify that the site header is displayed correctly for signed in users", async ({ dataBreachPage, }) => { diff --git a/src/e2e/specs/dashboard.spec.ts b/src/e2e/specs/dashboard.spec.ts index 59956149779..a72dba31d56 100644 --- a/src/e2e/specs/dashboard.spec.ts +++ b/src/e2e/specs/dashboard.spec.ts @@ -11,12 +11,7 @@ import { clickOnATagCheckDomain, escapeRegExp, forceLoginAs, - resetTestData, } from "../utils/helpers.js"; -import { - isUsingMockHIBPEndpoint, - isUsingMockONEREPndpoint, -} from "../../app/functions/universal/mock.js"; // bypass login test.use({ storageState: "./e2e/storageState.json" }); @@ -261,7 +256,7 @@ test.describe.skip( }, ); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Content @smoke`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Content`, () => { test.beforeEach(async ({ dashboardPage, page }) => { await dashboardPage.open(); @@ -341,7 +336,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Content @smoke }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Payment`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Payment`, () => { test.beforeEach(async ({ dashboardPage, page }) => { await dashboardPage.open(); @@ -378,7 +373,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Payment`, () = }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Breaches Scan, Continuous Protection, Data Profile Actions @smoke`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Breaches Scan, Continuous Protection, Data Profile Actions`, () => { test.use({ storageState: { cookies: [], origins: [] } }); test.beforeEach(async ({ landingPage, page, authPage, welcomePage }) => { @@ -456,7 +451,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Breaches Scan, }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Overview Card @smoke`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Overview Card`, () => { test.beforeEach(async ({ dashboardPage, page }) => { await dashboardPage.open(); @@ -609,7 +604,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Overview Card }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Footer @smoke`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Footer`, () => { test.beforeEach(async ({ dashboardPage, page }) => { await dashboardPage.open(); try { @@ -673,7 +668,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Footer @smoke` }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Navigation @smoke`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Navigation`, () => { test.beforeEach(async ({ dashboardPage, page }) => { await dashboardPage.open(); @@ -724,25 +719,21 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Navigation @sm }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Data Breaches @smoke`, () => { - test.use({ storageState: { cookies: [], origins: [] } }); +test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Data Breaches`, () => { + test.beforeEach(async ({ landingPage, page, authPage }) => { + const emailToUse = process.env + .E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED as string; + const pwdToUse = process.env.E2E_TEST_ACCOUNT_PASSWORD as string; + expect(emailToUse).not.toBeUndefined(); + expect(pwdToUse).not.toBeUndefined(); + await forceLoginAs(emailToUse, pwdToUse, page, landingPage, authPage); + }); test("Verify that the High risk data breaches step is displayed correctly", async ({ dashboardPage, dataBrokersPage, page, - landingPage, - authPage, }) => { - const emailToUse = process.env.E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED!; - const pwdToUse = process.env.E2E_TEST_ACCOUNT_PASSWORD!; - expect(emailToUse).not.toBeUndefined(); - expect(pwdToUse).not.toBeUndefined(); - await forceLoginAs(emailToUse, pwdToUse, page, landingPage, authPage); - - if (isUsingMockHIBPEndpoint()) await resetTestData(page, true, false); - if (isUsingMockONEREPndpoint()) await resetTestData(page, false, true); - test.info().annotations.push({ type: "testrail", description: @@ -764,119 +755,4 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Data Breaches highRiskDataBreachLi.locator("div").getByText("High risk data breaches"), ).toBeVisible(); }); - - test("Verify that the dashboard is displayed correctly for users with no scan results and no breaches", async ({ - dashboardPage, - page, - authPage, - landingPage, - }) => { - if (!isUsingMockHIBPEndpoint() || !isUsingMockONEREPndpoint()) return; - - test.info().annotations.push({ - type: "testrail", - description: - "https://testrail.stage.mozaws.net/index.php?/cases/view/2463610", - }); - - const emailToUse = - process.env.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES_ZERO_BROKERS!; - const pwdToUse = process.env.E2E_TEST_ACCOUNT_PASSWORD!; - expect(emailToUse).not.toBeUndefined(); - expect(pwdToUse).not.toBeUndefined(); - await forceLoginAs(emailToUse, pwdToUse, page, landingPage, authPage); - await resetTestData(page, true, true); - await dashboardPage.open(); - - await expect(dashboardPage.overviewCard).toBeVisible(); - const textArea = dashboardPage.overviewCard.locator("section"); - await expect(textArea.getByText(/No exposures found/)).toBeVisible(); - await expect( - textArea.getByText( - /Great news! We searched all known data breaches and .\d+. data broker sites that sell personal info and found no exposures\./, - ), - ).toBeVisible(); - await expect(textArea.getByRole("button")).toBeVisible(); - expect(await dashboardPage.overviewCard.locator("svg").count()).toBe(5); - expect(await dashboardPage.overviewCard.locator("circle").count()).toBe(4); - await expect( - dashboardPage.chartSvgExposuresCount.getByText("Exposures"), - ).toBeVisible(); - await expect( - dashboardPage.chartSvgExposuresCount.getByText("0"), - ).toBeVisible(); - - await expect(dashboardPage.exposuresHeading).toBeVisible(); - expect(await dashboardPage.exposuresHeading.textContent()).toBe( - "View all sites where your info is exposed", - ); - - const noExpFoundMsg = page - .locator("div > strong") - .getByText("No exposures found"); - await expect(noExpFoundMsg).toBeVisible(); - }); - - test("Verify that the dashboard is displayed correctly for users with no scan results and with data breaches", async ({ - dashboardPage, - page, - authPage, - landingPage, - }) => { - if (!isUsingMockHIBPEndpoint() || !isUsingMockONEREPndpoint()) return; - - test.info().annotations.push({ - type: "testrail", - description: - "https://testrail.stage.mozaws.net/index.php?/cases/view/2463611", - }); - - const emailToUse = process.env.E2E_TEST_ACCOUNT_EMAIL_ZERO_BROKERS!; - const pwdToUse = process.env.E2E_TEST_ACCOUNT_PASSWORD!; - expect(emailToUse).not.toBeUndefined(); - expect(pwdToUse).not.toBeUndefined(); - await forceLoginAs(emailToUse, pwdToUse, page, landingPage, authPage); - await resetTestData(page, true, true); - await dashboardPage.open(); - - // Assertions for the overview card - await expect(dashboardPage.overviewCard).toBeVisible(); - await expect( - page.getByText( - /You still have .\d+. exposures left to fix. Keep going and protect yourself\. We.ll guide you step-by-step\./, - ), - ).toBeVisible(); - await expect(dashboardPage.upsellScreenButton).toBeVisible(); - - // Chart reflecting results - await expect( - dashboardPage.chartSvgExposuresCount.getByText("Exposures"), - ).toBeVisible(); - await expect( - dashboardPage.chartSvgExposuresCount.getByText(/\d+/), - ).toBeVisible(); - - // Text above exposures list - await expect( - page.getByText("View all sites where your info is exposed"), - ).toBeVisible(); - await expect( - page.getByText( - /We found your information exposed .\d+. times over .\d+. data breaches and .0. data broker sites that are selling your personal info\./, - ), - ).toBeVisible(); - - // Exposures list - const exposureList = page.locator('[class*="exposureList"]'); - await expect(exposureList).toBeVisible(); - expect(await exposureList.locator("li").count()).toBeGreaterThan(0); - - // Click the "Let's keep going" button and check the redirection - await dashboardPage.upsellScreenButton.click(); - await page.waitForURL(/.*\/user\/dashboard\/fix.*/); - const dataBrokerFixed = page - .locator('[class*="FixNavigation"][class*="isCompleted"]') - .getByText("Data broker profiles"); - await expect(dataBrokerFixed).toBeVisible(); - }); }); diff --git a/src/e2e/specs/landing.spec.ts b/src/e2e/specs/landing.spec.ts index 141dfd12707..acb202d4656 100644 --- a/src/e2e/specs/landing.spec.ts +++ b/src/e2e/specs/landing.spec.ts @@ -9,7 +9,7 @@ import { } from "../utils/helpers.js"; test.describe.configure({ mode: "parallel" }); -test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page content @smoke`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page content`, () => { test.beforeEach(async ({ landingPage }) => { await landingPage.open(); }); @@ -211,7 +211,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page content @sm }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionality - without existing Account @smoke`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionality - without existing Account`, () => { test.beforeEach(async ({ landingPage }) => { await landingPage.open(); }); @@ -281,7 +281,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionality - with existing account @smoke`, () => { +test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionality - with existing account`, () => { test.beforeEach(async ({ landingPage }) => { await landingPage.open(); }); @@ -309,7 +309,13 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali await authPage.enterPassword(); // verify dashboard redirect - const successUrl = process.env.E2E_TEST_BASE_URL + "/user/dashboard"; + const successUrl = + process.env.E2E_TEST_BASE_URL + + `${ + process.env.E2E_TEST_ENV === "local" + ? "/user/welcome" + : "/user/dashboard" + }`; expect(page.url()).toBe(successUrl); }); @@ -330,7 +336,13 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali await authPage.enterPassword(); // verify dashboard redirect - const successUrl = process.env.E2E_TEST_BASE_URL + "/user/dashboard"; + const successUrl = + process.env.E2E_TEST_BASE_URL + + `${ + process.env.E2E_TEST_ENV === "local" + ? "/user/welcome" + : "/user/dashboard" + }`; expect(page.url()).toBe(successUrl); }); }); diff --git a/src/e2e/specs/settings.spec.ts b/src/e2e/specs/settings.spec.ts index 6377a26f83c..e86390f5616 100644 --- a/src/e2e/specs/settings.spec.ts +++ b/src/e2e/specs/settings.spec.ts @@ -6,7 +6,7 @@ import { test, expect } from "../fixtures/basePage.js"; // bypass login test.use({ storageState: "./e2e/storageState.json" }); -test.describe(`${process.env.E2E_TEST_ENV} Settings Page @smoke`, () => { +test.describe(`${process.env.E2E_TEST_ENV} Settings Page`, () => { test("Verify settings page loads", async ({ settingsPage }) => { // should go directly to data breach page await settingsPage.open(); diff --git a/src/e2e/utils/helpers.ts b/src/e2e/utils/helpers.ts index 412336f2306..0d398436dd0 100644 --- a/src/e2e/utils/helpers.ts +++ b/src/e2e/utils/helpers.ts @@ -170,9 +170,7 @@ export const clickOnATagCheckDomain = async ( page: Page, ) => { if (typeof host === "string") - host = new RegExp( - escapeRegExp(host.replace(/^(https?:\/\/)/, "").replace(/:\d+$/, "")), - ); + host = new RegExp(escapeRegExp(host.replace(/^(https?:\/\/)/, ""))); if (typeof path === "string") path = new RegExp(".*" + path + ".*"); const href = await aTag.getAttribute("href"); @@ -208,9 +206,6 @@ export const forceLoginAs = async ( await route.abort(); }); await page.context().clearCookies(); - await page - .context() - .addInitScript({ content: "window.localStorage.clear()" }); await landingPage.open(); await landingPage.goToSignIn(); let visible = true; @@ -224,35 +219,6 @@ export const forceLoginAs = async ( await page.waitForURL(/^(?!.*signin).*/); } await authPage.signIn(email, password); - const dashboardRegex = /.*\/user\/dashboard.*/; - if (dashboardRegex.test(page.url())) return; - await page.waitForURL(dashboardRegex); -}; - -export const resetTestData = async ( - page: Page, - hibp: boolean, - onerep: boolean, -) => { - const baseUrl = process.env.SERVER_URL!; - const param1 = `${hibp ? "hibp=true" : ""}`; - const param2 = `${onerep ? "onerep=true" : ""}`; - let delim = ""; - if (param1 && param2) delim = "&"; - const params = param1 + delim + param2; - await page.goto(baseUrl + "/e2e?" + params); - await page.waitForURL(/.*\/e2e*/); - - const clearDataButton = page.locator("button", { hasText: "Clear Data" }); - await expect(clearDataButton).toBeVisible(); - await clearDataButton.click(); - - const selectorQuery = '//div[contains(text(), "Request was successful")]'; - - await page.waitForSelector(selectorQuery); - - const successMessage = page.locator(selectorQuery); - await expect(successMessage).toBeVisible(); - - await page.goBack(); + await page.waitForURL("**/user/dashboard"); + await expect(page).toHaveURL(/.*\/user\/dashboard.*/); }; From e930d33c565f3c6d9597f2ff4602e9b610a37d17 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Wed, 17 Jul 2024 11:39:38 +0200 Subject: [PATCH 092/137] fix: e2e disable landing page experiment locally --- config/nimbus.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/nimbus.yaml b/config/nimbus.yaml index a2583065915..9da8af63541 100644 --- a/config/nimbus.yaml +++ b/config/nimbus.yaml @@ -61,8 +61,8 @@ features: defaults: - channel: local value: { - "enabled": true, - "variant": ctaOnly, + "enabled": false, + "variant": ctaWithEmail, } - channel: staging value: { From 7a6653b33c18b543138852e5a514d3eff42dd45d Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Wed, 17 Jul 2024 12:32:47 +0200 Subject: [PATCH 093/137] fix: Remove invalid OAuth parameter service --- src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx | 1 - src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx index a9b466da21a..be4ddc2d119 100644 --- a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx @@ -41,7 +41,6 @@ export function getAttributionSearchParams({ { entrypoint: "monitor.mozilla.org-monitor-product-page", form_type: typeof emailInput === "string" ? "email" : "button", - service: process.env.OAUTH_CLIENT_ID as string, ...(emailInput && { email: emailInput }), ...(experimentData && experimentData["landing-page-free-scan-cta"].enabled && { diff --git a/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx b/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx index 96c793c49b8..cff25795f40 100644 --- a/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx @@ -1015,7 +1015,6 @@ describe("Free scan CTA experiment", () => { [ "entrypoint=monitor.mozilla.org-monitor-product-page", "form_type=email", - "service=edd29a80019d61a1", "email=mail%40example.com", "entrypoint_experiment=landing-page-free-scan-cta", "entrypoint_variation=ctaWithEmail", From 938739f9f1df6dd962c9836ad62ba5fb4a326a69 Mon Sep 17 00:00:00 2001 From: mozilla-pontoon Date: Wed, 17 Jul 2024 12:01:37 +0000 Subject: [PATCH 094/137] Import translations from l10n repository (2024-07-17) --- locales/kab/app.ftl | 1 + locales/kab/breaches.ftl | 6 +----- locales/kab/dashboard.ftl | 5 +++++ locales/kab/fix.ftl | 4 ++++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/locales/kab/app.ftl b/locales/kab/app.ftl index 697c683befd..e4c4ad42236 100644 --- a/locales/kab/app.ftl +++ b/locales/kab/app.ftl @@ -198,6 +198,7 @@ error-page-error-404-cta-button = Uɣal ## Breach overview page +all-breaches-headline-2 = Akk tirewliwin ttwafent-d s { -brand-fx-monitor } search-breaches = Nadi tirewliwin n yisefka ## Public breach detail page diff --git a/locales/kab/breaches.ftl b/locales/kab/breaches.ftl index b90890ad881..85fe8ae0820 100644 --- a/locales/kab/breaches.ftl +++ b/locales/kab/breaches.ftl @@ -2,11 +2,7 @@ # 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/. -## Breaches header - -## Breaches resolved filter - -## Breaches table +breach-all-meta-social-title = Akk tirewliwin ttwafent-d s { -brand-fx-monitor } ## Links that we might refer to when prompting the user to make changes after a breach diff --git a/locales/kab/dashboard.ftl b/locales/kab/dashboard.ftl index 410691e93a0..932b31bf5bf 100644 --- a/locales/kab/dashboard.ftl +++ b/locales/kab/dashboard.ftl @@ -25,13 +25,18 @@ dashboard-exposures-filter-date-found-last-seven-days = 7 n wussan ineggura dashboard-exposures-filter-date-found-last-thirty-days = 30 n wussan ineggura dashboard-exposures-filter-date-found-last-year = Aseggas yezrin dashboard-exposures-filter-status = Addad +popover-open-filter-settings-alt = Fren imsizedgen dashboard-exposures-filter-show-all = Sken akk dashboard-exposures-filter-show-results = Sken igmaḍ dashboard-exposures-filter-reset = Wennez ## Top banner on the dashboard +dashboard-top-banner-your-data-is-protected-title = Isefka-k ttummestnen +dashboard-top-banner-your-data-is-protected-cta = Wali acu i iṣeggmen dashboard-top-banner-lets-keep-protecting-cta = Ad nkemmel +dashboard-top-banner-protect-your-data-title = Aha ad nemmesten isefka-k +dashboard-top-banner-protect-your-data-cta = Ṣeggem-it # About Exposure Indicators Modal diff --git a/locales/kab/fix.ftl b/locales/kab/fix.ftl index 7002eaf15c0..41ff7b91a32 100644 --- a/locales/kab/fix.ftl +++ b/locales/kab/fix.ftl @@ -28,6 +28,7 @@ fix-flow-celebration-security-questions-title = Isestanen n tɣellist ttwammestn # High Risk Data Breaches +high-risk-breach-heading = Ha-t-a wayen ara tgeḍ # Variables # $breach_name is the name of the breach where the high risk data was found. # $breach_date is the date when the breach occurred. @@ -77,7 +78,10 @@ security-recommendation-email-title = Mmesten tansa-k·m n yimayl # Leaked Passwords +leaked-passwords-steps-title = Ha-t-a wayen ara tgeḍ +leaked-passwords-mark-as-fixed = Creḍ iṣeggem leaked-passwords-skip = Suref akka tura # Leaked Security Questions +leaked-security-questions-steps-title = Ha-t-a wayen ara tgeḍ From 72aebfbdd73746d43d2db3b84ff54b08902363eb Mon Sep 17 00:00:00 2001 From: Kaitlyn Andres Date: Wed, 17 Jul 2024 08:34:20 -0400 Subject: [PATCH 095/137] Update to check that a user has a monthly subscription (#4812) * Update to check that a user has a monthly subscription * prevent users with yearly plan ID from seeing the prompt * prevent users with a yearly plan id in their subscriptions from seeing the coupon * fix unit tests --------- Co-authored-by: mansaj --- .../user/(dashboard)/settings/CancelFlow.tsx | 4 +- .../settings/SettingsPage.test.tsx | 80 +++++++++---------- .../user/(dashboard)/settings/View.tsx | 4 +- .../user/(dashboard)/settings/page.tsx | 9 ++- src/app/functions/universal/user.ts | 24 ++++-- 5 files changed, 66 insertions(+), 55 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/CancelFlow.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/CancelFlow.tsx index 1fc6d0cd84b..6627be2622c 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/CancelFlow.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/CancelFlow.tsx @@ -26,7 +26,7 @@ export type Props = { fxaSubscriptionsUrl: string; enableDiscountCoupon: boolean; experimentData?: ExperimentData; - isYearlySubscriber: boolean; + isMonthlySubscriber: boolean; }; type DiscountData = { @@ -180,7 +180,7 @@ export const CancelFlow = (props: Props) => {

{props.enableDiscountCoupon && !alreadyHasCouponSet && - !props.isYearlySubscriber ? ( + props.isMonthlySubscriber ? ( <> { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -272,7 +272,7 @@ it("Add email address button is not shown when email limit of five reached", () subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -316,7 +316,7 @@ it("Add email address button is shown when fewer than five emails", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -352,7 +352,7 @@ it("preselects 'Send all breach alerts to the primary email address' if that's t subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -396,7 +396,7 @@ it("preselects 'Send breach alerts to the affected email address' if that's the subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -440,7 +440,7 @@ it("disables breach alert notification options if a user opts out of breach aler subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={["UpdatedEmailPreferencesOption"]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -494,7 +494,7 @@ it("preselects primary email alert option", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={["UpdatedEmailPreferencesOption"]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -534,7 +534,7 @@ it("unselects the breach alerts checkbox and sends a null value to the API", asy subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={["UpdatedEmailPreferencesOption"]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -590,7 +590,7 @@ it("preselects the affected email comms option after a user decides to enable br subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={["UpdatedEmailPreferencesOption"]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -637,7 +637,7 @@ it("sends a call to the API to change the email alert preferences when changing subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={["UpdatedEmailPreferencesOption"]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -697,7 +697,7 @@ it("checks that monthly monitor report is enabled", () => { "MonthlyActivityEmail", ]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -744,7 +744,7 @@ it("sends an API call to disable monthly monitor reports", async () => { "MonthlyActivityEmail", ]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -790,7 +790,7 @@ it("refreshes the session token after changing email alert preferences, to ensur subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -826,7 +826,7 @@ it("marks unverified email addresses as such", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -863,7 +863,7 @@ it("calls the API to resend a verification email if requested to", async () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -910,7 +910,7 @@ it("calls the 'remove' action when clicking the rubbish bin icon", async () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -947,7 +947,7 @@ it("hides the Plus cancellation link if the user doesn't have Plus", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -983,7 +983,7 @@ it("shows the Plus cancellation link if the user has Plus", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1026,7 +1026,7 @@ it("takes you through the cancellation dialog flow all the way to subplat", asyn subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={["ConfirmCancellation", "CancellationFlow"]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1104,7 +1104,7 @@ it("closes the cancellation survey if the user selects nevermind, take me back", subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={["ConfirmCancellation", "CancellationFlow"]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1161,7 +1161,7 @@ it("closes the cancellation dialog", async () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={["CancellationFlow"]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1211,7 +1211,7 @@ it("shows the account deletion button if the user does not have Plus", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1252,7 +1252,7 @@ it("warns about the consequences before deleting a free user's account", async ( subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1295,7 +1295,7 @@ it("shows a loading state while account deletion is in progress", async () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1339,7 +1339,7 @@ it("shows the account deletion button if the user has Plus", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1380,7 +1380,7 @@ it("warns about the consequences before deleting a Plus user's account", async ( subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1432,7 +1432,7 @@ it.skip("calls the 'add' action when adding another email address", async () => subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1467,7 +1467,7 @@ describe("to learn about usage", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1511,7 +1511,7 @@ describe("to learn about usage", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1555,7 +1555,7 @@ describe("to learn about usage", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1600,7 +1600,7 @@ describe("to learn about usage", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1644,7 +1644,7 @@ describe("to learn about usage", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1693,7 +1693,7 @@ describe("to learn about usage", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1738,7 +1738,7 @@ describe("to learn about usage", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1787,7 +1787,7 @@ describe("to learn about usage", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1831,7 +1831,7 @@ describe("to learn about usage", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1880,7 +1880,7 @@ describe("to learn about usage", () => { subscriptionBillingAmount={mockedSubscriptionBillingAmount} enabledFeatureFlags={[]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -1941,7 +1941,7 @@ it("selects the coupon code discount cta and shows the all-set dialog step", asy "DiscountCouponNextThreeMonths", ]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -2027,7 +2027,7 @@ it("shows error message if the applying the coupon code function was unsuccessfu "DiscountCouponNextThreeMonths", ]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); @@ -2093,7 +2093,7 @@ it("does not show the coupon code if a user already has a coupon set", async () "DiscountCouponNextThreeMonths", ]} experimentData={defaultExperimentData} - isYearlySubscriber={false} + isMonthlySubscriber={true} /> , ); diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/View.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/View.tsx index 1ed24060d0c..bcec42408bc 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/View.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/View.tsx @@ -40,7 +40,7 @@ export type Props = { enabledFeatureFlags: FeatureFlagName[]; experimentData: ExperimentData; lastScanDate?: Date; - isYearlySubscriber: boolean; + isMonthlySubscriber: boolean; }; export const SettingsView = (props: Props) => { @@ -112,7 +112,7 @@ export const SettingsView = (props: Props) => { )} fxaSubscriptionsUrl={props.fxaSubscriptionsUrl} experimentData={props.experimentData} - isYearlySubscriber={props.isYearlySubscriber} + isMonthlySubscriber={props.isMonthlySubscriber} /> ) : ( ); } diff --git a/src/app/functions/universal/user.ts b/src/app/functions/universal/user.ts index 738b3a414df..3a406cbafa7 100644 --- a/src/app/functions/universal/user.ts +++ b/src/app/functions/universal/user.ts @@ -43,23 +43,28 @@ export function meetsAgeRequirement(dateOfBirth: ISO8601DateString): boolean { } /* c8 ignore start */ -export async function checkUserHasYearlySubscription(user: Session["user"]) { - if ( - !user.subscriber?.fxa_access_token || - !process.env.PREMIUM_PLAN_ID_YEARLY_US - ) { +export async function checkUserHasMonthlySubscription(user: Session["user"]) { + if (!user.subscriber?.fxa_access_token) { console.error("FXA token not set"); return false; } + if (!process.env.PREMIUM_PLAN_ID_MONTHLY_US) { + console.error("Monthly Plan ID not set"); + return false; + } + const billingAndSubscriptionInfo = await getBillingAndSubscriptions( user.subscriber.fxa_access_token, ); + if (billingAndSubscriptionInfo === null) { return false; } - const yearlyPlanId: string = process.env.PREMIUM_PLAN_ID_YEARLY_US; + const monthlyPlanId = process.env.PREMIUM_PLAN_ID_MONTHLY_US; + const yearlyPlanId = process.env.PREMIUM_PLAN_ID_YEARLY_US ?? ""; + const subscriptions = billingAndSubscriptionInfo.subscriptions; const planIds: string[] = []; @@ -67,6 +72,11 @@ export async function checkUserHasYearlySubscription(user: Session["user"]) { planIds.push(subscription.plan_id); }); - return planIds.includes(yearlyPlanId); + if (planIds.includes(yearlyPlanId)) { + console.error("User has yearly plan set"); + return false; + } + + return planIds.includes(monthlyPlanId); } /* c8 ignore stop */ From bb1e38cd08c1945b20b497caeff69a65b8329629 Mon Sep 17 00:00:00 2001 From: Vincent Date: Mon, 15 Jul 2024 14:19:41 +0200 Subject: [PATCH 096/137] Port breach data fetching cronjob to TS --- package.json | 3 ++- src/db/tables/breaches.js | 4 ++-- .../syncBreaches.ts} | 22 +++++++++---------- src/scripts/s3.js | 6 ++++- 4 files changed, 20 insertions(+), 15 deletions(-) rename src/scripts/{syncBreaches.js => cronjobs/syncBreaches.ts} (86%) diff --git a/package.json b/package.json index 5633cbe4892..a2ef69c894c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dev:cron:first-data-broker-removal-fixed": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/firstDataBrokerRemovalFixed.tsx", "dev:cron:monthly-activity": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/monthlyActivity.tsx", "dev:cron:db-delete-unverified-subscribers": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/deleteUnverifiedSubscribers.ts", + "dev:cron:db-pull-breaches": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/syncBreaches.ts", "dev:cron:onerep-limits-alert": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/onerepStatsAlert.ts", "dev:nimbus": "node --watch-path config/nimbus.yaml src/scripts/build/nimbusTypes.js", "build": "npm run get-location-data && npm run build-glean && npm run build-nimbus && next build && npm run build-cronjobs", @@ -27,7 +28,7 @@ "cron:monthly-activity": "node dist/scripts/cronjobs/monthlyActivity.js", "cron:breach-alerts": "node src/scripts/emailBreachAlerts.js", "cron:db-delete-unverified-subscribers": "node dist/scripts/cronjobs/deleteUnverifiedSubscribers.js", - "cron:db-pull-breaches": "node src/scripts/syncBreaches.js", + "cron:db-pull-breaches": "node dist/scripts/cronjobs/syncBreaches.js", "cron:remote-settings-pull-breaches": "node scripts/updatebreaches.js", "cron:onerep-limits-alert": "node dist/scripts/cronjobs/onerepStatsAlert.js", "db:migrate": "node -r dotenv-flow/config node_modules/knex/bin/cli.js migrate:latest --knexfile src/db/knexfile.js", diff --git a/src/db/tables/breaches.js b/src/db/tables/breaches.js index 9cbf75cc0cf..9930120015c 100644 --- a/src/db/tables/breaches.js +++ b/src/db/tables/breaches.js @@ -88,8 +88,8 @@ async function upsertBreaches(hibpBreaches) { /** * Update logo path of a breach by name * - * @param {string} name - * @param {string} faviconUrl + * @param {string} name + * @param {string | null} faviconUrl */ // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy /* c8 ignore start */ diff --git a/src/scripts/syncBreaches.js b/src/scripts/cronjobs/syncBreaches.ts similarity index 86% rename from src/scripts/syncBreaches.js rename to src/scripts/cronjobs/syncBreaches.ts index d8eafb61068..02d7e4a411e 100644 --- a/src/scripts/syncBreaches.js +++ b/src/scripts/cronjobs/syncBreaches.ts @@ -12,13 +12,14 @@ import { readdir } from "node:fs/promises"; import os from "node:os"; import Sentry from "@sentry/nextjs"; -import { req, formatDataClassesArray } from "../utils/hibp.js"; +import { req, formatDataClassesArray } from "../../utils/hibp.js"; import { getAllBreaches, upsertBreaches, updateBreachFaviconUrl, -} from "../db/tables/breaches.js"; -import { uploadToS3 } from "./s3.js"; +} from "../../db/tables/breaches.js"; +import { uploadToS3 } from "../s3.js"; +import type { Breach } from "../../app/functions/universal/breach.js"; const SENTRY_SLUG = "cron-sync-breaches"; @@ -32,7 +33,7 @@ const checkInId = Sentry.captureCheckIn({ status: "in_progress", }); -export async function getBreachIcons(breaches) { +export async function getBreachIcons(breaches: Breach[]) { // make logofolder if it doesn't exist const logoFolder = os.tmpdir(); console.log(`Logo folder: ${logoFolder}`); @@ -82,20 +83,19 @@ export async function getBreachIcons(breaches) { } // Get breaches and upserts to DB -const breachesResponse = await req("/breaches"); -const breaches = []; +const breachesResponse: Breach[] = await req("/breaches"); +const breaches: Breach[] = []; const seen = new Set(); for (const breach of breachesResponse) { breach.DataClasses = formatDataClassesArray(breach.DataClasses); - breach.LogoPath = /[^/]*$/.exec(breach.LogoPath)[0]; + breach.LogoPath = /[^/]*$/.exec(breach.LogoPath)![0]; breaches.push(breach); seen.add(breach.Name + breach.BreachDate); // sanity check: corrupt data structure if (!isValidBreach(breach)) throw new Error( - "Breach data structure is not valid", - JSON.stringify(breach), + "Breach data structure is not valid: " + JSON.stringify(breach), ); } @@ -128,10 +128,10 @@ setTimeout(process.exit, 1000); /** * Null check for some required field * - * @param {object} breach breach object from HIBP + * @param breach breach object from HIBP * @returns Boolean is it a valid breach */ -function isValidBreach(breach) { +function isValidBreach(breach: Breach) { return ( breach.Name !== undefined && breach.BreachDate !== undefined && diff --git a/src/scripts/s3.js b/src/scripts/s3.js index 3a40eda17cb..11f5fcc7874 100644 --- a/src/scripts/s3.js +++ b/src/scripts/s3.js @@ -24,6 +24,10 @@ const s3 = new S3({ }, }); +/** + * @param {string} fileName + * @param {Buffer} fileStream + */ export async function uploadToS3(fileName, fileStream) { console.log("Attempt to upload to s3: ", fileName); const uploadParams = { @@ -37,7 +41,7 @@ export async function uploadToS3(fileName, fileStream) { params: uploadParams, }).done(); console.log("Successfully uploaded data to " + Bucket + "/" + fileName); - } catch (err) { + } catch (/** @type {any} */ err) { console.error(err, err.stack); } } From eacb3b7a3f4ed47e5ae0cfff9ef6fbf6116c1439 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Wed, 17 Jul 2024 08:28:56 -0700 Subject: [PATCH 097/137] fix: yearly pricing default var --- .env.local.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.local.example b/.env.local.example index 74190266b0b..0fb8bef968b 100644 --- a/.env.local.example +++ b/.env.local.example @@ -58,7 +58,7 @@ NEXT_PUBLIC_SENTRY_DSN=https://573f784b5cc7481ebf8c0c385d2ad776@o1069899.ingest. # SubPlat product and plan IDs, used for Plus subscriptions: PREMIUM_PRODUCT_ID=prod_NErZh679W62lai PREMIUM_PLAN_ID_MONTHLY_US=price_1MUNq0Kb9q6OnNsL4BoJgepf -PREMIUM_PLAN_ID_YEARLY_US=price_1MUNq0Kb9q6OnNsL4BoJgepf +PREMIUM_PLAN_ID_YEARLY_US=price_1NvqawKb9q6OnNsLRTnYrtrV # Mozilla Accounts URLs FXA_SUBSCRIPTIONS_URL=https://accounts.stage.mozaws.net/subscriptions From f887e2d66217d112c9748e47facde4aecd975744 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Wed, 17 Jul 2024 09:14:21 -0700 Subject: [PATCH 098/137] fix: account uri --- .env.local.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.local.example b/.env.local.example index 0fb8bef968b..1a82ca694a7 100644 --- a/.env.local.example +++ b/.env.local.example @@ -69,7 +69,7 @@ OAUTH_API_URI="https://api-accounts.stage.mozaws.net/v1" OAUTH_AUTHORIZATION_URI=https://accounts.stage.mozaws.net/authorization OAUTH_PROFILE_URI=https://profile.stage.mozaws.net/v1/profile OAUTH_TOKEN_URI=https://oauth.stage.mozaws.net/v1/token -OAUTH_ACCOUNT_URI = "https://oauth.accounts.firefox.com/v1" +OAUTH_ACCOUNT_URI=https://oauth.stage.mozaws.net/v1 # Which environment to run end-to-end tests against: E2E_TEST_ENV=local From 66d1054a83b40d07714ea1de9b0ea6143c8e3cc3 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Wed, 17 Jul 2024 22:47:27 -0700 Subject: [PATCH 099/137] addressed comments and added safe fallbacks --- src/app/api/mock/hibp/config/defaults.ts | 2 +- .../hibp/range/search/[hashPrefix]/route.ts | 3 +- src/app/api/mock/onerep/config/config.ts | 70 +++++++++--------- .../api/mock/onerep/mockData/mockUser.json | 20 +++++ .../mock/onerep/profiles/[profileId]/route.ts | 26 +++---- .../[profileId]/scans/[scanId]/route.ts | 39 +++++----- .../profiles/[profileId]/scans/route.ts | 74 ++++++++++--------- src/app/api/mock/onerep/profiles/route.ts | 14 ++-- src/app/api/mock/onerep/scan-results/route.ts | 9 +-- src/app/api/utils/errorThrower.ts | 32 ++++---- src/app/api/utils/mockUtils.ts | 4 +- src/app/functions/server/onerep.ts | 4 +- src/app/functions/universal/mock.ts | 2 +- 13 files changed, 159 insertions(+), 140 deletions(-) diff --git a/src/app/api/mock/hibp/config/defaults.ts b/src/app/api/mock/hibp/config/defaults.ts index 86ec3073c66..1ccbc0ba12b 100644 --- a/src/app/api/mock/hibp/config/defaults.ts +++ b/src/app/api/mock/hibp/config/defaults.ts @@ -11,5 +11,5 @@ export interface BreachMap { export const getBreachesForHash = (hash: string) => { const key = hashToEmailKeyMap[hash] || "default"; - return (mockBreaches as BreachMap)[key]; + return (mockBreaches as BreachMap)[key] || []; }; diff --git a/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts b/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts index 3956d8a2230..53786210bc7 100644 --- a/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts +++ b/src/app/api/mock/hibp/range/search/[hashPrefix]/route.ts @@ -30,6 +30,5 @@ export function GET( websites: breachesList, }, ]; - const res = NextResponse.json(data); - return res; + return NextResponse.json(data); } diff --git a/src/app/api/mock/onerep/config/config.ts b/src/app/api/mock/onerep/config/config.ts index dcd1d219032..1e5bd4417a5 100644 --- a/src/app/api/mock/onerep/config/config.ts +++ b/src/app/api/mock/onerep/config/config.ts @@ -5,7 +5,7 @@ import { BinaryLike, createHash } from "crypto"; import { StateAbbr } from "../../../../../utils/states"; import MockUser from "../mockData/mockUser.json"; -import { computeSha1First6, hashToEmailKeyMap } from "../../../utils/mockUtils"; +import { emailHashPrefix, hashToEmailKeyMap } from "../../../utils/mockUtils"; import { getLatestOnerepScan } from "../../../../../db/tables/onerep_scans"; export interface Broker { @@ -66,51 +66,51 @@ function hasher(plaintext: number | string) { return parseInt(last4BytesHex, 16); } -export function MOCK_ONEREP_SCAN_ID(profileId: number) { +export function mockOnerepScanId(profileId: number) { return hasher(profileId); } -export function MOCK_ONEREP_DATABROKER_ID_START(profileId: number) { +export function mockOnerepDatabrokerIdStart(profileId: number) { return hasher(profileId * MAGIC_NUM_1); } -export function MOCK_ONEREP_ID_START(profileId: number) { +export function mockOnerepIdStart(profileId: number) { return hasher(profileId * MAGIC_NUM_2); } -export function MOCK_ONEREP_TIME() { +export function mockOnerepTime() { return MockUser.TIME; } -export function MOCK_ONEREP_FIRSTNAME() { +export function mockOnerepFirstName() { return MockUser.FIRSTNAME; } -export function MOCK_ONEREP_LASTNAME() { +export function mockOnerepLastName() { return MockUser.LASTNAME; } -export function MOCK_ONEREP_BIRTHDATE() { +export function mockOnerepBirthdate() { return MockUser.BIRTHDATE; } -export function MOCK_ONEREP_EMAILS() { +export function mockOnerepEmails() { return MockUser.EMAILS; } -export function MOCK_ONEREP_PHONES() { +export function mockOnerepPhones() { return MockUser.PHONES; } -export function MOCK_ONEREP_RELATIVES() { +export function mockOnerepRelatives() { return MockUser.RELATIVES; } -export function MOCK_ONEREP_PROFILE_STATUS() { +export function mockOnerepProfileStatus() { return MockUser.STATUS as "active" | "inactive"; } -export function MOCK_ONEREP_ADDRESSES() { +export function mockOnerepAddresses() { type typeOfAddr = [{ city: string; state: StateAbbr }]; return MockUser.ADDRESSES.map((address) => ({ @@ -119,7 +119,7 @@ export function MOCK_ONEREP_ADDRESSES() { })) as typeOfAddr; } -export function MOCK_ONEREP_OBJECT_META(page: number | string = 1) { +export function mockOnerepObjectMeta(page: number | string = 1) { if (typeof page === "string") page = parseInt(page); return { current_page: page, @@ -132,7 +132,7 @@ export function MOCK_ONEREP_OBJECT_META(page: number | string = 1) { }; } -export function MOCK_ONEREP_OBJECT_LINKS( +export function mockOnerepObjectLinks( profileId: number | string, page: number | string = 1, perPage: number | string = 100, @@ -149,46 +149,50 @@ export function MOCK_ONEREP_OBJECT_LINKS( }; } -export async function MOCK_ONEREP_BROKERS( +export async function mockOnerepBrokers( profileId: number, page: string, perPage: string, email: string, ) { let scanId = (await getLatestOnerepScan(profileId))?.onerep_scan_id; - if (!scanId) scanId = MOCK_ONEREP_SCAN_ID(profileId); - const mockMeta = MOCK_ONEREP_OBJECT_META(page); - const mockLinks = MOCK_ONEREP_OBJECT_LINKS(profileId, page, perPage); - const idStart = MOCK_ONEREP_ID_START(profileId); - const idStartDataBroker = MOCK_ONEREP_DATABROKER_ID_START(profileId); + if (!scanId) scanId = mockOnerepScanId(profileId); + const mockMeta = mockOnerepObjectMeta(page); + const mockLinks = mockOnerepObjectLinks(profileId, page, perPage); + const idStart = mockOnerepIdStart(profileId); + const idStartDataBroker = mockOnerepDatabrokerIdStart(profileId); - const emailHash = computeSha1First6(email); + const emailHash = emailHashPrefix(email); const brokersListMap = MockUser.BROKERS_LIST as BrokerMap; const datasetKey = hashToEmailKeyMap[emailHash] || "default"; - const brokersList = brokersListMap[datasetKey]; + const brokersListLookup = brokersListMap[datasetKey]; + const brokersList = + brokersListLookup === undefined + ? brokersListMap["default"] + : brokersListLookup; const res = brokersList.map( (elem: BrokerOptionals, index: number) => ({ id: idStart - index, profile_id: profileId, - scan_id: scanId, + scan_id: elem["scan_id"] || scanId, status: elem["status"] || "new", - first_name: elem["first_name"] || MOCK_ONEREP_FIRSTNAME(), + first_name: elem["first_name"] || mockOnerepFirstName(), middle_name: elem["middle_name"] || null, - last_name: elem["last_name"] || MOCK_ONEREP_LASTNAME(), + last_name: elem["last_name"] || mockOnerepLastName(), age: elem["age"] || null, - addresses: elem["addresses"] || MOCK_ONEREP_ADDRESSES(), - phones: elem["phones"] || MOCK_ONEREP_PHONES(), - emails: elem["emails"] || MOCK_ONEREP_EMAILS(), - relatives: elem["relatives"] || MOCK_ONEREP_RELATIVES(), + addresses: elem["addresses"] || mockOnerepAddresses(), + phones: elem["phones"] || mockOnerepPhones(), + emails: elem["emails"] || mockOnerepEmails(), + relatives: elem["relatives"] || mockOnerepRelatives(), link: elem["link"] || `https://mockexample.com/link-to-databroker${index}`, data_broker: elem["data_broker"] || `mockexample${index}.com`, - data_broker_id: idStartDataBroker - index, + data_broker_id: elem["data_broker_id"] || idStartDataBroker - index, optout_attempts: elem["optout_attempts"] || 0, - created_at: elem["created_at"] || MOCK_ONEREP_TIME(), - updated_at: elem["updated_at"] || MOCK_ONEREP_TIME(), + created_at: elem["created_at"] || mockOnerepTime(), + updated_at: elem["updated_at"] || mockOnerepTime(), }) as Broker, ); diff --git a/src/app/api/mock/onerep/mockData/mockUser.json b/src/app/api/mock/onerep/mockData/mockUser.json index 7b4dc487a71..b4fece7e4d5 100644 --- a/src/app/api/mock/onerep/mockData/mockUser.json +++ b/src/app/api/mock/onerep/mockData/mockUser.json @@ -49,6 +49,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://arivify.com", "data_broker": "arivify.com", + "data_broker_id": 13, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -83,6 +84,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://beenverified.com", "data_broker": "beenverified.com", + "data_broker_id": 1, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -117,6 +119,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://instantcheckmate.com", "data_broker": "instantcheckmate.com", + "data_broker_id": 11, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -151,6 +154,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://open-public-records.com", "data_broker": "open-public-records.com", + "data_broker_id": 3, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -185,6 +189,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://peoplefinders.com", "data_broker": "peoplefinders.com", + "data_broker_id": 4, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -219,6 +224,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://peoplesmart.com", "data_broker": "peoplesmart.com", + "data_broker_id": 2, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -253,6 +259,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://privateeye.com", "data_broker": "privateeye.com", + "data_broker_id": 5, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -287,6 +294,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://smartbackgroundchecks.com", "data_broker": "smartbackgroundchecks.com", + "data_broker_id": 6, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -321,6 +329,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://truthfinder.com", "data_broker": "truthfinder.com", + "data_broker_id": 10, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -355,6 +364,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://ussearch.com", "data_broker": "ussearch.com", + "data_broker_id": 12, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } @@ -393,6 +403,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://arivify.com", "data_broker": "arivify.com", + "data_broker_id": 13, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -427,6 +438,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://beenverified.com", "data_broker": "beenverified.com", + "data_broker_id": 1, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -461,6 +473,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://instantcheckmate.com", "data_broker": "instantcheckmate.com", + "data_broker_id": 11, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -495,6 +508,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://open-public-records.com", "data_broker": "open-public-records.com", + "data_broker_id": 3, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -529,6 +543,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://peoplefinders.com", "data_broker": "peoplefinders.com", + "data_broker_id": 4, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -563,6 +578,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://peoplesmart.com", "data_broker": "peoplesmart.com", + "data_broker_id": 2, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -597,6 +613,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://privateeye.com", "data_broker": "privateeye.com", + "data_broker_id": 5, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -631,6 +648,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://smartbackgroundchecks.com", "data_broker": "smartbackgroundchecks.com", + "data_broker_id": 6, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -665,6 +683,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://truthfinder.com", "data_broker": "truthfinder.com", + "data_broker_id": 10, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }, @@ -699,6 +718,7 @@ "relatives": ["John Smith", "Bob Smith", "Amy Smith"], "link": "https://ussearch.com", "data_broker": "ussearch.com", + "data_broker_id": 12, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } diff --git a/src/app/api/mock/onerep/profiles/[profileId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/route.ts index 464361a26f0..fbfce58dfb5 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/route.ts @@ -3,12 +3,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { - MOCK_ONEREP_TIME, - MOCK_ONEREP_FIRSTNAME, - MOCK_ONEREP_LASTNAME, - MOCK_ONEREP_BIRTHDATE, - MOCK_ONEREP_ADDRESSES, - MOCK_ONEREP_PROFILE_STATUS, + mockOnerepTime, + mockOnerepFirstName, + mockOnerepLastName, + mockOnerepBirthdate, + mockOnerepAddresses, + mockOnerepProfileStatus, } from "../../config/config.ts"; import { ShowProfileResponse } from "../../../../../functions/server/onerep.ts"; import { NextRequest, NextResponse } from "next/server"; @@ -30,13 +30,13 @@ export function GET( const mockProfileData: ShowProfileResponse = { id: profileId, - first_name: MOCK_ONEREP_FIRSTNAME(), - last_name: MOCK_ONEREP_LASTNAME(), - birth_date: MOCK_ONEREP_BIRTHDATE(), - addresses: MOCK_ONEREP_ADDRESSES(), - status: MOCK_ONEREP_PROFILE_STATUS(), - created_at: MOCK_ONEREP_TIME(), - updated_at: MOCK_ONEREP_TIME(), + first_name: mockOnerepFirstName(), + last_name: mockOnerepLastName(), + birth_date: mockOnerepBirthdate(), + addresses: mockOnerepAddresses(), + status: mockOnerepProfileStatus(), + created_at: mockOnerepTime(), + updated_at: mockOnerepTime(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}`, }; diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts index 721df9193ec..86fd39e5e02 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/[scanId]/route.ts @@ -4,7 +4,7 @@ import { getLatestOnerepScan } from "../../../../../../../../db/tables/onerep_scans"; import { errorIfProduction } from "../../../../../../utils/errorThrower"; -import { MOCK_ONEREP_TIME } from "../../../../config/config"; +import { mockOnerepTime } from "../../../../config/config"; import { NextRequest, NextResponse } from "next/server"; export async function GET( @@ -19,24 +19,25 @@ export async function GET( const latestScan = await getLatestOnerepScan(profileId); - const responseData = latestScan - ? { - id: latestScan.id, - profileId: latestScan.onerep_profile_id, - status: latestScan.onerep_scan_status, - created_at: latestScan.created_at, - updated_at: latestScan.updated_at, - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, - } - : { - id: scanId, - profile_id: profileId, - status: "finished", - reason: "manual", - created_at: MOCK_ONEREP_TIME(), - updated_at: MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, - }; + const responseData = + latestScan !== null + ? { + id: latestScan.id, + profileId: latestScan.onerep_profile_id, + status: latestScan.onerep_scan_status, + created_at: latestScan.created_at, + updated_at: latestScan.updated_at, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, + } + : { + id: scanId, + profile_id: profileId, + status: "finished", + reason: "manual", + created_at: mockOnerepTime(), + updated_at: mockOnerepTime(), + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, + }; return NextResponse.json(responseData); } diff --git a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts index eb1928e7878..188ab4011c9 100644 --- a/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts +++ b/src/app/api/mock/onerep/profiles/[profileId]/scans/route.ts @@ -4,10 +4,10 @@ import { NextRequest, NextResponse } from "next/server"; import { - MOCK_ONEREP_OBJECT_LINKS, - MOCK_ONEREP_OBJECT_META, - MOCK_ONEREP_SCAN_ID, - MOCK_ONEREP_TIME, + mockOnerepObjectLinks, + mockOnerepObjectMeta, + mockOnerepScanId, + mockOnerepTime, } from "../../../config/config"; import { errorIfProduction } from "../../../../../utils/errorThrower"; import { @@ -15,7 +15,7 @@ import { getLatestOnerepScan, } from "../../../../../../../db/tables/onerep_scans"; import { - computeSha1First6, + emailHashPrefix, hashToEmailKeyMap, } from "../../../../../utils/mockUtils"; import mockUser from "../../../mockData/mockUser.json"; @@ -59,15 +59,15 @@ export async function POST( const latestScan = await getLatestOnerepScan(profileId); if (latestScan) return NextResponse.json([latestScan]); - const mockScanId = MOCK_ONEREP_SCAN_ID(profileId); + const mockScanId = mockOnerepScanId(profileId); const mockResponse = { id: mockScanId, profile_id: profileId, status: "finished", reason: "manual", - created_at: MOCK_ONEREP_TIME(), - updated_at: MOCK_ONEREP_TIME(), + created_at: mockOnerepTime(), + updated_at: mockOnerepTime(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${mockScanId}`, } as MockScan; @@ -87,43 +87,45 @@ export async function GET( return NextResponse.json({ error: "Invalid profile ID" }); } - const scanId = MOCK_ONEREP_SCAN_ID(profileId); - const links = MOCK_ONEREP_OBJECT_LINKS(profileId); - const meta = MOCK_ONEREP_OBJECT_META(profileId); + const scanId = mockOnerepScanId(profileId); + const links = mockOnerepObjectLinks(profileId); + const meta = mockOnerepObjectMeta(profileId); const email = await getEmailForProfile(profileId); if (!email) return NextResponse.json({ error: "No email for this ID is found" }); - const emailHash = computeSha1First6(email); + const emailHash = emailHashPrefix(email); const dataKey = hashToEmailKeyMap[emailHash] || "default"; - const data = mockScans[dataKey]; + const dataLookup = mockScans[dataKey]; + const data = dataLookup === undefined ? mockScans["default"] : dataLookup; const latestScan = await getLatestOnerepScan(profileId); const responseData = { - data: latestScan - ? [ - { - id: latestScan.onerep_scan_id, - profileId: latestScan.onerep_profile_id, - status: latestScan.onerep_scan_status, - created_at: latestScan.created_at, - updated_at: latestScan.updated_at, - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${latestScan.onerep_scan_id}`, - }, - ] - : data.map( - (scan) => - ({ - id: scanId, - profile_id: profileId, - status: scan.status || "finished", - reason: scan.reason || "manual", - created_at: scan.created_at || MOCK_ONEREP_TIME(), - updated_at: scan.updated_at || MOCK_ONEREP_TIME(), - url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, - }) as MockScan, - ), + data: + latestScan !== null + ? [ + { + id: latestScan.onerep_scan_id, + profileId: latestScan.onerep_profile_id, + status: latestScan.onerep_scan_status, + created_at: latestScan.created_at, + updated_at: latestScan.updated_at, + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${latestScan.onerep_scan_id}`, + }, + ] + : data.map( + (scan) => + ({ + id: scanId, + profile_id: profileId, + status: scan.status || "finished", + reason: scan.reason || "manual", + created_at: scan.created_at || mockOnerepTime(), + updated_at: scan.updated_at || mockOnerepTime(), + url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/scans/${scanId}`, + }) as MockScan, + ), links: links, meta: meta, }; diff --git a/src/app/api/mock/onerep/profiles/route.ts b/src/app/api/mock/onerep/profiles/route.ts index 8a4f0951492..c2016125be6 100644 --- a/src/app/api/mock/onerep/profiles/route.ts +++ b/src/app/api/mock/onerep/profiles/route.ts @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { - MOCK_ONEREP_PROFILE_STATUS, - MOCK_ONEREP_TIME, + mockOnerepProfileStatus, + mockOnerepTime, profileIdLeftBound, profileIdRightBound, } from "../config/config.ts"; @@ -78,13 +78,13 @@ export async function POST(req: NextRequest) { city: addr.city, address_line: null, zip: null, - created_at: MOCK_ONEREP_TIME(), - updated_at: MOCK_ONEREP_TIME(), + created_at: mockOnerepTime(), + updated_at: mockOnerepTime(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}/addresses/${profileId + index}`, })), - status: MOCK_ONEREP_PROFILE_STATUS(), - created_at: MOCK_ONEREP_TIME(), - updated_at: MOCK_ONEREP_TIME(), + status: mockOnerepProfileStatus(), + created_at: mockOnerepTime(), + updated_at: mockOnerepTime(), url: `${process.env.ONEREP_API_BASE}/profiles/${profileId}`, }; return NextResponse.json(responseProfile, { status: 201 }); diff --git a/src/app/api/mock/onerep/scan-results/route.ts b/src/app/api/mock/onerep/scan-results/route.ts index 1774819ed98..c372df2bbe1 100644 --- a/src/app/api/mock/onerep/scan-results/route.ts +++ b/src/app/api/mock/onerep/scan-results/route.ts @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { errorIfProduction } from "../../../utils/errorThrower"; -import { MOCK_ONEREP_BROKERS } from "../config/config"; +import { mockOnerepBrokers } from "../config/config"; import { NextRequest, NextResponse } from "next/server"; export async function GET(req: NextRequest) { @@ -22,12 +22,7 @@ export async function GET(req: NextRequest) { ); } - const res = await MOCK_ONEREP_BROKERS( - Number(profileId), - page, - perPage, - email, - ); + const res = await mockOnerepBrokers(Number(profileId), page, perPage, email); return NextResponse.json(res); } diff --git a/src/app/api/utils/errorThrower.ts b/src/app/api/utils/errorThrower.ts index 1fbcbbc34ea..ba00ecf3bb9 100644 --- a/src/app/api/utils/errorThrower.ts +++ b/src/app/api/utils/errorThrower.ts @@ -6,33 +6,31 @@ import { NextResponse } from "next/server"; export function errorIfProduction() { //checks that the environment isnt production - return errorIfEnvCond("production", false); + if (process.env.APP_ENV === "production") { + return error403("Endpoint not available in prod environment"); + } + return null; } export function errorIfStage() { //checks that the environment isnt stage - return errorIfEnvCond("stage", false); + if (process.env.APP_ENV === "stage") { + return error403("Endpoint not available in stage environment"); + } + return null; } export function errorIfNotLocal() { - return errorIfEnvCond("local", true); -} - -export function errorIfNotENv(which: string) { - return errorIfEnvCond(which, true); -} - -export function errorIfEnvCond(which: string, isEqualToWhich: boolean) { - //checks that the app environment satisfies the 'isEqualToWhich' condition with 'which' - if (isEqualToWhich !== (process.env.APP_ENV === which)) { - return NextResponse.json( - { error: `Endpoint not available in ${which} environment` }, - { status: 403 }, - ); + if (process.env.APP_ENV !== "local") { + return error403("Endpoint not available in non-local environment"); } return null; } +function error403(msg: string) { + return NextResponse.json({ error: msg }, { status: 403 }); +} + export function unauthError() { return NextResponse.json( { error: "Unauthorized to access the endpoint" }, @@ -45,6 +43,6 @@ export function internalServerError( ) { return NextResponse.json( { error: `Internal server error: ${description}` }, - { status: 401 }, + { status: 500 }, ); } diff --git a/src/app/api/utils/mockUtils.ts b/src/app/api/utils/mockUtils.ts index 69c19b93396..b8842633dfb 100644 --- a/src/app/api/utils/mockUtils.ts +++ b/src/app/api/utils/mockUtils.ts @@ -6,7 +6,7 @@ import { BinaryLike } from "crypto"; import { getSha1 } from "../../../utils/fxa"; import { logger } from "../../functions/server/logging"; -export function computeSha1First6(email: string) { +export function emailHashPrefix(email: string) { return getSha1(email as BinaryLike) .slice(0, 6) .toUpperCase(); @@ -25,7 +25,7 @@ export const allE2ETestEmailKeys = (() => { export const hashToEmailKeyMap = (() => { const mapping: { [key: string]: string } = {}; allE2ETestEmailKeys.forEach((key) => { - mapping[computeSha1First6(process.env[key] as string)] = key; + mapping[emailHashPrefix(process.env[key] as string)] = key; }); return mapping; })(); diff --git a/src/app/functions/server/onerep.ts b/src/app/functions/server/onerep.ts index f7cc8f1b80a..ddb8eb9edaf 100644 --- a/src/app/functions/server/onerep.ts +++ b/src/app/functions/server/onerep.ts @@ -16,7 +16,7 @@ import { } from "../../../db/tables/onerep_scans"; import { RemovalStatus } from "../universal/scanResult.js"; import { logger } from "./logging"; -import { isUsingMockONEREPndpoint } from "../universal/mock.ts"; +import { isUsingMockONEREPEndpoint } from "../universal/mock.ts"; export const monthlyScansQuota = parseInt( (process.env.MONTHLY_SCANS_QUOTA as string) ?? "0", @@ -334,7 +334,7 @@ export async function listScanResults( }> = {}, ): Promise { let mockEmail = ""; - if (isUsingMockONEREPndpoint()) { + if (isUsingMockONEREPEndpoint()) { mockEmail = (await getEmailForProfile(profileId)) || mockEmail; } const queryParams = new URLSearchParams({ diff --git a/src/app/functions/universal/mock.ts b/src/app/functions/universal/mock.ts index 46db7685223..9b610658505 100644 --- a/src/app/functions/universal/mock.ts +++ b/src/app/functions/universal/mock.ts @@ -6,7 +6,7 @@ export function isUsingMockHIBPEndpoint() { return process.env.HIBP_KANON_API_ROOT?.includes("api/mock") as boolean; } -export function isUsingMockONEREPndpoint() { +export function isUsingMockONEREPEndpoint() { return process.env.ONEREP_API_BASE?.includes("api/mock") as boolean; } From c05d519bde1f7bbc0c7a07158da25d540b29cd6e Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Thu, 18 Jul 2024 15:36:53 +0200 Subject: [PATCH 100/137] chore: Add ctaButton view event --- .../(redesign)/(public)/SignUpForm.tsx | 3 ++- src/app/hooks/useViewTelemetry.ts | 5 +++-- src/telemetry/metrics.yaml | 18 ++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx index cb02d4c31f0..c3efb8b3b5f 100644 --- a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx @@ -38,8 +38,9 @@ export const SignUpForm = (props: Props) => { const record = useTelemetry(); const { view } = props.eventId; const refViewTelemetry = useViewTelemetry( + "ctaButton", { - element_id: view, + button_id: view, }, { skip: typeof view === "undefined", diff --git a/src/app/hooks/useViewTelemetry.ts b/src/app/hooks/useViewTelemetry.ts index 49c8528c5c8..863785c12db 100644 --- a/src/app/hooks/useViewTelemetry.ts +++ b/src/app/hooks/useViewTelemetry.ts @@ -7,9 +7,10 @@ import { TelemetryArgs, useTelemetry } from "./useTelemetry"; import { GleanMetricMap } from "../../telemetry/generated/_map"; export function useViewTelemetry< - EventModule extends "view", + EventModule extends keyof Pick, EventName extends keyof GleanMetricMap[EventModule], >( + eventModule: EventModule, args: TelemetryArgs & GleanMetricMap[EventModule][EventName], options?: IntersectionOptions, ) { @@ -24,7 +25,7 @@ export function useViewTelemetry< if (!inView) { return; } - recordTelemetry("view", "enter", telemetryArgs); + recordTelemetry(eventModule, "view", telemetryArgs); }, }); diff --git a/src/telemetry/metrics.yaml b/src/telemetry/metrics.yaml index b4e52c276c9..37d1cd582c1 100644 --- a/src/telemetry/metrics.yaml +++ b/src/telemetry/metrics.yaml @@ -396,11 +396,11 @@ collapse: description: Which tier of plan the user is on [Free, Plus] type: string -view: - enter: +cta_button: + click: type: event description: | - A DOM element entered the viewport. + A click on a button that has a specific call-to-action. bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1823766 data_reviews: @@ -423,18 +423,16 @@ view: flow_id: description: A randomly generated unique identifier for following user flows within the FxA system. type: string - element_id: - description: The ID of the element that entered the viewport, or some way to identify where on the viewport the element is located. - type: string + button_id: + description: The ID of the button that was clicked on, or some way to identify where on the page the interaction is located. plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string -cta_button: - click: + view: type: event description: | - A click on a button that has a specific call-to-action. + A button that has a specific call-to-action entered the viewport. bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1823766 data_reviews: @@ -458,7 +456,7 @@ cta_button: description: A randomly generated unique identifier for following user flows within the FxA system. type: string button_id: - description: The ID of the button that was clicked on, or some way to identify where on the page the interaction is located. + description: The ID of the button that entered the viewport, or some way to identify where on the page the interaction is located. plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string From 85adb1547a830a748f6964705295b11367f77c67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:52:10 +0000 Subject: [PATCH 101/137] chore(deps): bump the aws-sdk group across 1 directory with 2 updates Bumps the aws-sdk group with 2 updates in the / directory: [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) and [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage). Updates `@aws-sdk/client-s3` from 3.606.0 to 3.614.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.614.0/clients/client-s3) Updates `@aws-sdk/lib-storage` from 3.606.0 to 3.614.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.614.0/lib/lib-storage) --- updated-dependencies: - dependency-name: "@aws-sdk/client-s3" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: aws-sdk - dependency-name: "@aws-sdk/lib-storage" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: aws-sdk ... Signed-off-by: dependabot[bot] --- package-lock.json | 984 +++++++++++++++++++++++----------------------- package.json | 4 +- 2 files changed, 494 insertions(+), 494 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3a89ec6e4bb..f66cd9dc370 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "1.0.0", "license": "MPL-2.0", "dependencies": { - "@aws-sdk/client-s3": "^3.606.0", - "@aws-sdk/lib-storage": "^3.606.0", + "@aws-sdk/client-s3": "^3.614.0", + "@aws-sdk/lib-storage": "^3.614.0", "@fluent/bundle": "^0.18.0", "@fluent/langneg": "^0.7.0", "@fluent/react": "^0.15.2", @@ -322,67 +322,67 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.606.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.606.0.tgz", - "integrity": "sha512-IGM/E8kVk/NY/kZwLdmGRsX1QYtuPljoNutM5kBRdtGahQL5VwVAve5PElPUArcsTkfTyW+LfXpznDeeHxMCcA==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.614.0.tgz", + "integrity": "sha512-9BlhfeBegvyjOqHtcr9kvrT80wiy7EVUiqYyTFiiDv/hJIcG88XHQCZdLU7658XBkQ7aFrr5b8rF2HRD1oroxw==", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.606.0", - "@aws-sdk/client-sts": "3.606.0", - "@aws-sdk/core": "3.598.0", - "@aws-sdk/credential-provider-node": "3.600.0", - "@aws-sdk/middleware-bucket-endpoint": "3.598.0", - "@aws-sdk/middleware-expect-continue": "3.598.0", - "@aws-sdk/middleware-flexible-checksums": "3.598.0", - "@aws-sdk/middleware-host-header": "3.598.0", - "@aws-sdk/middleware-location-constraint": "3.598.0", - "@aws-sdk/middleware-logger": "3.598.0", - "@aws-sdk/middleware-recursion-detection": "3.598.0", - "@aws-sdk/middleware-sdk-s3": "3.598.0", - "@aws-sdk/middleware-signing": "3.598.0", - "@aws-sdk/middleware-ssec": "3.598.0", - "@aws-sdk/middleware-user-agent": "3.598.0", - "@aws-sdk/region-config-resolver": "3.598.0", - "@aws-sdk/signature-v4-multi-region": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@aws-sdk/util-endpoints": "3.598.0", - "@aws-sdk/util-user-agent-browser": "3.598.0", - "@aws-sdk/util-user-agent-node": "3.598.0", - "@aws-sdk/xml-builder": "3.598.0", - "@smithy/config-resolver": "^3.0.2", - "@smithy/core": "^2.2.1", - "@smithy/eventstream-serde-browser": "^3.0.2", - "@smithy/eventstream-serde-config-resolver": "^3.0.1", - "@smithy/eventstream-serde-node": "^3.0.2", - "@smithy/fetch-http-handler": "^3.0.2", - "@smithy/hash-blob-browser": "^3.1.0", - "@smithy/hash-node": "^3.0.1", - "@smithy/hash-stream-node": "^3.1.0", - "@smithy/invalid-dependency": "^3.0.1", - "@smithy/md5-js": "^3.0.1", - "@smithy/middleware-content-length": "^3.0.1", - "@smithy/middleware-endpoint": "^3.0.2", - "@smithy/middleware-retry": "^3.0.4", - "@smithy/middleware-serde": "^3.0.1", - "@smithy/middleware-stack": "^3.0.1", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/node-http-handler": "^3.0.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", - "@smithy/url-parser": "^3.0.1", + "@aws-sdk/client-sso-oidc": "3.614.0", + "@aws-sdk/client-sts": "3.614.0", + "@aws-sdk/core": "3.614.0", + "@aws-sdk/credential-provider-node": "3.614.0", + "@aws-sdk/middleware-bucket-endpoint": "3.614.0", + "@aws-sdk/middleware-expect-continue": "3.609.0", + "@aws-sdk/middleware-flexible-checksums": "3.614.0", + "@aws-sdk/middleware-host-header": "3.609.0", + "@aws-sdk/middleware-location-constraint": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.609.0", + "@aws-sdk/middleware-sdk-s3": "3.614.0", + "@aws-sdk/middleware-signing": "3.609.0", + "@aws-sdk/middleware-ssec": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.614.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@aws-sdk/xml-builder": "3.609.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.2.6", + "@smithy/eventstream-serde-browser": "^3.0.4", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.4", + "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/hash-blob-browser": "^3.1.2", + "@smithy/hash-node": "^3.0.3", + "@smithy/hash-stream-node": "^3.1.2", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/md5-js": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.3", + "@smithy/middleware-endpoint": "^3.0.5", + "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.2", + "@smithy/protocol-http": "^4.0.3", + "@smithy/smithy-client": "^3.1.7", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.4", - "@smithy/util-defaults-mode-node": "^3.0.4", - "@smithy/util-endpoints": "^2.0.2", - "@smithy/util-retry": "^3.0.1", - "@smithy/util-stream": "^3.0.2", + "@smithy/util-defaults-mode-browser": "^3.0.9", + "@smithy/util-defaults-mode-node": "^3.0.9", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.0.6", "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.0.1", + "@smithy/util-waiter": "^3.1.2", "tslib": "^2.6.2" }, "engines": { @@ -390,46 +390,46 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.598.0.tgz", - "integrity": "sha512-nOI5lqPYa+YZlrrzwAJywJSw3MKVjvu6Ge2fCqQUNYMfxFB0NAaDFnl0EPjXi+sEbtCuz/uWE77poHbqiZ+7Iw==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.614.0.tgz", + "integrity": "sha512-p5pyYaxRzBttjBkqfc8i3K7DzBdTg3ECdVgBo6INIUxfvDy0J8QUE8vNtCgvFIkq+uPw/8M+Eo4zzln7anuO0Q==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.598.0", - "@aws-sdk/middleware-host-header": "3.598.0", - "@aws-sdk/middleware-logger": "3.598.0", - "@aws-sdk/middleware-recursion-detection": "3.598.0", - "@aws-sdk/middleware-user-agent": "3.598.0", - "@aws-sdk/region-config-resolver": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@aws-sdk/util-endpoints": "3.598.0", - "@aws-sdk/util-user-agent-browser": "3.598.0", - "@aws-sdk/util-user-agent-node": "3.598.0", - "@smithy/config-resolver": "^3.0.2", - "@smithy/core": "^2.2.1", - "@smithy/fetch-http-handler": "^3.0.2", - "@smithy/hash-node": "^3.0.1", - "@smithy/invalid-dependency": "^3.0.1", - "@smithy/middleware-content-length": "^3.0.1", - "@smithy/middleware-endpoint": "^3.0.2", - "@smithy/middleware-retry": "^3.0.4", - "@smithy/middleware-serde": "^3.0.1", - "@smithy/middleware-stack": "^3.0.1", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/node-http-handler": "^3.0.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", - "@smithy/url-parser": "^3.0.1", + "@aws-sdk/core": "3.614.0", + "@aws-sdk/middleware-host-header": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.614.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.2.6", + "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.3", + "@smithy/middleware-endpoint": "^3.0.5", + "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.2", + "@smithy/protocol-http": "^4.0.3", + "@smithy/smithy-client": "^3.1.7", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.4", - "@smithy/util-defaults-mode-node": "^3.0.4", - "@smithy/util-endpoints": "^2.0.2", - "@smithy/util-middleware": "^3.0.1", - "@smithy/util-retry": "^3.0.1", + "@smithy/util-defaults-mode-browser": "^3.0.9", + "@smithy/util-defaults-mode-node": "^3.0.9", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -438,47 +438,47 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.606.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.606.0.tgz", - "integrity": "sha512-gL1FHPS6hwgMNS/A+Qh5bUyHOeRVOqdb7c6+i+9gR3wtGvt2lvoSm8w5DhS08Xiiacz2AqYRDEapp0xuyCrbBQ==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.614.0.tgz", + "integrity": "sha512-BI1NWcpppbHg/28zbUg54dZeckork8BItZIcjls12vxasy+p3iEzrJVG60jcbUTTsk3Qc1tyxNfrdcVqx0y7Ww==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.598.0", - "@aws-sdk/credential-provider-node": "3.600.0", - "@aws-sdk/middleware-host-header": "3.598.0", - "@aws-sdk/middleware-logger": "3.598.0", - "@aws-sdk/middleware-recursion-detection": "3.598.0", - "@aws-sdk/middleware-user-agent": "3.598.0", - "@aws-sdk/region-config-resolver": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@aws-sdk/util-endpoints": "3.598.0", - "@aws-sdk/util-user-agent-browser": "3.598.0", - "@aws-sdk/util-user-agent-node": "3.598.0", - "@smithy/config-resolver": "^3.0.2", - "@smithy/core": "^2.2.1", - "@smithy/fetch-http-handler": "^3.0.2", - "@smithy/hash-node": "^3.0.1", - "@smithy/invalid-dependency": "^3.0.1", - "@smithy/middleware-content-length": "^3.0.1", - "@smithy/middleware-endpoint": "^3.0.2", - "@smithy/middleware-retry": "^3.0.4", - "@smithy/middleware-serde": "^3.0.1", - "@smithy/middleware-stack": "^3.0.1", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/node-http-handler": "^3.0.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", - "@smithy/url-parser": "^3.0.1", + "@aws-sdk/core": "3.614.0", + "@aws-sdk/credential-provider-node": "3.614.0", + "@aws-sdk/middleware-host-header": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.614.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.2.6", + "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.3", + "@smithy/middleware-endpoint": "^3.0.5", + "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.2", + "@smithy/protocol-http": "^4.0.3", + "@smithy/smithy-client": "^3.1.7", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.4", - "@smithy/util-defaults-mode-node": "^3.0.4", - "@smithy/util-endpoints": "^2.0.2", - "@smithy/util-middleware": "^3.0.1", - "@smithy/util-retry": "^3.0.1", + "@smithy/util-defaults-mode-browser": "^3.0.9", + "@smithy/util-defaults-mode-node": "^3.0.9", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -486,52 +486,52 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.606.0" + "@aws-sdk/client-sts": "^3.614.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.606.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.606.0.tgz", - "integrity": "sha512-b11mAhjrkm3MMiAPoMGcmd6vsaz2120lg8rHG/NZCo9vB1K6Kc7WP+a1Q05TRMseer2egTtpWJfn44aVO97VqA==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.614.0.tgz", + "integrity": "sha512-i6QmaVA1KHHYNnI2VYQy/sc31rLm4+jSp8b/YbQpFnD0w3aXsrEEHHlxek45uSkHb4Nrj1omFBVy/xp1WVYx2Q==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.606.0", - "@aws-sdk/core": "3.598.0", - "@aws-sdk/credential-provider-node": "3.600.0", - "@aws-sdk/middleware-host-header": "3.598.0", - "@aws-sdk/middleware-logger": "3.598.0", - "@aws-sdk/middleware-recursion-detection": "3.598.0", - "@aws-sdk/middleware-user-agent": "3.598.0", - "@aws-sdk/region-config-resolver": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@aws-sdk/util-endpoints": "3.598.0", - "@aws-sdk/util-user-agent-browser": "3.598.0", - "@aws-sdk/util-user-agent-node": "3.598.0", - "@smithy/config-resolver": "^3.0.2", - "@smithy/core": "^2.2.1", - "@smithy/fetch-http-handler": "^3.0.2", - "@smithy/hash-node": "^3.0.1", - "@smithy/invalid-dependency": "^3.0.1", - "@smithy/middleware-content-length": "^3.0.1", - "@smithy/middleware-endpoint": "^3.0.2", - "@smithy/middleware-retry": "^3.0.4", - "@smithy/middleware-serde": "^3.0.1", - "@smithy/middleware-stack": "^3.0.1", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/node-http-handler": "^3.0.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", - "@smithy/url-parser": "^3.0.1", + "@aws-sdk/client-sso-oidc": "3.614.0", + "@aws-sdk/core": "3.614.0", + "@aws-sdk/credential-provider-node": "3.614.0", + "@aws-sdk/middleware-host-header": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.614.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.2.6", + "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.3", + "@smithy/middleware-endpoint": "^3.0.5", + "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.2", + "@smithy/protocol-http": "^4.0.3", + "@smithy/smithy-client": "^3.1.7", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.4", - "@smithy/util-defaults-mode-node": "^3.0.4", - "@smithy/util-endpoints": "^2.0.2", - "@smithy/util-middleware": "^3.0.1", - "@smithy/util-retry": "^3.0.1", + "@smithy/util-defaults-mode-browser": "^3.0.9", + "@smithy/util-defaults-mode-node": "^3.0.9", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -540,15 +540,15 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.598.0.tgz", - "integrity": "sha512-HaSjt7puO5Cc7cOlrXFCW0rtA0BM9lvzjl56x0A20Pt+0wxXGeTOZZOkXQIepbrFkV2e/HYukuT9e99vXDm59g==", - "dependencies": { - "@smithy/core": "^2.2.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/signature-v4": "^3.1.0", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.614.0.tgz", + "integrity": "sha512-BUuS5/1YkgmKc4J0bg83XEtMyDHVyqG2QDzfmhYe8gbOIZabUl1FlrFVwhCAthtrrI6MPGTQcERB4BtJKUSplw==", + "dependencies": { + "@smithy/core": "^2.2.6", + "@smithy/protocol-http": "^4.0.3", + "@smithy/signature-v4": "^3.1.2", + "@smithy/smithy-client": "^3.1.7", + "@smithy/types": "^3.3.0", "fast-xml-parser": "4.2.5", "tslib": "^2.6.2" }, @@ -557,13 +557,13 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.598.0.tgz", - "integrity": "sha512-vi1khgn7yXzLCcgSIzQrrtd2ilUM0dWodxj3PQ6BLfP0O+q1imO3hG1nq7DVyJtq7rFHs6+9N8G4mYvTkxby2w==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.609.0.tgz", + "integrity": "sha512-v69ZCWcec2iuV9vLVJMa6fAb5xwkzN4jYIT8yjo2c4Ia/j976Q+TPf35Pnz5My48Xr94EFcaBazrWedF+kwfuQ==", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/property-provider": "^3.1.1", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -571,18 +571,18 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.598.0.tgz", - "integrity": "sha512-N7cIafi4HVlQvEgvZSo1G4T9qb/JMLGMdBsDCT5XkeJrF0aptQWzTFH0jIdZcLrMYvzPcuEyO3yCBe6cy/ba0g==", - "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/fetch-http-handler": "^3.0.2", - "@smithy/node-http-handler": "^3.0.1", - "@smithy/property-provider": "^3.1.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", - "@smithy/util-stream": "^3.0.2", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.614.0.tgz", + "integrity": "sha512-YIEjlNUKb3Vo/iTnGAPdsiDC3FUUnNoex2OwU8LmR7AkYZiWdB8nx99DfgkkY+OFMUpw7nKD2PCOtuFONelfGA==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/node-http-handler": "^3.1.2", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.0.3", + "@smithy/smithy-client": "^3.1.7", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.0.6", "tslib": "^2.6.2" }, "engines": { @@ -590,45 +590,45 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.598.0.tgz", - "integrity": "sha512-/ppcIVUbRwDIwJDoYfp90X3+AuJo2mvE52Y1t2VSrvUovYn6N4v95/vXj6LS8CNDhz2jvEJYmu+0cTMHdhI6eA==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.598.0", - "@aws-sdk/credential-provider-http": "3.598.0", - "@aws-sdk/credential-provider-process": "3.598.0", - "@aws-sdk/credential-provider-sso": "3.598.0", - "@aws-sdk/credential-provider-web-identity": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@smithy/credential-provider-imds": "^3.1.1", - "@smithy/property-provider": "^3.1.1", - "@smithy/shared-ini-file-loader": "^3.1.1", - "@smithy/types": "^3.1.0", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.614.0.tgz", + "integrity": "sha512-KfLuLFGwlvFSZ2MuzYwWGPb1y5TeiwX5okIDe0aQ1h10oD3924FXbN+mabOnUHQ8EFcGAtCaWbrC86mI7ktC6A==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.609.0", + "@aws-sdk/credential-provider-http": "3.614.0", + "@aws-sdk/credential-provider-process": "3.614.0", + "@aws-sdk/credential-provider-sso": "3.614.0", + "@aws-sdk/credential-provider-web-identity": "3.609.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.598.0" + "@aws-sdk/client-sts": "^3.614.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.600.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.600.0.tgz", - "integrity": "sha512-1pC7MPMYD45J7yFjA90SxpR0yaSvy+yZiq23aXhAPZLYgJBAxHLu0s0mDCk/piWGPh8+UGur5K0bVdx4B1D5hw==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.598.0", - "@aws-sdk/credential-provider-http": "3.598.0", - "@aws-sdk/credential-provider-ini": "3.598.0", - "@aws-sdk/credential-provider-process": "3.598.0", - "@aws-sdk/credential-provider-sso": "3.598.0", - "@aws-sdk/credential-provider-web-identity": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@smithy/credential-provider-imds": "^3.1.1", - "@smithy/property-provider": "^3.1.1", - "@smithy/shared-ini-file-loader": "^3.1.1", - "@smithy/types": "^3.1.0", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.614.0.tgz", + "integrity": "sha512-4J6gPEuFZP0mkWq5E//oMS1vrmMM88iNNcv7TEljYnsc6JTAlKejCyFwx6CN+nkIhmIZsl06SXIhBemzBdBPfg==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.609.0", + "@aws-sdk/credential-provider-http": "3.614.0", + "@aws-sdk/credential-provider-ini": "3.614.0", + "@aws-sdk/credential-provider-process": "3.614.0", + "@aws-sdk/credential-provider-sso": "3.614.0", + "@aws-sdk/credential-provider-web-identity": "3.609.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -636,14 +636,14 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.598.0.tgz", - "integrity": "sha512-rM707XbLW8huMk722AgjVyxu2tMZee++fNA8TJVNgs1Ma02Wx6bBrfIvlyK0rCcIRb0WdQYP6fe3Xhiu4e8IBA==", - "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/property-provider": "^3.1.1", - "@smithy/shared-ini-file-loader": "^3.1.1", - "@smithy/types": "^3.1.0", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.614.0.tgz", + "integrity": "sha512-Q0SI0sTRwi8iNODLs5+bbv8vgz8Qy2QdxbCHnPk/6Cx6LMf7i3dqmWquFbspqFRd8QiqxStrblwxrUYZi09tkA==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -651,16 +651,16 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.598.0.tgz", - "integrity": "sha512-5InwUmrAuqQdOOgxTccRayMMkSmekdLk6s+az9tmikq0QFAHUCtofI+/fllMXSR9iL6JbGYi1940+EUmS4pHJA==", - "dependencies": { - "@aws-sdk/client-sso": "3.598.0", - "@aws-sdk/token-providers": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@smithy/property-provider": "^3.1.1", - "@smithy/shared-ini-file-loader": "^3.1.1", - "@smithy/types": "^3.1.0", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.614.0.tgz", + "integrity": "sha512-55+gp0JY4451cWI1qXmVMFM0GQaBKiQpXv2P0xmd9P3qLDyeFUSEW8XPh0d2lb1ICr6x4s47ynXVdGCIv2mXMg==", + "dependencies": { + "@aws-sdk/client-sso": "3.614.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -668,30 +668,30 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.598.0.tgz", - "integrity": "sha512-GV5GdiMbz5Tz9JO4NJtRoFXjW0GPEujA0j+5J/B723rTN+REHthJu48HdBKouHGhdzkDWkkh1bu52V02Wprw8w==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.609.0.tgz", + "integrity": "sha512-U+PG8NhlYYF45zbr1km3ROtBMYqyyj/oK8NRp++UHHeuavgrP+4wJ4wQnlEaKvJBjevfo3+dlIBcaeQ7NYejWg==", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/property-provider": "^3.1.1", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.598.0" + "@aws-sdk/client-sts": "^3.609.0" } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.606.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.606.0.tgz", - "integrity": "sha512-SagGtmyU0Zyjq5ZA+EvccqPrJTrRNAHXE/rjk9Z2ENqtTYI2pK451sD5kLQqgNIAHNf/6U1TX7hnYz1wkInLUQ==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.614.0.tgz", + "integrity": "sha512-Bzni+r7pS+nRiqcmWPpB/OiQEM4GszGRp1DXpL3rKnwaeu+Qgf2w12DULxWUacIvOc4IzLsv6tpEidQ/P1zKQg==", "dependencies": { - "@smithy/abort-controller": "^3.0.1", - "@smithy/middleware-endpoint": "^3.0.2", - "@smithy/smithy-client": "^3.1.2", + "@smithy/abort-controller": "^3.1.1", + "@smithy/middleware-endpoint": "^3.0.5", + "@smithy/smithy-client": "^3.1.7", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", @@ -701,19 +701,19 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-s3": "^3.606.0" + "@aws-sdk/client-s3": "^3.614.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.598.0.tgz", - "integrity": "sha512-PM7BcFfGUSkmkT6+LU9TyJiB4S8yI7dfuKQDwK5ZR3P7MKaK4Uj4yyDiv0oe5xvkF6+O2+rShj+eh8YuWkOZ/Q==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.614.0.tgz", + "integrity": "sha512-TqEY8KcZeZ0LIxXaqG9RSSNnDHvD8RAFP4Xenwsxqnyad0Yn7LgCoFwRByelJ0t54ROYL1/ETJleWE4U4TOXdg==", "dependencies": { - "@aws-sdk/types": "3.598.0", + "@aws-sdk/types": "3.609.0", "@aws-sdk/util-arn-parser": "3.568.0", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/types": "^3.1.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.0.3", + "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", "tslib": "^2.6.2" }, @@ -722,13 +722,13 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.598.0.tgz", - "integrity": "sha512-ZuHW18kaeHR8TQyhEOYMr8VwiIh0bMvF7J1OTqXHxDteQIavJWA3CbfZ9sgS4XGtrBZDyHJhjZKeCfLhN2rq3w==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.609.0.tgz", + "integrity": "sha512-+zeg//mSer4JZRxOB/4mUOMUJyuYPwATnIC5moBB8P8Xe+mJaVRFy8qlCtzYNj2TycnlsBPzTK0j7P1yvDh97w==", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/protocol-http": "^4.0.1", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -736,16 +736,16 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.598.0.tgz", - "integrity": "sha512-xukAzds0GQXvMEY9G6qt+CzwVzTx8NyKKh04O2Q+nOch6QQ8Rs+2kTRy3Z4wQmXq2pK9hlOWb5nXA7HWpmz6Ng==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.614.0.tgz", + "integrity": "sha512-ZLpxVXMboDeMT7p2Kdp5m1uLVKOktkZoMgLvvbe3zbrU4Ji5IU5xVE0aa4X7H28BtuODCs6SLESnPs19bhMKlA==", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", - "@aws-sdk/types": "3.598.0", + "@aws-sdk/types": "3.609.0", "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.0.1", - "@smithy/types": "^3.1.0", + "@smithy/protocol-http": "^4.0.3", + "@smithy/types": "^3.3.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -754,13 +754,13 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.598.0.tgz", - "integrity": "sha512-WiaG059YBQwQraNejLIi0gMNkX7dfPZ8hDIhvMr5aVPRbaHH8AYF3iNSsXYCHvA2Cfa1O9haYXsuMF9flXnCmA==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.609.0.tgz", + "integrity": "sha512-iTKfo158lc4jLDfYeZmYMIBHsn8m6zX+XB6birCSNZ/rrlzAkPbGE43CNdKfvjyWdqgLMRXF+B+OcZRvqhMXPQ==", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/protocol-http": "^4.0.1", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -768,12 +768,12 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.598.0.tgz", - "integrity": "sha512-8oybQxN3F1ISOMULk7JKJz5DuAm5hCUcxMW9noWShbxTJuStNvuHf/WLUzXrf8oSITyYzIHPtf8VPlKR7I3orQ==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", + "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -781,12 +781,12 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.598.0.tgz", - "integrity": "sha512-bxBjf/VYiu3zfu8SYM2S9dQQc3tz5uBAOcPz/Bt8DyyK3GgOpjhschH/2XuUErsoUO1gDJqZSdGOmuHGZQn00Q==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -794,13 +794,13 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.598.0.tgz", - "integrity": "sha512-vjT9BeFY9FeN0f8hm2l6F53tI0N5bUq6RcDkQXKNabXBnQxKptJRad6oP2X5y3FoVfBLOuDkQgiC2940GIPxtQ==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.609.0.tgz", + "integrity": "sha512-6sewsYB7/o/nbUfA99Aa/LokM+a/u4Wpm/X2o0RxOsDtSB795ObebLJe2BxY5UssbGaWkn7LswyfvrdZNXNj1w==", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/protocol-http": "^4.0.1", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -808,17 +808,17 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.598.0.tgz", - "integrity": "sha512-5AGtLAh9wyK6ANPYfaKTqJY1IFJyePIxsEbxa7zS6REheAqyVmgJFaGu3oQ5XlxfGr5Uq59tFTRkyx26G1HkHA==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.614.0.tgz", + "integrity": "sha512-9fJTaiuuOfFV4FqmUEhPYzrtv7JOfYpB7q65oG3uayVH4ngWHIJkjnnX79zRhNZKdPGta+XIsnZzjEghg82ngA==", "dependencies": { - "@aws-sdk/types": "3.598.0", + "@aws-sdk/types": "3.609.0", "@aws-sdk/util-arn-parser": "3.568.0", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/signature-v4": "^3.1.0", - "@smithy/smithy-client": "^3.1.2", - "@smithy/types": "^3.1.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.0.3", + "@smithy/signature-v4": "^3.1.2", + "@smithy/smithy-client": "^3.1.7", + "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", "tslib": "^2.6.2" }, @@ -827,16 +827,16 @@ } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.598.0.tgz", - "integrity": "sha512-XKb05DYx/aBPqz6iCapsCbIl8aD8EihTuPCs51p75QsVfbQoVr4TlFfIl5AooMSITzojdAQqxt021YtvxjtxIQ==", - "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/property-provider": "^3.1.1", - "@smithy/protocol-http": "^4.0.1", - "@smithy/signature-v4": "^3.1.0", - "@smithy/types": "^3.1.0", - "@smithy/util-middleware": "^3.0.1", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.609.0.tgz", + "integrity": "sha512-2w3dBLjQVKIajYzokO4hduq8/0hSMUYHHmIo1Kdl+MSY8uwRBt12bLL6pyreobTcRMxizvn2ph/CQ9I1ST/WGQ==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.0.3", + "@smithy/signature-v4": "^3.1.2", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { @@ -844,12 +844,12 @@ } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.598.0.tgz", - "integrity": "sha512-f0p2xP8IC1uJ5e/tND1l81QxRtRFywEdnbtKCE0H6RSn4UIt2W3Dohe1qQDbnh27okF0PkNW6BJGdSAz3p7qbA==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", + "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -857,14 +857,14 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.598.0.tgz", - "integrity": "sha512-4tjESlHG5B5MdjUaLK7tQs/miUtHbb6deauQx8ryqSBYOhfHVgb1ZnzvQR0bTrhpqUg0WlybSkDaZAICf9xctg==", - "dependencies": { - "@aws-sdk/types": "3.598.0", - "@aws-sdk/util-endpoints": "3.598.0", - "@smithy/protocol-http": "^4.0.1", - "@smithy/types": "^3.1.0", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.614.0.tgz", + "integrity": "sha512-xUxh0UPQiMTG6E31Yvu6zVYlikrIcFDKljM11CaatInzvZubGTGiX0DjpqRlfGzUNsuPc/zNrKwRP2+wypgqIw==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@smithy/protocol-http": "^4.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -872,15 +872,15 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.598.0.tgz", - "integrity": "sha512-oYXhmTokSav4ytmWleCr3rs/1nyvZW/S0tdi6X7u+dLNL5Jee+uMxWGzgOrWK6wrQOzucLVjS4E/wA11Kv2GTw==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.1", + "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { @@ -888,15 +888,15 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.598.0.tgz", - "integrity": "sha512-1r/EyTrO1gSa1FirnR8V7mabr7gk+l+HkyTI0fcTSr8ucB7gmYyW6WjkY8JCz13VYHFK62usCEDS7yoJoJOzTA==", - "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.598.0", - "@aws-sdk/types": "3.598.0", - "@smithy/protocol-http": "^4.0.1", - "@smithy/signature-v4": "^3.1.0", - "@smithy/types": "^3.1.0", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.614.0.tgz", + "integrity": "sha512-6mW3ONW4oLzxrePznYhz7sNT9ji9Am9ufLeV722tbOVS5lArBOZ6E1oPz0uYBhisUPznWKhcLRMggt7vIJWMng==", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.0.3", + "@smithy/signature-v4": "^3.1.2", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -904,29 +904,29 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.598.0.tgz", - "integrity": "sha512-TKY1EVdHVBnZqpyxyTHdpZpa1tUpb6nxVeRNn1zWG8QB5MvH4ALLd/jR+gtmWDNQbIG4cVuBOZFVL8hIYicKTA==", - "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/property-provider": "^3.1.1", - "@smithy/shared-ini-file-loader": "^3.1.1", - "@smithy/types": "^3.1.0", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.598.0" + "@aws-sdk/client-sso-oidc": "^3.614.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.598.0.tgz", - "integrity": "sha512-742uRl6z7u0LFmZwDrFP6r1wlZcgVPw+/TilluDJmCAR8BgRw3IR+743kUXKBGd8QZDRW2n6v/PYsi/AWCDDMQ==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -945,13 +945,13 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.598.0.tgz", - "integrity": "sha512-Qo9UoiVVZxcOEdiOMZg3xb1mzkTxrhd4qSlg5QQrfWPJVx/QOg+Iy0NtGxPtHtVZNHZxohYwDwV/tfsnDSE2gQ==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz", + "integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/types": "^3.1.0", - "@smithy/util-endpoints": "^2.0.2", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", "tslib": "^2.6.2" }, "engines": { @@ -970,24 +970,24 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.598.0.tgz", - "integrity": "sha512-36Sxo6F+ykElaL1mWzWjlg+1epMpSe8obwhCN1yGE7Js9ywy5U6k6l+A3q3YM9YRbm740sNxncbwLklMvuhTKw==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.598.0.tgz", - "integrity": "sha512-oyWGcOlfTdzkC6SVplyr0AGh54IMrDxbhg5RxJ5P+V4BKfcDoDcZV9xenUk9NsOi9MuUjxMumb9UJGkDhM1m0A==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", "dependencies": { - "@aws-sdk/types": "3.598.0", - "@smithy/node-config-provider": "^3.1.1", - "@smithy/types": "^3.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -1003,11 +1003,11 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.598.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.598.0.tgz", - "integrity": "sha512-ZIa2RK7CHFTZ4gwK77WRtsZ6vF7xwRXxJ8KQIxK2duhoTVcn0xYxpFLdW9WZZZvdP9GIF3Loqvf8DRdeU5Jc7Q==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz", + "integrity": "sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -8308,11 +8308,11 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.4.tgz", - "integrity": "sha512-VwiOk7TwXoE7NlNguV/aPq1hFH72tqkHCw8eWXbr2xHspRyyv9DLpLXhq+Ieje+NwoqXrY0xyQjPXdOE6cGcHA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", + "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", "dependencies": { - "@smithy/node-config-provider": "^3.1.3", + "@smithy/node-config-provider": "^3.1.4", "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", "@smithy/util-middleware": "^3.0.3", @@ -8323,15 +8323,15 @@ } }, "node_modules/@smithy/core": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.2.4.tgz", - "integrity": "sha512-qdY3LpMOUyLM/gfjjMQZui+UTNS7kBRDWlvyIhVOql5dn2J3isk9qUTBtQ1CbDH8MTugHis1zu3h4rH+Qmmh4g==", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.2.8.tgz", + "integrity": "sha512-1Y0XX0Ucyg0LWTfTVLWpmvSRtFRniykUl3dQ0os1sTd03mKDudR6mVyX+2ak1phwPXx2aEWMAAdW52JNi0mc3A==", "dependencies": { - "@smithy/middleware-endpoint": "^3.0.4", - "@smithy/middleware-retry": "^3.0.7", + "@smithy/middleware-endpoint": "^3.0.5", + "@smithy/middleware-retry": "^3.0.11", "@smithy/middleware-serde": "^3.0.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.5", + "@smithy/protocol-http": "^4.0.4", + "@smithy/smithy-client": "^3.1.9", "@smithy/types": "^3.3.0", "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" @@ -8341,11 +8341,11 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.1.3.tgz", - "integrity": "sha512-U1Yrv6hx/mRK6k8AncuI6jLUx9rn0VVSd9NPEX6pyYFBfkSkChOc/n4zUb8alHUVg83TbI4OdZVo1X0Zfj3ijA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.1.4.tgz", + "integrity": "sha512-NKyH01m97Xa5xf3pB2QOF3lnuE8RIK0hTVNU5zvZAwZU8uspYO4DHQVlK+Y5gwSrujTfHvbfd1D9UFJAc0iYKQ==", "dependencies": { - "@smithy/node-config-provider": "^3.1.3", + "@smithy/node-config-provider": "^3.1.4", "@smithy/property-provider": "^3.1.3", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", @@ -8356,23 +8356,23 @@ } }, "node_modules/@smithy/eventstream-codec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.0.tgz", - "integrity": "sha512-XFDl70ZY+FabSnTX3oQGGYvdbEaC8vPEFkCEOoBkumqaZIwR1WjjJCDu2VMXlHbKWKshefWXdT0NYteL5v6uFw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", + "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "@smithy/util-hex-encoding": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.2.tgz", - "integrity": "sha512-6147vdedQGaWn3Nt4P1KV0LuV8IH4len1SAeycyko0p8oRLWFyYyx0L8JHGclePDSphkjxZqBHtyIfyupCaTGg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.5.tgz", + "integrity": "sha512-dEyiUYL/ekDfk+2Ra4GxV+xNnFoCmk1nuIXg+fMChFTrM2uI/1r9AdiTYzPqgb72yIv/NtAj6C3dG//1wwgakQ==", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.2", - "@smithy/types": "^3.1.0", + "@smithy/eventstream-serde-universal": "^3.0.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -8380,11 +8380,11 @@ } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.1.tgz", - "integrity": "sha512-6+B8P+5Q1mll4u7IoI7mpmYOSW3/c2r3WQoYLdqOjbIKMixJFGmN79ZjJiNMy4X2GZ4We9kQ6LfnFuczSlhcyw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", + "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -8392,12 +8392,12 @@ } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.2.tgz", - "integrity": "sha512-DLtmGAfqxZAql8rB+HqyPlUne22u3EEVj+hxlUjgXk0hXt+SfLGK0ljzRFmiWQ3qGpHu1NdJpJA9e5JE/dJxFw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.4.tgz", + "integrity": "sha512-mjlG0OzGAYuUpdUpflfb9zyLrBGgmQmrobNT8b42ZTsGv/J03+t24uhhtVEKG/b2jFtPIHF74Bq+VUtbzEKOKg==", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.2", - "@smithy/types": "^3.1.0", + "@smithy/eventstream-serde-universal": "^3.0.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -8405,12 +8405,12 @@ } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.2.tgz", - "integrity": "sha512-d3SgAIQ/s4EbU8HAHJ8m2MMJPAL30nqJktyVgvqZWNznA8PJl61gJw5gj/yjIt/Fvs3d4fU8FmPPAhdp2yr/7A==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.4.tgz", + "integrity": "sha512-Od9dv8zh3PgOD7Vj4T3HSuox16n0VG8jJIM2gvKASL6aCtcS8CfHZDWe1Ik3ZXW6xBouU+45Q5wgoliWDZiJ0A==", "dependencies": { - "@smithy/eventstream-codec": "^3.1.0", - "@smithy/types": "^3.1.0", + "@smithy/eventstream-codec": "^3.1.2", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -8418,11 +8418,11 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.0.tgz", - "integrity": "sha512-vFvDxMrc6sO5Atec8PaISckMcAwsCrRhYxwUylg97bRT2KZoumOF7qk5+6EVUtuM1IG9AJV5aqXnHln9ZdXHpg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.2.tgz", + "integrity": "sha512-3LaWlBZObyGrOOd7e5MlacnAKEwFBmAeiW/TOj2eR9475Vnq30uS2510+tnKbxrGjROfNdOhQqGo5j3sqLT6bA==", "dependencies": { - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.0.4", "@smithy/querystring-builder": "^3.0.3", "@smithy/types": "^3.3.0", "@smithy/util-base64": "^3.0.0", @@ -8430,13 +8430,13 @@ } }, "node_modules/@smithy/hash-blob-browser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.0.tgz", - "integrity": "sha512-lKEHDN6bLzYdx5cFmdMHfYVmmTZTmjphwPBSumgkaniEYwRAXnbDEGETeuzfquS9Py1aH6cmqzXWxxkD7mV3sA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.2.tgz", + "integrity": "sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==", "dependencies": { "@smithy/chunked-blob-reader": "^3.0.0", "@smithy/chunked-blob-reader-native": "^3.0.0", - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, @@ -8455,11 +8455,11 @@ } }, "node_modules/@smithy/hash-stream-node": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.0.tgz", - "integrity": "sha512-OkU9vjN17yYsXTSrouctZn2iYwG4z8WSc7F50+9ogG2crOtMopkop+22j35tX2ry2i/vLRCYgnqEmBWfvnYT2g==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.2.tgz", + "integrity": "sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -8488,21 +8488,21 @@ } }, "node_modules/@smithy/md5-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.1.tgz", - "integrity": "sha512-wQa0YGsR4Zb1GQLGwOOgRAbkj22P6CFGaFzu5bKk8K4HVNIC2dBlIxqZ/baF0pLiSZySAPdDZT7CdZ7GkGXt5A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.3.tgz", + "integrity": "sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q==", "dependencies": { - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/middleware-content-length": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.3.tgz", - "integrity": "sha512-Dbz2bzexReYIQDWMr+gZhpwBetNXzbhnEMhYKA6urqmojO14CsXjnsoPYO8UL/xxcawn8ZsuVU61ElkLSltIUQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.4.tgz", + "integrity": "sha512-wySGje/KfhsnF8YSh9hP16pZcl3C+X6zRsvSfItQGvCyte92LliilU3SD0nR7kTlxnAJwxY8vE/k4Eoezj847Q==", "dependencies": { - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.0.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -8511,13 +8511,13 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.0.4.tgz", - "integrity": "sha512-whUJMEPwl3ANIbXjBXZVdJNgfV2ZU8ayln7xUM47rXL2txuenI7jQ/VFFwCzy5lCmXScjp6zYtptW5Evud8e9g==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.0.5.tgz", + "integrity": "sha512-V4acqqrh5tDxUEGVTOgf2lYMZqPQsoGntCrjrJZEeBzEzDry2d2vcI1QCXhGltXPPY+BMc6eksZMguA9fIY8vA==", "dependencies": { "@smithy/middleware-serde": "^3.0.3", - "@smithy/node-config-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-middleware": "^3.0.3", @@ -8528,14 +8528,14 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.7.tgz", - "integrity": "sha512-f5q7Y09G+2h5ivkSx5CHvlAT4qRR3jBFEsfXyQ9nFNiWQlr8c48blnu5cmbTQ+p1xmIO14UXzKoF8d7Tm0Gsjw==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.11.tgz", + "integrity": "sha512-/TIRWmhwMpv99JCGuMhJPnH7ggk/Lah7s/uNDyr7faF02BxNsyD/fz9Tw7pgCf9tYOKgjimm2Qml1Aq1pbkt6g==", "dependencies": { - "@smithy/node-config-provider": "^3.1.3", - "@smithy/protocol-http": "^4.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.0.4", "@smithy/service-error-classification": "^3.0.3", - "@smithy/smithy-client": "^3.1.5", + "@smithy/smithy-client": "^3.1.9", "@smithy/types": "^3.3.0", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", @@ -8571,12 +8571,12 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.3.tgz", - "integrity": "sha512-rxdpAZczzholz6CYZxtqDu/aKTxATD5DAUDVj7HoEulq+pDSQVWzbg0btZDlxeFfa6bb2b5tUvgdX5+k8jUqcg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", + "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", "dependencies": { "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -8585,12 +8585,12 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.1.tgz", - "integrity": "sha512-L71NLyPeP450r2J/mfu1jMc//Z1YnqJt2eSNw7uhiItaONnBLDA68J5jgxq8+MBDsYnFwNAIc7dBG1ImiWBiwg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.3.tgz", + "integrity": "sha512-UiKZm8KHb/JeOPzHZtRUfyaRDO1KPKPpsd7iplhiwVGOeVdkiVJ5bVe7+NhWREMOKomrDIDdSZyglvMothLg0Q==", "dependencies": { "@smithy/abort-controller": "^3.1.1", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.0.4", "@smithy/querystring-builder": "^3.0.3", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8612,9 +8612,9 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.0.3.tgz", - "integrity": "sha512-x5jmrCWwQlx+Zv4jAtc33ijJ+vqqYN+c/ZkrnpvEe/uDas7AT7A/4Rc2CdfxgWv4WFGmEqODIrrUToPN6DDkGw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.0.4.tgz", + "integrity": "sha512-fAA2O4EFyNRyYdFLVIv5xMMeRb+3fRKc/Rt2flh5k831vLvUmNFXcydeg7V3UeEhGURJI4c1asmGJBjvmF6j8Q==", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8660,9 +8660,9 @@ } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.3.tgz", - "integrity": "sha512-Z8Y3+08vgoDgl4HENqNnnzSISAaGrF2RoKupoC47u2wiMp+Z8P/8mDh1CL8+8ujfi2U5naNvopSBmP/BUj8b5w==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", + "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8672,14 +8672,14 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-3.1.0.tgz", - "integrity": "sha512-m0/6LW3IQ3/JBcdhqjpkpABPTPhcejqeAn0U877zxBdNLiWAnG2WmCe5MfkUyVuvpFTPQnQwCo/0ZBR4uF5kxg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-3.1.2.tgz", + "integrity": "sha512-3BcPylEsYtD0esM4Hoyml/+s7WP2LFhcM3J2AGdcL2vx9O60TtfpDOL72gjb4lU8NeRPeKAwR77YNyyGvMbuEA==", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", - "@smithy/types": "^3.1.0", + "@smithy/types": "^3.3.0", "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.1", + "@smithy/util-middleware": "^3.0.3", "@smithy/util-uri-escape": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -8689,15 +8689,15 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.5.tgz", - "integrity": "sha512-x9bL9Mx2CT2P1OiUlHM+ZNpbVU6TgT32f9CmTRzqIHA7M4vYrROCWEoC3o4xHNJASoGd4Opos3cXYPgh+/m4Ww==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.9.tgz", + "integrity": "sha512-My2RaInZ4gSwJUPMaiLR/Nk82+c4LlvqpXA+n7lonGYgCZq23Tg+/xFhgmiejJ6XPElYJysTPyV90vKyp17+1g==", "dependencies": { - "@smithy/middleware-endpoint": "^3.0.4", + "@smithy/middleware-endpoint": "^3.0.5", "@smithy/middleware-stack": "^3.0.3", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.0.4", "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.0.5", + "@smithy/util-stream": "^3.1.1", "tslib": "^2.6.2" }, "engines": { @@ -8781,12 +8781,12 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.7.tgz", - "integrity": "sha512-Q2txLyvQyGfmjsaDbVV7Sg8psefpFcrnlGapDzXGFRPFKRBeEg6OvFK8FljqjeHSaCZ6/UuzQExUPqBR/2qlDA==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.11.tgz", + "integrity": "sha512-O3s9DGb3bmRvEKmT8RwvSWK4A9r6svfd+MnJB+UMi9ZcCkAnoRtliulOnGF0qCMkKF9mwk2tkopBBstalPY/vg==", "dependencies": { "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.1.5", + "@smithy/smithy-client": "^3.1.9", "@smithy/types": "^3.3.0", "bowser": "^2.11.0", "tslib": "^2.6.2" @@ -8796,15 +8796,15 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.7.tgz", - "integrity": "sha512-F4Qcj1fG6MGi2BSWCslfsMSwllws/WzYONBGtLybyY+halAcXdWhcew+mej8M5SKd5hqPYp4f7b+ABQEaeytgg==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.11.tgz", + "integrity": "sha512-qd4a9qtyOa/WY14aHHOkMafhh9z8D2QTwlcBoXMTPnEwtcY+xpe1JyFm9vya7VsB8hHsfn3XodEtwqREiu4ygQ==", "dependencies": { - "@smithy/config-resolver": "^3.0.4", - "@smithy/credential-provider-imds": "^3.1.3", - "@smithy/node-config-provider": "^3.1.3", + "@smithy/config-resolver": "^3.0.5", + "@smithy/credential-provider-imds": "^3.1.4", + "@smithy/node-config-provider": "^3.1.4", "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.1.5", + "@smithy/smithy-client": "^3.1.9", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -8813,11 +8813,11 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.4.tgz", - "integrity": "sha512-ZAtNf+vXAsgzgRutDDiklU09ZzZiiV/nATyqde4Um4priTmasDH+eLpp3tspL0hS2dEootyFMhu1Y6Y+tzpWBQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", + "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", "dependencies": { - "@smithy/node-config-provider": "^3.1.3", + "@smithy/node-config-provider": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -8862,12 +8862,12 @@ } }, "node_modules/@smithy/util-stream": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.0.5.tgz", - "integrity": "sha512-xC3L5PKMAT/Bh8fmHNXP9sdQ4+4aKVUU3EEJ2CF/lLk7R+wtMJM+v/1B4en7jO++Wa5spGzFDBCl0QxgbUc5Ug==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.1.tgz", + "integrity": "sha512-EhRnVvl3AhoHAT2rGQ5o+oSDRM/BUSMPLZZdRJZLcNVUsFAjOs4vHaPdNQivTSzRcFxf5DA4gtO46WWU2zimaw==", "dependencies": { - "@smithy/fetch-http-handler": "^3.2.0", - "@smithy/node-http-handler": "^3.1.1", + "@smithy/fetch-http-handler": "^3.2.2", + "@smithy/node-http-handler": "^3.1.3", "@smithy/types": "^3.3.0", "@smithy/util-base64": "^3.0.0", "@smithy/util-buffer-from": "^3.0.0", @@ -8903,12 +8903,12 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.0.1.tgz", - "integrity": "sha512-wwnrVQdjQxvWGOAiLmqlEhENGCcDIN+XJ/+usPOgSZObAslrCXgKlkX7rNVwIWW2RhPguTKthvF+4AoO0Z6KpA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz", + "integrity": "sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==", "dependencies": { - "@smithy/abort-controller": "^3.0.1", - "@smithy/types": "^3.1.0", + "@smithy/abort-controller": "^3.1.1", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { diff --git a/package.json b/package.json index a2ef69c894c..6d1ec52a639 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,8 @@ "npm": "10.8.0" }, "dependencies": { - "@aws-sdk/client-s3": "^3.606.0", - "@aws-sdk/lib-storage": "^3.606.0", + "@aws-sdk/client-s3": "^3.614.0", + "@aws-sdk/lib-storage": "^3.614.0", "@fluent/bundle": "^0.18.0", "@fluent/langneg": "^0.7.0", "@fluent/react": "^0.15.2", From 5f2dc9e50d94205557fdbf0c3330f966441d4e80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:57:12 +0000 Subject: [PATCH 102/137] chore(deps): bump the sentry group across 1 directory with 3 updates Bumps the sentry group with 1 update in the / directory: [@sentry/nextjs](https://github.com/getsentry/sentry-javascript). Updates `@sentry/nextjs` from 8.2.1 to 8.18.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.2.1...8.18.0) Updates `@sentry/node` from 8.2.1 to 8.18.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.2.1...8.18.0) Updates `@sentry/utils` from 8.2.1 to 8.18.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.2.1...8.18.0) --- updated-dependencies: - dependency-name: "@sentry/nextjs" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: sentry - dependency-name: "@sentry/node" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: sentry - dependency-name: "@sentry/utils" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: sentry ... Signed-off-by: dependabot[bot] --- package-lock.json | 1193 ++++++++++++++++++++------------------------- package.json | 2 +- 2 files changed, 523 insertions(+), 672 deletions(-) diff --git a/package-lock.json b/package-lock.json index f66cd9dc370..bcae66a0f75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@leeoniya/ufuzzy": "^1.0.14", "@mozilla/glean": "^5.0.2", "@next/third-parties": "^14.2.5", - "@sentry/nextjs": "^8.2.1", + "@sentry/nextjs": "^8.18.0", "@sentry/node": "^8.0.0", "@sentry/utils": "^8.0.0", "@stripe/stripe-js": "^4.0.0", @@ -5506,9 +5506,9 @@ } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.51.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.51.1.tgz", - "integrity": "sha512-E3skn949Pk1z2XtXu/lxf6QAZpawuTM/IUEXcAzpiUkTd73Hmvw26FiN3cJuTmkpM5hZzHwkomVdtrh/n/zzwA==", + "version": "0.52.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz", + "integrity": "sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A==", "dependencies": { "@opentelemetry/api": "^1.0.0" }, @@ -5517,46 +5517,46 @@ } }, "node_modules/@opentelemetry/context-async-hooks": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.24.1.tgz", - "integrity": "sha512-R5r6DO4kgEOVBxFXhXjwospLQkv+sYxwCfjvoZBe7Zm6KKXAV9kDSJhi/D1BweowdZmO+sdbENLs374gER8hpQ==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.25.1.tgz", + "integrity": "sha512-UW/ge9zjvAEmRWVapOP0qyCvPulWU6cQxGxDbWEFfGOj1VBBZAuOqTo3X6yWmDTD3Xe15ysCZChHncr2xFMIfQ==", "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.9.0" + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "node_modules/@opentelemetry/core": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.24.1.tgz", - "integrity": "sha512-wMSGfsdmibI88K9wB498zXY04yThPexo8jvwNNlm542HZB7XrrMRBbAyKJqG8qDRJwIBdBrPMi4V9ZPW/sqrcg==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", + "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", "dependencies": { - "@opentelemetry/semantic-conventions": "1.24.1" + "@opentelemetry/semantic-conventions": "1.25.1" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.9.0" + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "node_modules/@opentelemetry/core/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@opentelemetry/instrumentation": { - "version": "0.51.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.51.1.tgz", - "integrity": "sha512-JIrvhpgqY6437QIqToyozrUG1h5UhwHkaGK/WAX+fkrpyPtc+RO5FkRtUd9BH0MibabHHvqsnBGKfKVijbmp8w==", + "version": "0.52.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.52.1.tgz", + "integrity": "sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==", "dependencies": { - "@opentelemetry/api-logs": "0.51.1", + "@opentelemetry/api-logs": "0.52.1", "@types/shimmer": "^1.0.2", - "import-in-the-middle": "1.7.4", + "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "semver": "^7.5.2", "shimmer": "^1.2.1" @@ -5569,13 +5569,13 @@ } }, "node_modules/@opentelemetry/instrumentation-connect": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.36.0.tgz", - "integrity": "sha512-k9++bmJZ9zDEs3u3DnKTn2l7QTiNFg3gPx7G9rW0TPnP+xZoBSBTrEcGYBaqflQlrFG23Q58+X1sM2ayWPv5Fg==", + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.38.0.tgz", + "integrity": "sha512-2/nRnx3pjYEmdPIaBwtgtSviTKHWnDZN3R+TkRUnhIVrvBKVcq+I5B2rtd6mr6Fe9cHlZ9Ojcuh7pkNh/xdWWg==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.51.0", - "@opentelemetry/semantic-conventions": "^1.0.0", + "@opentelemetry/instrumentation": "^0.52.0", + "@opentelemetry/semantic-conventions": "^1.22.0", "@types/connect": "3.4.36" }, "engines": { @@ -5585,6 +5585,14 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/instrumentation-connect/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", + "engines": { + "node": ">=14" + } + }, "node_modules/@opentelemetry/instrumentation-connect/node_modules/@types/connect": { "version": "3.4.36", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", @@ -5594,12 +5602,12 @@ } }, "node_modules/@opentelemetry/instrumentation-express": { - "version": "0.38.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.38.0.tgz", - "integrity": "sha512-izId/qcgMgfWV292ZI9b9E7HdV9446vi0Z5zu5fSlt4MF+R6LZXbZLTQAaboJ4Y2+JbtH7apvko1DF93qTFtqw==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.41.0.tgz", + "integrity": "sha512-/B7fbMdaf3SYe5f1P973tkqd6s7XZirjpfkoJ63E7nltU30qmlgm9tY5XwZOzAFI0rHS9tbrFI2HFPAvQUFe/A==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.51.0", + "@opentelemetry/instrumentation": "^0.52.0", "@opentelemetry/semantic-conventions": "^1.22.0" }, "engines": { @@ -5610,20 +5618,20 @@ } }, "node_modules/@opentelemetry/instrumentation-express/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@opentelemetry/instrumentation-fastify": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.36.1.tgz", - "integrity": "sha512-3Nfm43PI0I+3EX+1YbSy6xbDu276R1Dh1tqAk68yd4yirnIh52Kd5B+nJ8CgHA7o3UKakpBjj6vSzi5vNCzJIA==", + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.38.0.tgz", + "integrity": "sha512-HBVLpTSYpkQZ87/Df3N0gAw7VzYZV3n28THIBrJWfuqw3Or7UqdhnjeuMIPQ04BKk3aZc0cWn2naSQObbh5vXw==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.51.0", + "@opentelemetry/instrumentation": "^0.52.0", "@opentelemetry/semantic-conventions": "^1.22.0" }, "engines": { @@ -5634,19 +5642,19 @@ } }, "node_modules/@opentelemetry/instrumentation-fastify/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@opentelemetry/instrumentation-graphql": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.40.0.tgz", - "integrity": "sha512-LVRdEHWACWOczv2imD+mhUrLMxsEjPPi32vIZJT57zygR5aUiA4em8X3aiGOCycgbMWkIu8xOSGSxdx3JmzN+w==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.42.0.tgz", + "integrity": "sha512-N8SOwoKL9KQSX7z3gOaw5UaTeVQcfDO1c21csVHnmnmGUoqsXbArK2B8VuwPWcv6/BC/i3io+xTo7QGRZ/z28Q==", "dependencies": { - "@opentelemetry/instrumentation": "^0.51.0" + "@opentelemetry/instrumentation": "^0.52.0" }, "engines": { "node": ">=14" @@ -5656,13 +5664,13 @@ } }, "node_modules/@opentelemetry/instrumentation-hapi": { - "version": "0.38.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.38.0.tgz", - "integrity": "sha512-ZcOqEuwuutTDYIjhDIStix22ECblG/i9pHje23QGs4Q4YS4RMaZ5hKCoQJxW88Z4K7T53rQkdISmoXFKDV8xMg==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.40.0.tgz", + "integrity": "sha512-8U/w7Ifumtd2bSN1OLaSwAAFhb9FyqWUki3lMMB0ds+1+HdSxYBe9aspEJEgvxAqOkrQnVniAPTEGf1pGM7SOw==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.51.0", - "@opentelemetry/semantic-conventions": "^1.0.0" + "@opentelemetry/instrumentation": "^0.52.0", + "@opentelemetry/semantic-conventions": "^1.22.0" }, "engines": { "node": ">=14" @@ -5671,14 +5679,22 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/instrumentation-hapi/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", + "engines": { + "node": ">=14" + } + }, "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.51.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.51.1.tgz", - "integrity": "sha512-6b3nZnFFEz/3xZ6w8bVxctPUWIPWiXuPQ725530JgxnN1cvYFd8CJ75PrHZNjynmzSSnqBkN3ef4R9N+RpMh8Q==", + "version": "0.52.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.52.1.tgz", + "integrity": "sha512-dG/aevWhaP+7OLv4BQQSEKMJv8GyeOp3Wxl31NHqE8xo9/fYMfEljiZphUHIfyg4gnZ9swMyWjfOQs5GUQe54Q==", "dependencies": { - "@opentelemetry/core": "1.24.1", - "@opentelemetry/instrumentation": "0.51.1", - "@opentelemetry/semantic-conventions": "1.24.1", + "@opentelemetry/core": "1.25.1", + "@opentelemetry/instrumentation": "0.52.1", + "@opentelemetry/semantic-conventions": "1.25.1", "semver": "^7.5.2" }, "engines": { @@ -5689,17 +5705,17 @@ } }, "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@opentelemetry/instrumentation-http/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" }, @@ -5708,13 +5724,13 @@ } }, "node_modules/@opentelemetry/instrumentation-ioredis": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.40.0.tgz", - "integrity": "sha512-Jv/fH7KhpWe4KBirsiqeUJIYrsdR2iu2l4nWhfOlRvaZ+zYIiLEzTQR6QhBbyRoAbU4OuYJzjWusOmmpGBnwng==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.42.0.tgz", + "integrity": "sha512-P11H168EKvBB9TUSasNDOGJCSkpT44XgoM6d3gRIWAa9ghLpYhl0uRkS8//MqPzcJVHr3h3RmfXIpiYLjyIZTw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.51.0", + "@opentelemetry/instrumentation": "^0.52.0", "@opentelemetry/redis-common": "^0.36.2", - "@opentelemetry/semantic-conventions": "^1.0.0" + "@opentelemetry/semantic-conventions": "^1.23.0" }, "engines": { "node": ">=14" @@ -5723,16 +5739,22 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/instrumentation-ioredis/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", + "engines": { + "node": ">=14" + } + }, "node_modules/@opentelemetry/instrumentation-koa": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.40.0.tgz", - "integrity": "sha512-dJc3H/bKMcgUYcQpLF+1IbmUKus0e5Fnn/+ru/3voIRHwMADT3rFSUcGLWSczkg68BCgz0vFWGDTvPtcWIFr7A==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.42.0.tgz", + "integrity": "sha512-H1BEmnMhho8o8HuNRq5zEI4+SIHDIglNB7BPKohZyWG4fWNuR7yM4GTlR01Syq21vODAS7z5omblScJD/eZdKw==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.51.0", - "@opentelemetry/semantic-conventions": "^1.22.0", - "@types/koa": "2.14.0", - "@types/koa__router": "12.0.3" + "@opentelemetry/instrumentation": "^0.52.0", + "@opentelemetry/semantic-conventions": "^1.22.0" }, "engines": { "node": ">=14" @@ -5742,19 +5764,19 @@ } }, "node_modules/@opentelemetry/instrumentation-koa/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.43.0.tgz", - "integrity": "sha512-bMKej7Y76QVUD3l55Q9YqizXybHUzF3pujsBFjqbZrRn2WYqtsDtTUlbCK7fvXNPwFInqZ2KhnTqd0gwo8MzaQ==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.46.0.tgz", + "integrity": "sha512-VF/MicZ5UOBiXrqBslzwxhN7TVqzu1/LN/QDpkskqM0Zm0aZ4CVRbUygL8d7lrjLn15x5kGIe8VsSphMfPJzlA==", "dependencies": { - "@opentelemetry/instrumentation": "^0.51.0", + "@opentelemetry/instrumentation": "^0.52.0", "@opentelemetry/sdk-metrics": "^1.9.1", "@opentelemetry/semantic-conventions": "^1.22.0" }, @@ -5766,20 +5788,20 @@ } }, "node_modules/@opentelemetry/instrumentation-mongodb/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@opentelemetry/instrumentation-mongoose": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.38.1.tgz", - "integrity": "sha512-zaeiasdnRjXe6VhYCBMdkmAVh1S5MmXC/0spet+yqoaViGnYst/DOxPvhwg3yT4Yag5crZNWsVXnA538UjP6Ow==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.40.0.tgz", + "integrity": "sha512-niRi5ZUnkgzRhIGMOozTyoZIvJKNJyhijQI4nF4iFSb+FUx2v5fngfR+8XLmdQAO7xmsD8E5vEGdDVYVtKbZew==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.51.0", + "@opentelemetry/instrumentation": "^0.52.0", "@opentelemetry/semantic-conventions": "^1.22.0" }, "engines": { @@ -5790,19 +5812,19 @@ } }, "node_modules/@opentelemetry/instrumentation-mongoose/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@opentelemetry/instrumentation-mysql": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.38.1.tgz", - "integrity": "sha512-+iBAawUaTfX/HAlvySwozx0C2B6LBfNPXX1W8Z2On1Uva33AGkw2UjL9XgIg1Pj4eLZ9R4EoJ/aFz+Xj4E/7Fw==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.40.0.tgz", + "integrity": "sha512-d7ja8yizsOCNMYIJt5PH/fKZXjb/mS48zLROO4BzZTtDfhNCl2UM/9VIomP2qkGIFVouSJrGr/T00EzY7bPtKA==", "dependencies": { - "@opentelemetry/instrumentation": "^0.51.0", + "@opentelemetry/instrumentation": "^0.52.0", "@opentelemetry/semantic-conventions": "^1.22.0", "@types/mysql": "2.15.22" }, @@ -5814,19 +5836,19 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.38.1.tgz", - "integrity": "sha512-qkpHMgWSDTYVB1vlZ9sspf7l2wdS5DDq/rbIepDwX5BA0N0068JTQqh0CgAh34tdFqSCnWXIhcyOXC2TtRb0sg==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.40.0.tgz", + "integrity": "sha512-0xfS1xcqUmY7WE1uWjlmI67Xg3QsSUlNT+AcXHeA4BDUPwZtWqF4ezIwLgpVZfHOnkAEheqGfNSWd1PIu3Wnfg==", "dependencies": { - "@opentelemetry/instrumentation": "^0.51.0", + "@opentelemetry/instrumentation": "^0.52.0", "@opentelemetry/semantic-conventions": "^1.22.0", "@opentelemetry/sql-common": "^0.40.1" }, @@ -5838,20 +5860,20 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql2/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@opentelemetry/instrumentation-nestjs-core": { - "version": "0.37.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.37.1.tgz", - "integrity": "sha512-ebYQjHZEmGHWEALwwDGhSQVLBaurFnuLIkZD5igPXrt7ohfF4lc5/4al1LO+vKc0NHk8SJWStuRueT86ISA8Vg==", + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.39.0.tgz", + "integrity": "sha512-mewVhEXdikyvIZoMIUry8eb8l3HUjuQjSjVbmLVTt4NQi35tkpnHQrG9bTRBrl3403LoWZ2njMPJyg4l6HfKvA==", "dependencies": { - "@opentelemetry/instrumentation": "^0.51.0", - "@opentelemetry/semantic-conventions": "^1.0.0" + "@opentelemetry/instrumentation": "^0.52.0", + "@opentelemetry/semantic-conventions": "^1.23.0" }, "engines": { "node": ">=14" @@ -5860,12 +5882,20 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/instrumentation-nestjs-core/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", + "engines": { + "node": ">=14" + } + }, "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.41.0.tgz", - "integrity": "sha512-BSlhpivzBD77meQNZY9fS4aKgydA8AJBzv2dqvxXFy/Hq64b7HURgw/ztbmwFeYwdF5raZZUifiiNSMLpOJoSA==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.43.0.tgz", + "integrity": "sha512-og23KLyoxdnAeFs1UWqzSonuCkePUzCX30keSYigIzJe/6WSYA8rnEI5lobcxPEzg+GcU06J7jzokuEHbjVJNw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.51.0", + "@opentelemetry/instrumentation": "^0.52.0", "@opentelemetry/semantic-conventions": "^1.22.0", "@opentelemetry/sql-common": "^0.40.1", "@types/pg": "8.6.1", @@ -5879,17 +5909,41 @@ } }, "node_modules/@opentelemetry/instrumentation-pg/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-redis-4": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.41.0.tgz", + "integrity": "sha512-H7IfGTqW2reLXqput4yzAe8YpDC0fmVNal95GHMLOrS89W+qWUKIqxolSh63hJyfmwPSFwXASzj7wpSk8Az+Dg==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.52.0", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.22.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-redis-4/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@opentelemetry/instrumentation/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" }, @@ -5906,64 +5960,64 @@ } }, "node_modules/@opentelemetry/resources": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.24.1.tgz", - "integrity": "sha512-cyv0MwAaPF7O86x5hk3NNgenMObeejZFLJJDVuSeSMIsknlsj3oOZzRv3qSzlwYomXsICfBeFFlxwHQte5mGXQ==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", + "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", "dependencies": { - "@opentelemetry/core": "1.24.1", - "@opentelemetry/semantic-conventions": "1.24.1" + "@opentelemetry/core": "1.25.1", + "@opentelemetry/semantic-conventions": "1.25.1" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.9.0" + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@opentelemetry/sdk-metrics": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.24.1.tgz", - "integrity": "sha512-FrAqCbbGao9iKI+Mgh+OsC9+U2YMoXnlDHe06yH7dvavCKzE3S892dGtX54+WhSFVxHR/TMRVJiK/CV93GR0TQ==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.25.1.tgz", + "integrity": "sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==", "dependencies": { - "@opentelemetry/core": "1.24.1", - "@opentelemetry/resources": "1.24.1", + "@opentelemetry/core": "1.25.1", + "@opentelemetry/resources": "1.25.1", "lodash.merge": "^4.6.2" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.9.0" + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.24.1.tgz", - "integrity": "sha512-zz+N423IcySgjihl2NfjBf0qw1RWe11XIAWVrTNOSSI6dtSPJiVom2zipFB2AEEtJWpv0Iz6DY6+TjnyTV5pWg==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.25.1.tgz", + "integrity": "sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw==", "dependencies": { - "@opentelemetry/core": "1.24.1", - "@opentelemetry/resources": "1.24.1", - "@opentelemetry/semantic-conventions": "1.24.1" + "@opentelemetry/core": "1.25.1", + "@opentelemetry/resources": "1.25.1", + "@opentelemetry/semantic-conventions": "1.25.1" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.9.0" + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } @@ -6087,137 +6141,13 @@ } }, "node_modules/@prisma/instrumentation": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-5.13.0.tgz", - "integrity": "sha512-MEJX1aWLsEjS+2iheBkEy1LlzQuUruPgKEzA9HPMwzitCoUUK1qn5o+yIphU7wWs47Le/cED0egYQL7y9/rSsA==", - "dependencies": { - "@opentelemetry/api": "1.8.0", - "@opentelemetry/instrumentation": "0.50.0", - "@opentelemetry/sdk-trace-base": "1.23.0" - } - }, - "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/api-logs": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.50.0.tgz", - "integrity": "sha512-JdZuKrhOYggqOpUljAq4WWNi5nB10PmgoF0y2CvedLGXd0kSawb/UBnWT8gg1ND3bHCNHStAIVT0ELlxJJRqrA==", + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-5.16.1.tgz", + "integrity": "sha512-4m5gRFWnQb8s/yTyGbMZkL7A5uJgqOWcWJxapwcAD0T0kh5sGPEVSQl/zTQvE9aduXhFAxOtC3gO+R8Hb5xO1Q==", "dependencies": { - "@opentelemetry/api": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/core": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.23.0.tgz", - "integrity": "sha512-hdQ/a9TMzMQF/BO8Cz1juA43/L5YGtCSiKoOHmrTEf7VMDAZgy8ucpWx3eQTnQ3gBloRcWtzvcrMZABC3PTSKQ==", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.23.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.9.0" - } - }, - "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/instrumentation": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.50.0.tgz", - "integrity": "sha512-bhGhbJiZKpuu7wTaSak4hyZcFPlnDeuSF/2vglze8B4w2LubcSbbOnkVTzTs5SXtzh4Xz8eRjaNnAm+u2GYufQ==", - "dependencies": { - "@opentelemetry/api-logs": "0.50.0", - "@types/shimmer": "^1.0.2", - "import-in-the-middle": "1.7.1", - "require-in-the-middle": "^7.1.1", - "semver": "^7.5.2", - "shimmer": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/resources": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.23.0.tgz", - "integrity": "sha512-iPRLfVfcEQynYGo7e4Di+ti+YQTAY0h5mQEUJcHlU9JOqpb4x965O6PZ+wMcwYVY63G96KtdS86YCM1BF1vQZg==", - "dependencies": { - "@opentelemetry/core": "1.23.0", - "@opentelemetry/semantic-conventions": "1.23.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.9.0" - } - }, - "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.23.0.tgz", - "integrity": "sha512-PzBmZM8hBomUqvCddF/5Olyyviayka44O5nDWq673np3ctnvwMOvNrsUORZjKja1zJbwEuD9niAGbnVrz3jwRQ==", - "dependencies": { - "@opentelemetry/core": "1.23.0", - "@opentelemetry/resources": "1.23.0", - "@opentelemetry/semantic-conventions": "1.23.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.9.0" - } - }, - "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.23.0.tgz", - "integrity": "sha512-MiqFvfOzfR31t8cc74CTP1OZfz7MbqpAnLCra8NqQoaHJX6ncIRTdYOQYBDQ2uFISDq0WY8Y9dDTWvsgzzBYRg==", - "engines": { - "node": ">=14" - } - }, - "node_modules/@prisma/instrumentation/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/@prisma/instrumentation/node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/@prisma/instrumentation/node_modules/import-in-the-middle": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.7.1.tgz", - "integrity": "sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg==", - "dependencies": { - "acorn": "^8.8.2", - "acorn-import-assertions": "^1.9.0", - "cjs-module-lexer": "^1.2.2", - "module-details-from-path": "^1.0.3" - } - }, - "node_modules/@prisma/instrumentation/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "@opentelemetry/api": "^1.8", + "@opentelemetry/instrumentation": "^0.49 || ^0.50 || ^0.51 || ^0.52.0", + "@opentelemetry/sdk-trace-base": "^1.22" } }, "node_modules/@protobufjs/aspromise": { @@ -7661,22 +7591,22 @@ } }, "node_modules/@rollup/plugin-commonjs": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.0.tgz", - "integrity": "sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g==", + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-26.0.1.tgz", + "integrity": "sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==", "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", - "glob": "^8.0.3", + "glob": "^10.4.1", "is-reference": "1.2.1", - "magic-string": "^0.27.0" + "magic-string": "^0.30.3" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0 || 14 >= 14.17" }, "peerDependencies": { - "rollup": "^2.68.0||^3.0.0" + "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -7685,9 +7615,9 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", - "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", @@ -7711,96 +7641,96 @@ "integrity": "sha512-2/U3GXA6YiPYQDLGwtGlnNgKYBSwCFIHf8Y9LUY5VATHdtbLlU0Y1R3QoBnT0aB4qv/BEiVVsj7LJXoQCgJ2vA==" }, "node_modules/@sentry-internal/browser-utils": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.2.1.tgz", - "integrity": "sha512-jWueDzeb+LPEMfnJ5OR4YM5+PVnWbBI35DNwbT0TMiHNsqFjp2xtWAr8rpK9OayuLXEe5YtcoeyTUwU5c6i3DA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.18.0.tgz", + "integrity": "sha512-1R7QXp7Gu6ovJGWvGjbgHcDcvDstsQba3miHtUCyDSH9kXtnAVLCAItDkseetFh+JLsjBXf3QFi2H3HPY4hRCw==", "dependencies": { - "@sentry/core": "8.2.1", - "@sentry/types": "8.2.1", - "@sentry/utils": "8.2.1" + "@sentry/core": "8.18.0", + "@sentry/types": "8.18.0", + "@sentry/utils": "8.18.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/feedback": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.2.1.tgz", - "integrity": "sha512-HN2ys/dvisKmUybO3U6DwhutXujwZP+9bbuhBQWex7wu+iZrkIxT8TVb9Vye2Q0nsxupwD43dSzpKdGYBwx5XQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.18.0.tgz", + "integrity": "sha512-on6+4ZRkfdnsNgXecGQ6ME8aO26VTzkuM6y/kNN+bG2hSdxsmuU957B4x1Z5wEXiOWswuf3rhqGepg8JIdPkMQ==", "dependencies": { - "@sentry/core": "8.2.1", - "@sentry/types": "8.2.1", - "@sentry/utils": "8.2.1" + "@sentry/core": "8.18.0", + "@sentry/types": "8.18.0", + "@sentry/utils": "8.18.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.2.1.tgz", - "integrity": "sha512-Jwpbig9jJ4WoLpaZ/jhQRqI0ND9gPf+MrwXCDYf2NgKnvaKjbQiv0/DGVMpKdLZiasGqoEU3POI/UGd+GzTuxw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.18.0.tgz", + "integrity": "sha512-cCLib/HjD8UR0fB2F5hV6KsFBD6yTOEsi67RBllm5gT5vJt87VYoPliF6O7mmMNw8TWkQ0uc5laKld3q9ph+ug==", "dependencies": { - "@sentry-internal/browser-utils": "8.2.1", - "@sentry/core": "8.2.1", - "@sentry/types": "8.2.1", - "@sentry/utils": "8.2.1" + "@sentry-internal/browser-utils": "8.18.0", + "@sentry/core": "8.18.0", + "@sentry/types": "8.18.0", + "@sentry/utils": "8.18.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.2.1.tgz", - "integrity": "sha512-pP/ga8BR1qYDFnmhfNO+eruNjjpYeeB84mc/vfeZz0Ah5zh5LuaH/BIQM/jW615Ts77H82RFNdXYSwESz9AWPw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.18.0.tgz", + "integrity": "sha512-fcuLJBrhw3Ql8sU8veUgDCRYo6toQldFU807cpYphQ0uEw2oVZwNNPDQSu1651Ykvp0P/x+9hk/jjJxMohrO9g==", "dependencies": { - "@sentry-internal/replay": "8.2.1", - "@sentry/core": "8.2.1", - "@sentry/types": "8.2.1", - "@sentry/utils": "8.2.1" + "@sentry-internal/replay": "8.18.0", + "@sentry/core": "8.18.0", + "@sentry/types": "8.18.0", + "@sentry/utils": "8.18.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/babel-plugin-component-annotate": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.16.0.tgz", - "integrity": "sha512-+uy1qPkA5MSNgJ0L9ur/vNTydfdHwHnBX2RQ+0thsvkqf90fU788YjkkXwUiBBNuqNyI69JiOW6frixAWy7oUg==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.20.1.tgz", + "integrity": "sha512-4mhEwYTK00bIb5Y9UWIELVUfru587Vaeg0DQGswv4aIRHIiMKLyNqCEejaaybQ/fNChIZOKmvyqXk430YVd7Qg==", "engines": { "node": ">= 14" } }, "node_modules/@sentry/browser": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.2.1.tgz", - "integrity": "sha512-s9LcHtHOCYQYCnHYMJOcVbSQLeYRjAogskCCLNjVcxpBcfDU+fXnabRZq1rvH3IZnOogp3O6kvIgmLuO3yOBTw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.18.0.tgz", + "integrity": "sha512-E2w9u76JcjxcmgvroJrB7bcbG5oBCYI/pME1CtprBgZSS9mMYDsyBe6JKqGHdw2wvT3xNxNtkm7hf1O6+3NWUQ==", "dependencies": { - "@sentry-internal/browser-utils": "8.2.1", - "@sentry-internal/feedback": "8.2.1", - "@sentry-internal/replay": "8.2.1", - "@sentry-internal/replay-canvas": "8.2.1", - "@sentry/core": "8.2.1", - "@sentry/types": "8.2.1", - "@sentry/utils": "8.2.1" + "@sentry-internal/browser-utils": "8.18.0", + "@sentry-internal/feedback": "8.18.0", + "@sentry-internal/replay": "8.18.0", + "@sentry-internal/replay-canvas": "8.18.0", + "@sentry/core": "8.18.0", + "@sentry/types": "8.18.0", + "@sentry/utils": "8.18.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/bundler-plugin-core": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.16.0.tgz", - "integrity": "sha512-dhgIZsIR3L9KnE2OO5JJm6hPtStAjEPYKQsZzxRr69uVhd9xAvfXeXr0afKVNVEcIDksas6yMgHqwQ2wOXFIAg==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.20.1.tgz", + "integrity": "sha512-6ipbmGzHekxeRCbp7eoefr6bdd/lW4cNA9eNnrmd9+PicubweGaZZbH2NjhFHsaxzgOezwipDHjrTaap2kTHgw==", "dependencies": { "@babel/core": "^7.18.5", - "@sentry/babel-plugin-component-annotate": "2.16.0", + "@sentry/babel-plugin-component-annotate": "2.20.1", "@sentry/cli": "^2.22.3", "dotenv": "^16.3.1", "find-up": "^5.0.0", "glob": "^9.3.2", - "magic-string": "0.27.0", + "magic-string": "0.30.8", "unplugin": "1.0.1" }, "engines": { @@ -7808,9 +7738,9 @@ } }, "node_modules/@sentry/bundler-plugin-core/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "bin": { "acorn": "bin/acorn" }, @@ -7843,6 +7773,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@sentry/bundler-plugin-core/node_modules/magic-string": { + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@sentry/bundler-plugin-core/node_modules/minimatch": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", @@ -7877,9 +7818,9 @@ } }, "node_modules/@sentry/cli": { - "version": "2.31.2", - "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.31.2.tgz", - "integrity": "sha512-2aKyUx6La2P+pplL8+2vO67qJ+c1C79KYWAyQBE0JIT5kvKK9JpwtdNoK1F0/2mRpwhhYPADCz3sVIRqmL8cQQ==", + "version": "2.32.2", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.32.2.tgz", + "integrity": "sha512-m/6Z3FWu+rTd8jepVlJPKQhvbT8vCjt0N7BSWZiEUVW/8mhwAYJiwO0b+Ch/u4IqbBg1dp3805q5TFPl4AdrNw==", "hasInstallScript": true, "dependencies": { "https-proxy-agent": "^5.0.0", @@ -7895,19 +7836,19 @@ "node": ">= 10" }, "optionalDependencies": { - "@sentry/cli-darwin": "2.31.2", - "@sentry/cli-linux-arm": "2.31.2", - "@sentry/cli-linux-arm64": "2.31.2", - "@sentry/cli-linux-i686": "2.31.2", - "@sentry/cli-linux-x64": "2.31.2", - "@sentry/cli-win32-i686": "2.31.2", - "@sentry/cli-win32-x64": "2.31.2" + "@sentry/cli-darwin": "2.32.2", + "@sentry/cli-linux-arm": "2.32.2", + "@sentry/cli-linux-arm64": "2.32.2", + "@sentry/cli-linux-i686": "2.32.2", + "@sentry/cli-linux-x64": "2.32.2", + "@sentry/cli-win32-i686": "2.32.2", + "@sentry/cli-win32-x64": "2.32.2" } }, "node_modules/@sentry/cli-darwin": { - "version": "2.31.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.31.2.tgz", - "integrity": "sha512-BHA/JJXj1dlnoZQdK4efRCtHRnbBfzbIZUKAze7oRR1RfNqERI84BVUQeKateD3jWSJXQfEuclIShc61KOpbKw==", + "version": "2.32.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.32.2.tgz", + "integrity": "sha512-GDtePIavx3FKSRowdPdtIssahn46MfFFYNN+s7a9MjlhFwJtvC9A1bSDw7ksEtDaQolepUwmLPHaVe19y0T/zw==", "optional": true, "os": [ "darwin" @@ -7917,9 +7858,9 @@ } }, "node_modules/@sentry/cli-linux-arm": { - "version": "2.31.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.31.2.tgz", - "integrity": "sha512-W8k5mGYYZz/I/OxZH65YAK7dCkQAl+wbuoASGOQjUy5VDgqH0QJ8kGJufXvFPM+f3ZQGcKAnVsZ6tFqZXETBAw==", + "version": "2.32.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.32.2.tgz", + "integrity": "sha512-u9s08wr8bDDqsAl6pk9iGGlOHtU+T8btU6voNKy71QzeIBpV9c8VVk/OnmP9aswp/ea4NY416yjnzcTvCrFKAw==", "cpu": [ "arm" ], @@ -7933,9 +7874,9 @@ } }, "node_modules/@sentry/cli-linux-arm64": { - "version": "2.31.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.31.2.tgz", - "integrity": "sha512-FLVKkJ/rWvPy/ka7OrUdRW63a/z8HYI1Gt8Pr6rWs50hb7YJja8lM8IO10tYmcFE/tODICsnHO9HTeUg2g2d1w==", + "version": "2.32.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.32.2.tgz", + "integrity": "sha512-VECLVC1rLyvXk6rTVUfmfs4vhANjMgm4BVKGlA3rydmf2PJw2/NfipH3KeyijdE2vEoyLri+/6HH883pP0iniQ==", "cpu": [ "arm64" ], @@ -7949,9 +7890,9 @@ } }, "node_modules/@sentry/cli-linux-i686": { - "version": "2.31.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.31.2.tgz", - "integrity": "sha512-A64QtzaPi3MYFpZ+Fwmi0mrSyXgeLJ0cWr4jdeTGrzNpeowSteKgd6tRKU+LVq0k5shKE7wdnHk+jXnoajulMA==", + "version": "2.32.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.32.2.tgz", + "integrity": "sha512-XhofQz32OqLrQK1DEOsryhT7d29Df6VkccvxueGoIt2gpXEXtgRczsUwZjZqquDdkNCt+HPj9eUGcj8pY8JkmQ==", "cpu": [ "x86", "ia32" @@ -7966,9 +7907,9 @@ } }, "node_modules/@sentry/cli-linux-x64": { - "version": "2.31.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.31.2.tgz", - "integrity": "sha512-YL/r+15R4mOEiU3mzn7iFQOeFEUB6KxeKGTTrtpeOGynVUGIdq4nV5rHow5JDbIzOuBS3SpOmcIMluvo1NCh0g==", + "version": "2.32.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.32.2.tgz", + "integrity": "sha512-anyng4Qqt7zX4ZY4IzDH1RJWAVZNBe6sUHcuciNy7giCU3B4/XnxAHlwYmBSN5txpaumsWdstPgRKEUJG6AOSA==", "cpu": [ "x64" ], @@ -7982,9 +7923,9 @@ } }, "node_modules/@sentry/cli-win32-i686": { - "version": "2.31.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.31.2.tgz", - "integrity": "sha512-Az/2bmW+TFI059RE0mSBIxTBcoShIclz7BDebmIoCkZ+retrwAzpmBnBCDAHow+Yi43utOow+3/4idGa2OxcLw==", + "version": "2.32.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.32.2.tgz", + "integrity": "sha512-/auqx7QXG7F556fNK7vaB26pX7Far1CQMfI65iV4u/VWg6gV2WfvJWXB4iowhjqkYv56sZ+zOymLkEVF0R8wtg==", "cpu": [ "x86", "ia32" @@ -7998,9 +7939,9 @@ } }, "node_modules/@sentry/cli-win32-x64": { - "version": "2.31.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.31.2.tgz", - "integrity": "sha512-XIzyRnJu539NhpFa+JYkotzVwv3NrZ/4GfHB/JWA2zReRvsk39jJG8D5HOmm0B9JA63QQT7Dt39RW8g3lkmb6w==", + "version": "2.32.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.32.2.tgz", + "integrity": "sha512-w7hW2sEWVYQquqdILBSFhcVW+HdoyLqVPPkLPAXRSLTwBnuni9nQEIdXr0h/7db+K3cm7PvWndp5ixVyswLHZA==", "cpu": [ "x64" ], @@ -8013,32 +7954,33 @@ } }, "node_modules/@sentry/core": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.2.1.tgz", - "integrity": "sha512-xHS+DGZodTwXkoqe35UnNR9zWZ7I8pptXGxHntPrNnd/PmXK3ysj4NsRBshtSzDX3gWfwUsMN+vmjrYSwcfYeQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.18.0.tgz", + "integrity": "sha512-8moEMC3gp4W6mH9w5amb/zrYk6bNW8WGgcLRMCs5rguxny8YP5i8ISOJ0T0LP9x/RxSK/6xix5D2bzI/5ECzlw==", "dependencies": { - "@sentry/types": "8.2.1", - "@sentry/utils": "8.2.1" + "@sentry/types": "8.18.0", + "@sentry/utils": "8.18.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/nextjs": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-8.2.1.tgz", - "integrity": "sha512-vP6FEqThcuZQvZO759/NuxJGwdMxpav4SgtqDvdJV7Y+XRB7YpGdCN1wHDVZVeoIk+vnIGep669vVuaEZm/e9g==", - "dependencies": { - "@opentelemetry/instrumentation-http": "0.51.1", - "@rollup/plugin-commonjs": "24.0.0", - "@sentry/core": "8.2.1", - "@sentry/node": "8.2.1", - "@sentry/opentelemetry": "8.2.1", - "@sentry/react": "8.2.1", - "@sentry/types": "8.2.1", - "@sentry/utils": "8.2.1", - "@sentry/vercel-edge": "8.2.1", - "@sentry/webpack-plugin": "2.16.0", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-8.18.0.tgz", + "integrity": "sha512-fmBFxaChOWHU/5Mo5X1ASxFVMF3ZjYImJhM+iLWKpw0Yuu9f9T6p8FPPQC2mPerxTX0yHUqTeVlhHufSPjZNEg==", + "dependencies": { + "@opentelemetry/instrumentation-http": "0.52.1", + "@opentelemetry/semantic-conventions": "^1.25.1", + "@rollup/plugin-commonjs": "26.0.1", + "@sentry/core": "8.18.0", + "@sentry/node": "8.18.0", + "@sentry/opentelemetry": "8.18.0", + "@sentry/react": "8.18.0", + "@sentry/types": "8.18.0", + "@sentry/utils": "8.18.0", + "@sentry/vercel-edge": "8.18.0", + "@sentry/webpack-plugin": "2.20.1", "chalk": "3.0.0", "resolve": "1.22.8", "rollup": "3.29.4", @@ -8048,8 +7990,7 @@ "node": ">=14.18" }, "peerDependencies": { - "next": "^13.2.0 || ^14.0", - "react": "16.x || 17.x || 18.x", + "next": "^13.2.0 || ^14.0 || ^15.0.0-rc.0", "webpack": ">= 5.0.0" }, "peerDependenciesMeta": { @@ -8058,158 +7999,175 @@ } } }, - "node_modules/@sentry/nextjs/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "node_modules/@sentry/nextjs/node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@sentry/nextjs/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@sentry/nextjs/node_modules/@sentry/opentelemetry": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.2.1.tgz", - "integrity": "sha512-VXc6nOwSoP8ofE9gRrQ7Eaec344jxi+36zJnDDRfjtbX1Ah3XoRuQFwhMSS6nTz5leySXLxgYruZTZhj3r2V2A==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.18.0.tgz", + "integrity": "sha512-P2OoXXJcU2RiRZmpBqOkK+NLGkwQrYizlOHl1zckHI1nYmQgOD1tcJj4c1xOYzH+eGPLp/IViXHO6vaBr8BGGg==", "dependencies": { - "@sentry/core": "8.2.1", - "@sentry/types": "8.2.1", - "@sentry/utils": "8.2.1" + "@sentry/core": "8.18.0", + "@sentry/types": "8.18.0", + "@sentry/utils": "8.18.0" }, "engines": { "node": ">=14.18" }, "peerDependencies": { - "@opentelemetry/api": "^1.8.0", - "@opentelemetry/core": "^1.24.1", - "@opentelemetry/instrumentation": "^0.51.1", - "@opentelemetry/sdk-trace-base": "^1.23.0", - "@opentelemetry/semantic-conventions": "^1.23.0" + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^1.25.1", + "@opentelemetry/instrumentation": "^0.52.1", + "@opentelemetry/sdk-trace-base": "^1.25.1", + "@opentelemetry/semantic-conventions": "^1.25.1" } }, "node_modules/@sentry/node": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-8.2.1.tgz", - "integrity": "sha512-7vQQnn+M6qqqWISwrjOaRGx24g9V2Tz+UvrPOWAsZyPBy3kSkuVFef4gHE1geo6CSXMsVpnWkZ8VKJ9I7PaxPg==", - "dependencies": { - "@opentelemetry/api": "^1.8.0", - "@opentelemetry/context-async-hooks": "^1.23.0", - "@opentelemetry/core": "^1.24.1", - "@opentelemetry/instrumentation": "^0.51.1", - "@opentelemetry/instrumentation-connect": "0.36.0", - "@opentelemetry/instrumentation-express": "0.38.0", - "@opentelemetry/instrumentation-fastify": "0.36.1", - "@opentelemetry/instrumentation-graphql": "0.40.0", - "@opentelemetry/instrumentation-hapi": "0.38.0", - "@opentelemetry/instrumentation-http": "0.51.1", - "@opentelemetry/instrumentation-ioredis": "0.40.0", - "@opentelemetry/instrumentation-koa": "0.40.0", - "@opentelemetry/instrumentation-mongodb": "0.43.0", - "@opentelemetry/instrumentation-mongoose": "0.38.1", - "@opentelemetry/instrumentation-mysql": "0.38.1", - "@opentelemetry/instrumentation-mysql2": "0.38.1", - "@opentelemetry/instrumentation-nestjs-core": "0.37.1", - "@opentelemetry/instrumentation-pg": "0.41.0", - "@opentelemetry/resources": "^1.23.0", - "@opentelemetry/sdk-trace-base": "^1.23.0", - "@opentelemetry/semantic-conventions": "^1.23.0", - "@prisma/instrumentation": "5.13.0", - "@sentry/core": "8.2.1", - "@sentry/opentelemetry": "8.2.1", - "@sentry/types": "8.2.1", - "@sentry/utils": "8.2.1" + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-8.18.0.tgz", + "integrity": "sha512-a+W477bmt28I1DT51xJKmp4Y7hBAdEGqQ2K7gfOn3mRBHoihuhKl2Xe8BMwFH7+v4mAEZEwAZBUOLAC7h+Tjig==", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.25.1", + "@opentelemetry/core": "^1.25.1", + "@opentelemetry/instrumentation": "^0.52.1", + "@opentelemetry/instrumentation-connect": "0.38.0", + "@opentelemetry/instrumentation-express": "0.41.0", + "@opentelemetry/instrumentation-fastify": "0.38.0", + "@opentelemetry/instrumentation-graphql": "0.42.0", + "@opentelemetry/instrumentation-hapi": "0.40.0", + "@opentelemetry/instrumentation-http": "0.52.1", + "@opentelemetry/instrumentation-ioredis": "0.42.0", + "@opentelemetry/instrumentation-koa": "0.42.0", + "@opentelemetry/instrumentation-mongodb": "0.46.0", + "@opentelemetry/instrumentation-mongoose": "0.40.0", + "@opentelemetry/instrumentation-mysql": "0.40.0", + "@opentelemetry/instrumentation-mysql2": "0.40.0", + "@opentelemetry/instrumentation-nestjs-core": "0.39.0", + "@opentelemetry/instrumentation-pg": "0.43.0", + "@opentelemetry/instrumentation-redis-4": "0.41.0", + "@opentelemetry/resources": "^1.25.1", + "@opentelemetry/sdk-trace-base": "^1.25.1", + "@opentelemetry/semantic-conventions": "^1.25.1", + "@prisma/instrumentation": "5.16.1", + "@sentry/core": "8.18.0", + "@sentry/opentelemetry": "8.18.0", + "@sentry/types": "8.18.0", + "@sentry/utils": "8.18.0" }, "engines": { "node": ">=14.18" }, "optionalDependencies": { - "opentelemetry-instrumentation-fetch-node": "1.2.0" + "opentelemetry-instrumentation-fetch-node": "1.2.3" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "engines": { + "node": ">=8.0.0" } }, "node_modules/@sentry/node/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", - "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", "engines": { "node": ">=14" } }, "node_modules/@sentry/node/node_modules/@sentry/opentelemetry": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.2.1.tgz", - "integrity": "sha512-VXc6nOwSoP8ofE9gRrQ7Eaec344jxi+36zJnDDRfjtbX1Ah3XoRuQFwhMSS6nTz5leySXLxgYruZTZhj3r2V2A==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.18.0.tgz", + "integrity": "sha512-P2OoXXJcU2RiRZmpBqOkK+NLGkwQrYizlOHl1zckHI1nYmQgOD1tcJj4c1xOYzH+eGPLp/IViXHO6vaBr8BGGg==", "dependencies": { - "@sentry/core": "8.2.1", - "@sentry/types": "8.2.1", - "@sentry/utils": "8.2.1" + "@sentry/core": "8.18.0", + "@sentry/types": "8.18.0", + "@sentry/utils": "8.18.0" }, "engines": { "node": ">=14.18" }, "peerDependencies": { - "@opentelemetry/api": "^1.8.0", - "@opentelemetry/core": "^1.24.1", - "@opentelemetry/instrumentation": "^0.51.1", - "@opentelemetry/sdk-trace-base": "^1.23.0", - "@opentelemetry/semantic-conventions": "^1.23.0" + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^1.25.1", + "@opentelemetry/instrumentation": "^0.52.1", + "@opentelemetry/sdk-trace-base": "^1.25.1", + "@opentelemetry/semantic-conventions": "^1.25.1" } }, "node_modules/@sentry/react": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-8.2.1.tgz", - "integrity": "sha512-kat+Rs4V1DtlGy5rV3aKZ/Kweklqv3E3hsaEd6ZLY5eoqP+ENLiHPEgM7lOaxlPxZclxSHwZQtoB0OtnnNJWYw==", - "dependencies": { - "@sentry/browser": "8.2.1", - "@sentry/core": "8.2.1", - "@sentry/types": "8.2.1", - "@sentry/utils": "8.2.1", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-8.18.0.tgz", + "integrity": "sha512-ckCKdxmeFdfR6moE/Aiq+cJyQuCUKoUqU/++xZwqVbgecuImsk4s7CzzpX9T6JoYK7jqru2SvuRSiwcdtLN6AQ==", + "dependencies": { + "@sentry/browser": "8.18.0", + "@sentry/core": "8.18.0", + "@sentry/types": "8.18.0", + "@sentry/utils": "8.18.0", "hoist-non-react-statics": "^3.3.2" }, "engines": { "node": ">=14.18" }, "peerDependencies": { - "react": "16.x || 17.x || 18.x" + "react": "^16.14.0 || 17.x || 18.x || 19.x" } }, "node_modules/@sentry/types": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.2.1.tgz", - "integrity": "sha512-22ZuANU6Dj/XSvaGhcmNTKD+6WcMc7Zn5uKd8Oj7YcuME6rOnrU8dPGEVwbGTQkE87mTDjVTDSxl8ipb0L+Eag==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.18.0.tgz", + "integrity": "sha512-5J+uOqptnmAnW3Rk31AHIqW36Wzvlo3UOM+p2wjSYGrC/PgcE47Klzr+w4UcOhN6AZqefalGd3vaUXz9NaFdRg==", "engines": { "node": ">=14.18" } }, "node_modules/@sentry/utils": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.2.1.tgz", - "integrity": "sha512-qFeiCdo+QUVpwNSwe63LOPEKc8GWmJ051twtV3tfZ62XgUYOOi2C0qC6mliY3+GKiGVV8fQE6S930nM//j7G1w==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.18.0.tgz", + "integrity": "sha512-7wq7cgaeSIGJncl9/2VMu81ZN5ep4lp4H1/+O8+xUxOmnPb/05ZZcbn9/VxVQvIoqZSZdwCLPeBz6PEVukvokA==", "dependencies": { - "@sentry/types": "8.2.1" + "@sentry/types": "8.18.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/vercel-edge": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-8.2.1.tgz", - "integrity": "sha512-+zcAfYhqJ0K/4dZOYYLMjGp8dZMcqCIiM0qk7a0sqg0Bc/Eta9qJjGpIBx0yVM3pV3XosYqbf2clseO/NjwvmQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-8.18.0.tgz", + "integrity": "sha512-V8jPZNWI93sf1u1sYLAg62oD7Jxw6uGT5bPPHBHbF2XmowW8sIyuXmkXYKVy+s6sOPCyJoW3/0j4BFf/qLowsg==", "dependencies": { - "@sentry/core": "8.2.1", - "@sentry/types": "8.2.1", - "@sentry/utils": "8.2.1" + "@sentry/core": "8.18.0", + "@sentry/types": "8.18.0", + "@sentry/utils": "8.18.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/webpack-plugin": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-2.16.0.tgz", - "integrity": "sha512-BeKLmtK4OD9V3j92fm/lm6yp+++s2U5Uf17HwNFGt39PEOq+wUDISsx0dhXA5Qls2Bg3WhguDK71blCaVefMeg==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-2.20.1.tgz", + "integrity": "sha512-U6LzoE09Ndt0OCWROoRaZqqIHGxyMRdKpBhbqoBqyyfVwXN/zGW3I/cWZ1e8rreiKFj+2+c7+X0kOS+NGMTUrg==", "dependencies": { - "@sentry/bundler-plugin-core": "2.16.0", + "@sentry/bundler-plugin-core": "2.20.1", "unplugin": "1.0.1", "uuid": "^9.0.0" }, @@ -8221,9 +8179,9 @@ } }, "node_modules/@sentry/webpack-plugin/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "bin": { "acorn": "bin/acorn" }, @@ -9259,15 +9217,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@storybook/builder-webpack5/node_modules/magic-string": { - "version": "0.30.10", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", - "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, "node_modules/@storybook/builder-webpack5/node_modules/semver": { "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", @@ -10070,15 +10019,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@storybook/preset-react-webpack/node_modules/magic-string": { - "version": "0.30.10", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", - "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, "node_modules/@storybook/preset-react-webpack/node_modules/semver": { "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", @@ -10572,14 +10512,6 @@ "optional": true, "peer": true }, - "node_modules/@types/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/adm-zip": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.5.tgz", @@ -10640,6 +10572,7 @@ "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -10660,31 +10593,16 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, "dependencies": { "@types/node": "*" } }, - "node_modules/@types/content-disposition": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.8.tgz", - "integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==" - }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" }, - "node_modules/@types/cookies": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.0.tgz", - "integrity": "sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==", - "dependencies": { - "@types/connect": "*", - "@types/express": "*", - "@types/keygrip": "*", - "@types/node": "*" - } - }, "node_modules/@types/cross-spawn": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", @@ -10739,6 +10657,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -10750,6 +10669,7 @@ "version": "4.17.41", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "dev": true, "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -10790,15 +10710,11 @@ "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", "dev": true }, - "node_modules/@types/http-assert": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.5.tgz", - "integrity": "sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==" - }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", @@ -10920,42 +10836,6 @@ "integrity": "sha512-I/WFyFgk5GrNbkpmt14auGO3yFK1Wt4jXzkLuI+fDBNtO5ZI2rbymyGd6bKzfSBEuyRdM64ZUwxU1+eDcPSOEQ==", "dev": true }, - "node_modules/@types/keygrip": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", - "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==" - }, - "node_modules/@types/koa": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.14.0.tgz", - "integrity": "sha512-DTDUyznHGNHAl+wd1n0z1jxNajduyTh8R53xoewuerdBzGo6Ogj6F2299BFtrexJw4NtgjsI5SMPCmV9gZwGXA==", - "dependencies": { - "@types/accepts": "*", - "@types/content-disposition": "*", - "@types/cookies": "*", - "@types/http-assert": "*", - "@types/http-errors": "*", - "@types/keygrip": "*", - "@types/koa-compose": "*", - "@types/node": "*" - } - }, - "node_modules/@types/koa__router": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/@types/koa__router/-/koa__router-12.0.3.tgz", - "integrity": "sha512-5YUJVv6NwM1z7m6FuYpKfNLTZ932Z6EF6xy2BbtpJSyn13DKNQEkXVffFVSnJHxvwwWh2SAeumpjAYUELqgjyw==", - "dependencies": { - "@types/koa": "*" - } - }, - "node_modules/@types/koa-compose": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.8.tgz", - "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==", - "dependencies": { - "@types/koa": "*" - } - }, "node_modules/@types/lodash": { "version": "4.14.202", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", @@ -10976,7 +10856,8 @@ "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true }, "node_modules/@types/mjml": { "version": "4.7.4", @@ -11059,12 +10940,14 @@ "node_modules/@types/qs": { "version": "6.9.10", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", - "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==" + "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==", + "dev": true }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true }, "node_modules/@types/react": { "version": "18.2.79", @@ -11123,6 +11006,7 @@ "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -11132,6 +11016,7 @@ "version": "1.15.5", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, "dependencies": { "@types/http-errors": "*", "@types/mime": "*", @@ -11139,9 +11024,9 @@ } }, "node_modules/@types/shimmer": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.0.5.tgz", - "integrity": "sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==" }, "node_modules/@types/stack-utils": { "version": "2.0.3", @@ -17061,18 +16946,19 @@ "dev": true }, "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=12" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -17102,15 +16988,40 @@ "balanced-match": "^1.0.0" } }, + "node_modules/glob/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" } }, "node_modules/global-modules": { @@ -17816,9 +17727,9 @@ } }, "node_modules/import-in-the-middle": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.7.4.tgz", - "integrity": "sha512-Lk+qzWmiQuRPPulGQeK5qq0v32k2bHnWrRPFgqyvhw7Kkov5L6MOLOIU3pcWeujc9W4q54Cp3Q2WV16eQkc7Bg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.9.1.tgz", + "integrity": "sha512-E+3tEOutU1MV0mxhuCwfSPNNWRkbTJ3/YyL5be+blNIbHwZc53uYHQfuIhAU77xWR0BoF2eT7cqDJ6VlU5APPg==", "dependencies": { "acorn": "^8.8.2", "acorn-import-attributes": "^1.9.5", @@ -17827,9 +17738,9 @@ } }, "node_modules/import-in-the-middle/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "bin": { "acorn": "bin/acorn" }, @@ -20110,49 +20021,6 @@ "node": ">=14" } }, - "node_modules/js-beautify/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/js-beautify/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/js-beautify/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -21317,14 +21185,11 @@ } }, "node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.4.15" } }, "node_modules/make-dir": { @@ -21713,27 +21578,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/mjml-cli/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/mjml-cli/node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", @@ -22890,27 +22734,29 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/opentelemetry-instrumentation-fetch-node": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/opentelemetry-instrumentation-fetch-node/-/opentelemetry-instrumentation-fetch-node-1.2.0.tgz", - "integrity": "sha512-aiSt/4ubOTyb1N5C2ZbGrBvaJOXIZhZvpRPYuUVxQJe27wJZqf/o65iPrqgLcgfeOLaQ8cS2Q+762jrYvniTrA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/opentelemetry-instrumentation-fetch-node/-/opentelemetry-instrumentation-fetch-node-1.2.3.tgz", + "integrity": "sha512-Qb11T7KvoCevMaSeuamcLsAD+pZnavkhDnlVL0kRozfhl42dKG5Q3anUklAFKJZjY3twLR+BnRa6DlwwkIE/+A==", "optional": true, "dependencies": { - "@opentelemetry/api": "^1.6.0", - "@opentelemetry/instrumentation": "^0.43.0", + "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.17.0" }, "engines": { "node": ">18.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.6.0" } }, "node_modules/opentelemetry-instrumentation-fetch-node/node_modules/@opentelemetry/instrumentation": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.43.0.tgz", - "integrity": "sha512-S1uHE+sxaepgp+t8lvIDuRgyjJWisAb733198kwQTUc9ZtYQ2V2gmyCtR1x21ePGVLoMiX/NWY7WA290hwkjJQ==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.46.0.tgz", + "integrity": "sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw==", "optional": true, "dependencies": { "@types/shimmer": "^1.0.2", - "import-in-the-middle": "1.4.2", + "import-in-the-middle": "1.7.1", "require-in-the-middle": "^7.1.1", "semver": "^7.5.2", "shimmer": "^1.2.1" @@ -22923,9 +22769,9 @@ } }, "node_modules/opentelemetry-instrumentation-fetch-node/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "optional": true, "bin": { "acorn": "bin/acorn" @@ -22944,9 +22790,9 @@ } }, "node_modules/opentelemetry-instrumentation-fetch-node/node_modules/import-in-the-middle": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.4.2.tgz", - "integrity": "sha512-9WOz1Yh/cvO/p69sxRmhyQwrIGGSp7EIdcb+fFNVi7CzQGQB8U1/1XrKVSbEd/GNOAeM0peJtmi7+qphe7NvAw==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.7.1.tgz", + "integrity": "sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg==", "optional": true, "dependencies": { "acorn": "^8.8.2", @@ -22956,9 +22802,9 @@ } }, "node_modules/opentelemetry-instrumentation-fetch-node/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "optional": true, "bin": { "semver": "bin/semver.js" @@ -23098,6 +22944,11 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", diff --git a/package.json b/package.json index 6d1ec52a639..881e2e85f2e 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@leeoniya/ufuzzy": "^1.0.14", "@mozilla/glean": "^5.0.2", "@next/third-parties": "^14.2.5", - "@sentry/nextjs": "^8.2.1", + "@sentry/nextjs": "^8.18.0", "@sentry/node": "^8.0.0", "@sentry/utils": "^8.0.0", "@stripe/stripe-js": "^4.0.0", From c264e69281d949da76cf13f9a76bab10c1d0145f Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 16 Jul 2024 13:57:08 +0200 Subject: [PATCH 103/137] Sort-of port update-remote-settings cron to TS The request was to port it to TypeScript even though this code couldn't run yet, because we might be enabling this again soonish. Thus, this ports it to TypeScript, recovers the module that it referenced but no longer existed, replaces that module's use of the `got` package with `fetch`, and ensures it builds. I don't have the right access to be able to verify that it actually runs as expected though. --- package.json | 3 +- scripts/updatebreaches.js | 57 ------- .../updateBreachesInRemoteSettings.ts | 149 ++++++++++++++++++ 3 files changed, 151 insertions(+), 58 deletions(-) delete mode 100644 scripts/updatebreaches.js create mode 100644 src/scripts/cronjobs/updateBreachesInRemoteSettings.ts diff --git a/package.json b/package.json index 881e2e85f2e..ac5bfa839e6 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dev:cron:monthly-activity": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/monthlyActivity.tsx", "dev:cron:db-delete-unverified-subscribers": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/deleteUnverifiedSubscribers.ts", "dev:cron:db-pull-breaches": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/syncBreaches.ts", + "dev:cron:remote-settings-pull-breaches": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/updateBreachesInRemoteSettings.ts", "dev:cron:onerep-limits-alert": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/onerepStatsAlert.ts", "dev:nimbus": "node --watch-path config/nimbus.yaml src/scripts/build/nimbusTypes.js", "build": "npm run get-location-data && npm run build-glean && npm run build-nimbus && next build && npm run build-cronjobs", @@ -29,7 +30,7 @@ "cron:breach-alerts": "node src/scripts/emailBreachAlerts.js", "cron:db-delete-unverified-subscribers": "node dist/scripts/cronjobs/deleteUnverifiedSubscribers.js", "cron:db-pull-breaches": "node dist/scripts/cronjobs/syncBreaches.js", - "cron:remote-settings-pull-breaches": "node scripts/updatebreaches.js", + "cron:remote-settings-pull-breaches": "node dist/scripts/cronjobs/updateBreachesInRemoteSettings.js", "cron:onerep-limits-alert": "node dist/scripts/cronjobs/onerepStatsAlert.js", "db:migrate": "node -r dotenv-flow/config node_modules/knex/bin/cli.js migrate:latest --knexfile src/db/knexfile.js", "db:rollback": "node -r dotenv-flow/config node_modules/knex/bin/cli.js migrate:rollback --knexfile src/db/knexfile.js", diff --git a/scripts/updatebreaches.js b/scripts/updatebreaches.js deleted file mode 100644 index d870c653faa..00000000000 --- a/scripts/updatebreaches.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict' - -/** - * Cron: Daily - * From all the HIBP breaches, we parse out the new breaches that are not already present - * in firefox remote settings, and update the data source accordingly - */ -const AppConstants = require('../app-constants') -const HIBP = require('../hibp') -const RemoteSettings = require('../lib/remote-settings') - -if ( - !AppConstants.FX_REMOTE_SETTINGS_WRITER_USER || - !AppConstants.FX_REMOTE_SETTINGS_WRITER_PASS || - !AppConstants.FX_REMOTE_SETTINGS_WRITER_SERVER -) { - console.error('updatebreaches requires FX_REMOTE_SETTINGS_WRITER_SERVER, FX_REMOTE_SETTINGS_WRITER_USER, FX_REMOTE_SETTINGS_WRITER_PASS.') - process.exit(1) -} - -(async () => { - const allHibpBreaches = await HIBP.req('/breaches') - const verifiedSiteBreaches = HIBP.filterBreaches(allHibpBreaches.body) - const verifiedSiteBreachesWithPWs = verifiedSiteBreaches.filter(breach => breach.DataClasses.includes('Passwords')) - - const newBreaches = await RemoteSettings.whichBreachesAreNotInRemoteSettingsYet(verifiedSiteBreachesWithPWs) - - if (newBreaches.length <= 0) { - console.log('No new breaches detected.') - process.exit(0) - } - - console.log(`${newBreaches.length} new breach(es) found.`) - - for (const breach of newBreaches) { - const data = { - Name: breach.Name, - Domain: breach.Domain, - BreachDate: breach.BreachDate, - PwnCount: breach.PwnCount, - AddedDate: breach.AddedDate, - DataClasses: breach.DataClasses - } - - console.log('New breach detected: \n', data) - - try { - await RemoteSettings.postNewBreachToBreachesCollection(data) - } catch (e) { - console.error(e) - process.exit(1) - } - } - - console.log('Requesting review on breaches collection') - await RemoteSettings.requestReviewOnBreachesCollection() -})() diff --git a/src/scripts/cronjobs/updateBreachesInRemoteSettings.ts b/src/scripts/cronjobs/updateBreachesInRemoteSettings.ts new file mode 100644 index 00000000000..74c2738e82e --- /dev/null +++ b/src/scripts/cronjobs/updateBreachesInRemoteSettings.ts @@ -0,0 +1,149 @@ +"use strict"; + +/** + * Cron: Daily + * From all the HIBP breaches, we parse out the new breaches that are not already present + * in firefox remote settings, and update the data source accordingly + */ + +/* + * + * + * + * + *********************************** Warning *********************************** + * + * This script was in the repository unused, and referenced a module + * `remote-settings.js` that no longer existed at the time of writing (it was + * deleted in commit c727bfe968937e51b0cd42efefd010c7c401aeae). + * I dug it up from the Git history, inlined it in this file, and made sure + * it compiled, but I didn't get to actually run it. + * + * Thus, it could be used as a starting point when re-enabling the remote + * settings upload, but shouldn't be expected to work without modifications. + * + *********************************** Warning *********************************** + * + * + * + * + * + * + * + * + */ + +import AppConstants from "../../appConstants"; +import * as HIBP from "../../utils/hibp"; +import type { Breach } from "../../app/functions/universal/breach"; + +type RemoteSettingsBreach = Pick< + Breach, + "Name" | "Domain" | "BreachDate" | "PwnCount" | "AddedDate" | "DataClasses" +>; + +const BREACHES_COLLECTION = "fxmonitor-breaches"; +const FX_RS_COLLECTION = `${AppConstants.FX_REMOTE_SETTINGS_WRITER_SERVER}/buckets/main-workspace/collections/${BREACHES_COLLECTION}`; +const FX_RS_RECORDS = `${FX_RS_COLLECTION}/records`; +const FX_RS_WRITER_USER = AppConstants.FX_REMOTE_SETTINGS_WRITER_USER; +const FX_RS_WRITER_PASS = AppConstants.FX_REMOTE_SETTINGS_WRITER_PASS; + +async function whichBreachesAreNotInRemoteSettingsYet(breaches: Breach[]) { + const response = await fetch(FX_RS_RECORDS, { + headers: { + Authorization: `Basic ${Buffer.from(FX_RS_WRITER_USER + ":" + FX_RS_WRITER_PASS).toString("base64")}`, + }, + }); + const fxRSRecords = await response.json(); + const remoteSettingsBreachesSet = new Set( + fxRSRecords.body.data.map((b: Breach) => b.Name), + ); + + return breaches.filter(({ Name }) => !remoteSettingsBreachesSet.has(Name)); +} + +async function postNewBreachToBreachesCollection(data: RemoteSettingsBreach) { + // Create the record + const response = await fetch(FX_RS_RECORDS, { + method: "POST", + body: JSON.stringify(data), + headers: { + "Content-Type": "application/json", + Authorization: `Basic ${Buffer.from(FX_RS_WRITER_USER + ":" + FX_RS_WRITER_PASS).toString("base64")}`, + }, + }); + return response.json(); +} + +async function requestReviewOnBreachesCollection() { + const response = await fetch(FX_RS_COLLECTION, { + method: "PATCH", + body: JSON.stringify({ data: { status: "to-review" } }), + headers: { + "Content-Type": "application/json", + Authorization: `Basic ${Buffer.from(FX_RS_WRITER_USER + ":" + FX_RS_WRITER_PASS).toString("base64")}`, + }, + }); + return response.json(); +} + +if ( + !AppConstants.FX_REMOTE_SETTINGS_WRITER_USER || + !AppConstants.FX_REMOTE_SETTINGS_WRITER_PASS || + !AppConstants.FX_REMOTE_SETTINGS_WRITER_SERVER +) { + console.error( + "updatebreaches requires FX_REMOTE_SETTINGS_WRITER_SERVER, FX_REMOTE_SETTINGS_WRITER_USER, FX_REMOTE_SETTINGS_WRITER_PASS.", + ); + process.exit(1); +} + +(async () => { + const allHibpBreaches = (await HIBP.req("/breaches")) as { body: Breach[] }; + const verifiedSiteBreaches = allHibpBreaches.body.filter((breach) => { + return ( + !breach.IsRetired && + !breach.IsSpamList && + !breach.IsFabricated && + breach.IsVerified && + breach.Domain !== "" + ); + }); + const verifiedSiteBreachesWithPWs = verifiedSiteBreaches.filter((breach) => + breach.DataClasses.includes("Passwords"), + ); + + const newBreaches = await whichBreachesAreNotInRemoteSettingsYet( + verifiedSiteBreachesWithPWs, + ); + + if (newBreaches.length <= 0) { + console.log("No new breaches detected."); + process.exit(0); + } + + console.log(`${newBreaches.length} new breach(es) found.`); + + for (const breach of newBreaches) { + const data: RemoteSettingsBreach = { + Name: breach.Name, + Domain: breach.Domain, + BreachDate: breach.BreachDate, + PwnCount: breach.PwnCount, + AddedDate: breach.AddedDate, + DataClasses: breach.DataClasses, + }; + + console.log("New breach detected: \n", data); + + try { + await postNewBreachToBreachesCollection(data); + } catch (e) { + console.error(e); + process.exit(1); + } + } + + console.log("Requesting review on breaches collection"); + await requestReviewOnBreachesCollection(); +})(); From 95afc7ff30ca6e505e6cc218c77e5e4dfea8693e Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Thu, 18 Jul 2024 09:24:01 -0700 Subject: [PATCH 104/137] fix: use account uri --- src/utils/fxa.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/fxa.js b/src/utils/fxa.js index d2a8e281a19..b8a49a6effc 100644 --- a/src/utils/fxa.js +++ b/src/utils/fxa.js @@ -126,7 +126,7 @@ async function getSubscriptions(bearerToken) { */ async function getBillingAndSubscriptions(bearerToken) { - const subscriptionIdUrl = `${process.env.OAUTH_API_URI}/oauth/mozilla-subscriptions/customer/billing-and-subscriptions` + const subscriptionIdUrl = `${process.env.OAUTH_ACCOUNT_URI}/oauth/mozilla-subscriptions/customer/billing-and-subscriptions` try { const getResp = await fetch(subscriptionIdUrl, { From b1ad919acb03183bf5df3c78187219b535672394 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Thu, 18 Jul 2024 09:31:23 -0700 Subject: [PATCH 105/137] fix: consistency --- src/utils/fxa.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/fxa.js b/src/utils/fxa.js index b8a49a6effc..4ab47dba7c4 100644 --- a/src/utils/fxa.js +++ b/src/utils/fxa.js @@ -126,7 +126,7 @@ async function getSubscriptions(bearerToken) { */ async function getBillingAndSubscriptions(bearerToken) { - const subscriptionIdUrl = `${process.env.OAUTH_ACCOUNT_URI}/oauth/mozilla-subscriptions/customer/billing-and-subscriptions` + const subscriptionIdUrl = `${AppConstants.OAUTH_ACCOUNT_URI}/oauth/mozilla-subscriptions/customer/billing-and-subscriptions` try { const getResp = await fetch(subscriptionIdUrl, { From 70ee0686f279a5a1a52cd9ac16718b95c62fb9e5 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Thu, 18 Jul 2024 09:41:05 -0700 Subject: [PATCH 106/137] fix: get rid of API URI --- .env | 1 - .env.local.example | 1 - .github/workflows/preview_deploy_gcp.yml | 1 - 3 files changed, 3 deletions(-) diff --git a/.env b/.env index e7538055d1d..fa101866a92 100755 --- a/.env +++ b/.env @@ -40,7 +40,6 @@ OAUTH_AUTHORIZATION_URI=https://oauth.stage.mozaws.net/v1/authorization OAUTH_PROFILE_URI=https://profile.stage.mozaws.net/v1/profile OAUTH_TOKEN_URI=https://oauth.stage.mozaws.net/v1/token OAUTH_ACCOUNT_URI = "https://oauth.accounts.firefox.com/v1" -OAUTH_API_URI="https://api-accounts.stage.mozaws.net/v1" # HIBP API for breach data # How many seconds to wait before refreshing upstream breach data from HIBP diff --git a/.env.local.example b/.env.local.example index 1a82ca694a7..804444bffc8 100644 --- a/.env.local.example +++ b/.env.local.example @@ -64,7 +64,6 @@ PREMIUM_PLAN_ID_YEARLY_US=price_1NvqawKb9q6OnNsLRTnYrtrV FXA_SUBSCRIPTIONS_URL=https://accounts.stage.mozaws.net/subscriptions FXA_SETTINGS_URL=https://accounts.stage.mozaws.net/settings OAUTH_CLIENT_ID=edd29a80019d61a1 -OAUTH_API_URI="https://api-accounts.stage.mozaws.net/v1" # Set based on https://accounts.stage.mozaws.net/.well-known/openid-configuration OAUTH_AUTHORIZATION_URI=https://accounts.stage.mozaws.net/authorization OAUTH_PROFILE_URI=https://profile.stage.mozaws.net/v1/profile diff --git a/.github/workflows/preview_deploy_gcp.yml b/.github/workflows/preview_deploy_gcp.yml index 9f7bbf5ef70..c6fdef23a32 100644 --- a/.github/workflows/preview_deploy_gcp.yml +++ b/.github/workflows/preview_deploy_gcp.yml @@ -63,7 +63,6 @@ jobs: NEXTAUTH_URL= ${{ secrets.NEXTAUTH_URL }} NEXTAUTH_SECRET=${{ secrets.NEXTAUTH_SECRET }} OAUTH_ACCOUNT_URI=${{ secrets.OAUTH_ACCOUNT_URI }} - OAUTH_API_URI=${{ secrets.OAUTH_API_URI }} OAUTH_CLIENT_ID=${{ secrets.OAUTH_CLIENT_ID }} OAUTH_CLIENT_SECRET=${{ secrets.OAUTH_CLIENT_SECRET }} ONEREP_API_KEY=${{ secrets.ONEREP_API_KEY }} From 53b00c772b6f505dfcb0e0286ffea28b46ee87de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 08:59:15 +0000 Subject: [PATCH 107/137] chore(deps): bump node from 22.3-alpine to 22.4-alpine Bumps node from 22.3-alpine to 22.4-alpine. --- updated-dependencies: - dependency-name: node dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Dockerfile | 2 +- Dockerfile.cloudrun | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5f49577777f..6df05dc6dea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.3-alpine +FROM node:22.4-alpine RUN addgroup -g 10001 app && \ adduser -D -G app -h /app -u 10001 app diff --git a/Dockerfile.cloudrun b/Dockerfile.cloudrun index dc0b8110a6d..179005eac42 100644 --- a/Dockerfile.cloudrun +++ b/Dockerfile.cloudrun @@ -1,4 +1,4 @@ -FROM node:22.3-alpine +FROM node:22.4-alpine RUN addgroup -g 10001 app && \ adduser -D -G app -h /app -u 10001 app From 70269cccd32025c7ca7ae5c62dc8c33c8b3ef373 Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Wed, 10 Jul 2024 08:42:12 -0700 Subject: [PATCH 108/137] bump to node 22.4.1 and npm 10.8.1 --- .github/workflows/build.yaml | 2 +- .github/workflows/e2e_cron.yml | 2 +- .github/workflows/e2e_pr.yml | 2 +- .github/workflows/lint.yaml | 2 +- .github/workflows/unittests.yaml | 2 +- esbuild.cronjobs.js | 2 +- netlify.toml | 2 +- package-lock.json | 2 +- package.json | 6 +++--- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 053c5070923..e22647197ec 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,7 +12,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: '22.3.x' + node-version: '22.4.x' - run: npm ci - run: npm run build-glean # Verify that the build (incl. type-checking) succeeds diff --git a/.github/workflows/e2e_cron.yml b/.github/workflows/e2e_cron.yml index 1e4b93b7d8e..08f68c3dd61 100644 --- a/.github/workflows/e2e_cron.yml +++ b/.github/workflows/e2e_cron.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22.3 + node-version: 22.4 - name: Install dependencies run: npm ci diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 8dca4b381d7..d6f3986c6c6 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22.3 + node-version: 22.4 - name: Install dependencies run: npm ci diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index e5827c7cabb..c97a575e6fd 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -12,7 +12,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: '22.3.x' + node-version: '22.4.x' - run: npm ci - run: npm run build-glean - run: npm run build-nimbus diff --git a/.github/workflows/unittests.yaml b/.github/workflows/unittests.yaml index c934ad1fac3..8bcc252faeb 100644 --- a/.github/workflows/unittests.yaml +++ b/.github/workflows/unittests.yaml @@ -12,7 +12,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: '22.3.x' + node-version: '22.4.x' - run: npm ci - run: npm run build-glean - run: npm test diff --git a/esbuild.cronjobs.js b/esbuild.cronjobs.js index b8be0739677..dcbea0261b0 100644 --- a/esbuild.cronjobs.js +++ b/esbuild.cronjobs.js @@ -21,6 +21,6 @@ build({ format: "esm", outdir: "dist/scripts/cronjobs/", sourcemap: true, - target: "node22.3", + target: "node22.4", packages: "external", }); diff --git a/netlify.toml b/netlify.toml index 8f21f1b47a8..1957208e9d3 100644 --- a/netlify.toml +++ b/netlify.toml @@ -18,4 +18,4 @@ # Default build command. command = "npm ci; npm run build-storybook" - environment = { NODE_VERSION = "22.3.0", NPM_VERSION = "10.8.0" } + environment = { NODE_VERSION = "22.4.1", NPM_VERSION = "10.8.1" } diff --git a/package-lock.json b/package-lock.json index bcae66a0f75..93df26589f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -105,7 +105,7 @@ "yaml": "^2.4.5" }, "engines": { - "node": "22.3.x", + "node": "22.4.x", "npm": "10.8.x" } }, diff --git a/package.json b/package.json index ac5bfa839e6..d24420e07e9 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Firefox Monitor", "engines": { - "node": "22.3.x", + "node": "22.4.x", "npm": "10.8.x" }, "type": "module", @@ -56,8 +56,8 @@ "homepage": "https://github.com/mozilla/blurts-server", "license": "MPL-2.0", "volta": { - "node": "22.3.0", - "npm": "10.8.0" + "node": "22.4.1", + "npm": "10.8.1" }, "dependencies": { "@aws-sdk/client-s3": "^3.614.0", From fe6b56064d59327914143ba9f8480a457c12ab17 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Thu, 18 Jul 2024 16:14:24 -0700 Subject: [PATCH 109/137] fix: typo --- .github/workflows/production_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/production_deploy.yml b/.github/workflows/production_deploy.yml index 3e29b551b4b..fe87ef491c5 100644 --- a/.github/workflows/production_deploy.yml +++ b/.github/workflows/production_deploy.yml @@ -34,7 +34,7 @@ jobs: run: docker pull ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.originalImageTag }} - name: Retag image - run: docker tag ${{ env.DOCKER_IMAGE_NAME }}${{ inputs.originalImageTag }} ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.environment }}-${{ inputs.originalImageTag }} + run: docker tag ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.originalImageTag }} ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.environment }}-${{ inputs.originalImageTag }} - name: Redeploy image run: docker push ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.environment }}-${{ inputs.originalImageTag }} \ No newline at end of file From bb4e3db0e30f0f6941f4dbcc89f295a552f00b29 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Thu, 18 Jul 2024 17:52:28 -0700 Subject: [PATCH 110/137] fix: avoid crashing with profile call --- src/app/functions/server/onerep.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/functions/server/onerep.ts b/src/app/functions/server/onerep.ts index be35d5379b8..c83ba353d63 100644 --- a/src/app/functions/server/onerep.ts +++ b/src/app/functions/server/onerep.ts @@ -477,9 +477,9 @@ export async function getProfilesStats( logger.error( `Failed to fetch OneRep profile: [${response.status}] [${response.statusText}]`, ); - throw new Error( - `Failed to fetch OneRep profile: [${response.status}] [${response.statusText}]`, - ); + // throw new Error( + // `Failed to fetch OneRep profile: [${response.status}] [${response.statusText}]`, + // ); } const profileStats: ProfileStats = await response.json(); From d31f7f5a3f5e1d417c1d7a45787c0ed08492b7e9 Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Thu, 18 Jul 2024 17:52:58 -0700 Subject: [PATCH 111/137] fix: avoid crashing with profile call --- src/app/functions/server/onerep.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/functions/server/onerep.ts b/src/app/functions/server/onerep.ts index c83ba353d63..5137553c123 100644 --- a/src/app/functions/server/onerep.ts +++ b/src/app/functions/server/onerep.ts @@ -475,10 +475,10 @@ export async function getProfilesStats( ); if (!response.ok) { logger.error( - `Failed to fetch OneRep profile: [${response.status}] [${response.statusText}]`, + `Failed to fetch OneRep profile stats: [${response.status}] [${response.statusText}]`, ); // throw new Error( - // `Failed to fetch OneRep profile: [${response.status}] [${response.statusText}]`, + // `Failed to fetch OneRep profile stats: [${response.status}] [${response.statusText}]`, // ); } From f0c641071edba69db62778e59e2052ee44e4d24b Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 19 Jul 2024 03:30:34 +0200 Subject: [PATCH 112/137] chore: Add unit test coverage --- .../(redesign)/(public)/FreeScanCta.tsx | 25 +++++-------- .../(redesign)/(public)/LandingView.test.tsx | 37 ++++++++++++++++++- .../(redesign)/(public)/LandingView.tsx | 5 --- .../(redesign)/(public)/SignUpForm.tsx | 18 +++------ src/app/hooks/useViewTelemetry.ts | 5 +++ 5 files changed, 56 insertions(+), 34 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx index be4ddc2d119..7f715142692 100644 --- a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx @@ -6,24 +6,14 @@ import { signIn } from "next-auth/react"; import { useCookies } from "react-cookie"; -import { SignUpForm, Props as SignUpFormProps } from "./SignUpForm"; +import { Props, SignUpForm } from "./SignUpForm"; import { TelemetryButton } from "../../../components/client/TelemetryButton"; import { modifyAttributionsForUrlSearchParams } from "../../../functions/universal/attributions"; import { ExperimentData } from "../../../../telemetry/generated/nimbus/experiments"; import { useL10n } from "../../../hooks/l10n"; import { WaitlistCta } from "./ScanLimit"; - -export type Props = { - eligibleForPremium: boolean; - signUpCallbackUrl: string; - isHero?: boolean; - eventId: { - cta: string; - field?: string; - }; - scanLimitReached: boolean; - placeholder?: string; -}; +import { useViewTelemetry } from "../../../hooks/useViewTelemetry"; +import { RefObject } from "react"; export function getAttributionSearchParams({ cookies, @@ -60,12 +50,16 @@ export function getAttributionSearchParams({ } export const FreeScanCta = ( - props: SignUpFormProps & { + props: Props & { experimentData: ExperimentData; }, ) => { const l10n = useL10n(); const [cookies] = useCookies(["attributionsFirstTouch"]); + const telemetryButtonId = `${props.eventId.cta}-${props.experimentData["landing-page-free-scan-cta"].variant}`; + const refViewTelemetry = useViewTelemetry("ctaButton", { + button_id: telemetryButtonId, + }); if ( !props.experimentData["landing-page-free-scan-cta"].enabled || props.experimentData["landing-page-free-scan-cta"].variant === @@ -88,12 +82,13 @@ export const FreeScanCta = ( ) : (
} variant="primary" event={{ module: "ctaButton", name: "click", data: { - button_id: `${props.eventId.cta}-${props.experimentData["landing-page-free-scan-cta"].variant}`, + button_id: telemetryButtonId, }, }} onPress={() => { diff --git a/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx b/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx index cff25795f40..3793bf44a68 100644 --- a/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx @@ -5,6 +5,7 @@ import { it, expect } from "@jest/globals"; import { composeStory } from "@storybook/react"; import { + act, getAllByRole, getByRole, getByText, @@ -27,6 +28,7 @@ import Meta, { } from "./LandingView.stories"; import { deleteAllCookies } from "../../../functions/client/deleteAllCookies"; import { defaultExperimentData } from "../../../../telemetry/generated/nimbus/experiments"; +import { mockIsIntersecting } from "react-intersection-observer/test-utils"; jest.mock("next-auth/react", () => { return { @@ -950,7 +952,7 @@ describe("Free scan CTA experiment", () => { expect(waitlistCta[0]).toBeInTheDocument(); }); - it("sends telemetry for the different experiment variants", async () => { + it("sends telemetry when clicking on one of the experiment variants", async () => { const mockedRecord = useTelemetry(); const user = userEvent.setup(); const ComposedDashboard = composeStory(LandingUs, Meta); @@ -983,6 +985,39 @@ describe("Free scan CTA experiment", () => { ); }); + it("sends telemetry when a free scan CTA is shown in the viewport", () => { + const mockedRecord = useTelemetry(); + const ComposedDashboard = composeStory(LandingUs, Meta); + render( + , + ); + + // jsdom will complain about not being able to navigate to a different page + // after clicking the link; suppress that error, as it's not relevant to the + // test: + jest.spyOn(console, "error").mockImplementation(() => undefined); + + const submitButton = screen.getAllByRole("button", { + name: "Get free scan", + }); + act(() => { + mockIsIntersecting(submitButton[0], true); + }); + expect(mockedRecord).toHaveBeenCalledWith( + "ctaButton", + "view", + expect.objectContaining({ button_id: "clicked_get_scan_header-ctaOnly" }), + ); + }); + it("passes the expected URL to the identity provider", async () => { const user = userEvent.setup(); const ComposedDashboard = composeStory(LandingUs, Meta); diff --git a/src/app/(proper_react)/(redesign)/(public)/LandingView.tsx b/src/app/(proper_react)/(redesign)/(public)/LandingView.tsx index a3f2aa96eac..82ec8d4bff5 100644 --- a/src/app/(proper_react)/(redesign)/(public)/LandingView.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/LandingView.tsx @@ -67,7 +67,6 @@ export const View = (props: Props) => { eventId={{ cta: "clicked_get_scan_header", field: "entered_email_address_header", - view: "viewed_get_scan_header", }} experimentData={props.experimentData} /> @@ -140,7 +139,6 @@ export const View = (props: Props) => { eventId={{ cta: "clicked_get_scan_second", field: "entered_email_address_second", - view: "viewed_get_scan_second", }} experimentData={props.experimentData} /> @@ -188,7 +186,6 @@ export const View = (props: Props) => { eventId={{ cta: "clicked_get_scan_third", field: "entered_email_address_third", - view: "viewed_get_scan_third", }} experimentData={props.experimentData} /> @@ -209,7 +206,6 @@ export const View = (props: Props) => { eventId={{ cta: "clicked_get_scan_fourth", field: "entered_email_address_fourth", - view: "viewed_get_scan_fourth", }} scanLimitReached={props.scanLimitReached} experimentData={props.experimentData} @@ -254,7 +250,6 @@ export const View = (props: Props) => { eventId={{ cta: "clicked_get_scan_last", field: "entered_email_address_last", - view: "viewed_get_scan_last", }} scanLimitReached={props.scanLimitReached} experimentData={props.experimentData} diff --git a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx index c3efb8b3b5f..9e40827f2de 100644 --- a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx @@ -19,15 +19,14 @@ import { ExperimentData } from "../../../../telemetry/generated/nimbus/experimen export type Props = { eligibleForPremium: boolean; - signUpCallbackUrl: string; - isHero?: boolean; eventId: { cta: string; field?: string; - view?: string; }; scanLimitReached: boolean; + signUpCallbackUrl: string; experimentData?: ExperimentData; + isHero?: boolean; placeholder?: string; }; @@ -36,16 +35,9 @@ export const SignUpForm = (props: Props) => { const l10n = useL10n(); const [emailInput, setEmailInput] = useState(""); const record = useTelemetry(); - const { view } = props.eventId; - const refViewTelemetry = useViewTelemetry( - "ctaButton", - { - button_id: view, - }, - { - skip: typeof view === "undefined", - }, - ); + const refViewTelemetry = useViewTelemetry("ctaButton", { + button_id: props.eventId.cta, + }); const [cookies] = useCookies(["attributionsFirstTouch"]); const onSubmit: FormEventHandler = (event) => { diff --git a/src/app/hooks/useViewTelemetry.ts b/src/app/hooks/useViewTelemetry.ts index 863785c12db..b7ff9e720fb 100644 --- a/src/app/hooks/useViewTelemetry.ts +++ b/src/app/hooks/useViewTelemetry.ts @@ -22,6 +22,11 @@ export function useViewTelemetry< triggerOnce: true, ...options, onChange: (inView) => { + // Since this function is only triggered once when an element is entering + // the viewport and not again after leaving it. With the current setting + // the following condition is not expected to get called. Keeping the + // condition in place in case this changes. + /* c8 ignore next 3 */ if (!inView) { return; } From 4a68966f429e952573e24bcb4552b28003a86d71 Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Thu, 18 Jul 2024 18:54:56 -0700 Subject: [PATCH 113/137] ongoing incident: catch and log profile stats being down (#4832) --- src/app/functions/server/onerep.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/app/functions/server/onerep.ts b/src/app/functions/server/onerep.ts index 5137553c123..c24e04fe6e3 100644 --- a/src/app/functions/server/onerep.ts +++ b/src/app/functions/server/onerep.ts @@ -482,10 +482,21 @@ export async function getProfilesStats( // ); } - const profileStats: ProfileStats = await response.json(); - - // cache results in map, with a flush hack to keep the size low - if (profileStatsCache.size > 5) profileStatsCache.clear(); - profileStatsCache.set(queryParamsString, profileStats); - return profileStats; + try { + const profileStats: ProfileStats = await response.json(); + + // cache results in map, with a flush hack to keep the size low + if (profileStatsCache.size > 5) profileStatsCache.clear(); + profileStatsCache.set(queryParamsString, profileStats); + return profileStats; + } catch (e) { + if (e instanceof Error) { + logger.error("failed_fetching_stats", { + stack: e.stack, + message: e.message, + }); + } else { + logger.error("failed_fetching_stats", { e }); + } + } } From 044902cb95c7eb9386f98c55d59545e95e9db4c8 Mon Sep 17 00:00:00 2001 From: mansaj Date: Thu, 18 Jul 2024 19:20:52 -0700 Subject: [PATCH 114/137] Revert "chore(deps): bump node from 22.3-alpine to 22.4-alpine" (#4833) * Revert "bump to node 22.4.1 and npm 10.8.1" This reverts commit 70269cccd32025c7ca7ae5c62dc8c33c8b3ef373. * Revert "chore(deps): bump node from 22.3-alpine to 22.4-alpine" This reverts commit 53b00c772b6f505dfcb0e0286ffea28b46ee87de. --- .github/workflows/build.yaml | 2 +- .github/workflows/e2e_cron.yml | 2 +- .github/workflows/e2e_pr.yml | 2 +- .github/workflows/lint.yaml | 2 +- .github/workflows/unittests.yaml | 2 +- Dockerfile | 2 +- Dockerfile.cloudrun | 2 +- esbuild.cronjobs.js | 2 +- netlify.toml | 2 +- package-lock.json | 2 +- package.json | 6 +++--- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e22647197ec..053c5070923 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,7 +12,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: '22.4.x' + node-version: '22.3.x' - run: npm ci - run: npm run build-glean # Verify that the build (incl. type-checking) succeeds diff --git a/.github/workflows/e2e_cron.yml b/.github/workflows/e2e_cron.yml index 08f68c3dd61..1e4b93b7d8e 100644 --- a/.github/workflows/e2e_cron.yml +++ b/.github/workflows/e2e_cron.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22.4 + node-version: 22.3 - name: Install dependencies run: npm ci diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 813dade02d0..53d92d28e91 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22.4 + node-version: 22.3 - name: Install dependencies run: npm ci diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index c97a575e6fd..e5827c7cabb 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -12,7 +12,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: '22.4.x' + node-version: '22.3.x' - run: npm ci - run: npm run build-glean - run: npm run build-nimbus diff --git a/.github/workflows/unittests.yaml b/.github/workflows/unittests.yaml index 8bcc252faeb..c934ad1fac3 100644 --- a/.github/workflows/unittests.yaml +++ b/.github/workflows/unittests.yaml @@ -12,7 +12,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: '22.4.x' + node-version: '22.3.x' - run: npm ci - run: npm run build-glean - run: npm test diff --git a/Dockerfile b/Dockerfile index 6df05dc6dea..5f49577777f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.4-alpine +FROM node:22.3-alpine RUN addgroup -g 10001 app && \ adduser -D -G app -h /app -u 10001 app diff --git a/Dockerfile.cloudrun b/Dockerfile.cloudrun index 179005eac42..dc0b8110a6d 100644 --- a/Dockerfile.cloudrun +++ b/Dockerfile.cloudrun @@ -1,4 +1,4 @@ -FROM node:22.4-alpine +FROM node:22.3-alpine RUN addgroup -g 10001 app && \ adduser -D -G app -h /app -u 10001 app diff --git a/esbuild.cronjobs.js b/esbuild.cronjobs.js index dcbea0261b0..b8be0739677 100644 --- a/esbuild.cronjobs.js +++ b/esbuild.cronjobs.js @@ -21,6 +21,6 @@ build({ format: "esm", outdir: "dist/scripts/cronjobs/", sourcemap: true, - target: "node22.4", + target: "node22.3", packages: "external", }); diff --git a/netlify.toml b/netlify.toml index 1957208e9d3..8f21f1b47a8 100644 --- a/netlify.toml +++ b/netlify.toml @@ -18,4 +18,4 @@ # Default build command. command = "npm ci; npm run build-storybook" - environment = { NODE_VERSION = "22.4.1", NPM_VERSION = "10.8.1" } + environment = { NODE_VERSION = "22.3.0", NPM_VERSION = "10.8.0" } diff --git a/package-lock.json b/package-lock.json index 93df26589f2..bcae66a0f75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -105,7 +105,7 @@ "yaml": "^2.4.5" }, "engines": { - "node": "22.4.x", + "node": "22.3.x", "npm": "10.8.x" } }, diff --git a/package.json b/package.json index d24420e07e9..ac5bfa839e6 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Firefox Monitor", "engines": { - "node": "22.4.x", + "node": "22.3.x", "npm": "10.8.x" }, "type": "module", @@ -56,8 +56,8 @@ "homepage": "https://github.com/mozilla/blurts-server", "license": "MPL-2.0", "volta": { - "node": "22.4.1", - "npm": "10.8.1" + "node": "22.3.0", + "npm": "10.8.0" }, "dependencies": { "@aws-sdk/client-s3": "^3.614.0", From e8542f3147490e308e0e845f42b0e14020a6ad93 Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Thu, 18 Jul 2024 19:25:19 -0700 Subject: [PATCH 115/137] Revert "MNTOR-3353: add refresh token logic to session management" (#4834) --- .github/workflows/e2e_pr.yml | 7 ++- .../dashboard/[[...slug]]/page.tsx | 2 +- .../user/(dashboard)/settings/page.tsx | 5 +- src/app/api/utils/auth.ts | 44 +---------------- src/app/auth/logout/page.tsx | 18 ------- src/app/functions/server/applyCoupon.ts | 8 +-- src/app/functions/server/checkSession.ts | 5 -- src/db/tables/emailAddresses.js | 5 +- src/db/tables/subscribers.js | 48 +----------------- src/next-auth.d.ts | 1 - src/utils/fxa.js | 49 +++---------------- 11 files changed, 22 insertions(+), 170 deletions(-) delete mode 100644 src/app/auth/logout/page.tsx diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 53d92d28e91..8dca4b381d7 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -63,8 +63,8 @@ jobs: run: npm run e2e:smoke timeout-minutes: 10 env: - E2E_TEST_ENV: local - E2E_TEST_BASE_URL: http://localhost:6060 + E2E_TEST_ENV: ${{ inputs.environment != null && inputs.environment || 'local' }} + E2E_TEST_BASE_URL: ${{ secrets.E2E_TEST_BASE_URL }} E2E_TEST_ACCOUNT_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL }} E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES }} E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED }} @@ -72,8 +72,7 @@ jobs: E2E_TEST_PAYPAL_LOGIN: ${{ secrets.E2E_TEST_PAYPAL_LOGIN }} E2E_TEST_PAYPAL_PASSWORD: ${{ secrets.E2E_TEST_PAYPAL_PASSWORD }} ADMINS: ${{ secrets.ADMINS }} - OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET_LOCAL }} - OAUTH_ACCOUNT_URI: ${{ secrets.OAUTH_ACCOUNT_URI }} + OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }} ONEREP_API_KEY: ${{ secrets.ONEREP_API_KEY }} NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx index 074db109a32..81e3b826df3 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx @@ -57,7 +57,7 @@ type Props = { export default async function DashboardPage({ params, searchParams }: Props) { const session = await getServerSession(); if (!checkSession(session) || !session?.user?.subscriber?.id) { - return redirect("/auth/logout"); + return redirect("/"); } const { slug } = params; diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/page.tsx index c6f0d82363c..b06fa5df122 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/page.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/page.tsx @@ -23,7 +23,6 @@ import { getExperiments } from "../../../../../../functions/server/getExperiment import { getLocale } from "../../../../../../functions/universal/getLocale"; import { getCountryCode } from "../../../../../../functions/server/getCountryCode"; import { getSubscriberById } from "../../../../../../../db/tables/subscribers"; -import { checkSession } from "../../../../../../functions/server/checkSession"; import { checkUserHasMonthlySubscription } from "../../../../../../functions/universal/user"; type Props = { @@ -36,8 +35,8 @@ export default async function SettingsPage({ searchParams }: Props) { const session = await getServerSession(); console.debug(searchParams); - if (!session?.user?.subscriber?.id || !checkSession(session)) { - return redirect("/auth/logout"); + if (!session?.user?.subscriber?.id) { + return redirect("/"); } const emailAddresses = await getUserEmails(session.user.subscriber.id); diff --git a/src/app/api/utils/auth.ts b/src/app/api/utils/auth.ts index e89ba0b19d5..0f75f3a7780 100644 --- a/src/app/api/utils/auth.ts +++ b/src/app/api/utils/auth.ts @@ -12,13 +12,11 @@ import { getSubscriberByFxaUid, updateFxAData, incrementSignInCountForEligibleFreeUser, - getFxATokens, - updateFxATokens, } from "../../../db/tables/subscribers.js"; import { addSubscriber } from "../../../db/tables/emailAddresses.js"; import { getBreaches } from "../../functions/server/getBreaches"; import { getBreachesForEmail } from "../../../utils/hibp.js"; -import { getSha1, refreshOAuthTokens } from "../../../utils/fxa.js"; +import { getSha1 } from "../../../utils/fxa.js"; import { getEmailCtaDashboardHref, initEmail, @@ -132,7 +130,6 @@ export const authOptions: AuthOptions = { existingUser, account.access_token, account.refresh_token, - account.expires_at ?? 0, JSON.stringify(profile), ); // MNTOR-2599 The breach_resolution object can get pretty big, @@ -147,7 +144,6 @@ export const authOptions: AuthOptions = { profile.locale, account.access_token, account.refresh_token, - account.expires_at, JSON.stringify(profile), ); // The date fields of `verifiedSubscriber` get converted to an ISO 8601 @@ -213,7 +209,7 @@ export const authOptions: AuthOptions = { } return token; }, - async session({ session, token }) { + session({ session, token }) { if (token.fxa) { session.user.fxa = { locale: token.fxa.locale, @@ -226,43 +222,7 @@ export const authOptions: AuthOptions = { } if (token.subscriber) { session.user.subscriber = token.subscriber; - - // refresh token - const dbFxATokens = await getFxATokens(token.subscriber.id); - if ( - !dbFxATokens?.fxa_session_expiry || - dbFxATokens.fxa_session_expiry.getTime() < Date.now() - ) { - // If the access token has expired, try to refresh it - if (!dbFxATokens?.fxa_refresh_token) { - logger.error("no_fxa_refresh_token", { dbFxATokens }); - session.error = "RefreshAccessTokenError"; - return session; - } - try { - const responseTokens = await refreshOAuthTokens( - dbFxATokens.fxa_refresh_token, - ); - const updatedUser = await updateFxATokens( - token.subscriber, - responseTokens.access_token, - responseTokens.refresh_token, - 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.breach_resolution; - token.subscriber = updatedUser; - } catch (error) { - logger.error("refresh_access_token", error); - // The error property can be used client-side to handle the refresh token error - session.error = "RefreshAccessTokenError"; - } - } } - return session; }, }, diff --git a/src/app/auth/logout/page.tsx b/src/app/auth/logout/page.tsx deleted file mode 100644 index d1bf7bda1a7..00000000000 --- a/src/app/auth/logout/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* 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/. */ - -"use client"; -import { signOut } from "next-auth/react"; -import { useEffect } from "react"; - -export default function LogoutPage() { - useEffect(() => { - void signOut({ - redirect: true, - callbackUrl: "/", - }); - }, []); - - return <>; -} diff --git a/src/app/functions/server/applyCoupon.ts b/src/app/functions/server/applyCoupon.ts index d337ac75ca7..3ac308b257b 100644 --- a/src/app/functions/server/applyCoupon.ts +++ b/src/app/functions/server/applyCoupon.ts @@ -66,10 +66,10 @@ export async function checkCurrentCouponCode( const currentCouponCode = process.env.CURRENT_COUPON_CODE_ID; if (!currentCouponCode) { - logger.error("fxa_check_coupon_failed", { - exception: - "Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID", - }); + logger.error( + "fxa_check_coupon_failed", + "Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID", + ); return { success: false, }; diff --git a/src/app/functions/server/checkSession.ts b/src/app/functions/server/checkSession.ts index a60d553c2b9..7dbdfd4c2fb 100644 --- a/src/app/functions/server/checkSession.ts +++ b/src/app/functions/server/checkSession.ts @@ -18,11 +18,6 @@ export function checkSession(session: Session | null) { } else if (!session.user.subscriber.id) { logger.warn("no_subscriber_id_in_session", { session }); return false; - } else if (session.error === "RefreshAccessTokenError") { - logger.error("refresh_access_token_failed", { - exception: "Refreshing access token failed... require login again", - }); - return false; } else { return true; } diff --git a/src/db/tables/emailAddresses.js b/src/db/tables/emailAddresses.js index 23fc6c3a027..f58ba88f12b 100644 --- a/src/db/tables/emailAddresses.js +++ b/src/db/tables/emailAddresses.js @@ -218,19 +218,18 @@ async function _addEmailHash (sha1, email, signupLanguage, verified = false) { * @param {string} signupLanguage from Accept-Language * @param {string | null} fxaAccessToken from Firefox Account Oauth * @param {string | null} fxaRefreshToken from Firefox Account Oauth - * @param {number} sessionExpiresAt from Firefox Account Oauth * @param {string | null} fxaProfileData from Firefox Account * @returns {Promise} subscriber knex object added to DB */ // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy /* c8 ignore start */ -async function addSubscriber (email, signupLanguage, fxaAccessToken = null, fxaRefreshToken = null, sessionExpiresAt = 0, fxaProfileData = null) { +async function addSubscriber (email, signupLanguage, fxaAccessToken = null, fxaRefreshToken = null, fxaProfileData = null) { const lowerCaseEmail = email.toLowerCase() const emailHash = await _addEmailHash(getSha1(lowerCaseEmail), lowerCaseEmail, signupLanguage, true) const verified = await _verifySubscriber(emailHash) const verifiedSubscriber = Array.isArray(verified) ? verified[0] : null if (fxaRefreshToken || fxaProfileData) { - return updateFxAData(verifiedSubscriber, fxaAccessToken, fxaRefreshToken, sessionExpiresAt, fxaProfileData) + return updateFxAData(verifiedSubscriber, fxaAccessToken, fxaRefreshToken, fxaProfileData) } return verifiedSubscriber } diff --git a/src/db/tables/subscribers.js b/src/db/tables/subscribers.js index c742a3c7dea..30c081d119c 100644 --- a/src/db/tables/subscribers.js +++ b/src/db/tables/subscribers.js @@ -121,13 +121,12 @@ async function updatePrimaryEmail (subscriber, updatedEmail) { * @param {any} subscriber knex object in DB * @param {string | null} fxaAccessToken from Firefox Account Oauth * @param {string | null} fxaRefreshToken from Firefox Account Oauth - * @param {number} sessionExpiresAt from Firefox Account Oauth * @param {any} 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 /* c8 ignore start */ -async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, sessionExpiresAt, fxaProfileData) { +async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, fxaProfileData) { const fxaUID = JSON.parse(fxaProfileData).uid const updated = await knex('subscribers') .where('id', '=', subscriber.id) @@ -135,7 +134,6 @@ async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, sessi fxa_uid: fxaUID, fxa_access_token: fxaAccessToken, fxa_refresh_token: fxaRefreshToken, - fxa_session_expiry: new Date(sessionExpiresAt), 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: @@ -150,48 +148,6 @@ async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, sessi } /* c8 ignore stop */ -/** - * Update fxa tokens for subscriber - * - * @param {any} subscriber knex object in DB - * @param {string | null} fxaAccessToken from Firefox Account Oauth - * @param {string | null} fxaRefreshToken from Firefox Account Oauth - * @param {number} sessionExpiresAt from Firefox Account Oauth - * @returns {Promise} updated subscriber knex object in DB - */ -// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy -/* c8 ignore start */ -async function updateFxATokens (subscriber, fxaAccessToken, fxaRefreshToken, sessionExpiresAt) { - const updateResp = await knex('subscribers') - .where('id', '=', subscriber.id) - .update({ - fxa_access_token: fxaAccessToken, - fxa_refresh_token: fxaRefreshToken, - fxa_session_expiry: new Date(sessionExpiresAt), - // @ts-ignore knex.fn.now() results in it being set to a date, - // even if it's not typed as a JS date object: - updated_at: knex.fn.now(), - }) - .returning('*'); - return (Array.isArray(updateResp) && updateResp.length > 0) ? updateResp[0] : null; -} -/* c8 ignore stop */ - -/** - * Get fxa tokens and expiry for subscriber - * - * @param {number} subscriberId - */ -// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy -/* c8 ignore start */ -async function getFxATokens (subscriberId) { - const res = await knex('subscribers') - .first('fxa_access_token', 'fxa_refresh_token', 'fxa_session_expiry') - .where('id', subscriberId) - return res ?? null -} -/* c8 ignore stop */ - /** * Update fxa_profile_json for subscriber * @@ -670,8 +626,6 @@ export { getSubscribersWithUnresolvedBreachesCount, updatePrimaryEmail, updateFxAData, - updateFxATokens, - getFxATokens, updateFxAProfileData, setAllEmailsToPrimary, setMonthlyMonitorReport, diff --git a/src/next-auth.d.ts b/src/next-auth.d.ts index 00153ed7364..ec4330a4e51 100644 --- a/src/next-auth.d.ts +++ b/src/next-auth.d.ts @@ -42,7 +42,6 @@ declare module "next-auth" { /** Session data available after deserialising the JWT */ interface Session { - error?: "RefreshAccessTokenError"; user: { fxa?: { /** The value of the Accept-Language header when the user signed up for their Firefox Account */ diff --git a/src/utils/fxa.js b/src/utils/fxa.js index 26af41e4803..4ab47dba7c4 100644 --- a/src/utils/fxa.js +++ b/src/utils/fxa.js @@ -73,41 +73,6 @@ async function revokeOAuthTokens(subscriber) { await destroyOAuthToken({ token: subscriber.fxa_refresh_token, token_type_hint: "refresh_token" }) } -/** - * @param {string} refreshToken - * @returns {Promise<{access_token: string, refresh_token: string, expires_in: number}>} - */ -// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy -/* c8 ignore start */ -async function refreshOAuthTokens(refreshToken) { - const subscriptionIdUrl = `${AppConstants.OAUTH_ACCOUNT_URI}/oauth/token` - try { - const postResp = await fetch(subscriptionIdUrl, { - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - client_id: AppConstants.OAUTH_CLIENT_ID, - client_secret: AppConstants.OAUTH_CLIENT_SECRET, - grant_type: "refresh_token", - refresh_token: refreshToken, - ttl: 604800, // request 7 days ttl - }), - method: "POST", - - }) - - const responseTokens = await postResp.json(); - if (!postResp.ok) throw responseTokens; - return responseTokens - - } catch (e) { - if (e instanceof Error) { - console.error('refresh_fxa_access_token', { stack: e.stack }) - } - throw e - } -} -/* c8 ignore stop */ - /** * @param {string} bearerToken * @returns {Promise | null>} @@ -123,12 +88,13 @@ async function getSubscriptions(bearerToken) { Authorization: `Bearer ${bearerToken}` } }) - const resp = getResp.json() - if (!getResp.ok) throw resp; - console.info(`get_fxa_subscriptions: success`) - return resp; - + if (!getResp.ok) { + throw new InternalServerError(`bad response: ${getResp.status}`) + } else { + console.info(`get_fxa_subscriptions: success`) + return await getResp.json() + } } catch (e) { if (e instanceof Error) { console.error('get_fxa_subscriptions', { stack: e.stack }) @@ -257,7 +223,7 @@ async function applyCoupon(bearerToken, couponCodeId) { }) if (!response.ok) { const errMsg = await response.text() - console.error(`apply_coupon: failed - ${errMsg}`) + console.info(`apply_coupon: failed - ${errMsg}`) throw new Error(`apply_coupon: failed - ${errMsg}`) } else { console.info(`apply_coupon: success - ${JSON.stringify(await response.json())}`) @@ -283,7 +249,6 @@ function getSha1(email) { } export { - refreshOAuthTokens, destroyOAuthToken, revokeOAuthTokens, getSha1, From b0b2c368b64c4c3d2ecd9c2450601fa4771c5c5e Mon Sep 17 00:00:00 2001 From: mansaj Date: Thu, 18 Jul 2024 20:20:40 -0700 Subject: [PATCH 116/137] =?UTF-8?q?Revert=20"Revert=20"MNTOR-3353:=20add?= =?UTF-8?q?=20refresh=20token=20logic=20to=20session=20management"=20(#?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit e8542f3147490e308e0e845f42b0e14020a6ad93. --- .github/workflows/e2e_pr.yml | 7 +-- .../dashboard/[[...slug]]/page.tsx | 2 +- .../user/(dashboard)/settings/page.tsx | 5 +- src/app/api/utils/auth.ts | 44 ++++++++++++++++- src/app/auth/logout/page.tsx | 18 +++++++ src/app/functions/server/applyCoupon.ts | 8 +-- src/app/functions/server/checkSession.ts | 5 ++ src/db/tables/emailAddresses.js | 5 +- src/db/tables/subscribers.js | 48 +++++++++++++++++- src/next-auth.d.ts | 1 + src/utils/fxa.js | 49 ++++++++++++++++--- 11 files changed, 170 insertions(+), 22 deletions(-) create mode 100644 src/app/auth/logout/page.tsx diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 8dca4b381d7..53d92d28e91 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -63,8 +63,8 @@ jobs: run: npm run e2e:smoke timeout-minutes: 10 env: - E2E_TEST_ENV: ${{ inputs.environment != null && inputs.environment || 'local' }} - E2E_TEST_BASE_URL: ${{ secrets.E2E_TEST_BASE_URL }} + E2E_TEST_ENV: local + E2E_TEST_BASE_URL: http://localhost:6060 E2E_TEST_ACCOUNT_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL }} E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_ZERO_BREACHES }} E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED: ${{ secrets.E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED }} @@ -72,7 +72,8 @@ jobs: E2E_TEST_PAYPAL_LOGIN: ${{ secrets.E2E_TEST_PAYPAL_LOGIN }} E2E_TEST_PAYPAL_PASSWORD: ${{ secrets.E2E_TEST_PAYPAL_PASSWORD }} ADMINS: ${{ secrets.ADMINS }} - OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }} + OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET_LOCAL }} + OAUTH_ACCOUNT_URI: ${{ secrets.OAUTH_ACCOUNT_URI }} ONEREP_API_KEY: ${{ secrets.ONEREP_API_KEY }} NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx index 81e3b826df3..074db109a32 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx @@ -57,7 +57,7 @@ type Props = { export default async function DashboardPage({ params, searchParams }: Props) { const session = await getServerSession(); if (!checkSession(session) || !session?.user?.subscriber?.id) { - return redirect("/"); + return redirect("/auth/logout"); } const { slug } = params; diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/page.tsx index b06fa5df122..c6f0d82363c 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/page.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/page.tsx @@ -23,6 +23,7 @@ import { getExperiments } from "../../../../../../functions/server/getExperiment import { getLocale } from "../../../../../../functions/universal/getLocale"; import { getCountryCode } from "../../../../../../functions/server/getCountryCode"; import { getSubscriberById } from "../../../../../../../db/tables/subscribers"; +import { checkSession } from "../../../../../../functions/server/checkSession"; import { checkUserHasMonthlySubscription } from "../../../../../../functions/universal/user"; type Props = { @@ -35,8 +36,8 @@ export default async function SettingsPage({ searchParams }: Props) { const session = await getServerSession(); console.debug(searchParams); - if (!session?.user?.subscriber?.id) { - return redirect("/"); + if (!session?.user?.subscriber?.id || !checkSession(session)) { + return redirect("/auth/logout"); } const emailAddresses = await getUserEmails(session.user.subscriber.id); diff --git a/src/app/api/utils/auth.ts b/src/app/api/utils/auth.ts index 0f75f3a7780..e89ba0b19d5 100644 --- a/src/app/api/utils/auth.ts +++ b/src/app/api/utils/auth.ts @@ -12,11 +12,13 @@ import { getSubscriberByFxaUid, updateFxAData, incrementSignInCountForEligibleFreeUser, + getFxATokens, + updateFxATokens, } from "../../../db/tables/subscribers.js"; import { addSubscriber } from "../../../db/tables/emailAddresses.js"; import { getBreaches } from "../../functions/server/getBreaches"; import { getBreachesForEmail } from "../../../utils/hibp.js"; -import { getSha1 } from "../../../utils/fxa.js"; +import { getSha1, refreshOAuthTokens } from "../../../utils/fxa.js"; import { getEmailCtaDashboardHref, initEmail, @@ -130,6 +132,7 @@ export const authOptions: AuthOptions = { existingUser, account.access_token, account.refresh_token, + account.expires_at ?? 0, JSON.stringify(profile), ); // MNTOR-2599 The breach_resolution object can get pretty big, @@ -144,6 +147,7 @@ export const authOptions: AuthOptions = { profile.locale, account.access_token, account.refresh_token, + account.expires_at, JSON.stringify(profile), ); // The date fields of `verifiedSubscriber` get converted to an ISO 8601 @@ -209,7 +213,7 @@ export const authOptions: AuthOptions = { } return token; }, - session({ session, token }) { + async session({ session, token }) { if (token.fxa) { session.user.fxa = { locale: token.fxa.locale, @@ -222,7 +226,43 @@ export const authOptions: AuthOptions = { } if (token.subscriber) { session.user.subscriber = token.subscriber; + + // refresh token + const dbFxATokens = await getFxATokens(token.subscriber.id); + if ( + !dbFxATokens?.fxa_session_expiry || + dbFxATokens.fxa_session_expiry.getTime() < Date.now() + ) { + // If the access token has expired, try to refresh it + if (!dbFxATokens?.fxa_refresh_token) { + logger.error("no_fxa_refresh_token", { dbFxATokens }); + session.error = "RefreshAccessTokenError"; + return session; + } + try { + const responseTokens = await refreshOAuthTokens( + dbFxATokens.fxa_refresh_token, + ); + const updatedUser = await updateFxATokens( + token.subscriber, + responseTokens.access_token, + responseTokens.refresh_token, + 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.breach_resolution; + token.subscriber = updatedUser; + } catch (error) { + logger.error("refresh_access_token", error); + // The error property can be used client-side to handle the refresh token error + session.error = "RefreshAccessTokenError"; + } + } } + return session; }, }, diff --git a/src/app/auth/logout/page.tsx b/src/app/auth/logout/page.tsx new file mode 100644 index 00000000000..d1bf7bda1a7 --- /dev/null +++ b/src/app/auth/logout/page.tsx @@ -0,0 +1,18 @@ +/* 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/. */ + +"use client"; +import { signOut } from "next-auth/react"; +import { useEffect } from "react"; + +export default function LogoutPage() { + useEffect(() => { + void signOut({ + redirect: true, + callbackUrl: "/", + }); + }, []); + + return <>; +} diff --git a/src/app/functions/server/applyCoupon.ts b/src/app/functions/server/applyCoupon.ts index 3ac308b257b..d337ac75ca7 100644 --- a/src/app/functions/server/applyCoupon.ts +++ b/src/app/functions/server/applyCoupon.ts @@ -66,10 +66,10 @@ export async function checkCurrentCouponCode( const currentCouponCode = process.env.CURRENT_COUPON_CODE_ID; if (!currentCouponCode) { - logger.error( - "fxa_check_coupon_failed", - "Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID", - ); + logger.error("fxa_check_coupon_failed", { + exception: + "Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID", + }); return { success: false, }; diff --git a/src/app/functions/server/checkSession.ts b/src/app/functions/server/checkSession.ts index 7dbdfd4c2fb..a60d553c2b9 100644 --- a/src/app/functions/server/checkSession.ts +++ b/src/app/functions/server/checkSession.ts @@ -18,6 +18,11 @@ export function checkSession(session: Session | null) { } else if (!session.user.subscriber.id) { logger.warn("no_subscriber_id_in_session", { session }); return false; + } else if (session.error === "RefreshAccessTokenError") { + logger.error("refresh_access_token_failed", { + exception: "Refreshing access token failed... require login again", + }); + return false; } else { return true; } diff --git a/src/db/tables/emailAddresses.js b/src/db/tables/emailAddresses.js index f58ba88f12b..23fc6c3a027 100644 --- a/src/db/tables/emailAddresses.js +++ b/src/db/tables/emailAddresses.js @@ -218,18 +218,19 @@ async function _addEmailHash (sha1, email, signupLanguage, verified = false) { * @param {string} signupLanguage from Accept-Language * @param {string | null} fxaAccessToken from Firefox Account Oauth * @param {string | null} fxaRefreshToken from Firefox Account Oauth + * @param {number} sessionExpiresAt from Firefox Account Oauth * @param {string | null} fxaProfileData from Firefox Account * @returns {Promise} subscriber knex object added to DB */ // Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy /* c8 ignore start */ -async function addSubscriber (email, signupLanguage, fxaAccessToken = null, fxaRefreshToken = null, fxaProfileData = null) { +async function addSubscriber (email, signupLanguage, fxaAccessToken = null, fxaRefreshToken = null, sessionExpiresAt = 0, fxaProfileData = null) { const lowerCaseEmail = email.toLowerCase() const emailHash = await _addEmailHash(getSha1(lowerCaseEmail), lowerCaseEmail, signupLanguage, true) const verified = await _verifySubscriber(emailHash) const verifiedSubscriber = Array.isArray(verified) ? verified[0] : null if (fxaRefreshToken || fxaProfileData) { - return updateFxAData(verifiedSubscriber, fxaAccessToken, fxaRefreshToken, fxaProfileData) + return updateFxAData(verifiedSubscriber, fxaAccessToken, fxaRefreshToken, sessionExpiresAt, fxaProfileData) } return verifiedSubscriber } diff --git a/src/db/tables/subscribers.js b/src/db/tables/subscribers.js index 30c081d119c..c742a3c7dea 100644 --- a/src/db/tables/subscribers.js +++ b/src/db/tables/subscribers.js @@ -121,12 +121,13 @@ async function updatePrimaryEmail (subscriber, updatedEmail) { * @param {any} subscriber knex object in DB * @param {string | null} fxaAccessToken from Firefox Account Oauth * @param {string | null} fxaRefreshToken from Firefox Account Oauth + * @param {number} sessionExpiresAt from Firefox Account Oauth * @param {any} 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 /* c8 ignore start */ -async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, fxaProfileData) { +async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, sessionExpiresAt, fxaProfileData) { const fxaUID = JSON.parse(fxaProfileData).uid const updated = await knex('subscribers') .where('id', '=', subscriber.id) @@ -134,6 +135,7 @@ async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, fxaPr fxa_uid: fxaUID, fxa_access_token: fxaAccessToken, fxa_refresh_token: fxaRefreshToken, + fxa_session_expiry: new Date(sessionExpiresAt), 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: @@ -148,6 +150,48 @@ async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, fxaPr } /* c8 ignore stop */ +/** + * Update fxa tokens for subscriber + * + * @param {any} subscriber knex object in DB + * @param {string | null} fxaAccessToken from Firefox Account Oauth + * @param {string | null} fxaRefreshToken from Firefox Account Oauth + * @param {number} sessionExpiresAt from Firefox Account Oauth + * @returns {Promise} updated subscriber knex object in DB + */ +// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy +/* c8 ignore start */ +async function updateFxATokens (subscriber, fxaAccessToken, fxaRefreshToken, sessionExpiresAt) { + const updateResp = await knex('subscribers') + .where('id', '=', subscriber.id) + .update({ + fxa_access_token: fxaAccessToken, + fxa_refresh_token: fxaRefreshToken, + fxa_session_expiry: new Date(sessionExpiresAt), + // @ts-ignore knex.fn.now() results in it being set to a date, + // even if it's not typed as a JS date object: + updated_at: knex.fn.now(), + }) + .returning('*'); + return (Array.isArray(updateResp) && updateResp.length > 0) ? updateResp[0] : null; +} +/* c8 ignore stop */ + +/** + * Get fxa tokens and expiry for subscriber + * + * @param {number} subscriberId + */ +// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy +/* c8 ignore start */ +async function getFxATokens (subscriberId) { + const res = await knex('subscribers') + .first('fxa_access_token', 'fxa_refresh_token', 'fxa_session_expiry') + .where('id', subscriberId) + return res ?? null +} +/* c8 ignore stop */ + /** * Update fxa_profile_json for subscriber * @@ -626,6 +670,8 @@ export { getSubscribersWithUnresolvedBreachesCount, updatePrimaryEmail, updateFxAData, + updateFxATokens, + getFxATokens, updateFxAProfileData, setAllEmailsToPrimary, setMonthlyMonitorReport, diff --git a/src/next-auth.d.ts b/src/next-auth.d.ts index ec4330a4e51..00153ed7364 100644 --- a/src/next-auth.d.ts +++ b/src/next-auth.d.ts @@ -42,6 +42,7 @@ declare module "next-auth" { /** Session data available after deserialising the JWT */ interface Session { + error?: "RefreshAccessTokenError"; user: { fxa?: { /** The value of the Accept-Language header when the user signed up for their Firefox Account */ diff --git a/src/utils/fxa.js b/src/utils/fxa.js index 4ab47dba7c4..26af41e4803 100644 --- a/src/utils/fxa.js +++ b/src/utils/fxa.js @@ -73,6 +73,41 @@ async function revokeOAuthTokens(subscriber) { await destroyOAuthToken({ token: subscriber.fxa_refresh_token, token_type_hint: "refresh_token" }) } +/** + * @param {string} refreshToken + * @returns {Promise<{access_token: string, refresh_token: string, expires_in: number}>} + */ +// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy +/* c8 ignore start */ +async function refreshOAuthTokens(refreshToken) { + const subscriptionIdUrl = `${AppConstants.OAUTH_ACCOUNT_URI}/oauth/token` + try { + const postResp = await fetch(subscriptionIdUrl, { + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + client_id: AppConstants.OAUTH_CLIENT_ID, + client_secret: AppConstants.OAUTH_CLIENT_SECRET, + grant_type: "refresh_token", + refresh_token: refreshToken, + ttl: 604800, // request 7 days ttl + }), + method: "POST", + + }) + + const responseTokens = await postResp.json(); + if (!postResp.ok) throw responseTokens; + return responseTokens + + } catch (e) { + if (e instanceof Error) { + console.error('refresh_fxa_access_token', { stack: e.stack }) + } + throw e + } +} +/* c8 ignore stop */ + /** * @param {string} bearerToken * @returns {Promise | null>} @@ -88,13 +123,12 @@ async function getSubscriptions(bearerToken) { Authorization: `Bearer ${bearerToken}` } }) + const resp = getResp.json() + if (!getResp.ok) throw resp; - if (!getResp.ok) { - throw new InternalServerError(`bad response: ${getResp.status}`) - } else { - console.info(`get_fxa_subscriptions: success`) - return await getResp.json() - } + console.info(`get_fxa_subscriptions: success`) + return resp; + } catch (e) { if (e instanceof Error) { console.error('get_fxa_subscriptions', { stack: e.stack }) @@ -223,7 +257,7 @@ async function applyCoupon(bearerToken, couponCodeId) { }) if (!response.ok) { const errMsg = await response.text() - console.info(`apply_coupon: failed - ${errMsg}`) + console.error(`apply_coupon: failed - ${errMsg}`) throw new Error(`apply_coupon: failed - ${errMsg}`) } else { console.info(`apply_coupon: success - ${JSON.stringify(await response.json())}`) @@ -249,6 +283,7 @@ function getSha1(email) { } export { + refreshOAuthTokens, destroyOAuthToken, revokeOAuthTokens, getSha1, From 20a7d58097db8f0f6173ff4781cf2d300d377134 Mon Sep 17 00:00:00 2001 From: mansaj Date: Thu, 18 Jul 2024 20:20:58 -0700 Subject: [PATCH 117/137] Revert "Revert "chore(deps): bump node from 22.3-alpine to 22.4-alpine" (#4833)" This reverts commit 044902cb95c7eb9386f98c55d59545e95e9db4c8. --- .github/workflows/build.yaml | 2 +- .github/workflows/e2e_cron.yml | 2 +- .github/workflows/e2e_pr.yml | 2 +- .github/workflows/lint.yaml | 2 +- .github/workflows/unittests.yaml | 2 +- Dockerfile | 2 +- Dockerfile.cloudrun | 2 +- esbuild.cronjobs.js | 2 +- netlify.toml | 2 +- package-lock.json | 2 +- package.json | 6 +++--- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 053c5070923..e22647197ec 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,7 +12,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: '22.3.x' + node-version: '22.4.x' - run: npm ci - run: npm run build-glean # Verify that the build (incl. type-checking) succeeds diff --git a/.github/workflows/e2e_cron.yml b/.github/workflows/e2e_cron.yml index 1e4b93b7d8e..08f68c3dd61 100644 --- a/.github/workflows/e2e_cron.yml +++ b/.github/workflows/e2e_cron.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22.3 + node-version: 22.4 - name: Install dependencies run: npm ci diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 8dca4b381d7..d6f3986c6c6 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22.3 + node-version: 22.4 - name: Install dependencies run: npm ci diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index e5827c7cabb..c97a575e6fd 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -12,7 +12,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: '22.3.x' + node-version: '22.4.x' - run: npm ci - run: npm run build-glean - run: npm run build-nimbus diff --git a/.github/workflows/unittests.yaml b/.github/workflows/unittests.yaml index c934ad1fac3..8bcc252faeb 100644 --- a/.github/workflows/unittests.yaml +++ b/.github/workflows/unittests.yaml @@ -12,7 +12,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: '22.3.x' + node-version: '22.4.x' - run: npm ci - run: npm run build-glean - run: npm test diff --git a/Dockerfile b/Dockerfile index 5f49577777f..6df05dc6dea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.3-alpine +FROM node:22.4-alpine RUN addgroup -g 10001 app && \ adduser -D -G app -h /app -u 10001 app diff --git a/Dockerfile.cloudrun b/Dockerfile.cloudrun index dc0b8110a6d..179005eac42 100644 --- a/Dockerfile.cloudrun +++ b/Dockerfile.cloudrun @@ -1,4 +1,4 @@ -FROM node:22.3-alpine +FROM node:22.4-alpine RUN addgroup -g 10001 app && \ adduser -D -G app -h /app -u 10001 app diff --git a/esbuild.cronjobs.js b/esbuild.cronjobs.js index b8be0739677..dcbea0261b0 100644 --- a/esbuild.cronjobs.js +++ b/esbuild.cronjobs.js @@ -21,6 +21,6 @@ build({ format: "esm", outdir: "dist/scripts/cronjobs/", sourcemap: true, - target: "node22.3", + target: "node22.4", packages: "external", }); diff --git a/netlify.toml b/netlify.toml index 8f21f1b47a8..1957208e9d3 100644 --- a/netlify.toml +++ b/netlify.toml @@ -18,4 +18,4 @@ # Default build command. command = "npm ci; npm run build-storybook" - environment = { NODE_VERSION = "22.3.0", NPM_VERSION = "10.8.0" } + environment = { NODE_VERSION = "22.4.1", NPM_VERSION = "10.8.1" } diff --git a/package-lock.json b/package-lock.json index bcae66a0f75..93df26589f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -105,7 +105,7 @@ "yaml": "^2.4.5" }, "engines": { - "node": "22.3.x", + "node": "22.4.x", "npm": "10.8.x" } }, diff --git a/package.json b/package.json index ac5bfa839e6..d24420e07e9 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Firefox Monitor", "engines": { - "node": "22.3.x", + "node": "22.4.x", "npm": "10.8.x" }, "type": "module", @@ -56,8 +56,8 @@ "homepage": "https://github.com/mozilla/blurts-server", "license": "MPL-2.0", "volta": { - "node": "22.3.0", - "npm": "10.8.0" + "node": "22.4.1", + "npm": "10.8.1" }, "dependencies": { "@aws-sdk/client-s3": "^3.614.0", From c2a379d21f9e2c454f28e91c5e126430da4ce67f Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Thu, 18 Jul 2024 23:59:06 -0700 Subject: [PATCH 118/137] fixed landing spec --- src/e2e/pages/landingPage.ts | 12 +++- src/e2e/specs/landing.spec.ts | 119 ++++++++++++++++++++++------------ src/e2e/utils/helpers.ts | 4 ++ 3 files changed, 92 insertions(+), 43 deletions(-) diff --git a/src/e2e/pages/landingPage.ts b/src/e2e/pages/landingPage.ts index 463dbc3c059..97aa2189c79 100644 --- a/src/e2e/pages/landingPage.ts +++ b/src/e2e/pages/landingPage.ts @@ -79,6 +79,9 @@ export class LandingPage { readonly monitorPlusTooltipText: Locator; readonly closeTooltips: Locator; + // Landing-page-free-scan-cta experiment enabled + readonly emailInputPrompt: Locator; + constructor(page: Page) { this.page = page; this.freeMonitoringTooltipTrigger = page @@ -166,7 +169,7 @@ export class LandingPage { has: this.reuseEmailInputField, }); this.couldBeAtRiskFormInputSubmitButton = this.couldBeAtRiskSection.filter({ - has: this.reuseButton, + hasText: "Get free scan", }); this.couldBeAtRiskGraphic = page.locator( 'img[data-testid="leaked-password-example"]', @@ -184,7 +187,7 @@ export class LandingPage { has: this.reuseEmailInputField, }); this.getStartedScanFormSubmitButton = this.getStartedScanSection.filter({ - has: this.reuseButton, + hasText: "Get free scan", }); // choose your level of protection section @@ -235,6 +238,11 @@ export class LandingPage { this.startFreeMonitoringButton = page.getByRole("button", { name: "Start free monitoring", }); + + // Landing-page-free-scan-cta experiment enabled + this.emailInputPrompt = page.locator( + '//label[text()="Enter your email address to check for data breach exposures and sites selling your info."]', + ); } async open() { diff --git a/src/e2e/specs/landing.spec.ts b/src/e2e/specs/landing.spec.ts index acb202d4656..9a3b2ec59b8 100644 --- a/src/e2e/specs/landing.spec.ts +++ b/src/e2e/specs/landing.spec.ts @@ -5,6 +5,7 @@ import { test, expect } from "../fixtures/basePage.js"; import { defaultScreenshotOpts, + emailInputShouldExist, getVerificationCode, } from "../utils/helpers.js"; @@ -41,8 +42,10 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page content`, ( await expect(landingPage.monitorHeroSubtitle).toHaveText( "We scan to see if your phone number, passwords or home address have been leaked, and help you make it private again.", ); - await expect(landingPage.monitorHeroFormEmailInputField).toBeVisible(); - await expect(landingPage.monitorHeroFormInputSubmitButton).toBeVisible(); + if (await emailInputShouldExist(landingPage)) { + await expect(landingPage.monitorHeroFormEmailInputField).toBeVisible(); + await expect(landingPage.monitorHeroFormInputSubmitButton).toBeVisible(); + } await expect(landingPage.monitorLandingMidHeading).toBeVisible(); }); @@ -57,8 +60,10 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page content`, ( await expect(landingPage.fixExposuresTitle).toBeVisible(); await expect(landingPage.fixExposuresSubtitle).toBeVisible(); - await expect(landingPage.fixExposuresFormEmailInputField).toBeVisible(); - await expect(landingPage.fixExposuresFormInputSubmitButton).toBeVisible(); + if (await emailInputShouldExist(landingPage)) { + await expect(landingPage.fixExposuresFormEmailInputField).toBeVisible(); + await expect(landingPage.fixExposuresFormInputSubmitButton).toBeVisible(); + } await expect(landingPage.fixExposuresGraphic).toBeVisible(); }); @@ -73,7 +78,9 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page content`, ( await expect(landingPage.couldBeAtRiskTitle).toBeVisible(); await expect(landingPage.couldBeAtRiskSubtitle).toBeVisible(); - await expect(landingPage.couldBeAtRiskFormEmailInputField).toBeVisible(); + if (await emailInputShouldExist(landingPage)) { + await expect(landingPage.couldBeAtRiskFormEmailInputField).toBeVisible(); + } await expect(landingPage.couldBeAtRiskFormInputSubmitButton).toBeVisible(); await expect(landingPage.couldBeAtRiskGraphic).toBeVisible(); }); @@ -88,7 +95,8 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page content`, ( }); await expect(landingPage.getStartedScanTitle).toBeVisible(); - await expect(landingPage.getStartedScanFormEmailInputField).toBeVisible(); + if (await emailInputShouldExist(landingPage)) + await expect(landingPage.getStartedScanFormEmailInputField).toBeVisible(); await expect(landingPage.getStartedScanFormSubmitButton).toBeVisible(); }); @@ -130,8 +138,12 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page content`, ( }); await expect(landingPage.takeBackControlTitle).toBeVisible(); - await expect(landingPage.takeBackControlFormEmailInputField).toBeVisible(); - await expect(landingPage.takeBackControlFormSubmitButton).toBeVisible(); + if (await emailInputShouldExist(landingPage)) { + await expect( + landingPage.takeBackControlFormEmailInputField, + ).toBeVisible(); + await expect(landingPage.takeBackControlFormSubmitButton).toBeVisible(); + } }); test("Observe footer section", async ({ landingPage }) => { @@ -182,17 +194,30 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page content`, ( description: "https://testrail.stage.mozaws.net/index.php?/cases/view/2463504", }); - - await landingPage.monitorHeroFormEmailInputField.fill("invalid"); - await landingPage.monitorHeroFormInputSubmitButton.click(); - // Stays on same page - await expect(landingPage.monitorHeroFormEmailInputField).toBeVisible(); - - const randomEmail = `_${Date.now()}_tstact@restmail.net`; - await landingPage.monitorHeroFormEmailInputField.fill(randomEmail); - await landingPage.monitorHeroFormInputSubmitButton.click(); - await authPage.passwordInputField.waitFor(); - await expect(authPage.passwordInputField).toBeVisible(); + if (await emailInputShouldExist(landingPage)) { + ///free-scan-cta experiment is off + await landingPage.monitorHeroFormEmailInputField.fill("invalid"); + await landingPage.monitorHeroFormInputSubmitButton.click(); + await expect(landingPage.monitorHeroFormEmailInputField).toBeVisible(); + + const randomEmail = `_${Date.now()}_tstact@restmail.net`; + await landingPage.monitorHeroFormEmailInputField.fill(randomEmail); + await landingPage.monitorHeroFormInputSubmitButton.click(); + await authPage.passwordInputField.waitFor(); + await expect(authPage.passwordInputField).toBeVisible(); + } else { + ///free-scan-cta experiment is on + await landingPage.monitorHeroFormInputSubmitButton.click(); + await authPage.emailInputField.waitFor({ + state: "visible", + timeout: 10000, + }); + const randomEmail = `_${Date.now()}_tstact@restmail.net`; + await authPage.emailInputField.fill(randomEmail); + await authPage.continueButton.click(); + await authPage.passwordInputField.waitFor(); + await expect(authPage.passwordInputField).toBeVisible(); + } }); test('Verify manual/automatic removal "more info" tips from "Choose your level of protection" section', async ({ @@ -221,20 +246,27 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali page, authPage, }) => { - // link to testrail case test.info().annotations.push({ type: "testrail", description: "https://testrail.stage.mozaws.net/index.php?/cases/view/2463502", }); - // fill out free scan form const randomEmail = `${Date.now()}_tstact@restmail.net`; - await landingPage.monitorHeroFormEmailInputField.fill(randomEmail); - await landingPage.monitorHeroFormInputSubmitButton.click(); - await page.waitForURL("**/oauth/**"); - - // complete registration form + if (await emailInputShouldExist(landingPage)) { + await landingPage.monitorHeroFormEmailInputField.fill(randomEmail); + await landingPage.monitorHeroFormInputSubmitButton.click(); + await page.waitForURL("**/oauth/**"); + } else { + await landingPage.monitorHeroFormInputSubmitButton.click(); + await authPage.emailInputField.waitFor({ + state: "visible", + timeout: 10000, + }); + await authPage.emailInputField.fill(randomEmail); + await authPage.continueButton.click(); + } + // continue with the common steps await authPage.passwordInputField.fill( process.env.E2E_TEST_ACCOUNT_PASSWORD as string, ); @@ -243,12 +275,8 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali ); await authPage.ageInputField.fill("31"); await authPage.continueButton.click(); - - // enter registration verification code const vc = await getVerificationCode(randomEmail, page); await authPage.enterVerificationCode(vc); - - // verify dashboard redirect const successUrl = process.env.E2E_TEST_BASE_URL + "/user/welcome"; expect(page.url()).toBe(successUrl); }); @@ -298,12 +326,23 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali "https://testrail.stage.mozaws.net/index.php?/cases/view/2463503", }); - // fill out free scan form - await landingPage.monitorHeroFormEmailInputField.fill( - process.env.E2E_TEST_ACCOUNT_EMAIL as string, - ); - await landingPage.monitorHeroFormInputSubmitButton.click(); - await page.waitForURL("**/oauth/**"); + const existingEmail = process.env.E2E_TEST_ACCOUNT_EMAIL as string; + + if (await emailInputShouldExist(landingPage)) { + // Scenario where the form is still used + await landingPage.monitorHeroFormEmailInputField.fill(existingEmail); + await landingPage.monitorHeroFormInputSubmitButton.click(); + await page.waitForURL("**/oauth/**"); + } else { + // Scenario where direct redirection happens + await landingPage.monitorHeroFormInputSubmitButton.click(); + await authPage.emailInputField.waitFor({ + state: "visible", + timeout: 10000, + }); + await authPage.emailInputField.fill(existingEmail); + await authPage.continueButton.click(); + } // complete sign in form await authPage.enterPassword(); @@ -311,11 +350,9 @@ test.describe(`${process.env.E2E_TEST_ENV} - Verify the Landing Page Functionali // verify dashboard redirect const successUrl = process.env.E2E_TEST_BASE_URL + - `${ - process.env.E2E_TEST_ENV === "local" - ? "/user/welcome" - : "/user/dashboard" - }`; + (process.env.E2E_TEST_ENV === "local" + ? "/user/welcome" + : "/user/dashboard"); expect(page.url()).toBe(successUrl); }); diff --git a/src/e2e/utils/helpers.ts b/src/e2e/utils/helpers.ts index 0d398436dd0..ddb6da2800f 100644 --- a/src/e2e/utils/helpers.ts +++ b/src/e2e/utils/helpers.ts @@ -222,3 +222,7 @@ export const forceLoginAs = async ( await page.waitForURL("**/user/dashboard"); await expect(page).toHaveURL(/.*\/user\/dashboard.*/); }; + +export async function emailInputShouldExist(landingPage: LandingPage) { + return 0 < (await landingPage.emailInputPrompt.count()); +} From f43dbf285ce7f5205460973ca711842950d2ba66 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Fri, 19 Jul 2024 02:42:26 -0700 Subject: [PATCH 119/137] changed the paypal button locator --- src/e2e/pages/purchasePage.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/e2e/pages/purchasePage.ts b/src/e2e/pages/purchasePage.ts index e1ead13b02e..2892c736f75 100644 --- a/src/e2e/pages/purchasePage.ts +++ b/src/e2e/pages/purchasePage.ts @@ -39,7 +39,10 @@ export class PurchasePage { this.returnToDashboardButton = page.getByLabel("Return to dashboard"); this.goToNextStep = page.getByLabel("Go to next step"); this.planDetails = page.locator(".plan-details-description"); - this.paypalButton = page.getByTitle("PayPal").nth(1); + this.paypalButton = this.page + .frameLocator('//iframe[@title="PayPal"]') + .first() + .locator('div[role="link"]'); } async fillOutStripeCardInfo() { From b7214c8d958d14dfc568b54496287107a3ca3e93 Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Fri, 19 Jul 2024 03:41:21 -0700 Subject: [PATCH 120/137] slightly modified the locators for a failing test --- src/e2e/specs/dashboard.spec.ts | 1 + src/e2e/utils/helpers.ts | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/e2e/specs/dashboard.spec.ts b/src/e2e/specs/dashboard.spec.ts index a72dba31d56..92b77d78f44 100644 --- a/src/e2e/specs/dashboard.spec.ts +++ b/src/e2e/specs/dashboard.spec.ts @@ -515,6 +515,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Overview Card` }); await dashboardPage.open(); + await page.waitForURL("**/dashboard/**"); //get the number of exposures count const overviewCardSummary = diff --git a/src/e2e/utils/helpers.ts b/src/e2e/utils/helpers.ts index ddb6da2800f..406bec589c4 100644 --- a/src/e2e/utils/helpers.ts +++ b/src/e2e/utils/helpers.ts @@ -208,12 +208,10 @@ export const forceLoginAs = async ( await page.context().clearCookies(); await landingPage.open(); await landingPage.goToSignIn(); - let visible = true; - try { - await expect(authPage.useDifferentEmailButton).toBeVisible(); - } catch { - visible = false; - } + await page + .locator("//input[@type='password'] | //div/input[@type='email']") + .waitFor({ state: "visible" }); + const visible = await authPage.useDifferentEmailButton.isVisible(); if (visible) { await authPage.useDifferentEmailButton.click(); await page.waitForURL(/^(?!.*signin).*/); From a3e9e7785c221bf0eb29c61010e57dd2a55dbc1c Mon Sep 17 00:00:00 2001 From: Mukhamediyar Kudaikulov Date: Fri, 19 Jul 2024 11:58:01 -0700 Subject: [PATCH 121/137] skipping an inconsistent test --- src/e2e/pages/dashBoardPage.ts | 4 +- src/e2e/specs/dashboard.spec.ts | 79 ++++++++++++++++++--------------- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/e2e/pages/dashBoardPage.ts b/src/e2e/pages/dashBoardPage.ts index 25f5939df65..80cd54082bb 100644 --- a/src/e2e/pages/dashBoardPage.ts +++ b/src/e2e/pages/dashBoardPage.ts @@ -241,7 +241,9 @@ export class DashboardPage { this.faqsPageLink = page.getByTitle("Frequently asked questions").first(); //upsell button - this.upsellScreenButton = page.getByText(/Let’s (keep going|fix it)/); + this.upsellScreenButton = page + .locator("a") + .getByText(/Let’s (keep going|fix it)/); this.overviewCard = page.locator("[class*='DashboardTopBanner_container']"); this.overviewCardSummary = page.locator( "[aria-label='Dashboard summary'] > div > p", diff --git a/src/e2e/specs/dashboard.spec.ts b/src/e2e/specs/dashboard.spec.ts index 92b77d78f44..34908776270 100644 --- a/src/e2e/specs/dashboard.spec.ts +++ b/src/e2e/specs/dashboard.spec.ts @@ -720,40 +720,49 @@ test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Navigation`, ( }); }); -test.describe(`${process.env.E2E_TEST_ENV} - Breaches Dashboard - Data Breaches`, () => { - test.beforeEach(async ({ landingPage, page, authPage }) => { - const emailToUse = process.env - .E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED as string; - const pwdToUse = process.env.E2E_TEST_ACCOUNT_PASSWORD as string; - expect(emailToUse).not.toBeUndefined(); - expect(pwdToUse).not.toBeUndefined(); - await forceLoginAs(emailToUse, pwdToUse, page, landingPage, authPage); - }); - - test("Verify that the High risk data breaches step is displayed correctly", async ({ - dashboardPage, - dataBrokersPage, - page, - }) => { - test.info().annotations.push({ - type: "testrail", - description: - "https://testrail.stage.mozaws.net/index.php?/cases/view/2463592", +// This test has inconsistent results - may need to rely on mocks. +test.describe.skip( + `${process.env.E2E_TEST_ENV} - Breaches Dashboard - Data Breaches`, + () => { + test.beforeEach(async ({ landingPage, page, authPage }) => { + const emailToUse = process.env + .E2E_TEST_ACCOUNT_EMAIL_EXPOSURES_STARTED as string; + const pwdToUse = process.env.E2E_TEST_ACCOUNT_PASSWORD as string; + expect(emailToUse).not.toBeUndefined(); + expect(pwdToUse).not.toBeUndefined(); + await forceLoginAs(emailToUse, pwdToUse, page, landingPage, authPage); }); - await expect(dashboardPage.upsellScreenButton).toBeVisible(); - await dashboardPage.upsellScreenButton.click(); - await page.waitForURL(/.*\/data-broker-profiles\/view-data-brokers\/?/); - await expect(dataBrokersPage.forwardArrowButton).toBeVisible(); - await dataBrokersPage.forwardArrowButton.click(); - await page.waitForURL(/.*\/high-risk-data-breaches.*/); - const highRiskDataBreachLi = page.locator( - 'li:has(div:has-text("High risk data breaches"))', - ); - await expect(highRiskDataBreachLi).toBeVisible(); - await expect(highRiskDataBreachLi).toHaveAttribute("aria-current", "step"); - await expect( - highRiskDataBreachLi.locator("div").getByText("High risk data breaches"), - ).toBeVisible(); - }); -}); + test("Verify that the High risk data breaches step is displayed correctly", async ({ + dashboardPage, + dataBrokersPage, + page, + }) => { + test.info().annotations.push({ + type: "testrail", + description: + "https://testrail.stage.mozaws.net/index.php?/cases/view/2463592", + }); + + await expect(dashboardPage.upsellScreenButton).toBeVisible(); + await dashboardPage.upsellScreenButton.click(); + await page.waitForURL(/.*\/data-broker-profiles\/view-data-brokers\/?/); + await expect(dataBrokersPage.forwardArrowButton).toBeVisible(); + await dataBrokersPage.forwardArrowButton.click(); + await page.waitForURL(/.*\/high-risk-data-breaches.*/); + const highRiskDataBreachLi = page.locator( + 'li:has(div:has-text("High risk data breaches"))', + ); + await expect(highRiskDataBreachLi).toBeVisible(); + await expect(highRiskDataBreachLi).toHaveAttribute( + "aria-current", + "step", + ); + await expect( + highRiskDataBreachLi + .locator("div") + .getByText("High risk data breaches"), + ).toBeVisible(); + }); + }, +); From 4e55128f72c470df18865bda08970b833a64c3d2 Mon Sep 17 00:00:00 2001 From: mozilla-pontoon Date: Sun, 21 Jul 2024 12:01:23 +0000 Subject: [PATCH 122/137] Import translations from l10n repository (2024-07-21) --- locales/cs/landing-all.ftl | 6 +----- locales/cy/landing-all.ftl | 6 +----- locales/de/landing-all.ftl | 6 +----- locales/el/landing-all.ftl | 6 +----- locales/en-GB/landing-all.ftl | 6 +----- locales/es-AR/landing-all.ftl | 6 +----- locales/es-CL/landing-all.ftl | 6 +----- locales/fi/dashboard.ftl | 20 ++------------------ locales/fi/fix.ftl | 9 ++++++++- locales/fi/landing-all.ftl | 6 +----- locales/fi/settings.ftl | 1 + locales/fr/landing-all.ftl | 6 +----- locales/hu/landing-all.ftl | 6 +----- locales/ia/landing-all.ftl | 6 +----- locales/id/landing-all.ftl | 6 +----- locales/it/landing-all.ftl | 6 +----- locales/nl/landing-all.ftl | 6 +----- locales/nn-NO/breaches.ftl | 2 ++ locales/pt-PT/landing-all.ftl | 6 +----- locales/ru/landing-all.ftl | 6 +----- locales/sl/dashboard.ftl | 6 ++---- locales/sl/landing-all.ftl | 1 + locales/sv-SE/landing-all.ftl | 6 +----- locales/tr/landing-all.ftl | 6 +----- locales/vi/landing-all.ftl | 6 +----- locales/zh-CN/landing-all.ftl | 6 +----- locales/zh-TW/landing-all.ftl | 6 +----- 27 files changed, 37 insertions(+), 128 deletions(-) diff --git a/locales/cs/landing-all.ftl b/locales/cs/landing-all.ftl index 835a81552ba..67b8cfcdf52 100644 --- a/locales/cs/landing-all.ftl +++ b/locales/cs/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Zjistěte, kde jsou vaše soukromé informace odhalené — a vezměte si je zpět landing-all-hero-lead = Prověřujeme úniky údajů a zjišťujeme, zda nedošlo k úniku vašich údajů. Následně vám nabízíme kroky k nápravě. - landing-all-hero-emailform-input-placeholder = vasejmeno@example.com landing-all-hero-emailform-input-label = Zadejte svou e-mailovou adresu a zkontrolujte, zda nedošlo k úniku údajů. landing-all-hero-emailform-submit-label = Zkontrolovat - +landing-all-hero-emailform-submit-sign-in-label = Pro skenování zdarma se přihlaste # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = odhalení @@ -66,7 +64,5 @@ landing-all-help-protect-you-feature-one = Vyhledáme vás ve všech známých p landing-all-help-protect-you-feature-two = Provedeme vás kroky k vyřešení každého úniku. landing-all-help-protect-you-feature-three = Průběžně budeme monitorovat a zasílat vám upozornění na nové úniky. landing-all-help-protect-you-cta = Přihlásit se k odběru upozornění na úniky - landing-all-get-started = Zadejte svou e-mailovou adresu a začněte landing-all-take-back-data = Získejte opět kontrolu nad svými údaji - diff --git a/locales/cy/landing-all.ftl b/locales/cy/landing-all.ftl index 8a11bdfab1c..084c7456a49 100644 --- a/locales/cy/landing-all.ftl +++ b/locales/cy/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Canfod lle mae'ch manylion preifat yn cael eu rhyddhau — a'u cipio nôl landing-all-hero-lead = Rydym yn sganio tor-data i weld a yw'ch data wedi'u datgelu ac yn cynnig camau i chi i'w drwsio. - landing-all-hero-emailform-input-placeholder = eichenw@example.com landing-all-hero-emailform-input-label = Rhowch eich cyfeiriad e-bost i wirio am ddatguddiadau tor-data. landing-all-hero-emailform-submit-label = Cael sgan am ddim - +landing-all-hero-emailform-submit-sign-in-label = Mewngofnodwch i gael sgan am ddim # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = datgeliadau @@ -72,7 +70,5 @@ landing-all-help-protect-you-feature-one = Byddwn yn chwilio amdanoch ym mhob ac landing-all-help-protect-you-feature-two = Byddwn yn eich arwain trwy'r camau i ddatrys pob tor-data landing-all-help-protect-you-feature-three = Byddwn yn monitro ac yn anfon rhybuddion atoch yn barhaus am unrhyw dor-data newydd landing-all-help-protect-you-cta = Cofrestrwch am rybuddion tor-data - landing-all-get-started = Sganiwch eich e-bost i ddechrau landing-all-take-back-data = Ail feddiannwch eich rheolaeth o'ch data - diff --git a/locales/de/landing-all.ftl b/locales/de/landing-all.ftl index f98caf68133..6b87a33b972 100644 --- a/locales/de/landing-all.ftl +++ b/locales/de/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Finden Sie heraus, wo Ihre persönlichen Daten offengelegt wurden – und holen Sie sie zurück landing-all-hero-lead = Wir untersuchen Datenlecks, um zu sehen, ob Ihre Daten offengelegt wurden, und zeigen Ihnen die Schritte, um das Problem zu beheben. - landing-all-hero-emailform-input-placeholder = ihrname@example.com landing-all-hero-emailform-input-label = Geben Sie Ihre E-Mail-Adresse ein, um sie auf Datenlecks zu überprüfen. landing-all-hero-emailform-submit-label = Kostenloser Scan - +landing-all-hero-emailform-submit-sign-in-label = Melden Sie sich an, um einen kostenlosen Scan zu erhalten # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = Offenlegungen @@ -64,7 +62,5 @@ landing-all-help-protect-you-feature-one = Wir suchen in allen bekannten Datenle landing-all-help-protect-you-feature-two = Wir führen Sie durch die Schritte zur Behebung jedes einzelnen Datenlecks landing-all-help-protect-you-feature-three = Wir führen unser Monitoring kontinuierlich durch und warnen Sie bei allen neuen Datenlecks landing-all-help-protect-you-cta = Melden Sie sich an, um Warnungen bei Datenlecks zu erhalten - landing-all-get-started = Scannen Sie Ihre E-Mail-Adresse, um loszulegen landing-all-take-back-data = Holen Sie sich die Kontrolle über Ihre Daten zurück - diff --git a/locales/el/landing-all.ftl b/locales/el/landing-all.ftl index ae6b935e813..29df4c85723 100644 --- a/locales/el/landing-all.ftl +++ b/locales/el/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Μάθετε πού εκτίθενται οι προσωπικές σας πληροφορίες και ανακτήστε τις landing-all-hero-lead = Σαρώνουμε τις παραβιάσεις δεδομένων για να δούμε εάν έχουν διαρρεύσει τα δεδομένα σας και σας παρέχουμε μέτρα για τη διόρθωσή τους. - landing-all-hero-emailform-input-placeholder = toonomasas@example.com landing-all-hero-emailform-input-label = Εισαγάγετε τη διεύθυνση email σας για να ελέγξετε εάν έχει εκτεθεί σε παραβιάσεις δεδομένων. landing-all-hero-emailform-submit-label = Δωρεάν σάρωση - +landing-all-hero-emailform-submit-sign-in-label = Συνδεθείτε για να λάβετε τη δωρεάν σάρωση # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = εκθέσεις @@ -62,7 +60,5 @@ landing-all-help-protect-you-feature-one = Θα κάνουμε αναζήτησ landing-all-help-protect-you-feature-two = Θα σας καθοδηγήσουμε στα βήματα για την επίλυση κάθε παραβίασης landing-all-help-protect-you-feature-three = Θα παρακολουθούμε συνεχώς και θα σας ειδοποιούμε για τυχόν νέες παραβιάσεις landing-all-help-protect-you-cta = Εγγραφή για ειδοποιήσεις παραβιάσεων - landing-all-get-started = Σαρώστε το email σας για να ξεκινήσετε landing-all-take-back-data = Ανακτήστε τον έλεγχο των δεδομένων σας - diff --git a/locales/en-GB/landing-all.ftl b/locales/en-GB/landing-all.ftl index af3cc298445..500d9b91acd 100644 --- a/locales/en-GB/landing-all.ftl +++ b/locales/en-GB/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Find where your private info is exposed — and take it back landing-all-hero-lead = We scan data breaches to see if your data has been leaked and give you steps to fix it. - landing-all-hero-emailform-input-placeholder = yourname@example.com landing-all-hero-emailform-input-label = Enter your email address to check for data breach exposures. landing-all-hero-emailform-submit-label = Get free scan - +landing-all-hero-emailform-submit-sign-in-label = Sign in to get free scan # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = exposures @@ -62,7 +60,5 @@ landing-all-help-protect-you-feature-one = We’ll search for you in all known d landing-all-help-protect-you-feature-two = We’ll guide you through the steps to resolve each breach landing-all-help-protect-you-feature-three = We’ll continuously monitor and send you alerts for any new breaches landing-all-help-protect-you-cta = Sign up for breach alerts - landing-all-get-started = Scan your email to get started landing-all-take-back-data = Take back control of your data - diff --git a/locales/es-AR/landing-all.ftl b/locales/es-AR/landing-all.ftl index 0261141a9bf..bdc313612ab 100644 --- a/locales/es-AR/landing-all.ftl +++ b/locales/es-AR/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Fijate si tu información privada fue expuesta — y recuperala landing-all-hero-lead = Analizamos las filtraciones de datos para ver si tus datos se han filtrado y te brindamos pasos para solucionarlo. - landing-all-hero-emailform-input-placeholder = yourname@example.com landing-all-hero-emailform-input-label = Ingresá tu dirección de correo electrónico para verificar si hay exposiciones a filtraciones de datos. landing-all-hero-emailform-submit-label = Hacé un escaneo gratuito - +landing-all-hero-emailform-submit-sign-in-label = Iniciá sesión para escanear gratis # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = exposiciones @@ -64,7 +62,5 @@ landing-all-help-protect-you-feature-one = Te buscaremos en todas las filtracion landing-all-help-protect-you-feature-two = Te guiaremos a través de los pasos necesarios para resolver cada filtración landing-all-help-protect-you-feature-three = Monitorearemos continuamente y te enviaremos alertas sobre cualquier nueva filtración. landing-all-help-protect-you-cta = Registrate para recibir alertas de filtraciones - landing-all-get-started = Escaneá tu correo electrónico para empezar landing-all-take-back-data = Recuperá el control de tus datos - diff --git a/locales/es-CL/landing-all.ftl b/locales/es-CL/landing-all.ftl index 3765c5ecaf0..4d26261ee5f 100644 --- a/locales/es-CL/landing-all.ftl +++ b/locales/es-CL/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Encuentra donde tu información privada fue expuesta — y recupérala landing-all-hero-lead = Analizamos las filtraciones de datos para ver si tus datos se han filtrado y te brindamos pasos para solucionarlo. - landing-all-hero-emailform-input-placeholder = tunombre@example.com landing-all-hero-emailform-input-label = Ingresa tu dirección de correo electrónico para verificar si hay exposiciones a filtraciones de datos. landing-all-hero-emailform-submit-label = Obtén un escaneo gratuito - +landing-all-hero-emailform-submit-sign-in-label = Conéctate para obtener un escaneo gratuito # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = exposiciones @@ -64,7 +62,5 @@ landing-all-help-protect-you-feature-one = Te buscaremos en todas las filtracion landing-all-help-protect-you-feature-two = Te guiaremos a través de los pasos para resolver cada filtración landing-all-help-protect-you-feature-three = Monitorearemos continuamente y te enviaremos alertas sobre cualquier nueva filtración. landing-all-help-protect-you-cta = Regístrate para recibir alertas de filtraciones - landing-all-get-started = Escanea tu correo electrónico para comenzar landing-all-take-back-data = Recupera el control de tu información - diff --git a/locales/fi/dashboard.ftl b/locales/fi/dashboard.ftl index 48d0f383cc8..9e0a5d76192 100644 --- a/locales/fi/dashboard.ftl +++ b/locales/fi/dashboard.ftl @@ -25,23 +25,15 @@ exposure-chart-caption = Tämä kaavio näyttää, kuinka monta kertaa tietosi o exposure-chart-returning-user-upgrade-prompt = Kotiosoite, perheenjäsenet ja muut eivät vielä sisälly. exposure-chart-returning-user-upgrade-prompt-cta = Aloita ilmainen tarkistus exposure-chart-scan-in-progress-prompt = Tarkistus käynnissä: osoite, perheenjäsenet ja muut eivät vielä sisälly. - modal-active-number-of-exposures-title = Tietoja aktiivisten altistumisten määrästä - modal-cta-ok = OK -modal-open-alt = Avaa -modal-close-alt = Sulje - +modal-cta-got-it = Selvä progress-card-heres-what-we-fixed-headline-all = Tämän korjasit progress-card-manually-fixed-headline = Käsin korjattu - dashboard-tab-label-action-needed = Toimenpiteitä tarvitaan dashboard-tab-label-fixed = Korjattu dashboard-exposures-all-fixed-label = Kaikki korjattu täällä! - - dashboard-exposures-area-headline = Näytä kaikki sivustot, joissa tietosi ovat altistuneet - # Note: this line follows dashboard-exposures-area-description-all-line1. # Variables: # $data_breach_unresolved_num (number) - the unresolved number of data breaches the user has. @@ -51,7 +43,6 @@ dashboard-exposures-area-description-all-line2 = *[other] Se esiintyi { $data_breach_unresolved_num } tietovuodossa. } dashboard-fixed-area-headline-all = Näytä kaikki altistumiset, jotka on korjattu - # This is the label on a button that opens a popover menu, which shows a menu to adjust filters for the listed exposures. dashboard-exposures-filter = Suodatin dashboard-exposures-filter-company = Yritys @@ -68,21 +59,14 @@ dashboard-exposures-filter-show-results = Näytä tulokset dashboard-top-banner-section-label = Hallintapaneelin yhteenveto dashboard-top-banner-scan-in-progress-title = Tarkistus on edelleen kesken - dashboard-top-banner-your-data-is-protected-title = Tietosi on suojattu - dashboard-top-banner-lets-keep-protecting-title = Jatketaan tietojesi suojaamista dashboard-top-banner-lets-keep-protecting-cta = Jatketaan - dashboard-top-banner-protect-your-data-title = Suojataan tietosi dashboard-top-banner-protect-your-data-cta = Korjataan se - dashboard-top-banner-no-exposures-found-title = Vuotoja ei löytynyt dashboard-no-exposures-label = Vuotoja ei löytynyt - dashboard-top-banner-monitor-more-cta = Tarkkaile useampia sähköpostiosoitteita -# About Exposure Statuses Modal +# About Exposure Indicators Modal -modal-exposure-status-title = Tietoja altistumistiloista -modal-exposure-status-fixed = Korjattu tarkoittaa, että altistuminen on ratkaistu, eikä sinun tarvitse tehdä mitään. diff --git a/locales/fi/fix.ftl b/locales/fi/fix.ftl index 213de094158..87ae3db5aa3 100644 --- a/locales/fi/fix.ftl +++ b/locales/fi/fix.ftl @@ -5,7 +5,6 @@ fix-flow-nav-high-risk-data-breaches = Suuren riskin tietomurrot fix-flow-nav-leaked-passwords = Vuotaneet salasanat fix-flow-nav-security-recommendations = Turvallisuussuositukset - guided-resolution-flow-exit = Palaa hallintapaneeliin guided-resolution-flow-next-arrow = Siirry seuraavaan vaiheeseen guided-resolution-flow-step-navigation-label = Ohjatut vaiheet @@ -117,6 +116,12 @@ security-recommendation-steps-cta-label = Selvä! # Phone security recommendation security-recommendation-phone-title = Suojaa puhelinnumerosi +# $num_breaches is the number of breaches where the phone number was found. +security-recommendation-phone-summary = + { $num_breaches -> + [one] Puhelinnumerosi paljastui { $num_breaches } tietovuodossa: + *[other] Puhelinnumerosi paljastui { $num_breaches } tietovuodossa: + } security-recommendation-phone-step-two = Älä napsauta tuntemattomien lähettäjien tekstiviesteissä olevia linkkejä. Jos viesti vaikuttaa olevan luotettavasta lähteestä, soita lähettäjälle vahvistaaksesi # Email security recommendation @@ -143,6 +148,8 @@ security-recommendation-ip-summary = [one] IP-osoitteesi paljastui { $num_breaches } tietovuodon yhteydessä: *[other] IP-osoitteesi paljastui { $num_breaches } tietovuodon yhteydessä: } +security-recommendation-ip-description = IP-osoitteesi osoittaa sijaintisi ja Internet-palveluntarjoajasi. Hakkerit voivat käyttää näitä tietoja paikantaakseen sinut tai yrittääkseen muodostaa yhteyden laitteihisi. +security-recommendation-ip-step-one = Käytä VPN:ää (kuten { -brand-mozilla-vpn }) piilottaaksesi todellisen IP-osoitteesi ja käyttääksesi Internetiä yksityisesti. # Leaked Passwords diff --git a/locales/fi/landing-all.ftl b/locales/fi/landing-all.ftl index 936bc296df3..8196541b4ee 100644 --- a/locales/fi/landing-all.ftl +++ b/locales/fi/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Selvitä missä yksityiset tietosi ovat paljastuneet – ja ota ne takaisin landing-all-hero-lead = Tarkistamme tietovuodot havaitaksemme, ovatko tietosi vuotaneet, ja annamme ohjeita vuotojen korjaamiseksi. - landing-all-hero-emailform-input-placeholder = nimi@example.com landing-all-hero-emailform-input-label = Syötä sähköpostiosoitteesi tarkistaaksesi mahdolliset tietovuodot. landing-all-hero-emailform-submit-label = Hanki ilmainen tarkistus - +landing-all-hero-emailform-submit-sign-in-label = Kirjaudu sisään saadaksesi ilmaisen tarkistuksen # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = vuotoa @@ -64,7 +62,5 @@ landing-all-help-protect-you-feature-one = Etsimme sinua kaikista tunnetuista ti landing-all-help-protect-you-feature-two = Opastamme sinua kunkin tietovuodon ratkaisemiseksi landing-all-help-protect-you-feature-three = Jatkamme tarkkailua ja lähetämme sinulle ilmoituksia uusista tietovuodoista landing-all-help-protect-you-cta = Tilaa vuotohälytykset - landing-all-get-started = Tarkista sähköpostiosoitteesi aloittaaksesi landing-all-take-back-data = Ota henkilökohtaiset tietosi takaisin hallintaasi - diff --git a/locales/fi/settings.ftl b/locales/fi/settings.ftl index f67061c5c89..2c24cfd6486 100644 --- a/locales/fi/settings.ftl +++ b/locales/fi/settings.ftl @@ -53,6 +53,7 @@ settings-delete-monitor-free-account-title = Poista { -brand-monitor } -tili settings-delete-monitor-free-account-description = Tämä poistaa pysyvästi { -brand-monitor } -tilisi ja poistaa kaikki ilmoitukset käytöstä. settings-delete-monitor-free-account-cta-label = Poista tili settings-delete-monitor-free-account-dialog-title = { -brand-monitor } -tilisi poistetaan pysyvästi +settings-delete-monitor-free-account-dialog-lead-v2 = Kaikki { -brand-monitor } -tilisi tiedot poistetaan, emmekä enää seuraa uusia tietovuotoja. Tämä ei poista { -brand-mozilla-account }äsi. settings-delete-monitor-free-account-dialog-cta-label = Poista tili settings-delete-monitor-free-account-dialog-cancel-button-label = Unohdetaan tämä, palataan takaisin settings-delete-monitor-account-confirmation-toast-label-2 = { -brand-monitor } -tilisi on nyt poistettu. diff --git a/locales/fr/landing-all.ftl b/locales/fr/landing-all.ftl index 8b27bc8b37d..067c7b8059c 100644 --- a/locales/fr/landing-all.ftl +++ b/locales/fr/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Découvrez où vos informations personnelles ont fuité et reprenez le contrôle landing-all-hero-lead = Nous analysons les fuites de données pour vérifier si vos données ont été divulguées et nous vous indiquons les étapes pour y remédier. - landing-all-hero-emailform-input-placeholder = votrenom@example.com landing-all-hero-emailform-input-label = Saisissez votre adresse e-mail pour vérifier l’existence de fuites de données. landing-all-hero-emailform-submit-label = Effectuer un scan gratuit - +landing-all-hero-emailform-submit-sign-in-label = Connectez-vous pour obtenir un scan gratuit # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = détections @@ -60,7 +58,5 @@ landing-all-help-protect-you-feature-one = Nous vous rechercherons dans toutes l landing-all-help-protect-you-feature-two = Nous vous guiderons à travers les étapes pour résoudre chaque fuite landing-all-help-protect-you-feature-three = Nous effectuerons une surveillance permanente et vous enverrons des alertes lors de toute nouvelle fuite de données landing-all-help-protect-you-cta = S’inscrire aux alertes de fuites de données - landing-all-get-started = Scannez votre adresse e-mail pour commencer landing-all-take-back-data = Reprenez le contrôle de vos données - diff --git a/locales/hu/landing-all.ftl b/locales/hu/landing-all.ftl index 4853d3b97b7..9efebe324ce 100644 --- a/locales/hu/landing-all.ftl +++ b/locales/hu/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Tudja meg, hogy hol kerültek ki a személyes adatai – és szerezze vissza azokat landing-all-hero-lead = Átvizsgáljuk az adatvédelmi incidenseket, hogy megtudjuk, kiszivárogtak-e az adatai, és lépéseket adunk a javításához. - landing-all-hero-emailform-input-placeholder = email@example.com landing-all-hero-emailform-input-label = Adja meg az e-mail-címét, hogy ellenőrizze az adatvédelmi incidenseket. landing-all-hero-emailform-submit-label = Ingyenes vizsgálat kérése - +landing-all-hero-emailform-submit-sign-in-label = Jelentkezzen be az ingyenes vizsgálathoz # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = kitettség @@ -64,7 +62,5 @@ landing-all-help-protect-you-feature-one = Keresni fogjuk az összes ismert adat landing-all-help-protect-you-feature-two = Végigvezetjük az egyes adatvédelmi incidensek megoldásához szükséges lépéseken landing-all-help-protect-you-feature-three = Folyamatosan monitorozzuk, és figyelmeztetjük az új adatvédelmi incidensekről landing-all-help-protect-you-cta = Iratkozzon fel az adatvédelmi incidensek figyelmeztetéseire - landing-all-get-started = A kezdéshez vizsgálja meg az e-mail-címét landing-all-take-back-data = Szerezze vissza az adatai feletti irányítást - diff --git a/locales/ia/landing-all.ftl b/locales/ia/landing-all.ftl index 13c47194e0f..bf9d417dc05 100644 --- a/locales/ia/landing-all.ftl +++ b/locales/ia/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Discoperi ubi tu info private es exponite, e recupera los landing-all-hero-lead = Nos scande le violationes de datos pro vider si tu datos ha essite revelate e dar te le passos pro corriger los. - landing-all-hero-emailform-input-placeholder = yourname@example.com landing-all-hero-emailform-input-label = Insere tu adresse email pro controlar pro exposition a violation de datos. landing-all-hero-emailform-submit-label = Recipe scansion gratuite - +landing-all-hero-emailform-submit-sign-in-label = Accede pro un scansion gratuite # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = expositiones @@ -64,7 +62,5 @@ landing-all-help-protect-you-feature-one = Nos cercara pro te in tote le note vi landing-all-help-protect-you-feature-two = Nos te guidara passo a passo a resolver cata violation landing-all-help-protect-you-feature-three = Nos continuemente surveliara e te inviara avisos pro cata nove violation landing-all-help-protect-you-cta = Inscribe te pro alertas de violationes - landing-all-get-started = Scande tu email pro comenciar landing-all-take-back-data = Reprende le controlo de tu datos - diff --git a/locales/id/landing-all.ftl b/locales/id/landing-all.ftl index 910fe49c15f..acad2a4c22d 100644 --- a/locales/id/landing-all.ftl +++ b/locales/id/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Temukan di mana info pribadi Anda terbuka — dan ambil kembali landing-all-hero-lead = Kami memindai kebocoran data untuk melihat apakah data Anda telah bocor dan memberi Anda langkah-langkah untuk memperbaikinya. - landing-all-hero-emailform-input-placeholder = namaanda@example.com landing-all-hero-emailform-input-label = Masukkan alamat surel Anda untuk memeriksa eksposur pelanggaran data. landing-all-hero-emailform-submit-label = Dapatkan pemindaian gratis - +landing-all-hero-emailform-submit-sign-in-label = Masuk untuk mendapatkan pindaian gratis # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = eksposur @@ -62,7 +60,5 @@ landing-all-help-protect-you-feature-one = Kami akan mencari Anda di semua pembo landing-all-help-protect-you-feature-two = Kami akan memandu Anda melalui langkah-langkah untuk menyelesaikan setiap pelanggaran landing-all-help-protect-you-feature-three = Kami akan terus memantau dan mengirimi Anda peringatan untuk setiap pelanggaran baru landing-all-help-protect-you-cta = Daftar untuk peringatan pembobolan - landing-all-get-started = Pindai surel Anda untuk memulai landing-all-take-back-data = Ambil kembali kendali atas data Anda - diff --git a/locales/it/landing-all.ftl b/locales/it/landing-all.ftl index fc326d836ff..78ef06c8f9e 100644 --- a/locales/it/landing-all.ftl +++ b/locales/it/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Scopri dove sono esposte le tue informazioni personali e riprendine possesso landing-all-hero-lead = Analizziamo le violazioni di dati per verificare se i tuoi dati sono stati esposti e ti forniamo suggerimenti per risolvere il problema. - landing-all-hero-emailform-input-placeholder = iltuonome@example.com landing-all-hero-emailform-input-label = Inserisci il tuo indirizzo email per verificare la presenza di violazioni di dati. landing-all-hero-emailform-submit-label = Ottieni una scansione gratuita - +landing-all-hero-emailform-submit-sign-in-label = Accedi per una scansione gratuita # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = esposizioni @@ -60,7 +58,5 @@ landing-all-help-protect-you-feature-one = Ti cercheremo in tutte le violazioni landing-all-help-protect-you-feature-two = Ti guideremo attraverso i passaggi per risolvere ogni violazione landing-all-help-protect-you-feature-three = Continueremo a monitorare e ti invieremo avvisi per qualsiasi nuova violazione landing-all-help-protect-you-cta = Iscriviti per ricevere avvisi sulle violazioni - landing-all-get-started = Scansiona la tua email per iniziare landing-all-take-back-data = Riprendi il controllo dei tuoi dati - diff --git a/locales/nl/landing-all.ftl b/locales/nl/landing-all.ftl index fb61fdced70..78a3702056f 100644 --- a/locales/nl/landing-all.ftl +++ b/locales/nl/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Ontdek waar uw privégegevens zijn gelekt – en neem ze terug landing-all-hero-lead = We scannen datalekken om te zien of uw gegevens zijn gelekt en bieden u stappen om dit op te lossen. - landing-all-hero-emailform-input-placeholder = uwnaam@example.com landing-all-hero-emailform-input-label = Voer uw e-mailadres in om te controleren op datalekken. landing-all-hero-emailform-submit-label = Ontvang een gratis scan - +landing-all-hero-emailform-submit-sign-in-label = Meld u aan om de gratis scan te ontvangen # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = lekken @@ -64,7 +62,5 @@ landing-all-help-protect-you-feature-one = Wij gaan voor u op zoek in alle beken landing-all-help-protect-you-feature-two = We leiden u door de stappen om elk lek op te lossen landing-all-help-protect-you-feature-three = We monitoren voortdurend en sturen u waarschuwingen bij nieuwe lekken landing-all-help-protect-you-cta = Inschrijven voor waarschuwingen over datalekken - landing-all-get-started = Scan uw e-mailadres om te beginnen landing-all-take-back-data = Neem uw gegevens weer onder controle - diff --git a/locales/nn-NO/breaches.ftl b/locales/nn-NO/breaches.ftl index 5efa6c6b97b..d5ee3533c88 100644 --- a/locales/nn-NO/breaches.ftl +++ b/locales/nn-NO/breaches.ftl @@ -48,6 +48,8 @@ breach-checklist-address-body = Det er lett å finne adresser i offentlege regis ## Prompts the user for changes when there is a breach detected of date of birth +breach-checklist-dob-header = Endre alle passord eller PIN-kodar som inneheld fødselsdagen din. +breach-checklist-dob-body = Fødselsdatoar er lette å finne i offentlege register, og folk som finn dei kan enkelt gjette PIN-koden din. ## Prompts the user for changes when there is a breach detected of phone number diff --git a/locales/pt-PT/landing-all.ftl b/locales/pt-PT/landing-all.ftl index 6c314434e37..0b5e67b74e2 100644 --- a/locales/pt-PT/landing-all.ftl +++ b/locales/pt-PT/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Descubra onde a sua informação privada está exposta – e recupere o controlo landing-all-hero-lead = Verificamos violações de dados para confirmar se os seus dados foram capturados e indicamos os passos que pode tomar para corrigir as mesmas. - landing-all-hero-emailform-input-placeholder = oseunome@example.com landing-all-hero-emailform-input-label = Insira o seu endereço de e-mail para verificar se existe exposição a violações de dados. landing-all-hero-emailform-submit-label = Obter verificação gratuita - +landing-all-hero-emailform-submit-sign-in-label = Iniciar sessão para obter a verificação gratuita # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = exposições @@ -64,7 +62,5 @@ landing-all-help-protect-you-feature-one = Iremos procurar por si em todas as vi landing-all-help-protect-you-feature-two = Iremos apoiar através de passos guiados para resolver cada violação de dados landing-all-help-protect-you-feature-three = Iremos monitorizar de forma continua e enviar-lhe alertas para novas violações de dados landing-all-help-protect-you-cta = Registe-se para alertas de falhas de segurança - landing-all-get-started = Analise o seu e-mail para começar landing-all-take-back-data = Retome o controlo dos seus dados - diff --git a/locales/ru/landing-all.ftl b/locales/ru/landing-all.ftl index e687d314e6c..bdbbed23e62 100644 --- a/locales/ru/landing-all.ftl +++ b/locales/ru/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Найдите, где находится ваша личная информация, и верните обратно landing-all-hero-lead = Мы сканируем утечки данных, чтобы определить, не произошла ли утечка ваших данных, и даем вам инструкции по исправлению. - landing-all-hero-emailform-input-placeholder = вашлогин@example.com landing-all-hero-emailform-input-label = Введите свой адрес электронной почты, чтобы проверить наличие утечки данных. landing-all-hero-emailform-submit-label = Получить бесплатное сканирование - +landing-all-hero-emailform-submit-sign-in-label = Войдите, чтобы получить бесплатное сканирование # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = утечки @@ -66,7 +64,5 @@ landing-all-help-protect-you-feature-one = Мы будем искать вас landing-all-help-protect-you-feature-two = Мы проведем вас через шаги по устранению каждой утечки landing-all-help-protect-you-feature-three = Мы будем постоянно отслеживать и отправлять вам оповещения о любых новых утечках landing-all-help-protect-you-cta = Подпишитесь на уведомления об утечках - landing-all-get-started = Просканируйте свой адрес электронной почты, чтобы начать landing-all-take-back-data = Верните контроль над своими данными - diff --git a/locales/sl/dashboard.ftl b/locales/sl/dashboard.ftl index b79bdcd1752..b1b35c3e6c1 100644 --- a/locales/sl/dashboard.ftl +++ b/locales/sl/dashboard.ftl @@ -47,6 +47,7 @@ modal-active-number-of-exposures-part-three-all = Ko bodo razrešene, bodo dodan modal-fixed-number-of-exposures-title = O številu določenih izpostavljenosti modal-fixed-number-of-exposures-all = Ta grafikon vključuje skupno število odpravljenih kraj podatkov za vse e-poštne naslove, ki jih trenutno spremljate. Ko so izpostavljenosti označene kot fiksne, bodo tukaj dodane skupni vsoti. modal-cta-ok = V redu +modal-cta-got-it = Razumem open-modal-alt = Odpri način close-modal-alt = Zapri modalno okno open-tooltip-alt = Odpri opis orodja @@ -143,9 +144,6 @@ dashboard-top-banner-non-us-your-data-is-protected-description = } dashboard-top-banner-monitor-more-cta = Spremljaj več naslovov -# About Exposure Statuses Modal +# About Exposure Indicators Modal -modal-exposure-status-title = O stanjih izpostavljenosti modal-exposure-status-description-all = Izpostavljenosti iščemo v vseh znanih krajah podatkov. Vaša izpostavljenost bo imela eno od naslednjih stanj: -modal-exposure-status-action-needed = Potrebno je dejanje pomeni, da je trenutno aktiven in da ga morate popraviti. -modal-exposure-status-fixed = Odpravljena pomeni, da je bila izpostavljenost rešena in ne morete storiti ničesar. diff --git a/locales/sl/landing-all.ftl b/locales/sl/landing-all.ftl index 288a6484e92..a000ff1040b 100644 --- a/locales/sl/landing-all.ftl +++ b/locales/sl/landing-all.ftl @@ -8,6 +8,7 @@ landing-all-hero-lead = Pregledamo kraje podatkov, da ugotovimo, ali so vaši po landing-all-hero-emailform-input-placeholder = ime@example.com landing-all-hero-emailform-input-label = Vnesite svoj e-poštni naslov za preverjanje izpostavljenosti kraji podatkov. landing-all-hero-emailform-submit-label = Zagotovite si brezplačen pregled +landing-all-hero-emailform-submit-sign-in-label = Prijavite se za brezplačno skeniranje # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = izpostavljenosti diff --git a/locales/sv-SE/landing-all.ftl b/locales/sv-SE/landing-all.ftl index 16baf436a8e..7ee23cf1beb 100644 --- a/locales/sv-SE/landing-all.ftl +++ b/locales/sv-SE/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Hitta var din privata information är exponerad — och ta tillbaka den landing-all-hero-lead = Vi skannar dataintrång för att se om din data har läckt och ger dig tips för att åtgärda det. - landing-all-hero-emailform-input-placeholder = dittnamn@exempel.se landing-all-hero-emailform-input-label = Ange din e-postadress för att söka efter exponeringar för dataintrång. landing-all-hero-emailform-submit-label = Få gratis skanning - +landing-all-hero-emailform-submit-sign-in-label = Logga in för att få gratis skanning # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = exponeringar @@ -62,7 +60,5 @@ landing-all-help-protect-you-feature-one = Vi kommer att söka åt dig i alla k landing-all-help-protect-you-feature-two = Vi guidar dig genom stegen för att lösa varje intrång landing-all-help-protect-you-feature-three = Vi kommer kontinuerligt att övervaka och skicka dig varningar om nya intrång landing-all-help-protect-you-cta = Registrera dig för intrångsvarningar - landing-all-get-started = Skanna din e-post för att komma igång landing-all-take-back-data = Ta tillbaka kontrollen över din data - diff --git a/locales/tr/landing-all.ftl b/locales/tr/landing-all.ftl index bff69a22785..c9b7ae8586c 100644 --- a/locales/tr/landing-all.ftl +++ b/locales/tr/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Kişisel bilgilerinizin nerede ele geçirildiğini öğrenin landing-all-hero-lead = Verilerinizin sızdırılıp sızdırılmadığını görmek için veri ihlallerini tarıyor ve bunları düzeltmeniz için gereken adımları söylüyoruz. - landing-all-hero-emailform-input-placeholder = kullanici@example.com landing-all-hero-emailform-input-label = Veri ihlali risklerini kontrol etmek için e-posta adresinizi yazın. landing-all-hero-emailform-submit-label = Ücretsiz taramayı başlat - +landing-all-hero-emailform-submit-sign-in-label = Ücretsiz tarama için giriş yapın # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = veri ihlali @@ -64,7 +62,5 @@ landing-all-help-protect-you-feature-one = Bilinen veri ihlallerinde yer alıp a landing-all-help-protect-you-feature-two = İhlalleri çözmeniz için gereken adımlarda size rehberlik edeceğiz landing-all-help-protect-you-feature-three = Yeni ihlalleri sürekli olarak izleyip size uyarı göndereceğiz landing-all-help-protect-you-cta = İhlal uyarılarına kaydolun - landing-all-get-started = Kullanmaya başlamak için e-postanızı tarayın landing-all-take-back-data = Verilerinizin kontrolünü geri alın - diff --git a/locales/vi/landing-all.ftl b/locales/vi/landing-all.ftl index 71f822d9533..4abf8996922 100644 --- a/locales/vi/landing-all.ftl +++ b/locales/vi/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Tìm hiểu xem thông tin cá nhân của bạn bị lộ ở đâu — và lấy lại thông tin đó landing-all-hero-lead = Chúng tôi quét các rò rỉ dữ liệu để xem dữ liệu của bạn có bị rò rỉ hay không và cung cấp cho bạn các bước để giải quyết. - landing-all-hero-emailform-input-placeholder = yourname@example.com landing-all-hero-emailform-input-label = Nhập địa chỉ email của bạn để kiểm tra mức độ rò rỉ dữ liệu. landing-all-hero-emailform-submit-label = Quét miễn phí - +landing-all-hero-emailform-submit-sign-in-label = Đăng nhập để được quét miễn phí # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = dữ liệu bị lộ @@ -62,7 +60,5 @@ landing-all-help-protect-you-feature-one = Chúng tôi sẽ tìm kiếm bạn tr landing-all-help-protect-you-feature-two = Chúng tôi sẽ hướng dẫn bạn các bước để giải quyết từng rò rỉ landing-all-help-protect-you-feature-three = Chúng tôi sẽ liên tục theo dõi và gửi cho bạn thông báo về mọi rò rỉ mới landing-all-help-protect-you-cta = Đăng ký cảnh báo vụ rò rỉ - landing-all-get-started = Quét email của bạn để bắt đầu landing-all-take-back-data = Lấy lại quyền kiểm soát dữ liệu của bạn - diff --git a/locales/zh-CN/landing-all.ftl b/locales/zh-CN/landing-all.ftl index 0d3723b191b..14f2a8fc2c3 100644 --- a/locales/zh-CN/landing-all.ftl +++ b/locales/zh-CN/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = 扫描隐私信息泄露,收回属于您的秘密 landing-all-hero-lead = 我们会扫描您的数据是否曾遭外泄,并指导您采取措施解决问题。 - landing-all-hero-emailform-input-placeholder = yourname@example.com landing-all-hero-emailform-input-label = 输入邮箱地址即可检查数据外泄事件。 landing-all-hero-emailform-submit-label = 免费扫描 - +landing-all-hero-emailform-submit-sign-in-label = 登录即可免费扫描 # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = 次暴露 @@ -56,7 +54,5 @@ landing-all-help-protect-you-feature-one = 我们将在所有已知的数据外 landing-all-help-protect-you-feature-two = 我们指导您采取措施,逐项解决外泄事件问题 landing-all-help-protect-you-feature-three = 我们将持续监测动态,并在发现新的外泄事件时向您发出警报 landing-all-help-protect-you-cta = 订阅数据外泄警报 - landing-all-get-started = 扫描邮箱地址,开始探查 landing-all-take-back-data = 夺回个人数据的控制权 - diff --git a/locales/zh-TW/landing-all.ftl b/locales/zh-TW/landing-all.ftl index 4f840e9a42e..f5a39b32d3a 100644 --- a/locales/zh-TW/landing-all.ftl +++ b/locales/zh-TW/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = 看看您的隱私資訊在哪裡外洩,並且搶回控制權 landing-all-hero-lead = 我們可掃描已知的資安事件,看看您的資料是否已遭外洩並建議您處理方式。 - landing-all-hero-emailform-input-placeholder = yourname@example.com landing-all-hero-emailform-input-label = 輸入您的電子郵件地址,即可檢查是否曾遭資料外洩。 landing-all-hero-emailform-submit-label = 免費掃描 - +landing-all-hero-emailform-submit-sign-in-label = 登入即可免費掃描 # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = 資料曝光事件 @@ -62,7 +60,5 @@ landing-all-help-protect-you-feature-one = 我們會在已知的資料外洩事 landing-all-help-protect-you-feature-two = 我們會帶您逐步操作,針對每場資料外洩事件進行處理 landing-all-help-protect-you-feature-three = 我們將持續監控您的資料,並在發生新的外洩事件時警示您 landing-all-help-protect-you-cta = 訂閱資料外洩警報 - landing-all-get-started = 掃描您的電子郵件地址,即可使用 landing-all-take-back-data = 搶回您的資料的控制權 - From 1fc96933a5e64b14e3aa0b3b10d3ac15f4649136 Mon Sep 17 00:00:00 2001 From: mozilla-pontoon Date: Mon, 22 Jul 2024 12:01:36 +0000 Subject: [PATCH 123/137] Import translations from l10n repository (2024-07-22) --- locales/cs/dashboard.ftl | 6 +++--- locales/nl/landing-all.ftl | 2 +- locales/pt-BR/landing-all.ftl | 6 +----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/locales/cs/dashboard.ftl b/locales/cs/dashboard.ftl index 6974b2d4a31..26a1e69c7a9 100644 --- a/locales/cs/dashboard.ftl +++ b/locales/cs/dashboard.ftl @@ -139,6 +139,6 @@ dashboard-top-banner-monitor-more-cta = Monitorovat více e-mailů # About Exposure Indicators Modal modal-exposure-status-description-all = Hledáme úniky údajů ve všech známých únicích. Vaše odhalení bude mít jeden z následujících stavů: -modal-exposure-indicator-title = Stavy kontaktů -modal-exposure-indicator-action-needed = K dokončení akce je vyžadována pokročilá nebo ruční akce. -modal-exposure-indicator-fixed = Problém byl vyřešen a vy už nemusíte podnikat žádné kroky. +modal-exposure-indicator-title = Stavy odhalení +modal-exposure-indicator-action-needed = K dokončení akce je nutná pokročilá nebo ruční akce. +modal-exposure-indicator-fixed = Odhalení bylo vyřešeno a vy už nemusíte podnikat žádné kroky. diff --git a/locales/nl/landing-all.ftl b/locales/nl/landing-all.ftl index 78a3702056f..3c87031c54e 100644 --- a/locales/nl/landing-all.ftl +++ b/locales/nl/landing-all.ftl @@ -8,7 +8,7 @@ landing-all-hero-lead = We scannen datalekken om te zien of uw gegevens zijn gel landing-all-hero-emailform-input-placeholder = uwnaam@example.com landing-all-hero-emailform-input-label = Voer uw e-mailadres in om te controleren op datalekken. landing-all-hero-emailform-submit-label = Ontvang een gratis scan -landing-all-hero-emailform-submit-sign-in-label = Meld u aan om de gratis scan te ontvangen +landing-all-hero-emailform-submit-sign-in-label = Meld u aan om een gratis scan te ontvangen # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = lekken diff --git a/locales/pt-BR/landing-all.ftl b/locales/pt-BR/landing-all.ftl index 6045411dd49..88c8ae8cd23 100644 --- a/locales/pt-BR/landing-all.ftl +++ b/locales/pt-BR/landing-all.ftl @@ -3,14 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. public-nav-name = { -brand-mozilla-monitor } - landing-all-hero-title = Descubra onde suas informações privativas estão sendo expostas e recupere o controle sobre elas landing-all-hero-lead = Verificamos vazamentos de dados para ver se seus dados foram expostos e fornecemos instruções passo a passo para resolver. - landing-all-hero-emailform-input-placeholder = seunome@example.com landing-all-hero-emailform-input-label = Insira seu endereço de email para verificar exposição em vazamentos de dados. landing-all-hero-emailform-submit-label = Obtenha uma verificação gratuita - +landing-all-hero-emailform-submit-sign-in-label = Entre para obter uma análise gratuita # This is a label underneath a big number "14" - it's an image that demos Monitor. landing-all-hero-image-chart-label = exposições @@ -64,7 +62,5 @@ landing-all-help-protect-you-feature-one = Procuramos em todos os vazamentos con landing-all-help-protect-you-feature-two = Orientamos nas etapas para resolver cada vazamento landing-all-help-protect-you-feature-three = Monitoramos continuamente e enviamos alertas sobre novos vazamentos landing-all-help-protect-you-cta = Cadastre-se para receber alertas de vazamentos - landing-all-get-started = Verifique seu email para começar landing-all-take-back-data = Reassuma o controle sobre seus dados - From 4637f12ed4ddd3f49509df659b222b2014757a4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 12:09:36 +0000 Subject: [PATCH 124/137] chore(deps-dev): bump prettier from 3.3.2 to 3.3.3 Bumps [prettier](https://github.com/prettier/prettier) from 3.3.2 to 3.3.3. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.3.2...3.3.3) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 93df26589f2..9f3780954f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,7 +93,7 @@ "jest-fail-on-console": "^3.3.0", "lint-staged": "^15.2.5", "mjml-browser": "^4.15.3", - "prettier": "3.3.2", + "prettier": "3.3.3", "react-intersection-observer": "^9.10.3", "sass": "^1.77.6", "storybook": "^8.1.11", @@ -23765,9 +23765,9 @@ } }, "node_modules/prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" diff --git a/package.json b/package.json index d24420e07e9..a22fd69da0d 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "jest-fail-on-console": "^3.3.0", "lint-staged": "^15.2.5", "mjml-browser": "^4.15.3", - "prettier": "3.3.2", + "prettier": "3.3.3", "react-intersection-observer": "^9.10.3", "sass": "^1.77.6", "storybook": "^8.1.11", From 85841725e0a369e6e0b688d27618ccdd51ee9a2c Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 18 Jul 2024 12:20:40 +0200 Subject: [PATCH 125/137] Re-add allowlist functionality for feature flags --- .../components/FlagEditor.module.scss | 88 +++++++++ .../feature-flags/components/FlagEditor.tsx | 180 ++++++++++++++++++ .../components/ToggleFlagEnabled.tsx | 63 ------ .../admin/feature-flags/page.module.scss | 14 ++ .../admin/feature-flags/page.tsx | 102 +++------- .../v1/admin/feature-flags/[flagId]/route.ts | 24 ++- src/db/tables/featureFlags.ts | 2 +- src/knex-tables.d.ts | 6 +- 8 files changed, 331 insertions(+), 148 deletions(-) create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/components/FlagEditor.module.scss create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/components/FlagEditor.tsx delete mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/components/ToggleFlagEnabled.tsx diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/components/FlagEditor.module.scss b/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/components/FlagEditor.module.scss new file mode 100644 index 00000000000..f8a8982f219 --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/components/FlagEditor.module.scss @@ -0,0 +1,88 @@ +@import "../../../../../../tokens"; + +.flagWrapper { + display: flex; + flex-direction: column; + gap: $spacing-md; + background-color: $color-white; + padding: $spacing-lg; + border-radius: $border-radius-md; + max-width: $content-md; +} + +.flagName { + font: $text-title-3xs; +} + +.enabledControl { + display: flex; + gap: $spacing-xs; + font: $text-body-lg; +} + +.allowListWrapper { + h4 { + font: $text-body-md; + font-weight: bold; + } +} + +.allowList { + flex-grow: 1; + display: flex; + flex-direction: column; + padding: 0; + list-style-type: none; +} + +.addressListing { + display: flex; + align-items: center; + gap: $spacing-xs; + font: $text-body-lg; + + :first-child { + flex-grow: 1; + } + + button { + background-color: transparent; + border: none; + border-radius: $border-radius-xl; + aspect-ratio: 1; + + &:hover { + cursor: pointer; + background-color: $color-blue-50; + color: $color-white; + } + } +} + +.addressAdder { + display: flex; + align-items: center; + gap: $spacing-sm; + + // This rule is more specific than the button:hover in `.addressListing`, + // but that class doesn't apply to the same elements: + /* stylelint-disable no-descending-specificity */ + button { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + background-color: transparent; + color: $color-blue-50; + border: none; + border-radius: $border-radius-sm; + padding: $spacing-sm; + + &:hover { + cursor: pointer; + background-color: $color-blue-50; + color: $color-white; + } + } + /* stylelint-enable no-descending-specificity */ +} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/components/FlagEditor.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/components/FlagEditor.tsx new file mode 100644 index 00000000000..8378e7a6f59 --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/components/FlagEditor.tsx @@ -0,0 +1,180 @@ +/* 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/. */ + +"use client"; +import { FeatureFlagRow } from "knex/types/tables"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import styles from "./FlagEditor.module.scss"; +import { + CheckIcon, + DeleteIcon, +} from "../../../../../../components/server/Icons"; +import type { UpdateFeatureFlagRequestBody } from "../../../../../../api/v1/admin/feature-flags/[flagId]/route"; +import { Button } from "../../../../../../components/client/Button"; + +export const FlagEditor = (props: { flag: FeatureFlagRow }) => { + const [isEnabled, setIsEnabled] = useState(props.flag.is_enabled); + const [newAddress, setNewAddress] = useState(""); + const [newAllowlistedAddresses, setNewAllowListedAddresses] = useState< + string[] + >([]); + const [unAllowlistedAddresses, setUnAllowListedAddresses] = useState< + string[] + >([]); + const router = useRouter(); + + const updateFlag = async () => { + try { + if (isEnabled !== props.flag.is_enabled) { + const isEnabledResonse = await sendUpdateRequest(props.flag.name, { + id: "isEnabled", + isEnabled: isEnabled, + }); + if (!isEnabledResonse.ok) { + throw new Error(await isEnabledResonse.text()); + } + } + + const allowList = (props.flag.allow_list ?? []) + .filter((address) => !unAllowlistedAddresses.includes(address)) + .concat(newAllowlistedAddresses); + const allowListResponse = await sendUpdateRequest(props.flag.name, { + id: "allowList", + value: allowList.map((address) => address.trim()).join(","), + }); + if (!allowListResponse.ok) { + throw new Error(await allowListResponse.text()); + } + } catch (e) { + console.error(e); + } + router.refresh(); + }; + + return ( +
+

{props.flag.name}

+
+ setIsEnabled(e.target.checked)} + /> + +
+
+ {" "} +

+ {(props.flag.allow_list?.length ?? 0) - + unAllowlistedAddresses.length === + 0 && newAllowlistedAddresses.length === 0 ? ( + <>Enabled for everyone + ) : ( + <>Only enable for: + )} +

+
    + {(props.flag.allow_list ?? []) + .filter((address) => !unAllowlistedAddresses.includes(address)) + .map((address) => { + return ( +
  • + + setUnAllowListedAddresses((prev) => [...prev, address]) + } + /> +
  • + ); + })} + {newAllowlistedAddresses.map((address) => { + return ( +
  • + + setNewAllowListedAddresses((prev) => + prev.filter((a) => a !== address), + ) + } + /> +
  • + ); + })} +
+
+ { + e.preventDefault(); + + setNewAllowListedAddresses((prev) => [...prev, newAddress]); + setNewAddress(""); + }} + className={styles.addressAdder} + > + + setNewAddress(e.target.value)} + /> + + + +
+ ); +}; + +const AllowlistedAddress = (props: { + address: string; + onRemove: () => void; +}) => { + return ( + + {props.address} + + + ); +}; + +async function sendUpdateRequest( + flagName: string, + body: UpdateFeatureFlagRequestBody, +) { + const endpoint = `/api/v1/admin/feature-flags/${flagName}`; + const options = { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }; + return fetch(endpoint, options); +} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/components/ToggleFlagEnabled.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/components/ToggleFlagEnabled.tsx deleted file mode 100644 index 3bf7976b6b7..00000000000 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/components/ToggleFlagEnabled.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* 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/. */ - -"use client"; -import { useRouter } from "next/navigation"; -import { ChangeEventHandler } from "react"; - -interface FeatureToggleElement { - id: string | null; - name: string | null; - checked: string | null; -} - -export const ToggleFlagEnabled = (props: { - id: string | undefined; - name: string | undefined; - isEnabled: boolean | undefined; -}) => { - const router = useRouter(); - - const handleChange = ( - event: ChangeEventHandler & { - target: FeatureToggleElement; - }, - ) => { - const eventTarget: FeatureToggleElement = event.target; - - const id = eventTarget.id; - const name = eventTarget.name; - const isEnabled = eventTarget.checked ? true : false; - - if (!name) { - throw new Error("No flag name provided"); - } - - const endpoint = `/api/v1/admin/feature-flags/${name}`; - const options = { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ id, name, isEnabled }), - }; - fetch(endpoint, options) - .then((response) => { - if (response.ok) { - router.refresh(); - } - }) - .catch((ex) => console.error(ex)); - }; - - return ( - } - > - ); -}; diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/page.module.scss b/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/page.module.scss index 4e6488eee58..9c7e7f512ae 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/page.module.scss +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/page.module.scss @@ -4,6 +4,7 @@ width: 100%; height: 100%; background-color: $color-grey-05; + overflow: auto; .tabBar { height: $tab-bar-height; @@ -37,4 +38,17 @@ border: 1px solid; padding: 10px; } + + h3 { + padding: 0 $spacing-2xl; + font: $text-title-2xs; + } +} + +.flagList { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: $spacing-lg; + padding: $spacing-2xl; } diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/page.tsx index 2c686d8df4b..7e41786ce4d 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/page.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/feature-flags/page.tsx @@ -4,14 +4,8 @@ import { notFound, redirect } from "next/navigation"; import { getServerSession } from "../../../../../functions/server/getServerSession"; -import { - getAllFeatureFlags, - getDeletedFeatureFlags, -} from "../../../../../../db/tables/featureFlags"; +import { getAllFeatureFlags } from "../../../../../../db/tables/featureFlags"; import { AddFeatureFlag } from "./components/AddFeatureFlag"; -import { DeleteFeatureFlag } from "./components/DeleteFeatureFlag"; -import { ToggleFlagEnabled } from "./components/ToggleFlagEnabled"; -import { FeatureFlagRow } from "knex/types/tables"; import { isAdmin } from "../../../../../api/utils/auth"; import { Toolbar } from "../../../../../components/client/toolbar/Toolbar"; import styles from "./page.module.scss"; @@ -20,6 +14,7 @@ import { getPremiumSubscriptionUrl, } from "../../../../../functions/server/getPremiumSubscriptionInfo"; import { defaultExperimentData } from "../../../../../../telemetry/generated/nimbus/experiments"; +import { FlagEditor } from "./components/FlagEditor"; export default async function FeatureFlagPage() { const session = await getServerSession(); @@ -36,72 +31,10 @@ export default async function FeatureFlagPage() { return notFound(); } - const ActiveFlagsTable = (featureFlags: { data: Array }) => { - const { data } = featureFlags; - - if (!data || data.length === 0) { - return

No data

; - } - - return ( - - - - - - - - - {data.map((item) => ( - - - - - - ))} - -
NameEnabled
{item.name} - - {item.is_enabled} - - -
- ); - }; - - const DeletedFlagsTable = (featureFlags: { data: Array }) => { - const { data } = featureFlags; - - if (!data || data.length === 0) { - return

No data

; - } - - return ( - - - - - - - - - {data.map((item) => ( - - - - - ))} - -
NameDeleted At
{item.name}{item.deleted_at?.toString()}
- ); - }; - - const featureFlags = (await getAllFeatureFlags()) ?? null; - const deletedFeatureFlags = (await getDeletedFeatureFlags()) ?? null; + const featureFlags = + (await getAllFeatureFlags()).toSorted( + (flagA, flagB) => flagB.created_at.getTime() - flagA.created_at.getTime(), + ) ?? []; return (
@@ -124,18 +57,29 @@ export default async function FeatureFlagPage() {

- Note: Feaure flags are deprecated, use{" "} + Note: Feature flags are deprecated, use{" "} Experimenter.


Add New Feature Flag


-

Active Feature Flags

- -
-

Deleted Feature Flags

- +

Enabled Feature Flags

+
+ {featureFlags + .filter((flag) => flag.is_enabled) + .map((flag) => ( + + ))} +
+

Disabled Feature Flags

+
+ {featureFlags + .filter((flag) => !flag.is_enabled) + .map((flag) => ( + + ))} +
); diff --git a/src/app/api/v1/admin/feature-flags/[flagId]/route.ts b/src/app/api/v1/admin/feature-flags/[flagId]/route.ts index df369961c35..56b4f2c9241 100644 --- a/src/app/api/v1/admin/feature-flags/[flagId]/route.ts +++ b/src/app/api/v1/admin/feature-flags/[flagId]/route.ts @@ -37,6 +37,28 @@ export async function GET( } } +export type UpdateFeatureFlagRequestBody = + | { + id: "isEnabled"; + isEnabled: boolean; + } + | { + id: "dependencies"; + value: string; + } + | { + id: "allowList"; + value: string; + } + | { + id: "waitList"; + value: string; + } + | { + id: "owner"; + value: string; + }; + export async function PUT(req: NextRequest) { const session = await getServerSession(); if (isAdmin(session?.user?.email || "")) { @@ -46,7 +68,7 @@ export async function PUT(req: NextRequest) { if (!flagName) { throw new Error("No flag name provided"); } - const result = await req.json(); + const result: UpdateFeatureFlagRequestBody = await req.json(); if (result.id === "isEnabled") { await enableFeatureFlagByName(flagName, result.isEnabled); diff --git a/src/db/tables/featureFlags.ts b/src/db/tables/featureFlags.ts index 24e461f5857..70c9b081050 100644 --- a/src/db/tables/featureFlags.ts +++ b/src/db/tables/featureFlags.ts @@ -93,7 +93,7 @@ export async function getFeatureFlagByName(name: string) { */ export async function addFeatureFlag(flag: FeatureFlag) { logger.info("addFeatureFlag", flag); - const featureFlagDb: FeatureFlagRow = { + const featureFlagDb: Omit = { name: flag.name, is_enabled: flag.isEnabled, description: flag.description, diff --git a/src/knex-tables.d.ts b/src/knex-tables.d.ts index b5d58d3197c..e3f9c2d9c28 100644 --- a/src/knex-tables.d.ts +++ b/src/knex-tables.d.ts @@ -47,8 +47,8 @@ declare module "knex/types/tables" { dependencies?: string[]; allow_list?: string[]; wait_list?: string[]; - added_at?: Date; - modified_at?: Date; + created_at: Date; + modified_at: Date; expired_at?: Date; deleted_at?: Date; owner?: string; @@ -60,8 +60,6 @@ declare module "knex/types/tables" { | "dependencies" | "allow_list" | "wait_list" - | "added_at" - | "modified_at" | "expired_at" | "owner" >; From 309ed8f39bce41b07e64ae665e7c8a9d2733cde8 Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 18 Jul 2024 10:01:47 +0200 Subject: [PATCH 126/137] Port breach alerts cronjob to TS This job uses appConstants, and ESBuild bundles everything into a single module. That means that resolving the .env files relative to the dirname would fail for the cronjob. Thus, I reverted that back to have dotenv-flow autodetect the .env file, and migrated the DB script (for which the dirname-relative resolving was added) to use process.env directly instead. --- README.md | 7 +- package.json | 3 +- src/appConstants.js | 11 +- src/db/knexfile.js | 11 +- .../emailBreachAlerts.test.ts} | 106 +++++++++-------- .../emailBreachAlerts.ts} | 108 ++++++++++++++---- src/utils/hibp.js | 31 ++--- 7 files changed, 180 insertions(+), 97 deletions(-) rename src/scripts/{emailBreachAlerts.test.js => cronjobs/emailBreachAlerts.test.ts} (78%) rename src/scripts/{emailBreachAlerts.js => cronjobs/emailBreachAlerts.ts} (73%) diff --git a/README.md b/README.md index efc5ec215e2..993c089e762 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,8 @@ Monitor uses GCP PubSub for processing incoming breach data, this can be tested gcloud beta emulators pubsub start --project=your-project-name ``` +(Set `your-project-name` as the value for `GCP_PUBSUB_PROJECT_ID` in your `.env.local`.) + ### In a different shell, set the environment to point at the emulator and run Monitor in dev mode: ```sh @@ -160,10 +162,13 @@ curl -d '{ "breachName": "000webhost", "hashPrefix": "test", "hashSuffixes": ["t http://localhost:6060/api/v1/hibp/notify ``` +This emulates HIBP notifying our API that a new breach was found. Our API will +then add it to the (emulated) pubsub queue. + ### This pubsub queue will be consumed by this cron job, which is responsible for looking up and emailing impacted users: ```sh -node src/scripts/emailBreachAlerts.js +npm run dev:cron:breach-alerts ``` ### Emails diff --git a/package.json b/package.json index a22fd69da0d..843ba663eaa 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dev": "npm run build-nimbus && next dev --port=6060", "dev:cron:first-data-broker-removal-fixed": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/firstDataBrokerRemovalFixed.tsx", "dev:cron:monthly-activity": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/monthlyActivity.tsx", + "dev:cron:breach-alerts": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/emailBreachAlerts.ts", "dev:cron:db-delete-unverified-subscribers": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/deleteUnverifiedSubscribers.ts", "dev:cron:db-pull-breaches": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/syncBreaches.ts", "dev:cron:remote-settings-pull-breaches": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/updateBreachesInRemoteSettings.ts", @@ -27,7 +28,7 @@ "e2e:smoke": "playwright test src/e2e/ --grep @smoke", "cron:first-data-broker-removal-fixed": "node dist/scripts/cronjobs/firstDataBrokerRemovalFixed.js", "cron:monthly-activity": "node dist/scripts/cronjobs/monthlyActivity.js", - "cron:breach-alerts": "node src/scripts/emailBreachAlerts.js", + "cron:breach-alerts": "node dist/scripts/cronjobs/emailBreachAlerts.js", "cron:db-delete-unverified-subscribers": "node dist/scripts/cronjobs/deleteUnverifiedSubscribers.js", "cron:db-pull-breaches": "node dist/scripts/cronjobs/syncBreaches.js", "cron:remote-settings-pull-breaches": "node dist/scripts/cronjobs/updateBreachesInRemoteSettings.js", diff --git a/src/appConstants.js b/src/appConstants.js index 7dac001fd49..9069e2ec5b5 100644 --- a/src/appConstants.js +++ b/src/appConstants.js @@ -2,21 +2,14 @@ * 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/. */ -// TODO: these vars were copy/pasted from the old app-constants.js and should be cleaned up -import path from "path"; -import url from "url"; - if (typeof process.env.NEXT_RUNTIME === "undefined" && typeof process.env.STORYBOOK === "undefined") { // Next.js already loads env vars by itself, and dotenv-flow will throw an // error if loaded in that context (about `fs` not existing), so only load // it if we're not running in a Next.js-context (e.g. cron jobs): - const __filename = url.fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - - const dotenvFlow = await import("dotenv-flow"); - dotenvFlow.config({ path: path.resolve(__dirname, "../") }); + await import("dotenv-flow/config"); } +// TODO: these vars were copy/pasted from the old app-constants.js and should be cleaned up const requiredEnvVars = [ 'ADMINS', 'APP_ENV', diff --git a/src/db/knexfile.js b/src/db/knexfile.js index 80e793c1362..d9118c7d6a8 100644 --- a/src/db/knexfile.js +++ b/src/db/knexfile.js @@ -6,7 +6,7 @@ // `pg-connection-string` works, triggering a false positive for this lint rule: /* eslint-disable import/no-named-as-default-member */ import pgConnectionStr from "pg-connection-string"; -import AppConstants from "../appConstants.js"; +import "dotenv-flow/config"; /** * @typedef {object} KnexConfig @@ -14,9 +14,12 @@ import AppConstants from "../appConstants.js"; * @property {import("pg-connection-string").ConnectionOptions} connection */ -const { DATABASE_URL, APP_ENV, NODE_ENV, PG_HOST } = AppConstants; +const DATABASE_URL = process.env.DATABASE_URL ?? ""; +const APP_ENV = process.env.APP_ENV ?? "production"; +/** @type {string} */ +const NODE_ENV = process.env.NODE_ENV ?? "production"; const connectionObj = pgConnectionStr.parse(DATABASE_URL); -if (APP_ENV === "heroku") { +if (typeof process.env.APP_ENV === "string" && process.env.APP_ENV === "heroku") { // @ts-ignore TODO: Check if this typing error is correct, or if the types are wrong? connectionObj.ssl = { rejectUnauthorized: false }; } @@ -40,7 +43,7 @@ let exportConfig = NODE_ENV === "tests" ? TESTS_CONFIG : RUNTIME_CONFIG if (APP_ENV === "cloudrun") { // @ts-ignore TODO: Check if this typing error is correct, or if the types are wrong? connectionObj.ssl = false; - connectionObj.host = PG_HOST + connectionObj.host = /** @type {string} */ (process.env.PG_HOST) exportConfig = { client: "pg", connection: connectionObj diff --git a/src/scripts/emailBreachAlerts.test.js b/src/scripts/cronjobs/emailBreachAlerts.test.ts similarity index 78% rename from src/scripts/emailBreachAlerts.test.js rename to src/scripts/cronjobs/emailBreachAlerts.test.ts index 9766ae85617..839597a3e3b 100644 --- a/src/scripts/emailBreachAlerts.test.js +++ b/src/scripts/cronjobs/emailBreachAlerts.test.ts @@ -4,6 +4,9 @@ import { test, expect, jest } from "@jest/globals"; +process.env.GCP_PUBSUB_PROJECT_ID = "arbitrary-id"; +process.env.GCP_PUBSUB_SUBSCRIPTION_NAME = "arbitrary-name"; + jest.mock("@sentry/nextjs", () => { return { init: jest.fn(), @@ -11,7 +14,7 @@ jest.mock("@sentry/nextjs", () => { }; }); -jest.mock("../utils/email.js", () => { +jest.mock("../../utils/email.js", () => { return { initEmail: jest.fn(), EmailTemplateType: jest.fn(), @@ -20,7 +23,7 @@ jest.mock("../utils/email.js", () => { }; }); -jest.mock("../utils/hibp.js", () => { +jest.mock("../../utils/hibp.js", () => { return { getAddressesAndLanguageForEmail: jest.fn(() => { return { @@ -34,19 +37,19 @@ jest.mock("../utils/hibp.js", () => { }; }); -jest.mock("../db/tables/subscribers.js", () => { +jest.mock("../../db/tables/subscribers.js", () => { return { getSubscribersByHashes: jest.fn(() => [""]), }; }); -jest.mock("../db/tables/emailAddresses.js", () => { +jest.mock("../../db/tables/emailAddresses.js", () => { return { getEmailAddressesByHashes: jest.fn(() => [""]), }; }); -jest.mock("../db/tables/email_notifications.js", () => { +jest.mock("../../db/tables/email_notifications.js", () => { return { getNotifiedSubscribersForBreach: jest.fn(() => [""]), addEmailNotification: jest.fn(), @@ -54,7 +57,7 @@ jest.mock("../db/tables/email_notifications.js", () => { }; }); -jest.mock("../utils/fluent.js", () => { +jest.mock("../../utils/fluent.js", () => { return { initFluentBundles: jest.fn(), getMessage: jest.fn(), @@ -62,24 +65,24 @@ jest.mock("../utils/fluent.js", () => { }; }); -jest.mock("../emails/email2022.js", () => { +jest.mock("../../emails/email2022.js", () => { return { getTemplate: jest.fn(), }; }); -jest.mock("../emails/emailBreachAlert.js", () => { +jest.mock("../../emails/emailBreachAlert.js", () => { return { breachAlertEmailPartial: jest.fn(), }; }); -const subClient = { +const subClient: any = { subscriptionPath: jest.fn(), acknowledge: jest.fn(), }; -function buildReceivedMessages(testBreachAlert) { +function buildReceivedMessages(testBreachAlert: any) { return [ { ackId: "testAckId", @@ -101,12 +104,14 @@ beforeEach(() => { }); test("rejects invalid messages", async () => { - const { poll } = await import("./emailBreachAlerts.js"); + const { poll } = await import("./emailBreachAlerts"); const consoleError = jest .spyOn(console, "error") .mockImplementation(() => {}); - const consoleLog = jest.spyOn(console, "log").mockImplementation(); + const consoleLog = jest + .spyOn(console, "log") + .mockImplementation(() => undefined); await poll( subClient, @@ -174,14 +179,19 @@ test("rejects invalid messages", async () => { }); test("processes valid messages", async () => { - const consoleLog = jest.spyOn(console, "log").mockImplementation(); + const consoleLog = jest + .spyOn(console, "log") + .mockImplementation(() => undefined); // It's not clear if the calls to console.info are important enough to remain, // but since they were already there when adding the "no logs" rule in tests, // I'm respecting Chesterton's Fence and leaving them in place for now: - jest.spyOn(console, "info").mockImplementation(); - const { sendEmail } = await import("../utils/email.js"); + jest.spyOn(console, "info").mockImplementation(() => undefined); + const emailMod = await import("../../utils/email.js"); + const sendEmail = emailMod.sendEmail as jest.Mock< + (typeof emailMod)["sendEmail"] + >; - const mockedUtilsHibp = jest.requireMock("../utils/hibp.js"); + const mockedUtilsHibp: any = jest.requireMock("../../utils/hibp.js"); mockedUtilsHibp.getBreachByName.mockReturnValue({ IsVerified: true, Domain: "test1", @@ -195,7 +205,7 @@ test("processes valid messages", async () => { hashSuffixes: ["test-suffix1"], }); - const { poll } = await import("./emailBreachAlerts.js"); + const { poll } = await import("./emailBreachAlerts"); await poll(subClient, receivedMessages); // Fabricated but valid breach is acknowledged. @@ -265,13 +275,15 @@ test("processes valid messages", async () => { }); test("skipping email when subscriber id exists in email_notifications table", async () => { - const consoleLog = jest.spyOn(console, "log").mockImplementation(); + const consoleLog = jest + .spyOn(console, "log") + .mockImplementation(() => undefined); // It's not clear if the calls to console.info are important enough to remain, // but since they were already there when adding the "no logs" rule in tests, // I'm respecting Chesterton's Fence and leaving them in place for now: - jest.spyOn(console, "info").mockImplementation(); - const { sendEmail } = await import("../utils/email.js"); - const mockedUtilsHibp = jest.requireMock("../utils/hibp.js"); + jest.spyOn(console, "info").mockImplementation(() => undefined); + const { sendEmail } = await import("../../utils/email.js"); + const mockedUtilsHibp: any = jest.requireMock("../../utils/hibp.js"); mockedUtilsHibp.getBreachByName.mockReturnValue({ IsVerified: true, Domain: "test1", @@ -280,19 +292,19 @@ test("skipping email when subscriber id exists in email_notifications table", as Id: 1, }); - jest.mock("../db/tables/subscribers.js", () => { + jest.mock("../../db/tables/subscribers.js", () => { return { getSubscribersByHashes: jest.fn(() => [{ id: 1 }]), }; }); - jest.mock("../db/tables/emailAddresses.js", () => { + jest.mock("../../db/tables/emailAddresses.js", () => { return { getEmailAddressesByHashes: jest.fn(() => []), }; }); - jest.mock("../db/tables/email_notifications.js", () => { + jest.mock("../../db/tables/email_notifications.js", () => { return { getNotifiedSubscribersForBreach: jest.fn(() => [1]), addEmailNotification: jest.fn(), @@ -305,7 +317,7 @@ test("skipping email when subscriber id exists in email_notifications table", as hashSuffixes: ["test-suffix1"], }); - const { poll } = await import("./emailBreachAlerts.js"); + const { poll } = await import("./emailBreachAlerts"); await poll(subClient, receivedMessages); // Verified, not fabricated, not spam list breaches are acknowledged. @@ -318,13 +330,15 @@ test("skipping email when subscriber id exists in email_notifications table", as }); test("throws an error when addEmailNotification fails", async () => { - const consoleLog = jest.spyOn(console, "log").mockImplementation(); + const consoleLog = jest + .spyOn(console, "log") + .mockImplementation(() => undefined); // It's not clear if the calls to console.info are important enough to remain, // but since they were already there when adding the "no logs" rule in tests, // I'm respecting Chesterton's Fence and leaving them in place for now: - jest.spyOn(console, "info").mockImplementation(); - const { sendEmail } = await import("../utils/email.js"); - const mockedUtilsHibp = jest.requireMock("../utils/hibp.js"); + jest.spyOn(console, "info").mockImplementation(() => undefined); + const { sendEmail } = await import("../../utils/email.js"); + const mockedUtilsHibp: any = jest.requireMock("../../utils/hibp.js"); mockedUtilsHibp.getBreachByName.mockReturnValue({ IsVerified: true, Domain: "test1", @@ -333,19 +347,19 @@ test("throws an error when addEmailNotification fails", async () => { Id: 1, }); - jest.mock("../db/tables/subscribers.js", () => { + jest.mock("../../db/tables/subscribers.js", () => { return { getSubscribersByHashes: jest.fn(() => [{ id: 1 }]), }; }); - jest.mock("../db/tables/emailAddresses.js", () => { + jest.mock("../../db/tables/emailAddresses.js", () => { return { getEmailAddressesByHashes: jest.fn(() => [""]), }; }); - jest.mock("../db/tables/email_notifications.js", () => { + jest.mock("../../db/tables/email_notifications.js", () => { return { getNotifiedSubscribersForBreach: jest.fn(() => [2]), addEmailNotification: jest.fn().mockImplementationOnce(() => { @@ -359,13 +373,13 @@ test("throws an error when addEmailNotification fails", async () => { hashSuffixes: ["test-suffix1"], }); - const { poll } = await import("./emailBreachAlerts.js"); + const { poll } = await import("./emailBreachAlerts"); try { await poll(subClient, receivedMessages); - } catch (e) { + } catch (e: unknown) { expect(console.error).toBeCalled(); - expect(e.message).toBe("add failed"); + expect((e as Error).message).toBe("add failed"); } expect(consoleLog).toHaveBeenCalledWith( @@ -375,13 +389,15 @@ test("throws an error when addEmailNotification fails", async () => { }); test("throws an error when markEmailAsNotified fails", async () => { - const consoleLog = jest.spyOn(console, "log").mockImplementation(); + const consoleLog = jest + .spyOn(console, "log") + .mockImplementation(() => undefined); // It's not clear if the calls to console.info are important enough to remain, // but since they were already there when adding the "no logs" rule in tests, // I'm respecting Chesterton's Fence and leaving them in place for now: - jest.spyOn(console, "info").mockImplementation(); - const { sendEmail } = await import("../utils/email.js"); - const mockedUtilsHibp = jest.requireMock("../utils/hibp.js"); + jest.spyOn(console, "info").mockImplementation(() => undefined); + const { sendEmail } = await import("../../utils/email.js"); + const mockedUtilsHibp: any = jest.requireMock("../../utils/hibp.js"); mockedUtilsHibp.getBreachByName.mockReturnValue({ IsVerified: true, Domain: "test1", @@ -390,19 +406,19 @@ test("throws an error when markEmailAsNotified fails", async () => { Id: 1, }); - jest.mock("../db/tables/subscribers.js", () => { + jest.mock("../../db/tables/subscribers.js", () => { return { getSubscribersByHashes: jest.fn(() => [{ id: 1 }]), }; }); - jest.mock("../db/tables/emailAddresses.js", () => { + jest.mock("../../db/tables/emailAddresses.js", () => { return { getEmailAddressesByHashes: jest.fn(() => [""]), }; }); - jest.mock("../db/tables/email_notifications.js", () => { + jest.mock("../../db/tables/email_notifications.js", () => { return { getNotifiedSubscribersForBreach: jest.fn(() => [2]), addEmailNotification: jest.fn(), @@ -417,13 +433,13 @@ test("throws an error when markEmailAsNotified fails", async () => { hashSuffixes: ["test-suffix1"], }); - const { poll } = await import("./emailBreachAlerts.js"); + const { poll } = await import("./emailBreachAlerts"); try { await poll(subClient, receivedMessages); - } catch (e) { + } catch (e: unknown) { expect(console.error).toBeCalled(); - expect(e.message).toBe("mark failed"); + expect((e as Error).message).toBe("mark failed"); } expect(consoleLog).toHaveBeenCalledWith( 'Received message: {"breachName":"test1","hashPrefix":"test-prefix1","hashSuffixes":["test-suffix1"]}', diff --git a/src/scripts/emailBreachAlerts.js b/src/scripts/cronjobs/emailBreachAlerts.ts similarity index 73% rename from src/scripts/emailBreachAlerts.js rename to src/scripts/cronjobs/emailBreachAlerts.ts index d4c333abf11..d58a72a60d9 100644 --- a/src/scripts/emailBreachAlerts.js +++ b/src/scripts/cronjobs/emailBreachAlerts.ts @@ -4,40 +4,42 @@ import Sentry from "@sentry/nextjs"; import { acceptedLanguages, negotiateLanguages } from "@fluent/langneg"; -import { localStorage } from "../utils/localStorage.js"; +import { localStorage } from "../../utils/localStorage.js"; import * as pubsub from "@google-cloud/pubsub"; import * as grpc from "@grpc/grpc-js"; +import type { SubscriberClient } from "@google-cloud/pubsub/build/src/v1/subscriber_client.js"; +import type { EmailAddressRow, SubscriberRow } from "knex/types/tables"; import { getSubscribersByHashes, knexSubscribers, -} from "../db/tables/subscribers.js"; +} from "../../db/tables/subscribers.js"; import { getEmailAddressesByHashes, knexEmailAddresses, -} from "../db/tables/emailAddresses.js"; +} from "../../db/tables/emailAddresses.js"; import { getNotifiedSubscribersForBreach, addEmailNotification, markEmailAsNotified, -} from "../db/tables/email_notifications.js"; -import { getTemplate } from "../emails/email2022.js"; -import { breachAlertEmailPartial } from "../emails/emailBreachAlert.js"; +} from "../../db/tables/email_notifications.js"; +import { getTemplate } from "../../emails/email2022.js"; +import { breachAlertEmailPartial } from "../../emails/emailBreachAlert.js"; import { initEmail, EmailTemplateType, getEmailCtaDashboardHref, sendEmail, -} from "../utils/email.js"; +} from "../../utils/email.js"; -import { initFluentBundles, getMessage } from "../utils/fluent.js"; +import { initFluentBundles, getMessage } from "../../utils/fluent.js"; import { getAddressesAndLanguageForEmail, getBreachByName, getAllBreachesFromDb, knexHibp, -} from "../utils/hibp.js"; +} from "../../utils/hibp.js"; const SENTRY_SLUG = "cron-breach-alerts"; @@ -55,7 +57,8 @@ const checkInId = Sentry.captureCheckIn({ // Only process this many messages before exiting. /* c8 ignore start */ const maxMessages = parseInt( - process.env.EMAIL_BREACH_ALERT_MAX_MESSAGES || 10000, + process.env.EMAIL_BREACH_ALERT_MAX_MESSAGES ?? "10000", + 10, ); /* c8 ignore stop */ const projectId = process.env.GCP_PUBSUB_PROJECT_ID; @@ -71,7 +74,28 @@ const subscriptionName = process.env.GCP_PUBSUB_SUBSCRIPTION_NAME; * * More about how account identities are anonymized: https://blog.mozilla.org/security/2018/06/25/scanning-breached-accounts-k-anonymity/ */ -export async function poll(subClient, receivedMessages) { +export async function poll( + subClient: SubscriberClient, + receivedMessages: Array<{ + ackId?: string | null; + deliveryAttempt?: number | null; + message?: { + messageId?: string | null; + orderingKey?: string | undefined | null; + data?: string | Uint8Array | null; + } | null; + }>, +) { + // These env vars are always set in tests: + /* c8 ignore next 8 */ + if (!projectId) { + throw new Error("Environment variable [$GCP_PUBSUB_PROJECT_ID] not set."); + } + if (!subscriptionName) { + throw new Error( + "Environment variable [$GCP_PUBSUB_SUBSCRIPTION_NAME] not set.", + ); + } const formattedSubscription = subClient.subscriptionPath( projectId, subscriptionName, @@ -81,8 +105,12 @@ export async function poll(subClient, receivedMessages) { // Process the messages. Skip any that cannot be processed, and do not mark as acknowledged. for (const message of receivedMessages) { - console.log(`Received message: ${message.message.data}`); - const data = JSON.parse(message.message.data); + console.log(`Received message: ${message.message?.data}`); + const data = JSON.parse(message.message?.data as string) as { + breachName: string; + hashPrefix: string; + hashSuffixes: string[]; + }; if (!(data.breachName && data.hashPrefix && data.hashSuffixes)) { console.error( @@ -143,7 +171,14 @@ export async function poll(subClient, receivedMessages) { subClient.acknowledge({ subscription: formattedSubscription, - ackIds: [message.ackId], + ackIds: + typeof message.ackId === "string" + ? [message.ackId] + : /* c8 ignore next 4 */ + // When porting this code to TypeScript, the undefined/null case + // wasn't dealt with, so presumably our messages always have an + // ackId: + message.ackId, }); continue; @@ -157,7 +192,9 @@ export async function poll(subClient, receivedMessages) { const subscribers = await getSubscribersByHashes(hashes); const emailAddresses = await getEmailAddressesByHashes(hashes); - const recipients = subscribers.concat(emailAddresses); + const recipients: Array< + SubscriberRow | (SubscriberRow & EmailAddressRow) + > = subscribers.concat(emailAddresses); console.info(EmailTemplateType.Notification, { breachAlertName: breachAlert.Name, @@ -165,7 +202,7 @@ export async function poll(subClient, receivedMessages) { }); const utmCampaignId = "breach-alert"; - const notifiedRecipients = []; + const notifiedRecipients: string[] = []; for (const recipient of recipients) { console.info("notify", { recipient }); @@ -175,7 +212,11 @@ export async function poll(subClient, receivedMessages) { // Get subscriber ID from: // - `subscriber_id`: if `email_addresses` record // - `id`: if `subscribers` record - const subscriberId = recipient.subscriber_id ?? recipient.id; + /* c8 ignore next 4 */ + // TODO: Add unit test when changing this code: + const subscriberId = hasEmailAddressAttached(recipient) + ? recipient.subscriber_id + : recipient.id; if (notifiedSubs.includes(subscriberId)) { console.info("Subscriber already notified, skipping: ", subscriberId); continue; @@ -189,7 +230,7 @@ export async function poll(subClient, receivedMessages) { : []; /* c8 ignore stop */ - const availableLanguages = process.env.SUPPORTED_LOCALES.split(","); + const availableLanguages = process.env.SUPPORTED_LOCALES!.split(","); const supportedLocales = negotiateLanguages( requestedLanguage, availableLanguages, @@ -246,7 +287,7 @@ export async function poll(subClient, receivedMessages) { breachId, data.recipientEmail, ); - } catch (e) { + } catch (e: any) { console.error("Failed to mark email as notified: ", e); throw new Error(e); } @@ -260,7 +301,14 @@ export async function poll(subClient, receivedMessages) { subClient.acknowledge({ subscription: formattedSubscription, - ackIds: [message.ackId], + ackIds: + typeof message.ackId === "string" + ? [message.ackId] + : /* c8 ignore next 4 */ + // When porting this code to TypeScript, the undefined/null case + // wasn't dealt with, so presumably our messages always have an + // ackId: + message.ackId, }); /* c8 ignore start */ } catch (error) { @@ -281,6 +329,16 @@ async function pullMessages() { sslCreds: grpc.credentials.createInsecure(), }; } + // These env vars are always set in tests: + /* c8 ignore next 8 */ + if (!projectId) { + throw new Error("Environment variable [$GCP_PUBSUB_PROJECT_ID] not set."); + } + if (!subscriptionName) { + throw new Error( + "Environment variable [$GCP_PUBSUB_SUBSCRIPTION_NAME] not set.", + ); + } const subClient = new pubsub.v1.SubscriberClient(options); @@ -297,14 +355,14 @@ async function pullMessages() { maxMessages, }); - return [subClient, response.receivedMessages]; + return [subClient, response.receivedMessages] as const; } async function init() { await initFluentBundles(); await initEmail(); const [subClient, receivedMessages] = await pullMessages(); - await poll(subClient, receivedMessages); + await poll(subClient, receivedMessages ?? []); } if (process.env.NODE_ENV !== "test") { @@ -331,3 +389,9 @@ if (process.env.NODE_ENV !== "test") { }); } /* c8 ignore stop */ + +function hasEmailAddressAttached( + row: SubscriberRow | (SubscriberRow & EmailAddressRow), +): row is SubscriberRow & EmailAddressRow { + return typeof (row as EmailAddressRow).subscriber_id !== "undefined"; +} diff --git a/src/utils/hibp.js b/src/utils/hibp.js index 4f7b7ce461e..a79a4d3de22 100644 --- a/src/utils/hibp.js +++ b/src/utils/hibp.js @@ -229,31 +229,24 @@ async function loadBreachesIntoApp(app) { /** * Get addresses and language from either subscribers or email_addresses fields: * - * @param {*} recipient + * @param {import('knex/types/tables').SubscriberRow | (import('knex/types/tables').SubscriberRow & import('knex/types/tables').EmailAddressRow)} recipient * @returns */ // TODO: Add unit test when changing this code: /* c8 ignore start */ function getAddressesAndLanguageForEmail(recipient) { - const { - all_emails_to_primary: allEmailsToPrimary, - email: breachedEmail, - primary_email: primaryEmail, - signup_language: signupLanguage - } = recipient - - if (breachedEmail) { + if (hasEmailAddressAttached(recipient)) { return { - breachedEmail, - recipientEmail: allEmailsToPrimary ? primaryEmail : breachedEmail, - signupLanguage + breachedEmail: recipient.email, + recipientEmail: recipient.all_emails_to_primary ? recipient.primary_email : recipient.email, + signupLanguage: recipient.signup_language, } } return { - breachedEmail: primaryEmail, - recipientEmail: primaryEmail, - signupLanguage + breachedEmail: recipient.primary_email, + recipientEmail: recipient.primary_email, + signupLanguage: recipient.signup_language, } } /* c8 ignore stop */ @@ -407,6 +400,14 @@ async function deleteSubscribedHash(sha1) { } /* c8 ignore stop */ +/** + * @param {import('knex/types/tables').SubscriberRow} subscriberRow + * @returns {subscriberRow is import('knex/types/tables').SubscriberRow & import('knex/types/tables').EmailAddressRow} + */ +function hasEmailAddressAttached(subscriberRow) { + return typeof (/** @type {import('knex/types/tables').SubscriberRow & import('knex/types/tables').EmailAddressRow} */ (subscriberRow)).email === "string"; +} + export { req, kAnonReq, From 946e41705db124b2ab33f5d96bdda46cf39a881e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:15:32 -0400 Subject: [PATCH 127/137] chore(deps): bump actions/checkout from 2 to 4 (#4840) Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kaitlyn Andres --- .github/workflows/production_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/production_deploy.yml b/.github/workflows/production_deploy.yml index fe87ef491c5..093a3ba512d 100644 --- a/.github/workflows/production_deploy.yml +++ b/.github/workflows/production_deploy.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Log in to Docker Hub uses: docker/login-action@v3 From 3d29d1de50acc06b4ccd56a45bc69429cb45e299 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Mon, 22 Jul 2024 16:36:51 +0200 Subject: [PATCH 128/137] chore: Add react-intersection-observer as dependency --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 93df26589f2..827d63181e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "react-aria": "^3.33.1", "react-cookie": "^7.1.0", "react-dom": "^18.2.0", + "react-intersection-observer": "^9.13.0", "react-stately": "^3.31.1", "server-only": "^0.0.1", "uuid": "^9.0.1", @@ -94,7 +95,6 @@ "lint-staged": "^15.2.5", "mjml-browser": "^4.15.3", "prettier": "3.3.2", - "react-intersection-observer": "^9.10.3", "sass": "^1.77.6", "storybook": "^8.1.11", "stylelint": "^16.7.0", @@ -24240,10 +24240,10 @@ "dev": true }, "node_modules/react-intersection-observer": { - "version": "9.10.3", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.10.3.tgz", - "integrity": "sha512-9NYfKwPZRovB6QJee7fDg0zz/SyYrqXtn5xTZU0vwLtLVBtfu9aZt1pVmr825REE49VPDZ7Lm5SNHjJBOTZHpA==", - "dev": true, + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.0.tgz", + "integrity": "sha512-y0UvBfjDiXqC8h0EWccyaj4dVBWMxgEx0t5RGNzQsvkfvZwugnKwxpu70StY4ivzYuMajavwUDjH4LJyIki9Lw==", + "license": "MIT", "peerDependencies": { "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/package.json b/package.json index d24420e07e9..03779eb5d35 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "react-aria": "^3.33.1", "react-cookie": "^7.1.0", "react-dom": "^18.2.0", + "react-intersection-observer": "^9.13.0", "react-stately": "^3.31.1", "server-only": "^0.0.1", "uuid": "^9.0.1", @@ -145,7 +146,6 @@ "lint-staged": "^15.2.5", "mjml-browser": "^4.15.3", "prettier": "3.3.2", - "react-intersection-observer": "^9.10.3", "sass": "^1.77.6", "storybook": "^8.1.11", "stylelint": "^16.7.0", From c247fcc23b366c2882661bb81f993324140a64e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 13:56:12 +0000 Subject: [PATCH 129/137] chore(deps-dev): bump the storybook group with 9 updates Bumps the storybook group with 9 updates: | Package | From | To | | --- | --- | --- | | [@storybook/addon-a11y](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/a11y) | `8.2.2` | `8.2.5` | | [@storybook/addon-actions](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/actions) | `8.2.2` | `8.2.5` | | [@storybook/addon-essentials](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/essentials) | `8.2.2` | `8.2.5` | | [@storybook/addon-interactions](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/interactions) | `8.2.2` | `8.2.5` | | [@storybook/addon-links](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/links) | `8.2.2` | `8.2.5` | | [@storybook/blocks](https://github.com/storybookjs/storybook/tree/HEAD/code/lib/blocks) | `8.2.2` | `8.2.5` | | [@storybook/nextjs](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/nextjs) | `8.2.2` | `8.2.5` | | [@storybook/react](https://github.com/storybookjs/storybook/tree/HEAD/code/renderers/react) | `8.2.2` | `8.2.5` | | [storybook](https://github.com/storybookjs/storybook/tree/HEAD/code/lib/cli) | `8.2.2` | `8.2.5` | Updates `@storybook/addon-a11y` from 8.2.2 to 8.2.5 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v8.2.5/code/addons/a11y) Updates `@storybook/addon-actions` from 8.2.2 to 8.2.5 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v8.2.5/code/addons/actions) Updates `@storybook/addon-essentials` from 8.2.2 to 8.2.5 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v8.2.5/code/addons/essentials) Updates `@storybook/addon-interactions` from 8.2.2 to 8.2.5 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v8.2.5/code/addons/interactions) Updates `@storybook/addon-links` from 8.2.2 to 8.2.5 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v8.2.5/code/addons/links) Updates `@storybook/blocks` from 8.2.2 to 8.2.5 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v8.2.5/code/lib/blocks) Updates `@storybook/nextjs` from 8.2.2 to 8.2.5 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v8.2.5/code/frameworks/nextjs) Updates `@storybook/react` from 8.2.2 to 8.2.5 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v8.2.5/code/renderers/react) Updates `storybook` from 8.2.2 to 8.2.5 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v8.2.5/code/lib/cli) --- updated-dependencies: - dependency-name: "@storybook/addon-a11y" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: storybook - dependency-name: "@storybook/addon-actions" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: storybook - dependency-name: "@storybook/addon-essentials" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: storybook - dependency-name: "@storybook/addon-interactions" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: storybook - dependency-name: "@storybook/addon-links" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: storybook - dependency-name: "@storybook/blocks" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: storybook - dependency-name: "@storybook/nextjs" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: storybook - dependency-name: "@storybook/react" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: storybook - dependency-name: storybook dependency-type: direct:development update-type: version-update:semver-patch dependency-group: storybook ... Signed-off-by: dependabot[bot] --- package-lock.json | 434 ++++++++++++++++++++++++++-------------------- package.json | 12 +- 2 files changed, 251 insertions(+), 195 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9f3780954f6..af1104b0a9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,13 +52,13 @@ "devDependencies": { "@faker-js/faker": "^8.4.1", "@playwright/test": "^1.43.1", - "@storybook/addon-a11y": "^8.2.2", - "@storybook/addon-actions": "^8.2.2", - "@storybook/addon-essentials": "^8.2.2", - "@storybook/addon-interactions": "^8.2.2", - "@storybook/addon-links": "^8.2.2", + "@storybook/addon-a11y": "^8.2.5", + "@storybook/addon-actions": "^8.2.5", + "@storybook/addon-essentials": "^8.2.5", + "@storybook/addon-interactions": "^8.2.5", + "@storybook/addon-links": "^8.2.5", "@storybook/blocks": "^8.0.0", - "@storybook/nextjs": "^8.2.2", + "@storybook/nextjs": "^8.2.5", "@storybook/react": "^8.0.0", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", @@ -8874,12 +8874,12 @@ } }, "node_modules/@storybook/addon-a11y": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-8.2.2.tgz", - "integrity": "sha512-wW7aU0L+TCi+mTxvS6zmfVVe81vLcjBVNbhjzFHdWVwYej8rguwVpKXo2/UhQyXb38xe6qYICB5HAJyUYOO5Hg==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-8.2.5.tgz", + "integrity": "sha512-sQxX/6mVSF15JnB/HEPrfY43Z6SvgyzAQYfMvqWS+sQBK04busLJBWElQRgekZo80sDCq52mq98lm+ZgpZs42A==", "dev": true, "dependencies": { - "@storybook/addon-highlight": "8.2.2", + "@storybook/addon-highlight": "8.2.5", "axe-core": "^4.2.0" }, "funding": { @@ -8887,13 +8887,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/addon-actions": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.2.2.tgz", - "integrity": "sha512-SN4cSRt3f0qXi5te+yhMseSdQuZntA8lGlASbRmN77YQTpIaGsNiH88xFoky0s9qz531hiRfU1R0ZSMylBwSKw==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.2.5.tgz", + "integrity": "sha512-8kevyvbvEdo0qn+hL/ub/RVsCGlWvCgL6ZAsZm50aWl1GXPpXj3nggKnyJwgAGUQzDC0CDPlNpSOP4ri3NY8tw==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -8907,13 +8907,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/addon-backgrounds": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.2.2.tgz", - "integrity": "sha512-m/xJe7uKL+kfJx7pQcHwAeIvJ3tdLIpDGrMAVDNDJHcAxfe44cFjIInaV/1HKf3y5Awap+DZFW66ekkxuI9zzA==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.2.5.tgz", + "integrity": "sha512-FZowGG+58qWHePjE6tGTTjT+m4/ZTI1uZD7GA++phdi5m8V/LGwJl8zBNZqUha/ygJ0i3Yvnj4tclWDSa8wtnQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -8925,13 +8925,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/addon-controls": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.2.2.tgz", - "integrity": "sha512-y241aOANGzT5XBADUIvALwG/xF5eC6UItzmWJaFvOzSBCq74GIA0+Hu9atyFdvFQbXOrdvPWC4jR+9iuBFRxAA==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.2.5.tgz", + "integrity": "sha512-8IoeEmiOyg5aTIyW4gdUUV/xJZk8y5bACkNhDTIepyfTZLoVNsVXS1tjqrG4EXQR6dNY4XV9dDIUIRI11/K6tQ==", "dev": true, "dependencies": { "dequal": "^2.0.2", @@ -8943,21 +8943,21 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/addon-docs": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.2.2.tgz", - "integrity": "sha512-qk/yjAR9RpsSrKLLbeCgb6u58c8TmYqyJSnXgbAozZZNKHBWlIpvZ/hTNYud8qo0coPlxnLdjnZf32TykWGlAg==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.2.5.tgz", + "integrity": "sha512-fJ2Aam6rQO5BVRIDrA4gVxxnVmMCkk4wC6RDi8oSTOcjM0FRl3ktv+6gPbNWq/+b8dqU23Y4wSyM4UUAIP0PAA==", "dev": true, "dependencies": { "@babel/core": "^7.24.4", "@mdx-js/react": "^3.0.0", - "@storybook/blocks": "8.2.2", - "@storybook/csf-plugin": "8.2.2", + "@storybook/blocks": "8.2.5", + "@storybook/csf-plugin": "8.2.5", "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "8.2.2", + "@storybook/react-dom-shim": "8.2.5", "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "fs-extra": "^11.1.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", @@ -8971,24 +8971,24 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/addon-essentials": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.2.2.tgz", - "integrity": "sha512-yN//BFMbSvNV0+Sll2hcKmgJX06TUKQDm6pZimUjkXczFtOmK7K/UdDmKjWS+qjhfJdWpxdRoEpxoHvvRmNfsA==", - "dev": true, - "dependencies": { - "@storybook/addon-actions": "8.2.2", - "@storybook/addon-backgrounds": "8.2.2", - "@storybook/addon-controls": "8.2.2", - "@storybook/addon-docs": "8.2.2", - "@storybook/addon-highlight": "8.2.2", - "@storybook/addon-measure": "8.2.2", - "@storybook/addon-outline": "8.2.2", - "@storybook/addon-toolbars": "8.2.2", - "@storybook/addon-viewport": "8.2.2", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.2.5.tgz", + "integrity": "sha512-SmA4QOiI9/d9bycagStm1gtlt2iR1EpWXJnhct4oqcj5nxmp+jviyhv1Pb+Rm/zKNE5qcaI4AcRDRVTJRUwESw==", + "dev": true, + "dependencies": { + "@storybook/addon-actions": "8.2.5", + "@storybook/addon-backgrounds": "8.2.5", + "@storybook/addon-controls": "8.2.5", + "@storybook/addon-docs": "8.2.5", + "@storybook/addon-highlight": "8.2.5", + "@storybook/addon-measure": "8.2.5", + "@storybook/addon-outline": "8.2.5", + "@storybook/addon-toolbars": "8.2.5", + "@storybook/addon-viewport": "8.2.5", "ts-dedent": "^2.0.0" }, "funding": { @@ -8996,13 +8996,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/addon-highlight": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.2.2.tgz", - "integrity": "sha512-yDTRzzL+IJAymgY32xoZl09BGBVmPOUV2wVNGYcZkkBLvz2GSQMTfUe1/7F4jAx//+rFBu48/MQzsTC7Bk8kPw==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.2.5.tgz", + "integrity": "sha512-fgzmilW3jGD68xYiR8sRjOB+joETc6/2+Fmj4S85BNnKy2ViXc1D+LHIw1id5/oT9MNN1i62D517EXRZkPakGw==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -9012,18 +9012,18 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/addon-interactions": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-8.2.2.tgz", - "integrity": "sha512-zRRuUwm/l41JtTUgjIoQTUgLT99Hsdz9cqKca/8NYo1MGBdEcKE41DH4aBIzKaOKFu7p9q00/o/X1EqYX4LMUA==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-8.2.5.tgz", + "integrity": "sha512-YVdpU/VRrRvX3BUiYxno6jDttbbQxngOkgcY8u+LLXbo3LfFLeXwpUmJXvGOrIU1wDHsZ4FAPBS/beFntcFhBw==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/instrumenter": "8.2.2", - "@storybook/test": "8.2.2", + "@storybook/instrumenter": "8.2.5", + "@storybook/test": "8.2.5", "polished": "^4.2.2", "ts-dedent": "^2.2.0" }, @@ -9032,13 +9032,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/addon-links": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.2.2.tgz", - "integrity": "sha512-eGh7O7SgTJMtnuXC0HlRPOegu1njcJS2cnVqjbzjvjxsPSBhbHpdYMi9Q9E7al/FKuqMUOjIR9YLIlmK1AJaqA==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.2.5.tgz", + "integrity": "sha512-jcD1/KXSqEE/QkQtBx3beD7UQJ3NQLEqYjCiF6UzsdFzcbfgAlfL5fKv8Lh/g8VynPcQzpfhMnSkj445EmF9hQ==", "dev": true, "dependencies": { "@storybook/csf": "0.1.11", @@ -9051,7 +9051,7 @@ }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.2" + "storybook": "^8.2.5" }, "peerDependenciesMeta": { "react": { @@ -9060,9 +9060,9 @@ } }, "node_modules/@storybook/addon-measure": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.2.2.tgz", - "integrity": "sha512-3rCo/aMltt5FrBVdr2dYlD8HlE2q9TLKGJZnwh9on4QyL6ArHbdYw0LmyHe/LrFahJ49w1XQZBMSJcAdRkkS7w==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.2.5.tgz", + "integrity": "sha512-rC/kukXM+3Sd1U5Wboozs82p3rLaHBRCmQfk3bFhWyGKvbYWEWmSr7w0DLH8/X7pi1u8IMSqeRll7tDJrEXeaw==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -9073,13 +9073,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/addon-outline": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.2.2.tgz", - "integrity": "sha512-Y+PQtfTNO8GLX5nz+3x5AMfHNvdGvBXazJ29+Rl1ygYN1+Q9ZhRJDE1kAK0wLxb7CG14peAgdYEaQb3Rduv7HQ==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.2.5.tgz", + "integrity": "sha512-YRS60tyZH79FfH9NEOndGVG/AmlXdYMd3nT2XfMBPZ+uTay/hcUzp6/wVsL+ucyVXi0avHbyNjY+iFP6m/MhRw==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -9090,26 +9090,26 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/addon-toolbars": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.2.2.tgz", - "integrity": "sha512-JGOueOc3EPljlCl9dVSQee0aMYoqGNvN0UH+R6wYJ3bDZ+tUG/iYpsZVPUOvS8vzp3Imk5Is1kzQbQYJtzdGLg==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.2.5.tgz", + "integrity": "sha512-XqjJxpXjTKurL81QF+Xa69J/8TSstXvLWVqeX+132C0//Yq3VeUir87hvI2qw/qbQT9sGeRX72vqKhoxS+kvIQ==", "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/addon-viewport": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.2.2.tgz", - "integrity": "sha512-gkZ8bsjGGP0NuevkT2iKC+szezSy+w4BrBDknf490mRU2K/B2e7TGojf/j/AtxzILMzD4IKzKUXbE/zwcqjZvA==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.2.5.tgz", + "integrity": "sha512-QH2A+rzoMf8dcREOUpAsx1vvP7w3MQ8HbZCawk7KdkW/KS0L8zhkHfsNL9cfLcgCJA0wtOmRPD25ZVGoxUAHSA==", "dev": true, "dependencies": { "memoizerific": "^1.11.3" @@ -9119,13 +9119,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/blocks": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.2.2.tgz", - "integrity": "sha512-av0Tryg4toDl2L/d1ABErtsAk9wvM1su6+M4wq5/Go50sk5IjGTldhbZFa9zNOohxLkZwaj0Q5xAgJ1Y+m5KrQ==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.2.5.tgz", + "integrity": "sha512-SJXo9NxgdXEeFA4OgfMaffjOxFBrMFIq/27F6/Z+JEs6lHZhayaBsofFcVbKEtivsg9+MbOLnN1TbwgnjjRw5g==", "dev": true, "dependencies": { "@storybook/csf": "0.1.11", @@ -9150,7 +9150,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.2" + "storybook": "^8.2.5" }, "peerDependenciesMeta": { "react": { @@ -9162,12 +9162,12 @@ } }, "node_modules/@storybook/builder-webpack5": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-8.2.2.tgz", - "integrity": "sha512-ud6a3pRusbC/TvT1ed15INxSivyL2y2zI61O/MWQZmM8sZOIC6ObdHLtzU4+535IIqiXhPoQ/QiOBbejqjgZvw==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-8.2.5.tgz", + "integrity": "sha512-qY/CuTYNPBYmeLihfawfe6O+6dDErhQ+xbwjxabt5Ba2QqUPhJZ0WEhWt0UidCb+G4ommLC4HOgri1pVHPObbw==", "dev": true, "dependencies": { - "@storybook/core-webpack": "8.2.2", + "@storybook/core-webpack": "8.2.5", "@types/node": "^18.0.0", "@types/semver": "^7.3.4", "browser-assert": "^1.2.1", @@ -9200,7 +9200,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" }, "peerDependenciesMeta": { "typescript": { @@ -9209,18 +9209,18 @@ } }, "node_modules/@storybook/builder-webpack5/node_modules/@types/node": { - "version": "18.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz", - "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==", + "version": "18.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.41.tgz", + "integrity": "sha512-LX84pRJ+evD2e2nrgYCHObGWkiQJ1mL+meAgbvnwk/US6vmMY7S2ygBTGV2Jw91s9vUsLSXeDEkUHZIJGLrhsg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@storybook/builder-webpack5/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -9236,15 +9236,15 @@ "dev": true }, "node_modules/@storybook/codemod": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-8.2.2.tgz", - "integrity": "sha512-wRUVKLHVUhbLJYKW3QOufUxJGwaUT4jTCD8+HOGpHPdJO3NrwXu186xt4tuPZO2Y/NnacPeCQPsaK5ok4O8o7A==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-8.2.5.tgz", + "integrity": "sha512-bUCvOqW3LUjz6epmTfocWBm0S7Ae52xmHvhVqgAUsKp9bVw2CGt9uaPR8dVE4IfI1yJZKRjf3u7Y60OTfWew4g==", "dev": true, "dependencies": { "@babel/core": "^7.24.4", "@babel/preset-env": "^7.24.4", "@babel/types": "^7.24.0", - "@storybook/core": "8.2.2", + "@storybook/core": "8.2.5", "@storybook/csf": "0.1.11", "@types/cross-spawn": "^6.0.2", "cross-spawn": "^7.0.3", @@ -9304,10 +9304,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@storybook/components": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.2.5.tgz", + "integrity": "sha512-/cqAzQ1w9tK44BvKDNkY3MxcqoDaMvZeI5c4rGh/nYMjulXV4cAOTSlVK07XfkOJENj/wHHSDz8tZTrl2FhmuQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.5" + } + }, "node_modules/@storybook/core": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.2.2.tgz", - "integrity": "sha512-L4ojYI+Os/i5bCReDIlFgEDQSS94mbJlNU9WRzEGZpqNC5/hbFEC9Tip7P1MiRx9NrewkzU7b+UCP7mi3e4drQ==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.2.5.tgz", + "integrity": "sha512-KjaeIkbdcog4Jmx3MoSjQZpfESin1qHEcFiLoOkICOpuKsj37xdMFcuSre8IbcVGCJPkt1RvEmfeu1N90jOgww==", "dev": true, "dependencies": { "@storybook/csf": "0.1.11", @@ -9328,9 +9341,9 @@ } }, "node_modules/@storybook/core-webpack": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.2.2.tgz", - "integrity": "sha512-M5wzgNbotVXcfo7WkXIuDxcBl7tTjnQ27lmlSBk+cu63pDvNn4UMDan621FcvxWq2DbjgIj+PASZ4DzM5O+ovA==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.2.5.tgz", + "integrity": "sha512-Kg1pgUavDM4wdKkvl6djNuyA9vhVaQcsU1nL8t14A9jjF8rqOqP40eunrR7oLf/QmvbWk35wThW3PJi0dvrBhg==", "dev": true, "dependencies": { "@types/node": "^18.0.0", @@ -9341,13 +9354,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/core-webpack/node_modules/@types/node": { - "version": "18.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz", - "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==", + "version": "18.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.41.tgz", + "integrity": "sha512-LX84pRJ+evD2e2nrgYCHObGWkiQJ1mL+meAgbvnwk/US6vmMY7S2ygBTGV2Jw91s9vUsLSXeDEkUHZIJGLrhsg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -9722,9 +9735,9 @@ } }, "node_modules/@storybook/core/node_modules/@types/node": { - "version": "18.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz", - "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==", + "version": "18.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.41.tgz", + "integrity": "sha512-LX84pRJ+evD2e2nrgYCHObGWkiQJ1mL+meAgbvnwk/US6vmMY7S2ygBTGV2Jw91s9vUsLSXeDEkUHZIJGLrhsg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -9778,9 +9791,9 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.2.2.tgz", - "integrity": "sha512-3K2RUpDDvq3DT46qAIj2VBC+fzTTebRUcZUsRfS6G1AzaX9p25iClEHiwcJacFkgQKhkci8A/Ly3Z4JJ3b4Pgw==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.2.5.tgz", + "integrity": "sha512-YpkvfDbKyilI54QMz/NyHGOlXxVeE+3LTKLx4GV/JrnGW+EtqQTYNaWWnTsesX0AsUICBAvxqyO9HtFtRjeL+Q==", "dev": true, "dependencies": { "unplugin": "^1.3.1" @@ -9790,7 +9803,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/global": { @@ -9813,9 +9826,9 @@ } }, "node_modules/@storybook/instrumenter": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.2.2.tgz", - "integrity": "sha512-refwnHqKHhya45MgqakhMG0jKhTiEIAl0aOwAaQy9+zf9ncMIYQAXRQsSZ2Z188lFWE24wbeHKteb62a5ZfWwQ==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.2.5.tgz", + "integrity": "sha512-HeETFUYYZDM3A76oO8p7V1nCrxdAglhO+3FtPa2EqSWueYISANyOOTu/8NIW3EbKP3GsfWi509ofQhsLBHy9dQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -9827,13 +9840,26 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" + } + }, + "node_modules/@storybook/manager-api": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.2.5.tgz", + "integrity": "sha512-4UHRlpcbYF2UiO9tonafnJMC2wJXWjTXivHjuf3ehbJXmopkNe/4zLtNTRyf3Hozf4CuYtNotw0tXflBrGlIlw==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.5" } }, "node_modules/@storybook/nextjs": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/nextjs/-/nextjs-8.2.2.tgz", - "integrity": "sha512-yjQwsbGsrwb8icl2aNqWN5BvbW7lx5niH3hVfdDxlhRFhTKaiLYo0gntJdr4MYBeejyxNFrqeYgQe3igkoOq+w==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/nextjs/-/nextjs-8.2.5.tgz", + "integrity": "sha512-Bnw1xAOdwMGyqx6N71gbaG66W8F5qn7tok0xP8C0nim6vVAcZvCSXbXcl9DV4tasxTPkzD7KdGS97gqk5cGPEg==", "dev": true, "dependencies": { "@babel/core": "^7.24.4", @@ -9850,10 +9876,10 @@ "@babel/preset-typescript": "^7.24.1", "@babel/runtime": "^7.24.4", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", - "@storybook/builder-webpack5": "8.2.2", - "@storybook/preset-react-webpack": "8.2.2", - "@storybook/react": "8.2.2", - "@storybook/test": "8.2.2", + "@storybook/builder-webpack5": "8.2.5", + "@storybook/preset-react-webpack": "8.2.5", + "@storybook/react": "8.2.5", + "@storybook/test": "8.2.5", "@types/node": "^18.0.0", "@types/semver": "^7.3.4", "babel-loader": "^9.1.3", @@ -9890,7 +9916,7 @@ "next": "^13.5.0 || ^14.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.2", + "storybook": "^8.2.5", "webpack": "^5.0.0" }, "peerDependenciesMeta": { @@ -9973,13 +9999,13 @@ "dev": true }, "node_modules/@storybook/preset-react-webpack": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-8.2.2.tgz", - "integrity": "sha512-GJkDtw4Ac8icD66fotGXYE3rmZkIwASpNLOeGzyP4eMMNaf5vlvTDxwkY551cGbnA5P7r4UkGjDiWinB9XE4VQ==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-8.2.5.tgz", + "integrity": "sha512-kKN+wXv9IdxtAmWLsCS621pHpCs4i+NIWJ7SZW5VfurQrkFme7+FxeXvph/ZKTi2Z2adU1b46Y/svPHBgMup2A==", "dev": true, "dependencies": { - "@storybook/core-webpack": "8.2.2", - "@storybook/react": "8.2.2", + "@storybook/core-webpack": "8.2.5", + "@storybook/react": "8.2.5", "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0", "@types/node": "^18.0.0", "@types/semver": "^7.3.4", @@ -10002,7 +10028,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.2" + "storybook": "^8.2.5" }, "peerDependenciesMeta": { "typescript": { @@ -10011,18 +10037,18 @@ } }, "node_modules/@storybook/preset-react-webpack/node_modules/@types/node": { - "version": "18.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz", - "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==", + "version": "18.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.41.tgz", + "integrity": "sha512-LX84pRJ+evD2e2nrgYCHObGWkiQJ1mL+meAgbvnwk/US6vmMY7S2ygBTGV2Jw91s9vUsLSXeDEkUHZIJGLrhsg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@storybook/preset-react-webpack/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -10031,14 +10057,31 @@ "node": ">=10" } }, + "node_modules/@storybook/preview-api": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.2.5.tgz", + "integrity": "sha512-C5A3MtubUM5Tq1An1gIqiEmiBX4ybaTzAeBuohsqToPmWHvM2uIdSl6XpTyQQJowkvrqBKjchqZUy/2mynX4lQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.5" + } + }, "node_modules/@storybook/react": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.2.2.tgz", - "integrity": "sha512-U4p/RV78yhjEwEzem8U7wE5/3sSpnqreGsPdAHMCIHd69e9tVeF0rwrTJGp917RClPjBKgEcfelCuvOlby4MrA==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.2.5.tgz", + "integrity": "sha512-Wgr7a8ZHSDIJyKNDEYdwwu+AEkaG1yM7UBBmROr8WrYHgKaC49ekEgY0i3bck6HArUvu3A6Z448mJTMY+XtK5Q==", "dev": true, "dependencies": { + "@storybook/components": "^8.2.5", "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "8.2.2", + "@storybook/manager-api": "^8.2.5", + "@storybook/preview-api": "^8.2.5", + "@storybook/react-dom-shim": "8.2.5", + "@storybook/theming": "^8.2.5", "@types/escodegen": "^0.0.6", "@types/estree": "^0.0.51", "@types/node": "^18.0.0", @@ -10065,7 +10108,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.2", + "storybook": "^8.2.5", "typescript": ">= 4.2.x" }, "peerDependenciesMeta": { @@ -10094,9 +10137,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.2.2.tgz", - "integrity": "sha512-4fb1/yT9WXHzHjs0In6orIEZxga5eXd9UaXEFGudBgowCjDUVP9LabDdKTbGusz20lfaAkATsRG/W+EcSLoh8w==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.2.5.tgz", + "integrity": "sha512-r+ZppgZR1AmM+2E9GRIaL/JjD3C/kl8sexD1mrGN4PBzrqqy6BNedHroWvf9JmfAvD/bp55peJ+LWAsSU/NvQQ==", "dev": true, "funding": { "type": "opencollective", @@ -10105,7 +10148,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/react/node_modules/@types/estree": { @@ -10157,13 +10200,13 @@ "dev": true }, "node_modules/@storybook/test": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.2.2.tgz", - "integrity": "sha512-X2qAKErjTh1X7XLAZqCMtU0ZK8JuwdKmgiqU0oXWxIDmCX6/Dm9ZIcdMZHs/S+K/UnIByjNlQpTShLVfRUeN1w==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.2.5.tgz", + "integrity": "sha512-8fo5qh3dNTlcUsnpYB5klcsnjIhEpkyVC+KCqapDI/iFD6qDmZXzbEcP/HsVMICwGTanr2kFCmf5c8kfAiOMew==", "dev": true, "dependencies": { "@storybook/csf": "0.1.11", - "@storybook/instrumenter": "8.2.2", + "@storybook/instrumenter": "8.2.5", "@testing-library/dom": "10.1.0", "@testing-library/jest-dom": "6.4.5", "@testing-library/user-event": "14.5.2", @@ -10176,7 +10219,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.2" + "storybook": "^8.2.5" } }, "node_modules/@storybook/test/node_modules/@testing-library/jest-dom": { @@ -10230,6 +10273,19 @@ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", "dev": true }, + "node_modules/@storybook/theming": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.2.5.tgz", + "integrity": "sha512-EEOSmW55MeLB3iskf5uUqffsqu003tTta8XQ1Xg8em3gePxPsjqzQtly1Ws5PtRg1Zvt1Zc6NKHwabiVzxothA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.5" + } + }, "node_modules/@stripe/stripe-js": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-4.0.0.tgz", @@ -10666,9 +10722,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.41", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", - "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", "dev": true, "dependencies": { "@types/node": "*", @@ -10938,9 +10994,9 @@ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/qs": { - "version": "6.9.10", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", - "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==", + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", "dev": true }, "node_modules/@types/range-parser": { @@ -11013,14 +11069,14 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", - "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dev": true, "dependencies": { "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/shimmer": { @@ -12142,6 +12198,18 @@ "node": "*" } }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -13927,9 +13995,9 @@ } }, "node_modules/css-loader/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -16520,9 +16588,9 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" }, "node_modules/flow-parser": { - "version": "0.239.1", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.239.1.tgz", - "integrity": "sha512-topOrETNxJ6T2gAnQiWqAlzGPj8uI2wtmNOlDIMNB+qyvGJZ6R++STbUOTAYmvPhOMz2gXnXPH0hOvURYmrBow==", + "version": "0.241.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.241.0.tgz", + "integrity": "sha512-82yKXpz7iWknWFsognZUf5a6mBQLnVrYoYSU9Nbu7FTOpKlu3v9ehpiI9mYXuaIO3J0ojX1b83M/InXvld9HUw==", "dev": true, "engines": { "node": ">=0.4.0" @@ -16626,9 +16694,9 @@ } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -24326,9 +24394,9 @@ } }, "node_modules/recast": { - "version": "0.23.6", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.6.tgz", - "integrity": "sha512-9FHoNjX1yjuesMwuthAmPKabxYQdOgihFYmT5ebXfYGBcnqXZf3WOVz+5foEZ8Y83P4ZY6yQD5GMmtV+pgCCAQ==", + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.9.tgz", + "integrity": "sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==", "dev": true, "dependencies": { "ast-types": "^0.16.1", @@ -24341,18 +24409,6 @@ "node": ">= 4" } }, - "node_modules/recast/node_modules/ast-types": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", - "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/recast/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -25445,15 +25501,15 @@ } }, "node_modules/storybook": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.2.2.tgz", - "integrity": "sha512-xDT9gyzAEFQNeK7P+Mj/8bNzN+fbm6/4D6ihdSzmczayjydpNjMs74HDHMY6S4Bfu6tRVyEK2ALPGnr6ZVofBA==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.2.5.tgz", + "integrity": "sha512-nfcly5CY3D6KuHbsfhScPaGeraRA9EJhO9GF00/dnI0GXW4ILS8Kwket515IkKAuKcdjdZis6maEuosbG//Kbg==", "dev": true, "dependencies": { "@babel/core": "^7.24.4", "@babel/types": "^7.24.0", - "@storybook/codemod": "8.2.2", - "@storybook/core": "8.2.2", + "@storybook/codemod": "8.2.5", + "@storybook/core": "8.2.5", "@types/semver": "^7.3.4", "@yarnpkg/fslib": "2.10.3", "@yarnpkg/libzip": "2.3.0", diff --git a/package.json b/package.json index 843ba663eaa..da147366448 100644 --- a/package.json +++ b/package.json @@ -104,13 +104,13 @@ "devDependencies": { "@faker-js/faker": "^8.4.1", "@playwright/test": "^1.43.1", - "@storybook/addon-a11y": "^8.2.2", - "@storybook/addon-actions": "^8.2.2", - "@storybook/addon-essentials": "^8.2.2", - "@storybook/addon-interactions": "^8.2.2", - "@storybook/addon-links": "^8.2.2", + "@storybook/addon-a11y": "^8.2.5", + "@storybook/addon-actions": "^8.2.5", + "@storybook/addon-essentials": "^8.2.5", + "@storybook/addon-interactions": "^8.2.5", + "@storybook/addon-links": "^8.2.5", "@storybook/blocks": "^8.0.0", - "@storybook/nextjs": "^8.2.2", + "@storybook/nextjs": "^8.2.5", "@storybook/react": "^8.0.0", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", From 6618a558dc6acc841886a6187cc795998b4f7eae Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Mon, 22 Jul 2024 16:43:06 +0200 Subject: [PATCH 130/137] chore: Move type assertion to useTelemetryView hook --- src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx | 2 +- src/app/hooks/useViewTelemetry.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx index 7f715142692..0251217dd56 100644 --- a/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx @@ -82,7 +82,7 @@ export const FreeScanCta = ( ) : (
} + buttonRef={refViewTelemetry as RefObject} variant="primary" event={{ module: "ctaButton", diff --git a/src/app/hooks/useViewTelemetry.ts b/src/app/hooks/useViewTelemetry.ts index b7ff9e720fb..5bafa869f1c 100644 --- a/src/app/hooks/useViewTelemetry.ts +++ b/src/app/hooks/useViewTelemetry.ts @@ -5,6 +5,7 @@ import { IntersectionOptions, useInView } from "react-intersection-observer"; import { TelemetryArgs, useTelemetry } from "./useTelemetry"; import { GleanMetricMap } from "../../telemetry/generated/_map"; +import { RefObject } from "react"; export function useViewTelemetry< EventModule extends keyof Pick, @@ -34,5 +35,5 @@ export function useViewTelemetry< }, }); - return ref; + return ref as unknown as RefObject; } From 017c203fa627046937fa5c1106e860fc7dbaaeaa Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Mon, 22 Jul 2024 17:13:40 +0200 Subject: [PATCH 131/137] fix: HTMLFormElement type --- src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx index 9e40827f2de..83051921292 100644 --- a/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/SignUpForm.tsx @@ -4,7 +4,7 @@ "use client"; -import { FormEventHandler, useId, useState } from "react"; +import { FormEventHandler, RefObject, useId, useState } from "react"; import { signIn } from "next-auth/react"; import { useL10n } from "../../../hooks/l10n"; import { Button } from "../../../components/client/Button"; @@ -66,7 +66,11 @@ export const SignUpForm = (props: Props) => { return props.scanLimitReached ? ( ) : ( -
+ } + className={styles.form} + onSubmit={onSubmit} + > Date: Mon, 22 Jul 2024 14:41:58 +0000 Subject: [PATCH 132/137] chore(deps-dev): bump lint-staged from 15.2.5 to 15.2.7 Bumps [lint-staged](https://github.com/okonet/lint-staged) from 15.2.5 to 15.2.7. - [Release notes](https://github.com/okonet/lint-staged/releases) - [Changelog](https://github.com/lint-staged/lint-staged/blob/master/CHANGELOG.md) - [Commits](https://github.com/okonet/lint-staged/compare/v15.2.5...v15.2.7) --- updated-dependencies: - dependency-name: lint-staged dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index af1104b0a9a..e779016121e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,7 +91,7 @@ "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^29.7.0", "jest-fail-on-console": "^3.3.0", - "lint-staged": "^15.2.5", + "lint-staged": "^15.2.7", "mjml-browser": "^4.15.3", "prettier": "3.3.3", "react-intersection-observer": "^9.10.3", @@ -20618,9 +20618,9 @@ "dev": true }, "node_modules/lint-staged": { - "version": "15.2.5", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.5.tgz", - "integrity": "sha512-j+DfX7W9YUvdzEZl3Rk47FhDF6xwDBV5wwsCPw6BwWZVPYJemusQmvb9bRsW23Sqsaa+vRloAWogbK4BUuU2zA==", + "version": "15.2.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.7.tgz", + "integrity": "sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw==", "dev": true, "dependencies": { "chalk": "~5.3.0", diff --git a/package.json b/package.json index da147366448..c30e71b0252 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^29.7.0", "jest-fail-on-console": "^3.3.0", - "lint-staged": "^15.2.5", + "lint-staged": "^15.2.7", "mjml-browser": "^4.15.3", "prettier": "3.3.3", "react-intersection-observer": "^9.10.3", From 3f6f51a48dbe398b7e6e9e4ea0b1c3eac1d9b0ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 15:33:26 +0000 Subject: [PATCH 133/137] chore(deps): bump @aws-sdk/lib-storage in the aws-sdk group Bumps the aws-sdk group with 1 update: [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage). Updates `@aws-sdk/lib-storage` from 3.614.0 to 3.616.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.616.0/lib/lib-storage) --- updated-dependencies: - dependency-name: "@aws-sdk/lib-storage" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: aws-sdk ... Signed-off-by: dependabot[bot] --- package-lock.json | 320 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 162 insertions(+), 160 deletions(-) diff --git a/package-lock.json b/package-lock.json index e779016121e..0fd204601df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MPL-2.0", "dependencies": { "@aws-sdk/client-s3": "^3.614.0", - "@aws-sdk/lib-storage": "^3.614.0", + "@aws-sdk/lib-storage": "^3.616.0", "@fluent/bundle": "^0.18.0", "@fluent/langneg": "^0.7.0", "@fluent/react": "^0.15.2", @@ -322,65 +322,65 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.614.0.tgz", - "integrity": "sha512-9BlhfeBegvyjOqHtcr9kvrT80wiy7EVUiqYyTFiiDv/hJIcG88XHQCZdLU7658XBkQ7aFrr5b8rF2HRD1oroxw==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.616.0.tgz", + "integrity": "sha512-6gyZnBlAgOU8cwNqPhFO9s6maGI4/iHV3cmqvQgUn3uqhi1EgTSZSsTMuRzKEgBpTGgC+9Bm6djKqOderMqjdA==", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.614.0", - "@aws-sdk/client-sts": "3.614.0", - "@aws-sdk/core": "3.614.0", - "@aws-sdk/credential-provider-node": "3.614.0", - "@aws-sdk/middleware-bucket-endpoint": "3.614.0", - "@aws-sdk/middleware-expect-continue": "3.609.0", - "@aws-sdk/middleware-flexible-checksums": "3.614.0", - "@aws-sdk/middleware-host-header": "3.609.0", + "@aws-sdk/client-sso-oidc": "3.616.0", + "@aws-sdk/client-sts": "3.616.0", + "@aws-sdk/core": "3.616.0", + "@aws-sdk/credential-provider-node": "3.616.0", + "@aws-sdk/middleware-bucket-endpoint": "3.616.0", + "@aws-sdk/middleware-expect-continue": "3.616.0", + "@aws-sdk/middleware-flexible-checksums": "3.616.0", + "@aws-sdk/middleware-host-header": "3.616.0", "@aws-sdk/middleware-location-constraint": "3.609.0", "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.609.0", - "@aws-sdk/middleware-sdk-s3": "3.614.0", - "@aws-sdk/middleware-signing": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.616.0", + "@aws-sdk/middleware-sdk-s3": "3.616.0", + "@aws-sdk/middleware-signing": "3.616.0", "@aws-sdk/middleware-ssec": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.614.0", + "@aws-sdk/middleware-user-agent": "3.616.0", "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/signature-v4-multi-region": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.616.0", "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@aws-sdk/xml-builder": "3.609.0", "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.2.6", + "@smithy/core": "^2.2.7", "@smithy/eventstream-serde-browser": "^3.0.4", "@smithy/eventstream-serde-config-resolver": "^3.0.3", "@smithy/eventstream-serde-node": "^3.0.4", - "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/fetch-http-handler": "^3.2.2", "@smithy/hash-blob-browser": "^3.1.2", "@smithy/hash-node": "^3.0.3", "@smithy/hash-stream-node": "^3.1.2", "@smithy/invalid-dependency": "^3.0.3", "@smithy/md5-js": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.4", "@smithy/middleware-endpoint": "^3.0.5", - "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-retry": "^3.0.10", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.2", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/node-http-handler": "^3.1.3", + "@smithy/protocol-http": "^4.0.4", + "@smithy/smithy-client": "^3.1.8", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.9", - "@smithy/util-defaults-mode-node": "^3.0.9", + "@smithy/util-defaults-mode-browser": "^3.0.10", + "@smithy/util-defaults-mode-node": "^3.0.10", "@smithy/util-endpoints": "^2.0.5", "@smithy/util-retry": "^3.0.3", - "@smithy/util-stream": "^3.0.6", + "@smithy/util-stream": "^3.1.0", "@smithy/util-utf8": "^3.0.0", "@smithy/util-waiter": "^3.1.2", "tslib": "^2.6.2" @@ -390,43 +390,43 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.614.0.tgz", - "integrity": "sha512-p5pyYaxRzBttjBkqfc8i3K7DzBdTg3ECdVgBo6INIUxfvDy0J8QUE8vNtCgvFIkq+uPw/8M+Eo4zzln7anuO0Q==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.616.0.tgz", + "integrity": "sha512-hwW0u1f8U4dSloAe61/eupUiGd5Q13B72BuzGxvRk0cIpYX/2m0KBG8DDl7jW1b2QQ+CflTLpG2XUf2+vRJxGA==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.614.0", - "@aws-sdk/middleware-host-header": "3.609.0", + "@aws-sdk/core": "3.616.0", + "@aws-sdk/middleware-host-header": "3.616.0", "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.614.0", + "@aws-sdk/middleware-recursion-detection": "3.616.0", + "@aws-sdk/middleware-user-agent": "3.616.0", "@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.2.6", - "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/core": "^2.2.7", + "@smithy/fetch-http-handler": "^3.2.2", "@smithy/hash-node": "^3.0.3", "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.4", "@smithy/middleware-endpoint": "^3.0.5", - "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-retry": "^3.0.10", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.2", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/node-http-handler": "^3.1.3", + "@smithy/protocol-http": "^4.0.4", + "@smithy/smithy-client": "^3.1.8", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.9", - "@smithy/util-defaults-mode-node": "^3.0.9", + "@smithy/util-defaults-mode-browser": "^3.0.10", + "@smithy/util-defaults-mode-node": "^3.0.10", "@smithy/util-endpoints": "^2.0.5", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", @@ -438,44 +438,44 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.614.0.tgz", - "integrity": "sha512-BI1NWcpppbHg/28zbUg54dZeckork8BItZIcjls12vxasy+p3iEzrJVG60jcbUTTsk3Qc1tyxNfrdcVqx0y7Ww==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.616.0.tgz", + "integrity": "sha512-YY1hpYS/G1uRGjQf88dL8VLHkP/IjGxKeXdhy+JnzMdCkAWl3V9j0fEALw40NZe0x79gr6R2KUOUH/IKYQfUmg==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.614.0", - "@aws-sdk/credential-provider-node": "3.614.0", - "@aws-sdk/middleware-host-header": "3.609.0", + "@aws-sdk/core": "3.616.0", + "@aws-sdk/credential-provider-node": "3.616.0", + "@aws-sdk/middleware-host-header": "3.616.0", "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.614.0", + "@aws-sdk/middleware-recursion-detection": "3.616.0", + "@aws-sdk/middleware-user-agent": "3.616.0", "@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.2.6", - "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/core": "^2.2.7", + "@smithy/fetch-http-handler": "^3.2.2", "@smithy/hash-node": "^3.0.3", "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.4", "@smithy/middleware-endpoint": "^3.0.5", - "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-retry": "^3.0.10", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.2", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/node-http-handler": "^3.1.3", + "@smithy/protocol-http": "^4.0.4", + "@smithy/smithy-client": "^3.1.8", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.9", - "@smithy/util-defaults-mode-node": "^3.0.9", + "@smithy/util-defaults-mode-browser": "^3.0.10", + "@smithy/util-defaults-mode-node": "^3.0.10", "@smithy/util-endpoints": "^2.0.5", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", @@ -486,49 +486,49 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.614.0" + "@aws-sdk/client-sts": "^3.616.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.614.0.tgz", - "integrity": "sha512-i6QmaVA1KHHYNnI2VYQy/sc31rLm4+jSp8b/YbQpFnD0w3aXsrEEHHlxek45uSkHb4Nrj1omFBVy/xp1WVYx2Q==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.616.0.tgz", + "integrity": "sha512-FP7i7hS5FpReqnysQP1ukQF1OUWy8lkomaOnbu15H415YUrfCp947SIx6+BItjmx+esKxPkEjh/fbCVzw2D6hQ==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.614.0", - "@aws-sdk/core": "3.614.0", - "@aws-sdk/credential-provider-node": "3.614.0", - "@aws-sdk/middleware-host-header": "3.609.0", + "@aws-sdk/client-sso-oidc": "3.616.0", + "@aws-sdk/core": "3.616.0", + "@aws-sdk/credential-provider-node": "3.616.0", + "@aws-sdk/middleware-host-header": "3.616.0", "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.614.0", + "@aws-sdk/middleware-recursion-detection": "3.616.0", + "@aws-sdk/middleware-user-agent": "3.616.0", "@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.2.6", - "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/core": "^2.2.7", + "@smithy/fetch-http-handler": "^3.2.2", "@smithy/hash-node": "^3.0.3", "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.4", "@smithy/middleware-endpoint": "^3.0.5", - "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-retry": "^3.0.10", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.2", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/node-http-handler": "^3.1.3", + "@smithy/protocol-http": "^4.0.4", + "@smithy/smithy-client": "^3.1.8", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.9", - "@smithy/util-defaults-mode-node": "^3.0.9", + "@smithy/util-defaults-mode-browser": "^3.0.10", + "@smithy/util-defaults-mode-node": "^3.0.10", "@smithy/util-endpoints": "^2.0.5", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", @@ -540,14 +540,14 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.614.0.tgz", - "integrity": "sha512-BUuS5/1YkgmKc4J0bg83XEtMyDHVyqG2QDzfmhYe8gbOIZabUl1FlrFVwhCAthtrrI6MPGTQcERB4BtJKUSplw==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.616.0.tgz", + "integrity": "sha512-O/urkh2kECs/IqZIVZxyeyHZ7OR2ZWhLNK7btsVQBQvJKrEspLrk/Fp20Qfg5JDerQfBN83ZbyRXLJOOucdZpw==", "dependencies": { - "@smithy/core": "^2.2.6", - "@smithy/protocol-http": "^4.0.3", - "@smithy/signature-v4": "^3.1.2", - "@smithy/smithy-client": "^3.1.7", + "@smithy/core": "^2.2.7", + "@smithy/protocol-http": "^4.0.4", + "@smithy/signature-v4": "^4.0.0", + "@smithy/smithy-client": "^3.1.8", "@smithy/types": "^3.3.0", "fast-xml-parser": "4.2.5", "tslib": "^2.6.2" @@ -571,18 +571,18 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.614.0.tgz", - "integrity": "sha512-YIEjlNUKb3Vo/iTnGAPdsiDC3FUUnNoex2OwU8LmR7AkYZiWdB8nx99DfgkkY+OFMUpw7nKD2PCOtuFONelfGA==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.616.0.tgz", + "integrity": "sha512-1rgCkr7XvEMBl7qWCo5BKu3yAxJs71dRaZ55Xnjte/0ZHH6Oc93ZrHzyYy6UH6t0nZrH+FAuw7Yko2YtDDwDeg==", "dependencies": { "@aws-sdk/types": "3.609.0", - "@smithy/fetch-http-handler": "^3.2.1", - "@smithy/node-http-handler": "^3.1.2", + "@smithy/fetch-http-handler": "^3.2.2", + "@smithy/node-http-handler": "^3.1.3", "@smithy/property-provider": "^3.1.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/protocol-http": "^4.0.4", + "@smithy/smithy-client": "^3.1.8", "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.0.6", + "@smithy/util-stream": "^3.1.0", "tslib": "^2.6.2" }, "engines": { @@ -590,14 +590,14 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.614.0.tgz", - "integrity": "sha512-KfLuLFGwlvFSZ2MuzYwWGPb1y5TeiwX5okIDe0aQ1h10oD3924FXbN+mabOnUHQ8EFcGAtCaWbrC86mI7ktC6A==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.616.0.tgz", + "integrity": "sha512-5gQdMr9cca3xV7FF2SxpxWGH2t6+t4o+XBGiwsHm8muEjf4nUmw7Ij863x25Tjt2viPYV0UStczSb5Sihp7bkA==", "dependencies": { "@aws-sdk/credential-provider-env": "3.609.0", - "@aws-sdk/credential-provider-http": "3.614.0", + "@aws-sdk/credential-provider-http": "3.616.0", "@aws-sdk/credential-provider-process": "3.614.0", - "@aws-sdk/credential-provider-sso": "3.614.0", + "@aws-sdk/credential-provider-sso": "3.616.0", "@aws-sdk/credential-provider-web-identity": "3.609.0", "@aws-sdk/types": "3.609.0", "@smithy/credential-provider-imds": "^3.1.4", @@ -610,19 +610,19 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.614.0" + "@aws-sdk/client-sts": "^3.616.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.614.0.tgz", - "integrity": "sha512-4J6gPEuFZP0mkWq5E//oMS1vrmMM88iNNcv7TEljYnsc6JTAlKejCyFwx6CN+nkIhmIZsl06SXIhBemzBdBPfg==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.616.0.tgz", + "integrity": "sha512-Se+u6DAxjDPjKE3vX1X2uxjkWgGq69BTo0uTB0vDUiWwBVgh16s9BsBhSAlKEH1CCbbJHvOg4YdTrzjwzqyClg==", "dependencies": { "@aws-sdk/credential-provider-env": "3.609.0", - "@aws-sdk/credential-provider-http": "3.614.0", - "@aws-sdk/credential-provider-ini": "3.614.0", + "@aws-sdk/credential-provider-http": "3.616.0", + "@aws-sdk/credential-provider-ini": "3.616.0", "@aws-sdk/credential-provider-process": "3.614.0", - "@aws-sdk/credential-provider-sso": "3.614.0", + "@aws-sdk/credential-provider-sso": "3.616.0", "@aws-sdk/credential-provider-web-identity": "3.609.0", "@aws-sdk/types": "3.609.0", "@smithy/credential-provider-imds": "^3.1.4", @@ -651,11 +651,11 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.614.0.tgz", - "integrity": "sha512-55+gp0JY4451cWI1qXmVMFM0GQaBKiQpXv2P0xmd9P3qLDyeFUSEW8XPh0d2lb1ICr6x4s47ynXVdGCIv2mXMg==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.616.0.tgz", + "integrity": "sha512-3rsWs9GBi8Z8Gps5ROwqguxtw+J6OIg1vawZMLRNMqqZoBvbOToe9wEnpid8ylU+27+oG8uibJNlNuRyXApUjw==", "dependencies": { - "@aws-sdk/client-sso": "3.614.0", + "@aws-sdk/client-sso": "3.616.0", "@aws-sdk/token-providers": "3.614.0", "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -685,13 +685,13 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.614.0.tgz", - "integrity": "sha512-Bzni+r7pS+nRiqcmWPpB/OiQEM4GszGRp1DXpL3rKnwaeu+Qgf2w12DULxWUacIvOc4IzLsv6tpEidQ/P1zKQg==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.616.0.tgz", + "integrity": "sha512-VhuJGCbJvI1TGGWDd5pdnCa+pobKAGv5/H4MqN1GlyRuWd6Biwby+pRaFb2nL4GT3bOJdCcM24CyZXO2PefPaw==", "dependencies": { "@smithy/abort-controller": "^3.1.1", "@smithy/middleware-endpoint": "^3.0.5", - "@smithy/smithy-client": "^3.1.7", + "@smithy/smithy-client": "^3.1.8", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", @@ -701,18 +701,18 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-s3": "^3.614.0" + "@aws-sdk/client-s3": "^3.616.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.614.0.tgz", - "integrity": "sha512-TqEY8KcZeZ0LIxXaqG9RSSNnDHvD8RAFP4Xenwsxqnyad0Yn7LgCoFwRByelJ0t54ROYL1/ETJleWE4U4TOXdg==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.616.0.tgz", + "integrity": "sha512-KZv78s8UE7+s3qZCfG3pE9U9XV5WTP478aNWis5gDXmsb5LF7QWzEeR8kve5dnjNlK6qVQ33v+mUZa6lR5PwMw==", "dependencies": { "@aws-sdk/types": "3.609.0", "@aws-sdk/util-arn-parser": "3.568.0", "@smithy/node-config-provider": "^3.1.4", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.0.4", "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", "tslib": "^2.6.2" @@ -722,12 +722,12 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.609.0.tgz", - "integrity": "sha512-+zeg//mSer4JZRxOB/4mUOMUJyuYPwATnIC5moBB8P8Xe+mJaVRFy8qlCtzYNj2TycnlsBPzTK0j7P1yvDh97w==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.616.0.tgz", + "integrity": "sha512-IM1pfJPm7pDUXa55js9bnGjS8o3ldzDwf95mL9ZAYdEsm10q6i0ZxZbbro2gTq25Ap5ykdeeS34lOSzIqPiW1A==", "dependencies": { "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.0.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -736,15 +736,15 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.614.0.tgz", - "integrity": "sha512-ZLpxVXMboDeMT7p2Kdp5m1uLVKOktkZoMgLvvbe3zbrU4Ji5IU5xVE0aa4X7H28BtuODCs6SLESnPs19bhMKlA==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.616.0.tgz", + "integrity": "sha512-Mrco/dURoTXVqwcnYRcyrFaPTIg36ifg2PK0kUYfSVTGEOClZOQXlVG5zYCZoo3yEMgy/gLT907FjUQxwoifIw==", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-sdk/types": "3.609.0", "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.0.4", "@smithy/types": "^3.3.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -754,12 +754,12 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.609.0.tgz", - "integrity": "sha512-iTKfo158lc4jLDfYeZmYMIBHsn8m6zX+XB6birCSNZ/rrlzAkPbGE43CNdKfvjyWdqgLMRXF+B+OcZRvqhMXPQ==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.616.0.tgz", + "integrity": "sha512-mhNfHuGhCDZwYCABebaOvTgOM44UCZZRq2cBpgPZLVKP0ydAv5aFHXv01goexxXHqgHoEGx0uXWxlw0s2EpFDg==", "dependencies": { "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.0.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -794,12 +794,12 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.609.0.tgz", - "integrity": "sha512-6sewsYB7/o/nbUfA99Aa/LokM+a/u4Wpm/X2o0RxOsDtSB795ObebLJe2BxY5UssbGaWkn7LswyfvrdZNXNj1w==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.616.0.tgz", + "integrity": "sha512-LQKAcrZRrR9EGez4fdCIVjdn0Ot2HMN12ChnoMGEU6oIxnQ2aSC7iASFFCV39IYfeMh7iSCPj7Wopqw8rAouzg==", "dependencies": { "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.0.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -808,18 +808,20 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.614.0.tgz", - "integrity": "sha512-9fJTaiuuOfFV4FqmUEhPYzrtv7JOfYpB7q65oG3uayVH4ngWHIJkjnnX79zRhNZKdPGta+XIsnZzjEghg82ngA==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.616.0.tgz", + "integrity": "sha512-heq9pzn0NRX6VL/oMlmwZdgcCh5eJUDscvwMl/oGev0tNdTpB2oGU+wPaNMka7IrW3eBPn7APmY9fdS1kBaBoQ==", "dependencies": { "@aws-sdk/types": "3.609.0", "@aws-sdk/util-arn-parser": "3.568.0", "@smithy/node-config-provider": "^3.1.4", - "@smithy/protocol-http": "^4.0.3", - "@smithy/signature-v4": "^3.1.2", - "@smithy/smithy-client": "^3.1.7", + "@smithy/protocol-http": "^4.0.4", + "@smithy/signature-v4": "^4.0.0", + "@smithy/smithy-client": "^3.1.8", "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-stream": "^3.1.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { @@ -827,14 +829,14 @@ } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.609.0.tgz", - "integrity": "sha512-2w3dBLjQVKIajYzokO4hduq8/0hSMUYHHmIo1Kdl+MSY8uwRBt12bLL6pyreobTcRMxizvn2ph/CQ9I1ST/WGQ==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.616.0.tgz", + "integrity": "sha512-wwzZFlXyURwo40oz1NmufreQa5DqwnCF8hR8tIP5+oKCyhbkmlmv8xG4Wn51bzY2WEbQhvFebgZSFTEvelCoCg==", "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/signature-v4": "^3.1.2", + "@smithy/protocol-http": "^4.0.4", + "@smithy/signature-v4": "^4.0.0", "@smithy/types": "^3.3.0", "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" @@ -857,13 +859,13 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.614.0.tgz", - "integrity": "sha512-xUxh0UPQiMTG6E31Yvu6zVYlikrIcFDKljM11CaatInzvZubGTGiX0DjpqRlfGzUNsuPc/zNrKwRP2+wypgqIw==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.616.0.tgz", + "integrity": "sha512-iMcAb4E+Z3vuEcrDsG6T2OBNiqWAquwahP9qepHqfmnmJqHr1mSHtXDYTGBNid31+621sUQmneUQ+fagpGAe4w==", "dependencies": { "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.614.0", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.0.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -888,14 +890,14 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.614.0.tgz", - "integrity": "sha512-6mW3ONW4oLzxrePznYhz7sNT9ji9Am9ufLeV722tbOVS5lArBOZ6E1oPz0uYBhisUPznWKhcLRMggt7vIJWMng==", + "version": "3.616.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.616.0.tgz", + "integrity": "sha512-WQn43cfnwbG6jnPjh/SyujDQVqbRjGFY9tGI/tqtUvvGwoDhI343TSDCA7fvs0FEC6Za6vgOBq1CwPv3CFyfhA==", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.614.0", + "@aws-sdk/middleware-sdk-s3": "3.616.0", "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.0.3", - "@smithy/signature-v4": "^3.1.2", + "@smithy/protocol-http": "^4.0.4", + "@smithy/signature-v4": "^4.0.0", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -8630,9 +8632,9 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-3.1.2.tgz", - "integrity": "sha512-3BcPylEsYtD0esM4Hoyml/+s7WP2LFhcM3J2AGdcL2vx9O60TtfpDOL72gjb4lU8NeRPeKAwR77YNyyGvMbuEA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.0.0.tgz", + "integrity": "sha512-ervYjQ+ZvmNG51Ui77IOTPri7nOyo8Kembzt9uwwlmtXJPmFXvslOahbA1blvAVs7G0KlYMiOBog1rAt7RVXxg==", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "@smithy/types": "^3.3.0", diff --git a/package.json b/package.json index c30e71b0252..d03150fccac 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.614.0", - "@aws-sdk/lib-storage": "^3.614.0", + "@aws-sdk/lib-storage": "^3.616.0", "@fluent/bundle": "^0.18.0", "@fluent/langneg": "^0.7.0", "@fluent/react": "^0.15.2", From 252178516c6a83d30c7ad06a92d463ad10aed823 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Mon, 22 Jul 2024 17:38:00 +0200 Subject: [PATCH 134/137] fix: Unit tests --- package-lock.json | 25 +++++++++++++------------ package.json | 24 +++++++++++------------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19c16beeabd..6ee0d3399a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,6 @@ "react-aria": "^3.33.1", "react-cookie": "^7.1.0", "react-dom": "^18.2.0", - "react-intersection-observer": "^9.13.0", "react-stately": "^3.31.1", "server-only": "^0.0.1", "uuid": "^9.0.1", @@ -53,13 +52,13 @@ "devDependencies": { "@faker-js/faker": "^8.4.1", "@playwright/test": "^1.43.1", - "@storybook/addon-a11y": "^8.2.5", - "@storybook/addon-actions": "^8.2.5", - "@storybook/addon-essentials": "^8.2.5", - "@storybook/addon-interactions": "^8.2.5", - "@storybook/addon-links": "^8.2.5", + "@storybook/addon-a11y": "^8.2.2", + "@storybook/addon-actions": "^8.2.2", + "@storybook/addon-essentials": "^8.2.2", + "@storybook/addon-interactions": "^8.2.2", + "@storybook/addon-links": "^8.2.2", "@storybook/blocks": "^8.0.0", - "@storybook/nextjs": "^8.2.5", + "@storybook/nextjs": "^8.2.2", "@storybook/react": "^8.0.0", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", @@ -94,7 +93,7 @@ "jest-fail-on-console": "^3.3.0", "lint-staged": "^15.2.5", "mjml-browser": "^4.15.3", - "prettier": "3.3.3", + "prettier": "3.3.2", "react-intersection-observer": "^9.10.3", "sass": "^1.77.6", "storybook": "^8.1.11", @@ -106,7 +105,7 @@ "yaml": "^2.4.5" }, "engines": { - "node": "22.4.x", + "node": "22.3.x", "npm": "10.8.x" } }, @@ -23834,10 +23833,11 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -24312,6 +24312,7 @@ "version": "9.13.0", "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.0.tgz", "integrity": "sha512-y0UvBfjDiXqC8h0EWccyaj4dVBWMxgEx0t5RGNzQsvkfvZwugnKwxpu70StY4ivzYuMajavwUDjH4LJyIki9Lw==", + "dev": true, "license": "MIT", "peerDependencies": { "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", diff --git a/package.json b/package.json index 72234a65788..ac5bfa839e6 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Firefox Monitor", "engines": { - "node": "22.4.x", + "node": "22.3.x", "npm": "10.8.x" }, "type": "module", @@ -11,7 +11,6 @@ "dev": "npm run build-nimbus && next dev --port=6060", "dev:cron:first-data-broker-removal-fixed": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/firstDataBrokerRemovalFixed.tsx", "dev:cron:monthly-activity": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/monthlyActivity.tsx", - "dev:cron:breach-alerts": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/emailBreachAlerts.ts", "dev:cron:db-delete-unverified-subscribers": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/deleteUnverifiedSubscribers.ts", "dev:cron:db-pull-breaches": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/syncBreaches.ts", "dev:cron:remote-settings-pull-breaches": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/updateBreachesInRemoteSettings.ts", @@ -28,7 +27,7 @@ "e2e:smoke": "playwright test src/e2e/ --grep @smoke", "cron:first-data-broker-removal-fixed": "node dist/scripts/cronjobs/firstDataBrokerRemovalFixed.js", "cron:monthly-activity": "node dist/scripts/cronjobs/monthlyActivity.js", - "cron:breach-alerts": "node dist/scripts/cronjobs/emailBreachAlerts.js", + "cron:breach-alerts": "node src/scripts/emailBreachAlerts.js", "cron:db-delete-unverified-subscribers": "node dist/scripts/cronjobs/deleteUnverifiedSubscribers.js", "cron:db-pull-breaches": "node dist/scripts/cronjobs/syncBreaches.js", "cron:remote-settings-pull-breaches": "node dist/scripts/cronjobs/updateBreachesInRemoteSettings.js", @@ -57,8 +56,8 @@ "homepage": "https://github.com/mozilla/blurts-server", "license": "MPL-2.0", "volta": { - "node": "22.4.1", - "npm": "10.8.1" + "node": "22.3.0", + "npm": "10.8.0" }, "dependencies": { "@aws-sdk/client-s3": "^3.614.0", @@ -96,7 +95,6 @@ "react-aria": "^3.33.1", "react-cookie": "^7.1.0", "react-dom": "^18.2.0", - "react-intersection-observer": "^9.13.0", "react-stately": "^3.31.1", "server-only": "^0.0.1", "uuid": "^9.0.1", @@ -105,13 +103,13 @@ "devDependencies": { "@faker-js/faker": "^8.4.1", "@playwright/test": "^1.43.1", - "@storybook/addon-a11y": "^8.2.5", - "@storybook/addon-actions": "^8.2.5", - "@storybook/addon-essentials": "^8.2.5", - "@storybook/addon-interactions": "^8.2.5", - "@storybook/addon-links": "^8.2.5", + "@storybook/addon-a11y": "^8.2.2", + "@storybook/addon-actions": "^8.2.2", + "@storybook/addon-essentials": "^8.2.2", + "@storybook/addon-interactions": "^8.2.2", + "@storybook/addon-links": "^8.2.2", "@storybook/blocks": "^8.0.0", - "@storybook/nextjs": "^8.2.5", + "@storybook/nextjs": "^8.2.2", "@storybook/react": "^8.0.0", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", @@ -146,7 +144,7 @@ "jest-fail-on-console": "^3.3.0", "lint-staged": "^15.2.5", "mjml-browser": "^4.15.3", - "prettier": "3.3.3", + "prettier": "3.3.2", "react-intersection-observer": "^9.10.3", "sass": "^1.77.6", "storybook": "^8.1.11", From 4ea711820ee0741a0f5e1cd960192d6ac54d275f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 15:42:07 +0000 Subject: [PATCH 135/137] chore(deps): bump winston from 3.13.0 to 3.13.1 Bumps [winston](https://github.com/winstonjs/winston) from 3.13.0 to 3.13.1. - [Release notes](https://github.com/winstonjs/winston/releases) - [Changelog](https://github.com/winstonjs/winston/blob/master/CHANGELOG.md) - [Commits](https://github.com/winstonjs/winston/compare/v3.13.0...v3.13.1) --- updated-dependencies: - dependency-name: winston dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0fd204601df..06809b49497 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "react-stately": "^3.31.1", "server-only": "^0.0.1", "uuid": "^9.0.1", - "winston": "^3.13.0" + "winston": "^3.13.1" }, "devDependencies": { "@faker-js/faker": "^8.4.1", @@ -28304,15 +28304,15 @@ } }, "node_modules/winston": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz", - "integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.1.tgz", + "integrity": "sha512-SvZit7VFNvXRzbqGHsv5KSmgbEYR5EiQfDAL9gxYkRqa934Hnk++zze0wANKtMHcy/gI4W/3xmSDwlhf865WGw==", "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", - "logform": "^2.4.0", + "logform": "^2.6.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", diff --git a/package.json b/package.json index d03150fccac..e45c082685f 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "react-stately": "^3.31.1", "server-only": "^0.0.1", "uuid": "^9.0.1", - "winston": "^3.13.0" + "winston": "^3.13.1" }, "devDependencies": { "@faker-js/faker": "^8.4.1", From 3ddef11c3cfffac11462bd99ec17eb7af94ce4fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 16:12:04 +0000 Subject: [PATCH 136/137] chore(deps): bump the sentry group with 3 updates Bumps the sentry group with 3 updates: [@sentry/nextjs](https://github.com/getsentry/sentry-javascript), [@sentry/node](https://github.com/getsentry/sentry-javascript) and [@sentry/utils](https://github.com/getsentry/sentry-javascript). Updates `@sentry/nextjs` from 8.18.0 to 8.19.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.18.0...8.19.0) Updates `@sentry/node` from 8.18.0 to 8.19.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.18.0...8.19.0) Updates `@sentry/utils` from 8.18.0 to 8.19.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.18.0...8.19.0) --- updated-dependencies: - dependency-name: "@sentry/nextjs" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: sentry - dependency-name: "@sentry/node" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: sentry - dependency-name: "@sentry/utils" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: sentry ... Signed-off-by: dependabot[bot] --- package-lock.json | 192 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/package-lock.json b/package-lock.json index dcec0566781..af238ce75ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@leeoniya/ufuzzy": "^1.0.14", "@mozilla/glean": "^5.0.2", "@next/third-parties": "^14.2.5", - "@sentry/nextjs": "^8.18.0", + "@sentry/nextjs": "^8.19.0", "@sentry/node": "^8.0.0", "@sentry/utils": "^8.0.0", "@stripe/stripe-js": "^4.0.0", @@ -6143,9 +6143,9 @@ } }, "node_modules/@prisma/instrumentation": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-5.16.1.tgz", - "integrity": "sha512-4m5gRFWnQb8s/yTyGbMZkL7A5uJgqOWcWJxapwcAD0T0kh5sGPEVSQl/zTQvE9aduXhFAxOtC3gO+R8Hb5xO1Q==", + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-5.17.0.tgz", + "integrity": "sha512-c1Sle4ji8aasMcYfBBHFM56We4ljfenVtRmS8aY06BllS7SoU6SmJBwG7vil+GHiR0Yrh+t9iBwt4AY0Jr4KNQ==", "dependencies": { "@opentelemetry/api": "^1.8", "@opentelemetry/instrumentation": "^0.49 || ^0.50 || ^0.51 || ^0.52.0", @@ -7643,54 +7643,54 @@ "integrity": "sha512-2/U3GXA6YiPYQDLGwtGlnNgKYBSwCFIHf8Y9LUY5VATHdtbLlU0Y1R3QoBnT0aB4qv/BEiVVsj7LJXoQCgJ2vA==" }, "node_modules/@sentry-internal/browser-utils": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.18.0.tgz", - "integrity": "sha512-1R7QXp7Gu6ovJGWvGjbgHcDcvDstsQba3miHtUCyDSH9kXtnAVLCAItDkseetFh+JLsjBXf3QFi2H3HPY4hRCw==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.19.0.tgz", + "integrity": "sha512-kM/2KlikKuBR63nFi2q7MGS3V9K9hakjvUknhr/jHZqDVfEuBKmp1ZlHFAdJtglKHHJy07gPj/XqDH7BbYh5yg==", "dependencies": { - "@sentry/core": "8.18.0", - "@sentry/types": "8.18.0", - "@sentry/utils": "8.18.0" + "@sentry/core": "8.19.0", + "@sentry/types": "8.19.0", + "@sentry/utils": "8.19.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/feedback": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.18.0.tgz", - "integrity": "sha512-on6+4ZRkfdnsNgXecGQ6ME8aO26VTzkuM6y/kNN+bG2hSdxsmuU957B4x1Z5wEXiOWswuf3rhqGepg8JIdPkMQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.19.0.tgz", + "integrity": "sha512-Jc77H8fEaGcBhERc2U/o7Q8CZHvlZLT9vAlzq0ZZR20v/1vwYcJW1ysKfTuvmw22hCR6ukhFNl6pqJocXFVhvA==", "dependencies": { - "@sentry/core": "8.18.0", - "@sentry/types": "8.18.0", - "@sentry/utils": "8.18.0" + "@sentry/core": "8.19.0", + "@sentry/types": "8.19.0", + "@sentry/utils": "8.19.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.18.0.tgz", - "integrity": "sha512-cCLib/HjD8UR0fB2F5hV6KsFBD6yTOEsi67RBllm5gT5vJt87VYoPliF6O7mmMNw8TWkQ0uc5laKld3q9ph+ug==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.19.0.tgz", + "integrity": "sha512-EW9e1J6XbqXUXQST1AfSIzT9O8OwPyeFOkhkn9/gqOQv08TJvQEIBtWJEoJS+XFMEUuB8IqIzVWNVko/DnGt9A==", "dependencies": { - "@sentry-internal/browser-utils": "8.18.0", - "@sentry/core": "8.18.0", - "@sentry/types": "8.18.0", - "@sentry/utils": "8.18.0" + "@sentry-internal/browser-utils": "8.19.0", + "@sentry/core": "8.19.0", + "@sentry/types": "8.19.0", + "@sentry/utils": "8.19.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.18.0.tgz", - "integrity": "sha512-fcuLJBrhw3Ql8sU8veUgDCRYo6toQldFU807cpYphQ0uEw2oVZwNNPDQSu1651Ykvp0P/x+9hk/jjJxMohrO9g==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.19.0.tgz", + "integrity": "sha512-l4pKJDHrXEctxrK7Xme/+fKToXpGwr/G2t77BzeE1WEw9LwSwADz/hi8HoMdZzuKWriM2BNbz20tpVS84sODxA==", "dependencies": { - "@sentry-internal/replay": "8.18.0", - "@sentry/core": "8.18.0", - "@sentry/types": "8.18.0", - "@sentry/utils": "8.18.0" + "@sentry-internal/replay": "8.19.0", + "@sentry/core": "8.19.0", + "@sentry/types": "8.19.0", + "@sentry/utils": "8.19.0" }, "engines": { "node": ">=14.18" @@ -7705,17 +7705,17 @@ } }, "node_modules/@sentry/browser": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.18.0.tgz", - "integrity": "sha512-E2w9u76JcjxcmgvroJrB7bcbG5oBCYI/pME1CtprBgZSS9mMYDsyBe6JKqGHdw2wvT3xNxNtkm7hf1O6+3NWUQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.19.0.tgz", + "integrity": "sha512-ZC1HxIFm4TIGONyy9MkPG6Dw8IAhzq43t5mq9PqrB1ehuWj8GX6Vk3E26kuc2sydAm4AXbj0562OmvZHsAJpUA==", "dependencies": { - "@sentry-internal/browser-utils": "8.18.0", - "@sentry-internal/feedback": "8.18.0", - "@sentry-internal/replay": "8.18.0", - "@sentry-internal/replay-canvas": "8.18.0", - "@sentry/core": "8.18.0", - "@sentry/types": "8.18.0", - "@sentry/utils": "8.18.0" + "@sentry-internal/browser-utils": "8.19.0", + "@sentry-internal/feedback": "8.19.0", + "@sentry-internal/replay": "8.19.0", + "@sentry-internal/replay-canvas": "8.19.0", + "@sentry/core": "8.19.0", + "@sentry/types": "8.19.0", + "@sentry/utils": "8.19.0" }, "engines": { "node": ">=14.18" @@ -7956,32 +7956,32 @@ } }, "node_modules/@sentry/core": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.18.0.tgz", - "integrity": "sha512-8moEMC3gp4W6mH9w5amb/zrYk6bNW8WGgcLRMCs5rguxny8YP5i8ISOJ0T0LP9x/RxSK/6xix5D2bzI/5ECzlw==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.19.0.tgz", + "integrity": "sha512-MrgjsZCEjOJgQjIznnDSrLEy7qL+4LVpNieAvr49cV1rzBNSwGmWRnt/puVaPsLyCUgupVx/43BPUHB/HtKNUw==", "dependencies": { - "@sentry/types": "8.18.0", - "@sentry/utils": "8.18.0" + "@sentry/types": "8.19.0", + "@sentry/utils": "8.19.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/nextjs": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-8.18.0.tgz", - "integrity": "sha512-fmBFxaChOWHU/5Mo5X1ASxFVMF3ZjYImJhM+iLWKpw0Yuu9f9T6p8FPPQC2mPerxTX0yHUqTeVlhHufSPjZNEg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-8.19.0.tgz", + "integrity": "sha512-WafL2zXKEp1jQJ0bC8H15zEUGT4m6bDiCwlaP8xAI3dz5E1e6f29OFlStvgzU3Tpx/Wi6qNTs5AGuwV3wK9qdg==", "dependencies": { "@opentelemetry/instrumentation-http": "0.52.1", "@opentelemetry/semantic-conventions": "^1.25.1", "@rollup/plugin-commonjs": "26.0.1", - "@sentry/core": "8.18.0", - "@sentry/node": "8.18.0", - "@sentry/opentelemetry": "8.18.0", - "@sentry/react": "8.18.0", - "@sentry/types": "8.18.0", - "@sentry/utils": "8.18.0", - "@sentry/vercel-edge": "8.18.0", + "@sentry/core": "8.19.0", + "@sentry/node": "8.19.0", + "@sentry/opentelemetry": "8.19.0", + "@sentry/react": "8.19.0", + "@sentry/types": "8.19.0", + "@sentry/utils": "8.19.0", + "@sentry/vercel-edge": "8.19.0", "@sentry/webpack-plugin": "2.20.1", "chalk": "3.0.0", "resolve": "1.22.8", @@ -8019,13 +8019,13 @@ } }, "node_modules/@sentry/nextjs/node_modules/@sentry/opentelemetry": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.18.0.tgz", - "integrity": "sha512-P2OoXXJcU2RiRZmpBqOkK+NLGkwQrYizlOHl1zckHI1nYmQgOD1tcJj4c1xOYzH+eGPLp/IViXHO6vaBr8BGGg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.19.0.tgz", + "integrity": "sha512-L1aSxO/aJJ7D3pIlTaVOgbiZJAnUHXezobTc8j5pqFCQACjxnLMSDrt53QfFV52CcjbliDWCYe4IB8umu4DgpA==", "dependencies": { - "@sentry/core": "8.18.0", - "@sentry/types": "8.18.0", - "@sentry/utils": "8.18.0" + "@sentry/core": "8.19.0", + "@sentry/types": "8.19.0", + "@sentry/utils": "8.19.0" }, "engines": { "node": ">=14.18" @@ -8039,9 +8039,9 @@ } }, "node_modules/@sentry/node": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-8.18.0.tgz", - "integrity": "sha512-a+W477bmt28I1DT51xJKmp4Y7hBAdEGqQ2K7gfOn3mRBHoihuhKl2Xe8BMwFH7+v4mAEZEwAZBUOLAC7h+Tjig==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-8.19.0.tgz", + "integrity": "sha512-r7AeKxfB9eE/UW0NZT3AMh+hNA65NFEwtsMYO6iI52FPLFZh0DLOvzVOeNsmsJqPpyetooUGTtUYpBdinZldWA==", "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.25.1", @@ -8065,11 +8065,11 @@ "@opentelemetry/resources": "^1.25.1", "@opentelemetry/sdk-trace-base": "^1.25.1", "@opentelemetry/semantic-conventions": "^1.25.1", - "@prisma/instrumentation": "5.16.1", - "@sentry/core": "8.18.0", - "@sentry/opentelemetry": "8.18.0", - "@sentry/types": "8.18.0", - "@sentry/utils": "8.18.0" + "@prisma/instrumentation": "5.17.0", + "@sentry/core": "8.19.0", + "@sentry/opentelemetry": "8.19.0", + "@sentry/types": "8.19.0", + "@sentry/utils": "8.19.0" }, "engines": { "node": ">=14.18" @@ -8095,13 +8095,13 @@ } }, "node_modules/@sentry/node/node_modules/@sentry/opentelemetry": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.18.0.tgz", - "integrity": "sha512-P2OoXXJcU2RiRZmpBqOkK+NLGkwQrYizlOHl1zckHI1nYmQgOD1tcJj4c1xOYzH+eGPLp/IViXHO6vaBr8BGGg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.19.0.tgz", + "integrity": "sha512-L1aSxO/aJJ7D3pIlTaVOgbiZJAnUHXezobTc8j5pqFCQACjxnLMSDrt53QfFV52CcjbliDWCYe4IB8umu4DgpA==", "dependencies": { - "@sentry/core": "8.18.0", - "@sentry/types": "8.18.0", - "@sentry/utils": "8.18.0" + "@sentry/core": "8.19.0", + "@sentry/types": "8.19.0", + "@sentry/utils": "8.19.0" }, "engines": { "node": ">=14.18" @@ -8115,14 +8115,14 @@ } }, "node_modules/@sentry/react": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-8.18.0.tgz", - "integrity": "sha512-ckCKdxmeFdfR6moE/Aiq+cJyQuCUKoUqU/++xZwqVbgecuImsk4s7CzzpX9T6JoYK7jqru2SvuRSiwcdtLN6AQ==", - "dependencies": { - "@sentry/browser": "8.18.0", - "@sentry/core": "8.18.0", - "@sentry/types": "8.18.0", - "@sentry/utils": "8.18.0", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-8.19.0.tgz", + "integrity": "sha512-MzuMy4AEdSuIrBEyp3W7c4+v215+2MiU9ba7Y0KBKcC/Nrf1cGfRFRbjl9OYm/JIuxkaop7kgYs6sPMrVJVlrQ==", + "dependencies": { + "@sentry/browser": "8.19.0", + "@sentry/core": "8.19.0", + "@sentry/types": "8.19.0", + "@sentry/utils": "8.19.0", "hoist-non-react-statics": "^3.3.2" }, "engines": { @@ -8133,32 +8133,32 @@ } }, "node_modules/@sentry/types": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.18.0.tgz", - "integrity": "sha512-5J+uOqptnmAnW3Rk31AHIqW36Wzvlo3UOM+p2wjSYGrC/PgcE47Klzr+w4UcOhN6AZqefalGd3vaUXz9NaFdRg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.19.0.tgz", + "integrity": "sha512-52C8X5V7mK2KIxMJt8MV5TxXAFHqrQR1RKm1oPTwKVWm8hKr1ZYJXINymNrWvpAc3oVIKLC/sa9WFYgXQh+YlA==", "engines": { "node": ">=14.18" } }, "node_modules/@sentry/utils": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.18.0.tgz", - "integrity": "sha512-7wq7cgaeSIGJncl9/2VMu81ZN5ep4lp4H1/+O8+xUxOmnPb/05ZZcbn9/VxVQvIoqZSZdwCLPeBz6PEVukvokA==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.19.0.tgz", + "integrity": "sha512-8dWJJKaUN6Hf92Oxw2TBmHchGua2W3ZmonrZTTwLvl06jcAigbiQD0MGuF5ytZP8PHx860orV+SbTGKFzfU3Pg==", "dependencies": { - "@sentry/types": "8.18.0" + "@sentry/types": "8.19.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/vercel-edge": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-8.18.0.tgz", - "integrity": "sha512-V8jPZNWI93sf1u1sYLAg62oD7Jxw6uGT5bPPHBHbF2XmowW8sIyuXmkXYKVy+s6sOPCyJoW3/0j4BFf/qLowsg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-8.19.0.tgz", + "integrity": "sha512-I1G19SGWKcwUo1VT57xD/c/ZBnl8qkz6V+6j+vCbms4i0GTFw3eASnUIAOd25kc59/Wih2tUVj5mfV6aX5/DFg==", "dependencies": { - "@sentry/core": "8.18.0", - "@sentry/types": "8.18.0", - "@sentry/utils": "8.18.0" + "@sentry/core": "8.19.0", + "@sentry/types": "8.19.0", + "@sentry/utils": "8.19.0" }, "engines": { "node": ">=14.18" diff --git a/package.json b/package.json index 5af6960ec94..94dc65abd0c 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@leeoniya/ufuzzy": "^1.0.14", "@mozilla/glean": "^5.0.2", "@next/third-parties": "^14.2.5", - "@sentry/nextjs": "^8.18.0", + "@sentry/nextjs": "^8.19.0", "@sentry/node": "^8.0.0", "@sentry/utils": "^8.0.0", "@stripe/stripe-js": "^4.0.0", From f969fad4498958d31767545c4c2585de3764ed4a Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Mon, 22 Jul 2024 14:36:44 -0700 Subject: [PATCH 137/137] change OAUTH_ACCOUNT_URI to stage to match other OAUTH defaults (#4850) --- .env | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.env b/.env index 06a5b7c2685..7e8de40d8ed 100755 --- a/.env +++ b/.env @@ -39,8 +39,7 @@ OAUTH_CLIENT_SECRET=get-this-from-groovecoder-or-fxmonitor-engineering OAUTH_AUTHORIZATION_URI=https://oauth.stage.mozaws.net/v1/authorization OAUTH_PROFILE_URI=https://profile.stage.mozaws.net/v1/profile OAUTH_TOKEN_URI=https://oauth.stage.mozaws.net/v1/token -OAUTH_ACCOUNT_URI = "https://oauth.accounts.firefox.com/v1" -OAUTH_API_URI="https://api-accounts.stage.mozaws.net/v1" +OAUTH_ACCOUNT_URI="https://api-accounts.stage.mozaws.net/v1" # HIBP API for breach data # How many seconds to wait before refreshing upstream breach data from HIBP