feat(console): set initial password on user create (#2920)

* fix: cnsl verified mail desc

* add initial password on user create

* change text
This commit is contained in:
Max Peintner
2022-01-04 10:56:29 +01:00
committed by GitHub
parent 6d78fe28f5
commit fcf0fcc301
9 changed files with 192 additions and 58 deletions

View File

@@ -53,14 +53,46 @@
</cnsl-form-field>
<div class="email-is-verified">
<mat-checkbox class="verified-checkbox" formControlName="isVerified">
<mat-checkbox class="block-checkbox" formControlName="isVerified">
{{'USER.LOGINMETHODS.EMAIL.ISVERIFIED' | translate}}
</mat-checkbox>
<mat-checkbox class="block-checkbox" [(ngModel)]="usePassword" [ngModelOptions]="{standalone: true}">
{{'ORG.PAGES.USEPASSWORD' | translate}}
</mat-checkbox>
<cnsl-info-section class="full-width desc">
<span>{{'USER.LOGINMETHODS.EMAIL.ISVERIFIEDDESC' | translate}}</span>
<span>{{'USER.CREATE.INITMAILDESCRIPTION' | translate}}</span>
</cnsl-info-section>
</div>
<div class="pwd-section" *ngIf="usePassword && pwdForm">
<cnsl-password-complexity-view class="complexity-view" [policy]="this.policy" [password]="password">
</cnsl-password-complexity-view>
<form [formGroup]="pwdForm" class="user-create-pwd-form">
<cnsl-form-field class="pwd-field" *ngIf="password" appearance="outline">
<cnsl-label>{{ 'USER.PASSWORD.NEWINITIAL' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="firstpassword" formControlName="password" type="password" />
<span cnslError *ngIf="password?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field class="pwd-field" *ngIf="confirmPassword" appearance="outline">
<cnsl-label>{{ 'USER.PASSWORD.CONFIRMINITIAL' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="confirmPassword" formControlName="confirmPassword"
type="password" />
<span cnslError *ngIf="confirmPassword?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
<span cnslError *ngIf="confirmPassword?.errors?.notequal">
{{ 'USER.PASSWORD.NOTEQUAL' | translate }}
</span>
</cnsl-form-field>
</form>
</div>
<p class="section">{{ 'USER.CREATE.GENDERLANGSECTION' | translate }}</p>
<cnsl-form-field class="formfield">
@@ -97,7 +129,8 @@
</cnsl-form-field>
</div>
<div class="btn-container">
<button color="primary" [disabled]="userForm.invalid" type="submit" mat-raised-button>{{ 'ACTIONS.CREATE' |
<button color="primary" [disabled]="userForm.invalid || (this.usePassword && this.pwdForm.invalid)" type="submit"
mat-raised-button>{{ 'ACTIONS.CREATE' |
translate }}</button>
</div>
</form>

View File

@@ -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;
}
}

View File

@@ -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<void> = 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}`;

View File

@@ -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,