From 0d757ce746716f948a71d7b2272f94ab5524adfa Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Thu, 22 Aug 2024 09:14:29 -0600 Subject: [PATCH] [Data masking] Fix bug where masked field could be returned in parent query (#12014) --- .size-limits.json | 4 ++-- src/core/__tests__/masking.test.ts | 38 ++++++++++++++++++++++++++++++ src/core/masking.ts | 8 ++++++- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/.size-limits.json b/.size-limits.json index 6b9e9b23a05..7c04a30b992 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 40818, - "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 33647 + "dist/apollo-client.min.cjs": 40870, + "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 33688 } diff --git a/src/core/__tests__/masking.test.ts b/src/core/__tests__/masking.test.ts index 169ea2d4533..ce4cb237ae7 100644 --- a/src/core/__tests__/masking.test.ts +++ b/src/core/__tests__/masking.test.ts @@ -144,6 +144,44 @@ describe("maskOperation", () => { }); }); + test("masks fragments from nested objects when query gets fields from same object", () => { + const query = gql` + query { + user { + profile { + __typename + id + } + ...UserFields + } + } + fragment UserFields on User { + profile { + id + fullName + } + } + `; + + const data = maskOperation( + deepFreeze({ + user: { + __typename: "User", + profile: { __typename: "Profile", id: "1", fullName: "Test User" }, + }, + }), + query, + createFragmentMatcher(new InMemoryCache()) + ); + + expect(data).toEqual({ + user: { + __typename: "User", + profile: { __typename: "Profile", id: "1" }, + }, + }); + }); + test("deep freezes the masked result if the original data is frozen", () => { const query = gql` query { diff --git a/src/core/masking.ts b/src/core/masking.ts index 1519f4265ec..64cae9275e5 100644 --- a/src/core/masking.ts +++ b/src/core/masking.ts @@ -156,7 +156,13 @@ function maskSelectionSet( __DEV__ ? `${path || ""}.${keyName}` : void 0 ); - if (childChanged) { + if ( + childChanged || + // This check prevents cases where masked fields may accidentally be + // returned as part of this object when the fragment also selects + // additional fields from the same child selection. + Object.keys(masked).length !== Object.keys(data[keyName]).length + ) { memo[keyName] = masked; changed = true; }