User session improvement

- Add session handler on other blueprint's before request
- Adjustment in using jTimeout to close warning popup on
other tabs when we extend the session
This commit is contained in:
Khanh Ngo 2019-12-18 15:25:20 +07:00
parent 9a1b92fbc2
commit 7205b4a01b
No known key found for this signature in database
GPG key ID: D5FAA6A16150E49E
7 changed files with 124 additions and 23 deletions

View file

@ -26,6 +26,7 @@ class Setting(db.Model):
'bg_domain_updates': False, 'bg_domain_updates': False,
'site_name': 'PowerDNS-Admin', 'site_name': 'PowerDNS-Admin',
'session_timeout': 10, 'session_timeout': 10,
'warn_session_timeout': True,
'pdns_api_url': '', 'pdns_api_url': '',
'pdns_api_key': '', 'pdns_api_key': '',
'pdns_api_timeout': 30, 'pdns_api_timeout': 30,

View file

@ -1,7 +1,8 @@
import json import json
import datetime
import traceback import traceback
from ast import literal_eval from ast import literal_eval
from flask import Blueprint, render_template, make_response, url_for, current_app, request, redirect, jsonify, abort, flash from flask import Blueprint, render_template, make_response, url_for, current_app, request, redirect, jsonify, abort, flash, session
from flask_login import login_required, current_user from flask_login import login_required, current_user
from ..decorators import operator_role_required, admin_role_required from ..decorators import operator_role_required, admin_role_required
@ -23,6 +24,16 @@ admin_bp = Blueprint('admin',
url_prefix='/admin') url_prefix='/admin')
@admin_bp.before_request
def before_request():
# Manage session timeout
session.permanent = True
current_app.permanent_session_lifetime = datetime.timedelta(
minutes=int(Setting().get('session_timeout')))
session.modified = True
@admin_bp.route('/pdns', methods=['GET']) @admin_bp.route('/pdns', methods=['GET'])
@login_required @login_required
@operator_role_required @operator_role_required
@ -489,7 +500,8 @@ def setting_basic():
'default_domain_table_size', 'auto_ptr', 'record_quick_edit', 'default_domain_table_size', 'auto_ptr', 'record_quick_edit',
'pretty_ipv6_ptr', 'dnssec_admins_only', 'pretty_ipv6_ptr', 'dnssec_admins_only',
'allow_user_create_domain', 'bg_domain_updates', 'site_name', 'allow_user_create_domain', 'bg_domain_updates', 'site_name',
'session_timeout', 'ttl_options', 'pdns_api_timeout' 'session_timeout', 'warn_session_timeout', 'ttl_options',
'pdns_api_timeout'
] ]
return render_template('admin_setting_basic.html', settings=settings) return render_template('admin_setting_basic.html', settings=settings)

View file

@ -1,6 +1,6 @@
import json import json
from urllib.parse import urljoin from urllib.parse import urljoin
from flask import Blueprint, g, request, abort, current_app from flask import Blueprint, g, request, abort, current_app, make_response, jsonify
from flask_login import current_user from flask_login import current_user
from ..models.base import db from ..models.base import db
@ -88,7 +88,16 @@ def handle_request_is_not_json(err):
@api_bp.before_request @api_bp.before_request
@is_json @is_json
def before_request(): def before_request():
pass # Check site is in maintenance mode
maintenance = Setting().get('maintenance')
if maintenance and current_user.is_authenticated and current_user.role.name not in [
'Administrator', 'Operator'
]:
return make_response(
jsonify({
"status": False,
"msg": "Site is in maintenance mode"
}))
@api_bp.route('/pdnsadmin/zones', methods=['POST']) @api_bp.route('/pdnsadmin/zones', methods=['POST'])
@ -281,7 +290,7 @@ def api_get_apikeys(domain_name):
if current_user.role.name not in ['Administrator', 'Operator']: if current_user.role.name not in ['Administrator', 'Operator']:
if domain_name: if domain_name:
msg = "Check if domain {0} exists and \ msg = "Check if domain {0} exists and \
is allowed for user." .format(domain_name) is allowed for user." .format(domain_name)
current_app.logger.debug(msg) current_app.logger.debug(msg)
apikeys = current_user.get_apikeys(domain_name) apikeys = current_user.get_apikeys(domain_name)

View file

