diff --git a/src/libs/API.js b/src/libs/API.js index 852dee8497d8..b735e12d1bca 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -34,6 +34,7 @@ function isAuthTokenRequired(command) { 'SetPassword', 'User_SignUp', 'ResendValidateCode', + 'ResetPassword', ], command); } @@ -539,6 +540,17 @@ function ResendValidateCode(parameters) { return Network.post(commandName, parameters); } +/** + * @param {Object} parameters + * @param {Number} parameters.email + * @returns {Promise} + */ +function ResetPassword(parameters) { + const commandName = 'ResetPassword'; + requireParameters(['email'], parameters, commandName); + return Network.post(commandName, parameters); +} + /** * @param {Object} parameters * @param {String} parameters.password @@ -642,6 +654,7 @@ export { Report_TogglePinned, Report_UpdateLastRead, ResendValidateCode, + ResetPassword, SetNameValuePair, SetPassword, UpdateAccount, diff --git a/src/libs/actions/Session.js b/src/libs/actions/Session.js index a00ae68d0eda..759a7ba6c385 100644 --- a/src/libs/actions/Session.js +++ b/src/libs/actions/Session.js @@ -87,6 +87,8 @@ function fetchAccountDetails(login) { Onyx.merge(ONYXKEYS.ACCOUNT, { accountExists: response.accountExists, requiresTwoFactorAuth: response.requiresTwoFactorAuth, + validated: response.validated, + forgotPassword: false, }); if (!response.accountExists) { @@ -194,6 +196,17 @@ function resendValidationLink() { }); } +/** + * User forgot the password so let's send them the link to reset their password + */ +function resetPassword() { + Onyx.merge(ONYXKEYS.ACCOUNT, {loading: true, forgotPassword: true}); + API.ResetPassword({email: credentials.login}) + .finally(() => { + Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false}); + }); +} + /** * Restart the sign in process by clearing everything from Onyx */ @@ -238,5 +251,6 @@ export { signIn, signOut, resendValidationLink, + resetPassword, restartSignin, }; diff --git a/src/pages/signin/ChangeExpensifyLoginLink.js b/src/pages/signin/ChangeExpensifyLoginLink.js index 75341f8c5af2..901bb8d4906c 100644 --- a/src/pages/signin/ChangeExpensifyLoginLink.js +++ b/src/pages/signin/ChangeExpensifyLoginLink.js @@ -1,10 +1,21 @@ import React from 'react'; import {Text, TouchableOpacity, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import PropTypes from 'prop-types'; import styles from '../../styles/styles'; import {restartSignin} from '../../libs/actions/Session'; import themeColors from '../../styles/themes/default'; +import ONYXKEYS from '../../ONYXKEYS'; -const ChangeExpensifyLoginLink = () => ( +const propTypes = { + // The credentials of the logged in person + credentials: PropTypes.shape({ + // The email the user logged in with + login: PropTypes.string, + }).isRequired, +}; + +const ChangeExpensifyLoginLink = ({credentials}) => ( ( underlayColor={themeColors.componentBG} > - Change Expensify login + Not  + {credentials.login} ); +ChangeExpensifyLoginLink.propTypes = propTypes; ChangeExpensifyLoginLink.displayName = 'ChangeExpensifyLoginLink'; -export default ChangeExpensifyLoginLink; +export default withOnyx({ + credentials: {key: ONYXKEYS.CREDENTIALS}, +})(ChangeExpensifyLoginLink); diff --git a/src/pages/signin/PasswordForm.js b/src/pages/signin/PasswordForm.js index 68aca6d584ae..dc2428360e62 100644 --- a/src/pages/signin/PasswordForm.js +++ b/src/pages/signin/PasswordForm.js @@ -1,13 +1,13 @@ import React from 'react'; import { - Text, TextInput, View, + Text, TextInput, TouchableOpacity, View, } from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import styles from '../../styles/styles'; import ButtonWithLoader from '../../components/ButtonWithLoader'; import themeColors from '../../styles/themes/default'; -import {signIn} from '../../libs/actions/Session'; +import {signIn, resetPassword} from '../../libs/actions/Session'; import ONYXKEYS from '../../ONYXKEYS'; import ChangeExpensifyLoginLink from './ChangeExpensifyLoginLink'; @@ -78,6 +78,15 @@ class PasswordForm extends React.Component { autoFocus /> + + + Forgot? + + {this.props.account.requiresTwoFactorAuth && ( Two Factor Code diff --git a/src/pages/signin/ResendValidationForm.js b/src/pages/signin/ResendValidationForm.js index 19ea6c5e41a9..b5a598de5e1f 100644 --- a/src/pages/signin/ResendValidationForm.js +++ b/src/pages/signin/ResendValidationForm.js @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import _ from 'underscore'; import styles from '../../styles/styles'; import ButtonWithLoader from '../../components/ButtonWithLoader'; -import {resendValidationLink} from '../../libs/actions/Session'; +import {resendValidationLink, resetPassword} from '../../libs/actions/Session'; import ONYXKEYS from '../../ONYXKEYS'; import ChangeExpensifyLoginLink from './ChangeExpensifyLoginLink'; @@ -16,6 +16,9 @@ const propTypes = { account: PropTypes.shape({ // Whether or not a sign on form is loading (being submitted) loading: PropTypes.bool, + + // Weather or not the account is validated + validated: PropTypes.bool, }), }; @@ -48,7 +51,13 @@ class ResendValidationForm extends React.Component { formSuccess: 'Link has been re-sent', }); - resendValidationLink(); + if (!this.props.account.validated) { + resendValidationLink(); + console.debug('Account is unvalidated: Sending validation link.'); + } else { + resetPassword(); + console.debug('Account forgot password: Sending reset password link.'); + } this.successMessageTimer = setTimeout(() => { this.setState({formSuccess: ''}); @@ -60,7 +69,7 @@ class ResendValidationForm extends React.Component { - Please validate your account by clicking on the link we just sent you. + We've sent you a magic sign in link – just click on it to log in! diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index 7c805d6f58f4..4979f972b5a3 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -23,6 +23,12 @@ const propTypes = { // Error to display when there is an account error returned error: PropTypes.string, + + // Weather or not the account is validated + validated: PropTypes.bool, + + // Weather or not the account is validated + forgotPassword: PropTypes.bool, }), // The credentials of the person signing in @@ -56,21 +62,24 @@ class SignInPage extends Component { // - A login has not been entered yet const showLoginForm = !this.props.credentials.login; + const validAccount = this.props.account.accountExists + && this.props.account.validated + && !this.props.account.forgotPassword; + // Show the password form if // - A login has been entered // - AND a GitHub username has been entered OR they already have access to expensify cash - // - AND an account exists already for this login + // - AND an account exists and is validated for this login // - AND a password hasn't been entered yet const showPasswordForm = this.props.credentials.login - && this.props.account.accountExists + && validAccount && !this.props.credentials.password; // Show the resend validation link form if // - A login has been entered // - AND a GitHub username has been entered OR they already have access to this app - // - AND an account did not exist for that login - const showResendValidationLinkForm = this.props.credentials.login - && !this.props.account.accountExists; + // - AND an account did not exist or is not validated for that login + const showResendValidationLinkForm = this.props.credentials.login && !validAccount; return ( <>