diff --git a/scripts/langindex.json b/scripts/langindex.json index 7e8e452d6..5dd3147e9 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -2194,6 +2194,8 @@ "core.login.stillcantconnect": "local_moodlemobileapp", "core.login.supplyinfo": "moodle", "core.login.toggleremove": "local_moodlemobileapp", + "core.login.unsupportedsite": "local_moodlemobileapp", + "core.login.unsupportedsitemessage": "local_moodlemobileapp", "core.login.username": "moodle", "core.login.usernamelowercase": "moodle", "core.login.usernameoremail": "moodle", diff --git a/src/core/features/login/constants.ts b/src/core/features/login/constants.ts index 0da8f8fe3..b83fbe9d6 100644 --- a/src/core/features/login/constants.ts +++ b/src/core/features/login/constants.ts @@ -21,3 +21,6 @@ export const EMAIL_SIGNUP_FEATURE_NAME = 'CoreLoginEmailSignup'; export const FORGOTTEN_PASSWORD_FEATURE_NAME = 'NoDelegate_ForgottenPassword'; export const IDENTITY_PROVIDERS_FEATURE_NAME = 'NoDelegate_IdentityProviders'; export const IDENTITY_PROVIDER_FEATURE_NAME_PREFIX = 'NoDelegate_IdentityProvider_'; + +// Event indicating that a user left the app because it wasn't supported by a site. +export const APP_UNSUPPORTED_CHURN = 'app_unsupported_churn'; diff --git a/src/core/features/login/lang.json b/src/core/features/login/lang.json index edbf0c44c..a3b4e91e8 100644 --- a/src/core/features/login/lang.json +++ b/src/core/features/login/lang.json @@ -119,6 +119,8 @@ "stillcantconnect": "Still can't connect?", "supplyinfo": "More details", "toggleremove": "Edit accounts list", + "unsupportedsite": "Site not accessible through the app", + "unsupportedsitemessage": "{{site}} can't be accessed through this app.\nYou can still access it using a web browser", "username": "Username", "usernamelowercase": "Only lowercase letters allowed", "usernameoremail": "Enter either username or email address", diff --git a/src/core/features/login/pages/credentials/credentials.ts b/src/core/features/login/pages/credentials/credentials.ts index 2bef573d0..2f936c812 100644 --- a/src/core/features/login/pages/credentials/credentials.ts +++ b/src/core/features/login/pages/credentials/credentials.ts @@ -35,6 +35,7 @@ import { CorePlatform } from '@services/platform'; import { CoreSitesFactory } from '@services/sites-factory'; import { EMAIL_SIGNUP_FEATURE_NAME, FORGOTTEN_PASSWORD_FEATURE_NAME } from '@features/login/constants'; import { CoreCustomURLSchemes } from '@services/urlschemes'; +import { CoreSiteError } from '@classes/errors/siteerror'; /** * Page to enter the user credentials. @@ -292,7 +293,11 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { await CoreNavigator.navigateToSiteHome({ params: { urlToOpen: this.urlToOpen } }); } catch (error) { - CoreLoginHelper.treatUserTokenError(siteUrl, error, username, password); + if (error instanceof CoreSiteError && CoreLoginHelper.isAppUnsupportedError(error)) { + await CoreLoginHelper.showAppUnsupportedModal(siteUrl, this.site, error.debug); + } else { + CoreLoginHelper.treatUserTokenError(siteUrl, error, username, password); + } if (error.loggedout) { CoreNavigator.navigate('/login/sites', { reset: true }); diff --git a/src/core/features/login/pages/site/site.ts b/src/core/features/login/pages/site/site.ts index 9cabf031a..64f6eaa9d 100644 --- a/src/core/features/login/pages/site/site.ts +++ b/src/core/features/login/pages/site/site.ts @@ -48,6 +48,7 @@ import { CorePlatform } from '@services/platform'; import { CoreReferrer } from '@services/referrer'; import { CoreSitesFactory } from '@services/sites-factory'; import { ONBOARDING_DONE } from '@features/login/constants'; +import { CoreUnauthenticatedSite } from '@classes/sites/unauthenticated-site'; /** * Site (url) chooser when adding a new site. @@ -301,7 +302,7 @@ export class CoreLoginSitePage implements OnInit { url = url.trim(); if (url.match(/^(https?:\/\/)?campus\.example\.edu/)) { - this.showLoginIssue(null, new CoreError(Translate.instant('core.login.errorexampleurl'))); + this.showLoginIssue(url, new CoreError(Translate.instant('core.login.errorexampleurl'))); return; } @@ -403,17 +404,23 @@ export class CoreLoginSitePage implements OnInit { * @param url The URL the user was trying to connect to. * @param error Error to display. */ - protected async showLoginIssue(url: string | null, error: CoreError): Promise { + protected async showLoginIssue(url: string, error: CoreError): Promise { let errorMessage = CoreDomUtils.getErrorMessage(error); - let siteExists = false; - let supportConfig: CoreUserSupportConfig | undefined = undefined; - let errorTitle: string | undefined; let debug: CoreSiteErrorDebug | undefined; + let errorTitle: string | undefined; + let site: CoreUnauthenticatedSite | undefined; + let supportConfig: CoreUserSupportConfig | undefined; if (error instanceof CoreSiteError) { supportConfig = error.supportConfig; - siteExists = supportConfig instanceof CoreUserGuestSupportConfig; + site = supportConfig instanceof CoreUserGuestSupportConfig ? supportConfig.getSite() : undefined; debug = error.debug; + + if (CoreLoginHelper.isAppUnsupportedError(error)) { + await CoreLoginHelper.showAppUnsupportedModal(url, site, debug); + + return; + } } if (error instanceof CoreLoginError) { @@ -440,7 +447,7 @@ export class CoreLoginSitePage implements OnInit { }), } : ( - !siteExists + !site ? { text: Translate.instant('core.needhelp'), cssClass: 'core-login-need-help', diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 22bb66755..09b6cd7e0 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -48,6 +48,7 @@ import { TypeOfLogin, } from '@classes/sites/unauthenticated-site'; import { + APP_UNSUPPORTED_CHURN, EMAIL_SIGNUP_FEATURE_NAME, FAQ_QRCODE_IMAGE_HTML, FAQ_QRCODE_INFO_DONE, @@ -56,6 +57,7 @@ import { IDENTITY_PROVIDER_FEATURE_NAME_PREFIX, } from '../constants'; import { LazyRoutesModule } from '@/app/app-routing.module'; +import { CoreSiteError, CoreSiteErrorDebug } from '@classes/errors/siteerror'; /** * Helper provider that provides some common features regarding authentication. @@ -65,6 +67,15 @@ export class CoreLoginHelperProvider { protected static readonly PASSWORD_RESETS_CONFIG_KEY = 'password-resets'; + private static readonly APP_UNSUPPORTED_ERRORS = [ + 'loginfailed', + 'logintokenempty', + 'logintokenerror', + 'mobileservicesnotenabled', + 'sitehasredirect', + 'webservicesnotenabled', + ]; + protected logger: CoreLogger; protected sessionExpiredCheckingSite: Record = {}; protected isOpenEditAlertShown = false; @@ -930,11 +941,61 @@ export class CoreLoginHelperProvider { return String(CoreConstants.CONFIG.skipssoconfirmation) === 'true'; } + /** + * Check whether the given error means that the app is not working in the site. + * + * @param error Site error. + * @returns Whether the given error means that the app is not working in the site. + */ + isAppUnsupportedError(error: CoreSiteError): boolean { + return CoreLoginHelperProvider.APP_UNSUPPORTED_ERRORS.includes(error.debug?.code ?? ''); + } + + /** + * Show modal indicating that the app is not supported in the site. + * + * @param siteUrl Site url. + * @param site Site instance. + * @param debug Error debug information. + */ + async showAppUnsupportedModal(siteUrl: string, site?: CoreUnauthenticatedSite, debug?: CoreSiteErrorDebug): Promise { + const siteName = await site?.getSiteName() ?? siteUrl; + + await CoreDomUtils.showAlertWithOptions({ + header: Translate.instant('core.login.unsupportedsite'), + message: Translate.instant('core.login.unsupportedsitemessage', { site: siteName }), + buttons: [ + { + text: Translate.instant('core.cancel'), + role: 'cancel', + }, + { + text: Translate.instant('core.openinbrowser'), + handler: () => this.openInBrowserFallback(site?.getURL() ?? siteUrl, debug), + }, + ], + }); + } + + /** + * Open site in browser as fallback when it is not supported in the app. + * + * @param siteUrl Site url. + * @param debug Error debug information. + */ + async openInBrowserFallback(siteUrl: string, debug?: CoreSiteErrorDebug): Promise { + CoreEvents.trigger(APP_UNSUPPORTED_CHURN, { siteUrl, debug }); + + await CoreUtils.openInBrowser(siteUrl, { showBrowserWarning: false }); + } + /** * Show a modal warning that the credentials introduced were not correct. */ - protected showInvalidLoginModal(error: CoreWSError): void { - CoreDomUtils.showErrorModal(error.message); + protected showInvalidLoginModal(error: CoreError): void { + const errorDetails = error instanceof CoreSiteError ? error.debug?.details : null; + + CoreDomUtils.showErrorModal(errorDetails ?? error.message); } /** @@ -1074,8 +1135,10 @@ export class CoreLoginHelperProvider { * @param username Username. * @param password User password. */ - treatUserTokenError(siteUrl: string, error: CoreWSError, username?: string, password?: string): void { - switch (error.errorcode) { + treatUserTokenError(siteUrl: string, error: CoreError, username?: string, password?: string): void { + const errorCode = 'errorcode' in error ? error.errorcode : null; + + switch (errorCode) { case 'forcepasswordchangenotice': this.openChangePassword(siteUrl, CoreTextUtils.getErrorMessageFromError(error) ?? ''); break; @@ -1617,3 +1680,16 @@ export type CoreLoginSiteFinderSettings = { displayurl: boolean; defaultimageurl?: string; }; + +declare module '@singletons/events' { + + /** + * Augment CoreEventsData interface with events specific to this service. + * + * @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation + */ + export interface CoreEventsData { + [APP_UNSUPPORTED_CHURN]: { siteUrl: string; debug?: CoreSiteErrorDebug }; + } + +} diff --git a/src/core/features/login/tests/behat/basic_usage.feature b/src/core/features/login/tests/behat/basic_usage.feature index 46bc64d17..b0eb396ad 100755 --- a/src/core/features/login/tests/behat/basic_usage.feature +++ b/src/core/features/login/tests/behat/basic_usage.feature @@ -47,6 +47,16 @@ Feature: Test basic usage of login in app And I press "Connect to your site" in the app Then I should find "Can't connect to site" in the app + Scenario: Attempt invalid login + When I launch the app + And I set the field "Your site" to "$WWWROOT" in the app + And I press "Connect to your site" in the app + And I set the following fields to these values in the app: + | Username | student1 | + | Password | wrongpassword | + And I press "Log in" near "Lost password?" in the app + Then I should find "Invalid login" in the app + Scenario: Add a non existing account from accounts switcher Given I entered the app as "student1" And I press the user menu button in the app diff --git a/src/core/features/user/classes/support/guest-support-config.ts b/src/core/features/user/classes/support/guest-support-config.ts index a21c7c030..f60277e0d 100644 --- a/src/core/features/user/classes/support/guest-support-config.ts +++ b/src/core/features/user/classes/support/guest-support-config.ts @@ -71,6 +71,15 @@ export class CoreUserGuestSupportConfig extends CoreUserSupportConfig { return 'supportpage' in this.config; } + /** + * Get site. + * + * @returns site. + */ + getSite(): CoreUnauthenticatedSite { + return this.site; + } + /** * @inheritdoc */