Remember me function for staffs (#866)

* fix warning in checbox in form field.

* Add remember me function for staffs.

* Add staff instance in session cookie.

* Add result data staff in get user data in auto login.

* Fix remember me function for user.

* Fix login test rb and add remember me function test in login rb.

* Resolve github maxi comments.
This commit is contained in:
LautaroCesso 2020-08-19 23:33:40 -03:00 committed by GitHub
parent 1c5d156723
commit 4077dac8c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 130 additions and 30 deletions

View File

@ -54,11 +54,11 @@ export default {
data: { data: {
userId: rememberData.userId, userId: rememberData.userId,
rememberToken: rememberData.token, rememberToken: rememberData.token,
staff: rememberData.isStaff,
remember: 1, remember: 1,
isAutomatic: 1
} }
}).then((result) => { }).then((result) => {
store.dispatch(this.getUserData(result.data.userId, result.data.token)); store.dispatch(this.getUserData(result.data.userId, result.data.token, result.data.staff));
return result; return result;
}) })

View File

@ -49,11 +49,32 @@ class AdminLoginPage extends React.Component {
<div> <div>
<Widget className="admin-login-page__content"> <Widget className="admin-login-page__content">
<div className="admin-login-page__image"><img width="100%" src={API.getURL() + '/images/logo.png'} alt="OpenSupports Admin Panel"/></div> <div className="admin-login-page__image"><img width="100%" src={API.getURL() + '/images/logo.png'} alt="OpenSupports Admin Panel"/></div>
<div className="admin-login-page__login-form"> <div className="admin-login-page__login-form-container">
<Form onSubmit={this.onLoginFormSubmit.bind(this)} loading={this.props.session.pending}> <Form {...this.getLoginFormProps()}>
<FormField name="email" label={i18n('EMAIL')} field="input" validation="EMAIL" fieldProps={{size:'large'}} required /> <div className="admin-login-page__login-form-container__login-form__fields">
<FormField name="password" label={i18n('PASSWORD')} field="input" fieldProps={{password:true, size:'large'}} /> <FormField
<SubmitButton>{i18n('LOG_IN')}</SubmitButton> name="email"
label={i18n('EMAIL')}
className="admin-login-page__login-form-container__login-form__fields__email"
field="input"
validation="EMAIL"
fieldProps={{size:'large'}}
required />
<FormField
name="password"
label={i18n('PASSWORD')}
className="admin-login-page__login-form-container__login-form__fields__password"
field="input"
fieldProps={{password:true, size:'large'}} />
<FormField
name="remember"
label={i18n('REMEMBER_ME')}
className="admin-login-page__login-form-container__login-form__fields__remember"
field="checkbox" />
</div>
<div className="admin-login-page__login-form-container__login-form__submit-button">
<SubmitButton>{i18n('LOG_IN')}</SubmitButton>
</div>
</Form> </Form>
</div> </div>
{this.renderRecoverStatus()} {this.renderRecoverStatus()}
@ -68,7 +89,7 @@ class AdminLoginPage extends React.Component {
renderPasswordRecovery() { renderPasswordRecovery() {
return ( return (
<div> <div className="admin-login-page__recovery-form-container">
<PasswordRecovery recoverSent={this.state.recoverSent} formProps={this.getRecoverFormProps()} onBackToLoginClick={this.onBackToLoginClick.bind(this)} renderLogo={true}/> <PasswordRecovery recoverSent={this.state.recoverSent} formProps={this.getRecoverFormProps()} onBackToLoginClick={this.onBackToLoginClick.bind(this)} renderLogo={true}/>
</div> </div>
); );
@ -105,7 +126,7 @@ class AdminLoginPage extends React.Component {
getLoginFormProps() { getLoginFormProps() {
return { return {
loading: this.props.session.pending, loading: this.props.session.pending,
className: 'admin-login-page__form', className: 'admin-login-page__login-form-container__login-form',
ref: 'loginForm', ref: 'loginForm',
onSubmit: this.onLoginFormSubmit.bind(this), onSubmit: this.onLoginFormSubmit.bind(this),
errors: this.getLoginFormErrors(), errors: this.getLoginFormErrors(),
@ -114,12 +135,17 @@ class AdminLoginPage extends React.Component {
} }
getRecoverFormProps() { getRecoverFormProps() {
const {
loadingRecover,
recoverFormErrors
} = this.state;
return { return {
loading: this.state.loadingRecover, loading: loadingRecover,
className: 'admin-login-page__form', className: 'admin-login-page__recovery-form-container__recovery-form',
ref: 'recoverForm', ref: 'recoverForm',
onSubmit: this.onForgotPasswordSubmit.bind(this), onSubmit: this.onForgotPasswordSubmit.bind(this),
errors: this.state.recoverFormErrors, errors: recoverFormErrors,
onValidateErrors: this.onRecoverFormErrorsValidation.bind(this) onValidateErrors: this.onRecoverFormErrorsValidation.bind(this)
}; };
} }

View File

@ -19,9 +19,13 @@
margin-bottom: 30px; margin-bottom: 30px;
} }
&__login-form { &__login-form-container {
margin: 0 auto; margin: 0 auto;
display: inline-block; display: inline-block;
&__login-form__fields {
padding: 10px 0;
}
} }
&__error { &__error {

View File

@ -194,8 +194,12 @@ class FormField extends React.Component {
if(field === 'autocomplete') { if(field === 'autocomplete') {
props.values = value; props.values = value;
} }
props.value = value; if(field === 'checkbox') {
props.value = !!value;
} else {
props.value = value;
}
return props; return props;
} }

View File

@ -48,9 +48,10 @@ class SessionStore {
return JSON.parse(this.getItem('departments')); return JSON.parse(this.getItem('departments'));
} }
storeRememberData({token, userId, expiration}) { storeRememberData({token, userId, expiration, isStaff}) {
this.setItem('rememberData-token', token); this.setItem('rememberData-token', token);
this.setItem('rememberData-userId', userId); this.setItem('rememberData-userId', userId);
this.setItem('rememberData-isStaff', isStaff);
this.setItem('rememberData-expiration', expiration); this.setItem('rememberData-expiration', expiration);
} }
@ -106,6 +107,7 @@ class SessionStore {
return { return {
token: this.getItem('rememberData-token'), token: this.getItem('rememberData-token'),
userId: this.getItem('rememberData-userId'), userId: this.getItem('rememberData-userId'),
isStaff: this.getItem('rememberData-isStaff'),
expiration: this.getItem('rememberData-expiration') expiration: this.getItem('rememberData-expiration')
}; };
} }
@ -113,6 +115,7 @@ class SessionStore {
clearRememberData() { clearRememberData() {
this.removeItem('rememberData-token'); this.removeItem('rememberData-token');
this.removeItem('rememberData-userId'); this.removeItem('rememberData-userId');
this.removeItem('rememberData-isStaff');
this.removeItem('rememberData-expiration'); this.removeItem('rememberData-expiration');
} }

View File

@ -95,7 +95,7 @@ class SessionReducer extends Reducer {
sessionStore.storeRememberData({ sessionStore.storeRememberData({
token: resultData.rememberToken, token: resultData.rememberToken,
userId: resultData.userId, userId: resultData.userId,
staff: resultData.staff, isStaff: resultData.staff ? 1 : 0,
expiration: resultData.rememberExpiration expiration: resultData.rememberExpiration
}); });
} }

View File

@ -61,6 +61,7 @@ class LoginController extends Controller {
$this->createUserSession(); $this->createUserSession();
$this->createRememberToken(); $this->createRememberToken();
if(Controller::request('staff')) { if(Controller::request('staff')) {
$this->userInstance->lastLogin = Date::getCurrentDate(); $this->userInstance->lastLogin = Date::getCurrentDate();
$this->userInstance->store(); $this->userInstance->store();
@ -116,13 +117,18 @@ class LoginController extends Controller {
$rememberToken = Controller::request('rememberToken'); $rememberToken = Controller::request('rememberToken');
$userInstance = new NullDataStore(); $userInstance = new NullDataStore();
if ($rememberToken) { if($rememberToken) {
$sessionCookie = SessionCookie::getDataStore($rememberToken, 'token'); $sessionCookie = SessionCookie::getDataStore($rememberToken, 'token');
$userId = Controller::request('userId'); $userId = Controller::request('userId');
$isStaff = !!Controller::request('staff');
if (!$sessionCookie->isNull() && $userId === $sessionCookie->user->id) { if(!$sessionCookie->isNull()) {
$userInstance = $sessionCookie->user; $loggedInstance = $isStaff ? $sessionCookie->staff : $sessionCookie->user;
$sessionCookie->delete();
if(($userId == $loggedInstance->id) && ($isStaff == $sessionCookie->isStaff)) {
$userInstance = $loggedInstance;
$sessionCookie->delete();
}
} }
} }
@ -140,13 +146,15 @@ class LoginController extends Controller {
private function createRememberToken() { private function createRememberToken() {
$remember = Controller::request('remember'); $remember = Controller::request('remember');
if (!Controller::request('staff') && $remember) { if($remember) {
$this->rememberToken = Hashing::generateRandomToken(); $this->rememberToken = Hashing::generateRandomToken();
$this->rememberExpiration = Date::getNextDate(30); $this->rememberExpiration = Date::getNextDate(30);
$sessionCookie = new SessionCookie(); $sessionCookie = new SessionCookie();
$sessionCookie->setProperties(array( $sessionCookie->setProperties(array(
'user' => $this->userInstance, 'isStaff' => !!Controller::request('staff'),
'user' => $this->userInstance instanceof User ? $this->userInstance : null,
'staff' => $this->userInstance instanceof Staff ? $this->userInstance : null,
'token' => $this->rememberToken, 'token' => $this->rememberToken,
'ip' => $_SERVER['REMOTE_ADDR'], 'ip' => $_SERVER['REMOTE_ADDR'],
'creationDate' => Date::getCurrentDate(), 'creationDate' => Date::getCurrentDate(),

View File

@ -1,10 +1,24 @@
<?php <?php
/**
* @api {OBJECT} SessionCookie SessionCookie
* @apiVersion 4.8.0
* @apiGroup Data Structures
* @apiParam {Boolean} isStaff Indicates if it wants to login a staff or a regular user.
* @apiParam {Object} user The user.
* @apiParam {Object} staff The staff.
* @apiParam {String} token Token of the session, used to verify the session when making other requests.
* @apiParam {String} ip The ip.
* @apiParam {String} creationDate The creationDate.
* @apiParam {String} expirationDate The expirationDate.
*/
class SessionCookie extends DataStore { class SessionCookie extends DataStore {
const TABLE = 'sessioncookie'; const TABLE = 'sessioncookie';
public static function getProps() { public static function getProps() {
return array ( return array (
'isStaff',
'staff',
'user', 'user',
'token', 'token',
'ip', 'ip',

View File

@ -6,6 +6,7 @@ describe '/user/ban' do
password: 'staff', password: 'staff',
staff: true staff: true
}) })
(result['status']).should.equal('success')
$csrf_userid = result['data']['userId'] $csrf_userid = result['data']['userId']
$csrf_token = result['data']['token'] $csrf_token = result['data']['token']

View File

@ -9,7 +9,6 @@ describe '/user/login' do
email: @loginEmail, email: @loginEmail,
password: 'some_incorrect_password' password: 'some_incorrect_password'
}) })
(result['status']).should.equal('fail') (result['status']).should.equal('fail')
end end
@ -18,7 +17,6 @@ describe '/user/login' do
email: @loginEmail, email: @loginEmail,
password: @loginPass password: @loginPass
}) })
(result['status']).should.equal('success') (result['status']).should.equal('success')
end end
@ -27,7 +25,6 @@ describe '/user/login' do
email: @loginEmail, email: @loginEmail,
password: @loginPass password: @loginPass
}) })
(result['status']).should.equal('success') (result['status']).should.equal('success')
end end
@ -36,21 +33,20 @@ describe '/user/login' do
result = request('/user/login', { result = request('/user/login', {
email: $staff[:email], email: $staff[:email],
password: $staff[:password], password: $staff[:password],
staff: true staff: 1
}) })
(result['status']).should.equal('success') (result['status']).should.equal('success')
(result['data']['staff']).should.equal(true) (result['data']['staff']).should.equal(true)
end end
it 'should work with remember token' do it 'should work autologin user with remember token' do
request('/user/logout', {}) request('/user/logout', {})
result = request('/user/login', { result = request('/user/login', {
email: @loginEmail, email: @loginEmail,
password: @loginPass, password: @loginPass,
staff: 0,
remember: 1 remember: 1
}) })
(result['status']).should.equal('success') (result['status']).should.equal('success')
@rememberToken = result['data']['rememberToken'] @rememberToken = result['data']['rememberToken']
@ -60,12 +56,15 @@ describe '/user/login' do
result = request('/user/login', { result = request('/user/login', {
userId: @userId, userId: @userId,
rememberToken: '12abc', rememberToken: '12abc',
staff: 0,
remember: 1 remember: 1
}) })
(result['status']).should.equal('fail') (result['status']).should.equal('fail')
result = request('/user/login', { result = request('/user/login', {
userId: 1, userId: 1,
rememberToken: @rememberToken, rememberToken: @rememberToken,
staff: 0,
remember: 1 remember: 1
}) })
(result['status']).should.equal('fail') (result['status']).should.equal('fail')
@ -73,8 +72,49 @@ describe '/user/login' do
result = request('/user/login', { result = request('/user/login', {
userId: @userId, userId: @userId,
rememberToken: @rememberToken, rememberToken: @rememberToken,
staff: 0,
remember: 1 remember: 1
}) })
(result['status']).should.equal('success') (result['status']).should.equal('success')
end end
it 'should work autologin staff with remember token' do
request('/user/logout', {})
result = request('/user/login', {
email: $staff[:email],
password: $staff[:password],
staff: 1,
remember: 1
})
(result['status']).should.equal('success')
@rememberToken = result['data']['rememberToken']
@staffId = result['data']['userId']
request('/user/logout', {})
result = request('/user/login', {
userId: @staffId,
rememberToken: '12abc',
staff: 1,
remember: 1
})
(result['status']).should.equal('fail')
result = request('/user/login', {
userId: 3,
rememberToken: @rememberToken,
staff: 1,
remember: 1
})
(result['status']).should.equal('fail')
result = request('/user/login', {
userId: @staffId,
rememberToken: @rememberToken,
staff: 1,
remember: 1
})
(result['status']).should.equal('success')
end
end end