Skip to content

Commit bff5da7

Browse files
thomasmburkejoehan
andauthored
quote entire custom claims object and escape inner quotes (firebase#5630)
* quote entire custom claims object and escape inner quotes --------- Co-authored-by: Thomas Burke <[email protected]> Co-authored-by: joehan <[email protected]>
1 parent b14b5f3 commit bff5da7

File tree

3 files changed

+34
-38
lines changed

3 files changed

+34
-38
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
- Releases Cloud Firestore emulator v1.16.1, which adds support for read_time in ListCollectionIds.
2+
- Fixes auth:export with csv format for users with custom claims. (#3319)

src/accountExporter.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,10 @@ function transUserToArray(user: any): any[] {
8484
arr[24] = user.lastLoginAt;
8585
arr[25] = user.phoneNumber;
8686
arr[26] = user.disabled;
87-
arr[27] = user.customAttributes;
87+
// quote entire custom claims object and escape inner quotes with quotes
88+
arr[27] = user.customAttributes
89+
? `"${user.customAttributes.replace(/(?<!\\)"/g, '""')}"`
90+
: user.customAttributes;
8891
return arr;
8992
}
9093

src/test/accountExporter.spec.ts

Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -62,42 +62,7 @@ describe("accountExporter", () => {
6262
});
6363

6464
it("should call api.request multiple times for JSON export", async () => {
65-
nock("https://www.googleapis.com")
66-
.post("/identitytoolkit/v3/relyingparty/downloadAccount", {
67-
maxResults: 3,
68-
targetProjectId: "test-project-id",
69-
})
70-
.reply(200, {
71-
users: userList.slice(0, 3),
72-
nextPageToken: "3",
73-
})
74-
.post("/identitytoolkit/v3/relyingparty/downloadAccount", {
75-
maxResults: 3,
76-
nextPageToken: "3",
77-
targetProjectId: "test-project-id",
78-
})
79-
.reply(200, {
80-
users: userList.slice(3, 6),
81-
nextPageToken: "6",
82-
})
83-
.post("/identitytoolkit/v3/relyingparty/downloadAccount", {
84-
maxResults: 3,
85-
nextPageToken: "6",
86-
targetProjectId: "test-project-id",
87-
})
88-
.reply(200, {
89-
users: userList.slice(6, 7),
90-
nextPageToken: "7",
91-
})
92-
.post("/identitytoolkit/v3/relyingparty/downloadAccount", {
93-
maxResults: 3,
94-
nextPageToken: "7",
95-
targetProjectId: "test-project-id",
96-
})
97-
.reply(200, {
98-
users: [],
99-
nextPageToken: "7",
100-
});
65+
mockAllUsersRequests();
10166

10267
await serialExportUsers("test-project-id", {
10368
format: "JSON",
@@ -216,7 +181,7 @@ describe("accountExporter", () => {
216181
expect(nock.isDone()).to.be.true;
217182
});
218183

219-
it("should export a user's custom attributes", async () => {
184+
it("should export a user's custom attributes for JSON formats", async () => {
220185
userList[0].customAttributes =
221186
'{ "customBoolean": true, "customString": "test", "customInt": 99 }';
222187
userList[1].customAttributes =
@@ -244,6 +209,33 @@ describe("accountExporter", () => {
244209
expect(nock.isDone()).to.be.true;
245210
});
246211

212+
it("should export a user's custom attributes for CSV formats", async () => {
213+
userList[0].customAttributes =
214+
'{ "customBoolean": true, "customString": "test", "customInt": 99 }';
215+
userList[1].customAttributes = '{ "customBoolean": true }';
216+
nock("https://www.googleapis.com")
217+
.post("/identitytoolkit/v3/relyingparty/downloadAccount", {
218+
maxResults: 3,
219+
targetProjectId: "test-project-id",
220+
})
221+
.reply(200, {
222+
users: userList.slice(0, 3),
223+
});
224+
await serialExportUsers("test-project-id", {
225+
format: "JSON",
226+
batchSize: 3,
227+
writeStream: writeStream,
228+
});
229+
expect(spyWrite.getCall(0).args[0]).to.eq(JSON.stringify(userList[0], null, 2));
230+
expect(spyWrite.getCall(1).args[0]).to.eq(
231+
"," + os.EOL + JSON.stringify(userList[1], null, 2)
232+
);
233+
expect(spyWrite.getCall(2).args[0]).to.eq(
234+
"," + os.EOL + JSON.stringify(userList[2], null, 2)
235+
);
236+
expect(nock.isDone()).to.be.true;
237+
});
238+
247239
function mockAllUsersRequests(): void {
248240
nock("https://www.googleapis.com")
249241
.post("/identitytoolkit/v3/relyingparty/downloadAccount", {

0 commit comments

Comments
 (0)