Add auto-logout function.

Add an auto logout function that makes a call to the /obp/v5.1.0/ui/suggested-session-timeout to find the suggested session timeout time. Failing this, we default to a timeout of 300 seconds. A timer can be seen next to the logout button which shows the time remaining before logout.
This commit is contained in:
nemo 2023-11-20 22:52:20 +00:00
parent eec9ed8022
commit d73c16efa2
4 changed files with 169 additions and 16 deletions

View File

@ -89,20 +89,6 @@ MIDDLEWARE = [
# 'django.middleware.cache.FetchFromCacheMiddleware',
]
# Content Security Policy - External Urls for scripts, styles, and images should be included here
#TODO these outside scripts should really just be loaded when we run "manage.py collectstatic"
# Or the whole static folder could be uploaded to github, this prevents API manager breaking when
# we run it on a server that may not connect to these sites
# Inline styles loaded by jsoneditor.min.js have been allowed by adding their hashes to CSP_STYLE_SRC
CSP_IMG_SRC = ("'self' data:", 'https://static.openbankproject.com')
CSP_STYLE_SRC = ("'self' 'sha256-z2a+NIknPDE7NIEqE1lfrnG39eWOhJXWsXHYGGNb5oU=' 'sha256-Dn0vMZLidJplZ4cSlBMg/F5aa7Vol9dBMHzBF4fGEtk=' 'sha256-sA0hymKbXmMTpnYi15KmDw4u6uRdLXqHyoYIaORFtjU=' 'sha256-jUuiwf3ITuJc/jfynxWHLwTZifHIlhddD8NPmmVBztk=' 'sha256-RqzjtXRBqP4i+ruV3IRuHFq6eGIACITqGbu05VSVXsI='", 'https://cdnjs.cloudflare.com', )
CSP_SCRIPT_SRC = ("'self' 'sha256-4Hr8ttnXaUA4A6o0hGi3NUGNP2Is3Ep0W+rvm+W7BAk=' 'sha256-GgQWQ4Ejk4g9XpAZJ4YxIgZDgp7CdQCmqjMOMh9hD2g=' 'sha256-05NIAwVBHkAzKcXTfkYqTnBPtkpX+AmQvM/raql3qo0='", 'http://code.jquery.com', 'https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/', 'https://cdnjs.cloudflare.com')
CSP_FONT_SRC = ("'self'", 'http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/')
CSP_FRAME_ANCESTORS = ("'self'")
CSP_FORM_ACTION = ("'self'")
#cache the view page, we set 60s = 1m,
# CACHE_MIDDLEWARE_SECONDS = 60
@ -353,3 +339,19 @@ if not OAUTH_CONSUMER_KEY:
raise ImproperlyConfigured('Missing settings for OAUTH_CONSUMER_KEY')
if not OAUTH_CONSUMER_SECRET:
raise ImproperlyConfigured('Missing settings for OAUTH_CONSUMER_SECRET')
#This has been moved to after API_HOST is imported so that connections to the API are allowed by the csp
# Content Security Policy - External Urls for scripts, styles, and images should be included here
#TODO these outside scripts should really just be loaded when we run "manage.py collectstatic"
# Or the whole static folder could be uploaded to github, this prevents API manager breaking when
# we run it on a server that may not connect to these sites
# Inline styles loaded by jsoneditor.min.js have been allowed by adding their hashes to CSP_STYLE_SRC
CSP_IMG_SRC = ("'self' data:", 'https://static.openbankproject.com')
CSP_STYLE_SRC = ("'self' 'sha256-z2a+NIknPDE7NIEqE1lfrnG39eWOhJXWsXHYGGNb5oU=' 'sha256-Dn0vMZLidJplZ4cSlBMg/F5aa7Vol9dBMHzBF4fGEtk=' 'sha256-sA0hymKbXmMTpnYi15KmDw4u6uRdLXqHyoYIaORFtjU=' 'sha256-jUuiwf3ITuJc/jfynxWHLwTZifHIlhddD8NPmmVBztk=' 'sha256-RqzjtXRBqP4i+ruV3IRuHFq6eGIACITqGbu05VSVXsI='", 'https://cdnjs.cloudflare.com', )
CSP_SCRIPT_SRC = ("'self' 'sha256-4Hr8ttnXaUA4A6o0hGi3NUGNP2Is3Ep0W+rvm+W7BAk=' 'sha256-GgQWQ4Ejk4g9XpAZJ4YxIgZDgp7CdQCmqjMOMh9hD2g=' 'sha256-05NIAwVBHkAzKcXTfkYqTnBPtkpX+AmQvM/raql3qo0='", 'http://code.jquery.com', 'https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/', 'https://cdnjs.cloudflare.com')
CSP_FONT_SRC = ("'self'", 'http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/')
CSP_FRAME_ANCESTORS = ("'self'")
CSP_FORM_ACTION = ("'self'")
CSP_CONNECT_SRC = (API_HOST)

