Skip to content

Commit 95b2813

Browse files
authored
Merge 7aca281 into 1741fef
2 parents 1741fef + 7aca281 commit 95b2813

File tree

4 files changed

+162
-3
lines changed

4 files changed

+162
-3
lines changed

packages/auth/src/api/errors.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ export const enum ServerError {
9898
MISSING_CLIENT_TYPE = 'MISSING_CLIENT_TYPE',
9999
MISSING_RECAPTCHA_VERSION = 'MISSING_RECAPTCHA_VERSION',
100100
INVALID_RECAPTCHA_VERSION = 'INVALID_RECAPTCHA_VERSION',
101-
INVALID_REQ_TYPE = 'INVALID_REQ_TYPE'
101+
INVALID_REQ_TYPE = 'INVALID_REQ_TYPE',
102+
PASSWORD_DOES_NOT_MEET_REQUIREMENTS = 'PASSWORD_DOES_NOT_MEET_REQUIREMENTS'
102103
}
103104

104105
/**
@@ -177,6 +178,8 @@ export const SERVER_ERROR_MAP: Partial<ServerErrorMap<ServerError>> = {
177178
// Other errors.
178179
[ServerError.TOO_MANY_ATTEMPTS_TRY_LATER]:
179180
AuthErrorCode.TOO_MANY_ATTEMPTS_TRY_LATER,
181+
[ServerError.PASSWORD_DOES_NOT_MEET_REQUIREMENTS]:
182+
AuthErrorCode.PASSWORD_DOES_NOT_MEET_REQUIREMENTS,
180183

181184
// Phone Auth related errors.
182185
[ServerError.INVALID_CODE]: AuthErrorCode.INVALID_CODE,

packages/auth/src/core/errors.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ export const enum AuthErrorCode {
133133
MISSING_RECAPTCHA_VERSION = 'missing-recaptcha-version',
134134
INVALID_RECAPTCHA_VERSION = 'invalid-recaptcha-version',
135135
INVALID_REQ_TYPE = 'invalid-req-type',
136-
UNSUPPORTED_PASSWORD_POLICY_SCHEMA_VERSION = 'unsupported-password-policy-schema-version'
136+
UNSUPPORTED_PASSWORD_POLICY_SCHEMA_VERSION = 'unsupported-password-policy-schema-version',
137+
PASSWORD_DOES_NOT_MEET_REQUIREMENTS = 'password-does-not-meet-requirements'
137138
}
138139

139140
function _debugErrorMap(): ErrorMap<AuthErrorCode> {
@@ -384,7 +385,9 @@ function _debugErrorMap(): ErrorMap<AuthErrorCode> {
384385
[AuthErrorCode.INVALID_RECAPTCHA_VERSION]:
385386
'The reCAPTCHA version is invalid when sending request to the backend.',
386387
[AuthErrorCode.UNSUPPORTED_PASSWORD_POLICY_SCHEMA_VERSION]:
387-
'The password policy received from the backend uses a schema version that is not supported by this version of the Firebase SDK.'
388+
'The password policy received from the backend uses a schema version that is not supported by this version of the Firebase SDK.',
389+
[AuthErrorCode.PASSWORD_DOES_NOT_MEET_REQUIREMENTS]:
390+
'The password does not meet the requirements.'
388391
};
389392
}
390393

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

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,149 @@ describe('core/strategies/email_and_password/createUserWithEmailAndPassword', ()
745745
expect(user.isAnonymous).to.be.false;
746746
});
747747
});
748+
749+
context('#passwordPolicy', () => {
750+
const TEST_MIN_PASSWORD_LENGTH = 6;
751+
const TEST_ALLOWED_NON_ALPHANUMERIC_CHARS = ['!', '(', ')'];
752+
const TEST_SCHEMA_VERSION = 1;
753+
754+
const TEST_TENANT_ID = 'tenant-id';
755+
const TEST_REQUIRE_NUMERIC_TENANT_ID = 'other-tenant-id';
756+
757+
const PASSWORD_ERROR_MSG =
758+
'Firebase: The password does not meet the requirements. (auth/password-does-not-meet-requirements).';
759+
760+
const passwordPolicyResponse = {
761+
customStrengthOptions: {
762+
minPasswordLength: TEST_MIN_PASSWORD_LENGTH
763+
},
764+
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS,
765+
schemaVersion: TEST_SCHEMA_VERSION
766+
};
767+
const passwordPolicyResponseRequireNumeric = {
768+
customStrengthOptions: {
769+
minPasswordLength: TEST_MIN_PASSWORD_LENGTH,
770+
containsNumericCharacter: true
771+
},
772+
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS,
773+
schemaVersion: TEST_SCHEMA_VERSION
774+
};
775+
const cachedPasswordPolicy = {
776+
customStrengthOptions: {
777+
minPasswordLength: TEST_MIN_PASSWORD_LENGTH
778+
},
779+
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS
780+
};
781+
const cachedPasswordPolicyRequireNumeric = {
782+
customStrengthOptions: {
783+
minPasswordLength: TEST_MIN_PASSWORD_LENGTH,
784+
containsNumericCharacter: true
785+
},
786+
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS
787+
};
788+
let policyEndpointMock: mockFetch.Route;
789+
let policyEndpointMockWithTenant: mockFetch.Route;
790+
let policyEndpointMockWithOtherTenant: mockFetch.Route;
791+
792+
beforeEach(() => {
793+
policyEndpointMock = mockEndpointWithParams(
794+
Endpoint.GET_PASSWORD_POLICY,
795+
{},
796+
passwordPolicyResponse
797+
);
798+
policyEndpointMockWithTenant = mockEndpointWithParams(
799+
Endpoint.GET_PASSWORD_POLICY,
800+
{
801+
tenantId: TEST_TENANT_ID
802+
},
803+
passwordPolicyResponse
804+
);
805+
policyEndpointMockWithOtherTenant = mockEndpointWithParams(
806+
Endpoint.GET_PASSWORD_POLICY,
807+
{
808+
tenantId: TEST_REQUIRE_NUMERIC_TENANT_ID
809+
},
810+
passwordPolicyResponseRequireNumeric
811+
);
812+
});
813+
814+
it('does not update the cached password policy upon successful sign up when there is no existing policy cache', async () => {
815+
await expect(
816+
createUserWithEmailAndPassword(auth, 'some-email', 'some-password')
817+
).to.be.fulfilled;
818+
819+
expect(policyEndpointMock.calls.length).to.eq(0);
820+
expect(auth._getPasswordPolicy()).to.be.null;
821+
});
822+
823+
it('does not update the cached password policy upon successful sign up when there is an existing policy cache', async () => {
824+
await auth._updatePasswordPolicy();
825+
826+
await expect(
827+
createUserWithEmailAndPassword(auth, 'some-email', 'some-password')
828+
).to.be.fulfilled;
829+
830+
expect(policyEndpointMock.calls.length).to.eq(1);
831+
expect(auth._getPasswordPolicy()).to.eql(cachedPasswordPolicy);
832+
});
833+
834+
context('handles password validation errors', () => {
835+
beforeEach(() => {
836+
mockEndpoint(
837+
Endpoint.SIGN_UP,
838+
{
839+
error: {
840+
code: 400,
841+
message: ServerError.PASSWORD_DOES_NOT_MEET_REQUIREMENTS
842+
}
843+
},
844+
400
845+
);
846+
});
847+
848+
it('updates the cached password policy when password does not meet backend requirements', async () => {
849+
await auth._updatePasswordPolicy();
850+
expect(policyEndpointMock.calls.length).to.eq(1);
851+
expect(auth._getPasswordPolicy()).to.eql(cachedPasswordPolicy);
852+
853+
// Password policy changed after previous fetch.
854+
policyEndpointMock.response = passwordPolicyResponseRequireNumeric;
855+
await expect(
856+
createUserWithEmailAndPassword(auth, 'some-email', 'some-password')
857+
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
858+
859+
expect(policyEndpointMock.calls.length).to.eq(2);
860+
expect(auth._getPasswordPolicy()).to.eql(
861+
cachedPasswordPolicyRequireNumeric
862+
);
863+
});
864+
865+
it('does not update the cached password policy upon error if policy has not previously been fetched', async () => {
866+
expect(auth._getPasswordPolicy()).to.be.null;
867+
868+
await expect(
869+
createUserWithEmailAndPassword(auth, 'some-email', 'some-password')
870+
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
871+
872+
expect(policyEndpointMock.calls.length).to.eq(0);
873+
expect(auth._getPasswordPolicy()).to.be.null;
874+
});
875+
876+
it('does not update the cached password policy upon error if tenant changes and policy has not previously been fetched', async () => {
877+
auth.tenantId = TEST_TENANT_ID;
878+
await auth._updatePasswordPolicy();
879+
expect(policyEndpointMockWithTenant.calls.length).to.eq(1);
880+
expect(auth._getPasswordPolicy()).to.eql(cachedPasswordPolicy);
881+
882+
auth.tenantId = TEST_REQUIRE_NUMERIC_TENANT_ID;
883+
await expect(
884+
createUserWithEmailAndPassword(auth, 'some-email', 'some-password')
885+
).to.be.rejectedWith(FirebaseError, PASSWORD_ERROR_MSG);
886+
expect(policyEndpointMockWithOtherTenant.calls.length).to.eq(0);
887+
expect(auth._getPasswordPolicy()).to.be.undefined;
888+
});
889+
});
890+
});
748891
});
749892

750893
describe('core/strategies/email_and_password/signInWithEmailAndPassword', () => {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,16 @@ export async function createUserWithEmailAndPassword(
308308
);
309309
return signUp(authInternal, requestWithRecaptcha);
310310
} else {
311+
// Only fetch the password policy if the password did not meet policy requirements and there is an existing policy cached.
312+
// A developer must call validatePassword at least once for the cache to be automatically updated.
313+
if (
314+
error.code ===
315+
`auth/${AuthErrorCode.PASSWORD_DOES_NOT_MEET_REQUIREMENTS}` &&
316+
authInternal._getPasswordPolicy()
317+
) {
318+
await authInternal._updatePasswordPolicy();
319+
}
320+
311321
return Promise.reject(error);
312322
}
313323
});

0 commit comments

Comments
 (0)