Page MenuHomePhabricator

D12326.diff
No OneTemporary

D12326.diff

diff --git a/lib/actions/user-actions.js b/lib/actions/user-actions.js
--- a/lib/actions/user-actions.js
+++ b/lib/actions/user-actions.js
@@ -384,7 +384,7 @@
failed: 'DELETE_ACCOUNT_FAILED',
});
-function useDeleteAccount(): () => Promise<LogOutResult> {
+function useDeleteAccount(): (password: ?string) => Promise<LogOutResult> {
const client = React.useContext(IdentityClientContext);
const identityClient = client?.identityClient;
@@ -395,39 +395,60 @@
state => state.commServicesAccessToken,
);
- return React.useCallback(async () => {
- const identityPromise = (async () => {
- if (!usingCommServicesAccessToken) {
- return undefined;
- }
- if (!identityClient) {
- throw new Error('Identity service client is not initialized');
+ return React.useCallback(
+ async password => {
+ if (usingCommServicesAccessToken) {
+ if (!identityClient) {
+ throw new Error('Identity service client is not initialized');
+ }
+ if (
+ !identityClient.deleteWalletUser ||
+ !identityClient.deletePasswordUser
+ ) {
+ throw new Error('Delete user method unimplemented');
+ }
+ if (password) {
+ await identityClient.deletePasswordUser(password);
+ } else {
+ await identityClient.deleteWalletUser();
+ }
}
- if (!identityClient.deleteWalletUser) {
- throw new Error('Delete wallet user method unimplemented');
+ try {
+ const keyserverResult = await callKeyserverDeleteAccount({
+ preRequestUserState,
+ });
+ const { keyserverIDs: _, ...result } = keyserverResult;
+ return {
+ ...result,
+ preRequestUserState: {
+ ...result.preRequestUserState,
+ commServicesAccessToken,
+ },
+ };
+ } catch (e) {
+ if (!usingCommServicesAccessToken) {
+ throw e;
+ }
+ console.log(
+ 'Failed to delete account on keyserver:',
+ getMessageForException(e),
+ );
}
- return await identityClient.deleteWalletUser();
- })();
- const [keyserverResult] = await Promise.all([
- callKeyserverDeleteAccount({
- preRequestUserState,
- }),
- identityPromise,
- ]);
- const { keyserverIDs: _, ...result } = keyserverResult;
- return {
- ...result,
- preRequestUserState: {
- ...result.preRequestUserState,
- commServicesAccessToken,
- },
- };
- }, [
- callKeyserverDeleteAccount,
- commServicesAccessToken,
- identityClient,
- preRequestUserState,
- ]);
+ return {
+ currentUserInfo: null,
+ preRequestUserState: {
+ ...preRequestUserState,
+ commServicesAccessToken,
+ },
+ };
+ },
+ [
+ callKeyserverDeleteAccount,
+ commServicesAccessToken,
+ identityClient,
+ preRequestUserState,
+ ],
+ );
}
// Unlike useDeleteAccount, we always dispatch a success here (never throw).
diff --git a/native/profile/delete-account.react.js b/native/profile/delete-account.react.js
--- a/native/profile/delete-account.react.js
+++ b/native/profile/delete-account.react.js
@@ -1,7 +1,12 @@
// @flow
import * as React from 'react';
-import { Text, View, ActivityIndicator } from 'react-native';
+import {
+ Text,
+ View,
+ ActivityIndicator,
+ TextInput as BaseTextInput,
+} from 'react-native';
import { ScrollView } from 'react-native-gesture-handler';
import {
@@ -9,14 +14,19 @@
useDeleteAccount,
} from 'lib/actions/user-actions.js';
import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
+import { accountHasPassword } from 'lib/shared/account-utils.js';
+import { getMessageForException } from 'lib/utils/errors.js';
import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js';
+import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js';
import type { ProfileNavigationProp } from './profile.react.js';
import { deleteNativeCredentialsFor } from '../account/native-credentials.js';
import Button from '../components/button.react.js';
+import TextInput from '../components/text-input.react.js';
import type { NavigationRoute } from '../navigation/route-names.js';
import { useSelector } from '../redux/redux-utils.js';
-import { useStyles } from '../themes/colors.js';
+import { useStyles, useColors } from '../themes/colors.js';
+import { unknownErrorAlertDetails } from '../utils/alert-messages.js';
import Alert from '../utils/alert.js';
const deleteAccountLoadingStatusSelector = createLoadingStatusSelector(
@@ -34,6 +44,11 @@
);
const styles = useStyles(unboundStyles);
+ const isAccountWithPassword = useSelector(state =>
+ accountHasPassword(state.currentUserInfo),
+ );
+ const { panelForegroundTertiaryLabel } = useColors();
+ const [password, setPassword] = React.useState('');
const dispatchActionPromise = useDispatchActionPromise();
const callDeleteAccount = useDeleteAccount();
@@ -50,30 +65,79 @@
() => [styles.warningText, styles.lastWarningText],
[styles.warningText, styles.lastWarningText],
);
+ const passwordInputRef =
+ React.useRef<?React.ElementRef<typeof BaseTextInput>>(null);
+
+ const onErrorAlertAcknowledged = React.useCallback(() => {
+ passwordInputRef.current?.focus();
+ }, []);
const deleteAccountAction = React.useCallback(async () => {
try {
await deleteNativeCredentialsFor();
- return await callDeleteAccount();
+ return await callDeleteAccount(password);
} catch (e) {
- Alert.alert(
- 'Unknown error deleting account',
- 'Uhh... try again?',
- [{ text: 'OK' }],
- {
- cancelable: false,
- },
- );
+ if (getMessageForException(e) === 'login failed') {
+ Alert.alert(
+ 'Incorrect password',
+ 'The password you entered is incorrect',
+ [{ text: 'OK', onPress: onErrorAlertAcknowledged }],
+ { cancelable: false },
+ );
+ } else {
+ Alert.alert(
+ unknownErrorAlertDetails.title,
+ unknownErrorAlertDetails.message,
+ [{ text: 'OK', onPress: onErrorAlertAcknowledged }],
+ { cancelable: false },
+ );
+ }
throw e;
}
- }, [callDeleteAccount]);
+ }, [callDeleteAccount, onErrorAlertAcknowledged, password]);
const onDelete = React.useCallback(() => {
+ if (!password && isAccountWithPassword && usingCommServicesAccessToken) {
+ Alert.alert('Password required', 'Please enter your password.', [
+ { text: 'OK', onPress: onErrorAlertAcknowledged },
+ ]);
+ return;
+ }
void dispatchActionPromise(
deleteAccountActionTypes,
deleteAccountAction(),
);
- }, [dispatchActionPromise, deleteAccountAction]);
+ }, [
+ password,
+ isAccountWithPassword,
+ dispatchActionPromise,
+ deleteAccountAction,
+ onErrorAlertAcknowledged,
+ ]);
+
+ let inputPasswordPrompt;
+ if (isAccountWithPassword && usingCommServicesAccessToken) {
+ inputPasswordPrompt = (
+ <>
+ <Text style={styles.header}>PASSWORD</Text>
+ <View style={styles.section}>
+ <TextInput
+ style={styles.input}
+ value={password}
+ onChangeText={setPassword}
+ placeholder="Password"
+ placeholderTextColor={panelForegroundTertiaryLabel}
+ secureTextEntry={true}
+ textContentType="password"
+ autoComplete="password"
+ returnKeyType="go"
+ onSubmitEditing={onDelete}
+ ref={passwordInputRef}
+ />
+ </View>
+ </>
+ );
+ }
return (
<ScrollView
@@ -90,6 +154,7 @@
There is no way to reverse this.
</Text>
</View>
+ {inputPasswordPrompt}
<Button
onPress={onDelete}
style={styles.deleteButton}
@@ -131,6 +196,32 @@
marginHorizontal: 24,
textAlign: 'center',
},
+ input: {
+ color: 'panelForegroundLabel',
+ flex: 1,
+ fontFamily: 'Arial',
+ fontSize: 16,
+ paddingVertical: 0,
+ borderBottomColor: 'transparent',
+ },
+ section: {
+ backgroundColor: 'panelForeground',
+ borderBottomWidth: 1,
+ borderColor: 'panelForegroundBorder',
+ borderTopWidth: 1,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginBottom: 24,
+ paddingHorizontal: 24,
+ paddingVertical: 12,
+ },
+ header: {
+ color: 'panelBackgroundLabel',
+ fontSize: 12,
+ fontWeight: '400',
+ paddingBottom: 3,
+ paddingHorizontal: 24,
+ },
};
export default DeleteAccount;

File Metadata

Mime Type
text/plain
Expires
Fri, Dec 20, 8:32 AM (21 h, 11 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2681180
Default Alt Text
D12326.diff (8 KB)

Event Timeline