fix(app): permissions lost for UI on browser refresh (#3354)

* fix(app): permissions lost for UI on browser refresh

* fix(app): permissions retrieval moved to global app resolve
pull/3398/head
xAt0mZ 2019-11-26 05:01:39 +01:00 committed by Anthony Lapenna
parent a3a83d1d7e
commit 1a65dbf85f
5 changed files with 92 additions and 103 deletions

View File

@ -1,27 +1,13 @@
import _ from 'lodash-es';
import $ from 'jquery';
import '@babel/polyfill'
angular.module('portainer')
.run(['$rootScope', '$state', '$interval', 'LocalStorage', 'Authentication', 'authManager', 'StateManager', 'EndpointProvider', 'Notifications', 'Analytics', 'SystemService', 'cfpLoadingBar', '$transitions', 'HttpRequestHelper',
function ($rootScope, $state, $interval, LocalStorage, Authentication, authManager, StateManager, EndpointProvider, Notifications, Analytics, SystemService, cfpLoadingBar, $transitions, HttpRequestHelper) {
.run(['$rootScope', '$state', '$interval', 'LocalStorage', 'EndpointProvider', 'SystemService', 'cfpLoadingBar', '$transitions', 'HttpRequestHelper',
function ($rootScope, $state, $interval, LocalStorage, EndpointProvider, SystemService, cfpLoadingBar, $transitions, HttpRequestHelper) {
'use strict';
EndpointProvider.initialize();
StateManager.initialize()
.then(function success(state) {
if (state.application.authentication) {
initAuthentication(authManager, Authentication, $rootScope, $state);
}
if (state.application.analytics) {
initAnalytics(Analytics, $rootScope);
}
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve application settings');
});
$rootScope.$state = $state;
// Workaround to prevent the loading bar from going backward
@ -37,6 +23,10 @@ function ($rootScope, $state, $interval, LocalStorage, Authentication, authManag
HttpRequestHelper.resetAgentHeaders();
});
$state.defaultErrorHandler(function() {
// Do not log transitionTo errors
});
// Keep-alive Edge endpoints by sending a ping request every minute
$interval(function() {
ping(EndpointProvider, SystemService);
@ -58,28 +48,3 @@ function ping(EndpointProvider, SystemService) {
SystemService.ping(endpoint.Id);
}
}
function initAuthentication(authManager, Authentication, $rootScope, $state) {
authManager.checkAuthOnRefresh();
Authentication.init();
// The unauthenticated event is broadcasted by the jwtInterceptor when
// hitting a 401. We're using this instead of the usual combination of
// authManager.redirectWhenUnauthenticated() + unauthenticatedRedirector
// to have more controls on which URL should trigger the unauthenticated state.
$rootScope.$on('unauthenticated', function (event, data) {
if (!_.includes(data.config.url, '/v2/') && !_.includes(data.config.url, '/api/v4/')) {
$state.go('portainer.auth', { error: 'Your session has expired' });
}
});
}
function initAnalytics(Analytics, $rootScope) {
Analytics.offline(false);
Analytics.registerScriptTags();
Analytics.registerTrackers();
$rootScope.$on('$stateChangeSuccess', function (event, toState) {
Analytics.trackPage(toState.url);
Analytics.pageView();
});
}

View File

@ -1,3 +1,30 @@
import _ from 'lodash-es';
async function initAuthentication(authManager, Authentication, $rootScope, $state) {
authManager.checkAuthOnRefresh();
// The unauthenticated event is broadcasted by the jwtInterceptor when
// hitting a 401. We're using this instead of the usual combination of
// authManager.redirectWhenUnauthenticated() + unauthenticatedRedirector
// to have more controls on which URL should trigger the unauthenticated state.
$rootScope.$on('unauthenticated', function (event, data) {
if (!_.includes(data.config.url, '/v2/') && !_.includes(data.config.url, '/api/v4/')) {
$state.go('portainer.auth', { error: 'Your session has expired' });
}
});
await Authentication.init();
}
function initAnalytics(Analytics, $rootScope) {
Analytics.offline(false);
Analytics.registerScriptTags();
Analytics.registerTrackers();
$rootScope.$on('$stateChangeSuccess', function (event, toState) {
Analytics.trackPage(toState.url);
Analytics.pageView();
});
}
angular.module('portainer.app', [])
.config(['$stateRegistryProvider', function ($stateRegistryProvider) {
'use strict';
@ -6,10 +33,30 @@ angular.module('portainer.app', [])
name: 'root',
abstract: true,
resolve: {
requiresLogin: ['StateManager', function (StateManager) {
var applicationState = StateManager.getState();
return applicationState.application.authentication;
}]
initStateManager: ['StateManager', 'Authentication', 'Notifications', 'Analytics', 'authManager', '$rootScope', '$state', '$async', '$q',
(StateManager, Authentication, Notifications, Analytics, authManager, $rootScope, $state, $async, $q) => {
const deferred = $q.defer();
const appState = StateManager.getState();
if (!appState.loading) {
deferred.resolve();
} else {
StateManager.initialize()
.then(function success(state) {
if (state.application.analytics) {
initAnalytics(Analytics, $rootScope);
}
if (state.application.authentication) {
return $async(initAuthentication, authManager, Authentication, $rootScope, $state);
}
})
.then(() => deferred.resolve())
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve application settings');
deferred.reject(err);
});
}
return deferred.promise;
}]
},
views: {
'sidebar@': {
@ -60,9 +107,6 @@ angular.module('portainer.app', [])
controllerAs: 'ctrl'
},
'sidebar@': {}
},
data: {
requiresLogin: false
}
};
@ -170,9 +214,6 @@ angular.module('portainer.app', [])
name: 'portainer.init',
abstract: true,
url: '/init',
data: {
requiresLogin: false
},
views: {
'sidebar@': {}
}

View File

@ -1,7 +1,6 @@
angular.module('portainer.app')
.factory('Authentication', [
'$async', 'Auth', 'OAuth', 'jwtHelper', 'LocalStorage', 'StateManager', 'EndpointProvider', 'UserService',
function AuthenticationFactory($async, Auth, OAuth, jwtHelper, LocalStorage, StateManager, EndpointProvider, UserService) {
.factory('Authentication', ['$async', '$state', 'Auth', 'OAuth', 'jwtHelper', 'LocalStorage', 'StateManager', 'EndpointProvider', 'UserService',
function AuthenticationFactory($async, $state, Auth, OAuth, jwtHelper, LocalStorage, StateManager, EndpointProvider, UserService) {
'use strict';
var service = {};
@ -15,19 +14,32 @@ function AuthenticationFactory($async, Auth, OAuth, jwtHelper, LocalStorage, Sta
service.getUserDetails = getUserDetails;
service.isAdmin = isAdmin;
service.hasAuthorizations = hasAuthorizations;
service.retrievePermissions = retrievePermissions;
async function initAsync() {
try {
const jwt = LocalStorage.getJWT();
if (jwt) {
await setUser(jwt);
}
} catch (error) {
throw error;
}
}
function logout() {
StateManager.clean();
EndpointProvider.clean();
LocalStorage.clean();
LocalStorage.storeLoginStateUUID('');
}
function init() {
var jwt = LocalStorage.getJWT();
if (jwt) {
setUser(jwt);
}
return $async(initAsync);
}
async function OAuthLoginAsync(code) {
const response = await OAuth.validate({ code: code }).$promise;
setUser(response.jwt);
await setUser(response.jwt);
}
function OAuthLogin(code) {
@ -36,20 +48,13 @@ function AuthenticationFactory($async, Auth, OAuth, jwtHelper, LocalStorage, Sta
async function loginAsync(username, password) {
const response = await Auth.login({ username: username, password: password }).$promise;
setUser(response.jwt);
await setUser(response.jwt);
}
function login(username, password) {
return $async(loginAsync, username, password);
}
function logout() {
StateManager.clean();
EndpointProvider.clean();
LocalStorage.clean();
LocalStorage.storeLoginStateUUID('');
}
function isAuthenticated() {
var jwt = LocalStorage.getJWT();
return jwt && !jwtHelper.isTokenExpired(jwt);
@ -59,20 +64,19 @@ function AuthenticationFactory($async, Auth, OAuth, jwtHelper, LocalStorage, Sta
return user;
}
function retrievePermissions() {
return UserService.user(user.ID)
.then((data) => {
user.endpointAuthorizations = data.EndpointAuthorizations;
user.portainerAuthorizations = data.PortainerAuthorizations;
});
async function retrievePermissions() {
const data = await UserService.user(user.ID);
user.endpointAuthorizations = data.EndpointAuthorizations;
user.portainerAuthorizations = data.PortainerAuthorizations;
}
function setUser(jwt) {
async function setUser(jwt) {
LocalStorage.storeJWT(jwt);
var tokenPayload = jwtHelper.decodeToken(jwt);
user.username = tokenPayload.username;
user.ID = tokenPayload.id;
user.role = tokenPayload.role;
await retrievePermissions();
}
function isAdmin() {

View File

@ -2,8 +2,8 @@ import _ from 'lodash-es';
import moment from 'moment';
angular.module('portainer.app')
.factory('StateManager', ['$q', 'SystemService', 'InfoHelper', 'LocalStorage', 'SettingsService', 'StatusService', 'APPLICATION_CACHE_VALIDITY', 'AgentPingService',
function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, SettingsService, StatusService, APPLICATION_CACHE_VALIDITY, AgentPingService) {
.factory('StateManager', ['$q', 'SystemService', 'InfoHelper', 'EndpointProvider', 'LocalStorage', 'SettingsService', 'StatusService', 'APPLICATION_CACHE_VALIDITY', 'AgentPingService',
function StateManagerFactory($q, SystemService, InfoHelper, EndpointProvider, LocalStorage, SettingsService, StatusService, APPLICATION_CACHE_VALIDITY, AgentPingService) {
'use strict';
var manager = {};
@ -124,12 +124,8 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
var cacheValidity = now - applicationState.validity;
if (cacheValidity > APPLICATION_CACHE_VALIDITY) {
loadApplicationState()
.then(function success() {
deferred.resolve(state);
})
.catch(function error(err) {
deferred.reject(err);
});
.then(() => deferred.resolve(state))
.catch((err) => deferred.reject(err));
} else {
state.application = applicationState;
state.loading = false;
@ -137,12 +133,8 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
}
} else {
loadApplicationState()
.then(function success() {
deferred.resolve(state);
})
.catch(function error(err) {
deferred.reject(err);
});
.then(() => deferred.resolve(state))
.catch((err) => deferred.reject(err));
}
return deferred.promise;

View File

@ -32,7 +32,6 @@ class AuthenticationController {
};
this.retrieveAndSaveEnabledExtensionsAsync = this.retrieveAndSaveEnabledExtensionsAsync.bind(this);
this.retrievePermissionsAsync = this.retrievePermissionsAsync.bind(this);
this.checkForEndpointsAsync = this.checkForEndpointsAsync.bind(this);
this.checkForLatestVersionAsync = this.checkForLatestVersionAsync.bind(this);
this.postLoginSteps = this.postLoginSteps.bind(this);
@ -103,16 +102,6 @@ class AuthenticationController {
* POST LOGIN STEPS SECTION
*/
async retrievePermissionsAsync() {
try {
await this.Authentication.retrievePermissions();
} catch (err) {
this.state.permissionsError = true;
this.logout();
this.error(err, 'Unable to retrieve permissions.');
}
}
async retrieveAndSaveEnabledExtensionsAsync() {
try {
await this.ExtensionService.retrieveAndSaveEnabledExtensions();
@ -154,7 +143,6 @@ class AuthenticationController {
}
async postLoginSteps() {
await this.retrievePermissionsAsync();
await this.retrieveAndSaveEnabledExtensionsAsync();
await this.checkForEndpointsAsync(false);
await this.checkForLatestVersionAsync();
@ -264,7 +252,6 @@ class AuthenticationController {
if (this.$stateParams.logout || this.$stateParams.error) {
this.logout();
this.state.AuthenticationError = this.$stateParams.error;
return;
}
if (this.Authentication.isAuthenticated()) {