commit
f491ac3128
|
@ -90,7 +90,6 @@
|
|||
"multisitesdisplay": "",
|
||||
"sitefindersettings": {},
|
||||
"onlyallowlistedsites": false,
|
||||
"skipssoconfirmation": false,
|
||||
"forcedefaultlanguage": false,
|
||||
"privacypolicy": "https://moodle.net/moodle-app-privacy/",
|
||||
"notificoncolor": "#f98012",
|
||||
|
|
|
@ -2085,10 +2085,8 @@
|
|||
"core.login.invalidurl": "scorm",
|
||||
"core.login.invalidvaluemax": "local_moodlemobileapp",
|
||||
"core.login.invalidvaluemin": "local_moodlemobileapp",
|
||||
"core.login.loggedoutssodescription": "local_moodlemobileapp",
|
||||
"core.login.login": "moodle",
|
||||
"core.login.loginbutton": "local_moodlemobileapp",
|
||||
"core.login.logininsiterequired": "local_moodlemobileapp",
|
||||
"core.login.loginsteps": "moodle",
|
||||
"core.login.missingemail": "moodle",
|
||||
"core.login.missingfirstname": "moodle",
|
||||
|
@ -2124,7 +2122,6 @@
|
|||
"core.login.recaptchaincorrect": "local_moodlemobileapp",
|
||||
"core.login.reconnect": "local_moodlemobileapp",
|
||||
"core.login.reconnecthelp": "local_moodlemobileapp",
|
||||
"core.login.reconnectssodescription": "local_moodlemobileapp",
|
||||
"core.login.reconnectsupportsubject": "local_moodlemobileapp",
|
||||
"core.login.reconnecttosite": "local_moodlemobileapp",
|
||||
"core.login.removeaccount": "local_moodlemobileapp",
|
||||
|
@ -2231,6 +2228,7 @@
|
|||
"core.openfile": "local_moodlemobileapp",
|
||||
"core.openfullimage": "local_moodlemobileapp",
|
||||
"core.openinbrowser": "local_moodlemobileapp",
|
||||
"core.openinbrowserdescription": "local_moodlemobileapp",
|
||||
"core.openmodinbrowser": "local_moodlemobileapp",
|
||||
"core.opensecurityquestion": "local_moodlemobileapp",
|
||||
"core.opensettings": "local_moodlemobileapp",
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
display: block;
|
||||
|
||||
img {
|
||||
padding: 4px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,28 @@
|
|||
<ng-container *ngIf="loginMethods?.length">
|
||||
<div *ngIf="loginMethods.length || identityProviders.length || showScanQR" class="ion-text-center ion-padding core-login-methods-separator">
|
||||
<span>{{ 'core.login.or' | translate }}</span>
|
||||
</div>
|
||||
|
||||
<div class="core-login-methods" *ngIf="loginMethods.length">
|
||||
<ion-button [fill]="'outline'" class="ion-text-wrap ion-margin" *ngFor="let method of loginMethods" (click)="method.action()"
|
||||
[attr.aria-label]="method.name" expand="block">
|
||||
<ion-icon *ngIf="method.icon" [name]="method.icon" slot="start"></ion-icon>
|
||||
<ion-label>{{ method.name }}</ion-label>
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="showScanQR">
|
||||
<ion-button expand="block" fill="outline" class="ion-margin core-login-site-qrcode" (click)="showInstructionsAndScanQR()">
|
||||
<ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon>
|
||||
{{ 'core.scanqr' | translate }}
|
||||
</ion-button>
|
||||
</ng-container>
|
||||
|
||||
<!-- Identity providers. -->
|
||||
<ion-list *ngIf="identityProviders.length" class="core-login-identity-providers">
|
||||
<h2 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h2>
|
||||
<ion-button [fill]="'outline'" *ngFor="let provider of identityProviders" class="ion-text-wrap ion-margin core-oauth-provider"
|
||||
(click)="oauthClicked(provider)" [attr.aria-label]="provider.name" expand="block">
|
||||
<img *ngIf="provider.iconurl" [src]="provider.iconurl" alt="" width="32" height="32" slot="start" aria-hidden="true">
|
||||
<ion-label>{{ provider.name }}</ion-label>
|
||||
</ion-button>
|
||||
</ion-list>
|
||||
|
|
|
@ -12,9 +12,12 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site';
|
||||
import { CoreLoginHelper, CoreLoginMethod } from '@features/login/services/login-helper';
|
||||
import { CoreRedirectPayload } from '@services/navigator';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
|
||||
@Component({
|
||||
selector: 'core-login-methods',
|
||||
|
@ -23,18 +26,75 @@ import { CoreSites } from '@services/sites';
|
|||
})
|
||||
export class CoreLoginMethodsComponent implements OnInit {
|
||||
|
||||
loginMethods?: CoreLoginMethod[];
|
||||
@Input() reconnect = false;
|
||||
@Input() siteUrl = '';
|
||||
@Input() siteConfig?: CoreSitePublicConfigResponse;
|
||||
@Input() redirectData?: CoreRedirectPayload;
|
||||
|
||||
showScanQR = false;
|
||||
loginMethods: CoreLoginMethod[] = [];
|
||||
identityProviders: CoreSiteIdentityProvider[] = [];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.loginMethods = await CoreLoginHelper.getLoginMethods();
|
||||
const currentSite = CoreSites.getCurrentSite();
|
||||
const defaultMethod = await CoreLoginHelper.getDefaultLoginMethod();
|
||||
if (this.reconnect) {
|
||||
this.loginMethods = await CoreLoginHelper.getLoginMethods();
|
||||
|
||||
if (currentSite?.isLoggedOut() && defaultMethod) {
|
||||
await defaultMethod.action();
|
||||
const currentSite = CoreSites.getCurrentSite();
|
||||
const defaultMethod = await CoreLoginHelper.getDefaultLoginMethod();
|
||||
if (currentSite?.isLoggedOut() && defaultMethod) {
|
||||
await defaultMethod.action();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.siteConfig) {
|
||||
const disabledFeatures = CoreLoginHelper.getDisabledFeatures(this.siteConfig);
|
||||
|
||||
this.identityProviders = CoreLoginHelper.getValidIdentityProviders(this.siteConfig, disabledFeatures);
|
||||
|
||||
if (this.reconnect) {
|
||||
this.showScanQR = CoreLoginHelper.displayQRInSiteScreen();
|
||||
}
|
||||
|
||||
// If still false or credentials screen.
|
||||
if (!this.reconnect || !this.showScanQR) {
|
||||
this.showScanQR = await CoreLoginHelper.displayQRInCredentialsScreen(this.siteConfig.tool_mobile_qrcodetype);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show instructions and scan QR code.
|
||||
*
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async showInstructionsAndScanQR(): Promise<void> {
|
||||
try {
|
||||
await CoreLoginHelper.showScanQRInstructions();
|
||||
|
||||
await CoreLoginHelper.scanQR();
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An OAuth button was clicked.
|
||||
*
|
||||
* @param provider The provider that was clicked.
|
||||
*/
|
||||
oauthClicked(provider: CoreSiteIdentityProvider): void {
|
||||
const result = CoreLoginHelper.openBrowserForOAuthLogin(
|
||||
this.siteUrl,
|
||||
provider,
|
||||
this.siteConfig?.launchurl,
|
||||
this.redirectData,
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
CoreDomUtils.showErrorModal('Invalid data.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button fill="clear" (click)="close($event)" [attr.aria-label]="'core.back' | translate">
|
||||
<ion-icon name="arrow-back" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
<ion-button fill="clear" (click)="close($event)" [attr.aria-label]="'core.back' | translate" class="ion-back-button">
|
||||
<ion-icon ios="chevron-back" md="arrow-back-sharp" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
|
||||
|
|
|
@ -64,10 +64,8 @@
|
|||
"invalidurl": "Invalid URL specified",
|
||||
"invalidvaluemax": "The maximum value is {{$a}}",
|
||||
"invalidvaluemin": "The minimum value is {{$a}}",
|
||||
"loggedoutssodescription": "You have to authenticate again. You need to log in to the site in a browser window.",
|
||||
"login": "Log in",
|
||||
"loginbutton": "Log in",
|
||||
"logininsiterequired": "You need to log in to the site in a browser window.",
|
||||
"loginsteps": "For full access to this site, you first need to create an account.",
|
||||
"missingemail": "Missing email address",
|
||||
"missingfirstname": "Missing given name",
|
||||
|
@ -86,7 +84,7 @@
|
|||
"onboardingprovidefeedback": "Provide timely feedback",
|
||||
"onboardingtoconnect": "To connect to the Moodle App you'll need a Moodle site",
|
||||
"onboardingwelcome": "Welcome to the Moodle App!",
|
||||
"or": "OR",
|
||||
"or": "Or",
|
||||
"password": "Password",
|
||||
"passwordforgotten": "Forgotten password",
|
||||
"passwordforgotteninstructions2": "To reset your password, submit your username or your email address below. If we can find you in the database, an email will be sent to your email address, with instructions how to get access again.",
|
||||
|
@ -103,7 +101,6 @@
|
|||
"recaptchaincorrect": "The security question answer is incorrect.",
|
||||
"reconnect": "Reconnect",
|
||||
"reconnecthelp": "If you have problems reconnecting, try again later or contact your school or learning provider.",
|
||||
"reconnectssodescription": "Your session has expired. Please log in again in a browser window to continue.",
|
||||
"reconnectsupportsubject": "Need help reconnecting",
|
||||
"reconnecttosite": "Reconnect to the site",
|
||||
"removeaccount": "Remove account",
|
||||
|
|
|
@ -16,10 +16,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
form div {
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
ion-button.core-button-as-link {
|
||||
--color: var(--core-login-text-color);
|
||||
text-decoration-color: var(--core-login-text-color);
|
||||
|
@ -29,56 +25,73 @@
|
|||
}
|
||||
}
|
||||
|
||||
form .item.item-input,
|
||||
form .core-username.item {
|
||||
margin-bottom: 16px;
|
||||
|
||||
|
||||
.core-login-reconnect-warning {
|
||||
margin: 0px 0px 32px 0px;
|
||||
}
|
||||
|
||||
form .core-username.ios {
|
||||
--inner-border-width: 0 0 1px 0;
|
||||
}
|
||||
.core-login-info-box {
|
||||
margin-bottom: 32px;
|
||||
|
||||
form .item,
|
||||
form .item ion-label {
|
||||
--background: var(--core-login-input-background);
|
||||
--color: var(--core-login-input-color);
|
||||
}
|
||||
.core-login-site {
|
||||
.core-login-site-logo {
|
||||
width: 90%;
|
||||
max-width: 300px;
|
||||
margin: 0px auto;
|
||||
|
||||
form .core-username.item p {
|
||||
font-size: 16px;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 104px;
|
||||
}
|
||||
}
|
||||
|
||||
form .core-username.item.md p {
|
||||
@include padding-horizontal(8px, null);
|
||||
}
|
||||
.core-sitename {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.core-login-site-logo img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.core-siteurl {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.core-sitename + .core-siteurl {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.core-sitename {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.core-login-site-logo {
|
||||
width: 90%;
|
||||
max-width: 300px;
|
||||
margin: 5px auto;
|
||||
}
|
||||
|
||||
.core-login-forgotten-password {
|
||||
text-decoration: underline;
|
||||
.core-login-site + .core-login-user {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
core-user-avatar.large-avatar {
|
||||
--core-avatar-size: var(--core-large-avatar-size);
|
||||
}
|
||||
|
||||
.core-login-fullname {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.core-login-methods {
|
||||
form .item.item-input {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
form .item,
|
||||
form .item ion-label {
|
||||
--background: var(--core-login-input-background);
|
||||
--color: var(--core-login-input-color);
|
||||
}
|
||||
}
|
||||
|
||||
ion-button {
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.core-login-forgotten-password {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@if ($core-login-hide-forgot-password) {
|
||||
.core-login-forgotten-password {
|
||||
display: none;
|
||||
|
@ -106,12 +119,61 @@
|
|||
}
|
||||
}
|
||||
|
||||
.core-login-methods-separator {
|
||||
position: relative;
|
||||
padding: 8px 0;
|
||||
|
||||
span {
|
||||
background: var(--core-login-background);
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
height: 1px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
border-bottom: 1px solid var(--gray-300);
|
||||
content: "";
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@if ($core-login-hide-qrcode) {
|
||||
.core-login-site-qrcode,
|
||||
.core-login-site-qrcode-separator {
|
||||
.core-login-methods-separator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.core-login-login-button {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.core-login-login-inbrowser-button {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
p.core-login-inbrowser {
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.core-login-sign-up {
|
||||
margin-top: 8px;
|
||||
border-top: 1px solid var(--gray-300);
|
||||
}
|
||||
|
||||
.core-login-identity-providers h2,
|
||||
.core-login-sign-up h2 {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
:host-context(html.dark) {
|
||||
|
|
|
@ -20,90 +20,92 @@
|
|||
</ion-header>
|
||||
<ion-content class="ion-padding limited-width">
|
||||
<core-loading [hideUntil]="pageLoaded">
|
||||
<div class="ion-text-wrap ion-text-center ion-margin-bottom">
|
||||
<div class="core-login-site-logo">
|
||||
<!-- Show site logo or a default image. -->
|
||||
<img *ngIf="logoUrl" [src]="logoUrl" role="presentation" alt="" onError="this.src='assets/img/login_logo.png'">
|
||||
<img *ngIf="!logoUrl" src="assets/img/login_logo.png" role="presentation" alt="">
|
||||
<ng-container *ngIf="!siteCheckError">
|
||||
<div class="ion-text-wrap ion-text-center core-login-info-box">
|
||||
<div class="core-login-site">
|
||||
<div class="core-login-site-logo">
|
||||
<!-- Show site logo or a default image. -->
|
||||
<img *ngIf="logoUrl" [src]="logoUrl" role="presentation" alt="" onError="this.src='assets/img/login_logo.png'">
|
||||
<img *ngIf="!logoUrl" src="assets/img/login_logo.png" role="presentation" alt="">
|
||||
</div>
|
||||
|
||||
<h2 *ngIf="siteName" class="ion-margin-top ion-no-padding core-sitename">
|
||||
<core-format-text [text]="siteName" [filter]="false"></core-format-text>
|
||||
</h2>
|
||||
<p class="core-siteurl">{{siteUrl}}</p>
|
||||
</div>
|
||||
|
||||
<core-login-exceeded-attempts *ngIf="exceededAttemptsHTML && supportConfig && loginAttempts >= 3"
|
||||
[supportConfig]="supportConfig" [supportSubject]="'core.login.exceededloginattemptssupportsubject' | translate">
|
||||
<div [innerHTML]="exceededAttemptsHTML" (click)="exceededAttemptsClicked($event)"></div>
|
||||
</core-login-exceeded-attempts>
|
||||
</div>
|
||||
|
||||
<h2 *ngIf="siteName" class="ion-padding core-sitename">
|
||||
<core-format-text [text]="siteName" [filter]="false"></core-format-text>
|
||||
</h2>
|
||||
<p class="core-siteurl">{{siteUrl}}</p>
|
||||
</div>
|
||||
<div class="core-login-methods">
|
||||
<form [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #credentialsForm *ngIf="!isBrowserSSO">
|
||||
<ion-item>
|
||||
<ion-label class="sr-only">{{ 'core.login.username' | translate }}</ion-label>
|
||||
<ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}"
|
||||
formControlName="username" autocapitalize="none" autocorrect="off" autocomplete="username" enterkeyhint="next"
|
||||
required="true">
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
<ion-item class="ion-margin-bottom">
|
||||
<ion-label class="sr-only">{{ 'core.login.password' | translate }}</ion-label>
|
||||
<core-show-password name="password">
|
||||
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
|
||||
formControlName="password" [clearOnEdit]="false" autocomplete="current-password" enterkeyhint="go"
|
||||
required="true">
|
||||
</ion-input>
|
||||
</core-show-password>
|
||||
</ion-item>
|
||||
<ion-button expand="block" type="submit" [disabled]="!credForm.valid"
|
||||
class="ion-margin core-login-login-button ion-text-wrap">
|
||||
{{ 'core.login.loginbutton' | translate }}
|
||||
</ion-button>
|
||||
<!-- Remove this once Ionic fixes this bug: https://github.com/ionic-team/ionic-framework/issues/19368 -->
|
||||
<input type="submit" class="core-submit-hidden-enter" />
|
||||
|
||||
<core-login-exceeded-attempts *ngIf="exceededAttemptsHTML && supportConfig && loginAttempts >= 3" [supportConfig]="supportConfig"
|
||||
[supportSubject]="'core.login.exceededloginattemptssupportsubject' | translate">
|
||||
<div [innerHTML]="exceededAttemptsHTML" (click)="exceededAttemptsClicked($event)"></div>
|
||||
</core-login-exceeded-attempts>
|
||||
<!-- Forgotten password option. -->
|
||||
<ion-button *ngIf="showForgottenPassword" expand="block" fill="clear"
|
||||
class="core-login-forgotten-password core-button-as-link ion-text-wrap" (click)="forgottenPassword()">
|
||||
{{ 'core.login.forgotten' | translate }}
|
||||
</ion-button>
|
||||
</form>
|
||||
|
||||
<form [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #credentialsForm>
|
||||
<ion-item *ngIf="siteChecked && !isBrowserSSO">
|
||||
<ion-label class="sr-only">{{ 'core.login.username' | translate }}</ion-label>
|
||||
<ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}" formControlName="username"
|
||||
autocapitalize="none" autocorrect="off" autocomplete="username" enterkeyhint="next" required="true">
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="siteChecked && !isBrowserSSO" class="ion-margin-bottom">
|
||||
<ion-label class="sr-only">{{ 'core.login.password' | translate }}</ion-label>
|
||||
<core-show-password name="password">
|
||||
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
|
||||
formControlName="password" [clearOnEdit]="false" autocomplete="current-password" enterkeyhint="go" required="true">
|
||||
</ion-input>
|
||||
</core-show-password>
|
||||
</ion-item>
|
||||
<ion-button expand="block" type="submit" [disabled]="siteChecked && !isBrowserSSO && !credForm.valid"
|
||||
class="ion-margin core-login-login-button ion-text-wrap">
|
||||
{{ 'core.login.loginbutton' | translate }}
|
||||
</ion-button>
|
||||
<!-- Remove this once Ionic fixes this bug: https://github.com/ionic-team/ionic-framework/issues/19368 -->
|
||||
<input type="submit" class="core-submit-hidden-enter" />
|
||||
<ng-container *ngIf="isBrowserSSO">
|
||||
<ion-button expand="block" (click)="openBrowserSSO()"
|
||||
class="ion-margin core-login-login-inbrowser-button ion-text-wrap">
|
||||
{{ 'core.login.loginbutton' | translate }}
|
||||
<ion-icon name="fas-up-right-from-square" slot="end" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
<p class="text-center core-login-inbrowser">{{ 'core.openinbrowserdescription' | translate }}</p>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="showScanQR">
|
||||
<div class="ion-text-center ion-padding core-login-site-qrcode-separator">{{ 'core.login.or' | translate }}</div>
|
||||
<ion-button expand="block" fill="outline" class="ion-margin core-login-site-qrcode" (click)="showInstructionsAndScanQR()">
|
||||
<ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon>
|
||||
{{ 'core.scanqr' | translate }}
|
||||
</ion-button>
|
||||
</ng-container>
|
||||
</form>
|
||||
|
||||
<!-- Forgotten password option. -->
|
||||
<ion-button *ngIf="showForgottenPassword" expand="block" fill="clear"
|
||||
class="core-login-forgotten-password core-button-as-link ion-text-wrap" (click)="forgottenPassword()">
|
||||
{{ 'core.login.forgotten' | translate }}
|
||||
</ion-button>
|
||||
<core-login-methods *ngIf="siteConfig" [siteConfig]="siteConfig" [siteUrl]="siteUrl"></core-login-methods>
|
||||
</div>
|
||||
|
||||
<ion-list *ngIf="identityProviders && identityProviders.length" class="ion-padding-top core-login-identity-providers">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-button fill="outline" *ngFor="let provider of identityProviders" class="ion-text-wrap ion-margin core-oauth-provider"
|
||||
(click)="oauthClicked(provider)" [attr.aria-label]="provider.name" expand="block">
|
||||
<img *ngIf="provider.iconurl" [src]="provider.iconurl" alt="" width="32" height="32" slot="start" aria-hidden="true">
|
||||
<ion-label>{{provider.name}}</ion-label>
|
||||
</ion-button>
|
||||
</ion-list>
|
||||
<div class="core-login-sign-up" *ngIf="canSignup || authInstructions">
|
||||
<h2>{{ 'core.login.firsttime' | translate }}</h2>
|
||||
|
||||
<ion-list *ngIf="canSignup || authInstructions" class="ion-padding-top core-login-sign-up">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2 class="item-heading">{{ 'core.login.firsttime' | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="authInstructions">
|
||||
<ion-label>
|
||||
<p>
|
||||
<core-format-text [text]="authInstructions" [filter]="false"></core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap ion-no-padding core-login-instructions">
|
||||
<ion-label>
|
||||
<core-format-text *ngIf="authInstructions" [text]="authInstructions" [filter]="false"></core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
|
||||
</div>
|
||||
<ion-button *ngIf="canSignup" expand="block" class="ion-margin ion-text-wrap" fill="outline" (click)="openEmailSignup()">
|
||||
{{ 'core.login.startsignup' | translate }}
|
||||
</ion-button>
|
||||
</ion-list>
|
||||
</ng-container>
|
||||
<core-empty-box *ngIf="siteCheckError" icon="fas-circle-exclamation" [message]="siteCheckError">
|
||||
<ion-button expand="block" (click)="checkSite()" fill="outline">
|
||||
{{ 'core.tryagain' | translate }}
|
||||
</ion-button>
|
||||
</core-empty-box>
|
||||
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
|
|
|
@ -19,12 +19,12 @@ import { debounceTime } from 'rxjs/operators';
|
|||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreNetwork } from '@services/network';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSiteCheckResponse, CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site';
|
||||
import { CoreSitePublicConfigResponse } from '@classes/site';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
|
@ -48,12 +48,10 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
|
||||
credForm!: FormGroup;
|
||||
siteUrl!: string;
|
||||
siteChecked = false;
|
||||
siteName?: string;
|
||||
logoUrl?: string;
|
||||
authInstructions?: string;
|
||||
canSignup?: boolean;
|
||||
identityProviders?: CoreSiteIdentityProvider[];
|
||||
pageLoaded = false;
|
||||
isBrowserSSO = false;
|
||||
showForgottenPassword = true;
|
||||
|
@ -61,8 +59,10 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
loginAttempts = 0;
|
||||
supportConfig?: CoreUserSupportConfig;
|
||||
exceededAttemptsHTML?: SafeHtml | string | null;
|
||||
siteConfig?: CoreSitePublicConfigResponse;
|
||||
siteCheckError = '';
|
||||
|
||||
protected siteConfig?: CoreSitePublicConfigResponse;
|
||||
protected siteCheck?: CoreSiteCheckResponse;
|
||||
protected eventThrown = false;
|
||||
protected viewLeft = false;
|
||||
protected siteId?: string;
|
||||
|
@ -78,10 +78,19 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
try {
|
||||
this.siteUrl = CoreNavigator.getRequiredRouteParam<string>('siteUrl');
|
||||
this.siteCheck = CoreNavigator.getRouteParam<CoreSiteCheckResponse>('siteCheck');
|
||||
if (this.siteCheck?.siteUrl) {
|
||||
this.siteUrl = this.siteCheck.siteUrl;
|
||||
} else {
|
||||
this.siteUrl = CoreNavigator.getRequiredRouteParam<string>('siteUrl');
|
||||
}
|
||||
|
||||
if (this.siteCheck?.config) {
|
||||
this.siteConfig = this.siteCheck.config;
|
||||
}
|
||||
|
||||
this.siteName = CoreNavigator.getRouteParam('siteName');
|
||||
this.logoUrl = !CoreConstants.CONFIG.forceLoginLogo && CoreNavigator.getRouteParam('logoUrl') || undefined;
|
||||
this.siteConfig = CoreNavigator.getRouteParam<CoreSitePublicConfigResponse>('siteConfig');
|
||||
this.urlToOpen = CoreNavigator.getRouteParam('urlToOpen');
|
||||
this.supportConfig = this.siteConfig && new CoreUserGuestSupportConfig(this.siteConfig);
|
||||
} catch (error) {
|
||||
|
@ -95,21 +104,9 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
password: ['', Validators.required],
|
||||
});
|
||||
|
||||
if (this.siteConfig) {
|
||||
this.treatSiteConfig();
|
||||
}
|
||||
await this.checkSite();
|
||||
|
||||
const isSingleFixedSite = await CoreLoginHelper.isSingleFixedSite();
|
||||
|
||||
if (isSingleFixedSite || !this.siteConfig) {
|
||||
// Fixed URL or not siteConfig retrieved from params, we need to check if it uses browser SSO login.
|
||||
this.checkSite(this.siteUrl, true);
|
||||
} else {
|
||||
this.siteChecked = true;
|
||||
this.pageLoaded = true;
|
||||
}
|
||||
|
||||
if (CorePlatform.isIOS()) {
|
||||
if (CorePlatform.isIOS() && !this.isBrowserSSO) {
|
||||
// Make iOS auto-fill work. The field that isn't focused doesn't get updated, do it manually.
|
||||
// Debounce it to prevent triggering this function too often when the user is typing.
|
||||
this.valueChangeSubscription = this.credForm.valueChanges.pipe(debounceTime(1000)).subscribe((changes) => {
|
||||
|
@ -147,48 +144,31 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
* Get site config and check if it requires SSO login.
|
||||
* This should be used only if a fixed URL is set, otherwise this check is already performed in CoreLoginSitePage.
|
||||
*
|
||||
* @param siteUrl Site URL to check.
|
||||
* @param onInit Whether the check site is done when initializing the page.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async checkSite(siteUrl: string, onInit = false): Promise<void> {
|
||||
async checkSite(): Promise<void> {
|
||||
this.pageLoaded = false;
|
||||
|
||||
// If the site is configured with http:// protocol we force that one, otherwise we use default mode.
|
||||
const protocol = siteUrl.indexOf('http://') === 0 ? 'http://' : undefined;
|
||||
const protocol = this.siteUrl.indexOf('http://') === 0 ? 'http://' : undefined;
|
||||
|
||||
try {
|
||||
const result = await CoreSites.checkSite(siteUrl, protocol);
|
||||
|
||||
this.siteChecked = true;
|
||||
this.siteUrl = result.siteUrl;
|
||||
|
||||
this.siteConfig = result.config;
|
||||
this.treatSiteConfig();
|
||||
|
||||
if (CoreLoginHelper.isSSOLoginNeeded(result.code)) {
|
||||
// SSO. User needs to authenticate in a browser.
|
||||
this.isBrowserSSO = true;
|
||||
|
||||
if (this.showScanQR && onInit) {
|
||||
// Don't open browser automatically, let the user view the scan QR button.
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that there's no SSO authentication ongoing and the view hasn't changed.
|
||||
if (!CoreApp.isSSOAuthenticationOngoing() && !this.viewLeft) {
|
||||
CoreLoginHelper.confirmAndOpenBrowserForSSOLogin(
|
||||
result.siteUrl,
|
||||
result.code,
|
||||
result.service,
|
||||
result.config?.launchurl,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.isBrowserSSO = false;
|
||||
if (!this.siteCheck) {
|
||||
this.siteCheck = await CoreSites.checkSite(this.siteUrl, protocol);
|
||||
}
|
||||
|
||||
this.siteUrl = this.siteCheck.siteUrl;
|
||||
this.siteConfig = this.siteCheck.config;
|
||||
|
||||
await this.treatSiteConfig();
|
||||
|
||||
this.siteCheckError = '';
|
||||
|
||||
// Check if user needs to authenticate in a browser.
|
||||
this.isBrowserSSO = CoreLoginHelper.isSSOLoginNeeded(this.siteCheck.code);
|
||||
} catch (error) {
|
||||
this.siteCheckError = CoreDomUtils.getErrorMessage(error) || 'Error loading site';
|
||||
|
||||
CoreDomUtils.showErrorModal(error);
|
||||
} finally {
|
||||
this.pageLoaded = true;
|
||||
|
@ -199,31 +179,54 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
* Treat the site configuration (if it exists).
|
||||
*/
|
||||
protected async treatSiteConfig(): Promise<void> {
|
||||
if (this.siteConfig) {
|
||||
this.siteName = this.siteConfig.sitename;
|
||||
this.logoUrl = CoreLoginHelper.getLogoUrl(this.siteConfig);
|
||||
this.authInstructions = this.siteConfig.authinstructions || Translate.instant('core.login.loginsteps');
|
||||
this.showScanQR = await CoreLoginHelper.displayQRInCredentialsScreen(this.siteConfig.tool_mobile_qrcodetype);
|
||||
|
||||
const disabledFeatures = CoreLoginHelper.getDisabledFeatures(this.siteConfig);
|
||||
this.identityProviders = CoreLoginHelper.getValidIdentityProviders(this.siteConfig, disabledFeatures);
|
||||
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;
|
||||
CoreEvents.trigger(CoreEvents.LOGIN_SITE_CHECKED, { config: this.siteConfig });
|
||||
}
|
||||
} else {
|
||||
if (!this.siteConfig) {
|
||||
this.authInstructions = undefined;
|
||||
this.canSignup = false;
|
||||
this.identityProviders = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.siteName = this.siteConfig.sitename;
|
||||
this.logoUrl = CoreLoginHelper.getLogoUrl(this.siteConfig);
|
||||
this.authInstructions = this.siteConfig.authinstructions || Translate.instant('core.login.loginsteps');
|
||||
this.showScanQR = await CoreLoginHelper.displayQRInCredentialsScreen(this.siteConfig.tool_mobile_qrcodetype);
|
||||
|
||||
const disabledFeatures = CoreLoginHelper.getDisabledFeatures(this.siteConfig);
|
||||
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;
|
||||
CoreEvents.trigger(CoreEvents.LOGIN_SITE_CHECKED, { config: this.siteConfig });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to authenticate the user using the browser.
|
||||
*
|
||||
* @param e Event.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async openBrowserSSO(e?: Event): Promise<void> {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
|
||||
// Check that there's no SSO authentication ongoing and the view hasn't changed.
|
||||
if (CoreApp.isSSOAuthenticationOngoing() || this.viewLeft || !this.siteCheck) {
|
||||
return;
|
||||
}
|
||||
|
||||
CoreLoginHelper.openBrowserForSSOLogin(
|
||||
this.siteCheck.siteUrl,
|
||||
this.siteCheck.code,
|
||||
this.siteCheck.service,
|
||||
this.siteCheck.config?.launchurl,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -233,10 +236,8 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async login(e?: Event): Promise<void> {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
|
||||
CoreApp.closeKeyboard();
|
||||
|
||||
|
@ -245,18 +246,6 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
const username = this.credForm.value.username;
|
||||
const password = this.credForm.value.password;
|
||||
|
||||
if (!this.siteChecked || this.isBrowserSSO) {
|
||||
// Site wasn't checked (it failed) or a previous check determined it was SSO. Let's check again.
|
||||
await this.checkSite(siteUrl);
|
||||
|
||||
if (!this.isBrowserSSO && this.siteChecked) {
|
||||
// Site doesn't use browser SSO, throw app's login again.
|
||||
return this.login();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
CoreDomUtils.showErrorModal('core.login.usernamerequired', true);
|
||||
|
||||
|
@ -329,32 +318,6 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
CoreLoginHelper.forgottenPasswordClicked(this.siteUrl, this.credForm.value.username, this.siteConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* An OAuth button was clicked.
|
||||
*
|
||||
* @param provider The provider that was clicked.
|
||||
*/
|
||||
oauthClicked(provider: CoreSiteIdentityProvider): void {
|
||||
if (!CoreLoginHelper.openBrowserForOAuthLogin(this.siteUrl, provider, this.siteConfig?.launchurl)) {
|
||||
CoreDomUtils.showErrorModal('Invalid data.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show instructions and scan QR code.
|
||||
*
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async showInstructionsAndScanQR(): Promise<void> {
|
||||
try {
|
||||
await CoreLoginHelper.showScanQRInstructions();
|
||||
|
||||
await CoreLoginHelper.scanQR();
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open email signup page.
|
||||
*/
|
||||
|
@ -370,7 +333,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* View destroyed.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.viewLeft = true;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||
<ion-button fill="clear" (click)="cancel($event)" [attr.aria-label]="'core.back' | translate" class="ion-back-button">
|
||||
<ion-icon ios="chevron-back" md="arrow-back-sharp" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
|
||||
<ion-title>
|
||||
|
@ -18,109 +20,79 @@
|
|||
<ion-content class="ion-padding" (keydown)="keyDown($event)" (keyup)="keyUp($event)">
|
||||
<core-loading [hideUntil]="!showLoading">
|
||||
<div class="list-item-limited-width">
|
||||
<div class="ion-text-wrap ion-text-center ion-margin-bottom" [ngClass]="{'item-avatar-center': showUserAvatar}">
|
||||
<!-- Show user avatar. -->
|
||||
<core-user-avatar class="large-avatar" *ngIf="showUserAvatar" [user]="siteInfo" [linkProfile]="false"
|
||||
[siteId]="siteId"></core-user-avatar>
|
||||
<ion-card *ngIf="!isLoggedOut" class="core-warning-card core-login-reconnect-warning">
|
||||
<ion-item>
|
||||
<ion-icon name="fas-triangle-exclamation" slot="start" aria-hidden="true"></ion-icon>
|
||||
<ion-label>
|
||||
<p>{{ 'core.lostconnection' | translate }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<div class="core-login-site-logo" *ngIf="!showUserAvatar">
|
||||
<!-- Show site logo or a default image. -->
|
||||
<img *ngIf="logoUrl" [src]="logoUrl" role="presentation" onError="this.src='assets/img/login_logo.png'" alt="">
|
||||
<img *ngIf="!logoUrl" src="assets/img/login_logo.png" role="presentation" alt="">
|
||||
<div class="ion-text-wrap ion-text-center core-login-info-box">
|
||||
<div class="core-login-site">
|
||||
<div class="core-login-site-logo" *ngIf="!showUserAvatar">
|
||||
<!-- Show site logo or a default image. -->
|
||||
<img *ngIf="logoUrl" [src]="logoUrl" role="presentation" onError="this.src='assets/img/login_logo.png'" alt="">
|
||||
<img *ngIf="!logoUrl" src="assets/img/login_logo.png" role="presentation" alt="">
|
||||
</div>
|
||||
|
||||
<p *ngIf="siteInfo?.siteName" class="ion-no-margin ion-no-padding core-sitename">
|
||||
<core-format-text [text]="siteInfo?.siteName" [filter]="false"></core-format-text>
|
||||
</p>
|
||||
<p class="core-siteurl">{{siteUrl}}</p>
|
||||
</div>
|
||||
|
||||
<p *ngIf="siteInfo?.siteName" class="ion-padding core-sitename">
|
||||
<core-format-text [text]="siteInfo?.siteName" [filter]="false"></core-format-text>
|
||||
</p>
|
||||
<p class="core-siteurl">{{siteUrl}}</p>
|
||||
|
||||
<ion-card *ngIf="!isLoggedOut" class="core-danger-card core-login-reconnect-warning">
|
||||
<ion-item>
|
||||
<ion-icon name="fas-circle-exclamation" slot="start" aria-hidden="true"></ion-icon>
|
||||
<ion-label>
|
||||
<p>{{ 'core.lostconnection' | translate }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<div class="core-login-user">
|
||||
<!-- Show user avatar. -->
|
||||
<core-user-avatar class="large-avatar" *ngIf="showUserAvatar" [user]="siteInfo" [linkProfile]="false"
|
||||
[siteId]="siteId"></core-user-avatar>
|
||||
<p *ngIf="siteInfo?.fullname" class="core-login-fullname">
|
||||
<core-format-text [text]="siteInfo?.fullname" [filter]="false"></core-format-text>
|
||||
</p>
|
||||
</div>
|
||||
<core-login-exceeded-attempts *ngIf="exceededAttemptsHTML && supportConfig && reconnectAttempts >= 3"
|
||||
[supportConfig]="supportConfig" [supportSubject]="'core.login.exceededloginattemptssupportsubject' | translate">
|
||||
<div [innerHTML]="exceededAttemptsHTML" (click)="exceededAttemptsClicked($event)"></div>
|
||||
</core-login-exceeded-attempts>
|
||||
</div>
|
||||
<form *ngIf="!isOAuth" [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #reconnectForm>
|
||||
<ion-item class="ion-text-wrap core-username item-interactive">
|
||||
<ion-label>
|
||||
<p>{{username}}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-margin-bottom" *ngIf="!isBrowserSSO">
|
||||
<ion-label class="sr-only">{{ 'core.login.password' | translate }}</ion-label>
|
||||
<core-show-password name="password">
|
||||
<ion-input class="core-ioninput-password" name="password" type="password"
|
||||
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
|
||||
autocomplete="current-password" enterkeyhint="go" required="true">
|
||||
</ion-input>
|
||||
</core-show-password>
|
||||
</ion-item>
|
||||
<div class="adaptable-buttons-row-reverse">
|
||||
<ion-button *ngIf="!isBrowserSSO" type="submit" expand="block" [disabled]="!credForm.valid"
|
||||
|
||||
<div class="core-login-methods">
|
||||
<form *ngIf="!isBrowserSSO" [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #reconnectForm>
|
||||
<ion-item class="ion-margin-bottom">
|
||||
<ion-label class="sr-only">{{ 'core.login.password' | translate }}</ion-label>
|
||||
<core-show-password name="password">
|
||||
<ion-input class="core-ioninput-password" name="password" type="password"
|
||||
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
|
||||
autocomplete="current-password" enterkeyhint="go" required="true">
|
||||
</ion-input>
|
||||
</core-show-password>
|
||||
</ion-item>
|
||||
<ion-button type="submit" expand="block" [disabled]="!credForm.valid"
|
||||
class="ion-margin core-login-login-button ion-text-wrap">
|
||||
{{ 'core.login.loginbutton' | translate }}
|
||||
</ion-button>
|
||||
<ion-button expand="block" fill="outline" (click)="cancel($event)" class="ion-margin ion-text-wrap">
|
||||
{{ 'core.login.cancel' | translate }}
|
||||
|
||||
<!-- Forgotten password option. -->
|
||||
<ion-button *ngIf="showForgottenPassword" expand="block" fill="clear"
|
||||
class="core-login-forgotten-password core-button-as-link ion-text-wrap" (click)="forgottenPassword()">
|
||||
{{ 'core.login.forgotten' | translate }}
|
||||
</ion-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ion-button expand="block" *ngIf="isBrowserSSO" (click)="openBrowserSSO()"
|
||||
class="ion-margin core-login-login-button ion-text-wrap">
|
||||
{{ 'core.login.loginbutton' | translate }}
|
||||
</ion-button>
|
||||
|
||||
<!-- Forgotten password option. -->
|
||||
<ion-button *ngIf="showForgottenPassword && !isOAuth" expand="block" fill="clear"
|
||||
class="core-login-forgotten-password core-button-as-link ion-text-wrap" (click)="forgottenPassword()">
|
||||
{{ 'core.login.forgotten' | translate }}
|
||||
</ion-button>
|
||||
|
||||
<div class="ion-text-center ion-padding core-login-site-qrcode-separator">{{ 'core.login.or' | translate }}</div>
|
||||
|
||||
<!-- Login methods -->
|
||||
<core-login-methods></core-login-methods>
|
||||
|
||||
<ng-container *ngIf="showScanQR && !isBrowserSSO">
|
||||
<ion-button expand="block" fill="outline" class="ion-margin core-login-site-qrcode"
|
||||
(click)="showInstructionsAndScanQR()">
|
||||
<ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon>
|
||||
{{ 'core.scanqr' | translate }}
|
||||
<ng-container *ngIf="isBrowserSSO">
|
||||
<ion-button expand="block" (click)="openBrowserSSO()"
|
||||
class="ion-margin core-login-login-inbrowser-button ion-text-wrap">
|
||||
{{ 'core.login.loginbutton' | translate }}
|
||||
<ion-icon name="fas-up-right-from-square" slot="end" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
<p class="text-center core-login-inbrowser">{{ 'core.openinbrowserdescription' | translate }}</p>
|
||||
</ng-container>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
<!-- Identity providers. -->
|
||||
<ion-list *ngIf="identityProviders?.length" class="ion-padding-top core-login-identity-providers">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-button [fill]="'outline'" *ngFor="let provider of identityProviders"
|
||||
class="ion-text-wrap ion-margin core-oauth-provider" (click)="oauthClicked(provider)" [attr.aria-label]="provider.name"
|
||||
expand="block">
|
||||
<img *ngIf="provider.iconurl" [src]="provider.iconurl" alt="" width="32" height="32" slot="start" aria-hidden="true">
|
||||
<ion-label>{{ provider.name }}</ion-label>
|
||||
</ion-button>
|
||||
</ion-list>
|
||||
|
||||
<!-- If OAuth, display cancel button since the form isn't displayed. -->
|
||||
<ion-list *ngIf="isOAuth">
|
||||
<ion-button expand="block" class="ion-margin" fill="outline" (click)="cancel($event)">
|
||||
{{ 'core.login.cancel' | translate }}
|
||||
</ion-button>
|
||||
</ion-list>
|
||||
<!-- Additional Login methods -->
|
||||
<core-login-methods *ngIf="siteConfig" [siteConfig]="siteConfig" [reconnect]="true" [siteUrl]="siteUrl"
|
||||
[redirectData]="redirectData"></core-login-methods>
|
||||
</div>
|
||||
</div>
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
|
|
|
@ -21,7 +21,7 @@ import { CoreSiteBasicInfo, CoreSites, CoreSitesReadingStrategy } from '@service
|
|||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
import { CoreSite, CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site';
|
||||
import { CoreSite, CoreSitePublicConfigResponse } from '@classes/site';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { CoreNavigator, CoreRedirectPayload } from '@services/navigator';
|
||||
|
@ -46,13 +46,10 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
|
||||
credForm: FormGroup;
|
||||
siteUrl!: string;
|
||||
username!: string;
|
||||
logoUrl?: string;
|
||||
identityProviders?: CoreSiteIdentityProvider[];
|
||||
showForgottenPassword = true;
|
||||
showUserAvatar = false;
|
||||
isBrowserSSO = false;
|
||||
isOAuth = false;
|
||||
isLoggedOut: boolean;
|
||||
siteId!: string;
|
||||
siteInfo?: CoreSiteBasicInfo;
|
||||
|
@ -61,12 +58,13 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
reconnectAttempts = 0;
|
||||
supportConfig?: CoreUserSupportConfig;
|
||||
exceededAttemptsHTML?: SafeHtml | string | null;
|
||||
siteConfig?: CoreSitePublicConfigResponse;
|
||||
redirectData?: CoreRedirectPayload;
|
||||
|
||||
protected siteConfig?: CoreSitePublicConfigResponse;
|
||||
protected viewLeft = false;
|
||||
protected eventThrown = false;
|
||||
protected redirectData?: CoreRedirectPayload;
|
||||
protected loginSuccessful = false;
|
||||
protected username = '';
|
||||
|
||||
constructor(
|
||||
protected fb: FormBuilder,
|
||||
|
@ -119,15 +117,12 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
this.username = site.infos.username;
|
||||
this.supportConfig = new CoreUserAuthenticatedSupportConfig(site);
|
||||
|
||||
// If login was OAuth we should only reach this page if the OAuth method ID has changed.
|
||||
this.isOAuth = site.isOAuth();
|
||||
|
||||
const availableSites = await CoreLoginHelper.getAvailableSites();
|
||||
|
||||
// Show logo instead of avatar if it's a fixed site.
|
||||
this.showUserAvatar = !availableSites.length;
|
||||
|
||||
this.checkSiteConfig(site);
|
||||
await this.checkSiteConfig(site);
|
||||
|
||||
this.showLoading = false;
|
||||
} catch (error) {
|
||||
|
@ -175,9 +170,6 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
return;
|
||||
}
|
||||
|
||||
const disabledFeatures = CoreLoginHelper.getDisabledFeatures(this.siteConfig);
|
||||
|
||||
this.identityProviders = CoreLoginHelper.getValidIdentityProviders(this.siteConfig, disabledFeatures);
|
||||
this.showForgottenPassword = !CoreLoginHelper.isForgottenPasswordDisabled(this.siteConfig);
|
||||
this.exceededAttemptsHTML = CoreLoginHelper.buildExceededAttemptsHTML(
|
||||
!!this.supportConfig?.canContactSupport(),
|
||||
|
@ -189,13 +181,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
CoreEvents.trigger(CoreEvents.LOGIN_SITE_CHECKED, { config: this.siteConfig });
|
||||
}
|
||||
|
||||
this.isBrowserSSO = !this.isOAuth && CoreLoginHelper.isSSOLoginNeeded(this.siteConfig.typeoflogin);
|
||||
|
||||
this.showScanQR = CoreLoginHelper.displayQRInSiteScreen();
|
||||
|
||||
if (!this.showScanQR) {
|
||||
this.showScanQR = await CoreLoginHelper.displayQRInCredentialsScreen(this.siteConfig.tool_mobile_qrcodetype);
|
||||
}
|
||||
this.isBrowserSSO = CoreLoginHelper.isSSOLoginNeeded(this.siteConfig.typeoflogin);
|
||||
|
||||
await CoreSites.checkApplication(this.siteConfig);
|
||||
|
||||
|
@ -315,7 +301,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
return;
|
||||
}
|
||||
|
||||
CoreLoginHelper.confirmAndOpenBrowserForSSOLogin(
|
||||
CoreLoginHelper.openBrowserForSSOLogin(
|
||||
this.siteUrl,
|
||||
this.siteConfig.typeoflogin,
|
||||
undefined,
|
||||
|
@ -324,39 +310,6 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* An OAuth button was clicked.
|
||||
*
|
||||
* @param provider The provider that was clicked.
|
||||
*/
|
||||
oauthClicked(provider: CoreSiteIdentityProvider): void {
|
||||
const result = CoreLoginHelper.openBrowserForOAuthLogin(
|
||||
this.siteUrl,
|
||||
provider,
|
||||
this.siteConfig?.launchurl,
|
||||
this.redirectData,
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
CoreDomUtils.showErrorModal('Invalid data.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show instructions and scan QR code.
|
||||
*
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async showInstructionsAndScanQR(): Promise<void> {
|
||||
try {
|
||||
await CoreLoginHelper.showScanQRInstructions();
|
||||
|
||||
await CoreLoginHelper.scanQR();
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A11y key functionality that prevents keyDown events.
|
||||
*
|
||||
|
|
|
@ -345,36 +345,26 @@ export class CoreLoginSitePage implements OnInit {
|
|||
/**
|
||||
* Process login to a site.
|
||||
*
|
||||
* @param response Response obtained from the site check request.
|
||||
* @param siteCheck Response obtained from the site check request.
|
||||
* @param foundSite The site clicked, if any, from the found sites list.
|
||||
*
|
||||
* @returns Promise resolved after logging in.
|
||||
*/
|
||||
protected async login(response: CoreSiteCheckResponse, foundSite?: CoreLoginSiteInfoExtended): Promise<void> {
|
||||
protected async login(siteCheck: CoreSiteCheckResponse, foundSite?: CoreLoginSiteInfoExtended): Promise<void> {
|
||||
try {
|
||||
await CoreSites.checkApplication(response.config);
|
||||
await CoreSites.checkApplication(siteCheck.config);
|
||||
|
||||
CoreForms.triggerFormSubmittedEvent(this.formElement, true);
|
||||
|
||||
if (CoreLoginHelper.isSSOLoginNeeded(response.code)) {
|
||||
// SSO. User needs to authenticate in a browser.
|
||||
CoreLoginHelper.confirmAndOpenBrowserForSSOLogin(
|
||||
response.siteUrl,
|
||||
response.code,
|
||||
response.service,
|
||||
response.config?.launchurl,
|
||||
);
|
||||
} else {
|
||||
const pageParams = { siteUrl: response.siteUrl, siteConfig: response.config };
|
||||
if (foundSite && !this.fixedSites) {
|
||||
pageParams['siteName'] = foundSite.name;
|
||||
pageParams['logoUrl'] = foundSite.imageurl;
|
||||
}
|
||||
|
||||
CoreNavigator.navigate('/login/credentials', {
|
||||
params: pageParams,
|
||||
});
|
||||
const pageParams = { siteCheck };
|
||||
if (foundSite && !this.fixedSites) {
|
||||
pageParams['siteName'] = foundSite.name;
|
||||
pageParams['logoUrl'] = foundSite.imageurl;
|
||||
}
|
||||
|
||||
CoreNavigator.navigate('/login/credentials', {
|
||||
params: pageParams,
|
||||
});
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
|
@ -573,17 +563,14 @@ export class CoreLoginSitePage implements OnInit {
|
|||
|
||||
try {
|
||||
// Check if site uses SSO.
|
||||
const response = await CoreSites.checkSite(siteUrl);
|
||||
const siteCheck = await CoreSites.checkSite(siteUrl);
|
||||
|
||||
await CoreSites.checkApplication(response.config);
|
||||
await CoreSites.checkApplication(siteCheck.config);
|
||||
|
||||
if (!CoreLoginHelper.isSSOLoginNeeded(response.code)) {
|
||||
if (!CoreLoginHelper.isSSOLoginNeeded(siteCheck.code)) {
|
||||
// No SSO, go to credentials page.
|
||||
await CoreNavigator.navigate('/login/credentials', {
|
||||
params: {
|
||||
siteUrl: response.siteUrl,
|
||||
siteConfig: response.config,
|
||||
},
|
||||
params: { siteCheck },
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
|
|
|
@ -47,14 +47,12 @@ export class CoreLoginSitesPage implements OnInit {
|
|||
async ngOnInit(): Promise<void> {
|
||||
if (CoreNavigator.getRouteBooleanParam('openAddSite')) {
|
||||
this.add();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.accountsList = await CoreLoginHelper.getAccountsList();
|
||||
this.loaded = true;
|
||||
|
||||
if (this.accountsList.count == 0) {
|
||||
if (this.accountsList.count == 0 && !CoreNavigator.getRouteBooleanParam('openAddSite')) {
|
||||
this.add();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,11 +52,6 @@ export const GET_STARTED_URL = 'https://moodle.com';
|
|||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreLoginHelperProvider {
|
||||
|
||||
/**
|
||||
* @deprecated since 3.9.5.
|
||||
*/
|
||||
static readonly OPEN_COURSE = 'open_course';
|
||||
|
||||
static readonly ONBOARDING_DONE = 'onboarding_done';
|
||||
static readonly FAQ_URL_IMAGE_HTML = '<img src="assets/img/login/faq_url.png" role="presentation" alt="">';
|
||||
static readonly FAQ_QRCODE_IMAGE_HTML = '<img src="assets/img/login/faq_qrcode.png" role="presentation" alt="">';
|
||||
|
@ -141,7 +136,7 @@ export class CoreLoginHelperProvider {
|
|||
}
|
||||
|
||||
/**
|
||||
* Show a confirm modal if needed and open a browser to perform SSO login.
|
||||
* Open a browser to perform SSO login.
|
||||
*
|
||||
* @param siteUrl URL of the site where the SSO login will be performed.
|
||||
* @param typeOfLogin CoreConstants.LOGIN_SSO_CODE or CoreConstants.LOGIN_SSO_INAPP_CODE.
|
||||
|
@ -149,6 +144,7 @@ export class CoreLoginHelperProvider {
|
|||
* @param launchUrl The URL to open for SSO. If not defined, default tool mobile launch URL will be used.
|
||||
* @param redirectData Data of the path/url to open once authenticated. If not defined, site initial page.
|
||||
* @returns Promise resolved when done or if user cancelled.
|
||||
* @deprecated since 4.3. Use openBrowserForSSOLogin instead.
|
||||
*/
|
||||
async confirmAndOpenBrowserForSSOLogin(
|
||||
siteUrl: string,
|
||||
|
@ -157,18 +153,6 @@ export class CoreLoginHelperProvider {
|
|||
launchUrl?: string,
|
||||
redirectData?: CoreRedirectPayload,
|
||||
): Promise<void> {
|
||||
// Show confirm only if it's needed. Treat "false" (string) as false to prevent typing errors.
|
||||
const showConfirmation = this.shouldShowSSOConfirm(typeOfLogin);
|
||||
|
||||
if (showConfirmation) {
|
||||
try {
|
||||
await CoreDomUtils.showConfirm(Translate.instant('core.login.logininsiterequired'));
|
||||
} catch {
|
||||
// User canceled, stop.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.openBrowserForSSOLogin(siteUrl, typeOfLogin, service, launchUrl, redirectData);
|
||||
}
|
||||
|
||||
|
@ -478,41 +462,6 @@ export class CoreLoginHelperProvider {
|
|||
return ['/login/site', { showKeyboard }];
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a page that doesn't belong to any site.
|
||||
*
|
||||
* @param page Page to open.
|
||||
* @param params Params of the page.
|
||||
* @returns Promise resolved when done.
|
||||
* @deprecated since 3.9.5. Use CoreNavigator.navigateToLoginCredentials instead.
|
||||
*/
|
||||
async goToNoSitePage(page: string, params?: Params): Promise<void> {
|
||||
await CoreNavigator.navigateToLoginCredentials(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the initial page of a site depending on 'userhomepage' setting.
|
||||
*
|
||||
* @param navCtrlUnused Deprecated param.
|
||||
* @param page Name of the page to load after loading the main page.
|
||||
* @param params Params to pass to the page.
|
||||
* @param options Navigation options.
|
||||
* @param url URL to open once the main menu is loaded.
|
||||
* @returns Promise resolved when done.
|
||||
* @deprecated since 3.9.5. Use CoreNavigator.navigateToSiteHome or CoreNavigator.navigateToSitePath instead.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async goToSiteInitialPage(navCtrlUnused?: unknown, page?: string, params?: any, options?: any, url?: string): Promise<void> {
|
||||
await CoreNavigator.navigateToSiteHome({
|
||||
...options,
|
||||
params: <CoreRedirectPayload> {
|
||||
redirectPath: page,
|
||||
redirectOptions: { params },
|
||||
urlToOpen: url,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient helper to handle authentication in the app using a token received by SSO login. If it's a new account,
|
||||
* the site is stored and the user is authenticated. If the account already exists, update its token.
|
||||
|
@ -666,17 +615,6 @@ export class CoreLoginHelperProvider {
|
|||
return code == CoreConstants.LOGIN_SSO_CODE || code == CoreConstants.LOGIN_SSO_INAPP_CODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a certain page in the main menu page.
|
||||
*
|
||||
* @param page Name of the page to load.
|
||||
* @param params Params to pass to the page.
|
||||
* @deprecated since 3.9.5. Use CoreNavigator.navigateToSitepath instead.
|
||||
*/
|
||||
loadPageInMainMenu(page: string, params?: Params): void {
|
||||
CoreNavigator.navigateToSitePath(page, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a browser to perform OAuth login (Google, Facebook, Microsoft).
|
||||
*
|
||||
|
@ -880,19 +818,6 @@ export class CoreLoginHelperProvider {
|
|||
return loginUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to a new page, setting it as the root page and loading the right site if needed.
|
||||
*
|
||||
* @param page Name of the page to load.
|
||||
* @param params Params to pass to the page.
|
||||
* @param siteId Site to load. If not defined, current site.
|
||||
* @returns Promise resolved when done.
|
||||
* @deprecated since 3.9.5. Use CoreNavigator.navigateToSitePath instead.
|
||||
*/
|
||||
async redirect(page: string, params?: Params, siteId?: string): Promise<void> {
|
||||
await CoreNavigator.navigateToSitePath(page, { params, siteId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a password reset.
|
||||
*
|
||||
|
@ -924,9 +849,8 @@ export class CoreLoginHelperProvider {
|
|||
async sessionExpired(data: CoreEventSessionExpiredData & CoreEventSiteData): Promise<void> {
|
||||
const siteId = data?.siteId;
|
||||
const currentSite = CoreSites.getCurrentSite();
|
||||
const siteUrl = currentSite?.getURL();
|
||||
|
||||
if (!currentSite || !siteUrl) {
|
||||
if (!currentSite) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -947,84 +871,20 @@ export class CoreLoginHelperProvider {
|
|||
|
||||
try {
|
||||
// Check authentication method.
|
||||
const result = await CoreSites.checkSite(siteUrl);
|
||||
|
||||
if (this.isSSOLoginNeeded(result.code)) {
|
||||
// SSO. User needs to authenticate in a browser. Check if we need to display a message.
|
||||
if (!CoreApp.isSSOAuthenticationOngoing() && !this.waitingForBrowser) {
|
||||
try {
|
||||
if (this.shouldShowSSOConfirm(result.code)) {
|
||||
await CoreDomUtils.showConfirm(Translate.instant('core.login.' +
|
||||
(currentSite.isLoggedOut() ? 'loggedoutssodescription' : 'reconnectssodescription')));
|
||||
}
|
||||
|
||||
this.waitForBrowser();
|
||||
|
||||
this.openBrowserForSSOLogin(
|
||||
result.siteUrl,
|
||||
result.code,
|
||||
result.service,
|
||||
result.config?.launchurl,
|
||||
redirectData,
|
||||
);
|
||||
} catch (error) {
|
||||
// User cancelled, logout him.
|
||||
CoreSites.logout();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (currentSite.isOAuth()) {
|
||||
// User authenticated using an OAuth method. Check if it's still valid.
|
||||
const identityProviders = this.getValidIdentityProviders(result.config);
|
||||
const providerToUse = identityProviders.find((provider) => {
|
||||
const params = CoreUrlUtils.extractUrlParams(provider.url);
|
||||
|
||||
return Number(params.id) == currentSite.getOAuthId();
|
||||
});
|
||||
|
||||
if (providerToUse) {
|
||||
if (!CoreApp.isSSOAuthenticationOngoing() && !this.waitingForBrowser) {
|
||||
// Open browser to perform the OAuth.
|
||||
const confirmMessage = Translate.instant('core.login.' +
|
||||
(currentSite.isLoggedOut() ? 'loggedoutssodescription' : 'reconnectssodescription'));
|
||||
|
||||
try {
|
||||
await CoreDomUtils.showConfirm(confirmMessage);
|
||||
|
||||
this.waitForBrowser();
|
||||
CoreSites.unsetCurrentSite(); // Unset current site to make authentication work fine.
|
||||
|
||||
this.openBrowserForOAuthLogin(
|
||||
siteUrl,
|
||||
providerToUse,
|
||||
result.config?.launchurl,
|
||||
redirectData,
|
||||
);
|
||||
} catch (error) {
|
||||
// User cancelled, logout him.
|
||||
CoreSites.logout();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
const info = currentSite.getInfo();
|
||||
if (info !== undefined && info.username !== undefined) {
|
||||
// If current page is already reconnect, stop.
|
||||
if (CoreNavigator.isCurrent('/login/reconnect')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const info = currentSite.getInfo();
|
||||
if (info !== undefined && info.username !== undefined) {
|
||||
// If current page is already reconnect, stop.
|
||||
if (CoreNavigator.isCurrent('/login/reconnect')) {
|
||||
return;
|
||||
}
|
||||
|
||||
await CoreUtils.ignoreErrors(CoreNavigator.navigate('/login/reconnect', {
|
||||
params: {
|
||||
siteId,
|
||||
...redirectData,
|
||||
},
|
||||
reset: true,
|
||||
}));
|
||||
}
|
||||
await CoreUtils.ignoreErrors(CoreNavigator.navigate('/login/reconnect', {
|
||||
params: {
|
||||
siteId,
|
||||
...redirectData,
|
||||
},
|
||||
reset: true,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
// Error checking site.
|
||||
|
@ -1043,10 +903,11 @@ export class CoreLoginHelperProvider {
|
|||
*
|
||||
* @param typeOfLogin CoreConstants.LOGIN_SSO_CODE or CoreConstants.LOGIN_SSO_INAPP_CODE.
|
||||
* @returns True if confirm modal should be shown, false otherwise.
|
||||
* @deprecated since 4.3 Not used anymore.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
shouldShowSSOConfirm(typeOfLogin: number): boolean {
|
||||
return !this.isSSOEmbeddedBrowser(typeOfLogin) &&
|
||||
(!CoreConstants.CONFIG.skipssoconfirmation || String(CoreConstants.CONFIG.skipssoconfirmation) === 'false');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 39 KiB |
|
@ -22,6 +22,7 @@ import { CoreSites } from '@services/sites';
|
|||
import { Http } from '@singletons';
|
||||
import { of } from 'rxjs';
|
||||
import { CoreLoginHelper } from '../services/login-helper';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
|
||||
describe('Credentials page', () => {
|
||||
|
||||
|
@ -54,6 +55,28 @@ describe('Credentials page', () => {
|
|||
maintenancemessage: '',
|
||||
typeoflogin: 1,
|
||||
}),
|
||||
checkSite: async () => ({
|
||||
code: 0,
|
||||
siteUrl,
|
||||
service: CoreConstants.CONFIG.wsservice,
|
||||
config: ({
|
||||
wwwroot: siteUrl,
|
||||
httpswwwroot: siteUrl,
|
||||
sitename: 'Example Campus',
|
||||
guestlogin: 0,
|
||||
rememberusername: 0,
|
||||
authloginviaemail: 0,
|
||||
registerauth: '',
|
||||
forgottenpasswordurl: '',
|
||||
authinstructions: '',
|
||||
authnoneenabled: 0,
|
||||
enablewebservices: 1,
|
||||
enablemobilewebservice: 1,
|
||||
maintenanceenabled: 0,
|
||||
maintenancemessage: '',
|
||||
typeoflogin: 1,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] });
|
||||
|
@ -72,6 +95,30 @@ describe('Credentials page', () => {
|
|||
});
|
||||
|
||||
it('suggests contacting support after multiple failed attempts', async () => {
|
||||
|
||||
const siteCheck = {
|
||||
code: 0,
|
||||
siteUrl,
|
||||
service: CoreConstants.CONFIG.wsservice,
|
||||
config: ({
|
||||
wwwroot: siteUrl,
|
||||
httpswwwroot: siteUrl,
|
||||
sitename: 'Example Campus',
|
||||
guestlogin: 0,
|
||||
rememberusername: 0,
|
||||
authloginviaemail: 0,
|
||||
registerauth: '',
|
||||
forgottenpasswordurl: '',
|
||||
authinstructions: '',
|
||||
authnoneenabled: 0,
|
||||
enablewebservices: 1,
|
||||
enablemobilewebservice: 1,
|
||||
maintenanceenabled: 0,
|
||||
maintenancemessage: '',
|
||||
typeoflogin: 1,
|
||||
supportpage: '',
|
||||
}),
|
||||
};
|
||||
// Arrange.
|
||||
mockSingleton(CoreSites, {
|
||||
getUserToken: () => {
|
||||
|
@ -80,12 +127,13 @@ describe('Credentials page', () => {
|
|||
errorcode: 'invalidlogin',
|
||||
});
|
||||
},
|
||||
checkSite: async () => (siteCheck),
|
||||
});
|
||||
|
||||
mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [] });
|
||||
|
||||
const fixture = await renderPageComponent(CoreLoginCredentialsPage, {
|
||||
routeParams: { siteUrl, siteConfig: { supportpage: '' } },
|
||||
routeParams: { siteUrl, siteCheck },
|
||||
imports: [CoreSharedModule, CoreLoginComponentsModule],
|
||||
});
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
|
@ -235,6 +235,7 @@
|
|||
"openfile": "Open file",
|
||||
"openfullimage": "Click here to display the full size image",
|
||||
"openinbrowser": "Open in browser",
|
||||
"openinbrowserdescription": "You will be taken to a web browser",
|
||||
"openmodinbrowser": "Open {{$a}} in browser",
|
||||
"opensecurityquestion": "Open security question",
|
||||
"opensettings": "Open settings",
|
||||
|
|
|
@ -386,44 +386,31 @@ export class CoreCustomURLSchemesProvider {
|
|||
* Go to page to add a site, or open a browser if SSO.
|
||||
*
|
||||
* @param data URL data.
|
||||
* @param checkResponse Result of checkSite.
|
||||
* @param siteCheck Result of checkSite.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async goToAddSite(data: CoreCustomURLSchemesParams, checkResponse: CoreSiteCheckResponse): Promise<void> {
|
||||
const ssoNeeded = CoreLoginHelper.isSSOLoginNeeded(checkResponse.code);
|
||||
protected async goToAddSite(data: CoreCustomURLSchemesParams, siteCheck: CoreSiteCheckResponse): Promise<void> {
|
||||
const pageParams = {
|
||||
siteUrl: checkResponse.siteUrl,
|
||||
username: data.username,
|
||||
urlToOpen: data.redirect,
|
||||
siteConfig: checkResponse.config,
|
||||
siteCheck,
|
||||
};
|
||||
|
||||
if (CoreSites.isLoggedIn()) {
|
||||
// Ask the user before changing site.
|
||||
await CoreDomUtils.showConfirm(Translate.instant('core.contentlinks.confirmurlothersite'));
|
||||
|
||||
if (!ssoNeeded) {
|
||||
const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
|
||||
redirectPath: '/login/credentials',
|
||||
redirectOptions: { params: pageParams },
|
||||
});
|
||||
const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
|
||||
redirectPath: '/login/credentials',
|
||||
redirectOptions: { params: pageParams },
|
||||
});
|
||||
|
||||
if (willReload) {
|
||||
return;
|
||||
}
|
||||
if (willReload) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ssoNeeded) {
|
||||
CoreLoginHelper.confirmAndOpenBrowserForSSOLogin(
|
||||
checkResponse.siteUrl,
|
||||
checkResponse.code,
|
||||
checkResponse.service,
|
||||
checkResponse.config?.launchurl,
|
||||
);
|
||||
} else {
|
||||
await CoreNavigator.navigateToLoginCredentials(pageParams);
|
||||
}
|
||||
await CoreNavigator.navigateToLoginCredentials(pageParams);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,7 @@ $gray-500: #8f959e !default; // Stroke on inputs
|
|||
$gray-600: #6a737b !default;
|
||||
$gray-700: #495057 !default;
|
||||
$gray-800: #343a40 !default;
|
||||
$gray-900: #1d2125 !default; // Copy text
|
||||
$gray-900: #282828 !default; // Copy text
|
||||
$black: #000000 !default; // Avoid usage
|
||||
|
||||
$blue: #0f6cbf !default;
|
||||
|
@ -22,7 +22,7 @@ $green: #357a32 !default;
|
|||
$red: #ca3120 !default;
|
||||
$yellow: #f0ad4e !default;
|
||||
|
||||
$brand-color: #ff7518 !default;
|
||||
$brand-color: #f98012 !default;
|
||||
|
||||
$text-color: $gray-900 !default;
|
||||
$text-color-rgb: color-to-rgb-list($text-color) !default;
|
||||
|
|
|
@ -173,6 +173,11 @@ ion-header {
|
|||
display: none;
|
||||
}
|
||||
|
||||
// Style fake back button like the original ones.
|
||||
ion-button.ion-back-button {
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
.button.button-clear.button-has-icon-only,
|
||||
.button.button-solid.button-has-icon-only {
|
||||
width: var(--a11y-min-target-size);
|
||||
|
@ -897,10 +902,7 @@ img.large-avatar,
|
|||
height: var(--core-large-avatar-size);
|
||||
max-width: var(--core-large-avatar-size);
|
||||
max-height: var(--core-large-avatar-size);
|
||||
margin-bottom: 10px;
|
||||
border-radius: 50%;
|
||||
padding: 4px;
|
||||
border: 1px solid var(--stroke);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
|
|
|
@ -328,7 +328,7 @@ html {
|
|||
|
||||
--core-star-color: var(--primary);
|
||||
|
||||
--core-large-avatar-size: 90px;
|
||||
--core-large-avatar-size: 80px;
|
||||
--core-avatar-size: var(--a11y-min-target-size);
|
||||
--core-avatar-radius: 50%;
|
||||
|
||||
|
|
|
@ -46,7 +46,6 @@ export interface EnvironmentConfig {
|
|||
multisitesdisplay: CoreLoginSiteSelectorListMethod;
|
||||
sitefindersettings: Partial<CoreLoginSiteFinderSettings>;
|
||||
onlyallowlistedsites: boolean;
|
||||
skipssoconfirmation: boolean;
|
||||
forcedefaultlanguage: boolean;
|
||||
privacypolicy: string;
|
||||
notificoncolor: string;
|
||||
|
|
Loading…
Reference in New Issue