diff --git a/console/src/app/modules/password-complexity-view/password-complexity-view.component.html b/console/src/app/modules/password-complexity-view/password-complexity-view.component.html
index 8ad4998ff0..72415228af 100644
--- a/console/src/app/modules/password-complexity-view/password-complexity-view.component.html
+++ b/console/src/app/modules/password-complexity-view/password-complexity-view.component.html
@@ -1,42 +1,42 @@
-
+
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
{{ 'USER.PASSWORD.MINLENGTHERROR' | translate: {value: policy?.minLength} }}
- ({{password?.value?.length}}/{{ policy.minLength}})
-
-
-
-
-
- {{ 'USER.VALIDATION.SYMBOLERROR' | translate }}
-
-
-
-
- {{ 'USER.VALIDATION.NUMBERERROR' | translate }}
-
-
-
-
- {{ 'USER.VALIDATION.UPPERCASEMISSING' | translate }}
-
-
-
-
- {{ 'USER.VALIDATION.LOWERCASEMISSING' | translate }}
-
+
{{ 'USER.PASSWORD.MINLENGTHERROR' | translate: {value: policy?.minLength} }}
+ ({{password?.value?.length}}/{{ policy.minLength}})
+
+
+
+
+
+ {{ 'USER.VALIDATION.SYMBOLERROR' | translate }}
+
+
+
+
+ {{ 'USER.VALIDATION.NUMBERERROR' | translate }}
+
+
+
+
+ {{ 'USER.VALIDATION.UPPERCASEMISSING' | translate }}
+
+
+
+
+ {{ 'USER.VALIDATION.LOWERCASEMISSING' | translate }}
+
\ No newline at end of file
diff --git a/console/src/app/modules/password-complexity-view/password-complexity-view.component.scss b/console/src/app/modules/password-complexity-view/password-complexity-view.component.scss
index 0531f30b51..3c39c7fe78 100644
--- a/console/src/app/modules/password-complexity-view/password-complexity-view.component.scss
+++ b/console/src/app/modules/password-complexity-view/password-complexity-view.component.scss
@@ -1,4 +1,3 @@
-
.validation-col {
display: flex;
flex-wrap: wrap;
@@ -6,7 +5,7 @@
width: 100%;
&.between {
- margin: 0 .5rem;
+ margin: 0 0.5rem;
}
.val {
@@ -20,20 +19,20 @@
color: var(--grey);
}
- .sp-wrapper {
+ .complexity-sp-wrapper {
height: 20px;
width: 20px;
margin-right: 1rem;
i {
- font-size: .9rem;
+ font-size: 0.9rem;
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
- .spinner[color='valid'] {
+ .complexity-spinner[color='valid'] {
color: #56a392;
}
}
diff --git a/console/src/app/pages/users/user-create/user-create.component.html b/console/src/app/pages/users/user-create/user-create.component.html
index 07a4b526f0..c4f433ebdc 100644
--- a/console/src/app/pages/users/user-create/user-create.component.html
+++ b/console/src/app/pages/users/user-create/user-create.component.html
@@ -53,14 +53,46 @@
-
+
{{'USER.LOGINMETHODS.EMAIL.ISVERIFIED' | translate}}
+
+ {{'ORG.PAGES.USEPASSWORD' | translate}}
+
- {{'USER.LOGINMETHODS.EMAIL.ISVERIFIEDDESC' | translate}}
+ {{'USER.CREATE.INITMAILDESCRIPTION' | translate}}
+
+
+
+
+
+
+
{{ 'USER.CREATE.GENDERLANGSECTION' | translate }}
@@ -97,7 +129,8 @@
-
diff --git a/console/src/app/pages/users/user-create/user-create.component.scss b/console/src/app/pages/users/user-create/user-create.component.scss
index bf2b7ab98c..adaa3bc52b 100644
--- a/console/src/app/pages/users/user-create/user-create.component.scss
+++ b/console/src/app/pages/users/user-create/user-create.component.scss
@@ -33,9 +33,29 @@
margin: 0 0.5rem;
}
- .email-is-verified {
+ .email-is-verified,
+ .use-password-block {
margin: 0 0.5rem;
flex-basis: 100%;
margin-top: 1.5rem;
+
+ .block-checkbox {
+ display: block;
+ margin: 0.25rem 0;
+ }
+ }
+}
+
+.pwd-section {
+ margin: 0 0.5rem;
+
+ .section {
+ padding: 0.5rem 0;
+ }
+
+ .user-create-pwd-form {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-gap: 1rem;
}
}
diff --git a/console/src/app/pages/users/user-create/user-create.component.ts b/console/src/app/pages/users/user-create/user-create.component.ts
index da83ca691e..196b1b1276 100644
--- a/console/src/app/pages/users/user-create/user-create.component.ts
+++ b/console/src/app/pages/users/user-create/user-create.component.ts
@@ -1,15 +1,38 @@
import { ChangeDetectorRef, Component, OnDestroy, ViewChild } from '@angular/core';
-import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import parsePhoneNumber from 'libphonenumber-js';
import { Subject } from 'rxjs';
import { debounceTime, takeUntil } from 'rxjs/operators';
import { AddHumanUserRequest } from 'src/app/proto/generated/zitadel/management_pb';
import { Domain } from 'src/app/proto/generated/zitadel/org_pb';
+import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { Gender } from 'src/app/proto/generated/zitadel/user_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
+import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from '../../validators';
+
+function passwordConfirmValidator(c: AbstractControl): any {
+ if (!c.parent || !c) {
+ return;
+ }
+ const pwd = c.parent.get('password');
+ const cpwd = c.parent.get('confirmPassword');
+
+ if (!pwd || !cpwd) {
+ return;
+ }
+ if (pwd.value !== cpwd.value) {
+ return {
+ invalid: true,
+ notequal: {
+ valid: false,
+ },
+ };
+ }
+}
+
@Component({
selector: 'cnsl-user-create',
templateUrl: './user-create.component.html',
@@ -20,6 +43,8 @@ export class UserCreateComponent implements OnDestroy {
public genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
public languages: string[] = ['de', 'en'];
public userForm!: FormGroup;
+ public pwdForm!: FormGroup;
+
public envSuffixLabel: string = '';
private destroyed$: Subject = new Subject();
@@ -28,6 +53,8 @@ export class UserCreateComponent implements OnDestroy {
@ViewChild('suffix') public suffix!: any;
private primaryDomain!: Domain.AsObject;
+ public usePassword: boolean = false;
+ public policy!: PasswordComplexityPolicy.AsObject;
constructor(
private router: Router,
@@ -79,6 +106,37 @@ export class UserCreateComponent implements OnDestroy {
isVerified: [false, []],
});
+ const validators: Validators[] = [Validators.required];
+
+ this.mgmtService.getPasswordComplexityPolicy().then((data) => {
+ if (data.policy) {
+ this.policy = data.policy;
+
+ if (this.policy.minLength) {
+ validators.push(Validators.minLength(this.policy.minLength));
+ }
+ if (this.policy.hasLowercase) {
+ validators.push(lowerCaseValidator);
+ }
+ if (this.policy.hasUppercase) {
+ validators.push(upperCaseValidator);
+ }
+ if (this.policy.hasNumber) {
+ validators.push(numberValidator);
+ }
+ if (this.policy.hasSymbol) {
+ validators.push(symbolValidator);
+ }
+ const pwdValidators = [...validators] as ValidatorFn[];
+ const confirmPwdValidators = [...validators, passwordConfirmValidator] as ValidatorFn[];
+
+ this.pwdForm = this.fb.group({
+ password: ['', pwdValidators],
+ confirmPassword: ['', confirmPwdValidators],
+ });
+ }
+ });
+
this.userForm.controls['phone'].valueChanges.pipe(takeUntil(this.destroyed$), debounceTime(300)).subscribe((value) => {
const phoneNumber = parsePhoneNumber(value ?? '', 'CH');
if (phoneNumber) {
@@ -112,6 +170,10 @@ export class UserCreateComponent implements OnDestroy {
emailreq.setIsEmailVerified(this.isVerified?.value);
humanReq.setEmail(emailreq);
+ if (this.usePassword && this.password?.value) {
+ humanReq.setInitialPassword(this.password.value);
+ }
+
if (this.phone && this.phone.value) {
humanReq.setPhone(new AddHumanUserRequest.Phone().setPhone(this.phone.value));
}
@@ -162,6 +224,12 @@ export class UserCreateComponent implements OnDestroy {
return this.userForm.get('phone');
}
+ public get password(): AbstractControl | null {
+ return this.pwdForm.get('password');
+ }
+ public get confirmPassword(): AbstractControl | null {
+ return this.pwdForm.get('confirmPassword');
+ }
private envSuffix(): string {
if (this.userLoginMustBeDomain && this.primaryDomain?.domainName) {
return `@${this.primaryDomain.domainName}`;
diff --git a/console/src/app/pages/users/user-create/user-create.module.ts b/console/src/app/pages/users/user-create/user-create.module.ts
index afcf51088f..4af4988cdf 100644
--- a/console/src/app/pages/users/user-create/user-create.module.ts
+++ b/console/src/app/pages/users/user-create/user-create.module.ts
@@ -13,6 +13,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
import { InputModule } from 'src/app/modules/input/input.module';
+import { PasswordComplexityViewModule } from 'src/app/modules/password-complexity-view/password-complexity-view.module';
import { UserCreateRoutingModule } from './user-create-routing.module';
import { UserCreateComponent } from './user-create.component';
@@ -29,6 +30,7 @@ import { UserCreateComponent } from './user-create.component';
MatIconModule,
MatProgressSpinnerModule,
MatProgressBarModule,
+ PasswordComplexityViewModule,
MatCheckboxModule,
MatTooltipModule,
TranslateModule,
diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json
index 52b228cb97..14a5282dad 100644
--- a/console/src/assets/i18n/de.json
+++ b/console/src/assets/i18n/de.json
@@ -325,7 +325,8 @@
"GENDERLANGSECTION": "Geschlecht und Sprache",
"PHONESECTION": "Telefonnummer",
"PASSWORDSECTION": "Setze ein initiales Passwort.",
- "ADDRESSANDPHONESECTION": "Telefonnummer"
+ "ADDRESSANDPHONESECTION": "Telefonnummer",
+ "INITMAILDESCRIPTION": "Wenn beide Optionen ausgewählt sind, wird keine E-Mail zur Initialisierung gesendet. Wenn nur eine der Optionen ausgewählt ist, wird eine E-Mail zur Aufforderung der Bereitstellung oder Verifikation der Daten gesendet."
},
"CODEDIALOG": {
"TITLE": "Telefonnummer verifizieren",
@@ -395,12 +396,14 @@
},
"PASSWORD": {
"TITLE": "Passwort",
- "DESCRIPTION": "Gebe das neue Password unter Einhaltung der Richtlinie für die Komplexität ein.",
+ "DESCRIPTION": "Gebe das neue PassworT unter Einhaltung der Richtlinie für die Komplexität ein.",
"OLD": "Aktuelles Passwort",
"NEW": "Neues Passwort",
"CONFIRM": "Neues Passwort wiederholen",
+ "NEWINITIAL": "Passwort",
+ "CONFIRMINITIAL": "PassworT wiederholen",
"RESET": "Passwort zurücksetzen",
- "SET": "Password neu setzen",
+ "SET": "PassworT neu setzen",
"RESENDNOTIFICATION": "Link für das Zurücksetzen des Passworts senden",
"REQUIRED": "Bitte prüfe, dass alle notwendigen Felder ausgefüllt sind.",
"MINLENGTHERROR": "Das Passwort muss mindestens {{value}} Zeichen lang sein.",
@@ -420,7 +423,7 @@
"TITLE": "E-Mail",
"VALID": "Validiert",
"ISVERIFIED": "Email Verifiziert",
- "ISVERIFIEDDESC": "Wenn die Email als verifiziert angegeben wird, wird keine Initialisierungsmail versendet.",
+ "ISVERIFIEDDESC": "Wenn die Email als verifiziert angegeben wird, wird keine Verifikationsmail an den Benutzer versendet.",
"RESEND": "Verifikationsmail erneut senden",
"EDITTITLE": "Email ändern",
"EDITDESC": "Geben Sie die neue Email in dem darunterliegenden Feld ein!"
@@ -665,7 +668,8 @@
"SELECTORGTOOLTIP": "Diese Organisation auswählen",
"PRIMARYDOMAIN": "Primäre Domain",
"STATE": "Status",
- "USEPASSWORD": "Initiales Password setzen"
+ "USEPASSWORD": "Initiales Password setzen",
+ "USEPASSWORDDESC": "Der Nutzer muss das Password bei der Initalisierung nicht setzen."
},
"DOMAINS": {
"NEW": "Domain hinzufügen",
diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json
index 7e35a0f6c0..a5707fac59 100644
--- a/console/src/assets/i18n/en.json
+++ b/console/src/assets/i18n/en.json
@@ -325,7 +325,8 @@
"GENDERLANGSECTION": "Gender and Language",
"PHONESECTION": "Phonenumbers",
"PASSWORDSECTION": "Initial Password",
- "ADDRESSANDPHONESECTION": "Phonenumber"
+ "ADDRESSANDPHONESECTION": "Phonenumber",
+ "INITMAILDESCRIPTION": "If both options are selected, no email for initialization will be sent. If only one of the options is selected, a mail to provide / verify the data will be sent."
},
"CODEDIALOG": {
"TITLE": "Verify Phone Number",
@@ -399,6 +400,8 @@
"OLD": "Current Password",
"NEW": "New Password",
"CONFIRM": "Confirm New Password",
+ "NEWINITIAL": "Password",
+ "CONFIRMINITIAL": "Confirm Password",
"RESET": "Reset Current Password",
"SET": "Set New Password",
"RESENDNOTIFICATION": "Resend Password Reset Link",
@@ -420,7 +423,7 @@
"TITLE": "E-mail",
"VALID": "validated",
"ISVERIFIED": "Email Verified",
- "ISVERIFIEDDESC": "If the email is indicated as verified, no initialization email will be sent.",
+ "ISVERIFIEDDESC": "If the email is indicated as verified, no email verification request will be made.",
"RESEND": "Resend Verification E-mail",
"EDITTITLE": "Change Email",
"EDITDESC": "Enter the new email in the field below."
@@ -665,7 +668,8 @@
"SELECTORGTOOLTIP": "Select this organisation.",
"PRIMARYDOMAIN": "Primary Domain",
"STATE": "State",
- "USEPASSWORD": "Set Initial Password"
+ "USEPASSWORD": "Set Initial Password",
+ "USEPASSWORDDESC": "The user does not have to set the password during initialization."
},
"DOMAINS": {
"NEW": "Add Domain",
diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json
index 5ef36acaed..8babf3a037 100644
--- a/console/src/assets/i18n/it.json
+++ b/console/src/assets/i18n/it.json
@@ -325,7 +325,8 @@
"GENDERLANGSECTION": "Genere e linguaggio",
"PHONESECTION": "Phonenumbers",
"PASSWORDSECTION": "Password iniziale",
- "ADDRESSANDPHONESECTION": "Numero di telefono"
+ "ADDRESSANDPHONESECTION": "Numero di telefono",
+ "INITMAILDESCRIPTION": "Se vengono selezionate entrambe le opzioni, non verrà inviata alcuna e-mail per l'inizializzazione. Se solo una delle opzioni viene selezionata, verrà inviata una mail per fornire/verificare i dati."
},
"CODEDIALOG": {
"TITLE": "Verificare il numero di telefono",
@@ -399,6 +400,8 @@
"OLD": "Password attuale",
"NEW": "Nuova password",
"CONFIRM": "Conferma la nuova password",
+ "NEWINITIAL": "Password",
+ "CONFIRMINITIAL": "Conferma password",
"RESET": "Ripristina la password attuale",
"SET": "Imposta nuova password",
"RESENDNOTIFICATION": "Reinvia il link per la reimpostazione della password",
@@ -420,7 +423,7 @@
"TITLE": "E-mail",
"VALID": "convalidato",
"ISVERIFIED": "Email Verificato",
- "ISVERIFIEDDESC": "Se l'email viene indicata come verificata, non verrà inviata alcuna email di inizializzazione.",
+ "ISVERIFIEDDESC": "Se l'email viene indicata come verificata, non verrà inviata alcuna email di verificazione.",
"RESEND": "Invia di nuovo l'e-mail di verifica",
"EDITTITLE": "Cambiare l'e-mail",
"EDITDESC": "Inserisci la nuova email nel campo sottostante."
@@ -665,7 +668,8 @@
"SELECTORGTOOLTIP": "Seleziona questa organizzazione.",
"PRIMARYDOMAIN": "Dominio primario",
"STATE": "Stato",
- "USEPASSWORD": "Imposta la password iniziale"
+ "USEPASSWORD": "Imposta la password iniziale",
+ "USEPASSWORDDESC": "L'utente non deve impostare la password durante l'inizializzazione."
},
"DOMAINS": {
"NEW": "Aggiungi dominio",