Skip to content

Commit 6c132bd

Browse files
committed
MOBILE-4680 login: Show the current oauth method to the top on reconnect
1 parent ae1c719 commit 6c132bd

File tree

13 files changed

+220
-66
lines changed

13 files changed

+220
-66
lines changed

scripts/langindex.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2168,6 +2168,7 @@
21682168
"core.login.missingfirstname": "moodle",
21692169
"core.login.missinglastname": "moodle",
21702170
"core.login.mobileservicesnotenabled": "local_moodlemobileapp",
2171+
"core.login.morewaystologin": "local_moodlemobileapp",
21712172
"core.login.mustconfirm": "moodle",
21722173
"core.login.newaccount": "moodle",
21732174
"core.login.notloggedin": "local_moodlemobileapp",

src/core/classes/sites/site.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ export class CoreSite extends CoreAuthenticatedSite {
195195
* Check if the user authenticated in the site using an OAuth method.
196196
*
197197
* @returns Whether the user authenticated in the site using an OAuth method.
198+
* @deprecated since 5.0. Use getOAuthId instead.
198199
*/
199200
isOAuth(): boolean {
200201
return this.oauthId != null && this.oauthId !== undefined;

src/core/features/login/components/components.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { NgModule } from '@angular/core';
1616
import { CoreSharedModule } from '@/core/shared.module';
1717
import { CoreLoginMethodsComponent } from './login-methods/login-methods';
1818
import { CoreLoginExceededAttemptsComponent } from '@features/login/components/exceeded-attempts/exceeded-attempts';
19+
import { CoreLoginIdentityProviderComponent } from './identity-provider/identity-provider';
1920

2021
@NgModule({
2122
declarations: [
@@ -24,6 +25,7 @@ import { CoreLoginExceededAttemptsComponent } from '@features/login/components/e
2425
],
2526
imports: [
2627
CoreSharedModule,
28+
CoreLoginIdentityProviderComponent,
2729
],
2830
exports: [
2931
CoreLoginExceededAttemptsComponent,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<ion-button class="ion-text-wrap ion-margin core-oauth-provider" (click)="openOAuth()" [ariaLabel]="provider.name" expand="block"
2+
fill="outline">
3+
@if (provider.iconurl) {
4+
<img [src]="provider.iconurl" alt="" width="32" height="32" slot="start" aria-hidden="true" (error)="provider.iconurl = ''" />
5+
}
6+
<ion-label>{{ provider.name }}</ion-label>
7+
</ion-button>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { Component, Input } from '@angular/core';
16+
import { CoreSiteIdentityProvider } from '@classes/sites/unauthenticated-site';
17+
import { CoreLoginHelper } from '@features/login/services/login-helper';
18+
import { CoreDomUtils } from '@services/utils/dom';
19+
import { CoreSharedModule } from '@/core/shared.module';
20+
import { CoreRedirectPayload } from '@services/navigator';
21+
22+
@Component({
23+
selector: 'core-identity-provider',
24+
templateUrl: 'identity-provider.html',
25+
standalone: true,
26+
imports: [
27+
CoreSharedModule,
28+
],
29+
})
30+
export class CoreLoginIdentityProviderComponent {
31+
32+
@Input({ required: true }) provider!: CoreSiteIdentityProvider;
33+
@Input() launchurl = '';
34+
@Input() siteUrl = '';
35+
@Input() redirectData?: CoreRedirectPayload;
36+
37+
/**
38+
* The button has been clicked.
39+
*/
40+
async openOAuth(): Promise<void> {
41+
const result = await CoreLoginHelper.openBrowserForOAuthLogin(
42+
this.siteUrl,
43+
this.provider,
44+
this.launchurl,
45+
this.redirectData,
46+
);
47+
48+
if (!result) {
49+
CoreDomUtils.showErrorModal('Invalid data.');
50+
}
51+
}
52+
53+
}

src/core/features/login/components/login-methods/login-methods.html

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@
2121
<!-- Identity providers. -->
2222
<ion-list *ngIf="identityProviders.length" class="core-login-identity-providers">
2323
<h2 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h2>
24-
<ion-button [fill]="'outline'" *ngFor="let provider of identityProviders" class="ion-text-wrap ion-margin core-oauth-provider"
25-
(click)="oauthClicked(provider)" [ariaLabel]="provider.name" expand="block">
26-
<img *ngIf="provider.iconurl" [src]="provider.iconurl" alt="" width="32" height="32" slot="start" aria-hidden="true">
27-
<ion-label>{{ provider.name }}</ion-label>
28-
</ion-button>
24+
<core-identity-provider *ngFor="let provider of identityProviders" [provider]="provider" [launchurl]="siteConfig?.launchurl"
25+
[redirectData]="redirectData" [siteUrl]="siteUrl" />
2926
</ion-list>

src/core/features/login/components/login-methods/login-methods.ts

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import { toBoolean } from '@/core/transforms/boolean';
1615
import { Component, Input, OnInit } from '@angular/core';
16+
import { CorePromisedValue } from '@classes/promised-value';
17+
import { CoreSite } from '@classes/sites/site';
1718
import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/sites/unauthenticated-site';
1819
import { CoreLoginHelper, CoreLoginMethod } from '@features/login/services/login-helper';
1920
import { CoreRedirectPayload } from '@services/navigator';
20-
import { CoreSites } from '@services/sites';
2121
import { CoreSitesFactory } from '@services/sites-factory';
22-
import { CoreDomUtils } from '@services/utils/dom';
2322

2423
@Component({
2524
selector: 'core-login-methods',
@@ -28,27 +27,31 @@ import { CoreDomUtils } from '@services/utils/dom';
2827
})
2928
export class CoreLoginMethodsComponent implements OnInit {
3029

31-
@Input({ transform: toBoolean }) reconnect = false;
3230
@Input() siteUrl = '';
3331
@Input() siteConfig?: CoreSitePublicConfigResponse;
3432
@Input() redirectData?: CoreRedirectPayload;
33+
@Input() site?: CoreSite; // Defined when the user is reconnecting.
3534
@Input() showLoginForm = true;
3635

3736
isBrowserSSO = false;
3837
showScanQR = false;
3938
loginMethods: CoreLoginMethod[] = [];
4039
identityProviders: CoreSiteIdentityProvider[] = [];
4140

41+
protected currentLoginProvider?: CoreSiteIdentityProvider;
42+
protected isReady = new CorePromisedValue<void>();
43+
4244
/**
4345
* @inheritdoc
4446
*/
4547
async ngOnInit(): Promise<void> {
46-
if (this.reconnect) {
48+
if (this.site) {
49+
this.siteUrl = this.site.getURL();
50+
4751
this.loginMethods = await CoreLoginHelper.getLoginMethods();
4852

49-
const currentSite = CoreSites.getCurrentSite();
5053
const defaultMethod = await CoreLoginHelper.getDefaultLoginMethod();
51-
if (currentSite?.isLoggedOut() && defaultMethod) {
54+
if (this.site.isLoggedOut() && defaultMethod) {
5255
await defaultMethod.action();
5356
}
5457
}
@@ -59,25 +62,29 @@ export class CoreLoginMethodsComponent implements OnInit {
5962
// Identity providers won't be shown if login on browser.
6063
if (!this.isBrowserSSO) {
6164
this.identityProviders = await CoreLoginHelper.getValidIdentityProvidersForSite(
62-
CoreSitesFactory.makeUnauthenticatedSite(this.siteUrl, this.siteConfig),
65+
this.site ?? CoreSitesFactory.makeUnauthenticatedSite(this.siteUrl, this.siteConfig),
6366
);
6467
}
6568

66-
if (this.reconnect) {
69+
if (this.site) {
6770
this.showScanQR = CoreLoginHelper.displayQRInSiteScreen();
71+
72+
// The identity provider set in the site will be shown at the top.
73+
const oAuthId = this.site.getOAuthId();
74+
this.currentLoginProvider = CoreLoginHelper.findIdentityProvider(this.identityProviders, oAuthId);
6875
}
6976

7077
// If still false or credentials screen.
71-
if (!this.reconnect || !this.showScanQR) {
78+
if (!this.site || !this.showScanQR) {
7279
this.showScanQR = await CoreLoginHelper.displayQRInCredentialsScreen(this.siteConfig.tool_mobile_qrcodetype);
7380
}
7481
}
82+
83+
this.isReady.resolve();
7584
}
7685

7786
/**
7887
* Show instructions and scan QR code.
79-
*
80-
* @returns Promise resolved when done.
8188
*/
8289
async showInstructionsAndScanQR(): Promise<void> {
8390
try {
@@ -90,21 +97,33 @@ export class CoreLoginMethodsComponent implements OnInit {
9097
}
9198

9299
/**
93-
* An OAuth button was clicked.
100+
* Get the current login, removing the identity provider from the list.
94101
*
95-
* @param provider The provider that was clicked.
102+
* @returns Current login.
96103
*/
97-
async oauthClicked(provider: CoreSiteIdentityProvider): Promise<void> {
98-
const result = await CoreLoginHelper.openBrowserForOAuthLogin(
99-
this.siteUrl,
100-
provider,
101-
this.siteConfig?.launchurl,
102-
this.redirectData,
103-
);
104-
105-
if (!result) {
106-
CoreDomUtils.showErrorModal('Invalid data.');
104+
async extractCurrentLogin(): Promise<CoreLoginMethodsCurrentLogin | undefined> {
105+
await this.isReady;
106+
107+
if (!this.currentLoginProvider) {
108+
return;
107109
}
110+
111+
// Remove the identity provider from the array.
112+
this.identityProviders = this.identityProviders.filter((provider) =>
113+
provider.url !== this.currentLoginProvider?.url);
114+
115+
const showOther = !!(this.showLoginForm || this.isBrowserSSO) &&
116+
!!(this.loginMethods.length || this.identityProviders.length || this.showScanQR);
117+
118+
return {
119+
provider: this.currentLoginProvider,
120+
showOther,
121+
};
108122
}
109123

110124
}
125+
126+
export type CoreLoginMethodsCurrentLogin = {
127+
provider: CoreSiteIdentityProvider;
128+
showOther: boolean;
129+
};

src/core/features/login/lang.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"missingfirstname": "Missing given name",
7272
"missinglastname": "Missing last name",
7373
"mobileservicesnotenabled": "Mobile services are not enabled on the site.",
74+
"morewaystologin": "More ways to log in",
7475
"mustconfirm": "You need to confirm your account",
7576
"newaccount": "New account",
7677
"notloggedin": "You need to be logged in.",

src/core/features/login/login-reconnect-lazy.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { CoreSharedModule } from '@/core/shared.module';
1919
import { CoreLoginComponentsModule } from '@features/login/components/components.module';
2020
import { CoreLoginReconnectPage } from '@features/login/pages/reconnect/reconnect';
2121
import { CoreSiteLogoComponent } from '@/core/components/site-logo/site-logo';
22+
import { CoreLoginIdentityProviderComponent } from './components/identity-provider/identity-provider';
2223

2324
const routes: Routes = [
2425
{
@@ -33,6 +34,7 @@ const routes: Routes = [
3334
CoreSharedModule,
3435
CoreLoginComponentsModule,
3536
CoreSiteLogoComponent,
37+
CoreLoginIdentityProviderComponent,
3638
],
3739
declarations: [
3840
CoreLoginReconnectPage,

src/core/features/login/pages/reconnect/reconnect.html

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -51,41 +51,62 @@ <h1>{{ 'core.login.reconnect' | translate }}</h1>
5151
</div>
5252

5353
<div class="core-login-methods">
54-
<form *ngIf="showLoginForm && !isBrowserSSO" [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form"
55-
#reconnectForm>
56-
<ion-item class="ion-margin-bottom" lines="inset">
57-
<ion-input class="core-ioninput-password" name="password" type="password"
58-
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
59-
autocomplete="current-password" enterkeyhint="go" required="true"
60-
[attr.aria-label]="'core.login.password' | translate">
61-
<ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" />
62-
</ion-input>
63-
</ion-item>
64-
<ion-button type="submit" expand="block" [disabled]="!credForm.valid"
65-
class="ion-margin core-login-login-button ion-text-wrap">
66-
{{ 'core.login.loginbutton' | translate }}
67-
</ion-button>
54+
@if (currentLogin && currentLogin.provider) {
55+
<core-identity-provider [provider]="currentLogin.provider" [launchurl]="siteConfig?.launchurl" [redirectData]="redirectData"
56+
[siteUrl]="site.siteUrl" />
57+
@if (currentLogin.showOther) {
58+
<ion-accordion-group>
59+
<ion-accordion toggleIconSlot="start">
60+
<ion-item class="ion-text-wrap" slot="header">
61+
<ion-label>
62+
<p class="item-heading">{{ 'core.login.morewaystologin' | translate }}</p>
63+
</ion-label>
64+
</ion-item>
6865

69-
<!-- Forgotten password option. -->
70-
<ion-button *ngIf="showForgottenPassword" expand="block" fill="clear"
71-
class="core-login-forgotten-password core-button-as-link ion-text-wrap" (click)="forgottenPassword()">
72-
{{ 'core.login.forgotaccount' | translate }}
73-
</ion-button>
74-
</form>
75-
76-
<ng-container *ngIf="isBrowserSSO">
77-
<ion-button expand="block" (click)="openBrowserSSO()"
78-
class="ion-margin core-login-login-inbrowser-button ion-text-wrap">
79-
{{ 'core.login.loginbutton' | translate }}
80-
<ion-icon name="fas-up-right-from-square" slot="end" aria-hidden="true" />
81-
</ion-button>
82-
<p class="text-center core-login-inbrowser">{{ 'core.openinbrowserdescription' | translate }}</p>
83-
</ng-container>
84-
85-
<!-- Additional Login methods -->
86-
<core-login-methods *ngIf="siteConfig" [siteConfig]="siteConfig" [reconnect]="true" [siteUrl]="site.siteUrl"
87-
[redirectData]="redirectData" [showLoginForm]="showLoginForm" />
66+
<div slot="content">
67+
<ng-template *ngTemplateOutlet="loginMethods" />
68+
</div>
69+
</ion-accordion>
70+
</ion-accordion-group>
71+
}
72+
} @else {
73+
<ng-template *ngTemplateOutlet="loginMethods" />
74+
}
8875
</div>
8976
</div>
9077
</core-loading>
9178
</ion-content>
79+
80+
81+
<ng-template #loginMethods>
82+
<form *ngIf="showLoginForm && !isBrowserSSO" [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #reconnectForm>
83+
<ion-item class="ion-margin-bottom" lines="inset">
84+
<ion-input class="core-ioninput-password" name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
85+
formControlName="password" [clearOnEdit]="false" autocomplete="current-password" enterkeyhint="go" required="true"
86+
[attr.aria-label]="'core.login.password' | translate">
87+
<ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" />
88+
</ion-input>
89+
</ion-item>
90+
<ion-button type="submit" expand="block" [disabled]="!credForm.valid" class="ion-margin core-login-login-button ion-text-wrap">
91+
{{ 'core.login.loginbutton' | translate }}
92+
</ion-button>
93+
94+
<!-- Forgotten password option. -->
95+
<ion-button *ngIf="showForgottenPassword" expand="block" fill="clear"
96+
class="core-login-forgotten-password core-button-as-link ion-text-wrap" (click)="forgottenPassword()">
97+
{{ 'core.login.forgotaccount' | translate }}
98+
</ion-button>
99+
</form>
100+
101+
<ng-container *ngIf="isBrowserSSO">
102+
<ion-button expand="block" (click)="openBrowserSSO()" class="ion-margin core-login-login-inbrowser-button ion-text-wrap">
103+
{{ 'core.login.loginbutton' | translate }}
104+
<ion-icon name="fas-up-right-from-square" slot="end" aria-hidden="true" />
105+
</ion-button>
106+
<p class="text-center core-login-inbrowser">{{ 'core.openinbrowserdescription' | translate }}</p>
107+
</ng-container>
108+
109+
<!-- Additional Login methods -->
110+
<core-login-methods *ngIf="siteConfig" [site]="site" [siteConfig]="siteConfig" [siteUrl]="site.siteUrl" [redirectData]="redirectData"
111+
[showLoginForm]="showLoginForm" />
112+
</ng-template>

0 commit comments

Comments
 (0)