Fix sign up; Add confirmations to sign up and log in

This commit is contained in:
Robbie Antenesse 2018-06-24 23:03:37 -06:00
parent 0f6b5deb1a
commit 095b8051be
4 changed files with 150 additions and 203 deletions

View File

@ -13,7 +13,7 @@ class User {
}
public function logIn ($email, $password) {
$query = 'SELECT * FROM users WHERE email=:email OR username=:email';
$query = 'SELECT * FROM users WHERE email=:email';
$user = $this->db->query($query, array(':email' => $email))->fetch();
if ($user) {
if ($user['old_password'] !== null) {
@ -39,22 +39,15 @@ class User {
return $user->rowCount() > 0;
}
public function usernameExists ($username) {
$query = 'SELECT * FROM users WHERE username=?';
$user = $this->db->query($query, array($username));
return $user->rowCount() > 0;
}
public function create ($email, $password, $user_data) {
$insert_user_query = 'INSERT INTO users (email, password, public_name, username, allow_email, created_on)
VALUES (?, ?, ?, ?, ?, ?)';
$insert_user_query = 'INSERT INTO users (email, password, public_name, allow_email, created_on)
VALUES (?, ?, ?, ?, ?)';
$password_hash = password_hash($password, PASSWORD_DEFAULT);
$insert_user = $this->db->execute($insert_user_query, array(
$email,
$password_hash,
$user_data['publicName'] !== '' ? $user_data['publicName'] : null,
$user_data['username'] !== '' ? $user_data['username'] : null,
$user_data['allowEmail'] ? 1 : 0,
time(),
));
@ -108,7 +101,6 @@ VALUES (?, ?, ?, ?, ?, ?)';
if ($stmt && $user) {
return array(
'email' => $user['email'],
'username' => $user['username'],
'publicName' => $user['public_name'],
'allowEmails' => $user['allow_email'] == 1 ? true : false,
);

View File

@ -1,11 +1,6 @@
import Inferno from 'inferno';
import { Component } from 'inferno';
import PropTypes from 'prop-types';
import marked from 'marked';
import store from 'store';
import { Modal } from '../../structure/Modal';
import { SearchBox } from '../../management/SearchBox';
import { request } from '../../../Helpers';
@ -26,19 +21,15 @@ export class LoginForm extends Component {
loginPasswordError: '',
loginFormIsValid: true,
signupEmail: '',
// signupUsername: '',
signupPublicName: '',
signupPassword: '',
signupConfirm: '',
signupAllowEmail: true,
signupEmailError: '',
// signupUsernameError: '',
signupPasswordError: '',
signupConfirmError: '',
signupEmailChecking: false,
// signupUsernameChecking: false,
signupEmailIsUnique: true,
// signupUsernameIsUnique: true,
signupFormIsValid: true,
};
}
@ -56,15 +47,11 @@ export class LoginForm extends Component {
signupEmailError,
signupEmailChecking,
signupEmailIsUnique,
// signupUsernameError,
// signupUsernameChecking,
// signupUsernameIsUnique,
signupPasswordError,
signupConfirmError,
} = this.state;
return !signupEmailChecking && !signupUsernameChecking
return !signupEmailChecking
&& signupEmailIsUnique && signupEmailError === ''
// && signupUsernameIsUnique && signupUsernameError === ''
&& signupPasswordError === '' && signupConfirmError === '';
}
@ -101,13 +88,6 @@ export class LoginForm extends Component {
}
}
// if (field === 'signupUsername') {
// if (value !== '' && /[^a-zA-Z0-9]+/g.test(value)) {
// isValid = false;
// fieldErrors[errorFieldName] = 'Please use only letters and numbers';
// }
// }
if (isValid) {
fieldErrors[errorFieldName] = '';
}
@ -118,12 +98,9 @@ export class LoginForm extends Component {
const {
signupEmailChecking,
signupEmailIsUnique,
// signupUsernameChecking,
// signupUsernameIsUnique,
} = this.state;
const fields = [
'signupEmail',
// 'signupUsername',
'signupPassword',
'signupConfirm'
];
@ -133,7 +110,6 @@ export class LoginForm extends Component {
errors = Object.assign(errors, fieldErrors);
});
errors.signupFormIsValid = !signupEmailChecking && signupEmailIsUnique
// && !signupUsernameChecking && signupUsernameIsUnique
&& Object.keys(errors).every(field => errors[field] === '');
this.setState(errors, callback);
}
@ -169,21 +145,6 @@ export class LoginForm extends Component {
});
});
}
// else if (field === 'signupUsername') {
// this.setState({ signupUsernameChecking: true }, () => {
// request('check-username', { username: value }, (response) => {
// const { data, error } = response;
// fieldUpdate['signupUsernameChecking'] = false;
// if (error) {
// console.error(data);
// } else {
// fieldUpdate['signupUsernameIsUnique'] = !data;
// }
// }).then(() => {
// this.setState(fieldUpdate);
// });
// });
// }
}
logIn () {
@ -200,13 +161,11 @@ export class LoginForm extends Component {
if (this.signupFormIsValid) {
const {
signupEmail,
// signupUsername,
signupPublicName,
signupPassword,
signupAllowEmail
} = this.state;
this.props.signUp(signupEmail, signupPassword, {
// username: signupUsername,
publicName: signupPublicName,
allowEmail: signupAllowEmail,
});
@ -216,7 +175,7 @@ export class LoginForm extends Component {
render () {
return (
<div className='columns'>
<div className='columns has-text-left'>
<div className='column'>
<div className='tabs is-boxed'>
<ul>
@ -274,10 +233,10 @@ export class LoginForm extends Component {
</div>
</div>
<div className='field'>
<a className='button is-success'
<button className='button is-success'
onClick={this.logIn.bind(this)}>
Log In
</a>
</button>
</div>
</div>
) : (
@ -326,29 +285,7 @@ export class LoginForm extends Component {
}
</div>
</div>
{/* <div className='field'>
<label className='label'>
Username
</label>
<div className='help'>
This is your unique identifier that appears in the URL if you ever decide to share your dictionaries publicly.
</div>
<div className={`control ${this.state.signupUsernameChecking && 'is-loading'}`}>
<input className={`input ${!this.state.signupUsernameIsUnique && 'is-danger'}`}
type='text' value={this.state.signupUsername}
onInput={(event) => this.updateField('signupUsername', event)}
onBlur={(event) => this.checkFieldUnique('signupUsername', event)} />
{
(this.state.signupUsernameError !== '' || !this.state.signupUsernameIsUnique)
? (
<div className='help is-danger'>
{!this.state.signupUsernameIsUnique && <p>This username address is already in use</p>}
{this.state.signupUsernameError}
</div>
) : null
}
</div>
</div> */}
<div className='field'>
<label className='label'>
Public Name
@ -362,6 +299,7 @@ export class LoginForm extends Component {
onInput={(event) => this.updateField('signupPublicName', event)} />
</div>
</div>
<div className='field'>
<label className='label'>
Password<sup>*</sup>
@ -380,6 +318,7 @@ export class LoginForm extends Component {
}
</div>
</div>
<div className='field'>
<label className='label'>
Confirm Password<sup>*</sup>
@ -398,6 +337,7 @@ export class LoginForm extends Component {
}
</div>
</div>
<div className='field'>
<div className='control'>
<input className='is-checkradio' id='signupAllowEmail' type='checkbox'
@ -411,12 +351,13 @@ export class LoginForm extends Component {
</div>
</div>
</div>
<div className='field'>
<div className='control'>
<a className='button is-success'
<button className='button is-success'
onClick={this.createAccount.bind(this)}>
Create Account
</a>
</button>
</div>
</div>
</div>

View File

@ -2,8 +2,6 @@ import Inferno from 'inferno';
import { Component } from 'inferno';
import PropTypes from 'prop-types';
import { request } from '../../../Helpers';
export class MyAccount extends Component {
constructor(props) {
super(props);
@ -69,134 +67,132 @@ export class MyAccount extends Component {
render() {
return (
<div>
<div className='columns'>
<div className='column'>
<h2 className='title'>Account Details</h2>
<div className='field'>
<label className='label'>
<span>Email:</span>
</label>
<div className='control'>
<input className='input' type='text' value={this.state.email}
onInput={ event => this.setState({ email: event.target.value }) }
onChange={ () => this.checkFields() } />
<div className='help'>
<strong>Note:</strong> If you change your email address, you will need to use your new email address to log in.
</div>
</div>
</div>
<div className='field'>
<label className='label'>
<span>Public Name:</span>
</label>
<div className='control'>
<input className='input' type='text' value={this.state.publicName}
onInput={ event => this.setState({ publicName: event.target.value }) }
onChange={ () => this.checkFields() } />
<div className='help'>
This is the name we greet you with. It's also the name displayed if you ever decide to share
any of your dictionaries.
</div>
<div className='help'>
<strong>Note:</strong> This is <em>not a username</em>, and is therefore not guaranteed to be unique.
Use something people will recognize you as to differentiate from other people who might use the same name!
</div>
</div>
</div>
<div className='field'>
<div className='control'>
<input className='is-checkradio is-rtl' type='checkbox' id='allowEmails'
checked={this.state.allowEmails ? 'checked' : false}
onChange={(event) => {
this.setState({ allowEmails: !this.state.allowEmails }, () => this.checkFields());
}} />
<label className='label is-unselectable' htmlFor='allowEmails'>
Allow Emails
</label>
<div className='help'>
We'll make sure that you're the first to hear about any new features that get added or if any of our policies
change for any reason. We'll never spam you or sell your information, but you may need to mark emails from
lexicon.ga as not spam to receive them.
</div>
<div className='help'>
<strong>Note:</strong> Password reset emails will be sent regardless of your choice here.
</div>
</div>
</div>
<div className='field'>
<div className='control'>
<button className='button' Disabled={ !this.state.canSend ? 'disabled' : null }
onClick={ () => this.saveChanges() }>
Save Changes
</button>
<div className='columns has-text-left'>
<div className='column'>
<h2 className='title'>Account Details</h2>
<div className='field'>
<label className='label'>
<span>Email:</span>
</label>
<div className='control'>
<input className='input' type='text' value={this.state.email}
onInput={ event => this.setState({ email: event.target.value }) }
onChange={ () => this.checkFields() } />
<div className='help'>
<strong>Note:</strong> If you change your email address, you will need to use your new email address to log in.
</div>
</div>
</div>
<div className='column'>
<h2 className='title'>Account Actions</h2>
<div className='field'>
<label className='label is-unselectable'>
<span>Change Dictionary</span>
</label>
<div className='control'>
<div className='select'>
<select>
{this.props.userDictionaries.map(item => {
return <option value={item.id}>{item.name}</option>;
})}
</select>
</div>
<div className='field'>
<label className='label'>
<span>Public Name:</span>
</label>
<div className='control'>
<input className='input' type='text' value={this.state.publicName}
onInput={ event => this.setState({ publicName: event.target.value }) }
onChange={ () => this.checkFields() } />
<div className='help'>
This is the name we greet you with. It's also the name displayed if you ever decide to share
any of your dictionaries.
</div>
<div className='help'>
<strong>Note:</strong> This is <em>not a username</em>, and is therefore not guaranteed to be unique.
Use something people will recognize you as to differentiate from other people who might use the same name!
</div>
</div>
<div className='field'>
<label className='label is-unselectable'>
<span>Reset Your Password</span>
</div>
<div className='field'>
<div className='control'>
<input className='is-checkradio is-rtl' type='checkbox' id='allowEmails'
checked={this.state.allowEmails ? 'checked' : false}
onChange={(event) => {
this.setState({ allowEmails: !this.state.allowEmails }, () => this.checkFields());
}} />
<label className='label is-unselectable' htmlFor='allowEmails'>
Allow Emails
</label>
<div className='help'>
Click the button below to reload the page and show the Reset Password form. Filling out this
form will instantly change your password, and you will need to log in using the new password
from that point forward.
We'll make sure that you're the first to hear about any new features that get added or if any of our policies
change for any reason. We'll never spam you or sell your information, but you may need to mark emails from
lexicon.ga as not spam to receive them.
</div>
<div className='control'>
<a className='button'>Reset Password</a>
<div className='help'>
<strong>Note:</strong> Password reset emails will be sent regardless of your choice here.
</div>
</div>
<div className='content is-small'>
<h4><strong>Request Your Data</strong></h4>
<p>
Per your <a href='https://www.eugdpr.org/' target='_blank'>GDPR</a> rights in Articles 1315 and 20,
we allow you to request any and all data we have stored about you. The only data we have about
you personally is your email address and your Public Name, if you decided to set one. All other
data (your Dictionary data) is visible and accessible via the Export button under your Dictionary's
Settings. Send an email to help@lexicon.ga to request your information.
</p>
</div>
<div className='content is-small'>
<h4><strong>Delete Your Account</strong></h4>
<p>
Per your <a href='https://www.eugdpr.org/' target='_blank'>GDPR</a> rights in Article 17, if you wish
for your account to be deleted, please contact us at help@lexicon.ga, and we will delete your account
and all associated dictionaries and words as quickly as possible. Note that you can delete dictionaries
yourself via your Dictionary's Settings.
</p>
<p>
Anything that is deleted from our system is permanently and irretrievably removed from our system and
cannot be restored, though search engines or internet archives may retain a cached version of your content
(there is nothing we can do about this, and you will need to seek out removal of that information by directly
contacting the services that are caching your data).
</p>
</div>
</div>
<div className='field'>
<div className='control'>
<button className='button' Disabled={ !this.state.canSend ? 'disabled' : null }
onClick={ () => this.saveChanges() }>
Save Changes
</button>
</div>
</div>
</div>
<div className='column'>
<h2 className='title'>Account Actions</h2>
<div className='field'>
<label className='label is-unselectable'>
<span>Change Dictionary</span>
</label>
<div className='control'>
<div className='select'>
<select>
{this.props.userDictionaries.map(item => {
return <option value={item.id}>{item.name}</option>;
})}
</select>
</div>
</div>
</div>
<div className='field'>
<label className='label is-unselectable'>
<span>Reset Your Password</span>
</label>
<div className='help'>
Click the button below to reload the page and show the Reset Password form. Filling out this
form will instantly change your password, and you will need to log in using the new password
from that point forward.
</div>
<div className='control'>
<a className='button'>Reset Password</a>
</div>
</div>
<div className='content is-small'>
<h4><strong>Request Your Data</strong></h4>
<p>
Per your <a href='https://www.eugdpr.org/' target='_blank'>GDPR</a> rights in Articles 1315 and 20,
we allow you to request any and all data we have stored about you. The only data we have about
you personally is your email address and your Public Name, if you decided to set one. All other
data (your Dictionary data) is visible and accessible via the Export button under your Dictionary's
Settings. Send an email to help@lexicon.ga to request your information.
</p>
</div>
<div className='content is-small'>
<h4><strong>Delete Your Account</strong></h4>
<p>
Per your <a href='https://www.eugdpr.org/' target='_blank'>GDPR</a> rights in Article 17, if you wish
for your account to be deleted, please contact us at help@lexicon.ga, and we will delete your account
and all associated dictionaries and words as quickly as possible. Note that you can delete dictionaries
yourself via your Dictionary's Settings.
</p>
<p>
Anything that is deleted from our system is permanently and irretrievably removed from our system and
cannot be restored, though search engines or internet archives may retain a cached version of your content
(there is nothing we can do about this, and you will need to seek out removal of that information by directly
contacting the services that are caching your data).
</p>
</div>
</div>
</div>
);
}

View File

@ -1,7 +1,6 @@
import Inferno from 'inferno';
import { Component } from 'inferno';
import PropTypes from 'prop-types';
import marked from 'marked';
import store from 'store';
import swal from 'sweetalert2';
@ -36,7 +35,16 @@ export class AccountManager extends Component {
}
logIn (email, password) {
return request('login', { email, password }, this.handleResponse.bind(this));
return request('login', { email, password }, response => this.handleResponse(response, userData => {
const nameGreeting = userData.hasOwnProperty('publicName') && userData.publicName !== '' ? ', ' + userData.publicName : '';
swal({
title: `Welcome back${nameGreeting}!`,
text: 'You have been logged in successfully.',
type: 'success',
confirmButtonClass: 'button',
buttonsStyling: false,
});
}));
}
logOut () {
@ -62,7 +70,16 @@ export class AccountManager extends Component {
email,
password,
userData,
}, this.handleResponse.bind(this));
}, response => this.handleResponse(response, () => {
const nameGreeting = userData.hasOwnProperty('publicName') && userData.publicName !== '' ? ', ' + userData.publicName : '';
swal({
title: `Welcome${nameGreeting}!`,
text: 'Your account was created successfully! We hope you enjoy what a Lexiconga account can provide for you!',
type: 'success',
confirmButtonClass: 'button',
buttonsStyling: false,
});
}));
}
updateUserData (token, userData, callback = () => {}) {
@ -76,12 +93,13 @@ export class AccountManager extends Component {
});
}
handleResponse (response) {
handleResponse (response, successCallback = () => {}) {
const { data, error } = response;
if (error) {
console.error(data);
} else {
this.updateUserData(data.token, data.user, () => {
successCallback(data.user);
this.getDictionaryNames();
this.props.updater.sync();
});