Skip to content

Commit 6b107c9

Browse files
Updated to fix issues-4170 (#7980)
* Updated to fix issues-4170 * Fixed as per the review * Fixed linting issues * Update src/emulator/auth/operations.ts Co-authored-by: Yuchen Shi <[email protected]> * Update src/emulator/auth/operations.ts Co-authored-by: Yuchen Shi <[email protected]> * Update CHANGELOG.md * Update CHANGELOG.md --------- Co-authored-by: Yuchen Shi <[email protected]>
1 parent e895fd2 commit 6b107c9

File tree

3 files changed

+106
-5
lines changed

3 files changed

+106
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
- Deprecated `emulators.apphosting.startCommandOverride`. Please use `emulators.apphosting.startCommand` instead.
99
- Updated `superstatic` to `9.1.0` in package.json.
1010
- Updated the Firebase Data Connect local toolkit to v1.7.4, which includes a fix for an issue that caused duplicate installations of the Firebase JS SDK. (#8028)
11+
- Add support for `linkProviderUserInfo` in the Firebase Emulator to allow linking providers to user accounts. (#4170)

src/emulator/auth/operations.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,11 +1030,7 @@ export function setAccountInfoImpl(
10301030
{ privileged = false, emulatorUrl = undefined }: { privileged?: boolean; emulatorUrl?: URL } = {},
10311031
): Schemas["GoogleCloudIdentitytoolkitV1SetAccountInfoResponse"] {
10321032
// TODO: Implement these.
1033-
const unimplementedFields: (keyof typeof reqBody)[] = [
1034-
"provider",
1035-
"upgradeToFederatedLogin",
1036-
"linkProviderUserInfo",
1037-
];
1033+
const unimplementedFields: (keyof typeof reqBody)[] = ["provider", "upgradeToFederatedLogin"];
10381034
for (const field of unimplementedFields) {
10391035
if (field in reqBody) {
10401036
throw new NotImplementedError(`${field} is not implemented yet.`);
@@ -1232,8 +1228,16 @@ export function setAccountInfoImpl(
12321228
}
12331229
}
12341230

1231+
if (reqBody.linkProviderUserInfo) {
1232+
assert(reqBody.linkProviderUserInfo.providerId, "MISSING_PROVIDER_ID");
1233+
assert(reqBody.linkProviderUserInfo.rawId, "MISSING_RAW_ID");
1234+
}
1235+
12351236
user = state.updateUserByLocalId(user.localId, updates, {
12361237
deleteProviders: reqBody.deleteProvider,
1238+
upsertProviders: reqBody.linkProviderUserInfo
1239+
? [reqBody.linkProviderUserInfo as ProviderUserInfo]
1240+
: undefined,
12371241
});
12381242

12391243
// Only initiate the recover email OOB flow for non-anonymous users

src/emulator/auth/setAccountInfo.spec.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,4 +1254,100 @@ describeAuthEmulator("accounts:update", ({ authApi, getClock }) => {
12541254
expect(oobs[0].requestType).to.equal("RECOVER_EMAIL");
12551255
expect(oobs[0].oobLink).to.include(tenant.tenantId);
12561256
});
1257+
1258+
it("should link provider account with existing user account", async () => {
1259+
const { idToken } = await registerUser(authApi(), {
1260+
1261+
password: "password",
1262+
});
1263+
1264+
const providerId = "google.com";
1265+
const rawId = "google_user_id";
1266+
const providerUserInfo = {
1267+
providerId,
1268+
rawId,
1269+
1270+
displayName: "Linked User",
1271+
photoUrl: "https://example.com/photo.jpg",
1272+
};
1273+
1274+
await authApi()
1275+
.post("/identitytoolkit.googleapis.com/v1/accounts:update")
1276+
.query({ key: "fake-api-key" })
1277+
.send({ idToken, linkProviderUserInfo: providerUserInfo })
1278+
.then((res) => {
1279+
expectStatusCode(200, res);
1280+
const providers = res.body.providerUserInfo;
1281+
expect(providers).to.have.length(2); // Original email/password + linked provider
1282+
1283+
const linkedProvider = providers.find((p: ProviderUserInfo) => p.providerId === providerId);
1284+
expect(linkedProvider).to.deep.equal(providerUserInfo);
1285+
});
1286+
1287+
const accountInfo = await getAccountInfoByIdToken(authApi(), idToken);
1288+
expect(accountInfo.providerUserInfo).to.have.length(2);
1289+
const linkedProviderInfo = accountInfo.providerUserInfo?.find(
1290+
(p: ProviderUserInfo) => p.providerId === providerId,
1291+
);
1292+
expect(linkedProviderInfo).to.deep.equal(providerUserInfo);
1293+
});
1294+
1295+
it("should error if linkProviderUserInfo is missing required fields", async () => {
1296+
const { idToken } = await registerUser(authApi(), {
1297+
1298+
password: "password",
1299+
});
1300+
1301+
const incompleteProviderUserInfo1 = {
1302+
providerId: "google.com",
1303+
1304+
};
1305+
1306+
await authApi()
1307+
.post("/identitytoolkit.googleapis.com/v1/accounts:update")
1308+
.query({ key: "fake-api-key" })
1309+
.send({ idToken, linkProviderUserInfo: incompleteProviderUserInfo1 })
1310+
.then((res) => {
1311+
expectStatusCode(400, res);
1312+
expect(res.body.error.message).to.contain("MISSING_RAW_ID");
1313+
});
1314+
1315+
const incompleteProviderUserInfo2 = {
1316+
rawId: "google_user_id",
1317+
1318+
};
1319+
1320+
await authApi()
1321+
.post("/identitytoolkit.googleapis.com/v1/accounts:update")
1322+
.query({ key: "fake-api-key" })
1323+
.send({ idToken, linkProviderUserInfo: incompleteProviderUserInfo2 })
1324+
.then((res) => {
1325+
expectStatusCode(400, res);
1326+
expect(res.body.error.message).to.contain("MISSING_PROVIDER_ID");
1327+
});
1328+
});
1329+
1330+
it("should error if user is disabled when linking a provider", async () => {
1331+
const { localId, idToken } = await registerUser(authApi(), {
1332+
1333+
password: "password",
1334+
});
1335+
1336+
await updateAccountByLocalId(authApi(), localId, { disableUser: true });
1337+
1338+
const providerUserInfo = {
1339+
providerId: "google.com",
1340+
rawId: "google_user_id",
1341+
1342+
};
1343+
1344+
await authApi()
1345+
.post("/identitytoolkit.googleapis.com/v1/accounts:update")
1346+
.query({ key: "fake-api-key" })
1347+
.send({ idToken, linkProviderUserInfo: providerUserInfo })
1348+
.then((res) => {
1349+
expectStatusCode(400, res);
1350+
expect(res.body.error.message).to.equal("USER_DISABLED");
1351+
});
1352+
});
12571353
});

0 commit comments

Comments
 (0)