feat: auto email verification

This commit is contained in:
Sean Yesmunt 2019-01-07 21:46:33 -05:00
parent ad90c1f96e
commit eb7de08ed1
11 changed files with 81 additions and 91 deletions

View file

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

View file

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

View file

@ -1,48 +1,37 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import { Form, FormField, FormRow, Submit } from 'component/common/form';
type Props = { type Props = {
cancelButton: React.Node, cancelButton: React.Node,
errorMessage: ?string,
email: string, email: string,
isPending: boolean,
onModal?: boolean,
verifyUserEmail: (string, string) => void,
verifyUserEmailFailure: string => void,
resendVerificationEmail: string => void, resendVerificationEmail: string => void,
doCheckEmailVerified: () => void,
user: {
has_verified_email: boolean,
},
}; };
type State = { class UserEmailVerify extends React.PureComponent<Props> {
code: string,
};
class UserEmailVerify extends React.PureComponent<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.emailVerifyCheckInterval = null;
this.state = {
code: '',
};
(this: any).handleSubmit = this.handleSubmit.bind(this);
(this: any).handleResendVerificationEmail = this.handleResendVerificationEmail.bind(this); (this: any).handleResendVerificationEmail = this.handleResendVerificationEmail.bind(this);
} }
handleCodeChanged(event: SyntheticInputEvent<*>) { componentDidMount() {
this.setState({ this.emailVerifyCheckInterval = setInterval(() => this.checkIfVerified(), 5000);
code: String(event.target.value).trim(),
});
} }
handleSubmit() { componentDidUpdate() {
const { code } = this.state; if (this.emailVerifyCheckInterval && this.props.user.has_verified_email) {
try { clearInterval(this.emailVerifyCheckInterval);
const verification = JSON.parse(atob(code)); }
this.props.verifyUserEmail(verification.token, verification.recaptcha); }
} catch (error) {
this.props.verifyUserEmailFailure('Invalid Verification Token'); componentWillUnmount() {
if (this.emailVerifyCheckInterval) {
clearInterval(this.emailVerifyCheckInterval);
} }
} }
@ -50,55 +39,38 @@ class UserEmailVerify extends React.PureComponent<Props, State> {
this.props.resendVerificationEmail(this.props.email); this.props.resendVerificationEmail(this.props.email);
} }
checkIfVerified() {
const { doCheckEmailVerified } = this.props;
doCheckEmailVerified();
}
emailVerifyCheckInterval: ?IntervalID;
render() { render() {
const { cancelButton, errorMessage, email, isPending, onModal } = this.props; const { cancelButton, email } = this.props;
return ( return (
<span> <div>
<p>Please enter the verification code emailed to {email}.</p> <p>
{__('An email was sent to')} {email}.{' '}
<Form onSubmit={this.handleSubmit}> {__('Follow the link and you will be good to go. This will update automatically.')}
<FormRow padded> </p>
<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"> <div className="card__actions">
<Submit label={__('Verify')} disabled={isPending} />
{cancelButton}
{!onModal && (
<Button <Button
button="link" button="primary"
label={__('Resend verification email')} label={__('Resend verification email')}
onClick={this.handleResendVerificationEmail} onClick={this.handleResendVerificationEmail}
/> />
)} {cancelButton}
</div> </div>
<p className="help"> <p className="help">
{__('Email')} <Button button="link" href="mailto:help@lbry.io" label="help@lbry.io" />{' '} {__('Email')} <Button button="link" href="mailto:help@lbry.io" label="help@lbry.io" /> or
or join our <Button button="link" href="https://chat.lbry.io" label="chat" />{' '} join our <Button button="link" href="https://chat.lbry.io" label="chat" />{' '}
{__('if you encounter any trouble with your code.')} {__('if you encounter any trouble verifying.')}
</p> </p>
{onModal && (
<div className="card__actions help">
<Button
button="link"
label={__('Resend verification email')}
onClick={this.handleResendVerificationEmail}
/>
</div> </div>
)}
</Form>
</span>
); );
} }
} }

View file

@ -33,6 +33,10 @@ const APPPAGEURL = 'lbry://?';
autoUpdater.logger = remote.require('electron-log'); 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 need to override Lbryio for getting/setting the authToken
// We interect with ipcRenderer to get the auth key from a users keyring // 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 // We keep a local variable for authToken beacuse `ipcRenderer.send` does not

View file

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

View file

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

View file

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

View file

@ -12,6 +12,15 @@ type Props = {
}; };
class ModalEmailCollection extends React.PureComponent<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() { renderInner() {
const { closeModal, email, user } = this.props; const { closeModal, email, user } = this.props;
@ -34,7 +43,7 @@ class ModalEmailCollection extends React.PureComponent<Props> {
} }
return ( 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> <section className="card__content">{this.renderInner()}</section>
</Modal> </Modal>
); );

View file

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

View file

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

View file

@ -5683,9 +5683,9 @@ lbry-redux@lbryio/lbry-redux#84b7d396934d57a37802aadbef71db91230a9404:
reselect "^3.0.0" reselect "^3.0.0"
uuid "^3.3.2" uuid "^3.3.2"
lbryinc@lbryio/lbryinc#68c8ce6b7608dabc9180fff9b4841d10e1c62284: lbryinc@lbryio/lbryinc#cee70d482cf352f2e66215fa109f4178406228ca:
version "0.0.1" 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: dependencies:
lbry-redux lbryio/lbry-redux#84b7d396934d57a37802aadbef71db91230a9404 lbry-redux lbryio/lbry-redux#84b7d396934d57a37802aadbef71db91230a9404
reselect "^3.0.0" reselect "^3.0.0"