Skip to content

Commit

Permalink
MOBILE-4400 signup: Check extendedusernamechars setting
Browse files Browse the repository at this point in the history
  • Loading branch information
dpalou committed Feb 22, 2024
1 parent 5ef95b6 commit ae79dc9
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 16 deletions.
2 changes: 2 additions & 0 deletions scripts/langindex.json
Original file line number Diff line number Diff line change
Expand Up @@ -2092,6 +2092,7 @@
"core.login.invalidsite": "local_moodlemobileapp",
"core.login.invalidtime": "local_moodlemobileapp",
"core.login.invalidurl": "scorm",
"core.login.invalidusername": "moodle",
"core.login.invalidvaluemax": "local_moodlemobileapp",
"core.login.invalidvaluemin": "local_moodlemobileapp",
"core.login.login": "moodle",
Expand Down Expand Up @@ -2155,6 +2156,7 @@
"core.login.supplyinfo": "moodle",
"core.login.toggleremove": "local_moodlemobileapp",
"core.login.username": "moodle",
"core.login.usernamelowercase": "moodle",
"core.login.usernameoremail": "moodle",
"core.login.usernamerequired": "local_moodlemobileapp",
"core.login.usernotaddederror": "error",
Expand Down
19 changes: 12 additions & 7 deletions src/core/components/input-errors/core-input-errors.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
<ng-container *ngIf="control && control.dirty && !control.valid">
<ng-container *ngFor="let error of errorKeys">
<div *ngIf="control.hasError(error)" class="core-input-error">
<span *ngIf="errorMessages && errorMessages[error]">{{ errorMessages[error] | translate }}</span>
<span *ngIf="(!errorMessages || !errorMessages[error]) && error === 'max' && control.errors?.max">
{{ 'core.login.invalidvaluemax' | translate:{$a: control.errors!.max.max} }}
</span>
<span *ngIf="(!errorMessages || !errorMessages[error]) && error === 'min' && control.errors?.min">
{{ 'core.login.invalidvaluemin' | translate:{$a: control.errors!.min.min} }}
</span>
<ng-container *ngIf="error !== 'pattern'">
<span *ngIf="errorMessages && errorMessages[error]">{{ errorMessages[error] | translate }}</span>
<span *ngIf="(!errorMessages || !errorMessages[error]) && error === 'max' && control.errors?.max">
{{ 'core.login.invalidvaluemax' | translate:{$a: control.errors!.max.max} }}
</span>
<span *ngIf="(!errorMessages || !errorMessages[error]) && error === 'min' && control.errors?.min">
{{ 'core.login.invalidvaluemin' | translate:{$a: control.errors!.min.min} }}
</span>
</ng-container>
<ng-container *ngIf="error === 'pattern' && getPatternErrorMessage() as errorMessage">
<span>{{ errorMessage | translate }}</span>
</ng-container>
</div>
</ng-container>
</ng-container>
Expand Down
42 changes: 41 additions & 1 deletion src/core/components/input-errors/input-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { FormControl } from '@angular/forms';
export class CoreInputErrorsComponent implements OnInit, OnChanges {

@Input() control?: FormControl<unknown>; // Needed to be able to check the validity of the input.
@Input() errorMessages: Record<string, string> = {}; // Error messages to show. Keys must be the name of the error.
@Input() errorMessages: CoreInputErrorsMessages = {}; // Error messages to show. Keys must be the name of the error.
@Input() errorText = ''; // Set other non automatic errors.
errorKeys: string[] = [];

Expand Down Expand Up @@ -124,4 +124,44 @@ export class CoreInputErrorsComponent implements OnInit, OnChanges {
}
}

/**
* Get error message for pattern error.
*
* @returns Error message, undefined if not found.
*/
getPatternErrorMessage(): string | undefined {
const patternError = this.control?.errors?.pattern;
if (!this.errorMessages?.pattern || !patternError) {
return;
}

if (typeof this.errorMessages.pattern === 'string') {
return this.errorMessages.pattern;
}

return this.errorMessages.pattern[patternError.requiredPattern];
}

}

