feat: auto email confirmation #2169

Merged
neb-b merged 3 commits from email into master 2019-01-09 23:04:40 +01:00
12 changed files with 84 additions and 114 deletions

View file

@ -52,7 +52,7 @@
"hast-util-sanitize": "^1.1.2",
"keytar": "^4.2.1",
"lbry-redux": "lbryio/lbry-redux#820b6eeadf9e58c1e5027fd4d92808ba817b410e",
"lbryinc": "lbryio/lbryinc#68c8ce6b7608dabc9180fff9b4841d10e1c62284",
"lbryinc": "lbryio/lbryinc#b3bb8c67745235ef54baea95accaedaa4bb86d4d",
"localforage": "^1.7.1",
"mammoth": "^1.4.6",
"mime": "^2.3.1",

View file

@ -69,12 +69,7 @@ class UserEmailNew extends React.PureComponent<Props, State> {
<div className="card__actions">
<Submit label="Submit" disabled={isPending} />
{cancelButton}
<br /><br />
</div>
<p className="card__content help">
{`${__('Your email may be used to sync usage data across devices.')} `}
</p>
</Form>
</span>
);

View file

@ -1,24 +1,22 @@
import { connect } from 'react-redux';
import {
selectEmailVerifyIsPending,
selectEmailToVerify,
selectEmailVerifyErrorMessage,
doUserEmailVerify,
doUserEmailVerifyFailure,
doUserResendVerificationEmail,
doUserCheckEmailVerified,
selectUser,
} from 'lbryinc';
import { doNavigate } from 'redux/actions/navigation';
import UserEmailVerify from './view';
const select = state => ({
isPending: selectEmailVerifyIsPending(state),
email: selectEmailToVerify(state),
errorMessage: selectEmailVerifyErrorMessage(state),
user: selectUser(state),
});
const perform = dispatch => ({
verifyUserEmail: (code, recaptcha) => dispatch(doUserEmailVerify(code, recaptcha)),
verifyUserEmailFailure: error => dispatch(doUserEmailVerifyFailure(error)),
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)),
checkEmailVerified: () => dispatch(doUserCheckEmailVerified()),
navigate: path => dispatch(doNavigate(path)),
});
export default connect(

View file

@ -1,48 +1,37 @@
// @flow
import * as React from 'react';
import Button from 'component/button';
import { Form, FormField, FormRow, Submit } from 'component/common/form';
type Props = {
cancelButton: React.Node,
errorMessage: ?string,
email: string,
isPending: boolean,
onModal?: boolean,
verifyUserEmail: (string, string) => void,
verifyUserEmailFailure: string => void,
resendVerificationEmail: string => void,
checkEmailVerified: () => void,
user: {
has_verified_email: boolean,
},
};
type State = {
code: string,
};
class UserEmailVerify extends React.PureComponent<Props, State> {
class UserEmailVerify extends React.PureComponent<Props> {
constructor(props: Props) {
super(props);
this.state = {
code: '',
};
(this: any).handleSubmit = this.handleSubmit.bind(this);
this.emailVerifyCheckInterval = null;
(this: any).handleResendVerificationEmail = this.handleResendVerificationEmail.bind(this);
}
handleCodeChanged(event: SyntheticInputEvent<*>) {
this.setState({
code: String(event.target.value).trim(),
});
componentDidMount() {
this.emailVerifyCheckInterval = setInterval(() => this.checkIfVerified(), 5000);
}
handleSubmit() {
const { code } = this.state;
try {
const verification = JSON.parse(atob(code));
this.props.verifyUserEmail(verification.token, verification.recaptcha);
} catch (error) {
this.props.verifyUserEmailFailure('Invalid Verification Token');
componentDidUpdate() {
if (this.emailVerifyCheckInterval && this.props.user.has_verified_email) {
clearInterval(this.emailVerifyCheckInterval);
}
}
componentWillUnmount() {
if (this.emailVerifyCheckInterval) {
clearInterval(this.emailVerifyCheckInterval);
}
}
@ -50,55 +39,38 @@ class UserEmailVerify extends React.PureComponent<Props, State> {
this.props.resendVerificationEmail(this.props.email);
}
checkIfVerified() {
const { checkEmailVerified } = this.props;
checkEmailVerified();
skhameneh commented 2019-01-09 19:53:19 +01:00 (Migrated from github.com)
Review

Five seconds? Seems a bit long, we should look into doing this with websockets instead of polling.

Five seconds? Seems a bit long, we should look into doing this with websockets instead of polling.
neb-b commented 2019-01-09 23:04:34 +01:00 (Migrated from github.com)
Review

Yeah I figured that would be less hammering on the server. I agree.

Yeah I figured that would be less hammering on the server. I agree.
}
emailVerifyCheckInterval: ?IntervalID;
render() {
const { cancelButton, errorMessage, email, isPending, onModal } = this.props;
const { cancelButton, email } = this.props;
return (
<span>
<p>Please enter the verification code emailed to {email}.</p>
<div>
<p>
{__('An email was sent to')} {email}.{' '}
{__('Follow the link and you will be good to go. This will update automatically.')}
</p>
<Form onSubmit={this.handleSubmit}>
<FormRow padded>
<FormField
stretch
name="code"
type="text"
placeholder="eyJyZWNhcHRjaGEiOiIw..."
label={__('Verification Code')}
error={errorMessage}
value={this.state.code}
onChange={event => this.handleCodeChanged(event)}
/>
</FormRow>
<div className="card__actions">
<Button
button="primary"
label={__('Resend verification email')}
onClick={this.handleResendVerificationEmail}
/>
{cancelButton}
</div>
<div className="card__actions">
<Submit label={__('Verify')} disabled={isPending} />
{cancelButton}
{!onModal && (
<Button
button="link"
label={__('Resend verification email')}
onClick={this.handleResendVerificationEmail}
/>
)}
</div>
<p className="help">
{__('Email')} <Button button="link" href="mailto:help@lbry.io" label="help@lbry.io" />{' '}
or join our <Button button="link" href="https://chat.lbry.io" label="chat" />{' '}
{__('if you encounter any trouble with your code.')}
</p>
{onModal && (
<div className="card__actions help">
<Button
button="link"
label={__('Resend verification email')}
onClick={this.handleResendVerificationEmail}
/>
</div>
)}
</Form>
</span>
<p className="help">
{__('Email')} <Button button="link" href="mailto:help@lbry.io" label="help@lbry.io" /> or
join our <Button button="link" href="https://chat.lbry.io" label="chat" />{' '}
{__('if you encounter any trouble verifying.')}
</p>
</div>
);
}
}

View file

@ -20,7 +20,7 @@ import {
import { doToast, doBlackListedOutpointsSubscribe, isURIValid } from 'lbry-redux';
import { doNavigate } from 'redux/actions/navigation';
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
import { doUserEmailVerify, doAuthenticate, Lbryio, rewards } from 'lbryinc';
import { doAuthenticate, Lbryio, rewards } from 'lbryinc';
import 'scss/all.scss';
import store from 'store';
import pjson from 'package.json';
@ -33,6 +33,10 @@ const APPPAGEURL = 'lbry://?';
autoUpdater.logger = remote.require('electron-log');
if (process.env.LBRY_API_URL) {
Lbryio.setLocalApi(process.env.LBRY_API_URL);
}
// We need to override Lbryio for getting/setting the authToken
// We interect with ipcRenderer to get the auth key from a users keyring
// We keep a local variable for authToken beacuse `ipcRenderer.send` does not
@ -95,23 +99,8 @@ rewards.setCallback('claimRewardSuccess', () => {
ipcRenderer.on('open-uri-requested', (event, uri, newSession) => {
if (uri && uri.startsWith('lbry://')) {
if (uri.startsWith('lbry://?verify=')) {
let verification = {};
try {
verification = JSON.parse(atob(uri.substring(15)));
} catch (error) {
console.log(error);
}
if (verification.token && verification.recaptcha) {
app.store.dispatch(doConditionalAuthNavigate(newSession));
app.store.dispatch(doUserEmailVerify(verification.token, verification.recaptcha));
} else {
app.store.dispatch(
doToast({
message: 'Invalid Verification URI',
})
);
}
if (uri.startsWith('lbry://?verify')) {
app.store.dispatch(doConditionalAuthNavigate(newSession));
} else if (uri.startsWith(APPPAGEURL)) {
const navpage = uri.replace(APPPAGEURL, '').toLowerCase();
app.store.dispatch(doNavigate(`/${navpage}`));

View file

@ -67,7 +67,9 @@ export class Modal extends React.PureComponent<ModalProps> {
![null, undefined, ''].includes(overlayClassName) ? overlayClassName : 'modal-overlay'
}
>
<h1 className="card__title">{title}</h1>
<header className="card__header">
<h2 className="card__title">{title}</h2>
</header>
<div className="card__content">{children}</div>
{type === 'custom' ? null : ( // custom modals define their own buttons
<div className="card__actions">

View file

@ -16,7 +16,7 @@ class ModalAffirmPurchase extends React.PureComponent<Props> {
constructor() {
super();
this.onAffirmPurchase = this.onAffirmPurchase.bind(this);
(this: any).onAffirmPurchase = this.onAffirmPurchase.bind(this);
}
onAffirmPurchase() {

View file

@ -9,8 +9,12 @@ type Props = {
declineAutoUpdate: () => any,
};
class ModalAutoUpdateDownloaded extends React.PureComponent<Props> {
constructor(props: ModalProps) {
type State = {
disabled: boolean,
};
class ModalAutoUpdateDownloaded extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {

View file

@ -12,6 +12,15 @@ type Props = {
};
class ModalEmailCollection extends React.PureComponent<Props> {
getTitle() {
const { user } = this.props;
if (user && !user.has_verified_email) {
return __('Awaiting Confirmation');
}
return __('Can We Stay In Touch?');
}
renderInner() {
const { closeModal, email, user } = this.props;
@ -34,7 +43,7 @@ class ModalEmailCollection extends React.PureComponent<Props> {
}
return (
<Modal type="custom" isOpen contentLabel="Email" title={__('Can We Stay In Touch?')}>
<Modal type="custom" isOpen contentLabel="Email" title={this.getTitle()}>
<section className="card__content">{this.renderInner()}</section>
</Modal>
);

View file

@ -31,8 +31,8 @@ import ModalWalletUnlock from 'modal/modalWalletUnlock';
import ModalRewardCode from 'modal/modalRewardCode';
type Props = {
notification: { id: string },
notificationProps: {},
modal: { id: string, modalProps: {} },
error: string | { message: string },
};
class ModalRouter extends React.PureComponent<Props> {
@ -80,6 +80,7 @@ class ModalRouter extends React.PureComponent<Props> {
checkShowWelcome(props) {
const { isWelcomeAcknowledged, user } = props;
if (!isWelcomeAcknowledged && user && !user.is_reward_approved && !user.is_identity_verified) {
return MODALS.WELCOME;
}

View file

@ -36,7 +36,7 @@ class AuthPage extends React.PureComponent<Props> {
if (isPending || (user && !user.has_verified_email && !email)) {
return __('Human Proofing');
} else if (user && !user.has_verified_email) {
return __('Confirm Email');
return __('Awaiting Confirmation');
} else if (user && !user.is_identity_verified && !user.is_reward_approved) {
return __('Final Verification');
}

View file

@ -5683,9 +5683,9 @@ lbry-redux@lbryio/lbry-redux#84b7d396934d57a37802aadbef71db91230a9404:
reselect "^3.0.0"
uuid "^3.3.2"
lbryinc@lbryio/lbryinc#68c8ce6b7608dabc9180fff9b4841d10e1c62284:
lbryinc@lbryio/lbryinc#cee70d482cf352f2e66215fa109f4178406228ca:
version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/68c8ce6b7608dabc9180fff9b4841d10e1c62284"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/cee70d482cf352f2e66215fa109f4178406228ca"
dependencies:
lbry-redux lbryio/lbry-redux#84b7d396934d57a37802aadbef71db91230a9404
reselect "^3.0.0"