[DEV-143] Handle expired sessions (#1192)
This commit is contained in:
parent
82fd54ffd9
commit
36c5f3264b
11
README.md
11
README.md
|
@ -13,18 +13,15 @@ Please, visit our website for more information: [http://www.opensupports.com/](h
|
|||
Here is a guide of how to set up the development environment in OpenSupports.
|
||||
|
||||
### Getting up and running FRONT-END (client folder)
|
||||
1. Update: `sudo apt-get update`
|
||||
1. Update: `sudo apt update`
|
||||
2. Clone this repo: `git clone https://github.com/opensupports/opensupports.git`
|
||||
3. Install node 4.x version:
|
||||
- `sudo apt-get install curl`
|
||||
- `curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -`
|
||||
- `sudo apt-get install -y nodejs`
|
||||
4. Install npm: `sudo apt-get install npm`
|
||||
3. Install `nvm`: https://github.com/nvm-sh/nvm
|
||||
4. Use node version 11.15.0: `nvm install 11` followed by `nvm use 11`
|
||||
5. Go to client: `cd opensupports/client`
|
||||
6. Install dependencies: `npm install`
|
||||
7. Rebuild node-sass: `npm rebuild node-sass`
|
||||
8. Run: `npm start` (PHP server api it must be running at :8080)
|
||||
10. Go to the main app: `http://localhost:3000/app` or to the component demo `http://localhost:3000/demo`
|
||||
10. Go to the main app: `http://localhost:3000/app`
|
||||
11. Your browser will automatically be opened and directed to the browser-sync proxy address.
|
||||
12. Use `npm start-fixtures` to enable fixtures and not require php server to be running.
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
},
|
||||
"private": false,
|
||||
"engines": {
|
||||
"node": "^0.12.x",
|
||||
"npm": "^2.1.x"
|
||||
"node": "^11.15.x",
|
||||
"npm": "^6.7.x"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --display-reasons --display-error-details --history-api-fallback --progress --colors",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export default {
|
||||
login: stub(),
|
||||
logout: stub(),
|
||||
initSession: stub()
|
||||
checkSession: stub()
|
||||
};
|
|
@ -88,7 +88,7 @@
|
|||
// });
|
||||
// });
|
||||
|
||||
// describe('initSession action', function () {
|
||||
// describe('checkSession action', function () {
|
||||
// beforeEach(function () {
|
||||
// APICallMock.call.returns({
|
||||
// then: function (resolve) {
|
||||
|
@ -125,7 +125,7 @@
|
|||
// }
|
||||
// });
|
||||
|
||||
// expect(SessionActions.initSession().type).to.equal('CHECK_SESSION');
|
||||
// expect(SessionActions.checkSession().type).to.equal('CHECK_SESSION');
|
||||
// expect(storeMock.dispatch).to.have.been.calledWith({type: 'SESSION_CHECKED'});
|
||||
// expect(APICallMock.call).to.have.been.calledWith({
|
||||
// path: '/user/check-session',
|
||||
|
@ -136,7 +136,7 @@
|
|||
// it('should return CHECK_SESSION and dispatch LOGOUT_FULFILLED if session is not active and no remember data', function () {
|
||||
// sessionStoreMock.isRememberDataExpired.returns(true);
|
||||
|
||||
// expect(SessionActions.initSession().type).to.equal('CHECK_SESSION');
|
||||
// expect(SessionActions.checkSession().type).to.equal('CHECK_SESSION');
|
||||
// expect(storeMock.dispatch).to.have.been.calledWith({type: 'LOGOUT_FULFILLED'});
|
||||
// expect(APICallMock.call).to.have.been.calledWith({
|
||||
// path: '/user/check-session',
|
||||
|
@ -147,7 +147,7 @@
|
|||
// it('should return CHECK_SESSION and dispatch LOGIN_AUTO if session is not active but remember data exists', function () {
|
||||
// sessionStoreMock.isRememberDataExpired.returns(false);
|
||||
|
||||
// expect(SessionActions.initSession().type).to.equal('CHECK_SESSION');
|
||||
// expect(SessionActions.checkSession().type).to.equal('CHECK_SESSION');
|
||||
// expect(storeMock.dispatch).to.not.have.been.calledWith({type: 'LOGOUT_FULFILLED'});
|
||||
// expect(APICallMock.call).to.have.been.calledWith({
|
||||
// path: '/user/check-session',
|
||||
|
|
|
@ -101,7 +101,7 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
initSession() {
|
||||
checkSession() {
|
||||
return {
|
||||
type: 'CHECK_SESSION',
|
||||
payload: new Promise((resolve, reject) => {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import React from "react";
|
||||
import _ from "lodash";
|
||||
|
||||
import i18n from "lib-app/i18n";
|
||||
|
||||
import Header from "core-components/header";
|
||||
|
||||
class SessionExpiredModal extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Header
|
||||
title={i18n("SESSION_EXPIRED")}
|
||||
description={i18n("SESSION_EXPIRED_DESCRIPTION")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SessionExpiredModal;
|
|
@ -250,6 +250,7 @@ export default {
|
|||
'USER_UNLOGGED_IN': 'This user has never logged in before',
|
||||
'RESEND_STAFF_INVITATION_SUCCESS': 'The invitation was sent successfully',
|
||||
'RESEND_STAFF_INVITATION_FAIL': 'The invitation could not be sent',
|
||||
'SESSION_EXPIRED': 'Session expired',
|
||||
|
||||
//ACTIVITIES
|
||||
'ACTIVITY_COMMENT': 'commented ticket',
|
||||
|
@ -372,6 +373,7 @@ export default {
|
|||
'INVITE_USER_VIEW_DESCRIPTION': 'Here you can invite an user to join our support system, he will just need to provide his password to create a new user.',
|
||||
'INVITE_STAFF_DESCRIPTION': 'Here you can invite staff members to your teams.',
|
||||
'TICKETS_INFORMATION' : 'Tickets from departments you don’t have assigned won’t be visible.',
|
||||
'SESSION_EXPIRED_DESCRIPTION': 'Your session has timed out. Please log in again.',
|
||||
|
||||
//ERRORS
|
||||
'EMAIL_OR_PASSWORD': 'Email or password invalid',
|
||||
|
|
|
@ -52,4 +52,4 @@ history.listen(() => {
|
|||
|
||||
store.dispatch(ConfigActions.checkInstallation());
|
||||
store.dispatch(ConfigActions.init());
|
||||
store.dispatch(SessionActions.initSession());
|
||||
store.dispatch(SessionActions.checkSession());
|
||||
|
|
|
@ -1,29 +1,34 @@
|
|||
const _ = require('lodash');
|
||||
const APIUtils = require('lib-core/APIUtils').default;
|
||||
const SessionStore = require('lib-app/session-store').default;
|
||||
const _ = require("lodash");
|
||||
const APIUtils = require("lib-core/APIUtils").default;
|
||||
const SessionStore = require("lib-app/session-store").default;
|
||||
import expiredSessionUtils from "./expired-session-utils";
|
||||
|
||||
function processData (data, dataAsForm = false) {
|
||||
function processData(data, dataAsForm = false) {
|
||||
let newData;
|
||||
|
||||
if(dataAsForm) {
|
||||
if (dataAsForm) {
|
||||
newData = new FormData();
|
||||
|
||||
_.each(data, (value, key) => {
|
||||
newData.append(key, value);
|
||||
});
|
||||
|
||||
newData.append('csrf_token', SessionStore.getSessionData().token);
|
||||
newData.append('csrf_userid', SessionStore.getSessionData().userId);
|
||||
newData.append("csrf_token", SessionStore.getSessionData().token);
|
||||
newData.append("csrf_userid", SessionStore.getSessionData().userId);
|
||||
} else {
|
||||
newData = _.extend({
|
||||
csrf_token: SessionStore.getSessionData().token,
|
||||
csrf_userid: SessionStore.getSessionData().userId
|
||||
}, data)
|
||||
newData = _.extend(
|
||||
{
|
||||
csrf_token: SessionStore.getSessionData().token,
|
||||
csrf_userid: SessionStore.getSessionData().userId,
|
||||
},
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
return newData;
|
||||
}
|
||||
|
||||
|
||||
const randomString = (length) => {
|
||||
var result = "";
|
||||
var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
@ -33,9 +38,8 @@ const randomString = (length) => {
|
|||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
call: function ({path, data, plain, dataAsForm}) {
|
||||
export default {
|
||||
call: function ({ path, data, plain, dataAsForm }) {
|
||||
const callId = randomString(3);
|
||||
const boldStyle = 'font-weight: bold;';
|
||||
const normalStyle = 'font-weight: normal;';
|
||||
|
@ -48,26 +52,29 @@ module.exports = {
|
|||
if (showLogs) {
|
||||
console.log(`▶ Result %c${path}%c [${callId}]: `, boldStyle, normalStyle, result);
|
||||
}
|
||||
|
||||
if (plain || result.status === 'success') {
|
||||
const { status, message } = result;
|
||||
if (plain || status === "success") {
|
||||
resolve(result);
|
||||
} else if (reject) {
|
||||
if (status === "fail" && message === "NO_PERMISSION") {
|
||||
expiredSessionUtils.checkSessionOrLogOut();
|
||||
}
|
||||
reject(result);
|
||||
}
|
||||
})
|
||||
.catch(function (result) {
|
||||
console.log('INVALID REQUEST to: ' + path);
|
||||
console.log("INVALID REQUEST to: " + path);
|
||||
console.log(result);
|
||||
reject({
|
||||
status: 'fail',
|
||||
message: 'Internal server error'
|
||||
status: "fail",
|
||||
message: "Internal server error",
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getFileLink(filePath) {
|
||||
return apiRoot + '/system/download?file=' + filePath;
|
||||
return apiRoot + "/system/download?file=" + filePath;
|
||||
},
|
||||
|
||||
getAPIUrl() {
|
||||
|
@ -76,5 +83,5 @@ module.exports = {
|
|||
|
||||
getURL() {
|
||||
return root;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
const _ = require("lodash");
|
||||
const APIUtils = require("lib-core/APIUtils").default;
|
||||
import SessionActions from "../actions/session-actions";
|
||||
import store from "app/store";
|
||||
import ModalContainer from "app-components/modal-container";
|
||||
import SessionExpiredModal from "../app-components/session-expired-modal";
|
||||
|
||||
const OPEN_MODAL_OPTIONS = {
|
||||
outsideClick: true,
|
||||
closeButton: {
|
||||
showCloseButton: false,
|
||||
},
|
||||
};
|
||||
|
||||
const LOG_OUT_DELAY = 750;
|
||||
const MODAL_DISAPPEAR_DELAY = 3000;
|
||||
|
||||
function onSessionExpired(result) {
|
||||
if (result.status === "success" && result.data.sessionActive === false) {
|
||||
ModalContainer.openModal(<SessionExpiredModal />, OPEN_MODAL_OPTIONS);
|
||||
setTimeout(() => {
|
||||
store.dispatch(SessionActions.checkSession());
|
||||
}, LOG_OUT_DELAY);
|
||||
setTimeout(() => {
|
||||
ModalContainer.closeModal();
|
||||
}, MODAL_DISAPPEAR_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
checkSessionOrLogOut() {
|
||||
APIUtils.post(apiRoot + "/user/check-session", {}, {})
|
||||
.then(onSessionExpired)
|
||||
.catch((err) =>
|
||||
console.error("An error ocurred when checking session: ", err)
|
||||
);
|
||||
},
|
||||
};
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
ini_set('session.cookie_secure', getenv('IS_DOCKER') ? 0 : 1);
|
||||
ini_set('session.gc_maxlifetime', 3600 * 24 * 30);
|
||||
|
||||
class Session {
|
||||
use SingletonTrait;
|
||||
|
@ -13,6 +14,7 @@ class Session {
|
|||
}
|
||||
|
||||
public function initSession() {
|
||||
session_set_cookie_params(3600 * 24 * 30);
|
||||
session_cache_limiter(false);
|
||||
session_start();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue