From fa61c06040b743affe5bf207868413146ce207b9 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 16 Sep 2025 15:25:22 +0300 Subject: [PATCH 1/5] fixed email validation --- ui-ngx/src/app/core/services/utils.service.ts | 10 ++++++++++ .../pages/admin/security-settings.component.html | 3 +++ .../home/pages/admin/security-settings.component.ts | 4 +++- .../modules/home/pages/profile/profile.component.ts | 4 +++- .../email-auth-dialog.component.ts | 4 +++- .../app/modules/home/pages/user/user.component.ts | 12 +++++++++--- .../app/modules/login/pages/login/login.component.ts | 8 +++++--- .../pages/login/reset-password-request.component.ts | 4 +++- .../src/app/shared/components/contact.component.ts | 11 ++++++++--- 9 files changed, 47 insertions(+), 13 deletions(-) diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 734e503aeb..174f6f941e 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -50,6 +50,7 @@ import { entityTypeTranslations } from '@shared/models/entity-type.models'; import cssjs from '@core/css/css'; import { isNotEmptyTbFunction } from '@shared/models/js-function.models'; import { defaultFormProperties, FormProperty } from '@shared/models/dynamic-form.models'; +import { AbstractControl, ValidationErrors, Validators } from "@angular/forms"; const i18nRegExp = new RegExp(`{${i18nPrefix}:([^{}]+)}`, 'g'); @@ -477,4 +478,13 @@ export class UtilsService { } } + public validateEmail(control: AbstractControl): ValidationErrors | null { + const email = control.value; + const nativeEmailError = Validators.email(control); + if (nativeEmailError !== null) { + return nativeEmailError; + } + const passesDomainCheck = /\.[^.\s]{2,}$/.test(email); + return passesDomainCheck ? null : {email: true}; + } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html index 21cfcba869..54d7b197cc 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html @@ -45,6 +45,9 @@ admin.user-lockout-notification-email + + {{ 'login.invalid-email-format' | translate }} + admin.user-activation-token-ttl diff --git a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts index ac02cf35f8..7f644b7441 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts @@ -38,6 +38,7 @@ import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { Observable, of } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { UtilsService } from "@core/services/utils.service"; @Component({ selector: 'tb-security-settings', @@ -61,6 +62,7 @@ export class SecuritySettingsComponent extends PageComponent implements HasConfi private dialogService: DialogService, private translate: TranslateService, private fb: UntypedFormBuilder, + private utils: UtilsService, private destroyRef: DestroyRef) { super(store); this.buildSecuritySettingsForm(); @@ -76,7 +78,7 @@ export class SecuritySettingsComponent extends PageComponent implements HasConfi buildSecuritySettingsForm() { this.securitySettingsFormGroup = this.fb.group({ maxFailedLoginAttempts: [null, [Validators.min(0)]], - userLockoutNotificationEmail: ['', []], + userLockoutNotificationEmail: ['', [this.utils.validateEmail]], userActivationTokenTtl: [24, [Validators.required, Validators.min(1), Validators.max(24)]], passwordResetTokenTtl: [24, [Validators.required, Validators.min(1), Validators.max(24)]], mobileSecretKeyLength: [null, [Validators.min(1)]], diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts index 7d1aa2aa4e..a097b470ca 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts @@ -33,6 +33,7 @@ import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { AuthService } from '@core/auth/auth.service'; import { UnitSystem, UnitSystems } from '@shared/models/unit.models'; import { UnitService } from '@core/services/unit.service'; +import { UtilsService } from "@core/services/utils.service"; @Component({ selector: 'tb-profile', @@ -54,6 +55,7 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir private authService: AuthService, private translate: TranslateService, private unitService: UnitService, + private utils: UtilsService, private fb: UntypedFormBuilder) { super(store); this.authUser = getCurrentAuthUser(this.store); @@ -66,7 +68,7 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir private buildProfileForm() { this.profile = this.fb.group({ - email: ['', [Validators.required, Validators.email]], + email: ['', [Validators.required, this.utils.validateEmail]], firstName: [''], lastName: [''], phone: [''], diff --git a/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.ts index 601c2e9277..769463c63b 100644 --- a/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.ts @@ -28,6 +28,7 @@ import { TwoFactorAuthProviderType } from '@shared/models/two-factor-auth.models'; import { MatStepper } from '@angular/material/stepper'; +import { UtilsService } from "@core/services/utils.service"; export interface EmailAuthDialogData { email: string; @@ -51,13 +52,14 @@ export class EmailAuthDialogComponent extends DialogComponent, protected router: Router, private twoFaService: TwoFactorAuthenticationService, + private utils: UtilsService, @Inject(MAT_DIALOG_DATA) public data: EmailAuthDialogData, public dialogRef: MatDialogRef, public fb: UntypedFormBuilder) { super(store, router, dialogRef); this.emailConfigForm = this.fb.group({ - email: [this.data.email, [Validators.required, Validators.email]] + email: [this.data.email, [Validators.required, this.utils.validateEmail]] }); this.emailVerificationForm = this.fb.group({ diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.ts b/ui-ngx/src/app/modules/home/pages/user/user.component.ts index da0a001b5d..0c9447e9d3 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, Inject, Optional } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, OnInit, Optional } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; @@ -27,13 +27,14 @@ import { isDefinedAndNotNull } from '@core/utils'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { ActionNotificationShow } from '@app/core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; +import { UtilsService } from "@core/services/utils.service"; @Component({ selector: 'tb-user', templateUrl: './user.component.html', styleUrls: ['./user.component.scss'] }) -export class UserComponent extends EntityComponent { +export class UserComponent extends EntityComponent implements OnInit{ authority = Authority; @@ -46,11 +47,16 @@ export class UserComponent extends EntityComponent { @Optional() @Inject('entity') protected entityValue: User, @Optional() @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, public fb: UntypedFormBuilder, + private utils: UtilsService, protected cd: ChangeDetectorRef, protected translate: TranslateService) { super(store, fb, entityValue, entitiesTableConfigValue, cd); } + ngOnInit(): void { + this.entityForm.controls.email.addValidators(this.utils.validateEmail); + } + hideDelete() { if (this.entitiesTableConfig) { return !this.entitiesTableConfig.deleteEnabled(this.entity); @@ -70,7 +76,7 @@ export class UserComponent extends EntityComponent { buildForm(entity: User): UntypedFormGroup { return this.fb.group( { - email: [entity ? entity.email : '', [Validators.required, Validators.email]], + email: [entity ? entity.email : '', [Validators.required]], firstName: [entity ? entity.firstName : ''], lastName: [entity ? entity.lastName : ''], phone: [entity ? entity.phone : ''], diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts index aa7642b654..89d5c8d8cd 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -19,11 +19,12 @@ import { AuthService } from '@core/auth/auth.service'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; -import { UntypedFormBuilder } from '@angular/forms'; +import { UntypedFormBuilder, Validators } from '@angular/forms'; import { HttpErrorResponse } from '@angular/common/http'; import { Constants } from '@shared/models/constants'; import { Router } from '@angular/router'; import { OAuth2ClientLoginInfo } from '@shared/models/oauth2.models'; +import { UtilsService } from "@core/services/utils.service"; @Component({ selector: 'tb-login', @@ -35,14 +36,15 @@ export class LoginComponent extends PageComponent implements OnInit { passwordViolation = false; loginFormGroup = this.fb.group({ - username: '', - password: '' + username: ['', [Validators.required, this.utils.validateEmail]], + password: [''] }); oauth2Clients: Array = null; constructor(protected store: Store, private authService: AuthService, public fb: UntypedFormBuilder, + private utils: UtilsService, private router: Router) { super(store); } diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts index 98b3d97398..eaa9622eb7 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts @@ -22,6 +22,7 @@ import { PageComponent } from '@shared/components/page.component'; import { UntypedFormBuilder, Validators } from '@angular/forms'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; +import { UtilsService } from "@core/services/utils.service"; @Component({ selector: 'tb-reset-password-request', @@ -33,12 +34,13 @@ export class ResetPasswordRequestComponent extends PageComponent implements OnIn clicked: boolean = false; requestPasswordRequest = this.fb.group({ - email: ['', [Validators.email, Validators.required]] + email: ['', [Validators.required, this.utils.validateEmail]], }, {updateOn: 'submit'}); constructor(protected store: Store, private authService: AuthService, private translate: TranslateService, + private utils: UtilsService, public fb: UntypedFormBuilder) { super(store); } diff --git a/ui-ngx/src/app/shared/components/contact.component.ts b/ui-ngx/src/app/shared/components/contact.component.ts index d52e7d139b..e085a5e99a 100644 --- a/ui-ngx/src/app/shared/components/contact.component.ts +++ b/ui-ngx/src/app/shared/components/contact.component.ts @@ -14,14 +14,15 @@ /// limitations under the License. /// -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; +import { UtilsService } from "@core/services/utils.service"; @Component({ selector: 'tb-contact', templateUrl: './contact.component.html' }) -export class ContactComponent { +export class ContactComponent implements OnInit { @Input() parentForm: UntypedFormGroup; @@ -30,7 +31,11 @@ export class ContactComponent { phoneInputDefaultCountry = 'US'; - constructor() { + constructor(private utils: UtilsService) { + } + + ngOnInit() { + this.parentForm.controls['email'].addValidators(this.utils.validateEmail) } changeCountry(countryCode: string) { From 5a75f999fde761b2aeed16872748f6ba820aedb5 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Mon, 22 Sep 2025 11:43:44 +0300 Subject: [PATCH 2/5] moved validator to utils.ts --- ui-ngx/src/app/core/services/utils.service.ts | 10 ---------- ui-ngx/src/app/core/utils.ts | 12 ++++++++++++ .../components/entity/contact-based.component.ts | 3 ++- .../pages/admin/security-settings.component.ts | 6 ++---- .../home/pages/profile/profile.component.ts | 6 ++---- .../email-auth-dialog.component.ts | 5 ++--- .../app/modules/home/pages/user/user.component.ts | 14 ++++---------- .../modules/login/pages/login/login.component.ts | 5 ++--- .../login/reset-password-request.component.ts | 5 ++--- .../src/app/shared/components/contact.component.ts | 12 ++---------- 10 files changed, 30 insertions(+), 48 deletions(-) diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 174f6f941e..59ce20c4bc 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -477,14 +477,4 @@ export class UtilsService { el.parentNode.removeChild(el); } } - - public validateEmail(control: AbstractControl): ValidationErrors | null { - const email = control.value; - const nativeEmailError = Validators.email(control); - if (nativeEmailError !== null) { - return nativeEmailError; - } - const passesDomainCheck = /\.[^.\s]{2,}$/.test(email); - return passesDomainCheck ? null : {email: true}; - } } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 8ef3dc074e..c824aa26fc 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -33,6 +33,7 @@ import { } from '@shared/models/js-function.models'; import { DomSanitizer } from '@angular/platform-browser'; import { SecurityContext } from '@angular/core'; +import { AbstractControl, ValidationErrors, Validators } from '@angular/forms'; const varsRegex = /\${([^}]*)}/g; @@ -988,3 +989,14 @@ export const trimDefaultValues = (input: Record, defaults: Record { + const email = control.value; + const nativeEmailError = Validators.email(control); + if (nativeEmailError !== null) { + return nativeEmailError; + } + const passesDomainCheck = /\.[^.\s]{2,}$/.test(email); + return passesDomainCheck ? null : {email: true}; +}; + diff --git a/ui-ngx/src/app/modules/home/components/entity/contact-based.component.ts b/ui-ngx/src/app/modules/home/components/entity/contact-based.component.ts index ff052313ff..2db5dd3c97 100644 --- a/ui-ngx/src/app/modules/home/components/entity/contact-based.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/contact-based.component.ts @@ -24,6 +24,7 @@ import { EntityComponent } from './entity.component'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { CountryData } from '@shared/models/country.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { validateEmail } from '@app/core/utils'; @Directive() export abstract class ContactBasedComponent> extends EntityComponent implements AfterViewInit { @@ -50,7 +51,7 @@ export abstract class ContactBasedComponent> exten entityForm.addControl('address', this.fb.control(entity ? entity.address : '', [])); entityForm.addControl('address2', this.fb.control(entity ? entity.address2 : '', [])); entityForm.addControl('phone', this.fb.control(entity ? entity.phone : '', [Validators.maxLength(255)])); - entityForm.addControl('email', this.fb.control(entity ? entity.email : '', [Validators.email])); + entityForm.addControl('email', this.fb.control(entity ? entity.email : '', [validateEmail])); return entityForm; } diff --git a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts index 7f644b7441..c6470ee983 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts @@ -32,13 +32,12 @@ import { JwtSettings, SecuritySettings } from '@shared/models/settings.models'; import { AdminService } from '@core/http/admin.service'; import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; import { mergeMap, tap } from 'rxjs/operators'; -import { randomAlphanumeric } from '@core/utils'; +import { randomAlphanumeric, validateEmail } from '@core/utils'; import { AuthService } from '@core/auth/auth.service'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { Observable, of } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { UtilsService } from "@core/services/utils.service"; @Component({ selector: 'tb-security-settings', @@ -62,7 +61,6 @@ export class SecuritySettingsComponent extends PageComponent implements HasConfi private dialogService: DialogService, private translate: TranslateService, private fb: UntypedFormBuilder, - private utils: UtilsService, private destroyRef: DestroyRef) { super(store); this.buildSecuritySettingsForm(); @@ -78,7 +76,7 @@ export class SecuritySettingsComponent extends PageComponent implements HasConfi buildSecuritySettingsForm() { this.securitySettingsFormGroup = this.fb.group({ maxFailedLoginAttempts: [null, [Validators.min(0)]], - userLockoutNotificationEmail: ['', [this.utils.validateEmail]], + userLockoutNotificationEmail: ['', [validateEmail]], userActivationTokenTtl: [24, [Validators.required, Validators.min(1), Validators.max(24)]], passwordResetTokenTtl: [24, [Validators.required, Validators.min(1), Validators.max(24)]], mobileSecretKeyLength: [null, [Validators.min(1)]], diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts index a097b470ca..3a8b99f166 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts @@ -28,12 +28,11 @@ import { environment as env } from '@env/environment'; import { TranslateService } from '@ngx-translate/core'; import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions'; import { ActivatedRoute } from '@angular/router'; -import { isDefinedAndNotNull, isNotEmptyStr } from '@core/utils'; +import { isDefinedAndNotNull, isNotEmptyStr, validateEmail } from '@core/utils'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { AuthService } from '@core/auth/auth.service'; import { UnitSystem, UnitSystems } from '@shared/models/unit.models'; import { UnitService } from '@core/services/unit.service'; -import { UtilsService } from "@core/services/utils.service"; @Component({ selector: 'tb-profile', @@ -55,7 +54,6 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir private authService: AuthService, private translate: TranslateService, private unitService: UnitService, - private utils: UtilsService, private fb: UntypedFormBuilder) { super(store); this.authUser = getCurrentAuthUser(this.store); @@ -68,7 +66,7 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir private buildProfileForm() { this.profile = this.fb.group({ - email: ['', [Validators.required, this.utils.validateEmail]], + email: ['', [Validators.required, validateEmail]], firstName: [''], lastName: [''], phone: [''], diff --git a/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.ts index 769463c63b..e69ff94bd2 100644 --- a/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.ts @@ -28,7 +28,7 @@ import { TwoFactorAuthProviderType } from '@shared/models/two-factor-auth.models'; import { MatStepper } from '@angular/material/stepper'; -import { UtilsService } from "@core/services/utils.service"; +import { validateEmail } from '@app/core/utils'; export interface EmailAuthDialogData { email: string; @@ -52,14 +52,13 @@ export class EmailAuthDialogComponent extends DialogComponent, protected router: Router, private twoFaService: TwoFactorAuthenticationService, - private utils: UtilsService, @Inject(MAT_DIALOG_DATA) public data: EmailAuthDialogData, public dialogRef: MatDialogRef, public fb: UntypedFormBuilder) { super(store, router, dialogRef); this.emailConfigForm = this.fb.group({ - email: [this.data.email, [Validators.required, this.utils.validateEmail]] + email: [this.data.email, [Validators.required, validateEmail]] }); this.emailVerificationForm = this.fb.group({ diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.ts b/ui-ngx/src/app/modules/home/pages/user/user.component.ts index 0c9447e9d3..18318045ce 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, Inject, OnInit, Optional } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, Optional } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; @@ -23,18 +23,17 @@ import { User } from '@shared/models/user.model'; import { selectAuth } from '@core/auth/auth.selectors'; import { map } from 'rxjs/operators'; import { Authority } from '@shared/models/authority.enum'; -import { isDefinedAndNotNull } from '@core/utils'; +import { isDefinedAndNotNull, validateEmail } from '@core/utils'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { ActionNotificationShow } from '@app/core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; -import { UtilsService } from "@core/services/utils.service"; @Component({ selector: 'tb-user', templateUrl: './user.component.html', styleUrls: ['./user.component.scss'] }) -export class UserComponent extends EntityComponent implements OnInit{ +export class UserComponent extends EntityComponent{ authority = Authority; @@ -47,16 +46,11 @@ export class UserComponent extends EntityComponent implements OnInit{ @Optional() @Inject('entity') protected entityValue: User, @Optional() @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, public fb: UntypedFormBuilder, - private utils: UtilsService, protected cd: ChangeDetectorRef, protected translate: TranslateService) { super(store, fb, entityValue, entitiesTableConfigValue, cd); } - ngOnInit(): void { - this.entityForm.controls.email.addValidators(this.utils.validateEmail); - } - hideDelete() { if (this.entitiesTableConfig) { return !this.entitiesTableConfig.deleteEnabled(this.entity); @@ -76,7 +70,7 @@ export class UserComponent extends EntityComponent implements OnInit{ buildForm(entity: User): UntypedFormGroup { return this.fb.group( { - email: [entity ? entity.email : '', [Validators.required]], + email: [entity ? entity.email : '', [Validators.required,validateEmail]], firstName: [entity ? entity.firstName : ''], lastName: [entity ? entity.lastName : ''], phone: [entity ? entity.phone : ''], diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts index 89d5c8d8cd..e553a22acb 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -24,7 +24,7 @@ import { HttpErrorResponse } from '@angular/common/http'; import { Constants } from '@shared/models/constants'; import { Router } from '@angular/router'; import { OAuth2ClientLoginInfo } from '@shared/models/oauth2.models'; -import { UtilsService } from "@core/services/utils.service"; +import { validateEmail } from '@app/core/utils'; @Component({ selector: 'tb-login', @@ -36,7 +36,7 @@ export class LoginComponent extends PageComponent implements OnInit { passwordViolation = false; loginFormGroup = this.fb.group({ - username: ['', [Validators.required, this.utils.validateEmail]], + username: ['', [Validators.required, validateEmail]], password: [''] }); oauth2Clients: Array = null; @@ -44,7 +44,6 @@ export class LoginComponent extends PageComponent implements OnInit { constructor(protected store: Store, private authService: AuthService, public fb: UntypedFormBuilder, - private utils: UtilsService, private router: Router) { super(store); } diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts index eaa9622eb7..3daeaa693d 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts @@ -22,7 +22,7 @@ import { PageComponent } from '@shared/components/page.component'; import { UntypedFormBuilder, Validators } from '@angular/forms'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; -import { UtilsService } from "@core/services/utils.service"; +import { validateEmail } from '@app/core/utils'; @Component({ selector: 'tb-reset-password-request', @@ -34,13 +34,12 @@ export class ResetPasswordRequestComponent extends PageComponent implements OnIn clicked: boolean = false; requestPasswordRequest = this.fb.group({ - email: ['', [Validators.required, this.utils.validateEmail]], + email: ['', [Validators.required, validateEmail]], }, {updateOn: 'submit'}); constructor(protected store: Store, private authService: AuthService, private translate: TranslateService, - private utils: UtilsService, public fb: UntypedFormBuilder) { super(store); } diff --git a/ui-ngx/src/app/shared/components/contact.component.ts b/ui-ngx/src/app/shared/components/contact.component.ts index e085a5e99a..330a3a100e 100644 --- a/ui-ngx/src/app/shared/components/contact.component.ts +++ b/ui-ngx/src/app/shared/components/contact.component.ts @@ -14,15 +14,14 @@ /// limitations under the License. /// -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { UtilsService } from "@core/services/utils.service"; @Component({ selector: 'tb-contact', templateUrl: './contact.component.html' }) -export class ContactComponent implements OnInit { +export class ContactComponent { @Input() parentForm: UntypedFormGroup; @@ -31,13 +30,6 @@ export class ContactComponent implements OnInit { phoneInputDefaultCountry = 'US'; - constructor(private utils: UtilsService) { - } - - ngOnInit() { - this.parentForm.controls['email'].addValidators(this.utils.validateEmail) - } - changeCountry(countryCode: string) { this.phoneInputDefaultCountry = countryCode ?? 'US'; setTimeout(() => { From 02c30aa00ae57e3f879e17466e7568706fad17c5 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Wed, 24 Sep 2025 11:52:06 +0300 Subject: [PATCH 3/5] add fixes --- ui-ngx/src/app/core/services/utils.service.ts | 2 +- .../authentication-dialog/email-auth-dialog.component.ts | 2 +- ui-ngx/src/app/modules/login/pages/login/login.component.ts | 4 ++-- ui-ngx/src/app/shared/components/contact.component.ts | 3 +++ 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 59ce20c4bc..734e503aeb 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -50,7 +50,6 @@ import { entityTypeTranslations } from '@shared/models/entity-type.models'; import cssjs from '@core/css/css'; import { isNotEmptyTbFunction } from '@shared/models/js-function.models'; import { defaultFormProperties, FormProperty } from '@shared/models/dynamic-form.models'; -import { AbstractControl, ValidationErrors, Validators } from "@angular/forms"; const i18nRegExp = new RegExp(`{${i18nPrefix}:([^{}]+)}`, 'g'); @@ -477,4 +476,5 @@ export class UtilsService { el.parentNode.removeChild(el); } } + } diff --git a/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.ts index e69ff94bd2..6ed9099522 100644 --- a/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.ts @@ -58,7 +58,7 @@ export class EmailAuthDialogComponent extends DialogComponent = null; diff --git a/ui-ngx/src/app/shared/components/contact.component.ts b/ui-ngx/src/app/shared/components/contact.component.ts index 330a3a100e..d52e7d139b 100644 --- a/ui-ngx/src/app/shared/components/contact.component.ts +++ b/ui-ngx/src/app/shared/components/contact.component.ts @@ -30,6 +30,9 @@ export class ContactComponent { phoneInputDefaultCountry = 'US'; + constructor() { + } + changeCountry(countryCode: string) { this.phoneInputDefaultCountry = countryCode ?? 'US'; setTimeout(() => { From 4af8f66982d93f0206f7b969ac9211b1cefd7ac0 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 30 Sep 2025 14:48:13 +0300 Subject: [PATCH 4/5] moved to email regex validation --- ui-ngx/src/app/core/utils.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index c824aa26fc..fe974f54a8 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -36,6 +36,7 @@ import { SecurityContext } from '@angular/core'; import { AbstractControl, ValidationErrors, Validators } from '@angular/forms'; const varsRegex = /\${([^}]*)}/g; +const emailRegex = /^[A-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; export function onParentScrollOrWindowResize(el: Node): Observable { const scrollSubject = new Subject(); @@ -991,12 +992,6 @@ export const trimDefaultValues = (input: Record, defaults: Record { - const email = control.value; - const nativeEmailError = Validators.email(control); - if (nativeEmailError !== null) { - return nativeEmailError; - } - const passesDomainCheck = /\.[^.\s]{2,}$/.test(email); - return passesDomainCheck ? null : {email: true}; + return emailRegex.test(control.value) ? null : {email: true}; }; From 24352443891b22006818c3da75cdd8a167f4f130 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 2 Oct 2025 11:20:27 +0300 Subject: [PATCH 5/5] removed attribute validators --- .../src/app/modules/home/pages/profile/profile.component.html | 2 +- .../authentication-dialog/email-auth-dialog.component.html | 2 +- ui-ngx/src/app/modules/home/pages/user/user.component.html | 2 +- ui-ngx/src/app/modules/home/pages/user/user.component.ts | 2 +- ui-ngx/src/app/modules/login/pages/login/login.component.html | 2 +- .../login/pages/login/reset-password-request.component.html | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html index 44eea5865e..f27bccdf4e 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html @@ -37,7 +37,7 @@
user.email - + {{ 'user.email-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.html b/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.html index 0175c85f56..6eba3f8cf7 100644 --- a/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/security/authentication-dialog/email-auth-dialog.component.html @@ -39,7 +39,7 @@
{{ 'user.email-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.html b/ui-ngx/src/app/modules/home/pages/user/user.component.html index 658b361255..a50f466027 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.html @@ -76,7 +76,7 @@
user.email - + {{ 'user.invalid-email-format' | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.ts b/ui-ngx/src/app/modules/home/pages/user/user.component.ts index 18318045ce..8142f4823a 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.ts @@ -70,7 +70,7 @@ export class UserComponent extends EntityComponent{ buildForm(entity: User): UntypedFormGroup { return this.fb.group( { - email: [entity ? entity.email : '', [Validators.required,validateEmail]], + email: [entity ? entity.email : '', [Validators.required, validateEmail]], firstName: [entity ? entity.firstName : ''], lastName: [entity ? entity.lastName : ''], phone: [entity ? entity.phone : ''], diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.html b/ui-ngx/src/app/modules/login/pages/login/login.component.html index 65b82023fe..c1a4b17a3c 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.html @@ -43,7 +43,7 @@
login.username - + email {{ 'login.invalid-email-format' | translate }} diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html index 96b85f2e47..abf2e6c61c 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html @@ -33,7 +33,7 @@ login.email - + email {{ 'user.invalid-email-format' | translate }}