mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-08 08:28:15 -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
|
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']`
|
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
|
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:
|
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
|
EMAIL_EXPIRY_SECONDS = 3600 # 1 hour
|
||||||
INVITATION_EXPIRY_SECONDS = 3600 * 24 * 2 # 2 days - also set on api
|
INVITATION_EXPIRY_SECONDS = 3600 * 24 * 2 # 2 days - also set on api
|
||||||
EMAIL_2FA_EXPIRY_SECONDS = 1800 # 30 Minutes
|
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
|
SEND_FILE_MAX_AGE_DEFAULT = 365 * 24 * 60 * 60 # 1 year
|
||||||
REPLY_TO_EMAIL_ADDRESS_VALIDATION_TIMEOUT = 45
|
REPLY_TO_EMAIL_ADDRESS_VALIDATION_TIMEOUT = 45
|
||||||
ACTIVITY_STATS_LIMIT_DAYS = 7
|
ACTIVITY_STATS_LIMIT_DAYS = 7
|
||||||
|
|||||||
@@ -137,7 +137,6 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block footer %}
|
{% block footer %}
|
||||||
|
|
||||||
|
|
||||||
{% if current_service and current_service.research_mode %}
|
{% 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>' %}
|
{% 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
|
"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 %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block bodyEnd %}
|
{% block bodyEnd %}
|
||||||
{% block extra_javascripts %}
|
{% block extra_javascripts %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -225,4 +261,7 @@
|
|||||||
<script type="text/javascript" src="{{ asset_url('javascripts/all.js') }}"></script>
|
<script type="text/javascript" src="{{ asset_url('javascripts/all.js') }}"></script>
|
||||||
<script type="text/javascript" src="{{ asset_url('js/uswds.min.js') }}"></script>
|
<script type="text/javascript" src="{{ asset_url('js/uswds.min.js') }}"></script>
|
||||||
<!--<![endif]-->
|
<!--<![endif]-->
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ const javascripts = () => {
|
|||||||
paths.src + 'javascripts/updateStatus.js',
|
paths.src + 'javascripts/updateStatus.js',
|
||||||
paths.src + 'javascripts/errorBanner.js',
|
paths.src + 'javascripts/errorBanner.js',
|
||||||
paths.src + 'javascripts/homepage.js',
|
paths.src + 'javascripts/homepage.js',
|
||||||
|
paths.src + 'javascripts/timeoutPopup.js',
|
||||||
paths.src + 'javascripts/main.js',
|
paths.src + 'javascripts/main.js',
|
||||||
])
|
])
|
||||||
.pipe(plugins.prettyerror())
|
.pipe(plugins.prettyerror())
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ module.exports = {
|
|||||||
setupFiles: ['./support/setup.js'],
|
setupFiles: ['./support/setup.js'],
|
||||||
testEnvironment: 'jsdom',
|
testEnvironment: 'jsdom',
|
||||||
testEnvironmentOptions: {
|
testEnvironmentOptions: {
|
||||||
url: 'https://www.notifications.service.gov.uk',
|
url: 'https://beta.notify.gov',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
let _location = {
|
let _location = {
|
||||||
reload: jest.fn(),
|
reload: jest.fn(),
|
||||||
hostname: "www.notifications.service.gov.uk",
|
hostname: "beta.notify.gov",
|
||||||
assign: jest.fn(),
|
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
|
// 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