diff --git a/README.md b/README.md
index a7b9f67e..8121aaff 100644
--- a/README.md
+++ b/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.
diff --git a/client/package.json b/client/package.json
index 2a7f0b91..20467599 100644
--- a/client/package.json
+++ b/client/package.json
@@ -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",
diff --git a/client/src/actions/__mocks__/session-actions-mock.js b/client/src/actions/__mocks__/session-actions-mock.js
index a34e9c24..6022cbbd 100644
--- a/client/src/actions/__mocks__/session-actions-mock.js
+++ b/client/src/actions/__mocks__/session-actions-mock.js
@@ -1,5 +1,5 @@
export default {
login: stub(),
logout: stub(),
- initSession: stub()
+ checkSession: stub()
};
\ No newline at end of file
diff --git a/client/src/actions/__tests__/session-actions-test.js b/client/src/actions/__tests__/session-actions-test.js
index 5b42fabb..92a1f82c 100644
--- a/client/src/actions/__tests__/session-actions-test.js
+++ b/client/src/actions/__tests__/session-actions-test.js
@@ -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',
diff --git a/client/src/actions/session-actions.js b/client/src/actions/session-actions.js
index 9bec3100..3e862cf3 100644
--- a/client/src/actions/session-actions.js
+++ b/client/src/actions/session-actions.js
@@ -101,7 +101,7 @@ export default {
};
},
- initSession() {
+ checkSession() {
return {
type: 'CHECK_SESSION',
payload: new Promise((resolve, reject) => {
diff --git a/client/src/app-components/session-expired-modal.js b/client/src/app-components/session-expired-modal.js
new file mode 100644
index 00000000..e266a319
--- /dev/null
+++ b/client/src/app-components/session-expired-modal.js
@@ -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 (
+
+ );
+ }
+}
+
+export default SessionExpiredModal;
diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js
index 65195a0f..48f01041 100644
--- a/client/src/data/languages/en.js
+++ b/client/src/data/languages/en.js
@@ -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',
diff --git a/client/src/index.js b/client/src/index.js
index 33be2781..157787b2 100644
--- a/client/src/index.js
+++ b/client/src/index.js
@@ -52,4 +52,4 @@ history.listen(() => {
store.dispatch(ConfigActions.checkInstallation());
store.dispatch(ConfigActions.init());
-store.dispatch(SessionActions.initSession());
+store.dispatch(SessionActions.checkSession());
diff --git a/client/src/lib-app/api-call.js b/client/src/lib-app/api-call.js
index c440b5f2..2b9792ae 100644
--- a/client/src/lib-app/api-call.js
+++ b/client/src/lib-app/api-call.js
@@ -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;
- }
+ },
};
diff --git a/client/src/lib-app/expired-session-utils.js b/client/src/lib-app/expired-session-utils.js
new file mode 100644
index 00000000..0ec529ee
--- /dev/null
+++ b/client/src/lib-app/expired-session-utils.js
@@ -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(, 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)
+ );
+ },
+};
diff --git a/server/models/Session.php b/server/models/Session.php
index 44803ded..61285e14 100755
--- a/server/models/Session.php
+++ b/server/models/Session.php
@@ -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();
}