Session timeout after idle session

This commit is the UI portion of the session timeout feature for 2.4 (not incorporating the session_limit yet). This includes actively ending a session when the user's session time has expired, as well as giving the user a notification one minute prior that their session is about to end. It also includes removing 'session_timeout' from config.js. This is now retrieved from the response headers of each REST call. Each time the user make a rest call the session is extended another 1800 seconds (or whatever amount the user might configure with AUTH_TOKEN_EXPIRATION in local_settings.py)
This commit is contained in:
Jared Tabor 2015-09-10 10:28:09 -04:00
parent 0cee9b3814
commit fd34854f7a
12 changed files with 141 additions and 25 deletions

View File

@ -55,7 +55,8 @@ import {InventoriesList, InventoriesAdd, InventoriesEdit, InventoriesManage} fro
import {AdminsList} from './controllers/Admins';
import {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
import './shared/RestServices';
import RestServices from './rest/main';
import './shared/api-loader';
import './shared/form-generator';
import './shared/Modal';
@ -78,7 +79,7 @@ var tower = angular.module('Tower', [
'ngRoute',
'ngSanitize',
'ngCookies',
'RestServices',
RestServices.name,
routeExtensions.name,
browserData.name,
breadcrumbs.name,
@ -878,7 +879,6 @@ var tower = angular.module('Tower', [
$rootScope.breadcrumbs = [];
$rootScope.crumbCache = [];
$rootScope.sessionTimer = Timer.init();
if ($rootScope.removeOpenSocket) {
$rootScope.removeOpenSocket();
@ -976,12 +976,12 @@ var tower = angular.module('Tower', [
}
if (Authorization.isUserLoggedIn() === false) {
if (next.templateUrl !== (urlPrefix + 'partials/login.html')) {
if (next.templateUrl !== (urlPrefix + 'login/loginBackDrop.partial.html')) {
$location.path('/login');
}
} else if ($rootScope.sessionTimer.isExpired()) {
// gets here on timeout
if (next.templateUrl !== (urlPrefix + 'partials/login.html')) {
if (next.templateUrl !== (urlPrefix + 'login/loginBackDrop.partial.html')) {
$rootScope.sessionTimer.expireSession();
if (sock) {
sock.socket.socket.disconnect();
@ -1010,6 +1010,7 @@ var tower = angular.module('Tower', [
$rootScope.user_is_superuser = Authorization.getUserInfo('is_superuser');
// when the user refreshes we want to open the socket, except if the user is on the login page, which should happen after the user logs in (see the AuthService module for that call to OpenSocket)
if($location.$$url !== '/login'){
$rootScope.sessionTimer = Timer.init();
$rootScope.$emit('OpenSocket');
pendoService.issuePendoIdentity();
}

View File

@ -40,10 +40,6 @@
password_hasSymbol: false, // require one of these symbols to be
// in the password: -!$%^&*()_+|~=`{}[]:";'<>?,./
session_timeout: 1800, // Number of seconds before an inactive session is automatically timed out and forced to log in again.
// Separate from time out value set in API.
variable_edit_modes: { // Options we pass to ControlMirror for editing YAML/JSON variables
yaml: {
mode:"text/x-yaml",

View File

@ -144,7 +144,7 @@ export function JobTemplatesList($scope, $rootScope, $location, $log, $routePara
CreateDialog({
id: 'copy-job-modal' ,
id: 'copy-job-modal',
title: "Copy",
scope: $scope,
buttons: buttons,

View File

@ -46,7 +46,7 @@ angular.module('LoadConfigHelper', ['Utilities'])
$rootScope.$emit('ConfigReady');
}
}, function(response) {
}, function() {
//local_settings.json not found
$log.info('local_settings.json not found');
$rootScope.$emit('ConfigReady');

View File

@ -94,6 +94,8 @@ export default
$rootScope.token_expires = null;
$rootScope.login_username = null;
$rootScope.login_password = null;
clearTimeout($rootScope.idleTimer);
clearTimeout($rootScope.endTimer);
},
getLicense: function () {
@ -109,7 +111,7 @@ export default
setLicense: function (data) {
var license = data.license_info;
license.analytics_status = data.analytics_status;
license.analytics_status = data.analytics_status;
license.version = data.version;
license.tested = false;
Store('license', license);
@ -138,7 +140,8 @@ export default
method: 'GET',
url: '/api/v1/me/',
headers: {
'Authorization': 'Token ' + this.getToken()
'Authorization': 'Token ' + this.getToken(),
"X-Auth-Token": 'Token ' + this.getToken()
}
});
},

View File

@ -22,8 +22,8 @@
* @description
*/
export default
['$rootScope', '$cookieStore', '$location', 'GetBasePath', 'Empty',
function ($rootScope, $cookieStore) {
['$rootScope', '$cookieStore', 'transitionTo', 'CreateDialog', 'Authorization',
function ($rootScope, $cookieStore, transitionTo, CreateDialog, Authorization) {
return {
sessionTime: null,
@ -46,10 +46,25 @@ export default
}
},
isIdle: function() {
var stime = this.getSessionTime()/1000,
now = new Date().getTime()/1000,
diff = stime-now;
if(diff < 61){
return true;
}
else {
return false;
}
},
expireSession: function () {
this.sessionTime = 0;
$rootScope.sessionExpired = true;
this.clearTimers();
$cookieStore.put('sessionExpired', true);
transitionTo('signOut');
},
moveForward: function () {
@ -60,6 +75,70 @@ export default
$cookieStore.put('sessionTime', t);
$rootScope.sessionExpired = false;
$cookieStore.put('sessionExpired', false);
this.startTimers();
},
startTimers: function() {
var that = this,
tm = ($AnsibleConfig) ? $AnsibleConfig.session_timeout : 1800,
t = tm - 60;
this.clearTimers();
// make a timeout that will go off in 30 mins to log them out
// unless they extend their time
$rootScope.endTimer = setTimeout(function(){
that.expireSession();
}, tm * 1000);
// notify the user a minute before the end of their session that
// their session is about to expire
if($rootScope.idleTimer){
clearTimeout($rootScope.idleTimer);
}
$rootScope.idleTimer = setTimeout(function() {
if(that.isIdle() === true){
var buttons = [{
"label": "Continue",
"onClick": function() {
// make a rest call here to force the API to
// move the session time forward
Authorization.getUser();
that.moveForward();
$(this).dialog('close');
},
"class": "btn btn-primary",
"id": "idle-modal-button"
}];
if ($rootScope.removeIdleDialogReady) {
$rootScope.removeIdleDialogReady();
}
$rootScope.removeIdleDialogReady = $rootScope.$on('IdleDialogReady', function() {
$('#idle-modal').show();
$('#idle-modal').dialog('open');
});
CreateDialog({
id: 'idle-modal' ,
title: "Idle Session",
scope: $rootScope,
buttons: buttons,
width: 470,
height: 240,
minWidth: 200,
callback: 'IdleDialogReady'
});
}
}, t * 1000);
},
clearTimers: function(){
clearTimeout($rootScope.idleTimer);
clearTimeout($rootScope.endTimer);
},
init: function () {

View File

@ -13,10 +13,5 @@ export default {
Authorization.logout();
$location.path('/login');
}],
templateUrl: '/static/partials/blank.html',
resolve: {
features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}]
}
templateUrl: '/static/partials/blank.html'
};

View File

@ -0,0 +1,26 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$rootScope',
function ($rootScope) {
return {
response: function(config) {
if(config.headers('auth-token-timeout') !== null){
// $rootScope.sessionTimer = Number(config.headers('auth-token-timeout'));
$AnsibleConfig.session_timeout = Number(config.headers('auth-token-timeout'));
// $rootScope.sessionTimer = Timer.init();
}
return config;
}
};
}];

View File

@ -0,0 +1,16 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import restServicesFactory from './restServices.factory';
import interceptors from './interceptors.service';
export default
angular.module('RestServices', [])
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('RestInterceptor');
}])
.factory('Rest', restServicesFactory)
.service('RestInterceptor', interceptors);

View File

@ -55,8 +55,7 @@
*/
export default
angular.module('RestServices', ['ngCookies'])
.factory('Rest', ['$http', '$rootScope', '$cookieStore', '$q', 'Authorization',
['$http', '$rootScope', '$cookieStore', '$q', 'Authorization',
function ($http, $rootScope, $cookieStore, $q, Authorization) {
return {
@ -269,4 +268,4 @@ angular.module('RestServices', ['ngCookies'])
}
};
}
]);
];

View File

@ -4,7 +4,7 @@ import '../support/node';
import {describeModule} from '../support/describe-module';
import 'shared/Utilities';
import 'shared/RestServices';
import JobStatusGraph from 'dashboard/graphs/job-status/main';
var resizeHandler = sinon.spy();

View File

@ -58,6 +58,7 @@
<!-- Password Dialog -->
<div id="password-modal" style="display: none;"></div>
<div id="idle-modal" style="display:none">Your session will expire in 1 minute, would you like to continue?</div>
<!-- Generic Form dialog -->
<div id="form-modal" class="modal fade">