mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-05-30 19:10:42 -04:00
@@ -321,7 +321,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:
|
||||
|
||||
65
app/assets/javascripts/timeoutPopup.js
Normal file
65
app/assets/javascripts/timeoutPopup.js
Normal file
@@ -0,0 +1,65 @@
|
||||
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();
|
||||
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", signoutUser);
|
||||
document.getElementById("extendSessionTimer").addEventListener("click", extendSession);
|
||||
if (difference < 0) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
closeTimer();
|
||||
expireUserSession();
|
||||
}
|
||||
}
|
||||
|
||||
function expireUserSession() {
|
||||
var signOutLink = '/sign-out?next=' + window.location.pathname;
|
||||
window.location.href = signOutLink;
|
||||
|
||||
}
|
||||
|
||||
function signoutUser() {
|
||||
window.location.href = '/sign-out';
|
||||
}
|
||||
|
||||
function extendSession() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function showTimer() {
|
||||
sessionTimer.showModal();
|
||||
}
|
||||
|
||||
function closeTimer() {
|
||||
sessionTimer.close();
|
||||
}
|
||||
|
||||
function setSessionTimer() {
|
||||
var timeTillSessionEnd = new Date().getTime() + (5 * 60 * 1000);
|
||||
intervalId = setInterval(checkTimer, 1000, timeTillSessionEnd);
|
||||
}
|
||||
|
||||
if (document.getElementById("timeLeft") !== null) {
|
||||
setTimeout(setSessionTimer, 25 * 60 * 1000);
|
||||
}
|
||||
|
||||
global.GOVUK.Modules.TimeoutPopup.checkTimer = checkTimer;
|
||||
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;
|
||||
})(window);
|
||||
@@ -54,7 +54,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
|
||||
|
||||
@@ -137,7 +137,6 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
|
||||
|
||||
{% if current_service and current_service.research_mode %}
|
||||
{% set meta_suffix = 'Built by the <a href="https://www.gsa.gov/about-us/organization/federal-acquisition-service/technology-transformation-services/tts-solutions" class="usa-link">Technology Transformation Services</a><span id="research-mode" class="research-mode">research mode</span>' %}
|
||||
@@ -216,8 +215,45 @@
|
||||
"html": meta_suffix
|
||||
}
|
||||
}) }}
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
{% block sessionUserWarning %}
|
||||
<dialog class="usa-modal" id="sessionTimer" aria-labelledby="sessionTimerHeading" aria-describedby="timerWarning">
|
||||
<div class="usa-modal__content">
|
||||
<div class="usa-modal__main">
|
||||
<h2 class="usa-modal__heading" id="sessionTimerHeading">
|
||||
Your session will end soon.
|
||||
<span class="usa-sr-only">Please choose to extend your session or sign out. Your session will expire in 5 minutes or less.</span>
|
||||
</h2>
|
||||
<div class="usa-prose">
|
||||
<p>You have been inactive for too long.
|
||||
Your session will expire in <span id="timeLeft" role="timer"></span>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="usa-modal__footer">
|
||||
<ul class="usa-button-group">
|
||||
<li class="usa-button-group__item">
|
||||
<button type="button" class="usa-button" id="extendSessionTimer" data-close-modal>
|
||||
Extend Session
|
||||
</button>
|
||||
</li>
|
||||
<li class="usa-button-group__item">
|
||||
<button type="button" class="usa-button usa-button--unstyled padding-105 text-center" id="logOutTimer"
|
||||
data-close-modal>
|
||||
Sign out
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block bodyEnd %}
|
||||
{% block extra_javascripts %}
|
||||
{% endblock %}
|
||||
@@ -225,4 +261,7 @@
|
||||
<script type="text/javascript" src="{{ asset_url('javascripts/all.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ asset_url('js/uswds.min.js') }}"></script>
|
||||
<!--<![endif]-->
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -125,6 +125,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())
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
131
tests/javascripts/timeoutPopup.test.js
Normal file
131
tests/javascripts/timeoutPopup.test.js
Normal file
@@ -0,0 +1,131 @@
|
||||
beforeAll(() => {
|
||||
jest.spyOn(global, 'setTimeout');
|
||||
|
||||
document.body.innerHTML = `
|
||||
<dialog class="usa-modal" id="sessionTimer" aria-labelledby="sessionTimerHeading" aria-describedby="timerWarning">
|
||||
<div class="usa-modal__content">
|
||||
<div class="usa-modal__main">
|
||||
<h2 class="usa-modal__heading" id="sessionTimerHeading">
|
||||
Your session will end soon.
|
||||
<span class="usa-sr-only">Please choose to extend your session or sign out. Your session will expire in 5 minutes or less.</span>
|
||||
</h2>
|
||||
<div class="usa-prose">
|
||||
<p>You have been inactive for too long.
|
||||
Your session will expire in <span id="timeLeft" role="timer"></span>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="usa-modal__footer">
|
||||
<ul class="usa-button-group">
|
||||
<li class="usa-button-group__item">
|
||||
<button type="button" class="usa-button" id="extendSessionTimer" data-close-modal>
|
||||
Extend Session
|
||||
</button>
|
||||
</li>
|
||||
<li class="usa-button-group__item">
|
||||
<button type="button" class="usa-button usa-button--unstyled padding-105 text-center" id="logOutTimer"
|
||||
data-close-modal>
|
||||
Sign out
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
`
|
||||
|
||||
const sessionTimerModule = require('../../app/assets/javascripts/timeoutPopup.js');
|
||||
window.GOVUK.modules.start();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('The session timer ', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
test('signoutUser method logs the user out', () => {
|
||||
const signoutUserMethod = window.GOVUK.Modules.TimeoutPopup.signoutUser;
|
||||
|
||||
expect(window.location.href).toEqual(expect.not.stringContaining('/sign-out'));
|
||||
|
||||
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;
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
test('checkTimer is called', () => {
|
||||
const checkTimerMock = jest.spyOn(window.GOVUK.Modules.TimeoutPopup, "checkTimer");
|
||||
window.GOVUK.Modules.TimeoutPopup.checkTimer();
|
||||
expect(checkTimerMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user