MOBILE-4028 logout: Refactor logout process
Now it uses a logout page so Angular guards are triggered before doing the logout process.main
parent
f39d4a9240
commit
feff5a87f8
|
@ -216,16 +216,7 @@ export class CoreContentLinksDelegateService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Site is logged out, authenticate first before treating the URL.
|
// Site is logged out, authenticate first before treating the URL.
|
||||||
const willReload = await CoreSites.logoutForRedirect(siteId, {
|
await CoreSites.logout({ urlToOpen: url, siteId });
|
||||||
urlToOpen: url,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!willReload) {
|
|
||||||
// Load the site with the redirect data.
|
|
||||||
await CoreSites.loadSite(siteId, {
|
|
||||||
urlToOpen: url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,10 @@ const appRoutes: Routes = [
|
||||||
loadChildren: () => import('./login-lazy.module'),
|
loadChildren: () => import('./login-lazy.module'),
|
||||||
canActivate: [redirectGuard],
|
canActivate: [redirectGuard],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'logout',
|
||||||
|
loadComponent: () => import('@features/login/pages/logout/logout'),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<ion-content>
|
||||||
|
<core-loading />
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,104 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreConstants } from '@/core/constants';
|
||||||
|
import { CoreNavigationOptions, CoreNavigator, CoreRedirectPayload } from '@services/navigator';
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
|
||||||
|
import { CoreRedirects } from '@singletons/redirects';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that logs the user out.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-core-login-logout',
|
||||||
|
templateUrl: 'logout.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CoreSharedModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export default class CoreLoginLogoutPage implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
const siteId = CoreNavigator.getRouteParam('siteId') ?? CoreConstants.NO_SITE_ID;
|
||||||
|
const logoutOptions = {
|
||||||
|
forceLogout: CoreNavigator.getRouteBooleanParam('forceLogout'),
|
||||||
|
removeAccount: CoreNavigator.getRouteBooleanParam('removeAccount') ?? !!CoreConstants.CONFIG.removeaccountonlogout,
|
||||||
|
};
|
||||||
|
const redirectData = {
|
||||||
|
redirectPath: CoreNavigator.getRouteParam('redirectPath'),
|
||||||
|
redirectOptions: CoreNavigator.getRouteParam<CoreNavigationOptions>('redirectOptions'),
|
||||||
|
urlToOpen: CoreNavigator.getRouteParam('urlToOpen'),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!CoreSites.isLoggedIn()) {
|
||||||
|
// This page shouldn't open if user isn't logged in, but if that happens just navigate to the right page.
|
||||||
|
await this.navigateAfterLogout(siteId, redirectData);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldReload = CoreSitePlugins.hasSitePluginsLoaded;
|
||||||
|
if (shouldReload && (siteId !== CoreConstants.NO_SITE_ID || redirectData.redirectPath || redirectData.urlToOpen)) {
|
||||||
|
// The app will reload and we need to open a page that isn't the default page. Store the redirect first.
|
||||||
|
CoreRedirects.storeRedirect(siteId, redirectData);
|
||||||
|
}
|
||||||
|
|
||||||
|
await CoreSites.internalLogout(logoutOptions);
|
||||||
|
|
||||||
|
if (shouldReload) {
|
||||||
|
// We need to reload the app to unload all the plugins. Leave the logout page first.
|
||||||
|
await CoreNavigator.navigate('/login', { reset: true });
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.navigateAfterLogout(siteId, redirectData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the right page after logout is done.
|
||||||
|
*
|
||||||
|
* @param siteId Site ID to load.
|
||||||
|
* @param redirectData Redirect data.
|
||||||
|
*/
|
||||||
|
protected async navigateAfterLogout(siteId: string, redirectData: CoreRedirectPayload): Promise<void> {
|
||||||
|
if (siteId === CoreConstants.NO_SITE_ID) {
|
||||||
|
// No site to load now, just navigate.
|
||||||
|
await CoreNavigator.navigate(redirectData.redirectPath ?? '/login/sites', {
|
||||||
|
...redirectData.redirectOptions,
|
||||||
|
reset: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the site and navigate.
|
||||||
|
const loggedIn = await CoreSites.loadSite(siteId, redirectData);
|
||||||
|
if (!loggedIn) {
|
||||||
|
return; // Session expired.
|
||||||
|
}
|
||||||
|
|
||||||
|
await CoreNavigator.navigateToSiteHome({ params: redirectData, preferCurrentTab: false, siteId });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -412,22 +412,19 @@ export class CoreLoginHelperProvider {
|
||||||
* @returns Promise resolved when done.
|
* @returns Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async goToAddSite(setRoot = false, showKeyboard = false): Promise<void> {
|
async goToAddSite(setRoot = false, showKeyboard = false): Promise<void> {
|
||||||
let path = '/login/sites';
|
|
||||||
let params: Params = { openAddSite: true , showKeyboard };
|
|
||||||
|
|
||||||
if (CoreSites.isLoggedIn()) {
|
if (CoreSites.isLoggedIn()) {
|
||||||
const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
|
// Logout first.
|
||||||
redirectPath: path,
|
await CoreSites.logout({
|
||||||
redirectOptions: { params },
|
siteId: CoreConstants.NO_SITE_ID,
|
||||||
|
redirectPath: '/login/sites',
|
||||||
|
redirectOptions: { params: { openAddSite: true , showKeyboard } },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (willReload) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
[path, params] = await this.getAddSiteRouteInfo(showKeyboard);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [path, params] = await this.getAddSiteRouteInfo(showKeyboard);
|
||||||
|
|
||||||
await CoreNavigator.navigate(path, { params, reset: setRoot });
|
await CoreNavigator.navigate(path, { params, reset: setRoot });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { CoreSite } from '@classes/sites/site';
|
import { CoreSite } from '@classes/sites/site';
|
||||||
import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
|
import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
|
||||||
import { CoreFilter } from '@features/filter/services/filter';
|
import { CoreFilter } from '@features/filter/services/filter';
|
||||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
|
||||||
import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
|
import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
|
||||||
import { CoreUserSupport } from '@features/user/services/support';
|
import { CoreUserSupport } from '@features/user/services/support';
|
||||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
|
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
|
||||||
|
@ -33,8 +32,9 @@ import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CorePromiseUtils } from '@singletons/promise-utils';
|
import { CorePromiseUtils } from '@singletons/promise-utils';
|
||||||
import { ModalController, Translate } from '@singletons';
|
import { ModalController } from '@singletons';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display a user menu.
|
* Component to display a user menu.
|
||||||
|
@ -208,12 +208,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
|
||||||
* @param event Click event
|
* @param event Click event
|
||||||
*/
|
*/
|
||||||
async logout(event: Event): Promise<void> {
|
async logout(event: Event): Promise<void> {
|
||||||
if (CoreNavigator.currentRouteCanBlockLeave()) {
|
|
||||||
await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.removeAccountOnLogout) {
|
if (this.removeAccountOnLogout) {
|
||||||
// Ask confirm.
|
// Ask confirm.
|
||||||
const siteName = this.siteName ?
|
const siteName = this.siteName ?
|
||||||
|
@ -242,12 +236,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
|
||||||
* @param event Click event
|
* @param event Click event
|
||||||
*/
|
*/
|
||||||
async switchAccounts(event: Event): Promise<void> {
|
async switchAccounts(event: Event): Promise<void> {
|
||||||
if (CoreNavigator.currentRouteCanBlockLeave()) {
|
|
||||||
await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const thisModal = await ModalController.getTop();
|
const thisModal = await ModalController.getTop();
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
@ -280,9 +280,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy {
|
||||||
* @returns Promise resolved when done.
|
* @returns Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async cancel(): Promise<void> {
|
async cancel(): Promise<void> {
|
||||||
await CorePromiseUtils.ignoreErrors(CoreSites.logout());
|
await CoreSites.logout();
|
||||||
|
|
||||||
await CoreNavigator.navigate('/login/sites', { reset: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,6 +32,7 @@ describe('Site Home link handlers', () => {
|
||||||
isStoredRootURL: () => Promise.resolve({ siteIds: [siteId] }),
|
isStoredRootURL: () => Promise.resolve({ siteIds: [siteId] }),
|
||||||
getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')),
|
getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')),
|
||||||
getSiteIdsFromUrl: () => Promise.resolve([siteId]),
|
getSiteIdsFromUrl: () => Promise.resolve([siteId]),
|
||||||
|
getCurrentSiteId: () => siteId,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] });
|
mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] });
|
||||||
|
|
|
@ -220,14 +220,13 @@ export class CoreNavigatorService {
|
||||||
|
|
||||||
// If we are logged into a different site, log out first.
|
// If we are logged into a different site, log out first.
|
||||||
if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) {
|
if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) {
|
||||||
const willReload = await CoreSites.logoutForRedirect(siteId, {
|
await CoreSites.logout({
|
||||||
redirectPath: path,
|
redirectPath: path,
|
||||||
redirectOptions: options || {},
|
redirectOptions: options || {},
|
||||||
|
siteId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (willReload) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the path doesn't belong to a site, call standard navigation.
|
// If the path doesn't belong to a site, call standard navigation.
|
||||||
|
|
|
@ -141,14 +141,6 @@ export class CoreSitesProvider {
|
||||||
|
|
||||||
// Remove version classes from body.
|
// Remove version classes from body.
|
||||||
CoreHTMLClasses.removeSiteClasses();
|
CoreHTMLClasses.removeSiteClasses();
|
||||||
|
|
||||||
// Go to sites page when user is logged out.
|
|
||||||
await CoreNavigator.navigate('/login/sites', { reset: true });
|
|
||||||
|
|
||||||
if (CoreSitePlugins.hasSitePluginsLoaded) {
|
|
||||||
// Temporary fix. Reload the page to unload all plugins.
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
CoreEvents.on(CoreEvents.LOGIN, async (data) => {
|
CoreEvents.on(CoreEvents.LOGIN, async (data) => {
|
||||||
|
@ -964,7 +956,7 @@ export class CoreSitesProvider {
|
||||||
promise.finally(() => {
|
promise.finally(() => {
|
||||||
if (siteId) {
|
if (siteId) {
|
||||||
// Logout the currentSite and expire the token.
|
// Logout the currentSite and expire the token.
|
||||||
this.logout();
|
this.internalLogout();
|
||||||
this.setSiteLoggedOut(siteId);
|
this.setSiteLoggedOut(siteId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1123,7 +1115,7 @@ export class CoreSitesProvider {
|
||||||
this.logger.debug(`Delete site ${siteId}`);
|
this.logger.debug(`Delete site ${siteId}`);
|
||||||
|
|
||||||
if (this.currentSite !== undefined && this.currentSite.id == siteId) {
|
if (this.currentSite !== undefined && this.currentSite.id == siteId) {
|
||||||
this.logout();
|
this.internalLogout();
|
||||||
}
|
}
|
||||||
|
|
||||||
const site = await this.getSite(siteId);
|
const site = await this.getSite(siteId);
|
||||||
|
@ -1457,10 +1449,23 @@ export class CoreSitesProvider {
|
||||||
/**
|
/**
|
||||||
* Logout the user.
|
* Logout the user.
|
||||||
*
|
*
|
||||||
* @param options Logout options.
|
* @param options Options.
|
||||||
* @returns Promise resolved when the user is logged out.
|
|
||||||
*/
|
*/
|
||||||
async logout(options: CoreSitesLogoutOptions = {}): Promise<void> {
|
async logout(options: CoreSitesLogoutOptions = {}): Promise<void> {
|
||||||
|
await CoreNavigator.navigate('/logout', {
|
||||||
|
params: { ...options },
|
||||||
|
reset: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout the user.
|
||||||
|
* This function is for internal usage, please use CoreSites.logout instead. The reason this function is public is because
|
||||||
|
* it's called from the CoreLoginLogoutPage page.
|
||||||
|
*
|
||||||
|
* @param options Logout options.
|
||||||
|
*/
|
||||||
|
async internalLogout(options: InternalLogoutOptions = {}): Promise<void> {
|
||||||
if (!this.currentSite) {
|
if (!this.currentSite) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1494,6 +1499,7 @@ export class CoreSitesProvider {
|
||||||
* @param siteId Site that will be opened after logout.
|
* @param siteId Site that will be opened after logout.
|
||||||
* @param redirectData Page/url to open after logout.
|
* @param redirectData Page/url to open after logout.
|
||||||
* @returns Promise resolved with boolean: true if app will be reloaded after logout.
|
* @returns Promise resolved with boolean: true if app will be reloaded after logout.
|
||||||
|
* @deprecated since 5.0. Use CoreSites.logout instead, it automatically handles redirects.
|
||||||
*/
|
*/
|
||||||
async logoutForRedirect(siteId: string, redirectData: CoreRedirectPayload): Promise<boolean> {
|
async logoutForRedirect(siteId: string, redirectData: CoreRedirectPayload): Promise<boolean> {
|
||||||
if (!this.currentSite) {
|
if (!this.currentSite) {
|
||||||
|
@ -1505,7 +1511,7 @@ export class CoreSitesProvider {
|
||||||
CoreRedirects.storeRedirect(siteId, redirectData);
|
CoreRedirects.storeRedirect(siteId, redirectData);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.logout();
|
await this.internalLogout();
|
||||||
|
|
||||||
return CoreSitePlugins.hasSitePluginsLoaded;
|
return CoreSitePlugins.hasSitePluginsLoaded;
|
||||||
}
|
}
|
||||||
|
@ -2480,7 +2486,14 @@ export type CoreSitesLoginTokenResponse = {
|
||||||
/**
|
/**
|
||||||
* Options for logout.
|
* Options for logout.
|
||||||
*/
|
*/
|
||||||
export type CoreSitesLogoutOptions = {
|
export type CoreSitesLogoutOptions = CoreRedirectPayload & InternalLogoutOptions & {
|
||||||
|
siteId?: string; // Site ID to load after logout.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for internal logout.
|
||||||
|
*/
|
||||||
|
type InternalLogoutOptions = {
|
||||||
forceLogout?: boolean; // If true, site will be marked as logged out, no matter the value tool_mobile_forcelogout.
|
forceLogout?: boolean; // If true, site will be marked as logged out, no matter the value tool_mobile_forcelogout.
|
||||||
removeAccount?: boolean; // If true, site will be removed too after logout.
|
removeAccount?: boolean; // If true, site will be removed too after logout.
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,7 +16,6 @@ import { CoreEvents } from '@singletons/events';
|
||||||
import { CoreLang, CoreLangProvider } from '@services/lang';
|
import { CoreLang, CoreLangProvider } from '@services/lang';
|
||||||
|
|
||||||
import { mock, mockSingleton } from '@/testing/utils';
|
import { mock, mockSingleton } from '@/testing/utils';
|
||||||
import { CoreNavigator, CoreNavigatorService } from '@services/navigator';
|
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { Http } from '@singletons';
|
import { Http } from '@singletons';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
@ -34,13 +33,10 @@ describe('CoreSitesProvider', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cleans up on logout', async () => {
|
it('cleans up on logout', async () => {
|
||||||
const navigator: CoreNavigatorService = mockSingleton(CoreNavigator, ['navigate']);
|
|
||||||
|
|
||||||
CoreSites.initialize();
|
CoreSites.initialize();
|
||||||
CoreEvents.trigger(CoreEvents.LOGOUT);
|
CoreEvents.trigger(CoreEvents.LOGOUT);
|
||||||
|
|
||||||
expect(langProvider.clearCustomStrings).toHaveBeenCalled();
|
expect(langProvider.clearCustomStrings).toHaveBeenCalled();
|
||||||
expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds ionic platform and theme classes', async () => {
|
it('adds ionic platform and theme classes', async () => {
|
||||||
|
|
|
@ -432,14 +432,13 @@ export class CoreCustomURLSchemesProvider {
|
||||||
// Ask the user before changing site.
|
// Ask the user before changing site.
|
||||||
await CoreDomUtils.showConfirm(Translate.instant('core.contentlinks.confirmurlothersite'));
|
await CoreDomUtils.showConfirm(Translate.instant('core.contentlinks.confirmurlothersite'));
|
||||||
|
|
||||||
const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
|
await CoreSites.logout({
|
||||||
|
siteId: CoreConstants.NO_SITE_ID,
|
||||||
redirectPath: '/login/credentials',
|
redirectPath: '/login/credentials',
|
||||||
redirectOptions: { params: pageParams },
|
redirectOptions: { params: pageParams },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (willReload) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await CoreNavigator.navigateToLoginCredentials(pageParams);
|
await CoreNavigator.navigateToLoginCredentials(pageParams);
|
||||||
|
|
|
@ -2,6 +2,10 @@ This file describes API changes in the Moodle App that affect site plugins, info
|
||||||
|
|
||||||
For more information about upgrading, read the official documentation: https://moodledev.io/general/app/upgrading/
|
For more information about upgrading, read the official documentation: https://moodledev.io/general/app/upgrading/
|
||||||
|
|
||||||
|
=== 5.0.0 ===
|
||||||
|
|
||||||
|
- The logout process has been refactored, now it uses a logout page to trigger Angular guards. CoreSites.logout now uses this process, and CoreSites.logoutForRedirect is deprecated and shouldn't be used anymore.
|
||||||
|
|
||||||
=== 4.5.0 ===
|
=== 4.5.0 ===
|
||||||
|
|
||||||
- Ionic has been upgraded to major version 8. See breaking changes and upgrade guide here: https://ionicframework.com/docs/updating/8-0
|
- Ionic has been upgraded to major version 8. See breaking changes and upgrade guide here: https://ionicframework.com/docs/updating/8-0
|
||||||
|
|
Loading…
Reference in New Issue