From a2772622f267d0d533200baf6017a3ae6d46e033 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Tue, 15 Nov 2022 11:21:10 +0100 Subject: [PATCH 1/3] MOBILE-4059 login: Update exceeded attempts text --- scripts/langindex.json | 4 +- src/core/features/login/lang.json | 6 ++- .../login/pages/credentials/credentials.html | 9 +---- .../login/pages/credentials/credentials.ts | 23 ++++++++++- .../login/pages/reconnect/reconnect.html | 11 ++---- .../login/pages/reconnect/reconnect.ts | 23 ++++++++++- .../features/login/services/login-helper.ts | 39 ++++++++++++++++++- 7 files changed, 91 insertions(+), 24 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 9964a93ae..eafbc0172 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1934,8 +1934,10 @@ "core.login.errorqrnoscheme": "local_moodlemobileapp", "core.login.errorupdatesite": "local_moodlemobileapp", "core.login.exceededloginattempts": "local_moodlemobileapp", - "core.login.exceededloginattemptsfallback": "local_moodlemobileapp", + "core.login.exceededloginattemptsrecoverpassword": "local_moodlemobileapp", "core.login.exceededloginattemptssupportsubject": "local_moodlemobileapp", + "core.login.exceededloginattemptswithoutpassword": "local_moodlemobileapp", + "core.login.exceededloginattemptswithoutsupport": "local_moodlemobileapp", "core.login.exceededpasswordresetattempts": "local_moodlemobileapp", "core.login.exceededpasswordresetattemptssupportsubject": "local_moodlemobileapp", "core.login.faqcannotfindmysiteanswer": "local_moodlemobileapp", diff --git a/src/core/features/login/lang.json b/src/core/features/login/lang.json index 00561e866..c1e7cd6f3 100644 --- a/src/core/features/login/lang.json +++ b/src/core/features/login/lang.json @@ -28,9 +28,11 @@ "errorexampleurl": "The URL https://campus.example.edu is only an example URL, it's not a real site. Please use the URL of your school or organization's site.", "errorqrnoscheme": "This URL isn't a valid login URL.", "errorupdatesite": "An error occurred while updating the site's token.", - "exceededloginattempts": "Need help logging in? Try recovering your password or contact your site support.", - "exceededloginattemptsfallback": "Need help logging in? Try recovering your password.", + "exceededloginattempts": "Need help logging in? Try {{recoverPassword}} or contact your site support.", + "exceededloginattemptsrecoverpassword": "recovering your password", "exceededloginattemptssupportsubject": "I can't log in", + "exceededloginattemptswithoutpassword": "Need help logging in? Try to contact your site support.", + "exceededloginattemptswithoutsupport": "Need help logging in? Try {{recoverPassword}}.", "exceededpasswordresetattempts": "It seems you are having trouble accessing your account. You can contact your school or learning provider or try again later.", "exceededpasswordresetattemptssupportsubject": "I can't reset my password", "faqcannotfindmysiteanswer": "If you still can't find it, please contact your school or learning provider for help.", diff --git a/src/core/features/login/pages/credentials/credentials.html b/src/core/features/login/pages/credentials/credentials.html index 19fb6ba74..3ad086208 100644 --- a/src/core/features/login/pages/credentials/credentials.html +++ b/src/core/features/login/pages/credentials/credentials.html @@ -33,14 +33,9 @@

{{siteUrl}}

- - - {{ 'core.login.exceededloginattempts' | translate }} - - - {{ 'core.login.exceededloginattemptsfallback' | translate }} - +
diff --git a/src/core/features/login/pages/credentials/credentials.ts b/src/core/features/login/pages/credentials/credentials.ts index 15eac45e1..dac04dec0 100644 --- a/src/core/features/login/pages/credentials/credentials.ts +++ b/src/core/features/login/pages/credentials/credentials.ts @@ -31,6 +31,7 @@ import { CoreForms } from '@singletons/form'; import { CoreUserSupport } from '@features/user/services/support'; import { CoreUserSupportConfig } from '@features/user/classes/support/support-config'; import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest-support-config'; +import { SafeHtml } from '@angular/platform-browser'; /** * Page to enter the user credentials. @@ -59,7 +60,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { showScanQR = false; loginAttempts = 0; supportConfig?: CoreUserSupportConfig; - canContactSupport?: boolean; + exceededAttemptsHTML?: SafeHtml | string | null; protected siteConfig?: CoreSitePublicConfigResponse; protected eventThrown = false; @@ -83,7 +84,6 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { this.siteConfig = CoreNavigator.getRouteParam('siteConfig'); this.urlToOpen = CoreNavigator.getRouteParam('urlToOpen'); this.supportConfig = this.siteConfig && new CoreUserGuestSupportConfig(this.siteConfig); - this.canContactSupport = this.supportConfig?.canContactSupport(); } catch (error) { CoreDomUtils.showErrorModal(error); @@ -208,6 +208,10 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { this.canSignup = this.siteConfig.registerauth == 'email' && !CoreLoginHelper.isEmailSignupDisabled(this.siteConfig, disabledFeatures); this.showForgottenPassword = !CoreLoginHelper.isForgottenPasswordDisabled(this.siteConfig, disabledFeatures); + this.exceededAttemptsHTML = CoreLoginHelper.buildExceededAttemptsHTML( + !!this.supportConfig?.canContactSupport(), + this.showForgottenPassword, + ); if (!this.eventThrown && !this.viewLeft) { this.eventThrown = true; @@ -301,6 +305,21 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { } } + /** + * Exceeded attempts message clicked. + * + * @param event Click event. + */ + exceededAttemptsClicked(event: Event): void { + event.preventDefault(); + + if (!(event.target instanceof HTMLAnchorElement)) { + return; + } + + this.forgottenPassword(); + } + /** * Forgotten password button clicked. */ diff --git a/src/core/features/login/pages/reconnect/reconnect.html b/src/core/features/login/pages/reconnect/reconnect.html index b03e38df3..94d6a452c 100644 --- a/src/core/features/login/pages/reconnect/reconnect.html +++ b/src/core/features/login/pages/reconnect/reconnect.html @@ -43,14 +43,9 @@ - - - {{ 'core.login.exceededloginattempts' | translate }} - - - {{ 'core.login.exceededloginattemptsfallback' | translate }} - + +
diff --git a/src/core/features/login/pages/reconnect/reconnect.ts b/src/core/features/login/pages/reconnect/reconnect.ts index f22cd949b..bd4bcb3a1 100644 --- a/src/core/features/login/pages/reconnect/reconnect.ts +++ b/src/core/features/login/pages/reconnect/reconnect.ts @@ -30,6 +30,7 @@ import { CoreUserSupport } from '@features/user/services/support'; import { CoreUserSupportConfig } from '@features/user/classes/support/support-config'; import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config'; import { Translate } from '@singletons'; +import { SafeHtml } from '@angular/platform-browser'; /** * Page to enter the user password to reconnect to a site. @@ -61,7 +62,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { showLoading = true; reconnectAttempts = 0; supportConfig?: CoreUserSupportConfig; - canContactSupport?: boolean; + exceededAttemptsHTML?: SafeHtml | string | null; protected siteConfig?: CoreSitePublicConfigResponse; protected viewLeft = false; @@ -109,7 +110,6 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { this.siteUrl = site.infos.siteurl; this.siteName = site.getSiteName(); this.supportConfig = new CoreUserAuthenticatedSupportConfig(site); - this.canContactSupport = this.supportConfig.canContactSupport(); // If login was OAuth we should only reach this page if the OAuth method ID has changed. this.isOAuth = site.isOAuth(); @@ -168,6 +168,10 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { this.identityProviders = CoreLoginHelper.getValidIdentityProviders(this.siteConfig, disabledFeatures); this.showForgottenPassword = !CoreLoginHelper.isForgottenPasswordDisabled(this.siteConfig); + this.exceededAttemptsHTML = CoreLoginHelper.buildExceededAttemptsHTML( + !!this.supportConfig?.canContactSupport(), + this.showForgottenPassword, + ); if (!this.eventThrown && !this.viewLeft) { this.eventThrown = true; @@ -270,6 +274,21 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { } } + /** + * Exceeded attempts message clicked. + * + * @param event Click event. + */ + exceededAttemptsClicked(event: Event): void { + event.preventDefault(); + + if (!(event.target instanceof HTMLAnchorElement)) { + return; + } + + this.forgottenPassword(); + } + /** * Forgotten password button clicked. */ diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 45595c83c..9f6b21e49 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable } from '@angular/core'; +import { Injectable, SecurityContext } from '@angular/core'; import { Params } from '@angular/router'; import { Md5 } from 'ts-md5/dist/md5'; @@ -29,7 +29,7 @@ import { CoreConstants } from '@/core/constants'; import { CoreSite, CoreSiteIdentityProvider, CoreSitePublicConfigResponse, CoreSiteQRCodeType } from '@classes/site'; import { CoreError } from '@classes/errors/error'; import { CoreWSError } from '@classes/errors/wserror'; -import { makeSingleton, Translate } from '@singletons'; +import { DomSanitizer, makeSingleton, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreUrl } from '@singletons/url'; import { CoreNavigator, CoreRedirectPayload } from '@services/navigator'; @@ -38,6 +38,7 @@ import { CoreCustomURLSchemes } from '@services/urlschemes'; import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; import { CoreText } from '@singletons/text'; import { CorePromisedValue } from '@classes/promised-value'; +import { SafeHtml } from '@angular/platform-browser'; const PASSWORD_RESETS_CONFIG_KEY = 'password-resets'; @@ -1515,6 +1516,40 @@ export class CoreLoginHelperProvider { } } + /** + * Build the HTML message to show once login attempts have been exceeded. + * + * @param canContactSupport Whether contacting support is enabled in the site. + * @param canRecoverPassword Whether recovering the password is enabled in the site. + * @return HTML message. + */ + buildExceededAttemptsHTML(canContactSupport: boolean, canRecoverPassword: boolean): SafeHtml | string | null { + const safeHTML = (html: string) => DomSanitizer.sanitize(SecurityContext.HTML, html) ?? ''; + const recoverPasswordHTML = (messageKey: string) => { + const placeholder = '%%RECOVER_PASSWORD%%'; + const message = safeHTML(Translate.instant(messageKey, { recoverPassword: placeholder })); + const recoverPassword = safeHTML(Translate.instant('core.login.exceededloginattemptsrecoverpassword')); + + return DomSanitizer.bypassSecurityTrustHtml( + message.replace(placeholder, `${recoverPassword}`), + ); + }; + + if (canContactSupport && canRecoverPassword) { + return recoverPasswordHTML('core.login.exceededloginattempts'); + } + + if (canContactSupport) { + return Translate.instant('core.login.exceededloginattemptswithoutpassword'); + } + + if (canRecoverPassword) { + return recoverPasswordHTML('core.login.exceededloginattemptswithoutsupport'); + } + + return null; + } + /** * Get a record indexing the last time a password reset was requested for a site. * From 001a19f066dce6e0b54be1a02a179a5567f742bb Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Tue, 15 Nov 2022 11:52:20 +0100 Subject: [PATCH 2/3] MOBILE-4059 user: Update contact support message --- src/core/features/user/lang.json | 2 +- src/core/features/user/tests/behat/support.feature | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/features/user/lang.json b/src/core/features/user/lang.json index d1c29e8b6..ed6261dc1 100644 --- a/src/core/features/user/lang.json +++ b/src/core/features/user/lang.json @@ -30,7 +30,7 @@ "roles": "Roles", "sendemail": "Email", "student": "Student", - "support": "Support", + "support": "Contact site support", "teacher": "Non-editing teacher", "userwithid": "User with ID {{id}}", "webpage": "Web page" diff --git a/src/core/features/user/tests/behat/support.feature b/src/core/features/user/tests/behat/support.feature index 0f4c9018d..4f11aea96 100644 --- a/src/core/features/user/tests/behat/support.feature +++ b/src/core/features/user/tests/behat/support.feature @@ -10,9 +10,9 @@ Feature: Site support Scenario: Uses default support page Given I entered the app as "student1" When I press the user menu button in the app - Then I should find "Support" in the app + Then I should find "Contact site support" in the app - When I press "Support" in the app + When I press "Contact site support" in the app Then the app should have opened a browser tab with url ".*\/user\/contactsitesupport\.php" Scenario: Uses custom support page @@ -20,9 +20,9 @@ Feature: Site support | supportpage | https://campus.example.edu/support | And I entered the app as "student1" When I press the user menu button in the app - Then I should find "Support" in the app + Then I should find "Contact site support" in the app - When I press "Support" in the app + When I press "Contact site support" in the app Then the app should have opened a browser tab with url "https:\/\/campus\.example\.edu\/support" Scenario: Cannot contact support @@ -31,4 +31,4 @@ Feature: Site support And I entered the app as "student1" When I press the user menu button in the app Then I should find "Blog entries" in the app - But I should not find "Support" in the app + But I should not find "Contact site support" in the app From 8b8c4d533a13257494163f2e281cacbd243fdcb8 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Tue, 15 Nov 2022 12:14:46 +0100 Subject: [PATCH 3/3] MOBILE-4059 login: Treat invalidlogin error --- .../features/login/services/login-helper.ts | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 9f6b21e49..c513b3d22 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -39,6 +39,7 @@ import { CorePushNotifications } from '@features/pushnotifications/services/push import { CoreText } from '@singletons/text'; import { CorePromisedValue } from '@classes/promised-value'; import { SafeHtml } from '@angular/platform-browser'; +import { CoreLoginError } from '@classes/errors/loginerror'; const PASSWORD_RESETS_CONFIG_KEY = 'password-resets'; @@ -1026,6 +1027,13 @@ export class CoreLoginHelperProvider { (!CoreConstants.CONFIG.skipssoconfirmation || String(CoreConstants.CONFIG.skipssoconfirmation) === 'false'); } + /** + * Show a modal warning that the credentials introduced were not correct. + */ + protected showInvalidLoginModal(error: CoreLoginError): void { + CoreDomUtils.showErrorModal(error.errorDetails ?? error.message); + } + /** * Show a modal warning the user that he should use the Workplace app. * @@ -1167,16 +1175,25 @@ export class CoreLoginHelperProvider { * @param password User password. */ treatUserTokenError(siteUrl: string, error: CoreWSError, username?: string, password?: string): void { - if (error.errorcode == 'forcepasswordchangenotice') { - this.openChangePassword(siteUrl, CoreTextUtils.getErrorMessageFromError(error)!); - } else if (error.errorcode == 'usernotconfirmed') { - this.showNotConfirmedModal(siteUrl, undefined, username, password); - } else if (error.errorcode == 'connecttomoodleapp') { - this.showMoodleAppNoticeModal(CoreTextUtils.getErrorMessageFromError(error)!); - } else if (error.errorcode == 'connecttoworkplaceapp') { - this.showWorkplaceNoticeModal(CoreTextUtils.getErrorMessageFromError(error)!); - } else { - CoreDomUtils.showErrorModal(error); + switch (error.errorcode) { + case 'forcepasswordchangenotice': + this.openChangePassword(siteUrl, CoreTextUtils.getErrorMessageFromError(error)!); + break; + case 'usernotconfirmed': + this.showNotConfirmedModal(siteUrl, undefined, username, password); + break; + case 'connecttomoodleapp': + this.showMoodleAppNoticeModal(CoreTextUtils.getErrorMessageFromError(error)!); + break; + case 'connecttoworkplaceapp': + this.showWorkplaceNoticeModal(CoreTextUtils.getErrorMessageFromError(error)!); + break; + case 'invalidlogin': + this.showInvalidLoginModal(error); + break; + default: + CoreDomUtils.showErrorModal(error); + break; } }