View File

@ -0,0 +1,39 @@
function addSeconds(date, seconds) {
let oldDate = date;
let addSeconds = seconds;
var newSeconds = oldDate + addSeconds;
//console.log(addSeconds);
date.setSeconds(date.getSeconds() + seconds);
return date;
}
export function showCountdownTimer() {
// Get current date and time
var now = new Date().getTime();
let distance = countDownDate - now;
// Output the result in an element with id="countdown-timer-span"
let elementId = ("countdown-timer-span");
document.getElementById(elementId).innerHTML = "in " + Math.floor(distance / 1000) + "s";
// If the count down is over release resources
if (distance < 0) {
destroyCountdownTimer();
}
}
// Set the date we're counting down to
let countDownDate = addSeconds(new Date(), 5);
let showTimerInterval = null;
export function destroyCountdownTimer() {
clearInterval(showTimerInterval);
}
export function resetCountdownTimer(seconds) {
destroyCountdownTimer(); // Destroy previous timer if any
countDownDate = addSeconds(new Date(), seconds); // Set the date we're counting down to
showTimerInterval = setInterval(showCountdownTimer, 1000); // Update the count down every 1 second
}

View File

@ -0,0 +1,107 @@
import * as countdownTimer from './inactivity-timer.js'
// holds the idle duration in ms (current value = 301 seconds)
var timeoutIntervalInMillis = 5 * 60 * 1000 + 1000;
// holds the timeout variables for easy destruction and reconstruction of the setTimeout hooks
var timeHook = null;
function initializeTimeHook() {
// this method has the purpose of creating our timehooks and scheduling the call to our logout function when the idle time has been reached
if (timeHook == null) {
timeHook = setTimeout( function () { destroyTimeHook(); logout()}.bind(this), timeoutIntervalInMillis);
}
}
function destroyTimeHook() {
// this method has the sole purpose of destroying any time hooks we might have created
clearTimeout(timeHook);
timeHook = null;
}
function resetTimeHook(event) {
// this method replaces the current time hook with a new time hook
destroyTimeHook();
initializeTimeHook();
countdownTimer.resetCountdownTimer(timeoutIntervalInMillis / 1000);
// show event type, element and coordinates of the click
// console.log(event.type + " at " + event.currentTarget);
// console.log("Coordinates: " + event.clientX + ":" + event.clientY);
console.log("Reset inactivity of a user");
}
function setupListeners() {
// here we setup the event listener for the mouse click operation
document.addEventListener("click", resetTimeHook);
document.addEventListener("mousemove", resetTimeHook);
document.addEventListener("mousedown", resetTimeHook);
document.addEventListener("keypress", resetTimeHook);
document.addEventListener("touchmove", resetTimeHook);
console.log("Listeners for user inactivity activated");
}
function destroyListeners() {
// here we destroy event listeners for the mouse click operation
document.removeEventListener("click", resetTimeHook);
document.removeEventListener("mousemove", resetTimeHook);
document.removeEventListener("mousedown", resetTimeHook);
document.removeEventListener("keypress", resetTimeHook);
document.removeEventListener("touchmove", resetTimeHook);
console.log("Listeners for user inactivity deactivated");
}
function logout() {
destroyListeners();
countdownTimer.destroyCountdownTimer();
console.log("Logging you out due to inactivity..");
const logoffButton = document.getElementById("logout");
logoffButton.click();
}
async function makeObpApiCall() {
let timeoutInSeconds;
try {
let obpApiHost = document.getElementById("api_home_link");
if(obpApiHost) {
obpApiHost = obpApiHost.href.split("?")[0];
}
console.log(obpApiHost);
const response = await fetch(`${obpApiHost}/obp/v5.1.0/ui/suggested-session-timeout`);
const json = await response.json();
if(json.timeout_in_seconds) {
timeoutInSeconds = json.timeout_in_seconds;
console.log(`Suggested value ${timeoutInSeconds} is used`);
} else {
timeoutInSeconds = 5 * 60 + 1; // Set default value to 301 seconds
console.log(`Default value ${timeoutInSeconds} is used`);
}
} catch (e) {
console.error(e);
timeoutInSeconds = 5 * 60 + 1; // Set default value to 301 seconds, even if the session timeout endpoint is not reachable for whatever reason
console.log(`Default value ${timeoutInSeconds} is used`);
}
return timeoutInSeconds;
}
async function getSuggestedSessionTimeout() {
if(!sessionStorage.getItem("suggested-session-timeout-in-seconds")) {
let timeoutInSeconds = await makeObpApiCall();
sessionStorage.setItem("suggested-session-timeout-in-seconds", timeoutInSeconds);
}
return sessionStorage.getItem("suggested-session-timeout-in-seconds") * 1000 + 1000; // We need timeout in millis
}
// self executing function to trigger the operation on page load
(async function () {
timeoutIntervalInMillis = await getSuggestedSessionTimeout(); // Try to get suggested value
const logoffButton = document.getElementById("countdown-timer-span");
if(logoffButton) {
// to prevent any lingering timeout handlers preventing memory leaks
destroyTimeHook();
// setup a fresh time hook
initializeTimeHook();
// setup initial event listeners
setupListeners();
// Reset countdown timer
countdownTimer.resetCountdownTimer(timeoutIntervalInMillis / 1000);
}
})();

