mirror of https://github.com/portainer/portainer
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 resolvepull/3398/head
parent
a3a83d1d7e
commit
1a65dbf85f
47
app/app.js
47
app/app.js
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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@': {}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
Loading…
Reference in New Issue