Skip to content

Commit

Permalink
Update password reset handler to handle password policy error (#1047)
Browse files Browse the repository at this point in the history
* Update password reset handler to handle 'PASSWORD_DOES_NOT_MEET_REQUIREMENTS' error

* Update the translations

* pin chrome 114 to run tests

* Unpin chrome 114
  • Loading branch information
NhienLam committed Nov 13, 2023
1 parent efc1b85 commit 44884c7
Show file tree
Hide file tree
Showing 48 changed files with 1,136 additions and 678 deletions.
33 changes: 21 additions & 12 deletions javascript/widgets/handler/actioncode.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,27 +129,36 @@ firebaseui.auth.widget.handler.resetPassword_ = function(
*/
firebaseui.auth.widget.handler.handlePasswordResetFailure_ = function(
app, container, opt_component, opt_error) {
var errorCode = opt_error && opt_error['code'];
if (errorCode == 'auth/weak-password') {
const errorCode = opt_error && opt_error['code'];
if (errorCode === 'auth/weak-password') {
// Handles this error differently as it just requires to display a message
// to the user to use a longer password.
var errorMessage =
const errorMessage =
firebaseui.auth.widget.handler.common.getErrorMessage(opt_error);
firebaseui.auth.ui.element.setValid(
opt_component.getNewPasswordElement(), false);
firebaseui.auth.ui.element.show(
opt_component.getNewPasswordErrorElement(), errorMessage);
opt_component.getNewPasswordElement().focus();
return;
}

if (opt_component) {
opt_component.dispose();
} else if (errorCode === 'auth/password-does-not-meet-requirements') {
// Pass the error message from the backend which contains all the password
// requirements to be met.
const errorMessage =
firebaseui.auth.widget.handler.common.getErrorMessage(opt_error);
firebaseui.auth.ui.element.setValid(
opt_component.getNewPasswordElement(), false);
firebaseui.auth.ui.element.show(
opt_component.getNewPasswordErrorElement(), errorMessage);
opt_component.getNewPasswordElement().focus();
} else {
if (opt_component) {
opt_component.dispose();
}
var component = new firebaseui.auth.ui.page.PasswordResetFailure();
component.render(container);
// Set current UI component.
app.setCurrentComponent(component);
}
var component = new firebaseui.auth.ui.page.PasswordResetFailure();
component.render(container);
// Set current UI component.
app.setCurrentComponent(component);
};


Expand Down
73 changes: 73 additions & 0 deletions javascript/widgets/handler/actioncode_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,79 @@ function testHandlePasswordReset_weakPasswordError() {
}


function testHandlePasswordReset_nonCompliantPasswordError() {
const errorMessage = 'Missing password requirements: [Password may contain at most 16 characters, Password must contain a lower case character, Password must contain an upper case character, Password must contain a numeric character, Password must contain a non-alphanumeric character]';
const error = {
'code': 'auth/password-does-not-meet-requirements',
'message': errorMessage
};
asyncTestCase.waitForSignals(1);
firebaseui.auth.widget.handler.handlePasswordReset(
app, container, 'PASSWORD_RESET_ACTION_CODE');
// Successful action code verification.
app.getAuth().assertVerifyPasswordResetCode(
['PASSWORD_RESET_ACTION_CODE'], 'user@example.com');
app.getAuth()
.process()
.then(function() {
// Password reset page should show.
assertPasswordResetPage();

goog.dom.forms.setValue(getNewPasswordElement(), '123');
// Submit password reset form.
submitForm();
// Simulates password doesn't meet requirements.
app.getAuth().assertConfirmPasswordReset(
['PASSWORD_RESET_ACTION_CODE', '123'], null, error);
return app.getAuth().process();
})
.then(function() {
// Error message should be shown on the same page.
assertPasswordResetPage();
assertEquals(
firebaseui.auth.widget.handler.common.getErrorMessage(error),
getNewPasswordErrorMessage());
asyncTestCase.signal();
});
}


function testHandlePasswordReset_nonCompliantPassword_emptyError() {
const error = {
'code': 'auth/password-does-not-meet-requirements',
'message': ''
};
asyncTestCase.waitForSignals(1);
firebaseui.auth.widget.handler.handlePasswordReset(
app, container, 'PASSWORD_RESET_ACTION_CODE');
// Successful action code verification.
app.getAuth().assertVerifyPasswordResetCode(
['PASSWORD_RESET_ACTION_CODE'], 'user@example.com');
app.getAuth()
.process()
.then(function() {
// Password reset page should show.
assertPasswordResetPage();

goog.dom.forms.setValue(getNewPasswordElement(), '123');
// Submit password reset form.
submitForm();
// Simulates password doesn't meet requirements.
app.getAuth().assertConfirmPasswordReset(
['PASSWORD_RESET_ACTION_CODE', '123'], null, error);
return app.getAuth().process();
})
.then(function() {
// Error message should be shown on the same page.
assertPasswordResetPage();
assertEquals(
firebaseui.auth.widget.handler.common.getErrorMessage(error),
getNewPasswordErrorMessage());
asyncTestCase.signal();
});
}


function testHandlePasswordReset_failToResetPassword() {
asyncTestCase.waitForSignals(1);
firebaseui.auth.widget.handler.handlePasswordReset(
Expand Down
63 changes: 63 additions & 0 deletions javascript/widgets/handler/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,69 @@ firebaseui.auth.widget.handler.common.getSignedInRedirectUrl_ =
* @package
*/
firebaseui.auth.widget.handler.common.getErrorMessage = function(error) {
// Password policy error message varies depending on the policy violations.
// Hence, we construct an error message from the strings file based on the
// error message from the backend.
if (error['code'] &&
error['code'] == 'auth/password-does-not-meet-requirements') {
const originalError = error['message'];
let minPasswordLength = '';
let maxPasswordLength = '';
let passwordNotMeetMinLength = false;
let passwordNotMeetMaxLength = false;
let passwordNotContainLowercaseLetter = false;
let passwordNotContainUppercaseLetter = false;
let passwordNotContainNumericCharacter = false;
let passwordNotContainNonAlphanumericCharacter = false;

const minLengthErrorMatch =
originalError.match(/Password must contain at least (\d+)/);
if (minLengthErrorMatch) {
passwordNotMeetMinLength = true;
minPasswordLength = minLengthErrorMatch[1];
}
const maxLengthErrorMatch =
originalError.match(/Password may contain at most (\d+)/);
if (maxLengthErrorMatch) {
passwordNotMeetMaxLength = true;
maxPasswordLength = maxLengthErrorMatch[1];
}
// This needs to be hardcoded. Checking against
// "firebaseui.auth.soy2.strings.errorPasswordNotContainLowercaseLetter().toString()"
// will return a translated string, while originalError is always in
// English.
if (originalError.includes(
'Password must contain a lower case character')) {
passwordNotContainLowercaseLetter = true;
}
if (originalError.includes(
'Password must contain an upper case character')) {
passwordNotContainUppercaseLetter = true;
}
if (originalError.includes('Password must contain a numeric character')) {
passwordNotContainNumericCharacter = true;
}
if (originalError.includes(
'Password must contain a non-alphanumeric character')) {
passwordNotContainNonAlphanumericCharacter = true;
}

return firebaseui.auth.soy2.strings
.errorMissingPasswordRequirements({
passwordNotMeetMinLength: passwordNotMeetMinLength,
minPasswordLength: minPasswordLength,
passwordNotMeetMaxLength: passwordNotMeetMaxLength,
maxPasswordLength: maxPasswordLength,
passwordNotContainLowercaseLetter: passwordNotContainLowercaseLetter,
passwordNotContainUppercaseLetter: passwordNotContainUppercaseLetter,
passwordNotContainNumericCharacter:
passwordNotContainNumericCharacter,
passwordNotContainNonAlphanumericCharacter:
passwordNotContainNonAlphanumericCharacter
})
.toString();
}

// Try to get an error message from the strings file, or fall back to the
// error message from the Firebase SDK if none is found.
var message =
Expand Down
13 changes: 13 additions & 0 deletions javascript/widgets/handler/common_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,19 @@ function testGetErrorMessage_unknownError_jsonMessage() {
}


function testGetErrorMessage_passwordPolicyError_message() {
const error = {
code: 'auth/password-does-not-meet-requirements',
message:
'Missing password requirements: [Password must contain at least 8 characters, Password may contain at most 16 characters, Password must contain a lower case character, Password must contain an upper case character, Password must contain a numeric character, Password must contain a non-alphanumeric character]'
};
const message = firebaseui.auth.widget.handler.common.getErrorMessage(error);
assertEquals(
'Missing password requirements: [ Password must contain at least 8 characters. Password may contain at most 16 characters. Password must contain a lower case character. Password must contain an upper case character. Password must contain a numeric character. Password must contain a non-alphanumeric character. ]',
message);
}


function testIsPasswordProviderOnly_multipleMixedProviders() {
// Set a password and federated providers in the FirebaseUI instance
// configuration.
Expand Down
112 changes: 111 additions & 1 deletion soy/strings.soy
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,111 @@
{/template}



/** Error message for when the user tries to sign in, sign up, or reset password with a
password that is not compliant with the policy.. */
{template .errorMissingPasswordRequirements kind="text"}
{@param passwordNotMeetMinLength: bool} /** Whether to display the
passwordNotMeetMinLength
error. */
{@param minPasswordLength: string} /** The minimum password length. */
{@param passwordNotMeetMaxLength: bool} /** Whether to display the
passwordNotMeetMaxLength
error. */
{@param maxPasswordLength: string} /** The maximum password length. */
{@param passwordNotContainLowercaseLetter: bool} /** Whether to display the
passwordNotContainLowercaseLetter
error. */
{@param passwordNotContainUppercaseLetter: bool} /** Whether to display the
passwordNotContainUppercaseLetter
error. */
{@param passwordNotContainNumericCharacter: bool} /** Whether to display the
passwordNotContainNumericCharacter
error. */
{@param passwordNotContainNonAlphanumericCharacter: bool} /** Whether to display the
passwordNotContainNonAlphanumericCharacter
error. */

{call .error}
{param code: 'auth/password-does-not-meet-requirements' /}
{/call}{sp}
[{sp}
{if $passwordNotMeetMinLength and $minPasswordLength != null}
{call .errorPasswordNotMeetMinLength}
{param minPasswordLength: $minPasswordLength /}
{/call}.{sp}
{/if}
{if $passwordNotMeetMaxLength and $maxPasswordLength != null}
{call .errorPasswordNotMeetMaxLength}
{param maxPasswordLength: $maxPasswordLength /}
{/call}.{sp}
{/if}
{if $passwordNotContainLowercaseLetter}{call .errorPasswordNotContainLowercaseLetter /}.{sp}{/if}
{if $passwordNotContainUppercaseLetter}{call .errorPasswordNotContainUppercaseLetter /}.{sp}{/if}
{if $passwordNotContainNumericCharacter}{call .errorPasswordNotContainNumericCharacter /}.{sp}{/if}
{if $passwordNotContainNonAlphanumericCharacter}
{call .errorPasswordNotContainNonAlphanumericCharacter /}.{sp}
{/if}
]
{/template}


/** Error message for a password that is shorter than the minimum password length. */
{template .errorPasswordNotMeetMinLength kind="text"}
{@param minPasswordLength: string} /** The minimum password length. */
{msg desc="Error message when the user enters a password that is shorter than the minimum password
length."}
Password must contain at least {$minPasswordLength} characters
{/msg}
{/template}


/** Error message for a password that is longer than the maximum password length. */
{template .errorPasswordNotMeetMaxLength kind="text"}
{@param maxPasswordLength: string} /** The maximum password length. */
{msg desc="Error message when the user enters a password that is longer than the maximum password
length."}
Password may contain at most {$maxPasswordLength} characters
{/msg}
{/template}


/** Error message for a password that does not contain a lower case character. */
{template .errorPasswordNotContainLowercaseLetter kind="text"}
{msg desc="Error message when the user enters a password that does not contain a lower case
character."}
Password must contain a lower case character
{/msg}
{/template}


/** Error message for a password that does not contain an upper case character. */
{template .errorPasswordNotContainUppercaseLetter kind="text"}
{msg desc="Error message when the user enters a password that does not contain an upper case
character."}
Password must contain an upper case character
{/msg}
{/template}


/** Error message for a password that does not contain a numeric character. */
{template .errorPasswordNotContainNumericCharacter kind="text"}
{msg desc="Error message when the user enters a password that does not contain a numeric
character."}
Password must contain a numeric character
{/msg}
{/template}


/** Error message for a password that does not contain a non-alphanumeric character. */
{template .errorPasswordNotContainNonAlphanumericCharacter kind="text"}
{msg desc="Error message when the user enters a password that does not contain a non-alphanumeric
character."}
Password must contain a non-alphanumeric character
{/msg}
{/template}


/** Translates an error code from Firebase Auth to a user-displayable string. */
{template .error kind="text"}
{@param? code: string} /** The error code. */
Expand Down Expand Up @@ -241,7 +346,7 @@
{case 'auth/weak-password'}
{msg desc="Error message for when the user tries to sign in or sign up with a password that is
too short."}
Strong passwords have at least 6 characters and a mix of letters and numbers
The password must be at least 6 characters long
{/msg}
{case 'auth/wrong-password'}
{msg desc="Error message for incorrect password."}
Expand Down Expand Up @@ -272,6 +377,11 @@
The action code is invalid. This can happen if the code is malformed, expired, or has
already been used.
{/msg}
{case 'auth/password-does-not-meet-requirements'}
{msg desc="Error message for when the user tries to sign in, sign up, or reset password with a
password that is not compliant with the policy."}
Missing password requirements:
{/msg}
{default}
{/switch}
{/template}
Expand Down
Loading

0 comments on commit 44884c7

Please sign in to comment.