Skip to content

Commit

Permalink
Session timeout dialog
Browse files Browse the repository at this point in the history
closes #5616
  • Loading branch information
tbaddade committed Jun 22, 2023
1 parent 25b6602 commit 2136fcb
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 1 deletion.

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions redaxo/src/addons/be_style/plugins/redaxo/scss/_modal.scss
@@ -1,3 +1,6 @@
.modal-backdrop {
display: none !important;
}
.rex-session-timeout-dialog + .modal-backdrop {
display: block !important;
}
232 changes: 232 additions & 0 deletions redaxo/src/core/assets/session-timeout.js
@@ -0,0 +1,232 @@
/*
* bootstrap-session-timeout
* www.orangehilldev.com
*
* Copyright (c) 2014 Vedran Opacic
* Licensed under the MIT license.
*/

if ('login' !== rex.page && rex.session_keep_alive) {
(function ($) {
/*jshint multistr: true */
'use strict';
$.sessionTimeout = function (options) {
let defaults = {
title: rex.i18n.session_timeout_title,
message: rex.i18n.session_timeout_message,
logoutButton: rex.i18n.session_timeout_logout_label,
keepAliveButton: rex.i18n.session_timeout_refresh_label,
keepAliveUrl: 'index.php?page=credits',
ajaxType: 'POST',
ajaxData: '',
logoutUrl: rex.session_logout_url,

keepAliveSession: rex.session_keep_alive * 1000, // stop request after x seconds - see config.yml
keepAliveInterval: 5 * 1000, // * 60 * 1000, // 5 minutes
keepAlive: true,

onStart: false,
onWarning: false,
onLogout: false,
countdownMessage: false,
countdownBar: true,
countdownSmart: true,

sessionWarningAfter: (rex.session_keep_alive + rex.session_duration - rex.session_warning) * 1000, // - see config.yml
sessionLogoutAfter: (rex.session_keep_alive + rex.session_duration) * 1000, // - see config.yml

sessionMaxOverallDurationWarningAfter: (rex.session_starttime + rex.session_max_overall_duration - 2 * 60) * 1000, // - see config.yml
};

let opt = defaults,
timer,
countdown = {};

// Extend user-set options over defaults
if (options) {
opt = $.extend(defaults, options);
}
console.log('keepAliveSession:' + opt.keepAliveSession);
console.log('sessionWarningAfter:' + opt.sessionWarningAfter);
console.log('sessionLogoutAfter:' + opt.sessionLogoutAfter);

// Some error handling if options are miss-configured
if (opt.sessionWarningAfter >= opt.sessionLogoutAfter) {
console.error('session-timeout.js is miss-configured. Option "sessionLogoutAfter" must be equal or greater than "sessionWarningAfter".');
return false;
}

// Unless user set his own callback function, prepare bootstrap modal elements and events
if (typeof opt.onWarning !== 'function') {
// If opt.countdownMessage is defined add a coundown timer message to the modal dialog
let countdownMessage = opt.countdownMessage ?
'<p>' + opt.countdownMessage.replace(/{timer}/g, '<span class="countdown-holder"></span>') + '</p>' : '';

let coundownBarHtml = opt.countdownBar ?
'<div class="progress"> \
<div class="progress-bar progress-bar-striped countdown-bar active" role="progressbar" style="min-width: 15px; width: 100%;"> \
<span class="countdown-holder"></span> \
</div> \
</div>' : '';
console.log('Create dialog');
// Create timeout warning dialog
$('body').append(
'<div class="modal fade rex-session-timeout-dialog"> \
<div class="modal-dialog"> \
<div class="modal-content"> \
<div class="modal-header"> \
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> \
<h4 class="modal-title">' + opt.title + '</h4> \
</div> \
<div class="modal-body"> \
<p>' + opt.message + '</p> \
' + countdownMessage + ' \
' + coundownBarHtml + ' \
</div> \
<div class="modal-footer"> \
<button type="button" class="rex-session-timeout-dialog-logout btn btn-default">' + opt.logoutButton + '</button> \
<button type="button" class="rex-session-timeout-dialog-keepalive btn btn-primary" data-dismiss="modal">' + opt.keepAliveButton + '</button> \
</div> \
</div> \
</div> \
</div>'
);

// "Logout" button click
$('.rex-session-timeout-dialog-logout').on('click', function () {
window.location = opt.logoutUrl;
});
// "Stay Connected" button click
$('.rex-session-timeout-dialog').on('hide.bs.modal', function () {
$.ajax(opt.keepAliveUrl, {
cache: false
});
// Restart session timer
console.log('Hide Dialog');
startSessionTimer();
});
}

// Keeps the server side connection live, by pingin url set in keepAliveUrl option.
// KeepAlivePinged is a helper var to ensure the functionality of the keepAliveInterval option
let keepAlivePinged = false;

function keepAlive() {
let keepAliveInterval = setInterval(function () {
$.ajax(opt.keepAliveUrl, {
cache: false
});
console.log('Ping keep alive url');
}, opt.keepAliveInterval);
setTimeout(function () {
clearInterval(keepAliveInterval);
}, opt.keepAliveSession);
}

function startSessionTimer() {
// Clear session timer
clearTimeout(timer);
if (opt.countdownMessage || opt.countdownBar) {
startCountdownTimer('session', true);
}

if (typeof opt.onStart === 'function') {
opt.onStart(opt);
}

// If keepAlive option is set to "true", ping the "keepAliveUrl" url
if (opt.keepAlive) {
keepAlive();
}

// Set session timer
timer = setTimeout(function () {
// Check for onWarning callback function and if there is none, launch dialog
if (typeof opt.onWarning !== 'function') {
console.log('Show Dialog');
$('.rex-session-timeout-dialog').modal('show');
} else {
opt.onWarning(opt);
}
// Start dialog timer
startDialogTimer();
}, opt.sessionWarningAfter);
}

function startDialogTimer() {
// Clear session timer
clearTimeout(timer);
if (!$('.rex-session-timeout-dialog').hasClass('in') && (opt.countdownMessage || opt.countdownBar)) {
// If warning dialog is not already open and either opt.countdownMessage
// or opt.countdownBar are set start countdown
startCountdownTimer('dialog', true);
}
// Set dialog timer
timer = setTimeout(function () {
// Check for onLogout callback function and if there is none, launch redirect
if (typeof opt.onLogout !== 'function') {
console.log('Logout');
// window.location = opt.logoutUrl;
} else {
opt.onLogout(opt);
}
}, (opt.sessionLogoutAfter - opt.sessionWarningAfter));
}

function startCountdownTimer(type, reset) {
// Clear countdown timer
clearTimeout(countdown.timer);

if (type === 'dialog' && reset) {
// If triggered by startDialogTimer start warning countdown
countdown.timeLeft = Math.floor((opt.sessionLogoutAfter - opt.sessionWarningAfter) / 1000);
} else if (type === 'session' && reset) {
// If triggered by startSessionTimer start full countdown
// (this is needed if user doesn't close the warning dialog)
countdown.timeLeft = Math.floor(opt.sessionLogoutAfter / 1000);
}
// If opt.countdownBar is true, calculate remaining time percentage
if (opt.countdownBar && type === 'dialog') {
countdown.percentLeft = Math.floor(countdown.timeLeft / ((opt.sessionLogoutAfter - opt.sessionWarningAfter) / 1000) * 100);
} else if (opt.countdownBar && type === 'session') {
countdown.percentLeft = Math.floor(countdown.timeLeft / (opt.sessionLogoutAfter / 1000) * 100);
}
// Set countdown message time value
let countdownEl = $('.countdown-holder');
let secondsLeft = countdown.timeLeft >= 0 ? countdown.timeLeft : 0;
if (opt.countdownSmart) {
let minLeft = Math.floor(secondsLeft / 60);
let secRemain = secondsLeft % 60;
let countTxt = minLeft > 0 ? minLeft + 'm' : '';
if (countTxt.length > 0) {
countTxt += ' ';
}
countTxt += secRemain + 's';
countdownEl.text(countTxt);
} else {
countdownEl.text(secondsLeft + 's');
}

// Set countdown message time value
if (opt.countdownBar) {
$('.countdown-bar').css('width', countdown.percentLeft + '%');
}

// Countdown by one second
countdown.timeLeft = countdown.timeLeft - 1;
countdown.timer = setTimeout(function () {
// Call self after one second
startCountdownTimer(type);
}, 1000);
}

// Start session timer
startSessionTimer();

};
})(jQuery);

$(document).on('rex:ready', function () {
$.sessionTimeout();
});
}
11 changes: 11 additions & 0 deletions redaxo/src/core/backend.php
Expand Up @@ -189,12 +189,23 @@
rex_view::addJsFile(rex_url::coreAssets('standard.js'), [rex_view::JS_IMMUTABLE => true]);
rex_view::addJsFile(rex_url::coreAssets('sha1.js'), [rex_view::JS_IMMUTABLE => true]);
rex_view::addJsFile(rex_url::coreAssets('clipboard-copy-element.js'), [rex_view::JS_IMMUTABLE => true]);
rex_view::addJsFile(rex_url::coreAssets('session-timeout.js'), [rex_view::JS_IMMUTABLE => true]);