@ -1,9 +1,10 @@
from flask import Blueprint, render_template, url_for, current_app, request, jsonify, redirect import datetime
from flask_login import login_required, current_user from flask import Blueprint, render_template, url_for, current_app, request, jsonify, redirect, g, session
from flask_login import login_required, current_user, login_manager
from sqlalchemy import not_ from sqlalchemy import not_
from ..lib.utils import customBoxes from ..lib.utils import customBoxes
from ..models.user import User from ..models.user import User, Anonymous
from ..models.account import Account from ..models.account import Account
from ..models.account_user import AccountUser from ..models.account_user import AccountUser
from ..models.domain import Domain from ..models.domain import Domain
@ -19,6 +20,26 @@ dashboard_bp = Blueprint('dashboard',
url_prefix='/dashboard') url_prefix='/dashboard')
@dashboard_bp.before_request
def before_request():
# Check if user is anonymous
g.user = current_user
login_manager.anonymous_user = Anonymous
# Check site is in maintenance mode
maintenance = Setting().get('maintenance')
if maintenance and current_user.is_authenticated and current_user.role.name not in [
'Administrator', 'Operator'
]:
return render_template('maintenance.html')
# Manage session timeout
session.permanent = True
current_app.permanent_session_lifetime = datetime.timedelta(
minutes=int(Setting().get('session_timeout')))
session.modified = True
@dashboard_bp.route('/domains-custom/<path:boxId>', methods=['GET']) @dashboard_bp.route('/domains-custom/<path:boxId>', methods=['GET'])
@login_required @login_required
def domains_custom(boxId): def domains_custom(boxId):

View file

@ -1,15 +1,16 @@
import re import re
import json import json
import datetime
import traceback import traceback
import dns.name import dns.name
import dns.reversename import dns.reversename
from distutils.version import StrictVersion from distutils.version import StrictVersion
from flask import Blueprint, render_template, make_response, url_for, current_app, request, redirect, abort, jsonify from flask import Blueprint, render_template, make_response, url_for, current_app, request, redirect, abort, jsonify, g, session
from flask_login import login_required, current_user from flask_login import login_required, current_user, login_manager
from ..lib.utils import pretty_json from ..lib.utils import pretty_json
from ..decorators import can_create_domain, operator_role_required, can_access_domain, can_configure_dnssec from ..decorators import can_create_domain, operator_role_required, can_access_domain, can_configure_dnssec
from ..models.user import User from ..models.user import User, Anonymous
from ..models.account import Account from ..models.account import Account
from ..models.setting import Setting from ..models.setting import Setting
from ..models.history import History from ..models.history import History
@ -26,6 +27,26 @@ domain_bp = Blueprint('domain',
url_prefix='/domain') url_prefix='/domain')
@domain_bp.before_request
def before_request():
# Check if user is anonymous
g.user = current_user
login_manager.anonymous_user = Anonymous
# Check site is in maintenance mode
maintenance = Setting().get('maintenance')
if maintenance and current_user.is_authenticated and current_user.role.name not in [
'Administrator', 'Operator'
]:
return render_template('maintenance.html')
# Manage session timeout
session.permanent = True
current_app.permanent_session_lifetime = datetime.timedelta(
minutes=int(Setting().get('session_timeout')))
session.modified = True
@domain_bp.route('/<path:domain_name>', methods=['GET']) @domain_bp.route('/<path:domain_name>', methods=['GET'])
@login_required @login_required
@can_access_domain @can_access_domain

View file

