Implement create account and login; fix validation issues

This commit is contained in:
Robbie Antenesse 2018-01-21 14:22:31 -07:00
parent bc7dc27443
commit a25e4726e5
6 changed files with 174 additions and 38 deletions

View File

@ -33,11 +33,11 @@ class Dictionary {
if ($insert_dictionary === true) { if ($insert_dictionary === true) {
$new_dictionary_id = $this->db->lastInsertId(); $new_dictionary_id = $this->db->lastInsertId();
$insert_linguistics_query = "INSERT INTO dictionary_linguistics (dictionary, partsOfSpeech, phonology) $insert_linguistics_query = "INSERT INTO dictionary_linguistics (dictionary, parts_of_speech, phonology)
VALUES ($new_dictionary_id, ?, ?)"; VALUES ($new_dictionary_id, ?, ?)";
$insert_linguistics = $this->db->execute($insert_linguistics_query, array( $insert_linguistics = $this->db->execute($insert_linguistics_query, array(
json_encode($this->defaults['partsOfSpeech']), json_encode($this->defaults['partsOfSpeech']),
json_encode($this->defaults['phonotactics']), json_encode($this->defaults['phonology']),
)); ));
if ($insert_linguistics === true) { if ($insert_linguistics === true) {

View File

@ -13,8 +13,8 @@ class User {
} }
public function logIn ($email, $password) { public function logIn ($email, $password) {
$query = 'SELECT * FROM users WHERE email=?'; $query = 'SELECT * FROM users WHERE email=:email OR username=:email';
$user = $this->db->query($query, array($email))->fetch(); $user = $this->db->query($query, array(':email' => $email))->fetch();
if ($user) { if ($user) {
if ($user['old_password'] !== null) { if ($user['old_password'] !== null) {
if ($user['old_password'] === crypt($password, $email)) { if ($user['old_password'] === crypt($password, $email)) {
@ -23,6 +23,7 @@ class User {
} }
} }
} else if (password_verify($password, $user['password'])) { } else if (password_verify($password, $user['password'])) {
$this->db->execute('UPDATE users SET last_login=' . time() . ' WHERE id=' . $user['id']);
return $this->generateUserToken($user['id'], $user['current_dictionary']); return $this->generateUserToken($user['id'], $user['current_dictionary']);
} }
} }
@ -41,11 +42,18 @@ class User {
return $user->rowCount() > 0; return $user->rowCount() > 0;
} }
public function create ($email, $password) { public function create ($email, $password, $user_data) {
$insert_user_query = 'INSERT INTO users (email, password) VALUES (?, ?)'; $insert_user_query = 'INSERT INTO users (email, password, public_name, username, allow_email, created_on)
VALUES (?, ?, ?, ?, ?, '. time() .')';
$password_hash = password_hash($password, PASSWORD_DEFAULT); $password_hash = password_hash($password, PASSWORD_DEFAULT);
$insert_user = $this->db->execute($insert_user_query, array($email, $password_hash)); $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,
));
if ($insert_user === true) { if ($insert_user === true) {
$new_user_id = $this->db->lastInsertId(); $new_user_id = $this->db->lastInsertId();

View File

@ -33,7 +33,7 @@ switch ($action) {
if (isset($request['email']) && isset($request['password'])) { if (isset($request['email']) && isset($request['password'])) {
$user = new User(); $user = new User();
if (!$user->emailExists($request['email'])) { if (!$user->emailExists($request['email'])) {
$token = $user->create($request['email'], $request['password']); $token = $user->create($request['email'], $request['password'], $request['userData']);
if ($token !== false) { if ($token !== false) {
return Response::json(array( return Response::json(array(
'data' => $token, 'data' => $token,

View File

@ -24,6 +24,7 @@ export class LoginForm extends Component {
loginPassword: '', loginPassword: '',
loginEmailError: '', loginEmailError: '',
loginPasswordError: '', loginPasswordError: '',
loginFormIsValid: true,
signupEmail: '', signupEmail: '',
signupUsername: '', signupUsername: '',
signupPublicName: '', signupPublicName: '',
@ -38,48 +39,104 @@ export class LoginForm extends Component {
signupUsernameChecking: false, signupUsernameChecking: false,
signupEmailIsUnique: true, signupEmailIsUnique: true,
signupUsernameIsUnique: true, signupUsernameIsUnique: true,
signupFormIsValid: true,
}; };
} }
get loginFormIsValid () {
const {
loginEmailError,
loginPasswordError,
} = this.state;
return loginEmailError === '' && loginPasswordError === '';
}
get signupFormIsValid () {
const {
signupEmailError,
signupEmailChecking,
signupEmailIsUnique,
signupUsernameError,
signupUsernameChecking,
signupUsernameIsUnique,
signupPasswordError,
signupConfirmError,
} = this.state;
return !signupEmailChecking && !signupUsernameChecking
&& signupEmailIsUnique && signupUsernameIsUnique
&& signupEmailError === '' && signupUsernameError === ''
&& signupPasswordError === '' && signupConfirmError === '';
}
changeTab (tab) { changeTab (tab) {
this.setState({ visibleTab: tab }); this.setState({ visibleTab: tab });
} }
updateField (field, event) { updateField (field, event) {
const requiredFields = ['loginEmail', 'loginPassword', 'signupEmail', 'signupPassword', 'signupConfirm'];
const {value, checked} = event.target; const {value, checked} = event.target;
const fieldUpdate = {}; const fieldUpdate = {};
const fieldErrors = this.validateField(field, value);
fieldUpdate[field] = (field === 'signupAllowEmail') ? checked : value;
this.setState(Object.assign(fieldUpdate, fieldErrors));
}
validateField (field, value) {
const fieldErrors = {};
const errorFieldName = `${field}Error`; const errorFieldName = `${field}Error`;
let isValid = true; let isValid = true;
const requiredFields = ['loginEmail', 'loginPassword', 'signupEmail', 'signupPassword', 'signupConfirm'];
if (requiredFields.includes(field)) { if (requiredFields.includes(field)) {
if (value === '') { if (value === '') {
isValid = false; isValid = false;
fieldUpdate[errorFieldName] = 'This field must not be blank'; fieldErrors[errorFieldName] = 'This field must not be blank';
} else if (field.includes('Email') && !/.+@.+/g.test(value)) { } else if (field === 'signupEmail' && !/.+@.+/g.test(value)) {
isValid = false; isValid = false;
fieldUpdate[errorFieldName] = 'The email address you entered looks wrong'; fieldErrors[errorFieldName] = 'The email address you entered looks wrong';
} else if (field === 'signupPassword' && value.length < 6) { } else if (field === 'signupPassword' && value.length < 6) {
isValid = false; isValid = false;
fieldUpdate[errorFieldName] = 'Please make your password at least 6 characters long'; fieldErrors[errorFieldName] = 'Please make your password at least 6 characters long';
} else if ((field === 'signupPassword' && value !== this.state.signupConfirm) } else if (field === 'signupConfirm' && value !== this.state.signupPassword) {
|| (field === 'signupConfirm' && value !== this.state.signupPassword)) {
isValid = false; isValid = false;
fieldUpdate[errorFieldName] = 'Your passwords must match'; fieldErrors[errorFieldName] = 'Your passwords must match';
} }
} }
if (field === 'signupUsername') { if (field === 'signupUsername') {
if (value !== '' && /[^a-zA-Z0-9]+/g.test(value)) { if (value !== '' && /[^a-zA-Z0-9]+/g.test(value)) {
isValid = false; isValid = false;
fieldUpdate[errorFieldName] = 'Please use only letters and numbers'; fieldErrors[errorFieldName] = 'Please use only letters and numbers';
} }
} }
if (isValid) { if (isValid) {
fieldUpdate[errorFieldName] = ''; fieldErrors[errorFieldName] = '';
} }
fieldUpdate[field] = (field === 'signupAllowEmail') ? checked : value; return fieldErrors;
this.setState(fieldUpdate); }
validateSignupForm (callback) {
const fields = ['signupEmail', 'signupUsername', 'signupPassword', 'signupConfirm'];
let errors = {};
fields.forEach(field => {
const fieldErrors = this.validateField(field, this.state[field]);
errors = Object.assign(errors, fieldErrors);
});
errors.signupFormIsValid = !signupEmailChecking && !signupUsernameChecking
&& signupEmailIsUnique && signupUsernameIsUnique
&& Object.keys(errors).every(field => errors[field] === '');
this.setState(errors, callback);
}
validateLoginForm (callback) {
const fields = ['loginEmail','loginPassword'];
let errors = {};
fields.forEach(field => {
errors = Object.assign(errors, this.validateField(field, this.state[field]));
});
errors.loginFormIsValid = Object.keys(errors).every(field => {
return errors[field] === '';
});
this.setState(errors, callback);
} }
checkFieldUnique (field, event) { checkFieldUnique (field, event) {
@ -117,6 +174,34 @@ export class LoginForm extends Component {
} }
} }
logIn () {
this.validateLoginForm(() => {
if (this.loginFormIsValid) {
const { loginEmail, loginPassword } = this.state;
this.props.logIn(loginEmail, loginPassword);
}
});
}
createAccount () {
this.validateSignupForm(() => {
if (this.signupFormIsValid) {
const {
signupEmail,
signupUsername,
signupPublicName,
signupPassword,
signupAllowEmail
} = this.state;
this.props.signUp(signupEmail, signupPassword, {
username: signupUsername,
publicName: signupPublicName,
allowEmail: signupAllowEmail,
});
}
});
}
render () { render () {
return ( return (
<div className='columns'> <div className='columns'>
@ -137,7 +222,7 @@ export class LoginForm extends Component {
</div> </div>
{this.state.visibleTab === 'login' {this.state.visibleTab === 'login'
? ( ? (
<div> <div className="has-text-left">
<h3 className='title is-3'> <h3 className='title is-3'>
Log In Log In
</h3> </h3>
@ -146,7 +231,16 @@ export class LoginForm extends Component {
Email/Username Email/Username
</label> </label>
<div className='control'> <div className='control'>
<input className='input' type='email' /> <input className={`input ${this.state.loginEmailError !== '' && 'is-danger'}`}
type='email' onInput={(event) => this.updateField('loginEmail', event)} />
{
this.state.loginEmailError !== ''
? (
<div className='help is-danger'>
{this.state.loginEmailError}
</div>
) : null
}
</div> </div>
</div> </div>
<div className='field'> <div className='field'>
@ -154,12 +248,28 @@ export class LoginForm extends Component {
Password Password
</label> </label>
<div className='control'> <div className='control'>
<input className='input' type='password' /> <input className={`input ${this.state.loginPasswordError !== '' && 'is-danger'}`}
type='password' onInput={(event) => this.updateField('loginPassword', event)}
onChange={this.validateLoginForm.bind(this)} />
{
this.state.loginPasswordError !== ''
? (
<div className='help is-danger'>
{this.state.loginPasswordError}
</div>
) : null
}
</div> </div>
</div> </div>
<div className='field'>
<a className='button is-success'
onClick={this.logIn.bind(this)}>
Log In
</a>
</div>
</div> </div>
) : ( ) : (
<div> <div className="has-text-left">
<h3 className='title is-3'> <h3 className='title is-3'>
Create a New Account Create a New Account
</h3> </h3>
@ -285,6 +395,14 @@ export class LoginForm extends Component {
</label> </label>
</div> </div>
</div> </div>
<div className='field'>
<div className='control'>
<a className='button is-success'
onClick={this.createAccount.bind(this)}>
Create Account
</a>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -23,17 +23,7 @@ export class AccountManager extends Component {
} }
logIn (email, password) { logIn (email, password) {
return request('login', { email, password }, response => { return request('login', { email, password }, this.handleResponse.bind(this));
const { data, error } = response;
if (error) {
console.error(data);
} else {
store.set('LexicongaToken', data);
this.setState({ isLoggedIn: true }, () => {
this.props.updater.sync();
});
}
});
} }
logOut () { logOut () {
@ -41,6 +31,26 @@ export class AccountManager extends Component {
this.setState({ isLoggedIn: false }); this.setState({ isLoggedIn: false });
} }
signUp (email, password, userData) {
request('create-account', {
email,
password,
userData,
}, this.handleResponse.bind(this));
}
handleResponse (response) {
const { data, error } = response;
if (error) {
console.error(data);
} else {
store.set('LexicongaToken', data);
this.setState({ isLoggedIn: true }, () => {
this.props.updater.sync();
});
}
}
render () { render () {
const token = store.get('LexicongaToken'); const token = store.get('LexicongaToken');
@ -60,7 +70,7 @@ export class AccountManager extends Component {
} }
return ( return (
<Modal buttonText='Log In/Sign Up' title='Log In/Sign Up'> <Modal buttonText='Log In/Sign Up' title='Log In/Sign Up'>
<LoginForm logIn={this.logIn.bind(this)} signUp={() => {}} /> <LoginForm logIn={this.logIn.bind(this)} signUp={this.signUp.bind(this)} />
</Modal> </Modal>
); );
} }

View File

@ -48,10 +48,10 @@ export class Header extends Component {
<div className={`navbar-menu${ this.state.displayNavMenu ? ' is-active' : '' }`}> <div className={`navbar-menu${ this.state.displayNavMenu ? ' is-active' : '' }`}>
<div className='navbar-end'> <div className='navbar-end'>
<span className='navbar-item'> <span className='navbar-item has-text-right-touch'>
<AccountManager updater={ this.props.updater } /> <AccountManager updater={ this.props.updater } />
</span> </span>
<span className='navbar-item'> <span className='navbar-item has-text-right-touch'>
<Modal buttonText='Help' title='Lexiconga Help'> <Modal buttonText='Help' title='Lexiconga Help'>
<div className='content has-text-left'> <div className='content has-text-left'>
<div dangerouslySetInnerHTML={{ <div dangerouslySetInnerHTML={{