MOBILE-4028 logout: Refactor logout process

Now it uses a logout page so Angular guards are triggered before doing the logout process.
main
Dani Palou 2024-11-19 08:09:15 +01:00
parent f39d4a9240
commit feff5a87f8
13 changed files with 161 additions and 64 deletions

View File

@ -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 });
};
});

View File

@ -41,6 +41,10 @@ const appRoutes: Routes = [
loadChildren: () => import('./login-lazy.module'),
canActivate: [redirectGuard],
},
{
path: 'logout',
loadComponent: () => import('@features/login/pages/logout/logout'),
},
];
@NgModule({

View File

@ -0,0 +1,3 @@
<ion-content>
<core-loading />
</ion-content>

View File

@ -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 });
}
}

View File

@ -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 });
}

View File

@ -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();

View File

@ -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();
}
/**

View File

@ -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' }] });

View File

@ -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) {

View File

@ -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.
};

View File

@ -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 () => {

View File

@ -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);
}

View File

@ -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