@ -1,10 +1,12 @@
import datetime
import qrcode as qrc import qrcode as qrc
import qrcode.image.svg as qrc_svg import qrcode.image.svg as qrc_svg
from io import BytesIO from io import BytesIO
from flask import Blueprint, request, render_template, make_response, jsonify, redirect, url_for, session from flask import Blueprint, request, render_template, make_response, jsonify, redirect, url_for, g, session, current_app
from flask_login import current_user, login_required from flask_login import current_user, login_required, login_manager
from ..models.user import User from ..models.user import User, Anonymous
from ..models.setting import Setting
user_bp = Blueprint('user', user_bp = Blueprint('user',
__name__, __name__,
@ -12,6 +14,26 @@ user_bp = Blueprint('user',
url_prefix='/user') url_prefix='/user')
@user_bp.before_request
def before_request():
# Check if user is anonymous
g.user = current_user
login_manager.anonymous_user = Anonymous
# Check site is in maintenance mode
maintenance = Setting().get('maintenance')
if maintenance and current_user.is_authenticated and current_user.role.name not in [
'Administrator', 'Operator'
]:
return render_template('maintenance.html')
# Manage session timeout
session.permanent = True
current_app.permanent_session_lifetime = datetime.timedelta(
minutes=int(Setting().get('session_timeout')))
session.modified = True
@user_bp.route('/profile', methods=['GET', 'POST']) @user_bp.route('/profile', methods=['GET', 'POST'])
@login_required @login_required
def profile(): def profile():

View file

@ -174,8 +174,18 @@
{% block scripts %} {% block scripts %}
{% assets "js_main" -%} {% assets "js_main" -%}
<script type="text/javascript" src="{{ ASSET_URL }}"></script> <script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% if SETTING.get('warn_session_timeout') %}
<script> <script>
// close the session warning popup when receive
// a boradcast message
var bc = new BroadcastChannel('powerdnsadmin');
bc.addEventListener('message', function (e) {
if (e.data == 'close_session_timeout_modal'){
$("#modal_session_warning").modal('hide');
}
});
// Stay Signed In button click event
$(document.body).on("click", ".button_stay_signed_in", function (e) { $(document.body).on("click", ".button_stay_signed_in", function (e) {
$.get({ $.get({
url: $.jTimeout().options.extendUrl, url: $.jTimeout().options.extendUrl,
@ -184,12 +194,15 @@
$.jTimeout().resetExpiration(); $.jTimeout().resetExpiration();
} }
}); });
$.jTimeout().options.onClickExtend();
}); });
// Sign Out button click event
$(document.body).on("click", ".button_sign_out", function (e) { $(document.body).on("click", ".button_sign_out", function (e) {
window.location.replace("{{ url_for('index.logout') }}"); window.location.replace("{{ url_for('index.logout') }}");
}); });
// Things happen when session warning popup shown
$(document).on('show.bs.modal','#modal_session_warning', function () { $(document).on('show.bs.modal','#modal_session_warning', function () {
var secondsLeft = jTimeout.getSecondsTillExpiration(); var secondsLeft = jTimeout.getSecondsTillExpiration();
var t = timer($('#modal-time'), secondsLeft); var t = timer($('#modal-time'), secondsLeft);
@ -199,32 +212,34 @@
$('#modal-time').text(""); $('#modal-time').text("");
$(this).off('hidden.bs.modal'); $(this).off('hidden.bs.modal');
}); });
}); });
// jTimeout definition
$(function(){ $(function(){
$.jTimeout({ $.jTimeout({
flashTitle: true, flashTitle: true,
flashTitleSpeed: 500, flashTitleSpeed: 500,
flashingTitleText: '**WARNING**', flashingTitleText: '**WARNING**',
originalTitle: document.title, originalTitle: document.title,
timeoutAfter: {{ SETTING.get('session_timeout')|int * 60 }},
timeoutAfter: {{ SETTING.get('session_timeout')|int *60 }},
secondsPrior: 60, secondsPrior: 60,
heartbeat: 1, heartbeat: 1,
extendOnMouseMove: true, extendOnMouseMove: true,
mouseDebounce: 30, mouseDebounce: 30,
extendUrl: '{{ url_for("index.ping") }}', extendUrl: '{{ url_for("index.ping") }}',
logoutUrl: '{{ url_for("index.logout") }}', logoutUrl: '{{ url_for("index.logout") }}',
loginUrl: '{{ url_for("index.login") }}', loginUrl: '{{ url_for("index.login") }}',
onClickExtend: function(jTimeout){ onClickExtend: function(){
console.log("onClickExtend!"); // broadcast a message to tell other tabes
// close the session warning popup
var bc = new BroadcastChannel('powerdnsadmin');
bc.postMessage('close_session_timeout_modal');
}, },
onMouseMove: function(jTimeout){ onMouseMove: function(){
// If the mouse is moving while popup is present, we // if the mouse is moving while popup is present, we
// don't extend the session. // don't extend the session.
if (!$('#modal_session_warning').hasClass('in')) { if (!$('#modal_session_warning').hasClass('in')) {
$.get({ $.get({
@ -248,10 +263,10 @@
onSessionExtended:function(jTimeout){ onSessionExtended:function(jTimeout){
$("#modal_session_warning").modal('hide'); $("#modal_session_warning").modal('hide');
} }
}); });
}); });
</script> </script>
{% endif %}
{%- endassets %} {%- endassets %}
{% endblock %} {% endblock %}
{% block extrascripts %} {% block extrascripts %}