File: script.js

package info (click to toggle)
django-session-security 2.4.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 624 kB
  • ctags: 283
  • sloc: python: 515; makefile: 130
file content (152 lines) | stat: -rw-r--r-- 5,029 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Use 'yourlabs' as namespace.
if (window.yourlabs == undefined) window.yourlabs = {};

// Session security constructor. These are the required options:
//
// - pingUrl: url to ping with last activity in this tab to get global last
//   activity time,
// - warnAfter: number of seconds of inactivity before warning,
// - expireAfter: number of seconds of inactivity before expiring the session.
//
// Optional options:
//
// - confirmFormDiscard: message that will be shown when the user tries to
//   leave a page with unsaved form data. Setting this will enable an
//   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();

    // Events that would trigger an activity
    this.events = ['mousemove', 'scroll', 'keyup', 'click'];
   
    // Merge the options dict here.
    $.extend(this, options);

    // Bind activity events to update this.lastActivity.
    for(var i=0; i<this.events.length; i++) {
        $(document)[this.events[i]]($.proxy(this.activity, this))
    }
   
    // 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));
    }
}

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');
    },
    
    // 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();
    },

    // Called by click, scroll, mousemove, keyup.
    activity: function() {
        var now = new Date();
        if (now - this.lastActivity < 1000)
            // Throttle these checks to once per second
            return;

        this.lastActivity = now;

        if (this.$warning.is(':visible')) {
            // Inform the server that the user came back manually, this should
            // block other browser tabs from expiring.
            this.ping();
        }

        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;
        }

        this.timeout = setTimeout($.proxy(this.ping, this), nextPing * 1000);
    },

    // 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');
    }
}