From de6f09d76faea39e8ec8ebf2d810866780b2473a Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Wed, 9 Mar 2022 05:58:12 +0600 Subject: [PATCH 1/6] feat: logout user if idle for configured duration --- config/constants/development.js | 6 +- package-lock.json | 5 ++ package.json | 1 + src/routes.js | 147 +++++++++++++++++++++----------- src/styles/modal.module.scss | 114 +++++++++++++++++++++++++ 5 files changed, 223 insertions(+), 50 deletions(-) create mode 100644 src/styles/modal.module.scss diff --git a/config/constants/development.js b/config/constants/development.js index 597dbb57..2efc33f9 100644 --- a/config/constants/development.js +++ b/config/constants/development.js @@ -39,5 +39,9 @@ module.exports = { FILE_PICKER_API_KEY: process.env.FILE_PICKER_API_KEY, FILE_PICKER_CONTAINER_NAME: 'tc-challenge-v5-dev', FILE_PICKER_REGION: 'us-east-1', - FILE_PICKER_CNAME: 'fs.topcoder.com' + FILE_PICKER_CNAME: 'fs.topcoder.com', + // if idle for this many minutes, show user a prompt saying they'll be logged out + IDLE_TIMEOUT_MINUTES: 10, + // duration to show the prompt saying user will be logged out, before actually logging out the user + IDLE_TIMEOUT_GRACE_MINUTES: 5 } diff --git a/package-lock.json b/package-lock.json index e44e6c3c..65c76e8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15002,6 +15002,11 @@ "react-side-effect": "^1.1.0" } }, + "react-idle-timer": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-4.6.4.tgz", + "integrity": "sha512-iq61dPud8fgj7l1KOJEY5pyiD532fW0KcIe/5XUe/0lB/4Vytoy4tZBlLGSiYodPzKxTL6HyKoOmG6tyzjD7OQ==" + }, "react-input-autosize": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz", diff --git a/package.json b/package.json index 726e30b9..02e50216 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "react-dom": "^16.7.0", "react-google-charts": "^3.0.13", "react-helmet": "^5.2.0", + "react-idle-timer": "^4.6.4", "react-js-pagination": "^3.0.3", "react-popper": "^2.2.4", "react-redux": "^6.0.0", diff --git a/src/routes.js b/src/routes.js index 1c3041da..f91e870e 100644 --- a/src/routes.js +++ b/src/routes.js @@ -17,8 +17,15 @@ import { loadChallengeDetails } from './actions/challenges' import { connect } from 'react-redux' import { checkAllowedRoles } from './util/tc' import { setCookie, removeCookie, isBetaMode } from './util/cookie' +import IdleTimer from 'react-idle-timer' +import AlertModal from './components/Modal/AlertModal' +import modalStyles from './styles/modal.module.scss' -const { ACCOUNTS_APP_LOGIN_URL } = process.env +const { ACCOUNTS_APP_LOGIN_URL, IDLE_TIMEOUT_MINUTES, IDLE_TIMEOUT_GRACE_MINUTES, COMMUNITY_APP_URL } = process.env + +const theme = { + container: modalStyles.modalContainer +} class RedirectToChallenge extends React.Component { componentWillMount () { @@ -59,6 +66,19 @@ RedirectToChallenge.propTypes = { const ConnectRedirectToChallenge = connect(mapStateToProps, mapDispatchToProps)(RedirectToChallenge) class Routes extends React.Component { + constructor (props) { + super(props) + this.idleTimer = null + this.handleOnIdle = this.handleOnIdle.bind(this) + + this.logoutIntervalRef = null + this.state = { + showIdleModal: false, + logsoutIn: IDLE_TIMEOUT_GRACE_MINUTES * 60, // convert to seconds + logoutIntervalRef: null + } + } + componentWillMount () { this.checkAuth() } @@ -87,68 +107,97 @@ class Routes extends React.Component { } } + handleOnIdle () { + this.idleTimer.pause() + const intervalId = setInterval(() => { + const remaining = this.state.logsoutIn + if (remaining > 0) { + this.setState(state => ({ ...state, logsoutIn: remaining - 1 })) + } else { + window.location = `${COMMUNITY_APP_URL}/logout` + } + }, 1000) + + this.setState(state => ({ ...state, showIdleModal: true, logoutIntervalRef: intervalId })) + } + render () { if (!this.props.isLoggedIn) { return null } - let isAllowed = checkAllowedRoles(_.get(decodeToken(this.props.token), 'roles')) + const isAllowed = checkAllowedRoles(_.get(decodeToken(this.props.token), 'roles')) + const modal = (= 60 ? Math.ceil(this.state.logsoutIn / 60) + ' minute(s).' : this.state.logsoutIn + ' second(s)'}`} + closeText='Resume Session' + onClose={() => { + clearInterval(this.state.logoutIntervalRef) + if (this.idleTimer.isIdle()) { + this.idleTimer.resume() + this.idleTimer.reset() + this.setState(state => ({ + ...state, showIdleModal: false, logsoutIn: 120 + })) + } + }} + />) - if (!isAllowed) { - let warnMessage = 'You are not authorized to use this application' - return ( + return ( + { this.idleTimer = ref }} timeout={1000 * 60 * IDLE_TIMEOUT_MINUTES} onIdle={this.handleOnIdle} debounce={250}> + {!isAllowed && renderApp( - , + , , )()} - /> + />} + + {isAllowed && <> + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} /> + + renderApp( + , + , + + )()} /> + renderApp( + , + , + + )()} /> + } + + {/* If path is not defined redirect to landing page */} - ) - } - - return ( - - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} /> - - renderApp( - , - , - - )()} /> - renderApp( - , - , - - )()} /> - {/* If path is not defined redirect to landing page */} - - + {this.state.showIdleModal && modal} + ) } } diff --git a/src/styles/modal.module.scss b/src/styles/modal.module.scss new file mode 100644 index 00000000..12a06dbc --- /dev/null +++ b/src/styles/modal.module.scss @@ -0,0 +1,114 @@ +@import "./includes.scss"; + +.modalContainer { + padding: 0; + position: fixed; + overflow: auto; + z-index: 10000; + top: 0; + right: 0; + bottom: 0; + left: 0; + box-sizing: border-box; + width: auto; + max-width: none; + transform: none; + background: transparent; + color: $text-color; + opacity: 1; + display: flex; + justify-content: center; + align-items: center; + + .contentContainer { + box-sizing: border-box; + background: $white; + opacity: 1; + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + border-radius: 6px; + margin: 0 auto; + width: 100% !important; + padding: 30px; + + .content { + padding: 30px; + width: 100%; + height: 100%; + } + + .title { + @include roboto-bold(); + + font-size: 30px; + line-height: 36px; + margin-bottom: 30px; + margin-top: 0; + + } + + span { + @include roboto; + + font-size: 22px; + font-weight: 400; + line-height: 26px; + } + + &.confirm { + width: 999px; + + .buttonGroup { + display: flex; + justify-content: space-between; + margin-top: 30px; + + .buttonSizeA { + width: 193px; + height: 40px; + margin-right: 33px; + + span { + font-size: 18px; + font-weight: 500; + } + } + + .buttonSizeB{ + width: 160px; + height: 40px; + + span { + font-size: 18px; + font-weight: 500; + line-height: 22px; + } + } + } + } + + .buttonGroup { + display: flex; + justify-content: space-between; + margin-top: 30px; + + .button { + width: 135px; + height: 40px; + margin-right: 66px; + + span { + font-size: 18px; + font-weight: 500; + } + } + + .button:last-child { + margin-right: 0; + } + } + } +} From 1949c12a2de544f70267be46498ba84cf9e8fb09 Mon Sep 17 00:00:00 2001 From: sachin-maheshwari Date: Wed, 9 Mar 2022 13:10:12 +0530 Subject: [PATCH 2/6] Update config.yml deploying on dev --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0d96a6c9..ca26190c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -150,7 +150,7 @@ workflows: context : org-global filters: &filters-dev branches: - only: ['develop'] + only: ['develop', 'jira-vuln-2333'] # Production builds are exectuted only on tagged commits to the # master branch. From 4dd155e8ab0ac662e56aeac64e8bd0e1950857fe Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Thu, 10 Mar 2022 07:57:53 +0600 Subject: [PATCH 3/6] feat: add ability to logout immediately * add "Logout Now" in idle timeout modal * reset modal display time when user resumes session --- config/constants/development.js | 4 ++-- src/routes.js | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/config/constants/development.js b/config/constants/development.js index 2efc33f9..1057e70b 100644 --- a/config/constants/development.js +++ b/config/constants/development.js @@ -41,7 +41,7 @@ module.exports = { FILE_PICKER_REGION: 'us-east-1', FILE_PICKER_CNAME: 'fs.topcoder.com', // if idle for this many minutes, show user a prompt saying they'll be logged out - IDLE_TIMEOUT_MINUTES: 10, + IDLE_TIMEOUT_MINUTES: 1, // duration to show the prompt saying user will be logged out, before actually logging out the user - IDLE_TIMEOUT_GRACE_MINUTES: 5 + IDLE_TIMEOUT_GRACE_MINUTES: 1 } diff --git a/src/routes.js b/src/routes.js index f91e870e..6fd6e8cf 100644 --- a/src/routes.js +++ b/src/routes.js @@ -18,8 +18,8 @@ import { connect } from 'react-redux' import { checkAllowedRoles } from './util/tc' import { setCookie, removeCookie, isBetaMode } from './util/cookie' import IdleTimer from 'react-idle-timer' -import AlertModal from './components/Modal/AlertModal' import modalStyles from './styles/modal.module.scss' +import ConfirmationModal from './components/Modal/ConfirmationModal' const { ACCOUNTS_APP_LOGIN_URL, IDLE_TIMEOUT_MINUTES, IDLE_TIMEOUT_GRACE_MINUTES, COMMUNITY_APP_URL } = process.env @@ -127,21 +127,25 @@ class Routes extends React.Component { } const isAllowed = checkAllowedRoles(_.get(decodeToken(this.props.token), 'roles')) - const modal = (= 60 ? Math.ceil(this.state.logsoutIn / 60) + ' minute(s).' : this.state.logsoutIn + ' second(s)'}`} - closeText='Resume Session' - onClose={() => { + confirmText='Logout Now' + cancelText='Resume Session' + onCancel={() => { clearInterval(this.state.logoutIntervalRef) if (this.idleTimer.isIdle()) { this.idleTimer.resume() this.idleTimer.reset() this.setState(state => ({ - ...state, showIdleModal: false, logsoutIn: 120 + ...state, showIdleModal: false, logsoutIn: IDLE_TIMEOUT_GRACE_MINUTES * 60 })) } }} + onConfirm={() => { + window.location = `${COMMUNITY_APP_URL}/logout` + }} />) return ( From 23b490939b8e38bbda3de6ea5ba67e4d66509a45 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Thu, 10 Mar 2022 08:02:15 +0600 Subject: [PATCH 4/6] update idle timeout duration --- config/constants/development.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/constants/development.js b/config/constants/development.js index 1057e70b..2efc33f9 100644 --- a/config/constants/development.js +++ b/config/constants/development.js @@ -41,7 +41,7 @@ module.exports = { FILE_PICKER_REGION: 'us-east-1', FILE_PICKER_CNAME: 'fs.topcoder.com', // if idle for this many minutes, show user a prompt saying they'll be logged out - IDLE_TIMEOUT_MINUTES: 1, + IDLE_TIMEOUT_MINUTES: 10, // duration to show the prompt saying user will be logged out, before actually logging out the user - IDLE_TIMEOUT_GRACE_MINUTES: 1 + IDLE_TIMEOUT_GRACE_MINUTES: 5 } From 1efc162d07a44613e4bb53037d6f9cfe5c6b4cf0 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Thu, 10 Mar 2022 09:52:14 +0600 Subject: [PATCH 5/6] fix: new challenge not working --- src/routes.js | 82 +++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/src/routes.js b/src/routes.js index 6fd6e8cf..fbe0be96 100644 --- a/src/routes.js +++ b/src/routes.js @@ -150,56 +150,54 @@ class Routes extends React.Component { return ( { this.idleTimer = ref }} timeout={1000 * 60 * IDLE_TIMEOUT_MINUTES} onIdle={this.handleOnIdle} debounce={250}> - - {!isAllowed && + {!isAllowed && renderApp( , , )()} - />} - - {isAllowed && <> - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} /> - - renderApp( - , - , - - )()} /> - renderApp( - , - , - - )()} /> - } - + /> + + } + {isAllowed && + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} /> + + renderApp( + , + , + + )()} /> + renderApp( + , + , + + )()} /> {/* If path is not defined redirect to landing page */} - + } {this.state.showIdleModal && modal} ) From 639bfd99d9815d570437c8cde9f175ce0398e5d9 Mon Sep 17 00:00:00 2001 From: Sachin Maheshwari Date: Tue, 15 Mar 2022 12:30:21 +0530 Subject: [PATCH 6/6] production conf issue --- config/constants/production.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/constants/production.js b/config/constants/production.js index 5d87c493..4040c262 100644 --- a/config/constants/production.js +++ b/config/constants/production.js @@ -39,5 +39,7 @@ module.exports = { FILE_PICKER_API_KEY: process.env.FILE_PICKER_API_KEY, FILE_PICKER_CONTAINER_NAME: 'tc-challenge-v5-prod', FILE_PICKER_REGION: 'us-east-1', - FILE_PICKER_CNAME: 'fs.topcoder.com' + FILE_PICKER_CNAME: 'fs.topcoder.com', + IDLE_TIMEOUT_MINUTES: 10, + IDLE_TIMEOUT_GRACE_MINUTES: 5 }