/**
* Error messages for each type of error.
* Error messages will be translated in the template, they don't need to be translated already.
*/
export type CoreInputErrorsMessages = {
required?: string;
requiredTrue?: string;
email?: string;
date?: string;
datetime?: string;
datetimelocal?: string;
time?: string;
url?: string;
max?: string;
min?: string;
maxlength?: string;
minlength?: string;
// For pattern errors you can define an error for all patterns (string), or one error per pattern.
// In the latter case, the key of the object is the pattern and the value is the error message identifier.
pattern?: string | Record<string,string>;
};
2 changes: 2 additions & 0 deletions src/core/features/login/lang.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"invalidsite": "The site URL is not valid.",
"invalidtime": "Time not valid",
"invalidurl": "Invalid URL specified",
"invalidusername": "The username can only contain alphanumeric lowercase characters (letters and numbers), underscore (_), hyphen (-), period (.) or at symbol (@).",
"invalidvaluemax": "The maximum value is {{$a}}",
"invalidvaluemin": "The minimum value is {{$a}}",
"login": "Log in",
Expand Down Expand Up @@ -125,6 +126,7 @@
"supplyinfo": "More details",
"toggleremove": "Edit accounts list",
"username": "Username",
"usernamelowercase": "Only lowercase letters allowed",
"usernameoremail": "Enter either username or email address",
"usernamerequired": "Username required",
"usernotaddederror": "User not added - error",
Expand Down
33 changes: 25 additions & 8 deletions src/core/features/login/pages/email-signup/email-signup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { CorePath } from '@singletons/path';
import { CoreDom } from '@singletons/dom';
import { CoreSitesFactory } from '@services/sites-factory';
import { EMAIL_SIGNUP_FEATURE_NAME } from '@features/login/constants';
import { CoreInputErrorsMessages } from '@components/input-errors/input-errors';

