Skip to content
3 changes: 3 additions & 0 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,7 @@ export default {

// The policyID of the last workspace whose settings were accessed by the user
LAST_ACCESSED_WORKSPACE_POLICY_ID: 'lastAccessedWorkspacePolicyID',

// Validating Email?
USER_SIGN_UP: 'userSignUp',
};
5 changes: 3 additions & 2 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,9 @@ export default {
setPassword: 'Set password',
newPasswordPrompt: 'Your password must have at least 8 characters, 1 capital letter, 1 lowercase letter, and 1 number.',
passwordFormTitle: 'Welcome back to the New Expensify! Please set your password.',
passwordNotSet: 'We were unable to set your new password correctly.',
accountNotValidated: 'We were unable to validate your account. The validation code may have expired.',
passwordNotSet: 'We were unable to set your new password. We have sent you a new password link to try again.',
setPasswordLinkInvalid: 'This set password link is invalid or has expired. A new one is waiting for you in your email inbox!',
verifyingAccount: 'Verifying account',
},
stepCounter: ({step, total}) => `Step ${step} of ${total}`,
bankAccount: {
Expand Down
5 changes: 3 additions & 2 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,9 @@ export default {
setPassword: 'Configura tu contraseña',
newPasswordPrompt: 'La contraseña debe tener al menos 8 caracteres, 1 letra mayúscula, 1 letra minúscula y 1 número.',
passwordFormTitle: '¡Bienvenido de vuelta al Nuevo Expensify! Por favor, elige una contraseña.',
passwordNotSet: 'No pudimos establecer to contaseña correctamente.',
accountNotValidated: 'No pudimos validar tu cuenta. Es posible que el enlace de validación haya caducado.',
passwordNotSet: 'No pudimos cambiar tu clave. Te hemos enviado un nuevo enlace para que intentes cambiar la clave nuevamente.',
setPasswordLinkInvalid: 'El enlace para configurar tu contraseña ha expirado. Te hemos enviado un nuevo enlace a tu correo.',
verifyingAccount: 'Verificando cuenta',
},
stepCounter: ({step, total}) => `Paso ${step} de ${total}`,
bankAccount: {
Expand Down
8 changes: 4 additions & 4 deletions src/libs/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,8 @@ function AddBillingCard(parameters) {


/**
* @param {Object} parameters
* @param {String} parameters.oldPassword
* @param {{password: String, oldPassword: String}} parameters
* @param {String} parameters.authToken
* @param {String} parameters.password
* @returns {Promise}
*/
Expand Down Expand Up @@ -717,7 +717,7 @@ function SetNameValuePair(parameters) {

/**
* @param {Object} parameters
* @param {Number} parameters.email
* @param {string} parameters.email
* @returns {Promise}
*/
function ResetPassword(parameters) {
Expand All @@ -730,7 +730,7 @@ function ResetPassword(parameters) {
* @param {Object} parameters
* @param {String} parameters.password
* @param {String} parameters.validateCode
* @param {String} parameters.accountID
* @param {Number} parameters.accountID
* @returns {Promise}
*/
function SetPassword(parameters) {
Expand Down
69 changes: 51 additions & 18 deletions src/libs/actions/Session/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,11 +293,10 @@ function resetPassword() {
*
* @param {String} password
* @param {String} validateCode
* @param {String} accountID
* @param {Number} accountID
*/
function setPassword(password, validateCode, accountID) {
Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true, validateCodeExpired: false});

API.SetPassword({
password,
validateCode,
Expand Down Expand Up @@ -384,52 +383,84 @@ function clearAccountMessages() {
}

/**
* Calls change password and signs if if successful. Otherwise, we request a new magic link
* if we know the account email. Otherwise or finally we redirect to the root of the nav.
* @param {String} authToken
* @param {String} password
*/
function changePasswordAndSignIn(authToken, password) {
Onyx.merge(ONYXKEYS.ACCOUNT, {validateSessionExpired: false});
API.ChangePassword({
authToken,
password,
})
.then((responsePassword) => {
Onyx.merge(ONYXKEYS.USER_SIGN_UP, {authToken: null});
if (responsePassword.jsonCode === 200) {
signIn(password);
return;
}

if (responsePassword.jsonCode === 407 && !credentials.login) {
// authToken has expired, and we don't have the email set to request a new magic link.
// send user to login page to enter email.
Navigation.navigate(ROUTES.HOME);
return;
}
if (responsePassword.jsonCode === 407) {
// authToken has expired, and we have the account email, so we request a new magic link.
Onyx.merge(ONYXKEYS.ACCOUNT, {accountExists: true, validateCodeExpired: true, error: null});
resetPassword();
Navigation.navigate(ROUTES.HOME);
return;
}
Onyx.merge(ONYXKEYS.SESSION, {error: 'setPasswordPage.passwordNotSet'});
});
}

/**
* @param {String} accountID
* Call set or change password based on if we have an auth token
* @param {Number} accountID
* @param {String} validateCode
* @param {String} password
* @param {String} authToken
*/
function validateEmail(accountID, validateCode, password) {

function setOrChangePassword(accountID, validateCode, password, authToken) {
if (authToken) {
changePasswordAndSignIn(authToken, password);
return;
}
setPassword(password, validateCode, accountID);
}

/**
* @param {Number} accountID
* @param {String} validateCode
* @param {String} login
* @param {String} authToken
*/
function validateEmail(accountID, validateCode) {
Onyx.merge(ONYXKEYS.USER_SIGN_UP, {isValidating: true});
Onyx.merge(ONYXKEYS.SESSION, {error: ''});
API.ValidateEmail({
accountID,
validateCode,
})
.then((responseValidate) => {
if (responseValidate.jsonCode === 200) {
changePasswordAndSignIn(responseValidate.authToken, password);
Onyx.merge(ONYXKEYS.USER_SIGN_UP, {authToken: responseValidate.authToken});
Onyx.merge(ONYXKEYS.ACCOUNT, {accountExists: true, validated: true});
Onyx.merge(ONYXKEYS.CREDENTIALS, {login: responseValidate.email});
return;
}

if (responseValidate.title === CONST.PASSWORD_PAGE.ERROR.ALREADY_VALIDATED) {
// If the email is already validated, set the password using the validate code
setPassword(
password,
validateCode,
accountID,
);
return;
if (responseValidate.jsonCode === 666) {
Onyx.merge(ONYXKEYS.ACCOUNT, {accountExists: true, validated: true});
}

Onyx.merge(ONYXKEYS.SESSION, {error: 'setPasswordPage.accountNotValidated'});
});
if (responseValidate.jsonCode === 401) {
Onyx.merge(ONYXKEYS.SESSION, {error: 'setPasswordPage.setPasswordLinkInvalid'});
}
})
.finally(Onyx.merge(ONYXKEYS.USER_SIGN_UP, {isValidating: false}));
}

// It's necessary to throttle requests to reauthenticate since calling this multiple times will cause Pusher to
Expand Down Expand Up @@ -493,6 +524,7 @@ function setShouldShowComposeInput(shouldShowComposeInput) {
export {
continueSessionFromECom,
fetchAccountDetails,
setOrChangePassword,
setPassword,
signIn,
signInWithShortLivedToken,
Expand All @@ -507,4 +539,5 @@ export {
authenticatePusher,
reauthenticatePusher,
setShouldShowComposeInput,
changePasswordAndSignIn,
};
82 changes: 52 additions & 30 deletions src/pages/SetPasswordPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const propTypes = {
/** An error message to display to the user */
error: PropTypes.string,

/** Whether or not a sign on form is loading (being submitted) */
/** Whether a sign on form is loading (being submitted) */
loading: PropTypes.bool,
}),

Expand All @@ -48,6 +48,15 @@ const propTypes = {
error: PropTypes.string,
}),

/** User signup object */
userSignUp: PropTypes.shape({
/** Is Validating Email */
isValidating: PropTypes.bool,

/** Auth token used to change password */
authToken: PropTypes.string,
}),

/** The accountID and validateCode are passed via the URL */
route: validateLinkPropTypes,

Expand All @@ -60,6 +69,11 @@ const defaultProps = {
route: validateLinkDefaultProps,
session: {
error: '',
authToken: '',
},
userSignUp: {
isValidating: false,
authToken: '',
},
};

Expand All @@ -75,20 +89,27 @@ class SetPasswordPage extends Component {
};
}

/**
* Validate the form and then submit it
*/
validateAndSubmitForm() {
componentDidMount() {
const accountID = lodashGet(this.props.route.params, 'accountID', '');
const validateCode = lodashGet(this.props.route.params, 'validateCode', '');
if (!this.state.isFormValid) {
if (this.props.userSignUp.authToken) {
return;
}
Session.validateEmail(accountID, validateCode);
}

Session.validateEmail(accountID, validateCode, this.state.password);
validateAndSubmitForm() {
if (!this.state.isFormValid) {
return;
}
const accountID = lodashGet(this.props.route.params, 'accountID', '');
const validateCode = lodashGet(this.props.route.params, 'validateCode', '');
Session.setOrChangePassword(accountID, validateCode, this.state.password, this.props.userSignUp.authToken);
}


render() {
const buttonText = this.props.userSignUp.isValidating ? this.props.translate('setPasswordPage.verifyingAccount') : this.props.translate('setPasswordPage.setPassword');
const sessionError = this.props.session.error && this.props.translate(this.props.session.error);
const error = sessionError || this.props.account.error;
return (
Expand All @@ -97,29 +118,29 @@ class SetPasswordPage extends Component {
shouldShowWelcomeText
welcomeText={this.props.translate('setPasswordPage.passwordFormTitle')}
>
<View style={[styles.mb4]}>
<NewPasswordForm
password={this.state.password}
updatePassword={password => this.setState({password})}
updateIsFormValid={isValid => this.setState({isFormValid: isValid})}
onSubmitEditing={this.validateAndSubmitForm}
/>
</View>
<View>
<Button
success
style={[styles.mb2]}
text={this.props.translate('setPasswordPage.setPassword')}
isLoading={this.props.account.loading}
onPress={this.validateAndSubmitForm}
isDisabled={!this.state.isFormValid}
/>
</View>
{!_.isEmpty(error) && (
<Text style={[styles.formError]}>
{error}
</Text>
)}
{_.isEmpty(error) ? (
<>
<View style={[styles.mb4]}>
<NewPasswordForm
password={this.state.password}
updatePassword={password => this.setState({password})}
updateIsFormValid={isValid => this.setState({isFormValid: isValid})}
onSubmitEditing={this.validateAndSubmitForm}
/>
</View>
<View>
<Button
success
style={[styles.mb2]}
text={buttonText}
isLoading={this.props.account.loading || this.props.userSignUp.isValidatingEmail}
onPress={this.validateAndSubmitForm}
isDisabled={!this.state.isFormValid}
/>
</View>
</>
)
: (<Text>{error}</Text>)}
</SignInPageLayout>
</SafeAreaView>
);
Expand All @@ -138,5 +159,6 @@ export default compose(
key: ONYXKEYS.SESSION,
initWithStoredValues: false,
},
userSignUp: {key: ONYXKEYS.USER_SIGN_UP},
}),
)(SetPasswordPage);