(function ConnectedLogInPanel(props: BaseProps) {
const logInLoadingStatus = useSelector(logInLoadingStatusSelector);
const olmSessionInitializationDataLoadingStatus = useSelector(
olmSessionInitializationDataLoadingStatusSelector,
);
const loadingStatus = combineLoadingStatuses(
logInLoadingStatus,
olmSessionInitializationDataLoadingStatus,
);
const legacyLogInExtraInfo = useSelector(
nativeLegacyLogInExtraInfoSelector,
);
const dispatchActionPromise = useDispatchActionPromise();
const callLegacyLogIn = useLegacyLogIn();
const callIdentityPasswordLogIn = usePasswordLogIn();
const getInitialNotificationsEncryptedMessage =
useInitialNotificationsEncryptedMessage(authoritativeKeyserverID);
return (
);
});
export default ConnectedLogInPanel;
diff --git a/native/account/registration/existing-ethereum-account.react.js b/native/account/registration/existing-ethereum-account.react.js
index 4279edaf9..5100f205e 100644
--- a/native/account/registration/existing-ethereum-account.react.js
+++ b/native/account/registration/existing-ethereum-account.react.js
@@ -1,212 +1,213 @@
// @flow
import type {
StackNavigationEventMap,
StackNavigationState,
StackOptions,
} from '@react-navigation/core';
import invariant from 'invariant';
import * as React from 'react';
import { Text, View } from 'react-native';
import { setDataLoadedActionType } from 'lib/actions/client-db-store-actions.js';
import { useENSName } from 'lib/hooks/ens-cache.js';
import { useWalletLogIn } from 'lib/hooks/login-hooks.js';
import type { SIWEResult } from 'lib/types/siwe-types.js';
import { getMessageForException } from 'lib/utils/errors.js';
import { useDispatch } from 'lib/utils/redux-utils.js';
import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js';
import RegistrationButtonContainer from './registration-button-container.react.js';
import RegistrationContainer from './registration-container.react.js';
import RegistrationContentContainer from './registration-content-container.react.js';
import { RegistrationContext } from './registration-context.js';
import type { RegistrationNavigationProp } from './registration-navigator.react.js';
import PrimaryButton from '../../components/primary-button.react.js';
import type { RootNavigationProp } from '../../navigation/root-navigator.react.js';
import type {
NavigationRoute,
ScreenParamList,
} from '../../navigation/route-names.js';
import { useStyles } from '../../themes/colors.js';
import {
unknownErrorAlertDetails,
appOutOfDateAlertDetails,
} from '../../utils/alert-messages.js';
import Alert from '../../utils/alert.js';
import { useLegacySIWEServerCall } from '../siwe-hooks.js';
export type ExistingEthereumAccountParams = SIWEResult;
type Props = {
+navigation: RegistrationNavigationProp<'ExistingEthereumAccount'>,
+route: NavigationRoute<'ExistingEthereumAccount'>,
};
function ExistingEthereumAccount(props: Props): React.Node {
const legacySiweServerCall = useLegacySIWEServerCall();
const walletLogIn = useWalletLogIn();
const [logInPending, setLogInPending] = React.useState(false);
const registrationContext = React.useContext(RegistrationContext);
invariant(registrationContext, 'registrationContext should be set');
const { setCachedSelections } = registrationContext;
const { params } = props.route;
const dispatch = useDispatch();
const { navigation } = props;
const goBackToHome = navigation.getParent<
ScreenParamList,
'Registration',
StackNavigationState,
StackOptions,
StackNavigationEventMap,
RootNavigationProp<'Registration'>,
>()?.goBack;
const onProceedToLogIn = React.useCallback(async () => {
if (logInPending) {
return;
}
setLogInPending(true);
try {
if (usingCommServicesAccessToken) {
await walletLogIn(params.address, params.message, params.signature);
} else {
await legacySiweServerCall({ ...params, doNotRegister: true });
dispatch({
type: setDataLoadedActionType,
payload: {
dataLoaded: true,
},
});
}
} catch (e) {
const messageForException = getMessageForException(e);
if (messageForException === 'nonce_expired') {
setCachedSelections(oldUserSelections => ({
...oldUserSelections,
ethereumAccount: undefined,
}));
Alert.alert(
'Login attempt timed out',
'Try logging in from the main SIWE button on the home screen',
[{ text: 'OK', onPress: goBackToHome }],
{
cancelable: false,
},
);
} else if (
messageForException === 'unsupported_version' ||
- messageForException === 'client_version_unsupported'
+ messageForException === 'client_version_unsupported' ||
+ messageForException === 'use_new_flow'
) {
Alert.alert(
appOutOfDateAlertDetails.title,
appOutOfDateAlertDetails.message,
[{ text: 'OK', onPress: goBackToHome }],
{ cancelable: false },
);
} else {
Alert.alert(
unknownErrorAlertDetails.title,
unknownErrorAlertDetails.message,
[{ text: 'OK' }],
{
cancelable: false,
},
);
}
throw e;
} finally {
setLogInPending(false);
}
}, [
logInPending,
legacySiweServerCall,
walletLogIn,
params,
dispatch,
goBackToHome,
setCachedSelections,
]);
const { address } = params;
const walletIdentifier = useENSName(address);
const walletIdentifierTitle =
walletIdentifier === address ? 'Ethereum wallet' : 'ENS name';
const { goBack } = navigation;
const styles = useStyles(unboundStyles);
return (
Account already exists for wallet
You can proceed to log in with this wallet, or go back and use a
different wallet.
{walletIdentifierTitle}
{walletIdentifier}
);
}
const unboundStyles = {
header: {
fontSize: 24,
color: 'panelForegroundLabel',
paddingBottom: 16,
},
body: {
fontFamily: 'Arial',
fontSize: 15,
lineHeight: 20,
color: 'panelForegroundSecondaryLabel',
paddingBottom: 40,
},
walletTile: {
backgroundColor: 'panelForeground',
borderRadius: 8,
padding: 24,
alignItems: 'center',
},
walletIdentifierTitleText: {
fontSize: 17,
color: 'panelForegroundLabel',
textAlign: 'center',
},
walletIdentifier: {
backgroundColor: 'panelSecondaryForeground',
paddingVertical: 8,
paddingHorizontal: 24,
borderRadius: 56,
marginTop: 8,
alignItems: 'center',
},
walletIdentifierText: {
fontSize: 15,
color: 'panelForegroundLabel',
},
};
export default ExistingEthereumAccount;
diff --git a/web/account/siwe-login-form.react.js b/web/account/siwe-login-form.react.js
index 3761707b5..fd6ddc704 100644
--- a/web/account/siwe-login-form.react.js
+++ b/web/account/siwe-login-form.react.js
@@ -1,364 +1,365 @@
// @flow
import '@rainbow-me/rainbowkit/styles.css';
import classNames from 'classnames';
import invariant from 'invariant';
import * as React from 'react';
import { useAccount, useWalletClient } from 'wagmi';
import { setDataLoadedActionType } from 'lib/actions/client-db-store-actions.js';
import {
getSIWENonce,
getSIWENonceActionTypes,
legacySiweAuth,
legacySiweAuthActionTypes,
} from 'lib/actions/siwe-actions.js';
import {
identityGenerateNonceActionTypes,
useIdentityGenerateNonce,
} from 'lib/actions/user-actions.js';
import ConnectedWalletInfo from 'lib/components/connected-wallet-info.react.js';
import SWMansionIcon from 'lib/components/swmansion-icon.react.js';
import stores from 'lib/facts/stores.js';
import { useWalletLogIn } from 'lib/hooks/login-hooks.js';
import { useLegacyAshoatKeyserverCall } from 'lib/keyserver-conn/legacy-keyserver-call.js';
import { legacyLogInExtraInfoSelector } from 'lib/selectors/account-selectors.js';
import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
import type {
LegacyLogInStartingPayload,
LegacyLogInExtraInfo,
} from 'lib/types/account-types.js';
import { SIWEMessageTypes } from 'lib/types/siwe-types.js';
import { getMessageForException } from 'lib/utils/errors.js';
import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js';
import { useDispatch } from 'lib/utils/redux-utils.js';
import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js';
import {
createSIWEMessage,
getSIWEStatementForPublicKey,
siweMessageSigningExplanationStatements,
} from 'lib/utils/siwe-utils.js';
import HeaderSeparator from './header-separator.react.js';
import css from './siwe.css';
import Button from '../components/button.react.js';
import OrBreak from '../components/or-break.react.js';
import { olmAPI } from '../crypto/olm-api.js';
import LoadingIndicator from '../loading-indicator.react.js';
import { useSelector } from '../redux/redux-utils.js';
import { getVersionUnsupportedError } from '../utils/version-utils.js';
type SIWELogInError =
| 'account_does_not_exist'
| 'client_version_unsupported'
| 'retry_from_native';
type SIWELoginFormProps = {
+cancelSIWEAuthFlow: () => void,
};
const legacyGetSIWENonceLoadingStatusSelector = createLoadingStatusSelector(
getSIWENonceActionTypes,
);
const identityGenerateNonceLoadingStatusSelector = createLoadingStatusSelector(
identityGenerateNonceActionTypes,
);
const legacySiweAuthLoadingStatusSelector = createLoadingStatusSelector(
legacySiweAuthActionTypes,
);
function SIWELoginForm(props: SIWELoginFormProps): React.Node {
const { address } = useAccount();
const { data: signer } = useWalletClient();
const dispatchActionPromise = useDispatchActionPromise();
const legacyGetSIWENonceCall = useLegacyAshoatKeyserverCall(getSIWENonce);
const legacyGetSIWENonceCallLoadingStatus = useSelector(
legacyGetSIWENonceLoadingStatusSelector,
);
const identityGenerateNonce = useIdentityGenerateNonce();
const identityGenerateNonceLoadingStatus = useSelector(
identityGenerateNonceLoadingStatusSelector,
);
const siweAuthLoadingStatus = useSelector(
legacySiweAuthLoadingStatusSelector,
);
const legacySiweAuthCall = useLegacyAshoatKeyserverCall(legacySiweAuth);
const legacyLogInExtraInfo = useSelector(legacyLogInExtraInfoSelector);
const walletLogIn = useWalletLogIn();
const [siweNonce, setSIWENonce] = React.useState(null);
const siweNonceShouldBeFetched =
!siweNonce &&
legacyGetSIWENonceCallLoadingStatus !== 'loading' &&
identityGenerateNonceLoadingStatus !== 'loading';
React.useEffect(() => {
if (!siweNonceShouldBeFetched) {
return;
}
if (usingCommServicesAccessToken) {
void dispatchActionPromise(
identityGenerateNonceActionTypes,
(async () => {
const response = await identityGenerateNonce();
setSIWENonce(response);
})(),
);
} else {
void dispatchActionPromise(
getSIWENonceActionTypes,
(async () => {
const response = await legacyGetSIWENonceCall();
setSIWENonce(response);
})(),
);
}
}, [
dispatchActionPromise,
identityGenerateNonce,
legacyGetSIWENonceCall,
siweNonceShouldBeFetched,
]);
const callLegacySIWEAuthEndpoint = React.useCallback(
async (
message: string,
signature: string,
extraInfo: LegacyLogInExtraInfo,
) => {
await olmAPI.initializeCryptoAccount();
const userPublicKey = await olmAPI.getUserPublicKey();
try {
return await legacySiweAuthCall({
message,
signature,
signedIdentityKeysBlob: {
payload: userPublicKey.blobPayload,
signature: userPublicKey.signature,
},
doNotRegister: true,
...extraInfo,
});
} catch (e) {
const messageForException = getMessageForException(e);
if (messageForException === 'account_does_not_exist') {
setError('account_does_not_exist');
} else if (messageForException === 'client_version_unsupported') {
setError('client_version_unsupported');
}
throw e;
}
},
[legacySiweAuthCall],
);
const attemptLegacySIWEAuth = React.useCallback(
(message: string, signature: string) => {
return dispatchActionPromise(
legacySiweAuthActionTypes,
callLegacySIWEAuthEndpoint(message, signature, legacyLogInExtraInfo),
undefined,
({
calendarQuery: legacyLogInExtraInfo.calendarQuery,
}: LegacyLogInStartingPayload),
);
},
[callLegacySIWEAuthEndpoint, dispatchActionPromise, legacyLogInExtraInfo],
);
const attemptWalletLogIn = React.useCallback(
async (
walletAddress: string,
siweMessage: string,
siweSignature: string,
) => {
try {
await walletLogIn(walletAddress, siweMessage, siweSignature);
} catch (e) {
const messageForException = getMessageForException(e);
if (messageForException === 'user_not_found') {
setError('account_does_not_exist');
} else if (
messageForException === 'client_version_unsupported' ||
- messageForException === 'unsupported_version'
+ messageForException === 'unsupported_version' ||
+ messageForException === 'use_new_flow'
) {
setError('client_version_unsupported');
} else if (messageForException === 'retry_from_native') {
setError('retry_from_native');
}
}
},
[walletLogIn],
);
const dispatch = useDispatch();
const onSignInButtonClick = React.useCallback(async () => {
invariant(signer, 'signer must be present during SIWE attempt');
invariant(siweNonce, 'nonce must be present during SIWE attempt');
await olmAPI.initializeCryptoAccount();
const {
primaryIdentityPublicKeys: { ed25519 },
} = await olmAPI.getUserPublicKey();
const statement = getSIWEStatementForPublicKey(
ed25519,
SIWEMessageTypes.MSG_AUTH,
);
const message = createSIWEMessage(address, statement, siweNonce);
let signature;
try {
signature = await signer.signMessage({ message });
} catch (e) {
// If we fail to get the signature (e.g. user cancels the request), we
// should return immediately
return;
}
if (usingCommServicesAccessToken) {
await attemptWalletLogIn(address, message, signature);
} else {
await attemptLegacySIWEAuth(message, signature);
dispatch({
type: setDataLoadedActionType,
payload: {
dataLoaded: true,
},
});
}
}, [
address,
attemptLegacySIWEAuth,
attemptWalletLogIn,
signer,
siweNonce,
dispatch,
]);
const { cancelSIWEAuthFlow } = props;
const backButtonColor = React.useMemo(
() => ({ backgroundColor: '#211E2D' }),
[],
);
const signInButtonColor = React.useMemo(
() => ({ backgroundColor: '#6A20E3' }),
[],
);
const [error, setError] = React.useState();
const mainMiddleAreaClassName = classNames({
[css.mainMiddleArea]: true,
[css.hidden]: !!error,
});
const errorOverlayClassNames = classNames({
[css.errorOverlay]: true,
[css.hidden]: !error,
});
if (siweAuthLoadingStatus === 'loading' || !siweNonce) {
return (
);
}
let errorText;
if (error === 'account_does_not_exist') {
errorText = (
<>
No Comm account found for that Ethereum wallet!
We require that users register on their mobile devices. Comm relies on
a primary device capable of scanning QR codes in order to authorize
secondary devices.
You can install our iOS app
here
, or our Android app
here
.
>
);
} else if (error === 'client_version_unsupported') {
errorText = {getVersionUnsupportedError()}
;
} else if (error === 'retry_from_native') {
errorText = (
<>
No primary device found for that Ethereum wallet!
Please try logging in from a mobile device to establish your primary
device. Comm relies on a primary device capable of scanning QR codes
in order to authorize secondary devices. Once you’ve logged in
from a mobile device, you will be able to log in from your browser.
You can install our iOS app
here
, or our Android app
here
.
>
);
}
return (
Sign in with Ethereum
Sign in using this wallet
{errorText}
Back to sign in with username
);
}
export default SIWELoginForm;
diff --git a/web/account/traditional-login-form.react.js b/web/account/traditional-login-form.react.js
index e3f862c4e..755f35dfa 100644
--- a/web/account/traditional-login-form.react.js
+++ b/web/account/traditional-login-form.react.js
@@ -1,272 +1,273 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import {
useLegacyLogIn,
legacyLogInActionTypes,
} from 'lib/actions/user-actions.js';
import { useModalContext } from 'lib/components/modal-provider.react.js';
import { usePasswordLogIn } from 'lib/hooks/login-hooks.js';
import { legacyLogInExtraInfoSelector } from 'lib/selectors/account-selectors.js';
import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
import {
oldValidUsernameRegex,
validEmailRegex,
} from 'lib/shared/account-utils.js';
import type {
LegacyLogInExtraInfo,
LegacyLogInStartingPayload,
} from 'lib/types/account-types.js';
import { logInActionSources } from 'lib/types/account-types.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 HeaderSeparator from './header-separator.react.js';
import css from './log-in-form.css';
import PasswordInput from './password-input.react.js';
import Button from '../components/button.react.js';
import { olmAPI } from '../crypto/olm-api.js';
import LoadingIndicator from '../loading-indicator.react.js';
import Input from '../modals/input.react.js';
import { useSelector } from '../redux/redux-utils.js';
import { getShortVersionUnsupportedError } from '../utils/version-utils.js';
const loadingStatusSelector = createLoadingStatusSelector(
legacyLogInActionTypes,
);
function TraditionalLoginForm(): React.Node {
const legacyAuthInProgress = useSelector(loadingStatusSelector) === 'loading';
const [identityAuthInProgress, setIdentityAuthInProgress] =
React.useState(false);
const inputDisabled = legacyAuthInProgress || identityAuthInProgress;
const legacyLoginExtraInfo = useSelector(legacyLogInExtraInfoSelector);
const callLegacyLogIn = useLegacyLogIn();
const dispatchActionPromise = useDispatchActionPromise();
const modalContext = useModalContext();
const usernameInputRef = React.useRef();
React.useEffect(() => {
usernameInputRef.current?.focus();
}, []);
const [username, setUsername] = React.useState('');
const onUsernameChange = React.useCallback(
(e: SyntheticEvent) => {
invariant(e.target instanceof HTMLInputElement, 'target not input');
setUsername(e.target.value);
},
[],
);
const onUsernameBlur = React.useCallback(() => {
setUsername(untrimmedUsername => untrimmedUsername.trim());
}, []);
const [password, setPassword] = React.useState('');
const onPasswordChange = React.useCallback(
(e: SyntheticEvent) => {
invariant(e.target instanceof HTMLInputElement, 'target not input');
setPassword(e.target.value);
},
[],
);
const [errorMessage, setErrorMessage] = React.useState('');
const legacyLogInAction = React.useCallback(
async (extraInfo: LegacyLogInExtraInfo) => {
await olmAPI.initializeCryptoAccount();
const userPublicKey = await olmAPI.getUserPublicKey();
try {
const result = await callLegacyLogIn({
...extraInfo,
username,
password,
authActionSource: logInActionSources.logInFromWebForm,
signedIdentityKeysBlob: {
payload: userPublicKey.blobPayload,
signature: userPublicKey.signature,
},
});
modalContext.popModal();
return result;
} catch (e) {
const messageForException = getMessageForException(e);
if (messageForException === 'invalid_credentials') {
setUsername('');
setPassword('');
setErrorMessage('incorrect username or password');
} else if (messageForException === 'client_version_unsupported') {
setErrorMessage(getShortVersionUnsupportedError());
} else {
setErrorMessage('unknown error');
}
usernameInputRef.current?.focus();
throw e;
}
},
[callLegacyLogIn, modalContext, password, username],
);
const callIdentityPasswordLogIn = usePasswordLogIn();
const identityPasswordLogInAction = React.useCallback(async () => {
if (identityAuthInProgress) {
return;
}
setIdentityAuthInProgress(true);
try {
await callIdentityPasswordLogIn(username, password);
modalContext.popModal();
} catch (e) {
const messageForException = getMessageForException(e);
if (
messageForException === 'user_not_found' ||
messageForException === 'login_failed'
) {
setUsername('');
setPassword('');
setErrorMessage('incorrect username or password');
} else if (
messageForException === 'client_version_unsupported' ||
- messageForException === 'unsupported_version'
+ messageForException === 'unsupported_version' ||
+ messageForException === 'use_new_flow'
) {
setErrorMessage(getShortVersionUnsupportedError());
} else if (
messageForException === 'need_keyserver_message_to_claim_username'
) {
// We don't want to let users claim their reserved usernames from web
// because we won't be able to establish a primary device for them.
setErrorMessage('please log in from a mobile device then retry');
} else {
setErrorMessage('unknown error');
}
usernameInputRef.current?.focus();
} finally {
setIdentityAuthInProgress(false);
}
}, [
identityAuthInProgress,
callIdentityPasswordLogIn,
modalContext,
password,
username,
]);
const onSubmit = React.useCallback(
(event: SyntheticEvent) => {
event.preventDefault();
if (username.search(validEmailRegex) > -1) {
setUsername('');
setErrorMessage('usernames only, not emails');
usernameInputRef.current?.focus();
return;
} else if (username.search(oldValidUsernameRegex) === -1) {
setUsername('');
setErrorMessage('alphanumeric usernames only');
usernameInputRef.current?.focus();
return;
} else if (password === '') {
setErrorMessage('password is empty');
usernameInputRef.current?.focus();
return;
}
if (usingCommServicesAccessToken) {
void identityPasswordLogInAction();
} else {
void dispatchActionPromise(
legacyLogInActionTypes,
legacyLogInAction(legacyLoginExtraInfo),
undefined,
({
calendarQuery: legacyLoginExtraInfo.calendarQuery,
}: LegacyLogInStartingPayload),
);
}
},
[
dispatchActionPromise,
identityPasswordLogInAction,
legacyLogInAction,
legacyLoginExtraInfo,
username,
password,
],
);
const loadingIndicatorClassName = inputDisabled
? css.loadingIndicator
: css.hiddenLoadingIndicator;
const buttonTextClassName = inputDisabled
? css.invisibleButtonText
: undefined;
const loginButtonContent = React.useMemo(
() => (
<>
Sign in
>
),
[loadingIndicatorClassName, buttonTextClassName],
);
const signInButtonColor = React.useMemo(
() => ({ backgroundColor: '#6A20E3' }),
[],
);
return (
);
}
export default TraditionalLoginForm;