feat(console): set email verified on change, user create (#2847)

* feat: set email verified on change

* user create
This commit is contained in:
Max Peintner 2021-12-15 11:23:53 +01:00 committed by GitHub
parent a533872c66
commit fb43b13232
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 289 additions and 251 deletions

View File

@ -7,17 +7,17 @@
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.MACHINE.USERNAME' | translate }}*</cnsl-label>
<input cnslInput formControlName="userName" required />
<span cnsl-error *ngIf="userName?.invalid && userName?.errors?.required">
<span cnslError *ngIf="userName?.invalid && userName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
<span cnsl-error *ngIf="userName?.invalid && userName?.errors?.noEmailValidator">
<span cnslError *ngIf="userName?.invalid && userName?.errors?.noEmailValidator">
{{ 'USER.VALIDATION.NOEMAIL' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.MACHINE.NAME' | translate }}*</cnsl-label>
<input cnslInput formControlName="name" required />
<span cnsl-error *ngIf="name?.invalid && name?.errors?.required">
<span cnslError *ngIf="name?.invalid && name?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>

View File

@ -52,6 +52,15 @@
</span>
</cnsl-form-field>
<div class="email-is-verified">
<mat-checkbox class="verified-checkbox" formControlName="isVerified">
{{'USER.LOGINMETHODS.EMAIL.ISVERIFIED' | translate}}
</mat-checkbox>
<cnsl-info-section class="full-width desc">
<span>{{'USER.LOGINMETHODS.EMAIL.ISVERIFIEDDESC' | translate}}</span>
</cnsl-info-section>
</div>
<p class="section">{{ 'USER.CREATE.GENDERLANGSECTION' | translate }}</p>
<cnsl-form-field class="formfield">

View File

@ -1,4 +1,3 @@
.form {
width: 100%;
padding-top: 1rem;
@ -7,8 +6,8 @@
button {
margin-top: 3rem;
display: block;
padding: .5rem 4rem;
border-radius: .5rem;
padding: 0.5rem 4rem;
border-radius: 0.5rem;
}
}
}
@ -18,19 +17,25 @@
display: flex;
flex-wrap: wrap;
flex-direction: row;
margin: 0 -.5rem;
margin: 0 -0.5rem;
.section {
padding: .5rem;
padding: 0.5rem;
flex-basis: 100%;
color: var(--grey);
font-size: .9rem;
letter-spacing: .05em;
font-size: 0.9rem;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.formfield {
flex: 1 0 33%;
margin: 0 .5rem;
margin: 0 0.5rem;
}
.email-is-verified {
margin: 0 0.5rem;
flex-basis: 100%;
margin-top: 1.5rem;
}
}

View File

@ -38,7 +38,9 @@ export class UserCreateComponent implements OnDestroy {
) {
this.loading = true;
this.loadOrg();
this.mgmtService.getOrgIAMPolicy().then((resp) => {
this.mgmtService
.getOrgIAMPolicy()
.then((resp) => {
if (resp.policy?.userLoginMustBeDomain) {
this.userLoginMustBeDomain = resp.policy.userLoginMustBeDomain;
}
@ -46,7 +48,8 @@ export class UserCreateComponent implements OnDestroy {
this.loading = false;
this.envSuffixLabel = this.envSuffix();
this.changeDetRef.detectChanges();
}).catch(error => {
})
.catch((error) => {
console.error(error);
this.initForm();
this.loading = false;
@ -56,8 +59,8 @@ export class UserCreateComponent implements OnDestroy {
}
private async loadOrg(): Promise<void> {
const domains = (await this.mgmtService.listOrgDomains());
const found = domains.resultList.find(resp => resp.isPrimary);
const domains = await this.mgmtService.listOrgDomains();
const found = domains.resultList.find((resp) => resp.isPrimary);
if (found) {
this.primaryDomain = found;
}
@ -66,23 +69,17 @@ export class UserCreateComponent implements OnDestroy {
private initForm(): void {
this.userForm = this.fb.group({
email: ['', [Validators.required, Validators.email]],
userName: ['',
[
Validators.required,
Validators.minLength(2),
],
],
userName: ['', [Validators.required, Validators.minLength(2)]],
firstName: ['', Validators.required],
lastName: ['', Validators.required],
nickName: [''],
gender: [],
preferredLanguage: [''],
phone: [''],
isVerified: [false, []],
});
this.userForm.controls['phone'].valueChanges.pipe(
takeUntil(this.destroyed$),
debounceTime(300)).subscribe(value => {
this.userForm.controls['phone'].valueChanges.pipe(takeUntil(this.destroyed$), debounceTime(300)).subscribe((value) => {
const phoneNumber = parsePhoneNumber(value ?? '', 'CH');
if (phoneNumber) {
const formmatted = phoneNumber.formatInternational();
@ -110,7 +107,10 @@ export class UserCreateComponent implements OnDestroy {
humanReq.setUserName(this.userName?.value);
humanReq.setProfile(profileReq);
humanReq.setEmail(new AddHumanUserRequest.Email().setEmail(this.email?.value));
const emailreq = new AddHumanUserRequest.Email();
emailreq.setEmail(this.email?.value);
emailreq.setIsEmailVerified(this.isVerified?.value);
humanReq.setEmail(emailreq);
if (this.phone && this.phone.value) {
humanReq.setPhone(new AddHumanUserRequest.Phone().setPhone(this.phone.value));
@ -123,7 +123,7 @@ export class UserCreateComponent implements OnDestroy {
this.toast.showInfo('USER.TOAST.CREATED', true);
this.router.navigate(['users', data.userId]);
})
.catch(error => {
.catch((error) => {
this.loading = false;
this.toast.showError(error);
});
@ -137,6 +137,9 @@ export class UserCreateComponent implements OnDestroy {
public get email(): AbstractControl | null {
return this.userForm.get('email');
}
public get isVerified(): AbstractControl | null {
return this.userForm.get('isVerified');
}
public get userName(): AbstractControl | null {
return this.userForm.get('userName');
}

View File

@ -11,6 +11,7 @@ import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
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 { UserCreateRoutingModule } from './user-create-routing.module';
@ -31,6 +32,7 @@ import { UserCreateComponent } from './user-create.component';
MatCheckboxModule,
MatTooltipModule,
TranslateModule,
InfoSectionModule,
DetailLayoutModule,
InputModule,
MatRippleModule,

View File

@ -27,11 +27,11 @@
.btn-container {
display: flex;
justify-content: flex-end;
margin: 0 -.5rem;
margin: 0 -0.5rem;
button {
border-radius: .5rem;
margin: 0 .5rem;
border-radius: 0.5rem;
margin: 0 0.5rem;
}
}
@ -39,11 +39,11 @@
display: flex;
flex-wrap: wrap;
align-items: stretch;
margin: -1.5rem -.5rem;
margin: -1.5rem -0.5rem;
.app-card {
flex: 1;
margin: .5rem;
margin: 0.5rem;
}
}
@ -55,6 +55,7 @@
.resendemail {
margin-right: 1rem;
margin-top: 0.5rem;
}
.side-padding {

View File

@ -227,9 +227,9 @@ export class AuthUserDetailComponent implements OnDestroy {
width: '400px',
});
dialogRefPhone.afterClosed().subscribe((resp) => {
if (resp) {
this.savePhone(resp);
dialogRefPhone.afterClosed().subscribe((resp: { value: string; isVerified: boolean }) => {
if (resp && resp.value) {
this.savePhone(resp.value);
}
});
break;
@ -247,9 +247,9 @@ export class AuthUserDetailComponent implements OnDestroy {
width: '400px',
});
dialogRefEmail.afterClosed().subscribe((resp) => {
if (resp) {
this.saveEmail(resp);
dialogRefEmail.afterClosed().subscribe((resp: { value: string; isVerified: boolean }) => {
if (resp && resp.value) {
this.saveEmail(resp.value);
}
});
break;

View File

@ -9,6 +9,16 @@
<input [formControl]="valueControl" cnslInput
(keydown.enter)="valueControl.valid ? closeDialogWithValue() : null" />
</cnsl-form-field>
<ng-container *ngIf="data.type === EditDialogType.EMAIL && data.isVerifiedTextKey">
<mat-checkbox class="verified-checkbox" [(ngModel)]="isVerified">
{{data.isVerifiedTextKey |
translate}}
</mat-checkbox>
<cnsl-info-section class="full-width desc">
<span>{{data.isVerifiedTextDescKey | translate}}</span>
</cnsl-info-section>
</ng-container>
</div>
<div mat-dialog-actions class="action">
<button cdkFocusInitial color="primary" mat-button class="ok-button" (click)="closeDialog()">

View File

@ -2,6 +2,10 @@
width: 100%;
}
.verified-checkbox {
margin-top: 1.5rem;
}
.desc {
font-size: 14px;
color: var(--grey);
@ -12,10 +16,10 @@
justify-content: flex-end;
.ok-button {
margin-left: .5rem;
margin-left: 0.5rem;
}
button {
border-radius: .5rem;
border-radius: 0.5rem;
}
}

View File

@ -15,16 +15,17 @@ export enum EditDialogType {
})
export class EditDialogComponent {
public isPhone: boolean = false;
public isVerified: boolean = false;
public phoneCountry: string = 'CH';
public valueControl: FormControl = new FormControl(['', [Validators.required]]);
constructor(public dialogRef: MatDialogRef<EditDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
public EditDialogType: any = EditDialogType;
constructor(public dialogRef: MatDialogRef<EditDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: any) {
this.valueControl.setValue(data.value);
if (data.type === EditDialogType.PHONE) {
this.isPhone = true;
}
this.valueControl.valueChanges.subscribe(value => {
this.valueControl.valueChanges.subscribe((value) => {
if (value && value.length > 1) {
this.changeValue(value);
}
@ -53,6 +54,6 @@ export class EditDialogComponent {
}
closeDialogWithValue(): void {
this.dialogRef.close(this.valueControl.value);
this.dialogRef.close({ value: this.valueControl.value, isVerified: this.isVerified });
}
}

View File

@ -19,7 +19,7 @@
}
p {
margin: .5rem 0;
margin: 0.5rem 0;
font-size: 14px;
color: var(--grey);
}
@ -30,13 +30,13 @@
}
.actions-trigger {
margin-top: .25rem;
margin-top: 0.25rem;
display: flex;
align-items: center;
.icon {
margin-left: .5rem;
margin-right: -.5rem;
margin-left: 0.5rem;
margin-right: -0.5rem;
}
}
}
@ -66,3 +66,7 @@
.side-padding {
padding-top: 1rem;
}
.resendemail {
margin-top: 0.5rem;
}

View File

@ -229,10 +229,10 @@ export class UserDetailComponent implements OnInit {
});
}
public saveEmail(email: string): void {
public saveEmail(email: string, isVerified: boolean): void {
if (this.user.id && email) {
this.mgmtUserService
.updateHumanEmail(this.user.id, email)
.updateHumanEmail(this.user.id, email, isVerified)
.then(() => {
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
if (this.user.state === UserState.USER_STATE_INITIAL) {
@ -355,9 +355,9 @@ export class UserDetailComponent implements OnInit {
width: '400px',
});
dialogRefPhone.afterClosed().subscribe((resp) => {
if (resp) {
this.savePhone(resp);
dialogRefPhone.afterClosed().subscribe((resp: { value: string; isVerified: boolean }) => {
if (resp && resp.value) {
this.savePhone(resp.value);
}
});
break;
@ -369,15 +369,17 @@ export class UserDetailComponent implements OnInit {
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.EMAIL.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.EMAIL.EDITDESC',
isVerifiedTextKey: 'USER.LOGINMETHODS.EMAIL.ISVERIFIED',
isVerifiedTextDescKey: 'USER.LOGINMETHODS.EMAIL.ISVERIFIEDDESC',
value: this.user.human?.email?.email,
type: EditDialogType.EMAIL,
},
width: '400px',
});
dialogRefEmail.afterClosed().subscribe((resp) => {
if (resp) {
this.saveEmail(resp);
dialogRefEmail.afterClosed().subscribe((resp: { value: string; isVerified: boolean }) => {
if (resp && resp.value) {
this.saveEmail(resp.value, resp.isVerified);
}
});
break;

View File

@ -899,32 +899,24 @@ export class ManagementService {
return this.grpcService.mgmt.listHumanLinkedIDPs(req, null).then((resp) => resp.toObject());
}
public getAction(
id: string,
): Promise<GetActionResponse.AsObject> {
public getAction(id: string): Promise<GetActionResponse.AsObject> {
const req = new GetActionRequest();
req.setId(id);
return this.grpcService.mgmt.getAction(req, null).then(resp => resp.toObject());
return this.grpcService.mgmt.getAction(req, null).then((resp) => resp.toObject());
}
public createAction(
req: CreateActionRequest,
): Promise<CreateActionResponse.AsObject> {
return this.grpcService.mgmt.createAction(req, null).then(resp => resp.toObject());
public createAction(req: CreateActionRequest): Promise<CreateActionResponse.AsObject> {
return this.grpcService.mgmt.createAction(req, null).then((resp) => resp.toObject());
}
public updateAction(
req: UpdateActionRequest,
): Promise<UpdateActionResponse.AsObject> {
return this.grpcService.mgmt.updateAction(req, null).then(resp => resp.toObject());
public updateAction(req: UpdateActionRequest): Promise<UpdateActionResponse.AsObject> {
return this.grpcService.mgmt.updateAction(req, null).then((resp) => resp.toObject());
}
public deleteAction(
id: string,
): Promise<DeleteActionResponse.AsObject> {
public deleteAction(id: string): Promise<DeleteActionResponse.AsObject> {
const req = new DeleteActionRequest();
req.setId(id);
return this.grpcService.mgmt.deleteAction(req, null).then(resp => resp.toObject());
return this.grpcService.mgmt.deleteAction(req, null).then((resp) => resp.toObject());
}
public listActions(
@ -949,23 +941,19 @@ export class ManagementService {
metadata.setAsc(asc);
}
req.setQuery(metadata);
return this.grpcService.mgmt.listActions(req, null).then(resp => resp.toObject());
return this.grpcService.mgmt.listActions(req, null).then((resp) => resp.toObject());
}
public getFlow(
type: FlowType
): Promise<GetFlowResponse.AsObject> {
public getFlow(type: FlowType): Promise<GetFlowResponse.AsObject> {
const req = new GetFlowRequest();
req.setType(type);
return this.grpcService.mgmt.getFlow(req, null).then(resp => resp.toObject());
return this.grpcService.mgmt.getFlow(req, null).then((resp) => resp.toObject());
}
public clearFlow(
type: FlowType
): Promise<ClearFlowResponse.AsObject> {
public clearFlow(type: FlowType): Promise<ClearFlowResponse.AsObject> {
const req = new ClearFlowRequest();
req.setType(type);
return this.grpcService.mgmt.clearFlow(req, null).then(resp => resp.toObject());
return this.grpcService.mgmt.clearFlow(req, null).then((resp) => resp.toObject());
}
public setTriggerActions(
@ -977,7 +965,7 @@ export class ManagementService {
req.setActionIdsList(actionIdsList);
req.setFlowType(type);
req.setTriggerType(triggerType);
return this.grpcService.mgmt.setTriggerActions(req, null).then(resp => resp.toObject());
return this.grpcService.mgmt.setTriggerActions(req, null).then((resp) => resp.toObject());
}
public getIAM(): Promise<GetIAMResponse.AsObject> {
@ -1468,10 +1456,13 @@ export class ManagementService {
return this.grpcService.mgmt.getHumanEmail(req, null).then((resp) => resp.toObject());
}
public updateHumanEmail(userId: string, email: string): Promise<UpdateHumanEmailResponse.AsObject> {
public updateHumanEmail(userId: string, email: string, isVerified?: boolean): Promise<UpdateHumanEmailResponse.AsObject> {
const req = new UpdateHumanEmailRequest();
req.setUserId(userId);
req.setEmail(email);
if (isVerified) {
req.setIsEmailVerified(isVerified);
}
return this.grpcService.mgmt.updateHumanEmail(req, null).then((resp) => resp.toObject());
}

View File

@ -419,6 +419,8 @@
"EMAIL": {
"TITLE": "E-Mail",
"VALID": "Validiert",
"ISVERIFIED": "Email Verifiziert",
"ISVERIFIEDDESC": "Wenn die Email als verifiziert angegeben wird, wird keine Initialisierungsmail versendet.",
"RESEND": "Verifikationsmail erneut senden",
"EDITTITLE": "Email ändern",
"EDITDESC": "Geben Sie die neue Email in dem darunterliegenden Feld ein!"

View File

@ -419,6 +419,8 @@
"EMAIL": {
"TITLE": "E-mail",
"VALID": "validated",
"ISVERIFIED": "Email Verified",
"ISVERIFIEDDESC": "If the email is indicated as verified, no initialization email will be sent.",
"RESEND": "Resend Verification E-mail",
"EDITTITLE": "Change Email",
"EDITDESC": "Enter the new email in the field below."

View File

@ -419,6 +419,8 @@
"EMAIL": {
"TITLE": "E-mail",
"VALID": "convalidato",
"ISVERIFIED": "Email Verificato",
"ISVERIFIEDDESC": "Se l'email viene indicata come verificata, non verrà inviata alcuna email di inizializzazione.",
"RESEND": "Invia di nuovo l'e-mail di verifica",
"EDITTITLE": "Cambiare l'e-mail",
"EDITDESC": "Inserisci la nuova email nel campo sottostante."