Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle decoding problems with passed in fides_string #4350

Merged
merged 12 commits into from
Oct 31, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The types of changes are:
- Refactor Fides.js embedded modal to not use A11y dialog [#4355](https://github.com/ethyca/fides/pull/4355)

### Fixed
- Handle invalid `fides_string` when passed in as an override [#4350](https://github.com/ethyca/fides/pull/4350)
- Bug where vendor opt-ins would not initialize properly based on a `fides_string` in the TCF overlay [#4368](https://github.com/ethyca/fides/pull/4368)

## [2.23.0](https://github.com/ethyca/fides/compare/2.22.1...2.23.0)
Expand Down
95 changes: 79 additions & 16 deletions clients/fides-js/src/fides-tcf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import {
import type { Fides } from "./lib/initialize";
import { dispatchFidesEvent } from "./lib/events";
import {
buildTcfEntitiesFromCookie,
debugLog,
experienceIsValid,
FidesCookie,
Expand Down Expand Up @@ -121,13 +122,43 @@ const getInitialPreference = (
return tcfObject.default_preference ?? UserConsentPreference.OPT_OUT;
};

const updateCookie = async (
oldCookie: FidesCookie,
experience: PrivacyExperience
): Promise<FidesCookie> => {
// ignore server-side prefs if either user has no prefs to override, or TC str override is set
const updateCookieAndExperience = async ({
cookie,
experience,
debug = false,
hasValidFidesStringOverride,
isExperienceClientSideFetched,
}: {
cookie: FidesCookie;
experience: PrivacyExperience;
debug?: boolean;
hasValidFidesStringOverride: boolean;
isExperienceClientSideFetched: boolean;
}): Promise<{
cookie: FidesCookie;
experience: Partial<PrivacyExperience>;
}> => {
// If string override exists and is valid, the cookie has already been overridden
if (hasValidFidesStringOverride) {
// However, if we didn't get the experience until the client side fetch, we need to update the fetched
// experience based on the cookie here.
if (isExperienceClientSideFetched) {
debugLog(
debug,
"Overriding preferences from client-side fetched experience with cookie fides_string consent",
cookie.fides_string
);
const tcfEntities = buildTcfEntitiesFromCookie(experience, cookie);
return { cookie, experience: tcfEntities };
}
// If it's not client side fetched, we don't update anything since the cookie has already
// been updated earlier.
return { cookie, experience };
}

// If user has no prefs saved, we don't need to override the prefs on the cookie
if (!hasSavedTcfPreferences(experience)) {
return { ...oldCookie, fides_string: "" };
return { cookie: { ...cookie, fides_string: "" }, experience };
}

const tcSavePrefs: TcfSavePreferences = {};
Expand Down Expand Up @@ -165,7 +196,38 @@ const updateCookie = async (
tcStringPreferences: enabledIds,
});
const tcfConsent = transformTcfPreferencesToCookieKeys(tcSavePrefs);
return { ...oldCookie, fides_string: fidesString, tcf_consent: tcfConsent };
return {
cookie: { ...cookie, fides_string: fidesString, tcf_consent: tcfConsent },
experience,
};
};

/**
* If a fidesString is explicitly passed in, we override the associated cookie props, which are then
* used to override associated props in the experience.
*/
const updateFidesCookieFromString = (
allisonking marked this conversation as resolved.
Show resolved Hide resolved
cookie: FidesCookie,
fidesString: string,
debug: boolean
): { cookie: FidesCookie; success: boolean } => {
debugLog(
debug,
"Explicit fidesString detected. Proceeding to override all TCF preferences with given fidesString"
);
try {
const cookieKeys = transformFidesStringToCookieKeys(fidesString, debug);
return {
cookie: { ...cookie, tcf_consent: cookieKeys, fides_string: fidesString },
success: true,
};
} catch (error) {
debugLog(
debug,
`Could not decode tcString from ${fidesString}, it may be invalid. ${error}`
);
return { cookie, success: false };
}
};

/**
Expand All @@ -177,18 +239,18 @@ const init = async (config: FidesConfig) => {
// eslint-disable-next-line no-param-reassign
config.options = { ...config.options, ...overrideOptions };
const cookie = getInitialCookie(config);
let hasValidFidesStringOverride = !!config.options.fidesString;
if (config.options.fidesString) {
// If a fidesString is explicitly passed in, we override the associated cookie props, which are then used to
// override associated props in experience
debugLog(
config.options.debug,
"Explicit fidesString detected. Proceeding to override all TCF preferences with given fidesString"
);
cookie.fides_string = config.options.fidesString;
cookie.tcf_consent = transformFidesStringToCookieKeys(
const { cookie: updatedCookie, success } = updateFidesCookieFromString(
cookie,
config.options.fidesString,
config.options.debug
);
if (success) {
Object.assign(cookie, updatedCookie);
} else {
hasValidFidesStringOverride = false;
}
} else if (
tcfConsentCookieObjHasSomeConsentSet(cookie.tcf_consent) &&
!cookie.fides_string &&
Expand Down Expand Up @@ -221,7 +283,8 @@ const init = async (config: FidesConfig) => {
cookie,
experience,
renderOverlay,
updateCookie,
updateCookieAndExperience: (props) =>
allisonking marked this conversation as resolved.
Show resolved Hide resolved
updateCookieAndExperience({ ...props, hasValidFidesStringOverride }),
});
Object.assign(_Fides, updatedFides);

Expand Down
10 changes: 7 additions & 3 deletions clients/fides-js/src/fides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@ const updateCookie = async (
oldCookie: FidesCookie,
experience: PrivacyExperience,
debug?: boolean
): Promise<FidesCookie> => {
): Promise<{ cookie: FidesCookie; experience: PrivacyExperience }> => {
const context = getConsentContext();
const consent = buildCookieConsentForExperiences(
experience,
context,
!!debug
);
return { ...oldCookie, consent };
return { cookie: { ...oldCookie, consent }, experience };
};

/**
Expand All @@ -122,7 +122,11 @@ const init = async (config: FidesConfig) => {
cookie,
experience,
renderOverlay,
updateCookie,
updateCookieAndExperience: ({
cookie: oldCookie,
experience: effectiveExperience,
debug,
}) => updateCookie(oldCookie, effectiveExperience, debug),
});
Object.assign(_Fides, updatedFides);

Expand Down
63 changes: 28 additions & 35 deletions clients/fides-js/src/lib/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { meta } from "../integrations/meta";
import { shopify } from "../integrations/shopify";
import { getConsentContext } from "./consent-context";
import {
buildTcfEntitiesFromCookie,
CookieIdentity,
CookieKeyConsent,
CookieMeta,
Expand Down Expand Up @@ -259,15 +258,28 @@ export const initialize = async ({
experience,
geolocation,
renderOverlay,
updateCookie,
updateCookieAndExperience,
}: {
cookie: FidesCookie;
renderOverlay: (props: OverlayProps, parent: ContainerNode) => void;
updateCookie: (
oldCookie: FidesCookie,
experience: PrivacyExperience,
debug?: boolean
) => Promise<FidesCookie>;
/**
* Once we for sure have a valid experience, this is another chance to update values
* before the overlay renders.
*/
updateCookieAndExperience: ({
cookie,
experience,
debug,
isExperienceClientSideFetched,
}: {
cookie: FidesCookie;
experience: PrivacyExperience;
debug?: boolean;
isExperienceClientSideFetched: boolean;
}) => Promise<{
cookie: FidesCookie;
experience: Partial<PrivacyExperience>;
}>;
} & FidesConfig): Promise<Partial<Fides>> => {
let shouldInitOverlay: boolean = options.isOverlayEnabled;
let effectiveExperience = experience;
Expand Down Expand Up @@ -312,34 +324,15 @@ export const initialize = async ({
isPrivacyExperience(effectiveExperience) &&
experienceIsValid(effectiveExperience, options)
) {
if (options.fidesString) {
if (fetchedClientSideExperience) {
// if tc str was explicitly passed in, we need to override the client-side-fetched experience with consent from the cookie
// we don't update cookie because it already has been overridden by the injected fidesString
debugLog(
options.debug,
"Overriding preferences from client-side fetched experience with cookie fides_string consent",
cookie.fides_string
);
const tcfEntities = buildTcfEntitiesFromCookie(
effectiveExperience,
cookie
);
Object.assign(effectiveExperience, tcfEntities);
}
} else {
const updatedCookie = await updateCookie(
cookie,
effectiveExperience,
options.debug
);
debugLog(
options.debug,
"Updated cookie based on experience",
updatedCookie
);
Object.assign(cookie, updatedCookie);
}
const updated = await updateCookieAndExperience({
cookie,
experience: effectiveExperience,
debug: options.debug,
isExperienceClientSideFetched: fetchedClientSideExperience,
});
debugLog(options.debug, "Updated cookie and experience", updated);
Object.assign(cookie, updated.cookie);
Object.assign(effectiveExperience, updated.experience);
if (shouldInitOverlay) {
await initOverlay({
experience: effectiveExperience,
Expand Down
Loading