1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-07 14:55:27 +02:00

fix(app/logout): always perform API logout + make API logout route public [EE-6198] (#10448)

* feat(api/logout): make logout route public

* feat(app/logout): always perform API logout on /logout redirect

* fix(app): send a logout event to AngularJS when axios hits a 401
This commit is contained in:
LP B 2023-10-27 14:44:05 +02:00 committed by GitHub
parent 47fa1626c6
commit 9e60723e4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 54 additions and 27 deletions

View file

@ -12,18 +12,33 @@ import { reactModule } from './react';
import { sidebarModule } from './react/views/sidebar';
import environmentsModule from './environments';
import { helpersModule } from './helpers';
import { AXIOS_UNAUTHENTICATED } from './services/axios';
async function initAuthentication(authManager, Authentication, $rootScope, $state) {
authManager.checkAuthOnRefresh();
function handleUnauthenticated(data, performReload) {
if (!_.includes(data.config.url, '/v2/') && !_.includes(data.config.url, '/api/v4/') && isTransitionRequiresAuthentication($state.transition)) {
$state.go('portainer.logout', { error: 'Your session has expired' });
if (performReload) {
window.location.reload();
}
}
}
// 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/') && isTransitionRequiresAuthentication($state.transition)) {
$state.go('portainer.logout', { error: 'Your session has expired' });
window.location.reload();
}
handleUnauthenticated(data, true);
});
// the AXIOS_UNAUTHENTICATED event is emitted by axios when a request returns with a 401 code
// the event contains the entire AxiosError in detail.err
window.addEventListener(AXIOS_UNAUTHENTICATED, (event) => {
const data = event.detail.err;
handleUnauthenticated(data);
});
return await Authentication.init();
@ -157,7 +172,6 @@ angular
url: '/logout',
params: {
error: '',
performApiLogout: true,
},
views: {
'content@': {

View file

@ -40,8 +40,8 @@ angular.module('portainer.app').factory('Authentication', [
}
}
async function logoutAsync(performApiLogout) {
if (performApiLogout && isAuthenticated()) {
async function logoutAsync() {
if (isAuthenticated()) {
await Auth.logout().$promise;
}
@ -53,8 +53,8 @@ angular.module('portainer.app').factory('Authentication', [
tryAutoLoginExtension();
}
function logout(performApiLogout) {
return $async(logoutAsync, performApiLogout);
function logout() {
return $async(logoutAsync);
}
function init() {

View file

@ -49,6 +49,8 @@ export function agentInterceptor(config: AxiosRequestConfig) {
axios.interceptors.request.use(agentInterceptor);
export const AXIOS_UNAUTHENTICATED = '__axios__unauthenticated__';
/**
* Parses an Axios error and returns a PortainerError.
* @param err The original error.
@ -72,6 +74,16 @@ export function parseAxiosError(
} else {
resultMsg = msg || details;
}
// dispatch an event for unauthorized errors that AngularJS can catch
if (err.response?.status === 401) {
dispatchEvent(
new CustomEvent(AXIOS_UNAUTHENTICATED, {
detail: {
err,
},
})
);
}
}
return new PortainerError(resultMsg, resultErr);

View file

@ -25,11 +25,10 @@ class LogoutController {
*/
async logoutAsync() {
const error = this.$transition$.params().error;
const performApiLogout = this.$transition$.params().performApiLogout;
const settings = await this.SettingsService.publicSettings();
try {
await this.Authentication.logout(performApiLogout);
await this.Authentication.logout();
} finally {
this.LocalStorage.storeLogoutReason(error);
if (settings.OAuthLogoutURI && this.Authentication.getUserDetails().ID !== 1) {