From 198d5cda0b19fc5acb591a5981e811d5a6b77e61 Mon Sep 17 00:00:00 2001 From: asmenon Date: Thu, 21 Oct 2021 23:18:27 +0530 Subject: [PATCH 1/5] Migrate to native javascript instead of jquery --- .../static/session_security/script.js | 300 +++++++++--------- 1 file changed, 157 insertions(+), 143 deletions(-) diff --git a/session_security/static/session_security/script.js b/session_security/static/session_security/script.js index a9bef38..f06c99a 100644 --- a/session_security/static/session_security/script.js +++ b/session_security/static/session_security/script.js @@ -15,154 +15,168 @@ if (window.yourlabs == undefined) window.yourlabs = {}; // onbeforeunload handler that doesn't block expire(). // - events: a list of event types to watch for activity updates. // - returnToUrl: a url to redirect users to expired sessions to. If this is not defined we just reload the page -yourlabs.SessionSecurity = function(options) { - // **HTML element** that should show to warn the user that his session will - // expire. - this.$warning = $('#session_security_warning'); - - // Last recorded activity datetime. - this.lastActivity = new Date(); +yourlabs.SessionSecurity = function (options) { + // **HTML element** that should show to warn the user that his session will + // expire. + this.$warning = document.querySelector("#session_security_warning"); + + // Last recorded activity datetime. + this.lastActivity = new Date(); + + // Events that would trigger an activity + this.events = [ + "mousemove", + "scroll", + "keyup", + "click", + "touchstart", + "touchend", + "touchmove", + ]; + + // Merge the options dict here. + Object.assign(this, options); + + // Bind activity events to update this.lastActivity. + var $document = document.querySelector(document); + for (var i = 0; i < this.events.length; i++) { + if ($document[this.events[i]]) { + $document[this.events[i]](this.activity.bind(this)); + } + } - // Events that would trigger an activity - this.events = ['mousemove', 'scroll', 'keyup', 'click', 'touchstart', 'touchend', 'touchmove']; + // Initialize timers. + this.apply(); - // Merge the options dict here. - $.extend(this, options); + if (this.confirmFormDiscard) { + window.onbeforeunload = this.onbeforeunload.bind(this); + $document.addEventListener("change", ":input", this.formChange.bind(this)); + $document.addEventListener("submit", "form", this.formClean.bind(this)); + $document.addEventListener("reset", "form", this.formClean.bind(this)); + } +}; - // Bind activity events to update this.lastActivity. - var $document = $(document); - for(var i=0; i= this.expireAfter) { + // Enforces checking whether a user's session is expired. This + // ensures a user being redirected instead of waiting until nextPing. + this.expire(); } - // Initialize timers. - this.apply() - - if (this.confirmFormDiscard) { - window.onbeforeunload = $.proxy(this.onbeforeunload, this); - $document.on('change', ':input', $.proxy(this.formChange, this)); - $document.on('submit', 'form', $.proxy(this.formClean, this)); - $document.on('reset', 'form', $.proxy(this.formClean, this)); + if (this.$warning.is(":visible")) { + // Inform the server that the user came back manually, this should + // block other browser tabs from expiring. + this.ping(); + // The hideWarning should only be called when the warning is visible + this.hideWarning(); + } + }, + + // Hit the PingView with the number of seconds since last activity. + ping: function () { + var idleFor = Math.floor((new Date() - this.lastActivity) / 1000); + + var request = new XMLHttpRequest(); + request.open("GET", this.pingUrl, true); + + request.onload = function () { + if (this.status >= 200 && this.status < 400) { + // Success! + this.pong.bind(this); + } else { + // We reached our target server, but it returned an error + this.apply.bind(this); + } + }; + + request.onerror = function () { + // There was a connection error of some sort + }; + + request.send(idleFor); + }, + + // Callback to process PingView response. + pong: function (data) { + if (data == "logout") return this.expire(); + this.lastActivity = new Date(); + this.lastActivity.setSeconds(this.lastActivity.getSeconds() - data); + this.apply(); + }, + + // Apply warning or expiry, setup next ping + apply: function () { + // Cancel timeout if any, since we're going to make our own + clearTimeout(this.timeout); + + var idleFor = Math.floor((new Date() - this.lastActivity) / 1000); + + if (idleFor >= this.expireAfter) { + return this.expire(); + } else if (idleFor >= this.warnAfter) { + this.showWarning(); + nextPing = this.expireAfter - idleFor; + } else { + this.hideWarning(); + nextPing = this.warnAfter - idleFor; } -} -yourlabs.SessionSecurity.prototype = { - // Called when there has been no activity for more than expireAfter - // seconds. - expire: function() { - this.expired = true; - if (this.returnToUrl !== undefined) { - window.location.href = this.returnToUrl; - } - else { - window.location.reload(); - } - }, - - // Called when there has been no activity for more than warnAfter - // seconds. - showWarning: function() { - this.$warning.fadeIn('slow'); - this.$warning.attr('aria-hidden', 'false'); - $('.session_security_modal').focus(); - }, - - // Called to hide the warning, for example if there has been activity on - // the server side - in another browser tab. - hideWarning: function() { - this.$warning.hide(); - this.$warning.attr('aria-hidden', 'true'); - }, - - // Called by click, scroll, mousemove, keyup, touchstart, touchend, touchmove - activity: function() { - var now = new Date(); - if (now - this.lastActivity < 1000) - // Throttle these checks to once per second - return; - - var idleFor = Math.floor((now - this.lastActivity) / 1000); - this.lastActivity = now; - - if (idleFor >= this.expireAfter) { - // Enforces checking whether a user's session is expired. This - // ensures a user being redirected instead of waiting until nextPing. - this.expire(); - } - - if (this.$warning.is(':visible')) { - // Inform the server that the user came back manually, this should - // block other browser tabs from expiring. - this.ping(); - // The hideWarning should only be called when the warning is visible - this.hideWarning(); - } - }, - - // Hit the PingView with the number of seconds since last activity. - ping: function() { - var idleFor = Math.floor((new Date() - this.lastActivity) / 1000); - - $.ajax(this.pingUrl, { - data: {idleFor: idleFor}, - cache: false, - success: $.proxy(this.pong, this), - // In case of network error, we still want to hide potentially - // confidential data !! - error: $.proxy(this.apply, this), - dataType: 'json', - type: 'get' - }); - }, - - // Callback to process PingView response. - pong: function(data) { - if (data == 'logout') return this.expire(); - - this.lastActivity = new Date(); - this.lastActivity.setSeconds(this.lastActivity.getSeconds() - data); - this.apply(); - }, - - // Apply warning or expiry, setup next ping - apply: function() { - // Cancel timeout if any, since we're going to make our own - clearTimeout(this.timeout); - - var idleFor = Math.floor((new Date() - this.lastActivity) / 1000); - - if (idleFor >= this.expireAfter) { - return this.expire(); - } else if (idleFor >= this.warnAfter) { - this.showWarning(); - nextPing = this.expireAfter - idleFor; - } else { - this.hideWarning(); - nextPing = this.warnAfter - idleFor; - } - - // setTimeout expects the timeout value not to exceed - // a 32-bit unsigned int, so cap the value - var milliseconds = Math.min(nextPing * 1000, 2147483647) - this.timeout = setTimeout($.proxy(this.ping, this), milliseconds); - }, - - // onbeforeunload handler. - onbeforeunload: function(e) { - if ($('form[data-dirty]').length && !this.expired) { - return this.confirmFormDiscard; - } - }, - - // When an input change, set data-dirty attribute on its form. - formChange: function(e) { - $(e.target).closest('form').attr('data-dirty', true); - }, - - // When a form is submitted or resetted, unset data-dirty attribute. - formClean: function(e) { - $(e.target).removeAttr('data-dirty'); + // setTimeout expects the timeout value not to exceed + // a 32-bit unsigned int, so cap the value + var milliseconds = Math.min(nextPing * 1000, 2147483647); + this.timeout = setTimeout(this.ping.bind(this), milliseconds); + }, + + // onbeforeunload handler. + onbeforeunload: function (e) { + if (document.querySelector("form[data-dirty]").length && !this.expired) { + return this.confirmFormDiscard; } -} + }, + + // When an input change, set data-dirty attribute on its form. + formChange: function (e) { + document.querySelector(e.target).closest("form").attr("data-dirty", true); + }, + + // When a form is submitted or resetted, unset data-dirty attribute. + formClean: function (e) { + document.querySelector(e.target).removeAttr("data-dirty"); + }, +}; From 19bef2d0bb52f8742e23cea6b252370167c2cc98 Mon Sep 17 00:00:00 2001 From: asdmenon Date: Thu, 4 Nov 2021 12:04:54 +0530 Subject: [PATCH 2/5] Fix binding callback and ajax conversion. --- .../static/session_security/script.js | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/session_security/static/session_security/script.js b/session_security/static/session_security/script.js index f06c99a..2d89456 100644 --- a/session_security/static/session_security/script.js +++ b/session_security/static/session_security/script.js @@ -18,7 +18,7 @@ if (window.yourlabs == undefined) window.yourlabs = {}; yourlabs.SessionSecurity = function (options) { // **HTML element** that should show to warn the user that his session will // expire. - this.$warning = document.querySelector("#session_security_warning"); + this.warning = document.querySelector("#session_security_warning"); // Last recorded activity datetime. this.lastActivity = new Date(); @@ -36,12 +36,13 @@ yourlabs.SessionSecurity = function (options) { // Merge the options dict here. Object.assign(this, options); - + console.log(options); // Bind activity events to update this.lastActivity. - var $document = document.querySelector(document); + var m_document = document.querySelector(document); + for (var i = 0; i < this.events.length; i++) { - if ($document[this.events[i]]) { - $document[this.events[i]](this.activity.bind(this)); + if (m_document[this.events[i]]) { + m_document[this.events[i]](this.activity.bind(this)); } } @@ -50,9 +51,9 @@ yourlabs.SessionSecurity = function (options) { if (this.confirmFormDiscard) { window.onbeforeunload = this.onbeforeunload.bind(this); - $document.addEventListener("change", ":input", this.formChange.bind(this)); - $document.addEventListener("submit", "form", this.formClean.bind(this)); - $document.addEventListener("reset", "form", this.formClean.bind(this)); + m_document.addEventListener("change", "input", this.formChange.bind(this)); + m_document.addEventListener("submit", "form", this.formClean.bind(this)); + m_document.addEventListener("reset", "form", this.formClean.bind(this)); } }; @@ -71,16 +72,20 @@ yourlabs.SessionSecurity.prototype = { // Called when there has been no activity for more than warnAfter // seconds. showWarning: function () { - this.$warning.fadeIn("slow"); - this.$warning.attr("aria-hidden", "false"); + // TODO: Fix this for non jquery stuff. + this.warning.removeAttribute("style", ""); + console.log("hiding warning"); + this.warning.setAttribute("aria-hidden", "false"); document.querySelector(".session_security_modal").focus(); }, // Called to hide the warning, for example if there has been activity on // the server side - in another browser tab. hideWarning: function () { - this.$warning.hide(); - this.$warning.attr("aria-hidden", "true"); + // TODO: Fix this for non jquery stuff. + console.log("hiding warning"); + this.warning.setAttribute("style", "display:none"); + this.warning.setAttribute("aria-hidden", "true"); }, // Called by click, scroll, mousemove, keyup, touchstart, touchend, touchmove From 2e92a9496f1eabd31c0a2cdd8777cd0da257cb28 Mon Sep 17 00:00:00 2001 From: asdmenon Date: Thu, 4 Nov 2021 12:25:13 +0530 Subject: [PATCH 3/5] Minor fixes. --- .../static/session_security/script.js | 106 ++++++++++-------- 1 file changed, 59 insertions(+), 47 deletions(-) diff --git a/session_security/static/session_security/script.js b/session_security/static/session_security/script.js index 2d89456..4368f1c 100644 --- a/session_security/static/session_security/script.js +++ b/session_security/static/session_security/script.js @@ -36,13 +36,15 @@ yourlabs.SessionSecurity = function (options) { // Merge the options dict here. Object.assign(this, options); - console.log(options); - // Bind activity events to update this.lastActivity. - var m_document = document.querySelector(document); + - for (var i = 0; i < this.events.length; i++) { - if (m_document[this.events[i]]) { - m_document[this.events[i]](this.activity.bind(this)); + // Bind activity events to update this.lastActivity. + var m_document = document; + + + for (var i = 0; i < this.events.length; i++) { + if (m_document[this.events[i]] == null) { + m_document.addEventListener(this.events[i], this.activity.bind(this)); } } @@ -51,16 +53,16 @@ yourlabs.SessionSecurity = function (options) { if (this.confirmFormDiscard) { window.onbeforeunload = this.onbeforeunload.bind(this); - m_document.addEventListener("change", "input", this.formChange.bind(this)); - m_document.addEventListener("submit", "form", this.formClean.bind(this)); - m_document.addEventListener("reset", "form", this.formClean.bind(this)); + m_document.addEventListener("change", this.formChange.bind(this)); + m_document.addEventListener("submit", this.formClean.bind(this)); + m_document.addEventListener("reset", this.formClean.bind(this)); } }; yourlabs.SessionSecurity.prototype = { // Called when there has been no activity for more than expireAfter // seconds. - expire: function () { + expire: function () { this.expired = true; if (this.returnToUrl !== undefined) { window.location.href = this.returnToUrl; @@ -72,9 +74,8 @@ yourlabs.SessionSecurity.prototype = { // Called when there has been no activity for more than warnAfter // seconds. showWarning: function () { - // TODO: Fix this for non jquery stuff. + this.warning.removeAttribute("style", ""); - console.log("hiding warning"); this.warning.setAttribute("aria-hidden", "false"); document.querySelector(".session_security_modal").focus(); }, @@ -82,17 +83,16 @@ yourlabs.SessionSecurity.prototype = { // Called to hide the warning, for example if there has been activity on // the server side - in another browser tab. hideWarning: function () { - // TODO: Fix this for non jquery stuff. - console.log("hiding warning"); + this.warning.setAttribute("style", "display:none"); this.warning.setAttribute("aria-hidden", "true"); }, // Called by click, scroll, mousemove, keyup, touchstart, touchend, touchmove - activity: function () { + activity: function () { var now = new Date(); - if (now - this.lastActivity < 1000) - // Throttle these checks to once per second + if (now - this.lastActivity < 1500) + // Throttle these checks to once per 1.5 seconds return; var idleFor = Math.floor((now - this.lastActivity) / 1000); @@ -103,8 +103,7 @@ yourlabs.SessionSecurity.prototype = { // ensures a user being redirected instead of waiting until nextPing. this.expire(); } - - if (this.$warning.is(":visible")) { + if (!this.warning.hasAttribute('style')) { // Inform the server that the user came back manually, this should // block other browser tabs from expiring. this.ping(); @@ -114,31 +113,45 @@ yourlabs.SessionSecurity.prototype = { }, // Hit the PingView with the number of seconds since last activity. - ping: function () { - var idleFor = Math.floor((new Date() - this.lastActivity) / 1000); - - var request = new XMLHttpRequest(); - request.open("GET", this.pingUrl, true); - - request.onload = function () { - if (this.status >= 200 && this.status < 400) { - // Success! - this.pong.bind(this); - } else { - // We reached our target server, but it returned an error - this.apply.bind(this); - } - }; - - request.onerror = function () { - // There was a connection error of some sort - }; - - request.send(idleFor); + ping: function () { + var idleFor = Math.floor((new Date() - this.lastActivity) / 1000); + + var xhr = new XMLHttpRequest(); + + // For no cache, append _ with current timestamp. + let xUrl = this.pingUrl + "?idleFor=" + String(idleFor) + "&_=" + String(new Date().getTime()); + + xhr.open("GET", xUrl , true); + // set header to JSON? + xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); + var self = this; + var ponger = self.pong.bind(self); + var applier = self.apply.bind(self); + xhr.onload = function() { + if (xhr.readyState == xhr.DONE && xhr.status == 200) + { + + ponger(xhr.response); + } + else if (xhr.readyState == xhr.DONE && xhr.status == 400) + { + applier(xhr.response ); + } + else + { + } + }; + + xhr.onerror = function(){ + console.log("Error connecting to ping view"); + } + xhr.send(JSON.stringify({ + idleFor: idleFor, + })); }, // Callback to process PingView response. - pong: function (data) { + pong: function (data) { if (data == "logout") return this.expire(); this.lastActivity = new Date(); this.lastActivity.setSeconds(this.lastActivity.getSeconds() - data); @@ -147,11 +160,10 @@ yourlabs.SessionSecurity.prototype = { // Apply warning or expiry, setup next ping apply: function () { - // Cancel timeout if any, since we're going to make our own + // Cancel timeout if any, since we're going to make our own clearTimeout(this.timeout); - var idleFor = Math.floor((new Date() - this.lastActivity) / 1000); - + var idleFor = Math.floor((new Date() - this.lastActivity) / 1000); if (idleFor >= this.expireAfter) { return this.expire(); } else if (idleFor >= this.warnAfter) { @@ -163,8 +175,8 @@ yourlabs.SessionSecurity.prototype = { } // setTimeout expects the timeout value not to exceed - // a 32-bit unsigned int, so cap the value - var milliseconds = Math.min(nextPing * 1000, 2147483647); + // a 32-bit unsigned int, so cap the value + var milliseconds = Math.min(nextPing * 1000, 2147483647); this.timeout = setTimeout(this.ping.bind(this), milliseconds); }, @@ -184,4 +196,4 @@ yourlabs.SessionSecurity.prototype = { formClean: function (e) { document.querySelector(e.target).removeAttr("data-dirty"); }, -}; +}; \ No newline at end of file From 03fd929efd0c1b38345b020de3de55961215d514 Mon Sep 17 00:00:00 2001 From: asdmenon Date: Thu, 4 Nov 2021 15:19:06 +0530 Subject: [PATCH 4/5] Fix logout string check for expiration. --- session_security/static/session_security/script.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/session_security/static/session_security/script.js b/session_security/static/session_security/script.js index 4368f1c..72f233a 100644 --- a/session_security/static/session_security/script.js +++ b/session_security/static/session_security/script.js @@ -151,8 +151,8 @@ yourlabs.SessionSecurity.prototype = { }, // Callback to process PingView response. - pong: function (data) { - if (data == "logout") return this.expire(); + pong: function (data) { + if (String(data) == "\"logout\"") return this.expire(); this.lastActivity = new Date(); this.lastActivity.setSeconds(this.lastActivity.getSeconds() - data); this.apply(); From 85b19db3297bd7d6cf0b128cfb656543d2648b62 Mon Sep 17 00:00:00 2001 From: asdmenon Date: Thu, 4 Nov 2021 15:42:21 +0530 Subject: [PATCH 5/5] change expiration comparison to === --- session_security/static/session_security/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session_security/static/session_security/script.js b/session_security/static/session_security/script.js index 72f233a..5f9a579 100644 --- a/session_security/static/session_security/script.js +++ b/session_security/static/session_security/script.js @@ -152,7 +152,7 @@ yourlabs.SessionSecurity.prototype = { // Callback to process PingView response. pong: function (data) { - if (String(data) == "\"logout\"") return this.expire(); + if (String(data) === ""logout\"") return this.expire(); this.lastActivity = new Date(); this.lastActivity.setSeconds(this.lastActivity.getSeconds() - data); this.apply();