Changeset View
Changeset View
Standalone View
Standalone View
web/account/traditional-login-form.react.js
// @flow | // @flow | ||||
import olm from '@matrix-org/olm'; | |||||
import invariant from 'invariant'; | import invariant from 'invariant'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { logIn, logInActionTypes } from 'lib/actions/user-actions.js'; | import { logIn, logInActionTypes } from 'lib/actions/user-actions.js'; | ||||
import { useModalContext } from 'lib/components/modal-provider.react.js'; | import { useModalContext } from 'lib/components/modal-provider.react.js'; | ||||
import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; | import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; | ||||
import { | import { | ||||
oldValidUsernameRegex, | oldValidUsernameRegex, | ||||
validEmailRegex, | validEmailRegex, | ||||
} from 'lib/shared/account-utils.js'; | } from 'lib/shared/account-utils.js'; | ||||
import type { | import type { | ||||
LogInExtraInfo, | LogInExtraInfo, | ||||
LogInStartingPayload, | LogInStartingPayload, | ||||
} from 'lib/types/account-types.js'; | } from 'lib/types/account-types.js'; | ||||
import { logInActionSources } from 'lib/types/account-types.js'; | import { logInActionSources } from 'lib/types/account-types.js'; | ||||
import type { | import type { SignedIdentityKeysBlob } from 'lib/types/crypto-types.js'; | ||||
OLMIdentityKeys, | |||||
PickledOLMAccount, | |||||
SignedIdentityKeysBlob, | |||||
} from 'lib/types/crypto-types.js'; | |||||
import { | import { | ||||
useDispatchActionPromise, | useDispatchActionPromise, | ||||
useServerCall, | useServerCall, | ||||
} from 'lib/utils/action-utils.js'; | } from 'lib/utils/action-utils.js'; | ||||
import HeaderSeparator from './header-separator.react.js'; | import HeaderSeparator from './header-separator.react.js'; | ||||
import css from './log-in-form.css'; | import css from './log-in-form.css'; | ||||
import PasswordInput from './password-input.react.js'; | import PasswordInput from './password-input.react.js'; | ||||
import Button from '../components/button.react.js'; | import Button from '../components/button.react.js'; | ||||
import LoadingIndicator from '../loading-indicator.react.js'; | import LoadingIndicator from '../loading-indicator.react.js'; | ||||
import Input from '../modals/input.react.js'; | import Input from '../modals/input.react.js'; | ||||
import { useSelector } from '../redux/redux-utils.js'; | import { useSelector } from '../redux/redux-utils.js'; | ||||
import { webLogInExtraInfoSelector } from '../selectors/account-selectors.js'; | import { webLogInExtraInfoSelector } from '../selectors/account-selectors.js'; | ||||
import { signedIdentityKeysBlobSelector } from '../selectors/socket-selectors.js'; | |||||
const loadingStatusSelector = createLoadingStatusSelector(logInActionTypes); | const loadingStatusSelector = createLoadingStatusSelector(logInActionTypes); | ||||
function TraditionalLoginForm(): React.Node { | function TraditionalLoginForm(): React.Node { | ||||
const inputDisabled = useSelector(loadingStatusSelector) === 'loading'; | const inputDisabled = useSelector(loadingStatusSelector) === 'loading'; | ||||
const loginExtraInfo = useSelector(webLogInExtraInfoSelector); | const loginExtraInfo = useSelector(webLogInExtraInfoSelector); | ||||
const callLogIn = useServerCall(logIn); | const callLogIn = useServerCall(logIn); | ||||
const dispatchActionPromise = useDispatchActionPromise(); | const dispatchActionPromise = useDispatchActionPromise(); | ||||
const modalContext = useModalContext(); | const modalContext = useModalContext(); | ||||
const primaryIdentityPublicKeys: ?OLMIdentityKeys = useSelector( | const signedIdentityKeysBlob: ?SignedIdentityKeysBlob = useSelector( | ||||
state => state.cryptoStore.primaryIdentityKeys, | signedIdentityKeysBlobSelector, | ||||
); | |||||
const notificationIdentityPublicKeys: ?OLMIdentityKeys = useSelector( | |||||
state => state.cryptoStore.notificationIdentityKeys, | |||||
); | |||||
const primaryAccount: ?PickledOLMAccount = useSelector( | |||||
state => state.cryptoStore.primaryAccount, | |||||
); | ); | ||||
const usernameInputRef = React.useRef(); | const usernameInputRef = React.useRef(); | ||||
React.useEffect(() => { | React.useEffect(() => { | ||||
usernameInputRef.current?.focus(); | usernameInputRef.current?.focus(); | ||||
}, []); | }, []); | ||||
const [username, setUsername] = React.useState<string>(''); | const [username, setUsername] = React.useState<string>(''); | ||||
Show All 13 Lines | function TraditionalLoginForm(): React.Node { | ||||
}, []); | }, []); | ||||
const [errorMessage, setErrorMessage] = React.useState<string>(''); | const [errorMessage, setErrorMessage] = React.useState<string>(''); | ||||
const logInAction = React.useCallback( | const logInAction = React.useCallback( | ||||
async (extraInfo: LogInExtraInfo) => { | async (extraInfo: LogInExtraInfo) => { | ||||
try { | try { | ||||
invariant( | invariant( | ||||
primaryIdentityPublicKeys, | signedIdentityKeysBlob, | ||||
'primaryIdentityPublicKeys must be set in logInAction', | 'signedIdentityKeysBlob must be set in logInAction', | ||||
); | |||||
invariant( | |||||
notificationIdentityPublicKeys, | |||||
'notificationIdentityPublicKeys must be set in logInAction', | |||||
); | |||||
invariant(primaryAccount, 'primaryAccount must be set in logInAction'); | |||||
const primaryOLMAccount = new olm.Account(); | |||||
primaryOLMAccount.unpickle( | |||||
primaryAccount.picklingKey, | |||||
primaryAccount.pickledAccount, | |||||
); | ); | ||||
const payloadToBeSigned = JSON.stringify({ | |||||
primaryIdentityPublicKeys, | |||||
notificationIdentityPublicKeys, | |||||
}); | |||||
const signedIdentityKeysBlob: SignedIdentityKeysBlob = { | |||||
payload: payloadToBeSigned, | |||||
signature: primaryOLMAccount.sign(payloadToBeSigned), | |||||
}; | |||||
const result = await callLogIn({ | const result = await callLogIn({ | ||||
...extraInfo, | ...extraInfo, | ||||
username, | username, | ||||
password, | password, | ||||
logInActionSource: logInActionSources.logInFromWebForm, | logInActionSource: logInActionSources.logInFromWebForm, | ||||
signedIdentityKeysBlob, | signedIdentityKeysBlob, | ||||
}); | }); | ||||
modalContext.popModal(); | modalContext.popModal(); | ||||
return result; | return result; | ||||
} catch (e) { | } catch (e) { | ||||
setUsername(''); | setUsername(''); | ||||
setPassword(''); | setPassword(''); | ||||
if (e.message === 'invalid_credentials') { | if (e.message === 'invalid_credentials') { | ||||
setErrorMessage('incorrect username or password'); | setErrorMessage('incorrect username or password'); | ||||
} else { | } else { | ||||
setErrorMessage('unknown error'); | setErrorMessage('unknown error'); | ||||
} | } | ||||
usernameInputRef.current?.focus(); | usernameInputRef.current?.focus(); | ||||
throw e; | throw e; | ||||
} | } | ||||
}, | }, | ||||
[ | [callLogIn, modalContext, password, signedIdentityKeysBlob, username], | ||||
callLogIn, | |||||
modalContext, | |||||
notificationIdentityPublicKeys, | |||||
password, | |||||
primaryAccount, | |||||
primaryIdentityPublicKeys, | |||||
username, | |||||
], | |||||
); | ); | ||||
const onSubmit = React.useCallback( | const onSubmit = React.useCallback( | ||||
(event: SyntheticEvent<HTMLElement>) => { | (event: SyntheticEvent<HTMLElement>) => { | ||||
event.preventDefault(); | event.preventDefault(); | ||||
if (username.search(validEmailRegex) > -1) { | if (username.search(validEmailRegex) > -1) { | ||||
setUsername(''); | setUsername(''); | ||||
▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | <form method="POST"> | ||||
/> | /> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div className={css.form_footer}> | <div className={css.form_footer}> | ||||
<Button | <Button | ||||
variant="filled" | variant="filled" | ||||
type="submit" | type="submit" | ||||
disabled={ | disabled={ | ||||
primaryIdentityPublicKeys === null || | signedIdentityKeysBlob === null || | ||||
primaryIdentityPublicKeys === undefined || | signedIdentityKeysBlob === undefined || | ||||
notificationIdentityPublicKeys === null || | |||||
notificationIdentityPublicKeys === undefined || | |||||
primaryAccount === null || | |||||
primaryAccount === undefined || | |||||
inputDisabled | inputDisabled | ||||
} | } | ||||
onClick={onSubmit} | onClick={onSubmit} | ||||
buttonColor={signInButtonColor} | buttonColor={signInButtonColor} | ||||
> | > | ||||
{loginButtonContent} | {loginButtonContent} | ||||
</Button> | </Button> | ||||
<div className={css.modal_form_error}>{errorMessage}</div> | <div className={css.modal_form_error}>{errorMessage}</div> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
); | ); | ||||
} | } | ||||
export default TraditionalLoginForm; | export default TraditionalLoginForm; |