From 3bbe48df2da02006856330b35c9d48e251ef7159 Mon Sep 17 00:00:00 2001 From: Allison King Date: Wed, 6 Sep 2023 18:13:02 -0400 Subject: [PATCH 1/8] First pass at handling legint --- .../src/components/tcf/TcfOverlay.tsx | 22 +++-- clients/fides-js/src/fides-tcf.ts | 8 +- clients/fides-js/src/lib/tcf.ts | 89 +++++++++++++++++-- 3 files changed, 102 insertions(+), 17 deletions(-) diff --git a/clients/fides-js/src/components/tcf/TcfOverlay.tsx b/clients/fides-js/src/components/tcf/TcfOverlay.tsx index 33c575dc84..eca2fa3712 100644 --- a/clients/fides-js/src/components/tcf/TcfOverlay.tsx +++ b/clients/fides-js/src/components/tcf/TcfOverlay.tsx @@ -136,6 +136,18 @@ const createTcfSavePayload = ({ }) as TCFVendorSave[], }); +const updateCookie = async ( + oldCookie: FidesCookie, + tcf: TcfSavePreferences, + experience: PrivacyExperience +) => { + const tcString = await generateTcString({ + tcStringPreferences: tcf, + experience, + }); + return { ...oldCookie, tcString }; +}; + const TcfOverlay: FunctionComponent = ({ fidesRegionString, experience, @@ -177,14 +189,6 @@ const TcfOverlay: FunctionComponent = ({ [draftIds] ); - const updateCookie = async ( - oldCookie: FidesCookie, - tcf: TcfSavePreferences - ) => { - const tcString = await generateTcString(tcf); - return { ...oldCookie, tcString }; - }; - const handleUpdateAllPreferences = useCallback( (enabledIds: EnabledIds) => { const tcf = createTcfSavePayload({ experience, enabledIds }); @@ -198,7 +202,7 @@ const TcfOverlay: FunctionComponent = ({ debug: options.debug, servedNotices: null, // TODO: served notices tcf, - updateCookie: (oldCookie) => updateCookie(oldCookie, tcf), + updateCookie: (oldCookie) => updateCookie(oldCookie, tcf, experience), }); setDraftIds(enabledIds); }, diff --git a/clients/fides-js/src/fides-tcf.ts b/clients/fides-js/src/fides-tcf.ts index 4fd426ea2f..9daa749da0 100644 --- a/clients/fides-js/src/fides-tcf.ts +++ b/clients/fides-js/src/fides-tcf.ts @@ -104,7 +104,7 @@ const updateCookie = async ( return { ...oldCookie, tcString: "" }; } - const tcSavePrefs: TcfSavePreferences = { + const tcStringPreferences: TcfSavePreferences = { purpose_preferences: experience.tcf_purposes?.map((purpose) => ({ id: purpose.id, preference: getInitialPreference(purpose), @@ -129,9 +129,13 @@ const updateCookie = async ( id: vendor.id, preference: getInitialPreference(vendor), })), + system_preferences: experience.tcf_systems?.map((system) => ({ + id: system.id, + preference: getInitialPreference(system), + })), }; - const tcString = await generateTcString(tcSavePrefs); + const tcString = await generateTcString({ tcStringPreferences, experience }); return { ...oldCookie, tcString }; }; diff --git a/clients/fides-js/src/lib/tcf.ts b/clients/fides-js/src/lib/tcf.ts index c42e6d3fe2..c2e51e360f 100644 --- a/clients/fides-js/src/lib/tcf.ts +++ b/clients/fides-js/src/lib/tcf.ts @@ -15,19 +15,48 @@ import { import { makeStub } from "./tcf/stub"; import { transformUserPreferenceToBoolean } from "./consent-utils"; import gvlJson from "./tcf/gvl.json"; -import { TcfSavePreferences } from "./tcf/types"; +import { + LegalBasisForProcessingEnum, + TCFPurposeRecord, + TcfSavePreferences, +} from "./tcf/types"; import { vendorIsGvl } from "./tcf/vendors"; +import { PrivacyExperience } from "./consent-types"; const CMP_ID = 12; // TODO: hardcode our unique CMP ID after certification const CMP_VERSION = 1; +const FORBIDDEN_LEGITIMATE_INTEREST_PURPOSE_IDS = [1, 3, 4, 5, 6]; + +const purposeHasLegalBasis = ({ + id, + purposes, + legalBasis, +}: { + id: number; + purposes: TCFPurposeRecord[] | undefined; + legalBasis: LegalBasisForProcessingEnum; +}) => { + if (!purposes) { + return false; + } + const purpose = purposes.filter((p) => p.id === id)[0]; + if (!purpose) { + return false; + } + return purpose.legal_bases?.indexOf(legalBasis) !== -1; +}; /** * Generate TC String based on TCF-related info from privacy experience. * Called when there is either a FidesInitialized or FidesUpdated event */ -export const generateTcString = async ( - tcStringPreferences?: TcfSavePreferences -): Promise => { +export const generateTcString = async ({ + experience, + tcStringPreferences, +}: { + tcStringPreferences?: TcfSavePreferences; + experience: PrivacyExperience; +}): Promise => { // Creates a new TC string based on an old GVL version // (https://vendor-list.consensu.org/v2/archives/vendor-list-v1.json) // due to TCF library not yet supporting latest GVL (https://vendor-list.consensu.org/v3/vendor-list.json). @@ -45,7 +74,6 @@ export const generateTcString = async ( tcModel.consentScreen = 1; // todo- On which 'screen' consent was captured; this is a CMP proprietary number encoded into the TC string if (tcStringPreferences) { - // todo- when we set vendorLegitimateInterests, make sure we never set purposes 1, 3, 4, 5 and 6 if ( tcStringPreferences.vendor_preferences && tcStringPreferences.vendor_preferences.length > 0 @@ -56,6 +84,35 @@ export const generateTcString = async ( ); if (consented && vendorIsGvl(vendorPreference)) { tcModel.vendorConsents.set(+vendorPreference.id); + tcModel.vendorLegitimateInterests.set(+vendorPreference.id); + // // Get the purposes associated with this vendor + // const thisVendor = experience.tcf_vendors?.filter( + // (v) => v.id === vendorPreference.id + // )[0]; + // const vendorPurposes = thisVendor?.purposes; + // console.log({ vendorPurposes }); + // if ( + // vendorPurposes?.some( + // (p) => + // p.legal_bases?.indexOf(LegalBasisForProcessingEnum.CONSENT) !== + // -1 + // ) + // ) { + // tcModel.vendorConsents.set(+vendorPreference.id); + // } + // if ( + // vendorPurposes?.some( + // (p) => + // p.legal_bases?.indexOf( + // LegalBasisForProcessingEnum.LEGITIMATE_INTERESTS + // ) !== -1 && + // // per the IAB, make sure we never set purposes 1, 3, 4, 5, or 6 + // FORBIDDEN_LEGITIMATE_INTEREST_PURPOSE_IDS.indexOf(p.id) === -1 + // ) + // ) { + // console.log("setting vendor legint", vendorPreference.id); + // tcModel.vendorLegitimateInterests.set(+vendorPreference.id); + // } } }); } @@ -70,7 +127,27 @@ export const generateTcString = async ( purposePreference.preference ); if (consented) { - tcModel.purposeConsents.set(+purposePreference.id); + const id = +purposePreference.id; + if ( + purposeHasLegalBasis({ + id, + purposes: experience.tcf_purposes, + legalBasis: LegalBasisForProcessingEnum.CONSENT, + }) + ) { + tcModel.purposeConsents.set(id); + } + if ( + purposeHasLegalBasis({ + id, + purposes: experience.tcf_purposes, + legalBasis: LegalBasisForProcessingEnum.LEGITIMATE_INTERESTS, + }) && + // per the IAB, make sure we never set purposes 1, 3, 4, 5, or 6 + FORBIDDEN_LEGITIMATE_INTEREST_PURPOSE_IDS.indexOf(id) === -1 + ) { + tcModel.purposeLegitimateInterests.set(id); + } } }); } From 18f3b21fdd576b8dcaae2afe442927fcb3eac1cc Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 7 Sep 2023 11:55:02 -0400 Subject: [PATCH 2/8] Fix logic for rendering the banner --- clients/fides-js/src/fides-tcf.ts | 8 ++++++-- clients/fides-js/src/lib/consent-utils.ts | 24 ++++++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/clients/fides-js/src/fides-tcf.ts b/clients/fides-js/src/fides-tcf.ts index 9daa749da0..81d699494d 100644 --- a/clients/fides-js/src/fides-tcf.ts +++ b/clients/fides-js/src/fides-tcf.ts @@ -64,7 +64,11 @@ import { } from "./lib/initialize"; import type { Fides } from "./lib/initialize"; import { dispatchFidesEvent } from "./lib/events"; -import { FidesCookie, hasSavedTcfPreferences, isNewFidesCookie } from "./fides"; +import { + FidesCookie, + hasNoSavedTcfPreferences, + isNewFidesCookie, +} from "./fides"; import { renderOverlay } from "./lib/tcf/renderOverlay"; import { TCFPurposeRecord, TcfSavePreferences } from "./lib/tcf/types"; @@ -100,7 +104,7 @@ const updateCookie = async ( experience: PrivacyExperience ) => { // First check if the user has never consented before - if (!hasSavedTcfPreferences(experience)) { + if (!hasNoSavedTcfPreferences(experience)) { return { ...oldCookie, tcString: "" }; } diff --git a/clients/fides-js/src/lib/consent-utils.ts b/clients/fides-js/src/lib/consent-utils.ts index 85fd6a6abb..5c9d0f12b1 100644 --- a/clients/fides-js/src/lib/consent-utils.ts +++ b/clients/fides-js/src/lib/consent-utils.ts @@ -197,22 +197,40 @@ const hasCurrentPreference = ( return records.some((record) => record.current_preference); }; +const hasActionNeededTcfPreference = ( + records: Pick[] | undefined +) => { + if (!records || records.length === 0) { + return false; + } + return records.some((record) => record.current_preference == null); +}; + /** * Returns true if the user has any saved TCF preferences */ -export const hasSavedTcfPreferences = (experience: PrivacyExperience) => +export const hasNoSavedTcfPreferences = (experience: PrivacyExperience) => hasCurrentPreference(experience.tcf_purposes) || hasCurrentPreference(experience.tcf_special_purposes) || hasCurrentPreference(experience.tcf_features) || hasCurrentPreference(experience.tcf_special_features) || - hasCurrentPreference(experience.tcf_vendors); + hasCurrentPreference(experience.tcf_vendors) || + hasCurrentPreference(experience.tcf_systems); + +export const hasActionNeededTcfPreferences = (experience: PrivacyExperience) => + hasActionNeededTcfPreference(experience.tcf_purposes) || + hasActionNeededTcfPreference(experience.tcf_special_purposes) || + hasActionNeededTcfPreference(experience.tcf_features) || + hasActionNeededTcfPreference(experience.tcf_special_features) || + hasActionNeededTcfPreference(experience.tcf_vendors) || + hasActionNeededTcfPreference(experience.tcf_systems); /** * Returns true if there are notices in the experience that require a user preference */ export const hasActionNeededNotices = (experience: PrivacyExperience) => { if (experience.component === ComponentType.TCF_OVERLAY) { - return !hasSavedTcfPreferences(experience); + return hasActionNeededTcfPreferences(experience); } return Boolean( experience?.privacy_notices?.some( From 464b5e4d9bd9273f34b50a1f1619db791e0460e4 Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 7 Sep 2023 11:55:19 -0400 Subject: [PATCH 3/8] Add cypress tests --- .../cypress/e2e/consent-banner-tcf.cy.ts | 139 +++++++++++++++++- .../cypress/support/commands.ts | 9 ++ 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts index e89947abd7..991df7bd55 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -1,4 +1,9 @@ -import { CONSENT_COOKIE_NAME } from "fides-js"; +/* eslint-disable no-underscore-dangle */ +import { + CONSENT_COOKIE_NAME, + PrivacyExperience, + UserConsentPreference, +} from "fides-js"; import { stubConfig } from "../support/stubs"; const PURPOSE_1 = { @@ -64,6 +69,86 @@ describe("Fides-js TCF", () => { }); }); + describe("banner appears when it should", () => { + const setAllTcfToValue = ( + experience: PrivacyExperience, + value: UserConsentPreference | undefined + ): PrivacyExperience => { + const purposes = experience.tcf_purposes?.map((p) => ({ + ...p, + current_preference: value, + })); + const specialPurposes = experience.tcf_special_purposes?.map((p) => ({ + ...p, + current_preference: value, + })); + const features = experience.tcf_features?.map((f) => ({ + ...f, + current_preference: value, + })); + const specialFeatures = experience.tcf_special_features?.map((f) => ({ + ...f, + current_preference: value, + })); + const vendors = experience.tcf_vendors?.map((v) => ({ + ...v, + current_preference: value, + })); + const systems = experience.tcf_systems?.map((s) => ({ + ...s, + current_preference: value, + })); + return { + ...experience, + tcf_purposes: purposes, + tcf_special_purposes: specialPurposes, + tcf_features: features, + tcf_special_features: specialFeatures, + tcf_vendors: vendors, + tcf_systems: systems, + }; + }; + it("banner should not appear if everything already has a preference", () => { + cy.fixture("consent/experience_tcf.json").then((payload) => { + const experience = payload.items[0]; + const updatedExperience = setAllTcfToValue( + experience, + UserConsentPreference.OPT_IN + ); + stubConfig({ + options: { + isOverlayEnabled: true, + tcfEnabled: true, + }, + experience: updatedExperience, + }); + cy.waitUntilFidesInitialized().then(() => { + cy.get("div#fides-banner").should("not.exist"); + }); + }); + }); + it("should render the banner if there is even one preference that is not set", () => { + cy.fixture("consent/experience_tcf.json").then((payload) => { + const experience = payload.items[0]; + const updatedExperience = setAllTcfToValue( + experience, + UserConsentPreference.OPT_IN + ); + updatedExperience.tcf_purposes![0].current_preference = undefined; + stubConfig({ + options: { + isOverlayEnabled: true, + tcfEnabled: true, + }, + experience: updatedExperience, + }); + cy.waitUntilFidesInitialized().then(() => { + cy.get("div#fides-banner"); + }); + }); + }); + }); + describe("initial layer", () => { beforeEach(() => { cy.get("#fides-modal-link").click(); @@ -282,4 +367,56 @@ describe("Fides-js TCF", () => { }); }); }); + + describe("cmp api", () => { + beforeEach(() => { + cy.window().then((win) => { + win.__tcfapi("addEventListener", 2, cy.stub().as("TCFEvent")); + }); + cy.get("#fides-modal-link").click(); + }); + + it("can receive a cmpuishown event", () => { + cy.get("@TCFEvent") + .its("firstCall.args") + .then(([tcData, success]) => { + expect(success).to.eql(true); + expect(tcData.eventStatus === "cmpuishown"); + }); + }); + + describe("setting fields", () => { + it("can opt in to all and set legitimate interests", () => { + cy.getByTestId("consent-modal").within(() => { + cy.get("button").contains("Opt in to all").click(); + }); + cy.get("@TCFEvent") + .its("lastCall.args") + .then(([tcData, success]) => { + expect(success).to.eql(true); + expect(tcData.eventStatus === "useractioncomplete"); + expect(tcData.purpose.consents).to.eql({ + [PURPOSE_1.id]: true, + [PURPOSE_2.id]: true, + [PURPOSE_3.id]: true, + [PURPOSE_4.id]: true, + 1: false, + 2: false, + 3: false, + 5: false, + 8: false, + }); + expect(tcData.purpose.legitimateInterests).to.eql({ + [PURPOSE_5.id]: true, + 1: false, + }); + expect(tcData.vendor.consents).to.eql({ + 1: false, + [VENDOR_2.id]: true, + }); + expect(tcData.vendor.legitimateInterests).to.eql({}); + }); + }); + }); + }); }); diff --git a/clients/privacy-center/cypress/support/commands.ts b/clients/privacy-center/cypress/support/commands.ts index 9918944a92..f5499492d0 100644 --- a/clients/privacy-center/cypress/support/commands.ts +++ b/clients/privacy-center/cypress/support/commands.ts @@ -181,6 +181,15 @@ declare global { interface Window { fidesConfig?: FidesConfig; dataLayer?: Array; + __tcfapi: ( + command: string, + version: number, + // tcData should be type TCData from the IAB's TCF library. + // We could consider adding that library here, or reexporting just the type + // from fides-js + callback: (tcData: any, success: boolean) => void, + parameter?: number | string + ) => void; } } From c8609ded042c476a7fb388f8e6dd28fe366cdad1 Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 7 Sep 2023 11:57:57 -0400 Subject: [PATCH 4/8] Clean up unused code --- clients/fides-js/src/lib/tcf.ts | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/clients/fides-js/src/lib/tcf.ts b/clients/fides-js/src/lib/tcf.ts index c2e51e360f..2daa83e0c3 100644 --- a/clients/fides-js/src/lib/tcf.ts +++ b/clients/fides-js/src/lib/tcf.ts @@ -85,34 +85,6 @@ export const generateTcString = async ({ if (consented && vendorIsGvl(vendorPreference)) { tcModel.vendorConsents.set(+vendorPreference.id); tcModel.vendorLegitimateInterests.set(+vendorPreference.id); - // // Get the purposes associated with this vendor - // const thisVendor = experience.tcf_vendors?.filter( - // (v) => v.id === vendorPreference.id - // )[0]; - // const vendorPurposes = thisVendor?.purposes; - // console.log({ vendorPurposes }); - // if ( - // vendorPurposes?.some( - // (p) => - // p.legal_bases?.indexOf(LegalBasisForProcessingEnum.CONSENT) !== - // -1 - // ) - // ) { - // tcModel.vendorConsents.set(+vendorPreference.id); - // } - // if ( - // vendorPurposes?.some( - // (p) => - // p.legal_bases?.indexOf( - // LegalBasisForProcessingEnum.LEGITIMATE_INTERESTS - // ) !== -1 && - // // per the IAB, make sure we never set purposes 1, 3, 4, 5, or 6 - // FORBIDDEN_LEGITIMATE_INTEREST_PURPOSE_IDS.indexOf(p.id) === -1 - // ) - // ) { - // console.log("setting vendor legint", vendorPreference.id); - // tcModel.vendorLegitimateInterests.set(+vendorPreference.id); - // } } }); } From 7f7efddd1b42d7d0c445ddc6c5d30901e2774462 Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 7 Sep 2023 12:40:15 -0400 Subject: [PATCH 5/8] Add check and tests for setting forbidden legints --- clients/fides-js/src/lib/tcf.ts | 30 +++++++++- .../cypress/e2e/consent-banner-tcf.cy.ts | 56 ++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/clients/fides-js/src/lib/tcf.ts b/clients/fides-js/src/lib/tcf.ts index 2daa83e0c3..392a1fe394 100644 --- a/clients/fides-js/src/lib/tcf.ts +++ b/clients/fides-js/src/lib/tcf.ts @@ -43,7 +43,7 @@ const purposeHasLegalBasis = ({ if (!purpose) { return false; } - return purpose.legal_bases?.indexOf(legalBasis) !== -1; + return purpose.legal_bases?.includes(legalBasis); }; /** @@ -84,7 +84,31 @@ export const generateTcString = async ({ ); if (consented && vendorIsGvl(vendorPreference)) { tcModel.vendorConsents.set(+vendorPreference.id); - tcModel.vendorLegitimateInterests.set(+vendorPreference.id); + const thisVendor = experience.tcf_vendors?.filter( + (v) => v.id === vendorPreference.id + )[0]; + const vendorPurposes = thisVendor?.purposes; + // Handle the case where a vendor has forbidden legint purposes set + let skipSetLegInt = false; + if (vendorPurposes) { + const legIntPurposeIds = vendorPurposes + .filter((p) => + p.legal_bases?.includes( + LegalBasisForProcessingEnum.LEGITIMATE_INTERESTS + ) + ) + .map((p) => p.id); + if ( + legIntPurposeIds.filter((id) => + FORBIDDEN_LEGITIMATE_INTEREST_PURPOSE_IDS.includes(id) + ).length + ) { + skipSetLegInt = true; + } + } + if (!skipSetLegInt) { + tcModel.vendorLegitimateInterests.set(+vendorPreference.id); + } } }); } @@ -116,7 +140,7 @@ export const generateTcString = async ({ legalBasis: LegalBasisForProcessingEnum.LEGITIMATE_INTERESTS, }) && // per the IAB, make sure we never set purposes 1, 3, 4, 5, or 6 - FORBIDDEN_LEGITIMATE_INTEREST_PURPOSE_IDS.indexOf(id) === -1 + !FORBIDDEN_LEGITIMATE_INTEREST_PURPOSE_IDS.includes(id) ) { tcModel.purposeLegitimateInterests.set(id); } diff --git a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts index 991df7bd55..edbebea80f 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -394,7 +394,7 @@ describe("Fides-js TCF", () => { .its("lastCall.args") .then(([tcData, success]) => { expect(success).to.eql(true); - expect(tcData.eventStatus === "useractioncomplete"); + expect(tcData.eventStatus).to.eql("useractioncomplete"); expect(tcData.purpose.consents).to.eql({ [PURPOSE_1.id]: true, [PURPOSE_2.id]: true, @@ -417,6 +417,60 @@ describe("Fides-js TCF", () => { expect(tcData.vendor.legitimateInterests).to.eql({}); }); }); + + it("can handle inappropriate legint purposes", () => { + cy.fixture("consent/experience_tcf.json").then((payload) => { + const experience: PrivacyExperience = payload.items[0]; + // Set purpose with id 4 to LegInt which is not allowed! + experience.tcf_purposes![1].legal_bases = ["Legitimate interests"]; + // Set the corresponding embedded vendor purpose too + experience.tcf_vendors![0].purposes![0].legal_bases = [ + "Legitimate interests", + ]; + stubConfig({ + options: { + isOverlayEnabled: true, + tcfEnabled: true, + }, + experience, + }); + }); + // Since we've changed windows, redeclare the stub too + cy.window().then((win) => { + win.__tcfapi("addEventListener", 2, cy.stub().as("TCFEvent2")); + }); + cy.get("#fides-modal-link").click(); + cy.getByTestId("consent-modal").within(() => { + cy.get("button").contains("Opt in to all").click(); + }); + cy.get("@TCFEvent2") + .its("lastCall.args") + .then(([tcData, success]) => { + expect(success).to.eql(true); + expect(tcData.eventStatus).to.eql("useractioncomplete"); + expect(tcData.purpose.consents).to.eql({ + 4: false, + [PURPOSE_2.id]: true, + [PURPOSE_3.id]: true, + [PURPOSE_4.id]: true, + 1: false, + 2: false, + 3: false, + 5: false, + 8: false, + }); + expect(tcData.purpose.legitimateInterests).to.eql({ + // No id 4 here! + [PURPOSE_5.id]: true, + 1: false, + }); + expect(tcData.vendor.consents).to.eql({ + 1: false, + [VENDOR_2.id]: true, + }); + expect(tcData.vendor.legitimateInterests).to.eql({}); + }); + }); }); }); }); From c7b139e9288cd4acfb1462134f96f8881fd6555b Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 7 Sep 2023 12:47:38 -0400 Subject: [PATCH 6/8] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e311ed5132..3027dc0cdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ The types of changes are: ## [Unreleased](https://github.com/ethyca/fides/compare/2.19.0...main) +### Added +- TCF modal now supports setting legitimate interest fields [#4037](https://github.com/ethyca/fides/pull/4037) + ## [2.19.0](https://github.com/ethyca/fides/compare/2.18.0...2.19.0) ### Added From c736d5a035e7b2d8b266dfb3c723f8eb4f99917f Mon Sep 17 00:00:00 2001 From: Allison King Date: Fri, 8 Sep 2023 15:27:30 -0400 Subject: [PATCH 7/8] Fix function name --- clients/fides-js/src/fides-tcf.ts | 8 ++------ clients/fides-js/src/lib/consent-utils.ts | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/clients/fides-js/src/fides-tcf.ts b/clients/fides-js/src/fides-tcf.ts index 81d699494d..9daa749da0 100644 --- a/clients/fides-js/src/fides-tcf.ts +++ b/clients/fides-js/src/fides-tcf.ts @@ -64,11 +64,7 @@ import { } from "./lib/initialize"; import type { Fides } from "./lib/initialize"; import { dispatchFidesEvent } from "./lib/events"; -import { - FidesCookie, - hasNoSavedTcfPreferences, - isNewFidesCookie, -} from "./fides"; +import { FidesCookie, hasSavedTcfPreferences, isNewFidesCookie } from "./fides"; import { renderOverlay } from "./lib/tcf/renderOverlay"; import { TCFPurposeRecord, TcfSavePreferences } from "./lib/tcf/types"; @@ -104,7 +100,7 @@ const updateCookie = async ( experience: PrivacyExperience ) => { // First check if the user has never consented before - if (!hasNoSavedTcfPreferences(experience)) { + if (!hasSavedTcfPreferences(experience)) { return { ...oldCookie, tcString: "" }; } diff --git a/clients/fides-js/src/lib/consent-utils.ts b/clients/fides-js/src/lib/consent-utils.ts index 5c9d0f12b1..acfdc8c834 100644 --- a/clients/fides-js/src/lib/consent-utils.ts +++ b/clients/fides-js/src/lib/consent-utils.ts @@ -209,7 +209,7 @@ const hasActionNeededTcfPreference = ( /** * Returns true if the user has any saved TCF preferences */ -export const hasNoSavedTcfPreferences = (experience: PrivacyExperience) => +export const hasSavedTcfPreferences = (experience: PrivacyExperience) => hasCurrentPreference(experience.tcf_purposes) || hasCurrentPreference(experience.tcf_special_purposes) || hasCurrentPreference(experience.tcf_features) || From 4603405bbdc6e58e408b5448e47e2d8ef7df2d73 Mon Sep 17 00:00:00 2001 From: Allison King Date: Fri, 8 Sep 2023 15:27:37 -0400 Subject: [PATCH 8/8] Update comment to defer --- clients/privacy-center/cypress/support/commands.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/clients/privacy-center/cypress/support/commands.ts b/clients/privacy-center/cypress/support/commands.ts index f5499492d0..74a4779801 100644 --- a/clients/privacy-center/cypress/support/commands.ts +++ b/clients/privacy-center/cypress/support/commands.ts @@ -184,9 +184,8 @@ declare global { __tcfapi: ( command: string, version: number, - // tcData should be type TCData from the IAB's TCF library. - // We could consider adding that library here, or reexporting just the type - // from fides-js + // DEFER: tcData should be type TCData from the IAB's TCF library. + // Once we are importing that library, replace this `any` type. callback: (tcData: any, success: boolean) => void, parameter?: number | string ) => void;