From 9d16738c838a1ecd7211539c68a1db72b7e65d93 Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Wed, 16 Aug 2023 10:55:24 -0600 Subject: [PATCH 01/19] Add initial file for popup --- app/__init__.py | 2 +- app/assets/javascripts/timeoutPopup.js | 15 +++++++++++++++ app/config.py | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/timeoutPopup.js diff --git a/app/__init__.py b/app/__init__.py index 5f244f630..640563c56 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -349,7 +349,7 @@ def make_session_permanent(): """ Make sessions permanent. By permanent, we mean "admin app sets when it expires". Normally the cookie would expire whenever you close the browser. With this, the session expiry is set in `config['PERMANENT_SESSION_LIFETIME']` - (20 hours) and is refreshed after every request. IE: you will be logged out after twenty hours of inactivity. + (30 min) and is refreshed after every request. IE: you will be logged out after thirty minutes of inactivity. We don't _need_ to set this every request (it's saved within the cookie itself under the `_permanent` flag), only when you first log in/sign up/get invited/etc, but we do it just to be safe. For more reading, check here: diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js new file mode 100644 index 000000000..0807a0f3a --- /dev/null +++ b/app/assets/javascripts/timeoutPopup.js @@ -0,0 +1,15 @@ +(function(global ) { + "use strict"; + + var cookie = window.GOVUK.getCookie('notify_admin_session'); + + if (cookie) { + window.alert("Session will expire: Cookie Value" + cookie); + } else { + window.alert('Session has expired'); + } + console.log(cookie); + + +})(window); + diff --git a/app/config.py b/app/config.py index 568c51be7..33987cef3 100644 --- a/app/config.py +++ b/app/config.py @@ -50,7 +50,7 @@ class Config(object): EMAIL_EXPIRY_SECONDS = 3600 # 1 hour INVITATION_EXPIRY_SECONDS = 3600 * 24 * 2 # 2 days - also set on api EMAIL_2FA_EXPIRY_SECONDS = 1800 # 30 Minutes - PERMANENT_SESSION_LIFETIME = 20 * 60 * 60 # 20 hours + PERMANENT_SESSION_LIFETIME = 1800 # 30 Minutes SEND_FILE_MAX_AGE_DEFAULT = 365 * 24 * 60 * 60 # 1 year REPLY_TO_EMAIL_ADDRESS_VALIDATION_TIMEOUT = 45 ACTIVITY_STATS_LIMIT_DAYS = 7 From 8712d7d822bdfa34f00bed94612eb1a6ad82f726 Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Thu, 7 Sep 2023 08:38:41 -0600 Subject: [PATCH 02/19] Functionality for session timer display --- app/assets/javascripts/timeoutPopup.js | 48 +++++++++++++++++++++----- app/templates/admin_template.html | 9 ++++- gulpfile.js | 1 + 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index 0807a0f3a..30f179031 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -1,15 +1,47 @@ -(function(global ) { +(function(global) { "use strict"; + hideTimerButtons(); - var cookie = window.GOVUK.getCookie('notify_admin_session'); + setTimeout(function() { + var timeTillSessionEnd = new Date().getTime() + (5 * 60 * 1000); + var x = setInterval(function() { + var now = new Date().getTime(); + var difference = timeTillSessionEnd - now; + var minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)); + var seconds = Math.floor((difference % (1000 * 60)) / 1000); - if (cookie) { - window.alert("Session will expire: Cookie Value" + cookie); - } else { - window.alert('Session has expired'); + document.getElementById("timerWarning").innerHTML = "Your session will " + + "expire in " + minutes + "m " + seconds + "s "; + showTimerButtons(); + document.getElementById("logOutTimer").addEventListener("click", logoutByUser); + document.getElementById("extendSessionTimer").addEventListener("click", extendSession); + if (difference === 0) { + clearInterval(x); + redirectToSignin(); + } + }, 1000); + }, 25 * 60 * 1000); + + function redirectToSignin() { + window.location.href = '/sign-in'; } - console.log(cookie); + function logoutByUser() { + window.location.href = '/sign-out'; + } + + function extendSession() { + window.location.reload(); + } + + function hideTimerButtons() { + document.getElementById("logOutTimer").style.display = 'none'; + document.getElementById("extendSessionTimer").style.display = 'none'; + } + + function showTimerButtons() { + document.getElementById("logOutTimer").style.display = 'block'; + document.getElementById("extendSessionTimer").style.display = 'block'; + } })(window); - diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html index c6436380e..a3e4e7fe1 100644 --- a/app/templates/admin_template.html +++ b/app/templates/admin_template.html @@ -11,6 +11,13 @@ {% endblock %} {% block head %} + {% block sessionUserWarning %} +
+

+ + +
+ {% endblock %} {%- for font in font_paths %} {%- endfor %} @@ -231,6 +238,6 @@ {% endblock %} - + {% endblock %} diff --git a/gulpfile.js b/gulpfile.js index 70e569291..495b164bc 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -127,6 +127,7 @@ const javascripts = () => { paths.src + 'javascripts/updateStatus.js', paths.src + 'javascripts/errorBanner.js', paths.src + 'javascripts/homepage.js', + paths.src + 'javascripts/timeoutPopup.js', paths.src + 'javascripts/main.js', ]) .pipe(plugins.prettyerror()) From 1507199e6bf4ba74e8252c0e702ad892a90e9628 Mon Sep 17 00:00:00 2001 From: Jonathan Bobel Date: Thu, 7 Sep 2023 16:41:55 -0400 Subject: [PATCH 03/19] Altered the modal a bit to use USWDS styling and the dialog element - still needs some cleanup but passing it back off to Andrew --- app/assets/javascripts/timeoutPopup.js | 11 ++++--- app/templates/admin_template.html | 43 +++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index 30f179031..1becfc48d 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -1,15 +1,16 @@ (function(global) { "use strict"; - hideTimerButtons(); + //hideTimerButtons(); + + const sessionTimer = document.getElementById("sessionTimer"); setTimeout(function() { - var timeTillSessionEnd = new Date().getTime() + (5 * 60 * 1000); + var timeTillSessionEnd = new Date().getTime() + (1 * 10 * 1000); var x = setInterval(function() { var now = new Date().getTime(); var difference = timeTillSessionEnd - now; var minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)); var seconds = Math.floor((difference % (1000 * 60)) / 1000); - document.getElementById("timerWarning").innerHTML = "Your session will " + "expire in " + minutes + "m " + seconds + "s "; showTimerButtons(); @@ -20,7 +21,7 @@ redirectToSignin(); } }, 1000); - }, 25 * 60 * 1000); + }, 1 * 20 * 1000); function redirectToSignin() { window.location.href = '/sign-in'; @@ -40,7 +41,7 @@ } function showTimerButtons() { - document.getElementById("logOutTimer").style.display = 'block'; + sessionTimer.showModal(); document.getElementById("extendSessionTimer").style.display = 'block'; } diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html index fe7057b74..106db0c69 100644 --- a/app/templates/admin_template.html +++ b/app/templates/admin_template.html @@ -12,11 +12,46 @@ {% block head %} {% block sessionUserWarning %} -
-

- - +
+
+
+

+ Your session will end soon. +

+
+

+ You’ve been inactive for too long. Please choose to stay signed in + or sign out. Otherwise, you’ll be signed out automatically in 5 + minutes. +

+
+ +
+
+
+ {% endblock %} {%- for font in font_paths %} From 92953676c4ca1a9de11b499976680e626699974e Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Fri, 8 Sep 2023 13:51:12 -0600 Subject: [PATCH 04/19] Added user authentication check to timer --- app/assets/javascripts/timeoutPopup.js | 4 ++-- app/templates/admin_template.html | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index 1becfc48d..c331cb0f7 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -5,7 +5,7 @@ const sessionTimer = document.getElementById("sessionTimer"); setTimeout(function() { - var timeTillSessionEnd = new Date().getTime() + (1 * 10 * 1000); + var timeTillSessionEnd = new Date().getTime() + (5 * 60 * 1000); var x = setInterval(function() { var now = new Date().getTime(); var difference = timeTillSessionEnd - now; @@ -21,7 +21,7 @@ redirectToSignin(); } }, 1000); - }, 1 * 20 * 1000); + }, 1 * 60 * 1000); function redirectToSignin() { window.location.href = '/sign-in'; diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html index 106db0c69..483115985 100644 --- a/app/templates/admin_template.html +++ b/app/templates/admin_template.html @@ -11,6 +11,7 @@ {% endblock %} {% block head %} + {% if current_user.is_authenticated %} {% block sessionUserWarning %}
@@ -53,6 +54,7 @@ --> {% endblock %} + {% endif %} {%- for font in font_paths %} {%- endfor %} From 42cd3b4a0e3bf5937e07a646539f63047ba2151a Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Tue, 12 Sep 2023 11:30:27 -0600 Subject: [PATCH 05/19] Moved html to footer --- app/assets/javascripts/timeoutPopup.js | 21 ++-- app/templates/admin_template.html | 127 ++++++++++++++++--------- 2 files changed, 92 insertions(+), 56 deletions(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index c331cb0f7..c52d4d462 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -1,6 +1,5 @@ (function(global) { "use strict"; - //hideTimerButtons(); const sessionTimer = document.getElementById("sessionTimer"); @@ -11,17 +10,19 @@ var difference = timeTillSessionEnd - now; var minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)); var seconds = Math.floor((difference % (1000 * 60)) / 1000); - document.getElementById("timerWarning").innerHTML = "Your session will " + + document.getElementById("timerWarning").innerHTML = "You have been inactive " + + "for too long. Please choose to stay signed in or sign out. Your session will " + "expire in " + minutes + "m " + seconds + "s "; - showTimerButtons(); + showTimer(); document.getElementById("logOutTimer").addEventListener("click", logoutByUser); document.getElementById("extendSessionTimer").addEventListener("click", extendSession); - if (difference === 0) { + if (difference < 0) { clearInterval(x); + closeTimer(); redirectToSignin(); } }, 1000); - }, 1 * 60 * 1000); + }, 60 * 1000); function redirectToSignin() { window.location.href = '/sign-in'; @@ -35,14 +36,12 @@ window.location.reload(); } - function hideTimerButtons() { - document.getElementById("logOutTimer").style.display = 'none'; - document.getElementById("extendSessionTimer").style.display = 'none'; + function showTimer() { + sessionTimer.showModal(); } - function showTimerButtons() { - sessionTimer.showModal(); - document.getElementById("extendSessionTimer").style.display = 'block'; + function closeTimer() { + sessionTimer.close(); } })(window); diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html index 483115985..b3c6eb36b 100644 --- a/app/templates/admin_template.html +++ b/app/templates/admin_template.html @@ -11,50 +11,47 @@ {% endblock %} {% block head %} - {% if current_user.is_authenticated %} - {% block sessionUserWarning %} -
-
-
-

- Your session will end soon. -

-
-

- You’ve been inactive for too long. Please choose to stay signed in - or sign out. Otherwise, you’ll be signed out automatically in 5 - minutes. -

-
- - -
-
-
- - {% endblock %} - {% endif %} +{# {% if current_user.is_authenticated %}#} +{# {% block sessionUserWarning %}#} +{# #} +{#
#} +{#
#} +{#

#} +{# Your session will end soon.#} +{#

#} +{#
#} +{#

#} +{#

#} +{#
#} +{# #} +{# #} +{#
#} +{#
#} +{#
#} +{# #} +{# {% endblock %}#} +{# {% endif %}#} {%- for font in font_paths %} {%- endfor %} @@ -187,7 +184,47 @@ {% endblock %} {% block footer %} - + {% if current_user.is_authenticated %} + {% block sessionUserWarning %} + +
+
+

+ Your session will end soon. +

+
+

+

+
+ + +
+
+
+ + {% endblock %} + {% endif %} {% if current_service and current_service.research_mode %} {% set meta_suffix = 'Built by the Technology Transformation Servicesresearch mode' %} From c6e356c315465665c55f5d4ecb8d59961186b28d Mon Sep 17 00:00:00 2001 From: Jonathan Bobel Date: Tue, 12 Sep 2023 15:17:06 -0400 Subject: [PATCH 06/19] Changed a few things for accessibility --- app/assets/javascripts/timeoutPopup.js | 4 +- app/templates/admin_template.html | 83 +++++++++++++------------- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index c52d4d462..af9a9d1cb 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -10,9 +10,7 @@ var difference = timeTillSessionEnd - now; var minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)); var seconds = Math.floor((difference % (1000 * 60)) / 1000); - document.getElementById("timerWarning").innerHTML = "You have been inactive " + - "for too long. Please choose to stay signed in or sign out. Your session will " + - "expire in " + minutes + "m " + seconds + "s "; + document.getElementById("timeLeft").innerHTML = + minutes + "m " + seconds + "s"; showTimer(); document.getElementById("logOutTimer").addEventListener("click", logoutByUser); document.getElementById("extendSessionTimer").addEventListener("click", extendSession); diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html index b3c6eb36b..86a9847c3 100644 --- a/app/templates/admin_template.html +++ b/app/templates/admin_template.html @@ -184,47 +184,6 @@ {% endblock %} {% block footer %} - {% if current_user.is_authenticated %} - {% block sessionUserWarning %} - -
-
-

- Your session will end soon. -

-
-

-

-
- - -
-
-
- - {% endblock %} - {% endif %} {% if current_service and current_service.research_mode %} {% set meta_suffix = 'Built by the Technology Transformation Servicesresearch mode' %} @@ -303,13 +262,53 @@ "html": meta_suffix } }) }} + + {% if current_user.is_authenticated %} + {% block sessionUserWarning %} + +
+
+

+ Your session will end soon. + Please choose to extend your session or sign out. Your session will expire in 5 minutes or less. +

+
+

You have been inactive for too long. + Your session will expire in . +

+
+ +
+
+
+ {% endblock %} + {% endif %} + {% endblock %} + {% block bodyEnd %} {% block extra_javascripts %} {% endblock %} - + + {% endblock %} + + From f257a38084dc10c9f86d7e06b81793003b4ce981 Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Wed, 13 Sep 2023 12:44:40 -0600 Subject: [PATCH 07/19] Refactor code/initial tests --- app/assets/javascripts/timeoutPopup.js | 12 +++----- app/templates/admin_template.html | 41 -------------------------- tests/javascripts/timeoutPopup.test.js | 18 +++++++++++ 3 files changed, 22 insertions(+), 49 deletions(-) create mode 100644 tests/javascripts/timeoutPopup.test.js diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index af9a9d1cb..0a98ea3d7 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -12,21 +12,17 @@ var seconds = Math.floor((difference % (1000 * 60)) / 1000); document.getElementById("timeLeft").innerHTML = + minutes + "m " + seconds + "s"; showTimer(); - document.getElementById("logOutTimer").addEventListener("click", logoutByUser); + document.getElementById("logOutTimer").addEventListener("click", logoutUser); document.getElementById("extendSessionTimer").addEventListener("click", extendSession); if (difference < 0) { clearInterval(x); closeTimer(); - redirectToSignin(); + logoutUser(); } }, 1000); - }, 60 * 1000); + }, 25 * 60 * 1000); - function redirectToSignin() { - window.location.href = '/sign-in'; - } - - function logoutByUser() { + function logoutUser() { window.location.href = '/sign-out'; } diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html index 86a9847c3..8a7aa49b1 100644 --- a/app/templates/admin_template.html +++ b/app/templates/admin_template.html @@ -11,47 +11,6 @@ {% endblock %} {% block head %} -{# {% if current_user.is_authenticated %}#} -{# {% block sessionUserWarning %}#} -{# #} -{#
#} -{#
#} -{#

#} -{# Your session will end soon.#} -{#

#} -{#
#} -{#

#} -{#

#} -{#
#} -{# #} -{# #} -{#
#} -{#
#} -{#
#} -{# #} -{# {% endblock %}#} -{# {% endif %}#} {%- for font in font_paths %} {%- endfor %} diff --git a/tests/javascripts/timeoutPopup.test.js b/tests/javascripts/timeoutPopup.test.js new file mode 100644 index 000000000..a91e667ea --- /dev/null +++ b/tests/javascripts/timeoutPopup.test.js @@ -0,0 +1,18 @@ +const timeoutPopup = require('../../app/assets/javascripts/timeoutPopup.js'); + + +describe('Test popup process', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('Test timers work', () => { + jest.runAllTimers(); + }); + +}); + From d856e39484886962c41ed95b6a9e60585542bc21 Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Thu, 14 Sep 2023 08:56:46 -0600 Subject: [PATCH 08/19] Create export of session timer for tests --- app/assets/javascripts/timeoutPopup.js | 14 ++++++++++---- tests/javascripts/timeoutPopup.test.js | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index 0a98ea3d7..20a6e2eed 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -1,6 +1,6 @@ -(function(global) { +const sessionTimerWrapper = function() { "use strict"; - + (function(global) { const sessionTimer = document.getElementById("sessionTimer"); setTimeout(function() { @@ -20,7 +20,7 @@ logoutUser(); } }, 1000); - }, 25 * 60 * 1000); + }, 60 * 1000); function logoutUser() { window.location.href = '/sign-out'; @@ -37,5 +37,11 @@ function closeTimer() { sessionTimer.close(); } + })(window); +}; -})(window); +module.exports = sessionTimerWrapper; + +(function(){ + sessionTimerWrapper(); +})(); diff --git a/tests/javascripts/timeoutPopup.test.js b/tests/javascripts/timeoutPopup.test.js index a91e667ea..bd6c57803 100644 --- a/tests/javascripts/timeoutPopup.test.js +++ b/tests/javascripts/timeoutPopup.test.js @@ -1,4 +1,4 @@ -const timeoutPopup = require('../../app/assets/javascripts/timeoutPopup.js'); +const sessionTimerWrapper = require('../../app/assets/javascripts/timeoutPopup.js'); describe('Test popup process', () => { @@ -12,6 +12,7 @@ describe('Test popup process', () => { it('Test timers work', () => { jest.runAllTimers(); + sessionTimerWrapper(); }); }); From 25190117dc16d1517eb0ee0b59b53382e2595644 Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Wed, 27 Sep 2023 11:24:46 -0600 Subject: [PATCH 09/19] Remove attempts to export module --- app/assets/javascripts/timeoutPopup.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index 20a6e2eed..6ff292456 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -1,6 +1,5 @@ -const sessionTimerWrapper = function() { +(function(global) { "use strict"; - (function(global) { const sessionTimer = document.getElementById("sessionTimer"); setTimeout(function() { @@ -38,10 +37,8 @@ const sessionTimerWrapper = function() { sessionTimer.close(); } })(window); -}; -module.exports = sessionTimerWrapper; -(function(){ - sessionTimerWrapper(); -})(); + + + From d7e7a082cd7dad4659782ef1d193344f403ab1b6 Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Wed, 27 Sep 2023 11:37:43 -0600 Subject: [PATCH 10/19] Put timer back to 25 minute kickoff --- app/assets/javascripts/timeoutPopup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index 6ff292456..be64d7d22 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -19,7 +19,7 @@ logoutUser(); } }, 1000); - }, 60 * 1000); + }, 25 * 60 * 1000); function logoutUser() { window.location.href = '/sign-out'; From 8ab49f72ae9b332c001be5c823ca50eb5064985d Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Wed, 27 Sep 2023 11:47:52 -0600 Subject: [PATCH 11/19] Remove unused function in test --- tests/javascripts/timeoutPopup.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/javascripts/timeoutPopup.test.js b/tests/javascripts/timeoutPopup.test.js index bd6c57803..74373de5a 100644 --- a/tests/javascripts/timeoutPopup.test.js +++ b/tests/javascripts/timeoutPopup.test.js @@ -12,7 +12,6 @@ describe('Test popup process', () => { it('Test timers work', () => { jest.runAllTimers(); - sessionTimerWrapper(); }); }); From 57f2ee798be22a9ff2316e34d324b43ba63270cd Mon Sep 17 00:00:00 2001 From: Carlo Costino Date: Thu, 28 Sep 2023 15:31:08 -0400 Subject: [PATCH 12/19] Made several adjustments for session timer tests - Turned the timeoutPopup module into a proper module that is exported - Pulled out the setInterval inner function to be its own method - Adjusted the session timer tests to have additional setup and teardown pieces - Added a few tests There may be some additional adjustments needed, e.g., we may not have to expose all of the methods, but then we have to figure out how to make them accessible in the tests another way. h/t @A-Shumway42 for pairing with me to review all of these changes and approach! Signed-off-by: Carlo Costino --- app/assets/javascripts/timeoutPopup.js | 50 ++++++------ tests/javascripts/timeoutPopup.test.js | 103 ++++++++++++++++++++++--- 2 files changed, 119 insertions(+), 34 deletions(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index be64d7d22..f8b5b4cee 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -1,25 +1,23 @@ (function(global) { "use strict"; + const sessionTimer = document.getElementById("sessionTimer"); - setTimeout(function() { - var timeTillSessionEnd = new Date().getTime() + (5 * 60 * 1000); - var x = setInterval(function() { - var now = new Date().getTime(); - var difference = timeTillSessionEnd - now; - var minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)); - var seconds = Math.floor((difference % (1000 * 60)) / 1000); - document.getElementById("timeLeft").innerHTML = + minutes + "m " + seconds + "s"; - showTimer(); - document.getElementById("logOutTimer").addEventListener("click", logoutUser); - document.getElementById("extendSessionTimer").addEventListener("click", extendSession); - if (difference < 0) { - clearInterval(x); - closeTimer(); - logoutUser(); - } - }, 1000); - }, 25 * 60 * 1000); + function checkTimer(timeTillSessionEnd) { + var now = new Date().getTime(); + var difference = timeTillSessionEnd - now; + var minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)); + var seconds = Math.floor((difference % (1000 * 60)) / 1000); + document.getElementById("timeLeft").innerHTML = + minutes + "m " + seconds + "s"; + showTimer(); + document.getElementById("logOutTimer").addEventListener("click", logoutUser); + document.getElementById("extendSessionTimer").addEventListener("click", extendSession); + if (difference < 0) { + clearInterval(x); + closeTimer(); + logoutUser(); + } + } function logoutUser() { window.location.href = '/sign-out'; @@ -36,9 +34,17 @@ function closeTimer() { sessionTimer.close(); } - })(window); - - - + global.GOVUK.Modules.TimeoutPopup = function() { + setTimeout(function() { + var timeTillSessionEnd = new Date().getTime() + (5 * 60 * 1000); + var x = setInterval(checkTimer, 1000, timeTillSessionEnd); + }, 25 * 60 * 1000); + }; + global.GOVUK.Modules.TimeoutPopup.checkTimer = checkTimer; + global.GOVUK.Modules.TimeoutPopup.logoutUser = logoutUser; + global.GOVUK.Modules.TimeoutPopup.extendSession = extendSession; + global.GOVUK.Modules.TimeoutPopup.showTimer = showTimer; + global.GOVUK.Modules.TimeoutPopup.closeTimer = closeTimer; +})(window); diff --git a/tests/javascripts/timeoutPopup.test.js b/tests/javascripts/timeoutPopup.test.js index 74373de5a..46d88b7d2 100644 --- a/tests/javascripts/timeoutPopup.test.js +++ b/tests/javascripts/timeoutPopup.test.js @@ -1,18 +1,97 @@ -const sessionTimerWrapper = require('../../app/assets/javascripts/timeoutPopup.js'); +beforeAll(() => { + jest.useFakeTimers(); + jest.spyOn(global, 'setTimeout'); + + document.body.innerHTML = ` + +
+
+

+ Your session will end soon. + Please choose to extend your session or sign out. Your session will expire in 5 minutes or less. +

+
+

You have been inactive for too long. + Your session will expire in . +

+
+ +
+
+
+ ` + + const sessionTimerModule = require('../../app/assets/javascripts/timeoutPopup.js'); + window.GOVUK.modules.start(); +}); + +afterAll(() => { + jest.useRealTimers(); + document.body.innerHTML = ''; +}); -describe('Test popup process', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - it('Test timers work', () => { +describe('When an authenticated user', () => { + test('does whatever', () => { jest.runAllTimers(); }); -}); +}); + +describe('The session timer ', () => { + test('logoutUser method logs the user out', () => { + const logoutUserMethod = window.GOVUK.Modules.TimeoutPopup.logoutUser; + + expect(window.location.href).toEqual(expect.not.stringContaining('/sign-out')); + + logoutUserMethod(); + + expect(window.location.href).toEqual(expect.stringContaining('/sign-out')); + }); + + test('extendSession method reloads the page', () => { + const windowReload = jest.spyOn(window.location, 'reload'); + const extendSessionMethod = window.GOVUK.Modules.TimeoutPopup.extendSession; + + extendSessionMethod(); + + expect(windowReload).toHaveBeenCalled(); + }); + + test('showTimer method shows the session timer modal', () => { + const sessionTimer = document.getElementById("sessionTimer"); + sessionTimer.showModal = jest.fn(); + + const showTimerMock = jest.spyOn(sessionTimer, 'showModal'); + + window.GOVUK.Modules.TimeoutPopup.showTimer(); + + expect(showTimerMock).toHaveBeenCalled(); + }); + + test('closeTimer method closes the session timer modal', () => { + const sessionTimer = document.getElementById("sessionTimer"); + sessionTimer.close = jest.fn(); + + const closeTimerMock = jest.spyOn(sessionTimer, 'close'); + + window.GOVUK.Modules.TimeoutPopup.closeTimer(); + + expect(closeTimerMock).toHaveBeenCalled(); + }); +}); From 020dc871f77d7f8d29cfe8796f7f6c5b5de0be84 Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Mon, 2 Oct 2023 09:18:30 -0600 Subject: [PATCH 13/19] Added test for code coverage --- tests/javascripts/timeoutPopup.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/javascripts/timeoutPopup.test.js b/tests/javascripts/timeoutPopup.test.js index 46d88b7d2..30902c91a 100644 --- a/tests/javascripts/timeoutPopup.test.js +++ b/tests/javascripts/timeoutPopup.test.js @@ -94,4 +94,10 @@ describe('The session timer ', () => { expect(closeTimerMock).toHaveBeenCalled(); }); + + test('checkTimer is called', () => { + const checkTimerMock = jest.spyOn(window.GOVUK.Modules.TimeoutPopup, "checkTimer"); + window.GOVUK.Modules.TimeoutPopup.checkTimer(); + expect(checkTimerMock).toHaveBeenCalled(); + }); }); From 00db493305ad8383dd40a45a75f858568aa99be4 Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Mon, 2 Oct 2023 14:48:35 -0600 Subject: [PATCH 14/19] Clean up code/tests --- app/assets/javascripts/timeoutPopup.js | 22 +++++++++++++++------- tests/javascripts/timeoutPopup.test.js | 26 ++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index f8b5b4cee..75f3cfc38 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -1,7 +1,12 @@ +window.GOVUK = window.GOVUK || {}; +window.GOVUK.Modules = window.GOVUK.Modules || {}; +window.GOVUK.Modules.TimeoutPopup = window.GOVUK.Modules.TimeoutPopup || {}; + (function(global) { "use strict"; const sessionTimer = document.getElementById("sessionTimer"); + let intervalId = null; function checkTimer(timeTillSessionEnd) { var now = new Date().getTime(); @@ -13,7 +18,8 @@ document.getElementById("logOutTimer").addEventListener("click", logoutUser); document.getElementById("extendSessionTimer").addEventListener("click", extendSession); if (difference < 0) { - clearInterval(x); + clearInterval(intervalId); + intervalId = null; closeTimer(); logoutUser(); } @@ -35,12 +41,14 @@ sessionTimer.close(); } - global.GOVUK.Modules.TimeoutPopup = function() { - setTimeout(function() { - var timeTillSessionEnd = new Date().getTime() + (5 * 60 * 1000); - var x = setInterval(checkTimer, 1000, timeTillSessionEnd); - }, 25 * 60 * 1000); - }; + function setSessionTimer() { + var timeTillSessionEnd = new Date().getTime() + (5 * 60 * 1000); + intervalId = setInterval(checkTimer, 1000, timeTillSessionEnd); + } + + if (document.getElementById("timeLeft") !== null) { + setTimeout(setSessionTimer, 60 * 1000); + } global.GOVUK.Modules.TimeoutPopup.checkTimer = checkTimer; global.GOVUK.Modules.TimeoutPopup.logoutUser = logoutUser; diff --git a/tests/javascripts/timeoutPopup.test.js b/tests/javascripts/timeoutPopup.test.js index 30902c91a..98589a8a7 100644 --- a/tests/javascripts/timeoutPopup.test.js +++ b/tests/javascripts/timeoutPopup.test.js @@ -1,5 +1,4 @@ beforeAll(() => { - jest.useFakeTimers(); jest.spyOn(global, 'setTimeout'); document.body.innerHTML = ` @@ -40,13 +39,24 @@ beforeAll(() => { }); afterAll(() => { - jest.useRealTimers(); document.body.innerHTML = ''; }); -describe('When an authenticated user', () => { - test('does whatever', () => { +describe('When the session timer module is loaded', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useFakeTimers(); + }); + + test('everything initializes properly', () => { + const sessionTimer = document.getElementById("sessionTimer"); + sessionTimer.showModal = jest.fn(); + sessionTimer.close = jest.fn(); + jest.runAllTimers(); }); @@ -54,6 +64,14 @@ describe('When an authenticated user', () => { describe('The session timer ', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useFakeTimers(); + }); + test('logoutUser method logs the user out', () => { const logoutUserMethod = window.GOVUK.Modules.TimeoutPopup.logoutUser; From 93ac55ce351f20f149442e940f3494b084ff5b8d Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Mon, 2 Oct 2023 15:20:23 -0600 Subject: [PATCH 15/19] Separate logout by user or inactivity --- app/assets/javascripts/timeoutPopup.js | 13 +++++++++---- tests/javascripts/timeoutPopup.test.js | 16 +++++++++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index 75f3cfc38..c3831d96a 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -15,17 +15,21 @@ window.GOVUK.Modules.TimeoutPopup = window.GOVUK.Modules.TimeoutPopup || {}; var seconds = Math.floor((difference % (1000 * 60)) / 1000); document.getElementById("timeLeft").innerHTML = + minutes + "m " + seconds + "s"; showTimer(); - document.getElementById("logOutTimer").addEventListener("click", logoutUser); + document.getElementById("logOutTimer").addEventListener("click", signoutUser); document.getElementById("extendSessionTimer").addEventListener("click", extendSession); if (difference < 0) { clearInterval(intervalId); intervalId = null; closeTimer(); - logoutUser(); + expireUserSession(); } } - function logoutUser() { + function expireUserSession() { + window.location.href = '/sign-out' + '?next=' + window.location.pathname; + } + + function signoutUser() { window.location.href = '/sign-out'; } @@ -51,7 +55,8 @@ window.GOVUK.Modules.TimeoutPopup = window.GOVUK.Modules.TimeoutPopup || {}; } global.GOVUK.Modules.TimeoutPopup.checkTimer = checkTimer; - global.GOVUK.Modules.TimeoutPopup.logoutUser = logoutUser; + global.GOVUK.Modules.TimeoutPopup.expireUserSession = expireUserSession; + global.GOVUK.Modules.TimeoutPopup.signoutUser = signoutUser; global.GOVUK.Modules.TimeoutPopup.extendSession = extendSession; global.GOVUK.Modules.TimeoutPopup.showTimer = showTimer; global.GOVUK.Modules.TimeoutPopup.closeTimer = closeTimer; diff --git a/tests/javascripts/timeoutPopup.test.js b/tests/javascripts/timeoutPopup.test.js index 98589a8a7..afc8a2687 100644 --- a/tests/javascripts/timeoutPopup.test.js +++ b/tests/javascripts/timeoutPopup.test.js @@ -72,16 +72,26 @@ describe('The session timer ', () => { jest.useFakeTimers(); }); - test('logoutUser method logs the user out', () => { - const logoutUserMethod = window.GOVUK.Modules.TimeoutPopup.logoutUser; + test('signoutUser method logs the user out', () => { + const signoutUserMethod = window.GOVUK.Modules.TimeoutPopup.signoutUser; expect(window.location.href).toEqual(expect.not.stringContaining('/sign-out')); - logoutUserMethod(); + signoutUserMethod(); expect(window.location.href).toEqual(expect.stringContaining('/sign-out')); }); + test('expireUserSession method logs the user out with next query parameter', () => { + const expireUserSessionMethod = window.GOVUK.Modules.TimeoutPopup.expireUserSession; + + expect(window.location.href).toEqual(expect.not.stringContaining('/sign-out?next=')); + + expireUserSessionMethod(); + + expect(window.location.href).toEqual(expect.stringContaining('/sign-out?next=')); + }); + test('extendSession method reloads the page', () => { const windowReload = jest.spyOn(window.location, 'reload'); const extendSessionMethod = window.GOVUK.Modules.TimeoutPopup.extendSession; From efce22b1712b39400569a3fb142d315ae63743df Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Mon, 2 Oct 2023 15:21:00 -0600 Subject: [PATCH 16/19] Put timer back to 25 minutes --- app/assets/javascripts/timeoutPopup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index c3831d96a..a57641b5e 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -51,7 +51,7 @@ window.GOVUK.Modules.TimeoutPopup = window.GOVUK.Modules.TimeoutPopup || {}; } if (document.getElementById("timeLeft") !== null) { - setTimeout(setSessionTimer, 60 * 1000); + setTimeout(setSessionTimer, 25 * 60 * 1000); } global.GOVUK.Modules.TimeoutPopup.checkTimer = checkTimer; From d4d36087d4583d75a0291f298c422cb966e636a3 Mon Sep 17 00:00:00 2001 From: Carlo Costino Date: Wed, 11 Oct 2023 15:48:30 -0400 Subject: [PATCH 17/19] Swapped resetting window.location.href to using window.location.assign Signed-off-by: Carlo Costino --- app/assets/javascripts/timeoutPopup.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index a57641b5e..f4618419c 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -26,11 +26,11 @@ window.GOVUK.Modules.TimeoutPopup = window.GOVUK.Modules.TimeoutPopup || {}; } function expireUserSession() { - window.location.href = '/sign-out' + '?next=' + window.location.pathname; + window.location.assign('/sign-out?next=' + window.location.pathname); } function signoutUser() { - window.location.href = '/sign-out'; + window.location.assign('/sign-out'); } function extendSession() { From 4fff35590bd542770ca8597f047962e8fc05da8e Mon Sep 17 00:00:00 2001 From: Carlo Costino Date: Wed, 11 Oct 2023 17:10:53 -0400 Subject: [PATCH 18/19] Attempt at fixing sign-out link after session expires Note that this still does not seem to work; needs more investigation, but not a showstopper for the feature going out! Signed-off-by: Carlo Costino --- app/assets/javascripts/timeoutPopup.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index f4618419c..0cfe2e12a 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -26,7 +26,8 @@ window.GOVUK.Modules.TimeoutPopup = window.GOVUK.Modules.TimeoutPopup || {}; } function expireUserSession() { - window.location.assign('/sign-out?next=' + window.location.pathname); + var signOutLink = '/sign-out?next=' + window.location.pathname; + window.location.assign(signOutLink); } function signoutUser() { From a46a21a5190596729cff2942cb7e672583dc274d Mon Sep 17 00:00:00 2001 From: Carlo Costino Date: Thu, 12 Oct 2023 10:00:30 -0400 Subject: [PATCH 19/19] Revert recent changes for window location updates There are a few other things at play that will require more investigation at a future date: - Why window.location.assign() or history.pushState() do not work - If any other config or polyfills are needed with JSDOM Signed-off-by: Carlo Costino --- app/assets/javascripts/timeoutPopup.js | 5 +++-- tests/javascripts/jest.config.js | 2 +- tests/javascripts/support/polyfills.js | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/timeoutPopup.js b/app/assets/javascripts/timeoutPopup.js index 0cfe2e12a..ed33cf903 100644 --- a/app/assets/javascripts/timeoutPopup.js +++ b/app/assets/javascripts/timeoutPopup.js @@ -27,11 +27,12 @@ window.GOVUK.Modules.TimeoutPopup = window.GOVUK.Modules.TimeoutPopup || {}; function expireUserSession() { var signOutLink = '/sign-out?next=' + window.location.pathname; - window.location.assign(signOutLink); + window.location.href = signOutLink; + } function signoutUser() { - window.location.assign('/sign-out'); + window.location.href = '/sign-out'; } function extendSession() { diff --git a/tests/javascripts/jest.config.js b/tests/javascripts/jest.config.js index 6b649f57f..0e3dc0128 100644 --- a/tests/javascripts/jest.config.js +++ b/tests/javascripts/jest.config.js @@ -12,6 +12,6 @@ module.exports = { setupFiles: ['./support/setup.js'], testEnvironment: 'jsdom', testEnvironmentOptions: { - url: 'https://www.notifications.service.gov.uk', + url: 'https://beta.notify.gov', }, }; diff --git a/tests/javascripts/support/polyfills.js b/tests/javascripts/support/polyfills.js index 8e2170704..1549cbf8f 100644 --- a/tests/javascripts/support/polyfills.js +++ b/tests/javascripts/support/polyfills.js @@ -2,9 +2,9 @@ let _location = { reload: jest.fn(), - hostname: "www.notifications.service.gov.uk", + hostname: "beta.notify.gov", assign: jest.fn(), - href: "https://www.notifications.service.gov.uk", + href: "https://beta.notify.gov", } // JSDOM provides a read-only window.location, which does not allow for