View File

@ -31,7 +31,7 @@
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li> <a class="obp-home-button" href="{% url 'home' %}"><img src="{{ logo_url }}" alt="brand"></a></li>
<li><a href="{{ API_PORTAL }}">{% trans "Home" %}</a></li>
<li><a id="api_home_link" href="{{ API_PORTAL }}">{% trans "Home" %}</a></li>
{% url "consumers-index" as consumers_index_url %}
<li {% if consumers_index_url in request.path %} class="active" {% endif %}><a href="{{ consumers_index_url }}">{% trans "Consumers" %}</a></li>
{% url "entitlementrequests-index" as entitlementrequests_index_url %}
@ -104,7 +104,11 @@
{% endif %}
<li>
{% if user.is_authenticated %}
<p class="navbar-right button-select"><span id="navbar-login-username">{{API_USERNAME}}</span>&nbsp;&nbsp;<a href="/logout" class="btn btn-default">{% trans "Logout" %} </a></p>
<p class="navbar-right button-select">
<span id="navbar-login-username">{{API_USERNAME}}</span>&nbsp;&nbsp;
<a id="logout" href="/logout" class="btn btn-default">{% trans "Logout" %}</a>
<span class="badge badge-secondary" id="countdown-timer-span"></span>
</p>
{% endif %}
</li>
<li class="language-select language_underline_format"><a>Language
@ -146,6 +150,7 @@
<script type="text/javascript" src="{% static 'js/jquery.tablesorter.min.js' %}"></script>
<script src="{% static 'js/base.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jsoneditor.min.js' %}"></script>
<script type="module" defer src="{% static 'js/inactivity.js' %}"></script>
{% block extrajs %}{% endblock extrajs %}
</body>