[DEV-143] Handle expired sessions (#1192)

This commit is contained in:
Maximiliano Redigonda 2022-06-02 11:39:31 -03:00 committed by GitHub
parent 82fd54ffd9
commit 36c5f3264b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 102 additions and 37 deletions

View File

@ -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.

View File

@ -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",

View File

@ -1,5 +1,5 @@
export default {
login: stub(),
logout: stub(),
initSession: stub()
checkSession: stub()
};

View File

@ -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',

View File

@ -101,7 +101,7 @@ export default {
};
},
initSession() {
checkSession() {
return {
type: 'CHECK_SESSION',
payload: new Promise((resolve, reject) => {

View File

@ -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;

View File

@ -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 dont have assigned wont be visible.',
'SESSION_EXPIRED_DESCRIPTION': 'Your session has timed out. Please log in again.',
//ERRORS
'EMAIL_OR_PASSWORD': 'Email or password invalid',

View File

@ -52,4 +52,4 @@ history.listen(() => {
store.dispatch(ConfigActions.checkInstallation());
store.dispatch(ConfigActions.init());
store.dispatch(SessionActions.initSession());
store.dispatch(SessionActions.checkSession());

View File

@ -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;
}
},
};

View File

@ -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)
);
},
};

View File

@ -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();
}