mirror of
https://github.com/portainer/portainer.git
synced 2025-07-21 06:19:41 +02:00
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
This commit is contained in:
parent
a3a83d1d7e
commit
1a65dbf85f
5 changed files with 92 additions and 103 deletions
47
app/app.js
47
app/app.js
|
@ -1,27 +1,13 @@
|
||||||
import _ from 'lodash-es';
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import '@babel/polyfill'
|
import '@babel/polyfill'
|
||||||
|
|
||||||
angular.module('portainer')
|
angular.module('portainer')
|
||||||
.run(['$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, Authentication, authManager, StateManager, EndpointProvider, Notifications, Analytics, SystemService, cfpLoadingBar, $transitions, HttpRequestHelper) {
|
function ($rootScope, $state, $interval, LocalStorage, EndpointProvider, SystemService, cfpLoadingBar, $transitions, HttpRequestHelper) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
EndpointProvider.initialize();
|
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;
|
$rootScope.$state = $state;
|
||||||
|
|
||||||
// Workaround to prevent the loading bar from going backward
|
// Workaround to prevent the loading bar from going backward
|
||||||
|
@ -37,6 +23,10 @@ function ($rootScope, $state, $interval, LocalStorage, Authentication, authManag
|
||||||
HttpRequestHelper.resetAgentHeaders();
|
HttpRequestHelper.resetAgentHeaders();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$state.defaultErrorHandler(function() {
|
||||||
|
// Do not log transitionTo errors
|
||||||
|
});
|
||||||
|
|
||||||
// Keep-alive Edge endpoints by sending a ping request every minute
|
// Keep-alive Edge endpoints by sending a ping request every minute
|
||||||
$interval(function() {
|
$interval(function() {
|
||||||
ping(EndpointProvider, SystemService);
|
ping(EndpointProvider, SystemService);
|
||||||
|
@ -58,28 +48,3 @@ function ping(EndpointProvider, SystemService) {
|
||||||
SystemService.ping(endpoint.Id);
|
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', [])
|
angular.module('portainer.app', [])
|
||||||
.config(['$stateRegistryProvider', function ($stateRegistryProvider) {
|
.config(['$stateRegistryProvider', function ($stateRegistryProvider) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
@ -6,10 +33,30 @@ angular.module('portainer.app', [])
|
||||||
name: 'root',
|
name: 'root',
|
||||||
abstract: true,
|
abstract: true,
|
||||||
resolve: {
|
resolve: {
|
||||||
requiresLogin: ['StateManager', function (StateManager) {
|
initStateManager: ['StateManager', 'Authentication', 'Notifications', 'Analytics', 'authManager', '$rootScope', '$state', '$async', '$q',
|
||||||
var applicationState = StateManager.getState();
|
(StateManager, Authentication, Notifications, Analytics, authManager, $rootScope, $state, $async, $q) => {
|
||||||
return applicationState.application.authentication;
|
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: {
|
views: {
|
||||||
'sidebar@': {
|
'sidebar@': {
|
||||||
|
@ -60,9 +107,6 @@ angular.module('portainer.app', [])
|
||||||
controllerAs: 'ctrl'
|
controllerAs: 'ctrl'
|
||||||
},
|
},
|
||||||
'sidebar@': {}
|
'sidebar@': {}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: false
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -170,9 +214,6 @@ angular.module('portainer.app', [])
|
||||||
name: 'portainer.init',
|
name: 'portainer.init',
|
||||||
abstract: true,
|
abstract: true,
|
||||||
url: '/init',
|
url: '/init',
|
||||||
data: {
|
|
||||||
requiresLogin: false
|
|
||||||
},
|
|
||||||
views: {
|
views: {
|
||||||
'sidebar@': {}
|
'sidebar@': {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
angular.module('portainer.app')
|
angular.module('portainer.app')
|
||||||
.factory('Authentication', [
|
.factory('Authentication', ['$async', '$state', 'Auth', 'OAuth', 'jwtHelper', 'LocalStorage', 'StateManager', 'EndpointProvider', 'UserService',
|
||||||
'$async', 'Auth', 'OAuth', 'jwtHelper', 'LocalStorage', 'StateManager', 'EndpointProvider', 'UserService',
|
function AuthenticationFactory($async, $state, Auth, OAuth, jwtHelper, LocalStorage, StateManager, EndpointProvider, UserService) {
|
||||||
function AuthenticationFactory($async, Auth, OAuth, jwtHelper, LocalStorage, StateManager, EndpointProvider, UserService) {
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var service = {};
|
var service = {};
|
||||||
|
@ -15,19 +14,32 @@ function AuthenticationFactory($async, Auth, OAuth, jwtHelper, LocalStorage, Sta
|
||||||
service.getUserDetails = getUserDetails;
|
service.getUserDetails = getUserDetails;
|
||||||
service.isAdmin = isAdmin;
|
service.isAdmin = isAdmin;
|
||||||
service.hasAuthorizations = hasAuthorizations;
|
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() {
|
function init() {
|
||||||
var jwt = LocalStorage.getJWT();
|
return $async(initAsync);
|
||||||
|
|
||||||
if (jwt) {
|
|
||||||
setUser(jwt);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function OAuthLoginAsync(code) {
|
async function OAuthLoginAsync(code) {
|
||||||
const response = await OAuth.validate({ code: code }).$promise;
|
const response = await OAuth.validate({ code: code }).$promise;
|
||||||
setUser(response.jwt);
|
await setUser(response.jwt);
|
||||||
}
|
}
|
||||||
|
|
||||||
function OAuthLogin(code) {
|
function OAuthLogin(code) {
|
||||||
|
@ -36,20 +48,13 @@ function AuthenticationFactory($async, Auth, OAuth, jwtHelper, LocalStorage, Sta
|
||||||
|
|
||||||
async function loginAsync(username, password) {
|
async function loginAsync(username, password) {
|
||||||
const response = await Auth.login({ username: username, password: password }).$promise;
|
const response = await Auth.login({ username: username, password: password }).$promise;
|
||||||
setUser(response.jwt);
|
await setUser(response.jwt);
|
||||||
}
|
}
|
||||||
|
|
||||||
function login(username, password) {
|
function login(username, password) {
|
||||||
return $async(loginAsync, username, password);
|
return $async(loginAsync, username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
|
||||||
StateManager.clean();
|
|
||||||
EndpointProvider.clean();
|
|
||||||
LocalStorage.clean();
|
|
||||||
LocalStorage.storeLoginStateUUID('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAuthenticated() {
|
function isAuthenticated() {
|
||||||
var jwt = LocalStorage.getJWT();
|
var jwt = LocalStorage.getJWT();
|
||||||
return jwt && !jwtHelper.isTokenExpired(jwt);
|
return jwt && !jwtHelper.isTokenExpired(jwt);
|
||||||
|
@ -59,20 +64,19 @@ function AuthenticationFactory($async, Auth, OAuth, jwtHelper, LocalStorage, Sta
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
function retrievePermissions() {
|
async function retrievePermissions() {
|
||||||
return UserService.user(user.ID)
|
const data = await UserService.user(user.ID);
|
||||||
.then((data) => {
|
user.endpointAuthorizations = data.EndpointAuthorizations;
|
||||||
user.endpointAuthorizations = data.EndpointAuthorizations;
|
user.portainerAuthorizations = data.PortainerAuthorizations;
|
||||||
user.portainerAuthorizations = data.PortainerAuthorizations;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUser(jwt) {
|
async function setUser(jwt) {
|
||||||
LocalStorage.storeJWT(jwt);
|
LocalStorage.storeJWT(jwt);
|
||||||
var tokenPayload = jwtHelper.decodeToken(jwt);
|
var tokenPayload = jwtHelper.decodeToken(jwt);
|
||||||
user.username = tokenPayload.username;
|
user.username = tokenPayload.username;
|
||||||
user.ID = tokenPayload.id;
|
user.ID = tokenPayload.id;
|
||||||
user.role = tokenPayload.role;
|
user.role = tokenPayload.role;
|
||||||
|
await retrievePermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAdmin() {
|
function isAdmin() {
|
||||||
|
|
|
@ -2,8 +2,8 @@ import _ from 'lodash-es';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
angular.module('portainer.app')
|
angular.module('portainer.app')
|
||||||
.factory('StateManager', ['$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, LocalStorage, SettingsService, StatusService, APPLICATION_CACHE_VALIDITY, AgentPingService) {
|
function StateManagerFactory($q, SystemService, InfoHelper, EndpointProvider, LocalStorage, SettingsService, StatusService, APPLICATION_CACHE_VALIDITY, AgentPingService) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var manager = {};
|
var manager = {};
|
||||||
|
@ -124,12 +124,8 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
|
||||||
var cacheValidity = now - applicationState.validity;
|
var cacheValidity = now - applicationState.validity;
|
||||||
if (cacheValidity > APPLICATION_CACHE_VALIDITY) {
|
if (cacheValidity > APPLICATION_CACHE_VALIDITY) {
|
||||||
loadApplicationState()
|
loadApplicationState()
|
||||||
.then(function success() {
|
.then(() => deferred.resolve(state))
|
||||||
deferred.resolve(state);
|
.catch((err) => deferred.reject(err));
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject(err);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
state.application = applicationState;
|
state.application = applicationState;
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
|
@ -137,12 +133,8 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
loadApplicationState()
|
loadApplicationState()
|
||||||
.then(function success() {
|
.then(() => deferred.resolve(state))
|
||||||
deferred.resolve(state);
|
.catch((err) => deferred.reject(err));
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject(err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
|
|
|
@ -32,7 +32,6 @@ class AuthenticationController {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.retrieveAndSaveEnabledExtensionsAsync = this.retrieveAndSaveEnabledExtensionsAsync.bind(this);
|
this.retrieveAndSaveEnabledExtensionsAsync = this.retrieveAndSaveEnabledExtensionsAsync.bind(this);
|
||||||
this.retrievePermissionsAsync = this.retrievePermissionsAsync.bind(this);
|
|
||||||
this.checkForEndpointsAsync = this.checkForEndpointsAsync.bind(this);
|
this.checkForEndpointsAsync = this.checkForEndpointsAsync.bind(this);
|
||||||
this.checkForLatestVersionAsync = this.checkForLatestVersionAsync.bind(this);
|
this.checkForLatestVersionAsync = this.checkForLatestVersionAsync.bind(this);
|
||||||
this.postLoginSteps = this.postLoginSteps.bind(this);
|
this.postLoginSteps = this.postLoginSteps.bind(this);
|
||||||
|
@ -103,16 +102,6 @@ class AuthenticationController {
|
||||||
* POST LOGIN STEPS SECTION
|
* 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() {
|
async retrieveAndSaveEnabledExtensionsAsync() {
|
||||||
try {
|
try {
|
||||||
await this.ExtensionService.retrieveAndSaveEnabledExtensions();
|
await this.ExtensionService.retrieveAndSaveEnabledExtensions();
|
||||||
|
@ -154,7 +143,6 @@ class AuthenticationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
async postLoginSteps() {
|
async postLoginSteps() {
|
||||||
await this.retrievePermissionsAsync();
|
|
||||||
await this.retrieveAndSaveEnabledExtensionsAsync();
|
await this.retrieveAndSaveEnabledExtensionsAsync();
|
||||||
await this.checkForEndpointsAsync(false);
|
await this.checkForEndpointsAsync(false);
|
||||||
await this.checkForLatestVersionAsync();
|
await this.checkForLatestVersionAsync();
|
||||||
|
@ -264,7 +252,6 @@ class AuthenticationController {
|
||||||
if (this.$stateParams.logout || this.$stateParams.error) {
|
if (this.$stateParams.logout || this.$stateParams.error) {
|
||||||
this.logout();
|
this.logout();
|
||||||
this.state.AuthenticationError = this.$stateParams.error;
|
this.state.AuthenticationError = this.$stateParams.error;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.Authentication.isAuthenticated()) {
|
if (this.Authentication.isAuthenticated()) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue