diff --git a/app/actions/user/index.js b/app/actions/user/index.js index 95c7cc29afc..c370d110835 100644 --- a/app/actions/user/index.js +++ b/app/actions/user/index.js @@ -77,8 +77,17 @@ export function logOut() { }; } -export function checkedAuth() { +/** + * Temporary action to control auth flow + * + * @param {string} initialScreen - "login" or "onboarding" + * @returns - void + */ +export function checkedAuth(initialScreen) { return { type: 'CHECKED_AUTH', + payload: { + initialScreen, + }, }; } diff --git a/app/components/Nav/App/index.js b/app/components/Nav/App/index.js index ede931b418a..562d5222dc3 100644 --- a/app/components/Nav/App/index.js +++ b/app/components/Nav/App/index.js @@ -153,7 +153,7 @@ const App = ({ userLoggedIn }) => { const isAuthChecked = useSelector((state) => state.user.isAuthChecked); const dispatch = useDispatch(); - const triggerCheckedAuth = () => dispatch(checkedAuth()); + const triggerCheckedAuth = () => dispatch(checkedAuth('onboarding')); const handleDeeplink = useCallback(({ error, params, uri }) => { if (error) { diff --git a/app/components/Views/Login/index.js b/app/components/Views/Login/index.js index fe2911d70bf..0f0d060eabc 100644 --- a/app/components/Views/Login/index.js +++ b/app/components/Views/Login/index.js @@ -205,9 +205,9 @@ class Login extends PureComponent { */ setOnboardingWizardStep: PropTypes.func, /** - * Boolean flag that determines if password has been set + * Temporary string that controls if componentDidMount should handle initial auth logic on mount */ - passwordSet: PropTypes.bool, + initialScreen: PropTypes.string, /** * A string representing the selected address => account */ @@ -239,39 +239,30 @@ class Login extends PureComponent { fieldRef = React.createRef(); async componentDidMount() { + const { initialScreen } = this.props; const { KeyringController } = Engine.context; + const shouldHandleInitialAuth = initialScreen !== 'onboarding'; BackHandler.addEventListener('hardwareBackPress', this.handleBackPress); + // Lock keyring just in case if (KeyringController.isUnlocked()) { await KeyringController.setLocked(); } - if (!this.props.passwordSet) { - try { - await KeyringController.submitPassword(''); - await SecureKeychain.resetGenericPassword(); - this.props.navigation.navigate('HomeNav'); - } catch (e) { - // - } - } else { - const biometryType = await SecureKeychain.getSupportedBiometryType(); - if (biometryType) { - let enabled = true; - const previouslyDisabled = await AsyncStorage.getItem(BIOMETRY_CHOICE_DISABLED); - if (previouslyDisabled && previouslyDisabled === TRUE) { - enabled = false; - } + const biometryType = await SecureKeychain.getSupportedBiometryType(); + if (biometryType) { + const previouslyDisabled = await AsyncStorage.getItem(BIOMETRY_CHOICE_DISABLED); + const enabled = !(previouslyDisabled && previouslyDisabled === TRUE); - this.setState({ - biometryType: Device.isAndroid() ? 'biometrics' : biometryType, - biometryChoice: enabled, - biometryPreviouslyDisabled: !!previouslyDisabled, - }); + this.setState({ + biometryType: Device.isAndroid() ? 'biometrics' : biometryType, + biometryChoice: enabled, + biometryPreviouslyDisabled: !!previouslyDisabled, + }); + if (shouldHandleInitialAuth) { try { if (enabled && !previouslyDisabled) { - const hasBiometricCredentials = await this.tryBiometric(); - this.setState({ hasBiometricCredentials }); + await this.tryBiometric(); } } catch (e) { console.warn(e); @@ -279,9 +270,9 @@ class Login extends PureComponent { if (!enabled) { await this.checkIfRememberMeEnabled(); } - } else { - await this.checkIfRememberMeEnabled(); } + } else { + shouldHandleInitialAuth && (await this.checkIfRememberMeEnabled()); } this.props.checkedAuth(); @@ -318,13 +309,15 @@ class Login extends PureComponent { this.props.setOnboardingWizardStep(1); } + // Only way to land back on Login is to log out, which clears credentials (meaning we should not show biometric button) + this.setState({ hasBiometricCredentials: false }); delete credentials.password; this.props.logIn(); this.props.navigation.navigate('HomeNav'); } }; - onLogin = async () => { + onLogin = async (hasCredentials = false) => { const { password } = this.state; const { current: field } = this.fieldRef; const locked = !passwordRequirementsMet(password); @@ -342,7 +335,7 @@ class Login extends PureComponent { await AsyncStorage.setItem(ENCRYPTION_LIB, ORIGINAL); } // If the tryBiometric has been called and they password was retrived don't set it again - if (!this.state.hasBiometricCredentials) { + if (!hasCredentials) { if (this.state.biometryChoice && this.state.biometryType) { await SecureKeychain.setGenericPassword(this.state.password, SecureKeychain.TYPES.BIOMETRICS); } else if (this.state.rememberMe) { @@ -362,7 +355,8 @@ class Login extends PureComponent { this.props.setOnboardingWizardStep(1); this.props.navigation.navigate('HomeNav'); } - this.setState({ loading: false, password: '' }); + // Only way to land back on Login is to log out, which clears credentials (meaning we should not show biometric button) + this.setState({ loading: false, password: '', hasBiometricCredentials: false }); field.setValue(''); } catch (e) { // Should we force people to enable passcode / biometrics? @@ -394,6 +388,10 @@ class Login extends PureComponent { } }; + triggerLogIn = () => { + this.onLogin(); + }; + delete = async () => { const { KeyringController } = Engine.context; try { @@ -448,8 +446,6 @@ class Login extends PureComponent { updateBiometryChoice = async (biometryChoice) => { if (!biometryChoice) { await AsyncStorage.setItem(BIOMETRY_CHOICE_DISABLED, TRUE); - // This line will disable biometrics the next time SecureKeychain.getGenericPassword is called - await SecureKeychain.resetGenericPassword(); } else { await AsyncStorage.removeItem(BIOMETRY_CHOICE_DISABLED); } @@ -501,17 +497,24 @@ class Login extends PureComponent { field.blur(); try { const credentials = await SecureKeychain.getGenericPassword(); - if (!credentials) return false; + if (!credentials) { + this.setState({ hasBiometricCredentials: false }); + return; + } field.blur(); this.setState({ password: credentials.password }); field.setValue(credentials.password); field.blur(); - this.onLogin(); + await this.onLogin(true); } catch (error) { + const errObj = new Error(error); + const canceledBiometrics = errObj.message === 'Error: User canceled the operation.'; + if (canceledBiometrics) { + this.setState({ hasBiometricCredentials: true }); + } Logger.log(error); } field.blur(); - return true; }; render = () => ( @@ -601,7 +604,7 @@ class Login extends PureComponent { value={this.state.password} baseColor={colors.grey500} tintColor={colors.blue} - onSubmitEditing={this.onLogin} + onSubmitEditing={this.triggerLogIn} renderRightAccessory={() => ( - + {this.state.loading ? ( ) : ( @@ -655,15 +658,15 @@ class Login extends PureComponent { } const mapStateToProps = (state) => ({ - passwordSet: state.user.passwordSet, selectedAddress: state.engine.backgroundState.PreferencesController?.selectedAddress, + initialScreen: state.user.initialScreen, }); const mapDispatchToProps = (dispatch) => ({ setOnboardingWizardStep: (step) => dispatch(setOnboardingWizardStep(step)), logIn: () => dispatch(logIn()), logOut: () => dispatch(logOut()), - checkedAuth: () => dispatch(checkedAuth()), + checkedAuth: () => dispatch(checkedAuth('login')), }); export default connect(mapStateToProps, mapDispatchToProps)(Login); diff --git a/app/reducers/user/index.js b/app/reducers/user/index.js index f4369581a7d..3faf7f86c2a 100644 --- a/app/reducers/user/index.js +++ b/app/reducers/user/index.js @@ -8,6 +8,7 @@ const initialState = { gasEducationCarouselSeen: false, userLoggedIn: false, isAuthChecked: false, + initialScreen: '', }; const userReducer = (state = initialState, action) => { @@ -16,6 +17,7 @@ const userReducer = (state = initialState, action) => { return { ...state, isAuthChecked: true, + initialScreen: action.payload.initialScreen, }; case 'LOGIN': return { diff --git a/app/store/index.js b/app/store/index.js index d1ca9267476..47c62c669f9 100644 --- a/app/store/index.js +++ b/app/store/index.js @@ -87,7 +87,7 @@ const persistTransform = createTransform( const persistUserTransform = createTransform( (inboundState) => { - const { isAuthChecked, ...state } = inboundState; + const { initialScreen, isAuthChecked, ...state } = inboundState; // Reconstruct data to persist return state; }, diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4171fa0011e..d0cf51d0310 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -672,7 +672,7 @@ SPEC CHECKSUMS: Branch: 6a281514287f99d707615ac62c2cca69e0213df0 BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 - DoubleConversion: cde416483dac037923206447da6e1454df403714 + DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662 FBLazyVector: 3bb422f41b18121b71783a905c10e58606f7dc3e FBReactNativeSpec: f2c97f2529dd79c083355182cc158c9f98f4bd6e Flipper: 1bd2db48dcc31e4b167b9a33ec1df01c2ded4893 @@ -683,7 +683,7 @@ SPEC CHECKSUMS: Flipper-RSocket: 127954abe8b162fcaf68d2134d34dc2bd7076154 FlipperKit: 651f50a42eb95c01b3e89a60996dd6aded529eeb Folly: b73c3869541e86821df3c387eb0af5f65addfab4 - glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3 + glog: 5337263514dd6f09803962437687240c5dc39aa4 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 lottie-ios: a50d5c0160425cd4b01b852bb9578963e6d92d31 lottie-react-native: 7ca15c46249b61e3f9ffcf114cb4123e907a2156