/**
* Page to signup using email.
Expand All @@ -46,6 +47,10 @@ import { EMAIL_SIGNUP_FEATURE_NAME } from '@features/login/constants';
})
export class CoreLoginEmailSignupPage implements OnInit {

// Accept A-Z in strict chars pattern to be able to differentiate it from the lowercase pattern.
protected static readonly USERNAME_STRICT_CHARS_PATTERN = '^[A-Z-.@_a-z0-9]*$';
protected static readonly USERNAME_LOWERCASE_PATTERN = '^[^A-Z]*$';

@ViewChild(CoreRecaptchaComponent) recaptchaComponent?: CoreRecaptchaComponent;
@ViewChild('ageForm') ageFormElement?: ElementRef;
@ViewChild('signupFormEl') signupFormElement?: ElementRef;
Expand Down Expand Up @@ -77,12 +82,12 @@ export class CoreLoginEmailSignupPage implements OnInit {
supportEmail?: string;

// Validation errors.
usernameErrors: Record<string, string>;
passwordErrors: Record<string, string>;
emailErrors: Record<string, string>;
email2Errors: Record<string, string>;
policyErrors: Record<string, string>;
namefieldsErrors?: Record<string, Record<string, string>>;
usernameErrors: CoreInputErrorsMessages;
passwordErrors: CoreInputErrorsMessages;
emailErrors: CoreInputErrorsMessages;
email2Errors: CoreInputErrorsMessages;
policyErrors: CoreInputErrorsMessages;
namefieldsErrors?: Record<string, CoreInputErrorsMessages>;

constructor(
protected fb: FormBuilder,
Expand All @@ -98,14 +103,19 @@ export class CoreLoginEmailSignupPage implements OnInit {

// Create the signupForm with the basic controls. More controls will be added later.
this.signupForm = this.fb.group({
username: ['', Validators.required],
password: ['', Validators.required],
email: ['', Validators.compose([Validators.required, Validators.email])],
email2: ['', Validators.compose([Validators.required, Validators.email])],
});

// Setup validation errors.
this.usernameErrors = { required: 'core.login.usernamerequired' };
this.usernameErrors = {
required: 'core.login.usernamerequired',
pattern: {
[CoreLoginEmailSignupPage.USERNAME_STRICT_CHARS_PATTERN]: 'core.login.invalidusername',
[CoreLoginEmailSignupPage.USERNAME_LOWERCASE_PATTERN]: 'core.login.usernamelowercase',
},
};
this.passwordErrors = { required: 'core.login.passwordrequired' };
this.emailErrors = { required: 'core.login.missingemail' };
this.policyErrors = { required: 'core.login.policyagree' };
Expand Down Expand Up @@ -140,6 +150,13 @@ export class CoreLoginEmailSignupPage implements OnInit {
* Complete the FormGroup using the settings received from server.
*/
protected completeFormGroup(): void {
const checkStrictChars = this.settings?.extendedusernamechars === false;
this.signupForm.addControl('username', this.fb.control('', Validators.compose([
Validators.required,
Validators.pattern(CoreLoginEmailSignupPage.USERNAME_LOWERCASE_PATTERN),
checkStrictChars ? Validators.pattern(CoreLoginEmailSignupPage.USERNAME_STRICT_CHARS_PATTERN) : undefined,
])));

this.signupForm.addControl('city', this.fb.control(this.settings?.defaultcity || ''));
this.signUpCountryControl = this.fb.control(this.settings?.country || '', { nonNullable: true });
this.signupForm.addControl('country', this.signUpCountryControl);
Expand Down
1 change: 1 addition & 0 deletions src/core/features/login/services/login-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1553,6 +1553,7 @@ export type AuthEmailSignupSettings = {
sitepolicyhandler?: string; // Site policy handler.
defaultcity?: string; // Default city.
country?: string; // Default country.
extendedusernamechars?: boolean; // @since 4.4. Extended characters in usernames or no.
profilefields?: AuthEmailSignupProfileField[]; // Required profile fields.
recaptchapublickey?: string; // Recaptcha public key.
recaptchachallengehash?: string; // Recaptcha challenge hash.
Expand Down
41 changes: 41 additions & 0 deletions src/core/features/login/tests/behat/signup.feature
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,44 @@ Feature: Test signup in app
And I should find "1 January 2010, 11:45 AM" in the app
And I should find "This is my description" in the app
And I should find "https://moodle.com" in the app

@lms_from4.4
Scenario: Check extended characters in usernames show error if setting is disabled
Given the following config values are set as admin:
| extendedusernamechars | 0 |
When I launch the app
And I set the field "Your site" to "$WWWROOT" in the app
And I press "Connect to your site" in the app
And I press "Create new account" in the app
And I set the following fields to these values in the app:
| Username | u1$ |
Then I should find "The username can only contain alphanumeric" in the app

When I set the following fields to these values in the app:
| Username | u1 |
Then I should not find "The username can only contain alphanumeric" in the app

@lms_from4.4
Scenario: Check can include extended characters in usernames if setting is enabled
Given the following config values are set as admin:
| extendedusernamechars | 1 |
When I launch the app
And I set the field "Your site" to "$WWWROOT" in the app
And I press "Connect to your site" in the app
And I press "Create new account" in the app
And I set the following fields to these values in the app:
| Username | u1U |
Then I should find "Only lowercase letters allowed" in the app

When I set the following fields to these values in the app:
| Username | u1$ |
Then I should not find "The username can only contain alphanumeric" in the app

When I set the following fields to these values in the app:
| Password | pu1 |
| Email address | u1@u1.com |
| Email (again) | u1@u1.com |
| First name | User |
| Last name | Test |
And I press "Create my new account" in the app
Then I should find "An email should have been sent to your address" in the app

0 comments on commit ae79dc9

Please sign in to comment.