Skip to content

Commit

Permalink
Merge pull request #341 from nemozak1/develop
Browse files Browse the repository at this point in the history
Add auto-logout function.
  • Loading branch information
simonredfern authored Nov 21, 2023
2 parents effc266 + d73c16e commit b258431
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 16 deletions.
30 changes: 16 additions & 14 deletions apimanager/apimanager/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,6 @@
# '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

Expand Down Expand Up @@ -353,3 +339,19 @@
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)
39 changes: 39 additions & 0 deletions apimanager/base/static/js/inactivity-timer.js
Original file line number Diff line number Diff line change
@@ -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
}
107 changes: 107 additions & 0 deletions apimanager/base/static/js/inactivity.js
Original file line number Diff line number Diff line change
@@ -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);
}
})();
9 changes: 7 additions & 2 deletions apimanager/base/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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>

Expand Down

0 comments on commit b258431

Please sign in to comment.