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.
|
||||
const willReload = await CoreSites.logoutForRedirect(siteId, {
|
||||
urlToOpen: url,
|
||||
});
|
||||
|
||||
if (!willReload) {
|
||||
// Load the site with the redirect data.
|
||||
await CoreSites.loadSite(siteId, {
|
||||
urlToOpen: url,
|
||||
});
|
||||
}
|
||||
await CoreSites.logout({ urlToOpen: url, siteId });
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -41,6 +41,10 @@ const appRoutes: Routes = [
|
|||
loadChildren: () => import('./login-lazy.module'),
|
||||
canActivate: [redirectGuard],
|
||||
},
|
||||
{
|
||||
path: 'logout',
|
||||
loadComponent: () => import('@features/login/pages/logout/logout'),
|
||||
},
|
||||
];
|
||||
|
||||
@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,21 +412,18 @@ export class CoreLoginHelperProvider {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async goToAddSite(setRoot = false, showKeyboard = false): Promise<void> {
|
||||
let path = '/login/sites';
|
||||
let params: Params = { openAddSite: true , showKeyboard };
|
||||
|
||||
if (CoreSites.isLoggedIn()) {
|
||||
const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
|
||||
redirectPath: path,
|
||||
redirectOptions: { params },
|
||||
// Logout first.
|
||||
await CoreSites.logout({
|
||||
siteId: CoreConstants.NO_SITE_ID,
|
||||
redirectPath: '/login/sites',
|
||||
redirectOptions: { params: { openAddSite: true , showKeyboard } },
|
||||
});
|
||||
|
||||
if (willReload) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
[path, params] = await this.getAddSiteRouteInfo(showKeyboard);
|
||||
}
|
||||
|
||||
const [path, params] = await this.getAddSiteRouteInfo(showKeyboard);
|
||||
|
||||
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 { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
|
||||
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 { CoreUserSupport } from '@features/user/services/support';
|
||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
|
||||
|
@ -33,8 +32,9 @@ import { CoreNavigator } from '@services/navigator';
|
|||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CorePromiseUtils } from '@singletons/promise-utils';
|
||||
import { ModalController, Translate } from '@singletons';
|
||||
import { ModalController } from '@singletons';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
|
||||
/**
|
||||
* Component to display a user menu.
|
||||
|
@ -208,12 +208,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
|
|||
* @param event Click event
|
||||
*/
|
||||
async logout(event: Event): Promise<void> {
|
||||
if (CoreNavigator.currentRouteCanBlockLeave()) {
|
||||
await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.removeAccountOnLogout) {
|
||||
// Ask confirm.
|
||||
const siteName = this.siteName ?
|
||||
|
@ -242,12 +236,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
|
|||
* @param event Click event
|
||||
*/
|
||||
async switchAccounts(event: Event): Promise<void> {
|
||||
if (CoreNavigator.currentRouteCanBlockLeave()) {
|
||||
await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const thisModal = await ModalController.getTop();
|
||||
|
||||
event.preventDefault();
|
||||
|
|
|
@ -280,9 +280,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async cancel(): Promise<void> {
|
||||
await CorePromiseUtils.ignoreErrors(CoreSites.logout());
|
||||
|
||||
await CoreNavigator.navigate('/login/sites', { reset: true });
|
||||
await CoreSites.logout();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -32,6 +32,7 @@ describe('Site Home link handlers', () => {
|
|||
isStoredRootURL: () => Promise.resolve({ siteIds: [siteId] }),
|
||||
getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')),
|
||||
getSiteIdsFromUrl: () => Promise.resolve([siteId]),
|
||||
getCurrentSiteId: () => siteId,
|
||||
}));
|
||||
|
||||
mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] });
|
||||
|
|
|
@ -220,15 +220,14 @@ export class CoreNavigatorService {
|
|||
|
||||
// If we are logged into a different site, log out first.
|
||||
if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) {
|
||||
const willReload = await CoreSites.logoutForRedirect(siteId, {
|
||||
await CoreSites.logout({
|
||||
redirectPath: path,
|
||||
redirectOptions: options || {},
|
||||
siteId,
|
||||
});
|
||||
|
||||
if (willReload) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the path doesn't belong to a site, call standard navigation.
|
||||
if (siteId === CoreConstants.NO_SITE_ID) {
|
||||
|
|
|
@ -141,14 +141,6 @@ export class CoreSitesProvider {
|
|||
|
||||
// Remove version classes from body.
|
||||
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) => {
|
||||
|
@ -964,7 +956,7 @@ export class CoreSitesProvider {
|
|||
promise.finally(() => {
|
||||
if (siteId) {
|
||||
// Logout the currentSite and expire the token.
|
||||
this.logout();
|
||||
this.internalLogout();
|
||||
this.setSiteLoggedOut(siteId);
|
||||
}
|
||||
});
|
||||
|
@ -1123,7 +1115,7 @@ export class CoreSitesProvider {
|
|||
this.logger.debug(`Delete site ${siteId}`);
|
||||
|
||||
if (this.currentSite !== undefined && this.currentSite.id == siteId) {
|
||||
this.logout();
|
||||
this.internalLogout();
|
||||
}
|
||||
|
||||
const site = await this.getSite(siteId);
|
||||
|
@ -1457,10 +1449,23 @@ export class CoreSitesProvider {
|
|||
/**
|
||||
* Logout the user.
|
||||
*
|
||||
* @param options Logout options.
|
||||
* @returns Promise resolved when the user is logged out.
|
||||
* @param options Options.
|
||||
*/
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
@ -1494,6 +1499,7 @@ export class CoreSitesProvider {
|
|||
* @param siteId Site that will be opened after logout.
|
||||
* @param redirectData Page/url to open 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> {
|
||||
if (!this.currentSite) {
|
||||
|
@ -1505,7 +1511,7 @@ export class CoreSitesProvider {
|
|||
CoreRedirects.storeRedirect(siteId, redirectData);
|
||||
}
|
||||
|
||||
await this.logout();
|
||||
await this.internalLogout();
|
||||
|
||||
return CoreSitePlugins.hasSitePluginsLoaded;
|
||||
}
|
||||
|
@ -2480,7 +2486,14 @@ export type CoreSitesLoginTokenResponse = {
|
|||
/**
|
||||
* 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.
|
||||
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 { mock, mockSingleton } from '@/testing/utils';
|
||||
import { CoreNavigator, CoreNavigatorService } from '@services/navigator';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { Http } from '@singletons';
|
||||
import { of } from 'rxjs';
|
||||
|
@ -34,13 +33,10 @@ describe('CoreSitesProvider', () => {
|
|||
});
|
||||
|
||||
it('cleans up on logout', async () => {
|
||||
const navigator: CoreNavigatorService = mockSingleton(CoreNavigator, ['navigate']);
|
||||
|
||||
CoreSites.initialize();
|
||||
CoreEvents.trigger(CoreEvents.LOGOUT);
|
||||
|
||||
expect(langProvider.clearCustomStrings).toHaveBeenCalled();
|
||||
expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true });
|
||||
});
|
||||
|
||||
it('adds ionic platform and theme classes', async () => {
|
||||
|
|
|
@ -432,15 +432,14 @@ export class CoreCustomURLSchemesProvider {
|
|||
// Ask the user before changing site.
|
||||
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',
|
||||
redirectOptions: { params: pageParams },
|
||||
});
|
||||
|
||||
if (willReload) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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/
|
||||
|
||||
=== 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 ===
|
||||
|
||||
- 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