Skip to content

Commit 1532012

Browse files
authored
Update password policy cache in sign-in flow when the password does not meet the backend requirements (#7435)
* Update signInWithEmailAndPassword to handle updating password policy cache * Remove await on calls recachePasswordPolicy * Throw errors instead of rejecting promises
1 parent 1025840 commit 1532012

File tree

2 files changed

+200
-15
lines changed

2 files changed

+200
-15
lines changed

packages/auth/src/core/strategies/email_and_password.test.ts

Lines changed: 187 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ const TEST_REFRESH_TOKEN = 'refresh-token';
5959
const TEST_TOKEN_EXPIRY_TIME = '1234';
6060

6161
const TEST_LOCAL_ID = 'local-id';
62+
const TEST_SERVER_USER: APIUserInfo = {
63+
localId: TEST_LOCAL_ID
64+
};
6265

6366
const TEST_EMAIL = '[email protected]';
6467
const TEST_PASSWORD = 'some-password';
@@ -543,9 +546,7 @@ describe('core/strategies/verifyPasswordResetCode', () => {
543546

544547
describe('core/strategies/email_and_password/createUserWithEmailAndPassword', () => {
545548
let auth: TestAuth;
546-
const serverUser: APIUserInfo = {
547-
localId: TEST_LOCAL_ID
548-
};
549+
const serverUser: APIUserInfo = TEST_SERVER_USER;
549550

550551
beforeEach(async () => {
551552
auth = await testAuth();
@@ -758,9 +759,7 @@ describe('core/strategies/email_and_password/createUserWithEmailAndPassword', ()
758759

759760
describe('core/strategies/email_and_password/signInWithEmailAndPassword', () => {
760761
let auth: TestAuth;
761-
const serverUser: APIUserInfo = {
762-
localId: TEST_LOCAL_ID
763-
};
762+
const serverUser: APIUserInfo = TEST_SERVER_USER;
764763

765764
beforeEach(async () => {
766765
auth = await testAuth();
@@ -842,6 +841,17 @@ describe('password policy cache is updated in auth flows upon error', () => {
842841
let policyEndpointMockWithTenant: mockFetch.Route;
843842
let policyEndpointMockWithOtherTenant: mockFetch.Route;
844843

844+
/**
845+
* Wait for 50ms to allow the password policy to be fetched and recached.
846+
*/
847+
async function waitForRecachePasswordPolicy(): Promise<void> {
848+
await new Promise<void>(resolve => {
849+
setTimeout(() => {
850+
resolve();
851+
}, 50);
852+
});
853+
}
854+
845855
beforeEach(async () => {
846856
auth = await testAuth();
847857
mockFetch.setUp();
@@ -868,10 +878,6 @@ describe('password policy cache is updated in auth flows upon error', () => {
868878
afterEach(mockFetch.tearDown);
869879

870880
context('#createUserWithEmailAndPassword', () => {
871-
const TEST_SERVER_USER: APIUserInfo = {
872-
localId: TEST_LOCAL_ID
873-
};
874-
875881
beforeEach(() => {
876882
mockEndpoint(Endpoint.SIGN_UP, {
877883
idToken: TEST_ID_TOKEN,
@@ -889,6 +895,9 @@ describe('password policy cache is updated in auth flows upon error', () => {
889895
createUserWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASSWORD)
890896
).to.be.fulfilled;
891897

898+
// Wait to ensure the password policy is not fetched and recached.
899+
await waitForRecachePasswordPolicy();
900+
892901
expect(policyEndpointMock.calls.length).to.eq(0);
893902
expect(auth._getPasswordPolicyInternal()).to.be.null;
894903
});
@@ -900,6 +909,9 @@ describe('password policy cache is updated in auth flows upon error', () => {
900909
createUserWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASSWORD)
901910
).to.be.fulfilled;
902911

912+
// Wait to ensure the password policy is not fetched and recached.
913+
await waitForRecachePasswordPolicy();
914+
903915
expect(policyEndpointMock.calls.length).to.eq(1);
904916
expect(auth._getPasswordPolicyInternal()).to.eql(CACHED_PASSWORD_POLICY);
905917
});
@@ -931,6 +943,9 @@ describe('password policy cache is updated in auth flows upon error', () => {
931943
createUserWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASSWORD)
932944
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
933945

946+
// Wait for the password policy to be fetched and recached.
947+
await waitForRecachePasswordPolicy();
948+
934949
expect(policyEndpointMock.calls.length).to.eq(2);
935950
expect(auth._getPasswordPolicyInternal()).to.eql(
936951
CACHED_PASSWORD_POLICY_REQUIRE_NUMERIC
@@ -952,6 +967,9 @@ describe('password policy cache is updated in auth flows upon error', () => {
952967
createUserWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASSWORD)
953968
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
954969

970+
// Wait for the password policy to be fetched and recached.
971+
await waitForRecachePasswordPolicy();
972+
955973
expect(policyEndpointMockWithTenant.calls.length).to.eq(2);
956974
expect(auth._getPasswordPolicyInternal()).to.eql(
957975
CACHED_PASSWORD_POLICY_REQUIRE_NUMERIC
@@ -965,6 +983,9 @@ describe('password policy cache is updated in auth flows upon error', () => {
965983
createUserWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASSWORD)
966984
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
967985

986+
// Wait for the password policy to be fetched and recached.
987+
await waitForRecachePasswordPolicy();
988+
968989
expect(policyEndpointMock.calls.length).to.eq(0);
969990
expect(auth._getPasswordPolicyInternal()).to.be.null;
970991
});
@@ -981,6 +1002,10 @@ describe('password policy cache is updated in auth flows upon error', () => {
9811002
await expect(
9821003
createUserWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASSWORD)
9831004
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
1005+
1006+
// Wait to ensure the password policy is not fetched and recached.
1007+
await waitForRecachePasswordPolicy();
1008+
9841009
expect(policyEndpointMockWithOtherTenant.calls.length).to.eq(0);
9851010
expect(auth._getPasswordPolicyInternal()).to.be.undefined;
9861011
});
@@ -1000,6 +1025,9 @@ describe('password policy cache is updated in auth flows upon error', () => {
10001025
await expect(confirmPasswordReset(auth, TEST_OOB_CODE, TEST_PASSWORD)).to
10011026
.be.fulfilled;
10021027

1028+
// Wait to ensure the password policy is not fetched and recached.
1029+
await waitForRecachePasswordPolicy();
1030+
10031031
expect(policyEndpointMock.calls.length).to.eq(0);
10041032
expect(auth._getPasswordPolicyInternal()).to.be.null;
10051033
});
@@ -1010,6 +1038,9 @@ describe('password policy cache is updated in auth flows upon error', () => {
10101038
await expect(confirmPasswordReset(auth, TEST_OOB_CODE, TEST_PASSWORD)).to
10111039
.be.fulfilled;
10121040

1041+
// Wait to ensure the password policy is not fetched and recached.
1042+
await waitForRecachePasswordPolicy();
1043+
10131044
expect(policyEndpointMock.calls.length).to.eq(1);
10141045
expect(auth._getPasswordPolicyInternal()).to.eql(CACHED_PASSWORD_POLICY);
10151046
});
@@ -1041,6 +1072,9 @@ describe('password policy cache is updated in auth flows upon error', () => {
10411072
confirmPasswordReset(auth, TEST_OOB_CODE, TEST_PASSWORD)
10421073
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
10431074

1075+
// Wait for the password policy to be fetched and recached.
1076+
await waitForRecachePasswordPolicy();
1077+
10441078
expect(policyEndpointMock.calls.length).to.eq(2);
10451079
expect(auth._getPasswordPolicyInternal()).to.eql(
10461080
CACHED_PASSWORD_POLICY_REQUIRE_NUMERIC
@@ -1062,6 +1096,9 @@ describe('password policy cache is updated in auth flows upon error', () => {
10621096
confirmPasswordReset(auth, TEST_OOB_CODE, TEST_PASSWORD)
10631097
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
10641098

1099+
// Wait for the password policy to be fetched and recached.
1100+
await waitForRecachePasswordPolicy();
1101+
10651102
expect(policyEndpointMockWithTenant.calls.length).to.eq(2);
10661103
expect(auth._getPasswordPolicyInternal()).to.eql(
10671104
CACHED_PASSWORD_POLICY_REQUIRE_NUMERIC
@@ -1075,6 +1112,9 @@ describe('password policy cache is updated in auth flows upon error', () => {
10751112
confirmPasswordReset(auth, TEST_OOB_CODE, TEST_PASSWORD)
10761113
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
10771114

1115+
// Wait to ensure the password policy is not fetched and recached.
1116+
await waitForRecachePasswordPolicy();
1117+
10781118
expect(policyEndpointMock.calls.length).to.eq(0);
10791119
expect(auth._getPasswordPolicyInternal()).to.be.null;
10801120
});
@@ -1091,6 +1131,143 @@ describe('password policy cache is updated in auth flows upon error', () => {
10911131
await expect(
10921132
confirmPasswordReset(auth, TEST_OOB_CODE, TEST_PASSWORD)
10931133
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
1134+
1135+
// Wait to ensure the password policy is not fetched and recached.
1136+
await waitForRecachePasswordPolicy();
1137+
1138+
expect(policyEndpointMockWithOtherTenant.calls.length).to.eq(0);
1139+
expect(auth._getPasswordPolicyInternal()).to.be.undefined;
1140+
});
1141+
});
1142+
});
1143+
1144+
context('#signInWithEmailAndPassword', () => {
1145+
beforeEach(() => {
1146+
mockEndpoint(Endpoint.SIGN_IN_WITH_PASSWORD, {
1147+
idToken: TEST_ID_TOKEN,
1148+
refreshToken: TEST_REFRESH_TOKEN,
1149+
expiresIn: TEST_TOKEN_EXPIRY_TIME,
1150+
localId: TEST_SERVER_USER.localId!
1151+
});
1152+
mockEndpoint(Endpoint.GET_ACCOUNT_INFO, {
1153+
users: [TEST_SERVER_USER]
1154+
});
1155+
});
1156+
1157+
it('does not update the cached password policy upon successful sign-in when there is no existing policy cache', async () => {
1158+
await expect(signInWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASSWORD))
1159+
.to.be.fulfilled;
1160+
1161+
// Wait to ensure the password policy is not fetched and recached.
1162+
await waitForRecachePasswordPolicy();
1163+
1164+
expect(policyEndpointMock.calls.length).to.eq(0);
1165+
expect(auth._getPasswordPolicyInternal()).to.be.null;
1166+
});
1167+
1168+
it('does not update the cached password policy upon successful sign-in when there is an existing policy cache', async () => {
1169+
await auth._updatePasswordPolicy();
1170+
1171+
await expect(signInWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASSWORD))
1172+
.to.be.fulfilled;
1173+
1174+
// Wait to ensure the password policy is not fetched and recached.
1175+
await waitForRecachePasswordPolicy();
1176+
1177+
expect(policyEndpointMock.calls.length).to.eq(1);
1178+
expect(auth._getPasswordPolicyInternal()).to.eql(CACHED_PASSWORD_POLICY);
1179+
});
1180+
1181+
context('handles password validation errors', () => {
1182+
beforeEach(() => {
1183+
mockEndpoint(
1184+
Endpoint.SIGN_IN_WITH_PASSWORD,
1185+
{
1186+
error: {
1187+
code: 400,
1188+
message: ServerError.PASSWORD_DOES_NOT_MEET_REQUIREMENTS
1189+
}
1190+
},
1191+
400
1192+
);
1193+
});
1194+
1195+
it('updates the cached password policy when password does not meet backend requirements for the project', async () => {
1196+
await auth._updatePasswordPolicy();
1197+
expect(policyEndpointMock.calls.length).to.eq(1);
1198+
expect(auth._getPasswordPolicyInternal()).to.eql(
1199+
CACHED_PASSWORD_POLICY
1200+
);
1201+
1202+
// Password policy changed after previous fetch.
1203+
policyEndpointMock.response = PASSWORD_POLICY_RESPONSE_REQUIRE_NUMERIC;
1204+
await expect(
1205+
signInWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASSWORD)
1206+
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
1207+
1208+
// Wait for the password policy to be fetched and recached.
1209+
await waitForRecachePasswordPolicy();
1210+
1211+
expect(policyEndpointMock.calls.length).to.eq(2);
1212+
expect(auth._getPasswordPolicyInternal()).to.eql(
1213+
CACHED_PASSWORD_POLICY_REQUIRE_NUMERIC
1214+
);
1215+
});
1216+
1217+
it('updates the cached password policy when password does not meet backend requirements for the tenant', async () => {
1218+
auth.tenantId = TEST_TENANT_ID;
1219+
await auth._updatePasswordPolicy();
1220+
expect(policyEndpointMockWithTenant.calls.length).to.eq(1);
1221+
expect(auth._getPasswordPolicyInternal()).to.eql(
1222+
CACHED_PASSWORD_POLICY
1223+
);
1224+
1225+
// Password policy changed after previous fetch.
1226+
policyEndpointMockWithTenant.response =
1227+
PASSWORD_POLICY_RESPONSE_REQUIRE_NUMERIC;
1228+
await expect(
1229+
signInWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASSWORD)
1230+
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
1231+
1232+
// Wait for the password policy to be fetched and recached.
1233+
await waitForRecachePasswordPolicy();
1234+
1235+
expect(policyEndpointMockWithTenant.calls.length).to.eq(2);
1236+
expect(auth._getPasswordPolicyInternal()).to.eql(
1237+
CACHED_PASSWORD_POLICY_REQUIRE_NUMERIC
1238+
);
1239+
});
1240+
1241+
it('does not update the cached password policy upon error if policy has not previously been fetched', async () => {
1242+
expect(auth._getPasswordPolicyInternal()).to.be.null;
1243+
1244+
await expect(
1245+
signInWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASSWORD)
1246+
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
1247+
1248+
// Wait to ensure the password policy is not fetched and recached.
1249+
await waitForRecachePasswordPolicy();
1250+
1251+
expect(policyEndpointMock.calls.length).to.eq(0);
1252+
expect(auth._getPasswordPolicyInternal()).to.be.null;
1253+
});
1254+
1255+
it('does not update the cached password policy upon error if tenant changes and policy has not previously been fetched', async () => {
1256+
auth.tenantId = TEST_TENANT_ID;
1257+
await auth._updatePasswordPolicy();
1258+
expect(policyEndpointMockWithTenant.calls.length).to.eq(1);
1259+
expect(auth._getPasswordPolicyInternal()).to.eql(
1260+
CACHED_PASSWORD_POLICY
1261+
);
1262+
1263+
auth.tenantId = TEST_TENANT_ID_REQUIRE_NUMERIC;
1264+
await expect(
1265+
signInWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASSWORD)
1266+
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
1267+
1268+
// Wait to ensure the password policy is not fetched and recached.
1269+
await waitForRecachePasswordPolicy();
1270+
10941271
expect(policyEndpointMockWithOtherTenant.calls.length).to.eq(0);
10951272
expect(auth._getPasswordPolicyInternal()).to.be.undefined;
10961273
});

packages/auth/src/core/strategies/email_and_password.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,10 @@ export async function confirmPasswordReset(
184184
error.code ===
185185
`auth/${AuthErrorCode.PASSWORD_DOES_NOT_MEET_REQUIREMENTS}`
186186
) {
187-
await recachePasswordPolicy(auth);
187+
void recachePasswordPolicy(auth);
188188
}
189189

190-
return Promise.reject(error);
190+
throw error;
191191
});
192192
// Do not return the email.
193193
}
@@ -343,10 +343,10 @@ export async function createUserWithEmailAndPassword(
343343
error.code ===
344344
`auth/${AuthErrorCode.PASSWORD_DOES_NOT_MEET_REQUIREMENTS}`
345345
) {
346-
await recachePasswordPolicy(auth);
346+
void recachePasswordPolicy(auth);
347347
}
348348

349-
return Promise.reject(error);
349+
throw error;
350350
}
351351
});
352352
}
@@ -389,5 +389,13 @@ export function signInWithEmailAndPassword(
389389
return signInWithCredential(
390390
getModularInstance(auth),
391391
EmailAuthProvider.credential(email, password)
392-
);
392+
).catch(async error => {
393+
if (
394+
error.code === `auth/${AuthErrorCode.PASSWORD_DOES_NOT_MEET_REQUIREMENTS}`
395+
) {
396+
void recachePasswordPolicy(auth);
397+
}
398+
399+
throw error;
400+
});
393401
}

0 commit comments

Comments
 (0)