-
Notifications
You must be signed in to change notification settings - Fork 178
Add session timeout for portals #1237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
a0d8652
aed8976
b135f4f
f550fe7
bc7722b
c7a0cfd
3d301b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| /* | ||
| * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. | ||
DinithHerath marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * | ||
| * WSO2 LLC. licenses this file to you under the Apache License, | ||
| * Version 2.0 (the "License"); you may not use this file except | ||
| * in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS | ||
| * OF ANY KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
|
|
||
| import React, { useState, useEffect, useRef } from 'react'; | ||
| import { injectIntl, FormattedMessage } from 'react-intl'; | ||
| import Configurations from 'Config'; | ||
| import ConfirmDialog from 'AppComponents/Shared/ConfirmDialog'; | ||
|
|
||
| const SessionTimeout = () => { | ||
| const [openDialog, setOpenDialog] = useState(false); | ||
| const [remainingTime, setRemainingTime] = useState(0); // To track remaining time | ||
| const openDialogRef = useRef(openDialog); // Create a ref to track openDialog state | ||
|
|
||
| // Use refs to hold values that are mutated from closures and should persist | ||
| const idleTimeoutRef = useRef(0); | ||
| const idleWarningTimeoutRef = useRef(0); | ||
| const idleSecondsCounterRef = useRef(0); | ||
|
|
||
| const handleTimeOut = (idleSecondsCount) => { | ||
| // Only update the remaining time if the warning timeout is reached | ||
| if (idleSecondsCount >= idleWarningTimeoutRef.current) { | ||
| setRemainingTime(idleTimeoutRef.current - idleSecondsCount); // Update remaining time | ||
| } | ||
| if (idleSecondsCount === idleWarningTimeoutRef.current) { | ||
| setOpenDialog(true); // Open dialog when warning timeout is reached | ||
| } | ||
| if (idleSecondsCount === idleTimeoutRef.current) { | ||
| // Logout if the idle timeout is reached | ||
| setOpenDialog(false); // Close dialog if it was open | ||
| window.location = Configurations.app.context + '/services/logout'; | ||
|
Check warning on line 45 in portals/admin/src/main/webapp/source/src/app/components/SessionTimeout.jsx
|
||
DinithHerath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| openDialogRef.current = openDialog; // Update the ref whenever openDialog changes | ||
| }, [openDialog]); | ||
|
|
||
| useEffect(() => { | ||
| if (!(Configurations.sessionTimeout && Configurations.sessionTimeout.enable)) { | ||
|
Check warning on line 54 in portals/admin/src/main/webapp/source/src/app/components/SessionTimeout.jsx
|
||
| return () => { }; | ||
| } | ||
|
|
||
| idleTimeoutRef.current = Configurations.sessionTimeout.idleTimeout; | ||
| idleWarningTimeoutRef.current = Configurations.sessionTimeout.idleWarningTimeout; | ||
|
|
||
| const resetIdleTimer = () => { | ||
| if (!openDialogRef.current) { | ||
| idleSecondsCounterRef.current = 0; | ||
| } | ||
| }; | ||
|
|
||
| document.addEventListener('click', resetIdleTimer); | ||
| document.addEventListener('mousemove', resetIdleTimer); | ||
| document.addEventListener('keydown', resetIdleTimer); | ||
|
|
||
| const worker = new Worker(new URL('../webWorkers/timer.worker.js', import.meta.url)); | ||
| worker.onmessage = () => { | ||
| // increment the ref and pass the new value | ||
| idleSecondsCounterRef.current += 1; | ||
| handleTimeOut(idleSecondsCounterRef.current); | ||
| }; | ||
|
|
||
| // Cleanup function to remove event listeners and terminate the worker | ||
| return () => { | ||
| document.removeEventListener('click', resetIdleTimer); | ||
| document.removeEventListener('mousemove', resetIdleTimer); | ||
| document.removeEventListener('keydown', resetIdleTimer); | ||
| worker.terminate(); | ||
| }; | ||
| }, []); | ||
|
|
||
| const handleConfirmDialog = (res) => { | ||
| if (res) { | ||
| setOpenDialog(false); | ||
| idleSecondsCounterRef.current = 0; // Reset the idle timer stored in ref | ||
| } else { | ||
| window.location = Configurations.app.context + '/services/logout'; | ||
|
Check warning on line 92 in portals/admin/src/main/webapp/source/src/app/components/SessionTimeout.jsx
|
||
DinithHerath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <div> | ||
| <ConfirmDialog | ||
| key='key-dialog' | ||
| labelCancel={( | ||
| <FormattedMessage | ||
| id='SessionTimeout.dialog.label.cancel' | ||
| defaultMessage='Logout' | ||
| /> | ||
| )} | ||
| title={( | ||
| <FormattedMessage | ||
| id='SessionTimeout.dialog.title' | ||
| defaultMessage='Are you still there?' | ||
| /> | ||
| )} | ||
| message={( | ||
| <FormattedMessage | ||
| id='SessionTimeout.dialog.message' | ||
| defaultMessage={ | ||
| 'Your session is about to expire due to inactivity. To keep your session active, ' | ||
| + 'click "Stay Logged In". If no action is taken, ' | ||
| + 'you will be logged out automatically in {time} seconds, for security reasons.' | ||
| } | ||
| values={{ time: remainingTime }} // Use remainingTime here | ||
| /> | ||
| )} | ||
| labelOk={( | ||
| <FormattedMessage | ||
| id='SessionTimeout.dialog.label.ok' | ||
| defaultMessage='Stay Logged In' | ||
| /> | ||
| )} | ||
| callback={handleConfirmDialog} | ||
| open={openDialog} | ||
| /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default injectIntl(SessionTimeout); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| /* | ||
| * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. | ||
DinithHerath marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * | ||
| * WSO2 LLC. licenses this file to you under the Apache License, | ||
| * Version 2.0 (the "License"); you may not use this file except | ||
| * in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
|
|
||
| // This is a web worker that will be used to run the timer. | ||
| // This timer will be used to execute the session timeout | ||
| // functionality in the background without blocking the main thread. | ||
|
|
||
| // This is the timer interval in milliseconds. | ||
| const TIMER_INTERVAL = 1000; | ||
|
|
||
| // This is the timer function that will be executed in the background. | ||
| setInterval(() => { | ||
| // disable following rule because linter is unaware of the worker source | ||
| // eslint-disable-next-line no-restricted-globals | ||
| self.postMessage(''); | ||
| }, TIMER_INTERVAL); | ||
|
|
||
| export default {}; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| /* | ||
| * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. | ||
DinithHerath marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * | ||
| * WSO2 LLC. licenses this file to you under the Apache License, | ||
| * Version 2.0 (the "License"); you may not use this file except | ||
| * in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS | ||
| * OF ANY KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
|
|
||
| import React, { useState, useEffect, useRef } from 'react'; | ||
| import { injectIntl, FormattedMessage } from 'react-intl'; | ||
DinithHerath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| import Settings from 'Settings'; | ||
| import ConfirmDialog from 'AppComponents/Shared/ConfirmDialog'; | ||
|
|
||
| const SessionTimeout = () => { | ||
| const [openDialog, setOpenDialog] = useState(false); | ||
| const [remainingTime, setRemainingTime] = useState(0); // To track remaining time | ||
| const openDialogRef = useRef(openDialog); // Create a ref to track openDialog state | ||
|
|
||
| // Use refs to hold values that are mutated from closures and should persist | ||
| const idleTimeoutRef = useRef(0); | ||
| const idleWarningTimeoutRef = useRef(0); | ||
| const idleSecondsCounterRef = useRef(0); | ||
|
|
||
| const handleTimeOut = (idleSecondsCount) => { | ||
| // Only update the remaining time if the warning timeout is reached | ||
| if (idleSecondsCount >= idleWarningTimeoutRef.current) { | ||
| setRemainingTime(idleTimeoutRef.current - idleSecondsCount); // Update remaining time | ||
| } | ||
| if (idleSecondsCount === idleWarningTimeoutRef.current) { | ||
| setOpenDialog(true); // Open dialog when warning timeout is reached | ||
| } | ||
| if (idleSecondsCount === idleTimeoutRef.current) { | ||
| // Logout if the idle timeout is reached | ||
| setOpenDialog(false); // Close dialog if it was open | ||
| window.location = Settings.app.context + '/services/logout'; | ||
|
Check warning on line 45 in portals/devportal/src/main/webapp/source/src/app/components/SessionTimeout.jsx
|
||
DinithHerath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| openDialogRef.current = openDialog; // Update the ref whenever openDialog changes | ||
| }, [openDialog]); | ||
|
|
||
| useEffect(() => { | ||
| if (!(Settings.sessionTimeout && Settings.sessionTimeout.enable)) { | ||
|
Check warning on line 54 in portals/devportal/src/main/webapp/source/src/app/components/SessionTimeout.jsx
|
||
| return () => { }; | ||
| } | ||
|
|
||
| idleTimeoutRef.current = Settings.sessionTimeout.idleTimeout; | ||
| idleWarningTimeoutRef.current = Settings.sessionTimeout.idleWarningTimeout; | ||
|
|
||
| const resetIdleTimer = () => { | ||
| if (!openDialogRef.current) { | ||
| idleSecondsCounterRef.current = 0; | ||
| } | ||
| }; | ||
|
|
||
| document.addEventListener('click', resetIdleTimer); | ||
| document.addEventListener('mousemove', resetIdleTimer); | ||
| document.addEventListener('keydown', resetIdleTimer); | ||
|
|
||
| const worker = new Worker(new URL('../webWorkers/timer.worker.js', import.meta.url)); | ||
| worker.onmessage = () => { | ||
| // increment the ref and pass the new value | ||
| idleSecondsCounterRef.current += 1; | ||
| handleTimeOut(idleSecondsCounterRef.current); | ||
| }; | ||
|
|
||
| // Cleanup function to remove event listeners and terminate the worker | ||
| return () => { | ||
| document.removeEventListener('click', resetIdleTimer); | ||
| document.removeEventListener('mousemove', resetIdleTimer); | ||
| document.removeEventListener('keydown', resetIdleTimer); | ||
| worker.terminate(); | ||
| }; | ||
| }, []); | ||
|
|
||
| const handleConfirmDialog = (res) => { | ||
| if (res) { | ||
| setOpenDialog(false); | ||
| idleSecondsCounterRef.current = 0; // Reset the idle timer stored in ref | ||
| } else { | ||
| window.location = Settings.app.context + '/services/logout'; | ||
|
Check warning on line 92 in portals/devportal/src/main/webapp/source/src/app/components/SessionTimeout.jsx
|
||
DinithHerath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <div> | ||
| <ConfirmDialog | ||
| key='key-dialog' | ||
| labelCancel={( | ||
| <FormattedMessage | ||
| id='SessionTimeout.dialog.label.cancel' | ||
| defaultMessage='Logout' | ||
| /> | ||
| )} | ||
| title={( | ||
| <FormattedMessage | ||
| id='SessionTimeout.dialog.title' | ||
| defaultMessage='Are you still there?' | ||
| /> | ||
| )} | ||
| message={( | ||
| <FormattedMessage | ||
| id='SessionTimeout.dialog.message' | ||
| defaultMessage={ | ||
| 'Your session is about to expire due to inactivity. To keep your session active, click "Stay Logged In". ' | ||
| + 'If no action is taken, you will be logged out automatically in {time} seconds, for security reasons.' | ||
| } | ||
| values={{ time: remainingTime }} // Use remainingTime here | ||
| /> | ||
| )} | ||
| labelOk={( | ||
| <FormattedMessage | ||
| id='SessionTimeout.dialog.label.ok' | ||
| defaultMessage='Stay Logged In' | ||
| /> | ||
| )} | ||
| callback={handleConfirmDialog} | ||
| open={openDialog} | ||
| /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default injectIntl(SessionTimeout); | ||
Uh oh!
There was an error while loading. Please reload this page.