rex_view::setJsProperty('backend', true);
rex_view::setJsProperty('accesskeys', rex::getProperty('use_accesskeys'));
rex_view::setJsProperty('session_keep_alive', rex::getProperty('session_keep_alive', 0));
rex_view::setJsProperty('session_duration', rex::getProperty('session_duration', 0));
rex_view::setJsProperty('session_warning', rex::getProperty('session_warning', 0));
rex_view::setJsProperty('session_logout_url', rex_url::backendController(['rex_logout' => 1] + rex_csrf_token::factory('backend_logout')->getUrlParams(), false));
rex_view::setJsProperty('cookie_params', rex_login::getCookieParams());

rex_view::setJsProperty('i18n', [
'session_timeout_title' => rex_i18n::msg('session_timeout_title'),
'session_timeout_message' => rex_i18n::msg('session_timeout_message', rex::getProperty('session_warning', 0)),
'session_timeout_logout_label' => rex_i18n::msg('session_timeout_logout_label'),
'session_timeout_refresh_label' => rex_i18n::msg('session_timeout_refresh_label'),
]);

// ----- INCLUDE ADDONS
include_once rex_path::core('packages.php');

Expand Down
1 change: 1 addition & 0 deletions redaxo/src/core/default.config.yml
Expand Up @@ -11,6 +11,7 @@ dirperm: '0775'
session_duration: 7200
session_keep_alive: 21600
session_max_overall_duration: 2419200 # 4 weeks
session_warning: 120
backend_login_policy:
login_tries_until_blocked: 50
login_tries_until_delay: 3
Expand Down
5 changes: 5 additions & 0 deletions redaxo/src/core/lang/de_de.lang
Expand Up @@ -499,3 +499,8 @@ created_on = Erstellt am
updated_by = Aktualisiert von
updated_on = Aktualisiert am
status = Status

session_timeout_title = Die Sitzung läuft bald ab!
session_timeout_message = Die Session läuft in {0} Sekunden ab. Soll diese wieder verlängert werden?
session_timeout_logout_label = Abmelden
session_timeout_refresh_label = Session verlängern
4 changes: 4 additions & 0 deletions redaxo/src/core/schemas/config.json
Expand Up @@ -98,6 +98,10 @@
"description": "A session cannot stay longer then this value, no matter its actively used once in a while (seconds)",
"type": "integer"
},
"session_warning": {
"description": "Time until a warning dialog is opened. (seconds)",
"type": "integer"
},
"backend_login_policy": {
"description": "backend login policy",
"type": "object",
Expand Down

0 comments on commit 2136fcb

Please sign in to comment.