Merge branch 'main' into MNTOR-2935-free-scan-auth-and-noauth
|
@ -984,3 +984,4 @@ floating-banner-dismiss-button-label = Ne, díky
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Nové jméno, vzhled a ještě více způsobů, jak <b>získat zpět své soukromí</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Nové jméno, vzhled a ještě více způsobů, jak <b>získat zpět své soukromí</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = OK
|
banner-monitor-rebrand-dismiss-button-label = OK
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Zavřít
|
banner-monitor-rebrand-dismiss-button-tooltip = Zavřít
|
||||||
|
loading-accessibility = Načítání
|
||||||
|
|
|
@ -867,3 +867,4 @@ floating-banner-dismiss-button-label = Dim diolch
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Enw, golwg newydd a rhagor o ffyrdd i <b>adennill eich preifatrwydd</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Enw, golwg newydd a rhagor o ffyrdd i <b>adennill eich preifatrwydd</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = Iawn
|
banner-monitor-rebrand-dismiss-button-label = Iawn
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Cau
|
banner-monitor-rebrand-dismiss-button-tooltip = Cau
|
||||||
|
loading-accessibility = Llwytho
|
||||||
|
|
|
@ -788,3 +788,4 @@ floating-banner-dismiss-button-label = Nein, danke
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Neuer Name, neues Aussehen und noch mehr Möglichkeiten, um <b>Ihre Privatsphäre zurückzugewinnen</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Neuer Name, neues Aussehen und noch mehr Möglichkeiten, um <b>Ihre Privatsphäre zurückzugewinnen</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = OK
|
banner-monitor-rebrand-dismiss-button-label = OK
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Schließen
|
banner-monitor-rebrand-dismiss-button-tooltip = Schließen
|
||||||
|
loading-accessibility = Wird geladen…
|
||||||
|
|
|
@ -872,3 +872,4 @@ floating-banner-dismiss-button-label = Όχι, ευχαριστώ
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Νέο όνομα, εμφάνιση και ακόμα περισσότεροι τρόποι <b>διεκδίκησης του απορρήτου σας</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Νέο όνομα, εμφάνιση και ακόμα περισσότεροι τρόποι <b>διεκδίκησης του απορρήτου σας</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = OK
|
banner-monitor-rebrand-dismiss-button-label = OK
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Απόρριψη
|
banner-monitor-rebrand-dismiss-button-tooltip = Απόρριψη
|
||||||
|
loading-accessibility = Φόρτωση
|
||||||
|
|
|
@ -805,3 +805,4 @@ floating-banner-dismiss-button-label = No, gracias
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Nuevo nombre, apariencia e incluso más formas de <b>recuperar tu privacidad</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Nuevo nombre, apariencia e incluso más formas de <b>recuperar tu privacidad</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = Aceptar
|
banner-monitor-rebrand-dismiss-button-label = Aceptar
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Descartar
|
banner-monitor-rebrand-dismiss-button-tooltip = Descartar
|
||||||
|
loading-accessibility = Cargando
|
||||||
|
|
|
@ -779,3 +779,4 @@ floating-banner-dismiss-button-label = Non merci
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b> : un nouveau nom, une nouvelle interface et encore de nouvelles façons de <b>reprendre le contrôle de votre vie privée</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b> : un nouveau nom, une nouvelle interface et encore de nouvelles façons de <b>reprendre le contrôle de votre vie privée</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = OK
|
banner-monitor-rebrand-dismiss-button-label = OK
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Ignorer
|
banner-monitor-rebrand-dismiss-button-tooltip = Ignorer
|
||||||
|
loading-accessibility = Chargement…
|
||||||
|
|
|
@ -841,3 +841,4 @@ floating-banner-dismiss-button-label = Köszönöm, nem
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Új név, kinézet és még több módja annak, hogy <b>visszaszerezze a magánszféráját</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Új név, kinézet és még több módja annak, hogy <b>visszaszerezze a magánszféráját</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = OK
|
banner-monitor-rebrand-dismiss-button-label = OK
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Eltüntetés
|
banner-monitor-rebrand-dismiss-button-tooltip = Eltüntetés
|
||||||
|
loading-accessibility = Betöltés
|
||||||
|
|
|
@ -820,3 +820,4 @@ floating-banner-dismiss-button-label = Tidak, terima kasih
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Nama baru, tampilan, dan lebih banyak cara untuk <b>mendapatkan kembali privasi Anda</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Nama baru, tampilan, dan lebih banyak cara untuk <b>mendapatkan kembali privasi Anda</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = Oke
|
banner-monitor-rebrand-dismiss-button-label = Oke
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Tutup
|
banner-monitor-rebrand-dismiss-button-tooltip = Tutup
|
||||||
|
loading-accessibility = Memuat
|
||||||
|
|
|
@ -767,3 +767,4 @@ floating-banner-dismiss-button-label = No grazie
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: nuovo nome, nuovo look e ancora più modi per <b>riprendere possesso della tua privacy</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: nuovo nome, nuovo look e ancora più modi per <b>riprendere possesso della tua privacy</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = OK
|
banner-monitor-rebrand-dismiss-button-label = OK
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Chiudi
|
banner-monitor-rebrand-dismiss-button-tooltip = Chiudi
|
||||||
|
loading-accessibility = Caricamento…
|
||||||
|
|
|
@ -836,3 +836,4 @@ floating-banner-dismiss-button-label = Nee, bedankt
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: nieuwe naam, vormgeving en nog meer manieren om <b>uw privacy op te eisen</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: nieuwe naam, vormgeving en nog meer manieren om <b>uw privacy op te eisen</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = OK
|
banner-monitor-rebrand-dismiss-button-label = OK
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Sluiten
|
banner-monitor-rebrand-dismiss-button-tooltip = Sluiten
|
||||||
|
loading-accessibility = Laden
|
||||||
|
|
|
@ -837,3 +837,4 @@ floating-banner-dismiss-button-label = Não, obrigado
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Um novo nome, visual e ainda mais formas de <b>recuperar a sua privacidade</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Um novo nome, visual e ainda mais formas de <b>recuperar a sua privacidade</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = Ok
|
banner-monitor-rebrand-dismiss-button-label = Ok
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Dispensar
|
banner-monitor-rebrand-dismiss-button-tooltip = Dispensar
|
||||||
|
loading-accessibility = A carregar
|
||||||
|
|
|
@ -820,3 +820,4 @@ floating-banner-dismiss-button-label = Нет, спасибо
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Новое имя, внешний вид и ещё больше способов <b>восстановить вашу приватность</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Новое имя, внешний вид и ещё больше способов <b>восстановить вашу приватность</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = OK
|
banner-monitor-rebrand-dismiss-button-label = OK
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Скрыть
|
banner-monitor-rebrand-dismiss-button-tooltip = Скрыть
|
||||||
|
loading-accessibility = Загрузка
|
||||||
|
|
|
@ -900,3 +900,4 @@ floating-banner-dismiss-button-label = Ne, hvala
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Novo ime, podoba in še več načinov za <b>ponovno pridobitev zasebnosti</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Novo ime, podoba in še več načinov za <b>ponovno pridobitev zasebnosti</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = V redu
|
banner-monitor-rebrand-dismiss-button-label = V redu
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Opusti
|
banner-monitor-rebrand-dismiss-button-tooltip = Opusti
|
||||||
|
loading-accessibility = Nalaganje
|
||||||
|
|
|
@ -841,3 +841,4 @@ floating-banner-dismiss-button-label = Nej tack
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Nytt namn, utseende och ännu fler sätt att <b>återställa din integritet</b>.
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>: Nytt namn, utseende och ännu fler sätt att <b>återställa din integritet</b>.
|
||||||
banner-monitor-rebrand-dismiss-button-label = OK
|
banner-monitor-rebrand-dismiss-button-label = OK
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = Ignorera
|
banner-monitor-rebrand-dismiss-button-tooltip = Ignorera
|
||||||
|
loading-accessibility = Laddar
|
||||||
|
|
|
@ -735,3 +735,4 @@ floating-banner-dismiss-button-label = 不要,謝謝
|
||||||
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>:全新名稱、外觀與更多<b>奪回隱私權</b>的方式。
|
banner-monitor-rebrand-text = <b>{ -brand-mozilla-monitor }</b>:全新名稱、外觀與更多<b>奪回隱私權</b>的方式。
|
||||||
banner-monitor-rebrand-dismiss-button-label = 確定
|
banner-monitor-rebrand-dismiss-button-label = 確定
|
||||||
banner-monitor-rebrand-dismiss-button-tooltip = 知道了!
|
banner-monitor-rebrand-dismiss-button-tooltip = 知道了!
|
||||||
|
loading-accessibility = 載入中
|
||||||
|
|
|
@ -48,8 +48,8 @@
|
||||||
"npm": "10.2.4"
|
"npm": "10.2.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.496.0",
|
"@aws-sdk/client-s3": "^3.511.0",
|
||||||
"@aws-sdk/lib-storage": "^3.496.0",
|
"@aws-sdk/lib-storage": "^3.511.0",
|
||||||
"@fluent/bundle": "^0.18.0",
|
"@fluent/bundle": "^0.18.0",
|
||||||
"@fluent/langneg": "^0.7.0",
|
"@fluent/langneg": "^0.7.0",
|
||||||
"@fluent/react": "^0.15.2",
|
"@fluent/react": "^0.15.2",
|
||||||
|
@ -59,9 +59,9 @@
|
||||||
"@leeoniya/ufuzzy": "^1.0.14",
|
"@leeoniya/ufuzzy": "^1.0.14",
|
||||||
"@mozilla/glean": "4.0.0",
|
"@mozilla/glean": "4.0.0",
|
||||||
"@next/third-parties": "^14.1.0",
|
"@next/third-parties": "^14.1.0",
|
||||||
"@sentry/nextjs": "^7.99.0",
|
"@sentry/nextjs": "^7.100.1",
|
||||||
"@sentry/node": "^7.58.1",
|
"@sentry/node": "^7.58.1",
|
||||||
"@sentry/tracing": "^7.99.0",
|
"@sentry/tracing": "^7.100.1",
|
||||||
"@types/jsdom": "^21.1.5",
|
"@types/jsdom": "^21.1.5",
|
||||||
"@types/node": "^20.11.17",
|
"@types/node": "^20.11.17",
|
||||||
"@types/react": "^18.2.48",
|
"@types/react": "^18.2.48",
|
||||||
|
|
|
@ -9,8 +9,7 @@ import { useEffect, useRef, useState } from "react";
|
||||||
import { ProgressBar } from "../../../../../components/client/ProgressBar";
|
import { ProgressBar } from "../../../../../components/client/ProgressBar";
|
||||||
import styles from "./FindExposures.module.scss";
|
import styles from "./FindExposures.module.scss";
|
||||||
import { useL10n } from "../../../../../hooks/l10n";
|
import { useL10n } from "../../../../../hooks/l10n";
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { sendGAEvent } from "../../../../../components/client/GoogleAnalyticsWorkaround";
|
||||||
import { useGa } from "../../../../../hooks/useGa";
|
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
dataBrokerCount: number;
|
dataBrokerCount: number;
|
||||||
|
@ -84,7 +83,6 @@ export const FindExposures = ({
|
||||||
progressRange: [labelSwitchThreshold, 100],
|
progressRange: [labelSwitchThreshold, 100],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { gtag } = useGa();
|
|
||||||
const pathName = usePathname();
|
const pathName = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
@ -115,11 +113,11 @@ export const FindExposures = ({
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.status && result.status === "finished") {
|
if (result.status && result.status === "finished") {
|
||||||
setScanFinished(true);
|
setScanFinished(true);
|
||||||
gtag.record({
|
sendGAEvent(
|
||||||
type: "event",
|
"event",
|
||||||
name: "free_scan_completed",
|
"free_scan_completed",
|
||||||
params: scanCompletedTelemetryParams,
|
scanCompletedTelemetryParams,
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
setCheckingScanProgress(false);
|
setCheckingScanProgress(false);
|
||||||
})
|
})
|
||||||
|
@ -135,11 +133,7 @@ export const FindExposures = ({
|
||||||
userTimeSpentRef.current.endTime = Date.now();
|
userTimeSpentRef.current.endTime = Date.now();
|
||||||
findExposuresTelemetryParams.exit_time =
|
findExposuresTelemetryParams.exit_time =
|
||||||
userTimeSpentRef.current.endTime - userTimeSpentRef.current.startTime;
|
userTimeSpentRef.current.endTime - userTimeSpentRef.current.startTime;
|
||||||
gtag.record({
|
sendGAEvent("event", "exited_scan", findExposuresTelemetryParams);
|
||||||
type: "event",
|
|
||||||
name: "exited_scan",
|
|
||||||
params: findExposuresTelemetryParams,
|
|
||||||
});
|
|
||||||
router.push(previousRoute);
|
router.push(previousRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +147,6 @@ export const FindExposures = ({
|
||||||
scanFinished,
|
scanFinished,
|
||||||
percentageSteps,
|
percentageSteps,
|
||||||
previousRoute,
|
previousRoute,
|
||||||
gtag,
|
|
||||||
pathName,
|
pathName,
|
||||||
searchParams,
|
searchParams,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
import { NextRequest } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
import { AuthOptions, Profile as FxaProfile, User } from "next-auth";
|
import { AuthOptions, Profile as FxaProfile, User } from "next-auth";
|
||||||
|
import { SubscriberRow } from "knex/types/tables";
|
||||||
import { logger } from "../../functions/server/logging";
|
import { logger } from "../../functions/server/logging";
|
||||||
|
|
||||||
import AppConstants from "../../../appConstants.js";
|
import AppConstants from "../../../appConstants.js";
|
||||||
|
@ -72,20 +73,19 @@ export const authOptions: AuthOptions = {
|
||||||
async jwt({ token, account, profile, trigger }) {
|
async jwt({ token, account, profile, trigger }) {
|
||||||
if (trigger === "update") {
|
if (trigger === "update") {
|
||||||
// Refresh the user data from FxA, in case e.g. new subscriptions got added:
|
// Refresh the user data from FxA, in case e.g. new subscriptions got added:
|
||||||
const subscriber = await getSubscriberByFxaUid(
|
const subscriberFromDb = await getSubscriberByFxaUid(
|
||||||
token.subscriber?.fxa_uid ?? "",
|
token.subscriber?.fxa_uid ?? "",
|
||||||
);
|
);
|
||||||
profile = subscriber.fxa_profile_json as FxaProfile;
|
|
||||||
|
|
||||||
if (token.subscriber?.fxa_uid) {
|
if (subscriberFromDb) {
|
||||||
const updatedSubscriberData = await getSubscriberByFxaUid(
|
profile = subscriberFromDb.fxa_profile_json as FxaProfile;
|
||||||
token.subscriber.fxa_uid,
|
|
||||||
);
|
|
||||||
// MNTOR-2599 The breach_resolution object can get pretty big,
|
// MNTOR-2599 The breach_resolution object can get pretty big,
|
||||||
// causing the session token cookie to balloon in size,
|
// causing the session token cookie to balloon in size,
|
||||||
// eventually resulting in a 400 Bad Request due to headers being too large.
|
// eventually resulting in a 400 Bad Request due to headers being too large.
|
||||||
delete updatedSubscriberData.breach_resolution;
|
delete (subscriberFromDb as Partial<SubscriberRow>).breach_resolution;
|
||||||
token.subscriber = updatedSubscriberData;
|
token.subscriber =
|
||||||
|
subscriberFromDb as unknown as SerializedSubscriber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (profile) {
|
if (profile) {
|
||||||
|
@ -117,8 +117,8 @@ export const authOptions: AuthOptions = {
|
||||||
// MNTOR-2599 The breach_resolution object can get pretty big,
|
// MNTOR-2599 The breach_resolution object can get pretty big,
|
||||||
// causing the session token cookie to balloon in size,
|
// causing the session token cookie to balloon in size,
|
||||||
// eventually resulting in a 400 Bad Request due to headers being too large.
|
// eventually resulting in a 400 Bad Request due to headers being too large.
|
||||||
delete existingUser.breach_resolution;
|
delete (existingUser as Partial<SubscriberRow>).breach_resolution;
|
||||||
token.subscriber = existingUser;
|
token.subscriber = existingUser as unknown as SerializedSubscriber;
|
||||||
if (account.access_token && account.refresh_token) {
|
if (account.access_token && account.refresh_token) {
|
||||||
const updatedUser = await updateFxAData(
|
const updatedUser = await updateFxAData(
|
||||||
existingUser,
|
existingUser,
|
||||||
|
|
|
@ -3,15 +3,15 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import { ReactLocalization } from "@fluent/react";
|
import { ReactLocalization } from "@fluent/react";
|
||||||
|
import { SubscriberRow } from "knex/types/tables";
|
||||||
import { resetUnverifiedEmailAddress } from "../../../db/tables/emailAddresses.js";
|
import { resetUnverifiedEmailAddress } from "../../../db/tables/emailAddresses.js";
|
||||||
import { sendEmail, getVerificationUrl } from "../../../utils/email";
|
import { sendEmail, getVerificationUrl } from "../../../utils/email";
|
||||||
import { getStringLookup } from "../../../utils/fluent.js";
|
import { getStringLookup } from "../../../utils/fluent.js";
|
||||||
import { getTemplate } from "../../../views/emails/email2022.js";
|
import { getTemplate } from "../../../views/emails/email2022.js";
|
||||||
import { verifyPartial } from "../../../views/emails/emailVerify.js";
|
import { verifyPartial } from "../../../views/emails/emailVerify.js";
|
||||||
import { Subscriber } from "../../deprecated/(authenticated)/user/breaches/breaches";
|
|
||||||
|
|
||||||
export async function sendVerificationEmail(
|
export async function sendVerificationEmail(
|
||||||
user: Subscriber,
|
user: SubscriberRow,
|
||||||
emailId: number,
|
emailId: number,
|
||||||
l10n: ReactLocalization,
|
l10n: ReactLocalization,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -136,6 +136,9 @@ export async function PUT(
|
||||||
const subscriber = await getSubscriberByEmail(
|
const subscriber = await getSubscriberByEmail(
|
||||||
subscriberRow.primary_email,
|
subscriberRow.primary_email,
|
||||||
);
|
);
|
||||||
|
if (!subscriber) {
|
||||||
|
throw new Error("No subscriber found for given email.");
|
||||||
|
}
|
||||||
|
|
||||||
const onerepProfileId = await getOnerepProfileId(subscriber.id);
|
const onerepProfileId = await getOnerepProfileId(subscriber.id);
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,10 @@ export async function POST(request: NextRequest) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// get current profiledata
|
// get current profiledata
|
||||||
const currentFxAProfile = subscriber?.fxa_profile_json || {};
|
// Typed as `any` because `subscriber` used to be typed as `any`, and
|
||||||
|
// making that type more specific was enough work just by itself:
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const currentFxAProfile: any = subscriber?.fxa_profile_json;
|
||||||
|
|
||||||
// merge new event into existing profile data
|
// merge new event into existing profile data
|
||||||
for (const key in updatedProfileFromEvent) {
|
for (const key in updatedProfileFromEvent) {
|
||||||
|
@ -249,8 +252,25 @@ export async function POST(request: NextRequest) {
|
||||||
updateFromEvent,
|
updateFromEvent,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const refreshToken = subscriber.fxa_refresh_token;
|
||||||
|
const accessToken = subscriber.fxa_access_token;
|
||||||
|
if (refreshToken === null || accessToken === null) {
|
||||||
|
logger.error("failed_changing_password", {
|
||||||
|
subscriber_id: subscriber.id,
|
||||||
|
fxa_refresh_token: subscriber.fxa_refresh_token,
|
||||||
|
fxa_access_token: subscriber.fxa_access_token,
|
||||||
|
});
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, message: "failed_changing_password" },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// MNTOR-1932: Change password should revoke sessions
|
// MNTOR-1932: Change password should revoke sessions
|
||||||
await revokeOAuthTokens(subscriber);
|
await revokeOAuthTokens({
|
||||||
|
fxa_access_token: accessToken,
|
||||||
|
fxa_refresh_token: refreshToken,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FXA_SUBSCRIPTION_CHANGE_EVENT: {
|
case FXA_SUBSCRIPTION_CHANGE_EVENT: {
|
||||||
|
@ -291,7 +311,7 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
captureException(
|
captureException(
|
||||||
new Error(`No OneRep profile Id found, subscriber: ${
|
new Error(`No OneRep profile Id found, subscriber: ${
|
||||||
subscriber.id as string
|
subscriber.id
|
||||||
}\n
|
}\n
|
||||||
Event: ${event}\n
|
Event: ${event}\n
|
||||||
updateFromEvent: ${JSON.stringify(updatedSubscriptionFromEvent)}`),
|
updateFromEvent: ${JSON.stringify(updatedSubscriptionFromEvent)}`),
|
||||||
|
@ -351,7 +371,7 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
captureException(
|
captureException(
|
||||||
new Error(`No OneRep profile Id found, subscriber: ${
|
new Error(`No OneRep profile Id found, subscriber: ${
|
||||||
subscriber.id as string
|
subscriber.id
|
||||||
}\n
|
}\n
|
||||||
Event: ${event}\n
|
Event: ${event}\n
|
||||||
updateFromEvent: ${JSON.stringify(
|
updateFromEvent: ${JSON.stringify(
|
||||||
|
|
|
@ -6,10 +6,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||||
import { authOptions } from "../../../../utils/auth";
|
import { authOptions } from "../../../../utils/auth";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { logger } from "../../../../../functions/server/logging";
|
import { logger } from "../../../../../functions/server/logging";
|
||||||
import {
|
import { BreachBulkResolutionRequest } from "../../../../../deprecated/(authenticated)/user/breaches/breaches.js";
|
||||||
BreachBulkResolutionRequest,
|
|
||||||
Subscriber,
|
|
||||||
} from "../../../../../deprecated/(authenticated)/user/breaches/breaches.js";
|
|
||||||
import { getBreaches } from "../../../../../functions/server/getBreaches";
|
import { getBreaches } from "../../../../../functions/server/getBreaches";
|
||||||
import { getAllEmailsAndBreaches } from "../../../../../../utils/breaches";
|
import { getAllEmailsAndBreaches } from "../../../../../../utils/breaches";
|
||||||
import {
|
import {
|
||||||
|
@ -30,9 +27,12 @@ export async function PUT(req: NextRequest): Promise<NextResponse> {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const subscriber: Subscriber = await getSubscriberByFxaUid(
|
const subscriber = await getSubscriberByFxaUid(
|
||||||
session.user.subscriber.fxa_uid,
|
session.user.subscriber.fxa_uid,
|
||||||
);
|
);
|
||||||
|
if (!subscriber) {
|
||||||
|
throw new Error("No subscriber found for the current session.");
|
||||||
|
}
|
||||||
const allBreaches = await getBreaches();
|
const allBreaches = await getBreaches();
|
||||||
const { dataType: dataTypeToResolve }: BreachBulkResolutionRequest =
|
const { dataType: dataTypeToResolve }: BreachBulkResolutionRequest =
|
||||||
await req.json();
|
await req.json();
|
||||||
|
@ -42,7 +42,10 @@ export async function PUT(req: NextRequest): Promise<NextResponse> {
|
||||||
allBreaches,
|
allBreaches,
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentBreachResolution = subscriber.breach_resolution || {}; // get this from existing breach resolution if available
|
// Typed as `any` because `subscriber` used to be typed as `any`, and making
|
||||||
|
// that type more specific was enough work just by itself:
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const currentBreachResolution: any = subscriber.breach_resolution || {}; // get this from existing breach resolution if available
|
||||||
|
|
||||||
for (const verifiedEmail of verifiedEmails) {
|
for (const verifiedEmail of verifiedEmails) {
|
||||||
const currentEmail = verifiedEmail.email;
|
const currentEmail = verifiedEmail.email;
|
||||||
|
@ -83,6 +86,9 @@ export async function PUT(req: NextRequest): Promise<NextResponse> {
|
||||||
subscriber,
|
subscriber,
|
||||||
currentBreachResolution,
|
currentBreachResolution,
|
||||||
);
|
);
|
||||||
|
if (!updatedSubscriber) {
|
||||||
|
throw new Error("Could not retrieve updated subscriber data.");
|
||||||
|
}
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
|
@ -6,10 +6,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||||
import { getToken } from "next-auth/jwt";
|
import { getToken } from "next-auth/jwt";
|
||||||
|
|
||||||
import { logger } from "../../../../functions/server/logging";
|
import { logger } from "../../../../functions/server/logging";
|
||||||
import {
|
import { BreachResolutionRequest } from "../../../../deprecated/(authenticated)/user/breaches/breaches.js";
|
||||||
BreachResolutionRequest,
|
|
||||||
Subscriber,
|
|
||||||
} from "../../../../deprecated/(authenticated)/user/breaches/breaches.js";
|
|
||||||
import { getBreaches } from "../../../../functions/server/getBreaches";
|
import { getBreaches } from "../../../../functions/server/getBreaches";
|
||||||
import { getAllEmailsAndBreaches } from "../../../../../utils/breaches";
|
import { getAllEmailsAndBreaches } from "../../../../../utils/breaches";
|
||||||
import {
|
import {
|
||||||
|
@ -24,9 +21,7 @@ export async function GET(req: NextRequest) {
|
||||||
if (typeof token?.subscriber?.fxa_uid === "string") {
|
if (typeof token?.subscriber?.fxa_uid === "string") {
|
||||||
// Signed in
|
// Signed in
|
||||||
try {
|
try {
|
||||||
const subscriber: Subscriber = await getSubscriberByFxaUid(
|
const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid);
|
||||||
token.subscriber?.fxa_uid,
|
|
||||||
);
|
|
||||||
const allBreaches = await getBreaches();
|
const allBreaches = await getBreaches();
|
||||||
const breaches = await getAllEmailsAndBreaches(subscriber, allBreaches);
|
const breaches = await getAllEmailsAndBreaches(subscriber, allBreaches);
|
||||||
const successResponse = {
|
const successResponse = {
|
||||||
|
@ -48,9 +43,10 @@ export async function PUT(req: NextRequest) {
|
||||||
const token = await getToken({ req });
|
const token = await getToken({ req });
|
||||||
if (typeof token?.subscriber?.fxa_uid === "string") {
|
if (typeof token?.subscriber?.fxa_uid === "string") {
|
||||||
try {
|
try {
|
||||||
const subscriber: Subscriber = await getSubscriberByFxaUid(
|
const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid);
|
||||||
token.subscriber?.fxa_uid,
|
if (!subscriber) {
|
||||||
);
|
throw new Error("No subscriber found for current session.");
|
||||||
|
}
|
||||||
const allBreaches = await getBreaches();
|
const allBreaches = await getBreaches();
|
||||||
const j = await req.json();
|
const j = await req.json();
|
||||||
const {
|
const {
|
||||||
|
@ -125,7 +121,10 @@ export async function PUT(req: NextRequest) {
|
||||||
// */
|
// */
|
||||||
|
|
||||||
const currentBreachDataTypes = currentBreaches[0].DataClasses; // get this from existing breaches
|
const currentBreachDataTypes = currentBreaches[0].DataClasses; // get this from existing breaches
|
||||||
const currentBreachResolution = subscriber.breach_resolution || {}; // get this from existing breach resolution if available
|
// Typed as `any` because `subscriber` used to be typed as `any`, and
|
||||||
|
// making that type more specific was enough work just by itself:
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const currentBreachResolution: any = subscriber.breach_resolution || {}; // get this from existing breach resolution if available
|
||||||
const isResolved =
|
const isResolved =
|
||||||
resolutionsChecked.length === currentBreachDataTypes.length;
|
resolutionsChecked.length === currentBreachDataTypes.length;
|
||||||
currentBreachResolution[affectedEmail] = {
|
currentBreachResolution[affectedEmail] = {
|
||||||
|
@ -146,6 +145,9 @@ export async function PUT(req: NextRequest) {
|
||||||
subscriber,
|
subscriber,
|
||||||
currentBreachResolution,
|
currentBreachResolution,
|
||||||
);
|
);
|
||||||
|
if (!updatedSubscriber) {
|
||||||
|
throw new Error("Could not retrieve updated subscriber data.");
|
||||||
|
}
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
|
@ -14,7 +14,6 @@ import { sendVerificationEmail } from "../../../utils/email";
|
||||||
import { validateEmailAddress } from "../../../../../utils/emailAddress";
|
import { validateEmailAddress } from "../../../../../utils/emailAddress";
|
||||||
import { getL10n } from "../../../../functions/server/l10n";
|
import { getL10n } from "../../../../functions/server/l10n";
|
||||||
import { initEmail } from "../../../../../utils/email";
|
import { initEmail } from "../../../../../utils/email";
|
||||||
import { Subscriber } from "../../../../deprecated/(authenticated)/user/breaches/breaches";
|
|
||||||
import { CONST_MAX_NUM_ADDRESSES } from "../../../../../constants";
|
import { CONST_MAX_NUM_ADDRESSES } from "../../../../../constants";
|
||||||
|
|
||||||
interface EmailAddRequest {
|
interface EmailAddRequest {
|
||||||
|
@ -28,11 +27,10 @@ export async function POST(req: NextRequest) {
|
||||||
if (typeof token?.subscriber?.fxa_uid === "string") {
|
if (typeof token?.subscriber?.fxa_uid === "string") {
|
||||||
try {
|
try {
|
||||||
const body: EmailAddRequest = await req.json();
|
const body: EmailAddRequest = await req.json();
|
||||||
const subscriber = (await getSubscriberByFxaUid(
|
const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid);
|
||||||
token.subscriber?.fxa_uid,
|
if (!subscriber) {
|
||||||
)) as Subscriber & {
|
throw new Error("No subscriber found for current session.");
|
||||||
email_addresses: Array<{ id: number; email: string }>;
|
}
|
||||||
};
|
|
||||||
const emailCount = 1 + (subscriber.email_addresses?.length ?? 0); // primary + verified + unverified emails
|
const emailCount = 1 + (subscriber.email_addresses?.length ?? 0); // primary + verified + unverified emails
|
||||||
const validatedEmail = validateEmailAddress(body.email);
|
const validatedEmail = validateEmailAddress(body.email);
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,9 @@ export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const { emailId }: EmailDeleteRequest = await req.json();
|
const { emailId }: EmailDeleteRequest = await req.json();
|
||||||
const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid);
|
const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid);
|
||||||
|
if (!subscriber) {
|
||||||
|
throw new Error("No subscriber found for current session.");
|
||||||
|
}
|
||||||
const existingEmail = await getEmailById(emailId);
|
const existingEmail = await getEmailById(emailId);
|
||||||
|
|
||||||
if (existingEmail?.subscriber_id !== subscriber.id) {
|
if (existingEmail?.subscriber_id !== subscriber.id) {
|
||||||
|
|
|
@ -25,6 +25,9 @@ export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const { emailId }: EmailResendRequest = await req.json();
|
const { emailId }: EmailResendRequest = await req.json();
|
||||||
const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid);
|
const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid);
|
||||||
|
if (!subscriber) {
|
||||||
|
throw new Error("No subscriber found for current session.");
|
||||||
|
}
|
||||||
const existingEmail = await getUserEmails(subscriber.id);
|
const existingEmail = await getUserEmails(subscriber.id);
|
||||||
|
|
||||||
const filteredEmail = existingEmail.filter(
|
const filteredEmail = existingEmail.filter(
|
||||||
|
|
|
@ -26,6 +26,9 @@ export async function POST(req: NextRequest) {
|
||||||
const { communicationOption }: EmailUpdateCommOptionRequest =
|
const { communicationOption }: EmailUpdateCommOptionRequest =
|
||||||
await req.json();
|
await req.json();
|
||||||
const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid);
|
const subscriber = await getSubscriberByFxaUid(token.subscriber?.fxa_uid);
|
||||||
|
if (!subscriber) {
|
||||||
|
throw new Error("No subscriber found for current session.");
|
||||||
|
}
|
||||||
// 0 = Send breach alerts to the corresponding affected emails.
|
// 0 = Send breach alerts to the corresponding affected emails.
|
||||||
// 1 = Send all breach alerts to user's primary email address.
|
// 1 = Send all breach alerts to user's primary email address.
|
||||||
const allEmailsToPrimary = Number(communicationOption) === 1 ?? false;
|
const allEmailsToPrimary = Number(communicationOption) === 1 ?? false;
|
||||||
|
|
|
@ -86,6 +86,9 @@ export async function POST(
|
||||||
session.user.subscriber.fxa_uid,
|
session.user.subscriber.fxa_uid,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!subscriber) {
|
||||||
|
throw new Error("No subscriber found for current session.");
|
||||||
|
}
|
||||||
if (!subscriber.onerep_profile_id) {
|
if (!subscriber.onerep_profile_id) {
|
||||||
// Create OneRep profile
|
// Create OneRep profile
|
||||||
const profileId = await createProfile(profileData);
|
const profileId = await createProfile(profileData);
|
||||||
|
|
|
@ -43,6 +43,9 @@ export async function GET(
|
||||||
const subscriber = await getSubscriberByFxaUid(
|
const subscriber = await getSubscriberByFxaUid(
|
||||||
session.user.subscriber?.fxa_uid,
|
session.user.subscriber?.fxa_uid,
|
||||||
);
|
);
|
||||||
|
if (!subscriber) {
|
||||||
|
throw new Error("No subscriber found for current session.");
|
||||||
|
}
|
||||||
const profileId = await getOnerepProfileId(subscriber.id);
|
const profileId = await getOnerepProfileId(subscriber.id);
|
||||||
|
|
||||||
const latestScan = await getLatestOnerepScanResults(profileId);
|
const latestScan = await getLatestOnerepScanResults(profileId);
|
||||||
|
|
|
@ -31,6 +31,9 @@ export async function GET() {
|
||||||
const subscriber = await getSubscriberByFxaUid(
|
const subscriber = await getSubscriberByFxaUid(
|
||||||
session.user.subscriber?.fxa_uid,
|
session.user.subscriber?.fxa_uid,
|
||||||
);
|
);
|
||||||
|
if (!subscriber) {
|
||||||
|
throw new Error("No subscriber found for current session.");
|
||||||
|
}
|
||||||
const profileId = await getOnerepProfileId(subscriber.id);
|
const profileId = await getOnerepProfileId(subscriber.id);
|
||||||
|
|
||||||
const scanResults = await getLatestOnerepScanResults(profileId);
|
const scanResults = await getLatestOnerepScanResults(profileId);
|
||||||
|
|
|
@ -31,6 +31,9 @@ import { GAParams } from "@next/third-parties/dist/types/google";
|
||||||
import Script, { ScriptProps } from "next/script";
|
import Script, { ScriptProps } from "next/script";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
// We don't send Analytics events in tests:
|
||||||
|
/* c8 ignore start */
|
||||||
|
|
||||||
let currDataLayerName: string | undefined = undefined;
|
let currDataLayerName: string | undefined = undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,9 +42,9 @@ let currDataLayerName: string | undefined = undefined;
|
||||||
* @param props
|
* @param props
|
||||||
*/
|
*/
|
||||||
export const GoogleAnalyticsWorkaround = (
|
export const GoogleAnalyticsWorkaround = (
|
||||||
props: GAParams & { nonce: ScriptProps["nonce"] },
|
props: GAParams & { nonce?: ScriptProps["nonce"]; debugMode?: boolean },
|
||||||
) => {
|
) => {
|
||||||
const { gaId, dataLayerName = "dataLayer", nonce } = props;
|
const { gaId, dataLayerName = "dataLayer", nonce, debugMode } = props;
|
||||||
|
|
||||||
if (currDataLayerName === undefined) {
|
if (currDataLayerName === undefined) {
|
||||||
currDataLayerName = dataLayerName;
|
currDataLayerName = dataLayerName;
|
||||||
|
@ -70,7 +73,7 @@ export const GoogleAnalyticsWorkaround = (
|
||||||
function gtag(){window['${dataLayerName}'].push(arguments);}
|
function gtag(){window['${dataLayerName}'].push(arguments);}
|
||||||
gtag('js', new Date());
|
gtag('js', new Date());
|
||||||
|
|
||||||
gtag('config', '${gaId}');`,
|
gtag('config', '${gaId}', { 'debug_mode': ${debugMode} });`,
|
||||||
}}
|
}}
|
||||||
nonce={nonce}
|
nonce={nonce}
|
||||||
/>
|
/>
|
||||||
|
@ -83,17 +86,22 @@ export const GoogleAnalyticsWorkaround = (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendGAEvent = (...args: object[]) => {
|
export const sendGAEvent = (type: "event", eventName: string, args: object) => {
|
||||||
|
if (process.env.NODE_ENV === "test") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (currDataLayerName === undefined) {
|
if (currDataLayerName === undefined) {
|
||||||
console.warn(`@next/third-parties: GA has not been initialized`);
|
console.warn(`@next/third-parties: GA has not been initialized`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window[currDataLayerName]) {
|
if (window[currDataLayerName]) {
|
||||||
window[currDataLayerName].push(...args);
|
window.gtag(type, eventName, args);
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(
|
||||||
`@next/third-parties: GA dataLayer ${currDataLayerName} does not exist`,
|
`@next/third-parties: GA dataLayer ${currDataLayerName} does not exist`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/* c8 ignore stop */
|
||||||
|
|
|
@ -16,13 +16,10 @@ import {
|
||||||
hasPremium,
|
hasPremium,
|
||||||
} from "../../functions/universal/user";
|
} from "../../functions/universal/user";
|
||||||
import styles from "./UpsellBadge.module.scss";
|
import styles from "./UpsellBadge.module.scss";
|
||||||
// TODO: The use of `useGA` is restricted and will be cleaned up
|
|
||||||
// together with MNTOR-2335.
|
|
||||||
// eslint-disable-next-line no-restricted-imports
|
|
||||||
import { useGa } from "../../hooks/useGa";
|
|
||||||
import { useTelemetry } from "../../hooks/useTelemetry";
|
import { useTelemetry } from "../../hooks/useTelemetry";
|
||||||
import { CountryCodeContext } from "../../../contextProviders/country-code";
|
import { CountryCodeContext } from "../../../contextProviders/country-code";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
|
import { sendGAEvent } from "./GoogleAnalyticsWorkaround";
|
||||||
|
|
||||||
export type UpsellButtonProps = {
|
export type UpsellButtonProps = {
|
||||||
monthlySubscriptionUrl: string;
|
monthlySubscriptionUrl: string;
|
||||||
|
@ -38,7 +35,6 @@ export function UpsellButton(
|
||||||
label: string;
|
label: string;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const { gtag } = useGa();
|
|
||||||
const recordTelemetry = useTelemetry();
|
const recordTelemetry = useTelemetry();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
@ -50,13 +46,9 @@ export function UpsellButton(
|
||||||
button_id: "nav_upsell",
|
button_id: "nav_upsell",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
gtag.record({
|
sendGAEvent("event", "premium_upsell_modal", {
|
||||||
type: "event",
|
action: isOpen ? "opened" : "closed",
|
||||||
name: "premium_upsell_modal",
|
page_location: pathname,
|
||||||
params: {
|
|
||||||
action: isOpen ? "opened" : "closed",
|
|
||||||
page_location: pathname,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
После Ширина: | Высота: | Размер: 12 KiB |
После Ширина: | Высота: | Размер: 4.1 KiB |
После Ширина: | Высота: | Размер: 5.9 KiB |
Двоичные данные
src/app/components/client/assets/data-brokers/absolutepeoplesearch.com.png
Normal file
После Ширина: | Высота: | Размер: 12 KiB |
После Ширина: | Высота: | Размер: 6.3 KiB |
Двоичные данные
src/app/components/client/assets/data-brokers/advancedbackgroundchecks.com.png
Normal file
После Ширина: | Высота: | Размер: 7.2 KiB |
Двоичные данные
src/app/components/client/assets/data-brokers/affordablebackgroundchecks.com.png
Normal file
После Ширина: | Высота: | Размер: 12 KiB |
После Ширина: | Высота: | Размер: 12 KiB |
После Ширина: | Высота: | Размер: 12 KiB |
После Ширина: | Высота: | Размер: 24 KiB |
После Ширина: | Высота: | Размер: 7.4 KiB |
После Ширина: | Высота: | Размер: 7.3 KiB |
После Ширина: | Высота: | Размер: 16 KiB |
После Ширина: | Высота: | Размер: 13 KiB |
Двоичные данные
src/app/components/client/assets/data-brokers/backgroundcheckers.net.png
Normal file
После Ширина: | Высота: | Размер: 6.3 KiB |
После Ширина: | Высота: | Размер: 9.7 KiB |
После Ширина: | Высота: | Размер: 10 KiB |
После Ширина: | Высота: | Размер: 10 KiB |
После Ширина: | Высота: | Размер: 7.2 KiB |
После Ширина: | Высота: | Размер: 6.2 KiB |
После Ширина: | Высота: | Размер: 11 KiB |
После Ширина: | Высота: | Размер: 19 KiB |
После Ширина: | Высота: | Размер: 5.7 KiB |
После Ширина: | Высота: | Размер: 10 KiB |
После Ширина: | Высота: | Размер: 2.9 KiB |
После Ширина: | Высота: | Размер: 12 KiB |
После Ширина: | Высота: | Размер: 13 KiB |
После Ширина: | Высота: | Размер: 7.7 KiB |
После Ширина: | Высота: | Размер: 10 KiB |
После Ширина: | Высота: | Размер: 8.9 KiB |
Двоичные данные
src/app/components/client/assets/data-brokers/fastbackgroundcheck.com.png
Normal file
После Ширина: | Высота: | Размер: 6.9 KiB |
После Ширина: | Высота: | Размер: 6.5 KiB |
После Ширина: | Высота: | Размер: 14 KiB |
Двоичные данные
src/app/components/client/assets/data-brokers/freebackgroundcheck.org.png
Normal file
После Ширина: | Высота: | Размер: 16 KiB |
Двоичные данные
src/app/components/client/assets/data-brokers/freepeopledirectory.com.png
Normal file
После Ширина: | Высота: | Размер: 6.4 KiB |
После Ширина: | Высота: | Размер: 9.5 KiB |
После Ширина: | Высота: | Размер: 8.2 KiB |
После Ширина: | Высота: | Размер: 19 KiB |
После Ширина: | Высота: | Размер: 5.8 KiB |
После Ширина: | Высота: | Размер: 19 KiB |
После Ширина: | Высота: | Размер: 6.1 KiB |
После Ширина: | Высота: | Размер: 8.7 KiB |
После Ширина: | Высота: | Размер: 6.5 KiB |
После Ширина: | Высота: | Размер: 8.4 KiB |
После Ширина: | Высота: | Размер: 3.1 KiB |
После Ширина: | Высота: | Размер: 10 KiB |
После Ширина: | Высота: | Размер: 8.6 KiB |
После Ширина: | Высота: | Размер: 6.0 KiB |
После Ширина: | Высота: | Размер: 9.5 KiB |
После Ширина: | Высота: | Размер: 7.0 KiB |
После Ширина: | Высота: | Размер: 11 KiB |
После Ширина: | Высота: | Размер: 6.2 KiB |
После Ширина: | Высота: | Размер: 5.8 KiB |
После Ширина: | Высота: | Размер: 10 KiB |
После Ширина: | Высота: | Размер: 7.1 KiB |
После Ширина: | Высота: | Размер: 8.5 KiB |
После Ширина: | Высота: | Размер: 11 KiB |
После Ширина: | Высота: | Размер: 6.3 KiB |
После Ширина: | Высота: | Размер: 7.0 KiB |
После Ширина: | Высота: | Размер: 12 KiB |
Двоичные данные
src/app/components/client/assets/data-brokers/people-background-check.com.png
Normal file
После Ширина: | Высота: | Размер: 5.8 KiB |
Двоичные данные
src/app/components/client/assets/data-brokers/people.yellowpages.com.png
Normal file
После Ширина: | Высота: | Размер: 5.5 KiB |
После Ширина: | Высота: | Размер: 12 KiB |
После Ширина: | Высота: | Размер: 9.0 KiB |
После Ширина: | Высота: | Размер: 11 KiB |
После Ширина: | Высота: | Размер: 4.5 KiB |
После Ширина: | Высота: | Размер: 4.8 KiB |