Skip to content

Commit

Permalink
Handle decoding problems with passed in fides_string (#4350)
Browse files Browse the repository at this point in the history
  • Loading branch information
allisonking authored and Kelsey-Ethyca committed Nov 1, 2023
1 parent 561f828 commit 97dbb02
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 68 deletions.
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 @@ -120,13 +121,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 @@ -164,7 +195,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 = (
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 @@ -176,18 +238,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 @@ -219,7 +281,8 @@ const init = async (config: FidesConfig) => {
cookie,
experience,
renderOverlay,
updateCookie,
updateCookieAndExperience: (props) =>
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 @@ -87,14 +87,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 @@ -117,7 +117,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

0 comments on commit 97dbb02

Please sign in to comment.