fix(console): migrate from tslint to eslint, project delete from table (#2490)

* es lint

* modify tsconfig, auto lint some stuff

* lint

* lint

* lint

* lint

* html ts lint

* lint

* lint, tsconfig

* fix project delete, state table

* eslint config, remove cnslHint directive

* mfa selector, info row fixes

* lint

* fix login policy, granted orgs table state, lint

Co-authored-by: Florian Forster <[email protected]>
This commit is contained in:
Max Peintner
2021-10-22 10:47:06 +02:00
committed by GitHub
co-authored by Florian Forster
parent bdf63800f7
commit b1caef81da
233 changed files with 7098 additions and 4878 deletions
@@ -1,4 +1,4 @@
<app-detail-layout [backRouterLink]="[ '/users/list/machines']" title="{{ 'USER.CREATE.TITLE' | translate }}"
<cnsl-detail-layout [backRouterLink]="[ '/users/list/machines']" title="{{ 'USER.CREATE.TITLE' | translate }}"
description="{{ 'USER.CREATE.DESCRIPTION' | translate }}">
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
@@ -31,4 +31,4 @@
translate }}</button>
</div>
</form>
</app-detail-layout>
</cnsl-detail-layout>
@@ -7,76 +7,76 @@ import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-user-create-machine',
templateUrl: './user-create-machine.component.html',
styleUrls: ['./user-create-machine.component.scss'],
selector: 'cnsl-user-create-machine',
templateUrl: './user-create-machine.component.html',
styleUrls: ['./user-create-machine.component.scss'],
})
export class UserCreateMachineComponent implements OnDestroy {
public user: AddMachineUserRequest.AsObject = new AddMachineUserRequest().toObject();
public userForm!: FormGroup;
public user: AddMachineUserRequest.AsObject = new AddMachineUserRequest().toObject();
public userForm!: FormGroup;
private sub: Subscription = new Subscription();
public loading: boolean = false;
private sub: Subscription = new Subscription();
public loading: boolean = false;
constructor(
private router: Router,
private toast: ToastService,
public userService: ManagementService,
private fb: FormBuilder,
) {
this.initForm();
}
constructor(
private router: Router,
private toast: ToastService,
public userService: ManagementService,
private fb: FormBuilder,
) {
this.initForm();
}
private initForm(): void {
this.userForm = this.fb.group({
userName: ['',
[
Validators.required,
Validators.minLength(2),
],
],
name: ['', [Validators.required]],
description: ['', []],
});
}
private initForm(): void {
this.userForm = this.fb.group({
userName: ['',
[
Validators.required,
Validators.minLength(2),
],
],
name: ['', [Validators.required]],
description: ['', []],
});
}
public createUser(): void {
this.user = this.userForm.value;
public createUser(): void {
this.user = this.userForm.value;
this.loading = true;
this.loading = true;
const machineReq = new AddMachineUserRequest();
machineReq.setDescription(this.description?.value);
machineReq.setName(this.name?.value);
machineReq.setUserName(this.userName?.value);
const machineReq = new AddMachineUserRequest();
machineReq.setDescription(this.description?.value);
machineReq.setName(this.name?.value);
machineReq.setUserName(this.userName?.value);
this.userService
.addMachineUser(machineReq)
.then((data) => {
this.loading = false;
this.toast.showInfo('USER.TOAST.CREATED', true);
const id = data.userId;
if (id) {
this.router.navigate(['users', id]);
}
})
.catch((error: any) => {
this.loading = false;
this.toast.showError(error);
});
}
this.userService
.addMachineUser(machineReq)
.then((data) => {
this.loading = false;
this.toast.showInfo('USER.TOAST.CREATED', true);
const id = data.userId;
if (id) {
this.router.navigate(['users', id]);
}
})
.catch((error: any) => {
this.loading = false;
this.toast.showError(error);
});
}
ngOnDestroy(): void {
this.sub.unsubscribe();
}
ngOnDestroy(): void {
this.sub.unsubscribe();
}
public get name(): AbstractControl | null {
return this.userForm.get('name');
}
public get description(): AbstractControl | null {
return this.userForm.get('description');
}
public get userName(): AbstractControl | null {
return this.userForm.get('userName');
}
public get name(): AbstractControl | null {
return this.userForm.get('name');
}
public get description(): AbstractControl | null {
return this.userForm.get('description');
}
public get userName(): AbstractControl | null {
return this.userForm.get('userName');
}
}
@@ -1,4 +1,4 @@
<app-detail-layout [backRouterLink]="[ '/users/list/humans']" title="{{ 'USER.CREATE.TITLE' | translate }}"
<cnsl-detail-layout [backRouterLink]="[ '/users/list/humans']" title="{{ 'USER.CREATE.TITLE' | translate }}"
description="{{ 'USER.CREATE.DESCRIPTION' | translate }}">
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
@@ -8,10 +8,10 @@
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.EMAIL' | translate }}*</cnsl-label>
<input cnslInput matRipple formControlName="email" required />
<span cnsl-error *ngIf="email?.invalid && !email?.errors?.required">
<span cnslError *ngIf="email?.invalid && !email?.errors?.required">
{{ 'USER.VALIDATION.NOTANEMAIL' | translate }}
</span>
<span cnsl-error *ngIf="email?.invalid && email?.errors?.required">
<span cnslError *ngIf="email?.invalid && email?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
@@ -21,10 +21,10 @@
[ngStyle]="{'padding-right': suffixPadding ? suffixPadding : '10px'}" />
<span #suffix *ngIf="envSuffixLabel" cnslSuffix>{{envSuffixLabel}}</span>
<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>
@@ -33,21 +33,21 @@
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}*</cnsl-label>
<input cnslInput formControlName="firstName" required />
<span cnsl-error *ngIf="firstName?.invalid && firstName?.errors?.required">
<span cnslError *ngIf="firstName?.invalid && firstName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.LASTNAME' | translate }}*</cnsl-label>
<input cnslInput formControlName="lastName" required />
<span cnsl-error *ngIf="lastName?.invalid && lastName?.errors?.required">
<span cnslError *ngIf="lastName?.invalid && lastName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.NICKNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="nickName" />
<span cnsl-error *ngIf="nickName?.invalid && nickName?.errors?.required">
<span cnslError *ngIf="nickName?.invalid && nickName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
@@ -61,7 +61,7 @@
{{ 'GENDERS.'+gender | translate }}
</mat-option>
</mat-select>
<span cnsl-error *ngIf="gender?.invalid && gender?.errors?.required">
<span cnslError *ngIf="gender?.invalid && gender?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
@@ -71,7 +71,7 @@
<mat-option *ngFor="let language of languages" [value]="language">
{{ 'LANGUAGES.'+language | translate }}
</mat-option>
<span cnsl-error *ngIf="preferredLanguage?.invalid && preferredLanguage?.errors?.required">
<span cnslError *ngIf="preferredLanguage?.invalid && preferredLanguage?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</mat-select>
@@ -82,7 +82,7 @@
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.PHONE' | translate }}</cnsl-label>
<input cnslInput formControlName="phone" />
<span cnsl-error *ngIf="phone?.invalid && phone?.errors?.required">
<span cnslError *ngIf="phone?.invalid && phone?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
@@ -92,4 +92,4 @@
translate }}</button>
</div>
</form>
</app-detail-layout>
</cnsl-detail-layout>
@@ -11,165 +11,167 @@ import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-user-create',
templateUrl: './user-create.component.html',
styleUrls: ['./user-create.component.scss'],
selector: 'cnsl-user-create',
templateUrl: './user-create.component.html',
styleUrls: ['./user-create.component.scss'],
})
export class UserCreateComponent implements OnDestroy {
public user: AddHumanUserRequest.AsObject = new AddHumanUserRequest().toObject();
public genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
public languages: string[] = ['de', 'en'];
public userForm!: FormGroup;
public envSuffixLabel: string = '';
private destroyed$: Subject<void> = new Subject();
public user: AddHumanUserRequest.AsObject = new AddHumanUserRequest().toObject();
public genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
public languages: string[] = ['de', 'en'];
public userForm!: FormGroup;
public envSuffixLabel: string = '';
private destroyed$: Subject<void> = new Subject();
public userLoginMustBeDomain: boolean = false;
public loading: boolean = false;
public userLoginMustBeDomain: boolean = false;
public loading: boolean = false;
@ViewChild('suffix') public suffix!: any;
private primaryDomain!: Domain.AsObject;
@ViewChild('suffix') public suffix!: any;
private primaryDomain!: Domain.AsObject;
constructor(
private router: Router,
private toast: ToastService,
private fb: FormBuilder,
private mgmtService: ManagementService,
private changeDetRef: ChangeDetectorRef,
) {
this.loading = true;
this.loadOrg();
this.mgmtService.getOrgIAMPolicy().then((resp) => {
if (resp.policy?.userLoginMustBeDomain) {
this.userLoginMustBeDomain = resp.policy.userLoginMustBeDomain;
}
this.initForm();
this.loading = false;
this.envSuffixLabel = this.envSuffix();
this.changeDetRef.detectChanges();
}).catch(error => {
console.error(error);
this.initForm();
this.loading = false;
this.envSuffixLabel = this.envSuffix();
this.changeDetRef.detectChanges();
});
constructor(
private router: Router,
private toast: ToastService,
private fb: FormBuilder,
private mgmtService: ManagementService,
private changeDetRef: ChangeDetectorRef,
) {
this.loading = true;
this.loadOrg();
this.mgmtService.getOrgIAMPolicy().then((resp) => {
if (resp.policy?.userLoginMustBeDomain) {
this.userLoginMustBeDomain = resp.policy.userLoginMustBeDomain;
}
this.initForm();
this.loading = false;
this.envSuffixLabel = this.envSuffix();
this.changeDetRef.detectChanges();
}).catch(error => {
console.error(error);
this.initForm();
this.loading = false;
this.envSuffixLabel = this.envSuffix();
this.changeDetRef.detectChanges();
});
}
private async loadOrg(): Promise<void> {
const domains = (await this.mgmtService.listOrgDomains());
const found = domains.resultList.find(resp => resp.isPrimary);
if (found) {
this.primaryDomain = found;
}
}
private async loadOrg(): Promise<void> {
const domains = (await this.mgmtService.listOrgDomains());
const found = domains.resultList.find(resp => resp.isPrimary);
if (found) {
this.primaryDomain = found;
private initForm(): void {
this.userForm = this.fb.group({
email: ['', [Validators.required, Validators.email]],
userName: ['',
[
Validators.required,
Validators.minLength(2),
],
],
firstName: ['', Validators.required],
lastName: ['', Validators.required],
nickName: [''],
gender: [],
preferredLanguage: [''],
phone: [''],
});
this.userForm.controls['phone'].valueChanges.pipe(
takeUntil(this.destroyed$),
debounceTime(300)).subscribe(value => {
const phoneNumber = parsePhoneNumber(value ?? '', 'CH');
if (phoneNumber) {
const formmatted = phoneNumber.formatInternational();
const country = phoneNumber.country;
if (this.phone && country && this.phone.value && this.phone.value !== formmatted) {
this.phone.setValue(formmatted);
}
}
});
}
public createUser(): void {
this.user = this.userForm.value;
this.loading = true;
const profileReq = new AddHumanUserRequest.Profile();
profileReq.setFirstName(this.firstName?.value);
profileReq.setLastName(this.lastName?.value);
profileReq.setNickName(this.nickName?.value);
profileReq.setPreferredLanguage(this.preferredLanguage?.value);
profileReq.setGender(this.gender?.value);
const humanReq = new AddHumanUserRequest();
humanReq.setUserName(this.userName?.value);
humanReq.setProfile(profileReq);
humanReq.setEmail(new AddHumanUserRequest.Email().setEmail(this.email?.value));
if (this.phone && this.phone.value) {
humanReq.setPhone(new AddHumanUserRequest.Phone().setPhone(this.phone.value));
}
private initForm(): void {
this.userForm = this.fb.group({
email: ['', [Validators.required, Validators.email]],
userName: ['',
[
Validators.required,
Validators.minLength(2),
],
],
firstName: ['', Validators.required],
lastName: ['', Validators.required],
nickName: [''],
gender: [],
preferredLanguage: [''],
phone: [''],
});
this.mgmtService
.addHumanUser(humanReq)
.then((data) => {
this.loading = false;
this.toast.showInfo('USER.TOAST.CREATED', true);
this.router.navigate(['users', data.userId]);
})
.catch(error => {
this.loading = false;
this.toast.showError(error);
});
}
this.userForm.controls['phone'].valueChanges.pipe(
takeUntil(this.destroyed$),
debounceTime(300)).subscribe(value => {
const phoneNumber = parsePhoneNumber(value ?? '', 'CH');
if (phoneNumber) {
const formmatted = phoneNumber.formatInternational();
const country = phoneNumber.country;
if (this.phone && country && this.phone.value && this.phone.value !== formmatted) {
this.phone.setValue(formmatted);
}
}
});
}
ngOnDestroy(): void {
this.destroyed$.next();
this.destroyed$.complete();
}
public createUser(): void {
this.user = this.userForm.value;
public get email(): AbstractControl | null {
return this.userForm.get('email');
}
public get userName(): AbstractControl | null {
return this.userForm.get('userName');
}
public get firstName(): AbstractControl | null {
return this.userForm.get('firstName');
}
public get lastName(): AbstractControl | null {
return this.userForm.get('lastName');
}
public get nickName(): AbstractControl | null {
return this.userForm.get('nickName');
}
public get gender(): AbstractControl | null {
return this.userForm.get('gender');
}
public get preferredLanguage(): AbstractControl | null {
return this.userForm.get('preferredLanguage');
}
public get phone(): AbstractControl | null {
return this.userForm.get('phone');
}
this.loading = true;
private envSuffix(): string {
if (this.userLoginMustBeDomain && this.primaryDomain?.domainName) {
return `@${this.primaryDomain.domainName}`;
} else {
return '';
}
}
const profileReq = new AddHumanUserRequest.Profile();
profileReq.setFirstName(this.firstName?.value);
profileReq.setLastName(this.lastName?.value);
profileReq.setNickName(this.nickName?.value);
profileReq.setPreferredLanguage(this.preferredLanguage?.value);
profileReq.setGender(this.gender?.value);
const humanReq = new AddHumanUserRequest();
humanReq.setUserName(this.userName?.value);
humanReq.setProfile(profileReq);
humanReq.setEmail(new AddHumanUserRequest.Email().setEmail(this.email?.value));
if (this.phone && this.phone.value) {
humanReq.setPhone(new AddHumanUserRequest.Phone().setPhone(this.phone.value));
}
this.mgmtService
.addHumanUser(humanReq)
.then((data) => {
this.loading = false;
this.toast.showInfo('USER.TOAST.CREATED', true);
this.router.navigate(['users', data.userId]);
})
.catch(error => {
this.loading = false;
this.toast.showError(error);
});
}
ngOnDestroy(): void {
this.destroyed$.next();
this.destroyed$.complete();
}
public get email(): AbstractControl | null {
return this.userForm.get('email');
}
public get userName(): AbstractControl | null {
return this.userForm.get('userName');
}
public get firstName(): AbstractControl | null {
return this.userForm.get('firstName');
}
public get lastName(): AbstractControl | null {
return this.userForm.get('lastName');
}
public get nickName(): AbstractControl | null {
return this.userForm.get('nickName');
}
public get gender(): AbstractControl | null {
return this.userForm.get('gender');
}
public get preferredLanguage(): AbstractControl | null {
return this.userForm.get('preferredLanguage');
}
public get phone(): AbstractControl | null {
return this.userForm.get('phone');
}
private envSuffix(): string {
if (this.userLoginMustBeDomain && this.primaryDomain?.domainName) {
return `@${this.primaryDomain.domainName}`;
} else {
return '';
}
}
public get suffixPadding(): string | undefined {
if (this.suffix?.nativeElement.offsetWidth) {
return `${(this.suffix.nativeElement as HTMLElement).offsetWidth + 10}px`;
}
public get suffixPadding(): string | undefined {
if (this.suffix?.nativeElement.offsetWidth) {
return `${(this.suffix.nativeElement as HTMLElement).offsetWidth + 10}px`;
} else {
return;
}
}
}
@@ -2,7 +2,7 @@
<span class="title">{{'USER.MFA.DIALOG.ADD_MFA_TITLE' | translate}} {{data?.number}}</span>
</h1>
<div mat-dialog-content>
<ng-container *ngIf="selectedType == undefined">
<ng-container *ngIf="selectedType === undefined">
<p class="desc">{{'USER.MFA.DIALOG.ADD_MFA_DESCRIPTION' | translate}}</p>
<div class="type-selection">
@@ -25,7 +25,7 @@
</div>
</ng-container>
<div class="otp" *ngIf="selectedType == AuthFactorType.OTP">
<div class="otp" *ngIf="selectedType === AuthFactorType.OTP">
<p class="desc">{{'USER.MFA.OTP_DIALOG_DESCRIPTION' | translate}}</p>
<div class="qrcode-wrapper">
@@ -38,7 +38,7 @@
</cnsl-form-field>
</div>
<div class="u2f" *ngIf="selectedType == AuthFactorType.U2F">
<div class="u2f" *ngIf="selectedType === AuthFactorType.U2F">
<p>{{'USER.MFA.U2F_DIALOG_DESCRIPTION' | translate}}</p>
<cnsl-form-field class="form-field" label="Name" required="true">
@@ -9,133 +9,133 @@ import { _base64ToArrayBuffer } from '../../u2f-util';
import { _arrayBufferToBase64 } from '../u2f_util';
export enum AuthFactorType {
OTP,
U2F,
OTP,
U2F,
}
@Component({
selector: 'app-auth-factor-dialog',
templateUrl: './auth-factor-dialog.component.html',
styleUrls: ['./auth-factor-dialog.component.scss'],
selector: 'cnsl-auth-factor-dialog',
templateUrl: './auth-factor-dialog.component.html',
styleUrls: ['./auth-factor-dialog.component.scss'],
})
export class AuthFactorDialogComponent {
public otpurl: string = '';
public otpcode: string = '';
public otpurl: string = '';
public otpcode: string = '';
public u2fname: string = '';
public u2fCredentialOptions!: CredentialCreationOptions;
public u2fLoading: boolean = false;
public u2fError: string = '';
public u2fname: string = '';
public u2fCredentialOptions!: CredentialCreationOptions;
public u2fLoading: boolean = false;
public u2fError: string = '';
AuthFactorType: any = AuthFactorType;
selectedType!: AuthFactorType;
constructor(
private authService: GrpcAuthService,
private toast: ToastService,
private translate: TranslateService,
public dialogRef: MatDialogRef<AuthFactorDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) { }
AuthFactorType: any = AuthFactorType;
selectedType!: AuthFactorType;
constructor(
private authService: GrpcAuthService,
private toast: ToastService,
private translate: TranslateService,
public dialogRef: MatDialogRef<AuthFactorDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) { }
closeDialog(code: string = ''): void {
this.dialogRef.close(code);
}
closeDialog(code: string = ''): void {
this.dialogRef.close(code);
}
public selectType(type: AuthFactorType): void {
this.selectedType = type;
public selectType(type: AuthFactorType): void {
this.selectedType = type;
if (type === AuthFactorType.OTP) {
this.authService.addMyMultiFactorOTP().then((otpresp) => {
this.otpurl = otpresp.url;
}, error => {
this.toast.showError(error);
});
} else if (type === AuthFactorType.U2F) {
this.authService.addMyMultiFactorU2F().then((u2fresp) => {
if (u2fresp.key) {
const credOptions: CredentialCreationOptions = JSON.parse(atob(u2fresp.key?.publicKey as string));
if (type === AuthFactorType.OTP) {
this.authService.addMyMultiFactorOTP().then((otpresp) => {
this.otpurl = otpresp.url;
}, error => {
this.toast.showError(error);
});
} else if (type === AuthFactorType.U2F) {
this.authService.addMyMultiFactorU2F().then((u2fresp) => {
if (u2fresp.key) {
const credOptions: CredentialCreationOptions = JSON.parse(atob(u2fresp.key?.publicKey as string));
if (credOptions.publicKey?.challenge) {
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
if (credOptions.publicKey.excludeCredentials) {
credOptions.publicKey.excludeCredentials.map(cred => {
cred.id = _base64ToArrayBuffer(cred.id as any);
return cred;
});
}
this.u2fCredentialOptions = credOptions;
}
}
}, error => {
this.toast.showError(error);
});
if (credOptions.publicKey?.challenge) {
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
if (credOptions.publicKey.excludeCredentials) {
credOptions.publicKey.excludeCredentials.map(cred => {
cred.id = _base64ToArrayBuffer(cred.id as any);
return cred;
});
}
this.u2fCredentialOptions = credOptions;
}
}
}, error => {
this.toast.showError(error);
});
}
}
public submitAuth(): void {
if (this.selectedType === AuthFactorType.OTP) {
this.submitOTP();
} else if (this.selectedType === AuthFactorType.U2F) {
this.submitU2F();
}
public submitAuth(): void {
if (this.selectedType === AuthFactorType.OTP) {
this.submitOTP();
} else if (this.selectedType === AuthFactorType.U2F) {
this.submitU2F();
}
}
public submitOTP(): void {
this.authService.verifyMyMultiFactorOTP(this.otpcode).then(() => {
public submitOTP(): void {
this.authService.verifyMyMultiFactorOTP(this.otpcode).then(() => {
this.dialogRef.close(true);
}, error => {
this.dialogRef.close(false);
});
}
public submitU2F(): void {
if (this.u2fname && this.u2fCredentialOptions.publicKey) {
// this.data.credOptions.publicKey.rp.id = 'localhost';
navigator.credentials.create(this.u2fCredentialOptions).then((resp) => {
if (resp &&
(resp as any).response.attestationObject &&
(resp as any).response.clientDataJSON &&
(resp as any).rawId) {
const attestationObject = (resp as any).response.attestationObject;
const clientDataJSON = (resp as any).response.clientDataJSON;
const rawId = (resp as any).rawId;
const data = JSON.stringify({
id: resp.id,
rawId: _arrayBufferToBase64(rawId),
type: resp.type,
response: {
attestationObject: _arrayBufferToBase64(attestationObject),
clientDataJSON: _arrayBufferToBase64(clientDataJSON),
},
});
const base64 = btoa(data);
this.authService.verifyMyMultiFactorU2F(base64, this.u2fname).then(() => {
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
}, error => {
this.dialogRef.close(false);
});
}
public submitU2F(): void {
if (this.u2fname && this.u2fCredentialOptions.publicKey) {
// this.data.credOptions.publicKey.rp.id = 'localhost';
navigator.credentials.create(this.u2fCredentialOptions).then((resp) => {
if (resp &&
(resp as any).response.attestationObject &&
(resp as any).response.clientDataJSON &&
(resp as any).rawId) {
const attestationObject = (resp as any).response.attestationObject;
const clientDataJSON = (resp as any).response.clientDataJSON;
const rawId = (resp as any).rawId;
const data = JSON.stringify({
id: resp.id,
rawId: _arrayBufferToBase64(rawId),
type: resp.type,
response: {
attestationObject: _arrayBufferToBase64(attestationObject),
clientDataJSON: _arrayBufferToBase64(clientDataJSON),
},
});
const base64 = btoa(data);
this.authService.verifyMyMultiFactorU2F(base64, this.u2fname).then(() => {
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
this.u2fLoading = false;
}).catch(error => {
this.u2fLoading = false;
this.toast.showError(error);
});
} else {
this.u2fLoading = false;
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
}
}).catch(error => {
this.u2fLoading = false;
this.u2fError = error;
this.toast.showInfo(error.message);
});
this.u2fLoading = false;
}).catch(error => {
this.u2fLoading = false;
this.toast.showError(error);
});
} else {
this.u2fLoading = false;
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
}
}).catch(error => {
this.u2fLoading = false;
this.u2fError = error;
this.toast.showInfo(error.message);
});
}
}
}
@@ -1,10 +1,10 @@
<app-card title="{{'USER.PASSWORDLESS.TITLE' | translate}}"
<cnsl-card title="{{'USER.PASSWORDLESS.TITLE' | translate}}"
description="{{'USER.PASSWORDLESS.DESCRIPTION' | translate}}">
<button card-actions mat-icon-button (click)="getPasswordless()" class="icon-button" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
<mat-icon class="icon">refresh</mat-icon>
</button>
<app-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getPasswordless()"
[dataSize]="dataSource?.data?.length">
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getPasswordless()"
[dataSize]="dataSource?.data?.length ?? 0">
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.NAME' | translate }} </th>
@@ -37,7 +37,7 @@
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</app-refresh-table>
</cnsl-refresh-table>
<div class="add-row">
<button class="button" (click)="addPasswordless()" mat-raised-button color="primary"
matTooltip="{{'ACTIONS.NEW' | translate}}">
@@ -50,4 +50,4 @@
<mat-spinner diameter="50"></mat-spinner>
</div>
</div>
</app-card>
</cnsl-card>
@@ -23,7 +23,7 @@ export interface WebAuthNOptions {
}
@Component({
selector: 'app-auth-passwordless',
selector: 'cnsl-auth-passwordless',
templateUrl: './auth-passwordless.component.html',
styleUrls: ['./auth-passwordless.component.scss'],
})
@@ -13,7 +13,7 @@ export enum U2FComponentDestination {
}
@Component({
selector: 'app-dialog-passwordless',
selector: 'cnsl-dialog-passwordless',
templateUrl: './dialog-passwordless.component.html',
styleUrls: ['./dialog-passwordless.component.scss'],
})
@@ -1,4 +1,4 @@
<app-meta-layout>
<cnsl-meta-layout>
<div class="max-width-container">
<div class="header-row">
<div class="text">
@@ -7,7 +7,7 @@
</div>
<div class="theme">
<app-theme-setting></app-theme-setting>
<cnsl-theme-setting></cnsl-theme-setting>
</div>
</div>
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
@@ -16,40 +16,40 @@
<cnsl-info-row *ngIf="user" [user]="user"></cnsl-info-row>
<app-card *ngIf="user && user.human?.profile" class=" app-card" title="{{ 'USER.PROFILE.TITLE' | translate }}">
<app-detail-form [showEditImage]="true" [preferredLoginName]="user.preferredLoginName" [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
<cnsl-card *ngIf="user && user.human && user.human.profile" class=" app-card" title="{{ 'USER.PROFILE.TITLE' | translate }}">
<cnsl-detail-form [showEditImage]="true" [preferredLoginName]="user.preferredLoginName" [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
(changedLanguage)="changedLanguage($event)" (submitData)="saveProfile($event)">
</app-detail-form>
</app-card>
</cnsl-detail-form>
</cnsl-card>
<app-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
<cnsl-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
<button class="icon-button" card-actions mat-icon-button (click)="refreshUser()" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
<mat-icon class="icon">refresh</mat-icon>
</button>
<app-contact *ngIf="user?.human" [human]="user.human" [state]="user.state" canWrite="true"
<cnsl-contact *ngIf="user.human" [human]="user.human" [state]="user.state" [canWrite]="true"
(editType)="openEditDialog($event)" (enteredPhoneCode)="enteredPhoneCode($event)"
(deletedPhone)="deletePhone()" (resendEmailVerification)="resendEmailVerification()"
(resendPhoneVerification)="resendPhoneVerification()">
</app-contact>
</app-card>
</cnsl-contact>
</cnsl-card>
<app-external-idps [userId]="user?.id" [service]="userService"></app-external-idps>
<cnsl-external-idps *ngIf="user && user.id" [userId]="user.id" [service]="userService"></cnsl-external-idps>
<app-auth-passwordless *ngIf="user" #mfaComponent></app-auth-passwordless>
<cnsl-auth-passwordless *ngIf="user" #mfaComponent></cnsl-auth-passwordless>
<app-auth-user-mfa *ngIf="user" #mfaComponent></app-auth-user-mfa>
<cnsl-auth-user-mfa *ngIf="user" #mfaComponent></cnsl-auth-user-mfa>
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
<cnsl-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
description="{{'GRANTS.USER.DESCRIPTION' | translate }}">
<app-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
<cnsl-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
[displayedColumns]="['select', 'projectId', 'dates', 'roleNamesList']"
[disableWrite]="((['user.grant.write$'] | hasRole) | async) == false"
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) == false">
</app-user-grants>
</app-card>
[disableWrite]="((['user.grant.write$'] | hasRole) | async) === false"
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) === false">
</cnsl-user-grants>
</cnsl-card>
<ng-template appHasFeature [appHasFeature]="['metadata.user']">
<ng-template cnslHasFeature [hasFeature]="['metadata.user']">
<cnsl-metadata *ngIf="user?.id" [userId]="user.id"></cnsl-metadata>
</ng-template>
</div>
@@ -65,18 +65,18 @@
<span class="second"><span style="display: block;">{{user.preferredLoginName}}</span></span>
</div>
</div>
<mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true">
<mat-tab-group mat-stretch-tabs class="tab-group" [disablePagination]="true">
<mat-tab label="Details">
<div class="side-padding">
<ng-template appHasRole [appHasRole]="['user.membership.read']">
<app-memberships [auth]="true" [user]="user"></app-memberships>
<ng-template cnslHasRole [hasRole]="['user.membership.read']">
<cnsl-memberships [auth]="true" [user]="user"></cnsl-memberships>
</ng-template>
</div>
</mat-tab>
<mat-tab label="{{ 'CHANGES.PROJECT.TITLE' | translate }}" class="meta-flex-col">
<app-changes class="changes" [refresh]="refreshChanges$" [changeType]="ChangeType.MYUSER" [id]="user.id">
</app-changes>
<cnsl-changes class="changes" [refresh]="refreshChanges$" [changeType]="ChangeType.MYUSER" [id]="user.id">
</cnsl-changes>
</mat-tab>
</mat-tab-group>
</div>
</app-meta-layout>
</cnsl-meta-layout>
@@ -11,7 +11,7 @@ import { ToastService } from 'src/app/services/toast.service';
import { EditDialogComponent, EditDialogType } from './edit-dialog/edit-dialog.component';
@Component({
selector: 'app-auth-user-detail',
selector: 'cnsl-auth-user-detail',
templateUrl: './auth-user-detail.component.html',
styleUrls: ['./auth-user-detail.component.scss'],
})
@@ -1,8 +1,8 @@
<app-card title="{{'USER.MFA.TITLE' | translate}}" description="{{'USER.MFA.DESCRIPTION' | translate}}">
<cnsl-card title="{{'USER.MFA.TITLE' | translate}}" description="{{'USER.MFA.DESCRIPTION' | translate}}">
<button card-actions mat-icon-button (click)="getMFAs()" class="icon-button" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
<mat-icon class="icon">refresh</mat-icon>
</button>
<app-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getMFAs()" [dataSize]="dataSource?.data?.length">
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getMFAs()" [dataSize]="dataSource?.data?.length ?? 0">
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLETYPE' | translate }} </th>
@@ -44,7 +44,7 @@
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</app-refresh-table>
</cnsl-refresh-table>
<div class="add-row">
<button class="button" (click)="addAuthFactor()" mat-raised-button color="primary"
matTooltip="{{'ACTIONS.NEW' | translate}}">
@@ -57,4 +57,4 @@
<mat-spinner diameter="50"></mat-spinner>
</div>
</div>
</app-card>
</cnsl-card>
@@ -11,121 +11,121 @@ import { ToastService } from 'src/app/services/toast.service';
import { AuthFactorDialogComponent } from '../auth-factor-dialog/auth-factor-dialog.component';
export interface WebAuthNOptions {
challenge: string;
rp: { name: string, id: string; };
user: { name: string, id: string, displayName: string; };
pubKeyCredParams: any;
authenticatorSelection: { userVerification: string; };
timeout: number;
attestation: string;
challenge: string;
rp: { name: string, id: string; };
user: { name: string, id: string, displayName: string; };
pubKeyCredParams: any;
authenticatorSelection: { userVerification: string; };
timeout: number;
attestation: string;
}
@Component({
selector: 'app-auth-user-mfa',
templateUrl: './auth-user-mfa.component.html',
styleUrls: ['./auth-user-mfa.component.scss'],
selector: 'cnsl-auth-user-mfa',
templateUrl: './auth-user-mfa.component.html',
styleUrls: ['./auth-user-mfa.component.scss'],
})
export class AuthUserMfaComponent implements OnInit, OnDestroy {
public displayedColumns: string[] = ['type', 'name', 'state', 'actions'];
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
public displayedColumns: string[] = ['type', 'name', 'state', 'actions'];
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>;
@ViewChild(MatSort) public sort!: MatSort;
public dataSource!: MatTableDataSource<AuthFactor.AsObject>;
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>;
@ViewChild(MatSort) public sort!: MatSort;
public dataSource!: MatTableDataSource<AuthFactor.AsObject>;
public AuthFactorState: any = AuthFactorState;
public AuthFactorState: any = AuthFactorState;
public error: string = '';
public otpAvailable: boolean = false;
public error: string = '';
public otpAvailable: boolean = false;
constructor(
private service: GrpcAuthService,
private toast: ToastService,
private dialog: MatDialog,
) { }
constructor(
private service: GrpcAuthService,
private toast: ToastService,
private dialog: MatDialog,
) { }
public ngOnInit(): void {
this.getMFAs();
}
public ngOnInit(): void {
this.getMFAs();
}
public ngOnDestroy(): void {
this.loadingSubject.complete();
}
public ngOnDestroy(): void {
this.loadingSubject.complete();
}
public addAuthFactor(): void {
const dialogRef = this.dialog.open(AuthFactorDialogComponent, {
data: {
otpDisabled: !this.otpAvailable,
},
public addAuthFactor(): void {
const dialogRef = this.dialog.open(AuthFactorDialogComponent, {
data: {
otpDisabled: !this.otpAvailable,
},
});
dialogRef.afterClosed().subscribe((code) => {
if (code) {
this.service.verifyMyMultiFactorOTP(code).then(() => {
this.getMFAs();
});
}
});
}
dialogRef.afterClosed().subscribe((code) => {
if (code) {
this.service.verifyMyMultiFactorOTP(code).then(() => {
this.getMFAs();
});
public getMFAs(): void {
this.service.listMyMultiFactors().then(mfas => {
const list = mfas.resultList;
this.dataSource = new MatTableDataSource(list);
this.dataSource.sort = this.sort;
const index = list.findIndex(mfa => mfa.otp);
if (index === -1) {
this.otpAvailable = true;
}
}).catch(error => {
this.error = error.message;
});
}
public deleteMFA(factor: AuthFactor.AsObject): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.MFA.DIALOG.MFA_DELETE_TITLE',
descriptionKey: 'USER.MFA.DIALOG.MFA_DELETE_DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
if (factor.otp) {
this.service.removeMyMultiFactorOTP().then(() => {
this.toast.showInfo('USER.TOAST.OTPREMOVED', true);
const index = this.dataSource.data.findIndex(mfa => !!mfa.otp);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
});
}
this.getMFAs();
}).catch(error => {
this.toast.showError(error);
});
} else
if (factor.u2f) {
this.service.removeMyMultiFactorU2F(factor.u2f.id).then(() => {
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
public getMFAs(): void {
this.service.listMyMultiFactors().then(mfas => {
const list = mfas.resultList;
this.dataSource = new MatTableDataSource(list);
this.dataSource.sort = this.sort;
const index = list.findIndex(mfa => mfa.otp);
if (index === -1) {
this.otpAvailable = true;
}
}).catch(error => {
this.error = error.message;
});
}
public deleteMFA(factor: AuthFactor.AsObject): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.MFA.DIALOG.MFA_DELETE_TITLE',
descriptionKey: 'USER.MFA.DIALOG.MFA_DELETE_DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
if (factor.otp) {
this.service.removeMyMultiFactorOTP().then(() => {
this.toast.showInfo('USER.TOAST.OTPREMOVED', true);
const index = this.dataSource.data.findIndex(mfa => !!mfa.otp);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
this.getMFAs();
}).catch(error => {
this.toast.showError(error);
});
} else
if (factor.u2f) {
this.service.removeMyMultiFactorU2F(factor.u2f.id).then(() => {
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
const index = this.dataSource.data.findIndex(mfa => !!mfa.u2f);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
this.getMFAs();
}).catch(error => {
this.toast.showError(error);
});
}
}
});
}
const index = this.dataSource.data.findIndex(mfa => !!mfa.u2f);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
this.getMFAs();
}).catch(error => {
this.toast.showError(error);
});
}
}
});
}
}
@@ -2,17 +2,17 @@ import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'app-code-dialog',
templateUrl: './code-dialog.component.html',
styleUrls: ['./code-dialog.component.scss'],
selector: 'cnsl-code-dialog',
templateUrl: './code-dialog.component.html',
styleUrls: ['./code-dialog.component.scss'],
})
export class CodeDialogComponent {
public code: string = '';
constructor(public dialogRef: MatDialogRef<CodeDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { }
public code: string = '';
constructor(public dialogRef: MatDialogRef<CodeDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { }
closeDialog(code: string = ''): void {
this.dialogRef.close(code);
}
closeDialog(code: string = ''): void {
this.dialogRef.close(code);
}
}
@@ -8,93 +8,93 @@ import { ToastService } from 'src/app/services/toast.service';
import { _arrayBufferToBase64 } from '../u2f_util';
export enum U2FComponentDestination {
MFA = 'mfa',
PASSWORDLESS = 'passwordless',
MFA = 'mfa',
PASSWORDLESS = 'passwordless',
}
@Component({
selector: 'app-dialog-u2f',
templateUrl: './dialog-u2f.component.html',
styleUrls: ['./dialog-u2f.component.scss'],
selector: 'cnsl-dialog-u2f',
templateUrl: './dialog-u2f.component.html',
styleUrls: ['./dialog-u2f.component.scss'],
})
export class DialogU2FComponent {
private type!: U2FComponentDestination;
public name: string = '';
public error: string = '';
public loading: boolean = false;
private type!: U2FComponentDestination;
public name: string = '';
public error: string = '';
public loading: boolean = false;
constructor(public dialogRef: MatDialogRef<DialogU2FComponent>,
@Inject(MAT_DIALOG_DATA) public data: { credOptions: any; type: U2FComponentDestination; },
private service: GrpcAuthService, private translate: TranslateService, private toast: ToastService) {
this.type = data.type;
}
constructor(public dialogRef: MatDialogRef<DialogU2FComponent>,
@Inject(MAT_DIALOG_DATA) public data: { credOptions: any; type: U2FComponentDestination; },
private service: GrpcAuthService, private translate: TranslateService, private toast: ToastService) {
this.type = data.type;
}
public closeDialog(): void {
this.dialogRef.close();
}
public closeDialog(): void {
this.dialogRef.close();
}
public closeDialogWithCode(): void {
this.error = '';
this.loading = true;
if (this.name && this.data.credOptions.publicKey) {
// this.data.credOptions.publicKey.rp.id = 'localhost';
navigator.credentials.create(this.data.credOptions).then((resp) => {
if (resp &&
(resp as any).response.attestationObject &&
(resp as any).response.clientDataJSON &&
(resp as any).rawId) {
public closeDialogWithCode(): void {
this.error = '';
this.loading = true;
if (this.name && this.data.credOptions.publicKey) {
// this.data.credOptions.publicKey.rp.id = 'localhost';
navigator.credentials.create(this.data.credOptions).then((resp) => {
if (resp &&
(resp as any).response.attestationObject &&
(resp as any).response.clientDataJSON &&
(resp as any).rawId) {
const attestationObject = (resp as any).response.attestationObject;
const clientDataJSON = (resp as any).response.clientDataJSON;
const rawId = (resp as any).rawId;
const attestationObject = (resp as any).response.attestationObject;
const clientDataJSON = (resp as any).response.clientDataJSON;
const rawId = (resp as any).rawId;
const data = JSON.stringify({
id: resp.id,
rawId: _arrayBufferToBase64(rawId),
type: resp.type,
response: {
attestationObject: _arrayBufferToBase64(attestationObject),
clientDataJSON: _arrayBufferToBase64(clientDataJSON),
},
});
const data = JSON.stringify({
id: resp.id,
rawId: _arrayBufferToBase64(rawId),
type: resp.type,
response: {
attestationObject: _arrayBufferToBase64(attestationObject),
clientDataJSON: _arrayBufferToBase64(clientDataJSON),
},
});
const base64 = btoa(data);
if (this.type === U2FComponentDestination.MFA) {
this.service.verifyMyMultiFactorU2F(base64, this.name).then(() => {
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
this.loading = false;
}).catch(error => {
this.loading = false;
this.toast.showError(error);
});
} else if (this.type === U2FComponentDestination.PASSWORDLESS) {
this.service.verifyMyPasswordless(base64, this.name).then(() => {
this.translate.get('USER.PASSWORDLESS.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
this.loading = false;
}).catch(error => {
this.loading = false;
this.toast.showError(error);
});
}
} else {
this.loading = false;
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
}
const base64 = btoa(data);
if (this.type === U2FComponentDestination.MFA) {
this.service.verifyMyMultiFactorU2F(base64, this.name).then(() => {
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
this.loading = false;
}).catch(error => {
this.loading = false;
this.error = error;
this.toast.showInfo(error.message);
this.loading = false;
this.toast.showError(error);
});
} else if (this.type === U2FComponentDestination.PASSWORDLESS) {
this.service.verifyMyPasswordless(base64, this.name).then(() => {
this.translate.get('USER.PASSWORDLESS.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
this.loading = false;
}).catch(error => {
this.loading = false;
this.toast.showError(error);
});
}
} else {
this.loading = false;
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
}
}).catch(error => {
this.loading = false;
this.error = error;
this.toast.showInfo(error.message);
});
}
}
}
@@ -4,55 +4,55 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { parsePhoneNumber } from 'libphonenumber-js';
export enum EditDialogType {
PHONE = 1,
EMAIL = 2,
PHONE = 1,
EMAIL = 2,
}
@Component({
selector: 'app-edit-email-dialog',
templateUrl: './edit-dialog.component.html',
styleUrls: ['./edit-dialog.component.scss'],
selector: 'cnsl-edit-email-dialog',
templateUrl: './edit-dialog.component.html',
styleUrls: ['./edit-dialog.component.scss'],
})
export class EditDialogComponent {
public isPhone: 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) {
this.valueControl.setValue(data.value);
if (data.type === EditDialogType.PHONE) {
this.isPhone = true;
public isPhone: 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) {
this.valueControl.setValue(data.value);
if (data.type === EditDialogType.PHONE) {
this.isPhone = true;
}
this.valueControl.valueChanges.subscribe(value => {
if (value && value.length > 1) {
this.changeValue(value);
}
});
}
private changeValue(changedValue: string): void {
if (this.isPhone && changedValue) {
try {
const phoneNumber = parsePhoneNumber(changedValue ?? '', 'CH');
if (phoneNumber) {
const formmatted = phoneNumber.formatInternational();
this.phoneCountry = phoneNumber.country || '';
if (formmatted !== this.valueControl.value) {
this.valueControl.setValue(formmatted);
}
}
this.valueControl.valueChanges.subscribe(value => {
if (value && value.length > 1) {
this.changeValue(value);
}
});
} catch (error) {
console.error(error);
}
}
}
private changeValue(changedValue: string): void {
if (this.isPhone && changedValue) {
try {
const phoneNumber = parsePhoneNumber(changedValue ?? '', 'CH');
if (phoneNumber) {
const formmatted = phoneNumber.formatInternational();
this.phoneCountry = phoneNumber.country || '';
if (formmatted !== this.valueControl.value) {
this.valueControl.setValue(formmatted);
}
}
} catch (error) {
console.error(error);
}
}
}
closeDialog(): void {
this.dialogRef.close();
}
closeDialog(): void {
this.dialogRef.close();
}
closeDialogWithValue(): void {
this.dialogRef.close(this.valueControl.value);
}
closeDialogWithValue(): void {
this.dialogRef.close(this.valueControl.value);
}
}
@@ -2,20 +2,20 @@ import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'app-resend-email-dialog',
templateUrl: './resend-email-dialog.component.html',
styleUrls: ['./resend-email-dialog.component.scss'],
selector: 'cnsl-resend-email-dialog',
templateUrl: './resend-email-dialog.component.html',
styleUrls: ['./resend-email-dialog.component.scss'],
})
export class ResendEmailDialogComponent {
public email: string = '';
constructor(public dialogRef: MatDialogRef<ResendEmailDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { }
public email: string = '';
constructor(public dialogRef: MatDialogRef<ResendEmailDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { }
closeDialog(email: string = ''): void {
this.dialogRef.close(email);
}
closeDialog(email: string = ''): void {
this.dialogRef.close(email);
}
closeDialogWithSend(email: string = ''): void {
this.dialogRef.close({ send: true, email });
}
closeDialogWithSend(email: string = ''): void {
this.dialogRef.close({ send: true, email });
}
}
@@ -1,4 +1,4 @@
<input type="checkbox" id="switch" (change)="change($event.target.checked)" [(checked)]="isDarkTheme">
<input type="checkbox" id="switch" (change)="change($event)" [checked]="isDarkTheme"/>
<div class="theme-app">
<div class="theme-content">
<label for="switch">
@@ -3,27 +3,30 @@ import { Subscription } from 'rxjs';
import { ThemeService } from 'src/app/services/theme.service';
@Component({
selector: 'app-theme-setting',
templateUrl: './theme-setting.component.html',
styleUrls: ['./theme-setting.component.scss'],
selector: 'cnsl-theme-setting',
templateUrl: './theme-setting.component.html',
styleUrls: ['./theme-setting.component.scss'],
})
export class ThemeSettingComponent implements OnInit, OnDestroy {
public isDarkTheme: boolean = true;
private sub: Subscription = new Subscription();
constructor(private themeService: ThemeService) {
const theme = localStorage.getItem('theme');
this.isDarkTheme = theme === 'dark-theme' ? true : theme === 'light-theme' ? false : true;
}
public isDarkTheme: boolean = true;
private sub: Subscription = new Subscription();
constructor(private themeService: ThemeService) {
const theme = localStorage.getItem('theme');
this.isDarkTheme = theme === 'dark-theme' ? true : theme === 'light-theme' ? false : true;
}
ngOnInit(): void {
this.sub = this.themeService.isDarkTheme.subscribe(d => this.isDarkTheme = d);
}
ngOnInit(): void {
this.sub = this.themeService.isDarkTheme.subscribe(d => this.isDarkTheme = d);
}
ngOnDestroy(): void {
this.sub.unsubscribe();
}
ngOnDestroy(): void {
this.sub.unsubscribe();
}
public change(checked: boolean): void {
this.themeService.setDarkTheme(checked);
public change(event: any): void {
if (event.target) {
const checked = event.target.checked;
this.themeService.setDarkTheme(checked);
}
}
}
@@ -21,7 +21,7 @@
<span class="name">{{human?.email?.email}}</span>
<span *ngIf="human?.email?.isEmailVerified" class="contact-state verified">{{'USER.EMAILVERIFIED' |
translate}}</span>
<div *ngIf="!human?.email.isEmailVerified" class="block">
<div *ngIf="!human?.email?.isEmailVerified" class="block">
<span class="contact-state notverified">{{'USER.NOTVERIFIED' | translate}}</span>
<ng-container *ngIf="human?.email">
@@ -35,7 +35,7 @@
<div class="right">
<button matTooltip="{{'ACTIONS.EDIT' | translate}}"
[disabled]="!canWrite || state == UserState.USER_STATE_INITIAL"
[disabled]="!canWrite || state === UserState.USER_STATE_INITIAL"
(click)="openEditDialog(EditDialogType.EMAIL)" mat-icon-button>
<i class="las la-edit"></i>
</button>
@@ -46,9 +46,9 @@
<div class="left">
<span class="label">{{ 'USER.PHONE' | translate }}</span>
<span class="name">{{human?.phone?.phone ? human.phone?.phone : ('USER.PHONEEMPTY' | translate)}}</span>
<span *ngIf="human?.phone.isPhoneVerified" class="contact-state verified">{{'USER.PHONEVERIFIED' |
<span *ngIf="human?.phone?.isPhoneVerified" class="contact-state verified">{{'USER.PHONEVERIFIED' |
translate}}</span>
<div *ngIf="human.phone?.phone && !human?.phone.isPhoneVerified" class="block">
<div *ngIf="human.phone?.phone && !human?.phone?.isPhoneVerified" class="block">
<span class="contact-state notverified">{{'USER.NOTVERIFIED' | translate}}</span>
<ng-container *ngIf="human?.phone?.phone">
@@ -7,69 +7,69 @@ import { CodeDialogComponent } from '../auth-user-detail/code-dialog/code-dialog
import { EditDialogType } from '../auth-user-detail/edit-dialog/edit-dialog.component';
@Component({
selector: 'app-contact',
templateUrl: './contact.component.html',
styleUrls: ['./contact.component.scss'],
selector: 'cnsl-contact',
templateUrl: './contact.component.html',
styleUrls: ['./contact.component.scss'],
})
export class ContactComponent {
@Input() disablePhoneCode: boolean = false;
@Input() canWrite: boolean = false;
@Input() human!: Human.AsObject;
@Input() state!: UserState;
@Output() editType: EventEmitter<EditDialogType> = new EventEmitter();
@Output() resendEmailVerification: EventEmitter<void> = new EventEmitter();
@Output() resendPhoneVerification: EventEmitter<void> = new EventEmitter();
@Output() enteredPhoneCode: EventEmitter<string> = new EventEmitter();
@Output() deletedPhone: EventEmitter<void> = new EventEmitter();
public UserState: any = UserState;
@Input() disablePhoneCode: boolean = false;
@Input() canWrite: boolean | null = false;
@Input() human!: Human.AsObject;
@Input() state!: UserState;
@Output() editType: EventEmitter<EditDialogType> = new EventEmitter();
@Output() resendEmailVerification: EventEmitter<void> = new EventEmitter();
@Output() resendPhoneVerification: EventEmitter<void> = new EventEmitter();
@Output() enteredPhoneCode: EventEmitter<string> = new EventEmitter();
@Output() deletedPhone: EventEmitter<void> = new EventEmitter();
public UserState: any = UserState;
public EditDialogType: any = EditDialogType;
constructor(private dialog: MatDialog) { }
public EditDialogType: any = EditDialogType;
constructor(private dialog: MatDialog) { }
emitDeletePhone(): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.LOGINMETHODS.PHONE.DELETETITLE',
descriptionKey: 'USER.LOGINMETHODS.PHONE.DELETEDESC',
},
width: '400px',
});
emitDeletePhone(): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.LOGINMETHODS.PHONE.DELETETITLE',
descriptionKey: 'USER.LOGINMETHODS.PHONE.DELETEDESC',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
this.deletedPhone.emit();
}
});
}
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
this.deletedPhone.emit();
}
});
}
emitEmailVerification(): void {
this.resendEmailVerification.emit();
}
emitEmailVerification(): void {
this.resendEmailVerification.emit();
}
emitPhoneVerification(): void {
this.resendPhoneVerification.emit();
}
emitPhoneVerification(): void {
this.resendPhoneVerification.emit();
}
public enterCode(): void {
if (this.human) {
const dialogRef = this.dialog.open(CodeDialogComponent, {
data: {
number: this.human.phone,
},
width: '400px',
});
public enterCode(): void {
if (this.human) {
const dialogRef = this.dialog.open(CodeDialogComponent, {
data: {
number: this.human.phone,
},
width: '400px',
});
dialogRef.afterClosed().subscribe(code => {
if (code) {
this.enteredPhoneCode.emit(code);
}
});
dialogRef.afterClosed().subscribe(code => {
if (code) {
this.enteredPhoneCode.emit(code);
}
});
}
}
public openEditDialog(type: EditDialogType): void {
this.editType.emit(type);
}
public openEditDialog(type: EditDialogType): void {
this.editType.emit(type);
}
}
@@ -1,54 +1,54 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Subscription } from 'rxjs';
import { User } from 'src/app/proto/generated/zitadel/user_pb';
import { Human, Machine } from 'src/app/proto/generated/zitadel/user_pb';
@Component({
selector: 'app-detail-form-machine',
templateUrl: './detail-form-machine.component.html',
styleUrls: ['./detail-form-machine.component.scss'],
selector: 'cnsl-detail-form-machine',
templateUrl: './detail-form-machine.component.html',
styleUrls: ['./detail-form-machine.component.scss'],
})
export class DetailFormMachineComponent implements OnInit, OnDestroy {
@Input() public username!: string;
@Input() public user!: User;
@Input() public disabled: boolean = false;
@Output() public submitData: EventEmitter<any> = new EventEmitter<any>();
@Input() public username!: string;
@Input() public user!: Human.AsObject | Machine.AsObject;
@Input() public disabled: boolean = false;
@Output() public submitData: EventEmitter<any> = new EventEmitter<any>();
public machineForm!: FormGroup;
public machineForm!: FormGroup;
private sub: Subscription = new Subscription();
private sub: Subscription = new Subscription();
constructor(private fb: FormBuilder) {
this.machineForm = this.fb.group({
userName: [{ value: '', disabled: true }, [
Validators.required,
]],
name: [{ value: '', disabled: this.disabled }, Validators.required],
description: [{ value: '', disabled: this.disabled }],
});
}
constructor(private fb: FormBuilder) {
this.machineForm = this.fb.group({
userName: [{ value: '', disabled: true }, [
Validators.required,
]],
name: [{ value: '', disabled: this.disabled }, Validators.required],
description: [{ value: '', disabled: this.disabled }],
});
}
public ngOnInit(): void {
this.machineForm.patchValue({ userName: this.username, ...this.user });
}
public ngOnInit(): void {
this.machineForm.patchValue({ ...this.user, userName: this.username });
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
public submitForm(): void {
this.submitData.emit(this.machineForm.value);
}
public submitForm(): void {
this.submitData.emit(this.machineForm.value);
}
public get name(): AbstractControl | null {
return this.machineForm.get('name');
}
public get name(): AbstractControl | null {
return this.machineForm.get('name');
}
public get description(): AbstractControl | null {
return this.machineForm.get('description');
}
public get description(): AbstractControl | null {
return this.machineForm.get('description');
}
public get userName(): AbstractControl | null {
return this.machineForm.get('userName');
}
public get userName(): AbstractControl | null {
return this.machineForm.get('userName');
}
}
@@ -5,10 +5,10 @@
<div class="i-wrapper" *ngIf="showEditImage">
<i class="las la-camera"></i>
</div>
<app-avatar
*ngIf="user && user.profile?.displayName && user.profile?.firstName && user?.profile.lastName"
class="avatar" [name]="user.profile?.displayName" [avatarUrl]="user?.profile?.avatarUrl || ''" [forColor]="preferredLoginName" [size]="80">
</app-avatar>
<cnsl-avatar
*ngIf="user && user.profile?.displayName && user.profile?.firstName && user.profile?.lastName"
class="avatar" [name]="user.profile?.displayName ?? ''" [avatarUrl]="user?.profile?.avatarUrl || ''" [forColor]="preferredLoginName" [size]="80">
</cnsl-avatar>
</button>
<cnsl-form-field class="formfield">
@@ -2,12 +2,12 @@ import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@a
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { Subscription } from 'rxjs';
import { Gender, Human, User } from 'src/app/proto/generated/zitadel/user_pb';
import { Gender, Human, Profile } from 'src/app/proto/generated/zitadel/user_pb';
import { ProfilePictureComponent } from './profile-picture/profile-picture.component';
@Component({
selector: 'app-detail-form',
selector: 'cnsl-detail-form',
templateUrl: './detail-form.component.html',
styleUrls: ['./detail-form.component.scss'],
})
@@ -19,7 +19,7 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
@Input() public disabled: boolean = false;
@Input() public genders: Gender[] = [];
@Input() public languages: string[] = ['de', 'en'];
@Output() public submitData: EventEmitter<User> = new EventEmitter<User>();
@Output() public submitData: EventEmitter<Profile.AsObject> = new EventEmitter<Profile.AsObject>();
@Output() public changedLanguage: EventEmitter<string> = new EventEmitter<string>();
public profileForm!: FormGroup;
@@ -1,8 +1,8 @@
<app-card title="{{ 'USER.EXTERNALIDP.TITLE' | translate }}" description="{{ 'USER.EXTERNALIDP.DESC' | translate }}">
<cnsl-card title="{{ 'USER.EXTERNALIDP.TITLE' | translate }}" description="{{ 'USER.EXTERNALIDP.DESC' | translate }}">
<button card-actions mat-icon-button (click)="refreshPage()" class="icon-button" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
<mat-icon class="icon">refresh</mat-icon>
</button>
<app-refresh-table [hideRefresh]="true" [loading]="loading$ | async" [dataSize]="dataSource.data.length"
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async" [dataSize]="dataSource.data.length"
[timestamp]="viewTimestamp" [selection]="selection">
<div class="table-wrapper">
@@ -59,6 +59,5 @@
<cnsl-paginator #paginator class="paginator" [timestamp]="viewTimestamp" [length]="totalResult || 0" [pageSize]="10"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></cnsl-paginator>
</div>
</app-refresh-table>
</app-card>
</cnsl-refresh-table>
</cnsl-card>
@@ -13,7 +13,7 @@ import { ManagementService } from '../../../../services/mgmt.service';
import { ToastService } from '../../../../services/toast.service';
@Component({
selector: 'app-external-idps',
selector: 'cnsl-external-idps',
templateUrl: './external-idps.component.html',
styleUrls: ['./external-idps.component.scss'],
})
@@ -1,12 +1,12 @@
<app-detail-layout [backRouterLink]="[ '/users', user?.id]"
title="{{user?.human?.displayName}} {{ 'USER.MEMBERSHIPS.TITLE' | translate }}"
<cnsl-detail-layout [backRouterLink]="[ '/users', user?.id]"
title="{{ 'USER.MEMBERSHIPS.TITLE' | translate }}"
description="{{ 'USER.MEMBERSHIPS.DESCRIPTION' | translate }}">
<cnsl-info-section class="context-wrapper">
<span *ngIf="isMe">{{'USER.MEMBERSHIPS.USERCONTEXT' | translate}}</span>
<span *ngIf="!isMe">{{'USER.MEMBERSHIPS.ORGCONTEXT' | translate}}</span>
</cnsl-info-section>
<app-refresh-table class="refresh-table" (refreshed)="refreshPage()" [dataSize]="dataSource?.totalResult"
<cnsl-refresh-table class="refresh-table" (refreshed)="refreshPage()" [dataSize]="dataSource?.totalResult ?? 0"
[timestamp]="dataSource?.viewTimestamp" [selection]="selection" [loading]="dataSource?.loading$ | async">
<a actions color="primary" (click)="addMember()" color="primary" mat-raised-button>
@@ -70,7 +70,7 @@
<ng-container matColumnDef="actions" stickyEnd>
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let member">
<button [disabled]="member.projectId && member.projectGrantId ? (['project.grant.member.delete:' + member.projectGrantId, 'project.grant.member.delete'] | hasRole | async) == false : member.projectId ? (['project.member.delete:' + member.projectGrantId, 'project.member.delete'] | hasRole | async) == false : member.orgId ? (['org.member.delete:' + member.orgId, 'org.member.delete'] | hasRole | async) == false : (['iam.member.delete'] | hasRole | async) == false"
<button [disabled]="member.projectId && member.projectGrantId ? (['project.grant.member.delete:' + member.projectGrantId, 'project.grant.member.delete'] | hasRole | async) === false : member.projectId ? (['project.member.delete:' + member.projectGrantId, 'project.member.delete'] | hasRole | async) === false : member.orgId ? (['org.member.delete:' + member.orgId, 'org.member.delete'] | hasRole | async) === false : (['iam.member.delete'] | hasRole | async) === false"
mat-icon-button color="warn" matTooltip="{{'ACTIONS.REMOVE' | translate}}"
(click)="removeMembership(member)">
<i class="las la-minus-circle"></i>
@@ -86,5 +86,5 @@
<cnsl-paginator class="paginator" #paginator [timestamp]="dataSource?.viewTimestamp" [pageSize]="50" [pageSizeOptions]="[25, 50, 100, 250]">
</cnsl-paginator>
</div>
</app-refresh-table>
</app-detail-layout>
</cnsl-refresh-table>
</cnsl-detail-layout>
@@ -15,7 +15,7 @@ import { ToastService } from 'src/app/services/toast.service';
import { MembershipDetailDataSource } from './membership-detail-datasource';
@Component({
selector: 'app-membership-detail',
selector: 'cnsl-membership-detail',
templateUrl: './membership-detail.component.html',
styleUrls: ['./membership-detail.component.scss'],
})
@@ -3,7 +3,7 @@
<div class="people" *ngIf="memberships">
<div class="img-list" [@cardAnimation]="totalResult">
<mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
<ng-container *ngIf="totalResult < 8; else compact">
<ng-container *ngIf="totalResult < 8 && memberships && memberships.length; else compact">
<ng-container *ngFor="let membership of memberships; index as i">
<div @animate class="avatar-circle" (click)="navigateToObject()"
matTooltip="{{ membership.displayName }} | {{membership.rolesList?.join(' ')}}"
@@ -11,7 +11,7 @@ import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-memberships',
selector: 'cnsl-memberships',
templateUrl: './memberships.component.html',
styleUrls: ['./memberships.component.scss'],
animations: [
@@ -33,7 +33,7 @@ import { ToastService } from 'src/app/services/toast.service';
})
export class MembershipsComponent implements OnInit {
public loading: boolean = false;
public memberships!: Membership.AsObject[] | UserGrant.AsObject[];
public memberships: Membership.AsObject[] = [];
public totalResult: number = 0;
@Input() public auth: boolean = false;
@@ -181,7 +181,7 @@ export class MembershipsComponent implements OnInit {
}
}
getColor(type: Membership.AsObject[] | UserGrant.AsObject[]): string {
getColor(type: Membership.AsObject | UserGrant.AsObject): string {
const gen = type.toString();
const colors = [
'rgb(201, 115, 88)',
@@ -194,9 +194,9 @@ export class MembershipsComponent implements OnInit {
return colors[hash];
}
for (let i = 0; i < gen.length; i++) {
// tslint:disable-next-line: no-bitwise
// eslint-disable-next-line no-bitwise
hash = gen.charCodeAt(i) + ((hash << 5) - hash);
// tslint:disable-next-line: no-bitwise
// eslint-disable-next-line no-bitwise
hash = hash & hash;
}
hash = ((hash % colors.length) + colors.length) % colors.length;
@@ -7,7 +7,7 @@ import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-metadata-dialog',
selector: 'cnsl-metadata-dialog',
templateUrl: './metadata-dialog.component.html',
styleUrls: ['./metadata-dialog.component.scss'],
})
@@ -1,4 +1,4 @@
<app-card class="metadata-details" title="{{ 'USER.METADATA.TITLE' | translate }}">
<cnsl-card class="metadata-details" title="{{ 'USER.METADATA.TITLE' | translate }}">
<div card-actions class="actions">
<mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
<button mat-raised-button color="primary" class="edit" (click)="editMetadata()">{{'ACTIONS.EDIT' | translate}}</button>
@@ -18,4 +18,4 @@
<ng-template #emptyList>
<p class="empty-desc">{{'USER.METADATA.EMPTY' | translate}}</p>
</ng-template>
</app-card>
</cnsl-card>
@@ -1,10 +1,10 @@
<app-detail-layout [backRouterLink]="userId ? [ '/users',userId] : [ '/users/me']"
<cnsl-detail-layout [backRouterLink]="userId ? [ '/users',userId] : [ '/users/me']"
title="{{ 'USER.PASSWORD.TITLE' | translate }}" description="{{ 'USER.PASSWORD.DESCRIPTION' | translate }}">
<ng-container *ngIf="userId; else authUser">
<div class="validation" *ngIf="this.policy">
<app-password-complexity-view [policy]="this.policy" [password]="password">
</app-password-complexity-view>
<cnsl-password-complexity-view [policy]="this.policy" [password]="password">
</cnsl-password-complexity-view>
</div>
<form *ngIf="passwordForm" autocomplete="new-password" [formGroup]="passwordForm"
@@ -47,8 +47,8 @@
</cnsl-form-field>
<div class="validation between" *ngIf="this.policy">
<app-password-complexity-view [policy]="this.policy" [password]="newPassword">
</app-password-complexity-view>
<cnsl-password-complexity-view [policy]="this.policy" [password]="newPassword">
</cnsl-password-complexity-view>
</div>
<cnsl-form-field class="formfield" appearance="outline">
@@ -73,4 +73,4 @@
color="primary">{{ 'USER.PASSWORD.RESET' | translate }}</button>
</form>
</ng-template>
</app-detail-layout>
</cnsl-detail-layout>
@@ -9,131 +9,131 @@ import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
function passwordConfirmValidator(c: AbstractControl): any {
if (!c.parent || !c) {
return;
}
const pwd = c.parent.get('password');
const cpwd = c.parent.get('confirmPassword');
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: 'Password is not equal' };
}
if (!pwd || !cpwd) {
return;
}
if (pwd.value !== cpwd.value) {
return { invalid: true, notequal: 'Password is not equal' };
}
}
@Component({
selector: 'app-password',
templateUrl: './password.component.html',
styleUrls: ['./password.component.scss'],
selector: 'cnsl-password',
templateUrl: './password.component.html',
styleUrls: ['./password.component.scss'],
})
export class PasswordComponent implements OnDestroy {
userId: string = '';
userId: string = '';
public policy!: PasswordComplexityPolicy.AsObject;
public passwordForm!: FormGroup;
public policy!: PasswordComplexityPolicy.AsObject;
public passwordForm!: FormGroup;
private formSub: Subscription = new Subscription();
private formSub: Subscription = new Subscription();
constructor(
activatedRoute: ActivatedRoute,
private fb: FormBuilder,
private authService: GrpcAuthService,
private mgmtUserService: ManagementService,
private toast: ToastService,
) {
activatedRoute.params.subscribe(data => {
const { id } = data;
if (id) {
this.userId = id;
}
constructor(
activatedRoute: ActivatedRoute,
private fb: FormBuilder,
private authService: GrpcAuthService,
private mgmtUserService: ManagementService,
private toast: ToastService,
) {
activatedRoute.params.subscribe(data => {
const { id } = data;
if (id) {
this.userId = id;
}
const validators: Validators[] = [Validators.required];
this.authService.getMyPasswordComplexityPolicy().then(resp => {
if (resp.policy) {
this.policy = resp.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 validators: Validators[] = [Validators.required];
this.authService.getMyPasswordComplexityPolicy().then(resp => {
if (resp.policy) {
this.policy = resp.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);
}
this.setupForm(validators);
}).catch(error => {
this.setupForm(validators);
});
this.setupForm(validators);
}).catch(error => {
this.setupForm(validators);
});
});
}
public ngOnDestroy(): void {
this.formSub.unsubscribe();
}
setupForm(validators: Validators[]): void {
if (this.userId) {
this.passwordForm = this.fb.group({
password: ['', validators],
confirmPassword: ['', [...validators, passwordConfirmValidator]],
});
} else {
this.passwordForm = this.fb.group({
currentPassword: ['', Validators.required],
newPassword: ['', validators],
confirmPassword: ['', [...validators, passwordConfirmValidator]],
});
}
}
public setInitialPassword(userId: string): void {
if (this.passwordForm.valid && this.password && this.password.value) {
this.mgmtUserService.setHumanInitialPassword(userId, this.password.value).then((data: any) => {
this.toast.showInfo('USER.TOAST.INITIALPASSWORDSET', true);
window.history.back();
}).catch(error => {
this.toast.showError(error);
});
}
}
public setPassword(): void {
if (this.passwordForm.valid && this.currentPassword &&
this.currentPassword.value &&
this.newPassword && this.newPassword.value && this.newPassword.valid) {
this.authService.updateMyPassword(this.currentPassword.value, this.newPassword.value)
.then((data: any) => {
this.toast.showInfo('USER.TOAST.PASSWORDCHANGED', true);
window.history.back();
}).catch(error => {
this.toast.showError(error);
});
}
}
public ngOnDestroy(): void {
this.formSub.unsubscribe();
}
public get password(): AbstractControl | null {
return this.passwordForm.get('password');
}
setupForm(validators: Validators[]): void {
if (this.userId) {
this.passwordForm = this.fb.group({
password: ['', validators],
confirmPassword: ['', [...validators, passwordConfirmValidator]],
});
} else {
this.passwordForm = this.fb.group({
currentPassword: ['', Validators.required],
newPassword: ['', validators],
confirmPassword: ['', [...validators, passwordConfirmValidator]],
});
}
}
public get newPassword(): AbstractControl | null {
return this.passwordForm.get('newPassword');
}
public setInitialPassword(userId: string): void {
if (this.passwordForm.valid && this.password && this.password.value) {
this.mgmtUserService.setHumanInitialPassword(userId, this.password.value).then((data: any) => {
this.toast.showInfo('USER.TOAST.INITIALPASSWORDSET', true);
window.history.back();
}).catch(error => {
this.toast.showError(error);
});
}
}
public get currentPassword(): AbstractControl | null {
return this.passwordForm.get('currentPassword');
}
public setPassword(): void {
if (this.passwordForm.valid && this.currentPassword &&
this.currentPassword.value &&
this.newPassword && this.newPassword.value && this.newPassword.valid) {
this.authService.updateMyPassword(this.currentPassword.value, this.newPassword.value)
.then((data: any) => {
this.toast.showInfo('USER.TOAST.PASSWORDCHANGED', true);
window.history.back();
}).catch(error => {
this.toast.showError(error);
});
}
}
public get password(): AbstractControl | null {
return this.passwordForm.get('password');
}
public get newPassword(): AbstractControl | null {
return this.passwordForm.get('newPassword');
}
public get currentPassword(): AbstractControl | null {
return this.passwordForm.get('currentPassword');
}
public get confirmPassword(): AbstractControl | null {
return this.passwordForm.get('confirmPassword');
}
public get confirmPassword(): AbstractControl | null {
return this.passwordForm.get('confirmPassword');
}
}
@@ -1,10 +1,10 @@
<app-card title="{{'USER.PASSWORDLESS.TITLE' | translate}}"
<cnsl-card title="{{'USER.PASSWORDLESS.TITLE' | translate}}"
description="{{'USER.PASSWORDLESS.DESCRIPTION' | translate}}">
<button card-actions mat-icon-button (click)="getPasswordless()" class="icon-button" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
<mat-icon class="icon">refresh</mat-icon>
</button>
<app-refresh-table [hideRefresh]="true" [loading]="loading$ | async"
[dataSize]="dataSource?.data?.length">
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async"
[dataSize]="dataSource?.data?.length ?? 0">
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.NAME' | translate }} </th>
@@ -37,7 +37,7 @@
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</app-refresh-table>
</cnsl-refresh-table>
<div class="add-row">
<button class="button" (click)="sendPasswordlessRegistration()" mat-raised-button color="primary"
matTooltip="{{'ACTIONS.NEW' | translate}}">
@@ -50,4 +50,4 @@
<mat-spinner diameter="50"></mat-spinner>
</div>
</div>
</app-card>
</cnsl-card>
@@ -19,12 +19,12 @@ export interface WebAuthNOptions {
}
@Component({
selector: 'app-passwordless',
selector: 'cnsl-passwordless',
templateUrl: './passwordless.component.html',
styleUrls: ['./passwordless.component.scss'],
})
export class PasswordlessComponent implements OnInit, OnDestroy {
@Input() private user!: User.AsObject;
@Input() public user!: User.AsObject;
public displayedColumns: string[] = ['name', 'state', 'actions'];
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@@ -1,6 +1,8 @@
<div class="max-width-container" *ngIf="error">{{error}}</div>
<div *ngIf="error" class="max-width-container">
<p>{{error}}</p>
</div>
<app-meta-layout *ngIf="user && (['user.write$','user.write:' + user.id] | hasRole) as canWrite$">
<cnsl-meta-layout *ngIf="user && (['user.write$','user.write:' + user.id] | hasRole) as canWrite$">
<div class="max-width-container">
<div class="head">
<a (click)="navigateBack()" mat-icon-button>
@@ -12,7 +14,7 @@
</div>
<span class="fill-space"></span>
<ng-template appHasRole [appHasRole]="['user.write$', 'user.write:'+user?.id]">
<ng-template cnslHasRole [hasRole]="['user.write$', 'user.write:'+user?.id]">
<button class="actions-trigger" mat-raised-button color="primary" [matMenuTriggerFor]="actions">
<span>{{'ACTIONS.ACTIONS' | translate}}</span>
<mat-icon class="icon">keyboard_arrow_down</mat-icon>
@@ -24,9 +26,9 @@
<button mat-menu-item *ngIf="user?.state !== UserState.USER_STATE_INACTIVE"
(click)="changeState(UserState.USER_STATE_INACTIVE)">{{'USER.PAGES.DEACTIVATE' |
translate}}</button>
<button mat-menu-item *ngIf="user?.state == UserState.USER_STATE_INACTIVE"
<button mat-menu-item *ngIf="user?.state === UserState.USER_STATE_INACTIVE"
(click)="changeState(UserState.USER_STATE_ACTIVE)">{{'USER.PAGES.REACTIVATE' | translate}}</button>
<ng-template appHasRole [appHasRole]="['user.delete$', 'user.delete:'+user?.id]">
<ng-template cnslHasRole [hasRole]="['user.delete$', 'user.delete:'+user?.id]">
<button mat-menu-item matTooltip="{{'USER.PAGES.DELETE' | translate}}" (click)="deleteUser()"><span
[style.color]="'var(--warn)'">{{'USER.PAGES.DELETE' | translate}}</span></button>
</ng-template>
@@ -36,67 +38,68 @@
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
<cnsl-info-section class="locked" *ngIf="user?.state === UserState.USER_STATE_LOCKED" type="WARN">
<cnsl-info-section class="locked" *ngIf="user?.state === UserState.USER_STATE_LOCKED" [type]="InfoSectionType.WARN">
{{'USER.PAGES.LOCKEDDESCRIPTION' | translate}}</cnsl-info-section>
<span *ngIf="!loading && !user">{{ 'USER.PAGES.NOUSER' | translate }}</span>
<cnsl-info-row *ngIf="user" [user]="user"></cnsl-info-row>
<ng-template appHasRole [appHasRole]="['user.read$', 'user.read:'+user?.id]">
<app-card *ngIf="user.human" title="{{ 'USER.PROFILE.TITLE' | translate }}">
<app-detail-form [preferredLoginName]="user.preferredLoginName" [disabled]="(canWrite$ | async) == false"
<ng-template cnslHasRole [hasRole]="['user.read$', 'user.read:'+user?.id]">
<cnsl-card *ngIf="user.human" title="{{ 'USER.PROFILE.TITLE' | translate }}">
<cnsl-detail-form [preferredLoginName]="user.preferredLoginName" [disabled]="(canWrite$ | async) === false"
[genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
(submitData)="saveProfile($event)">
</app-detail-form>
</app-card>
</cnsl-detail-form>
</cnsl-card>
<app-card *ngIf="user.human" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
<cnsl-card *ngIf="user.human" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
<button card-actions class="icon-button" mat-icon-button (click)="refreshUser()"
matTooltip="{{'ACTIONS.REFRESH' | translate}}">
<mat-icon class="icon">refresh</mat-icon>
</button>
<app-contact disablePhoneCode="true" [canWrite]="(['user.write:' + user?.id, 'user.write$'] | hasRole | async)"
*ngIf="user?.human" [human]="user.human" (editType)="openEditDialog($event)" (deletedPhone)="deletePhone()"
<cnsl-contact [disablePhoneCode]="true"
[canWrite]="(['user.write:' + user?.id, 'user.write$'] | hasRole | async)" *ngIf="user?.human"
[human]="user.human" (editType)="openEditDialog($event)" (deletedPhone)="deletePhone()"
(resendEmailVerification)="resendEmailVerification()" (resendPhoneVerification)="resendPhoneVerification()">
<button pwdAction [disabled]="(canWrite$ | async) == false" (click)="sendSetPasswordNotification()"
<button pwdAction [disabled]="(canWrite$ | async) === false" (click)="sendSetPasswordNotification()"
mat-stroked-button color="primary" *ngIf="user.state === UserState.USER_STATE_INITIAL">{{
'USER.PASSWORD.RESENDNOTIFICATION' | translate }}</button>
<button emailAction class="resendemail" *ngIf="user.state == UserState.USER_STATE_INITIAL" mat-stroked-button
<button emailAction class="resendemail" *ngIf="user.state === UserState.USER_STATE_INITIAL" mat-stroked-button
color="primary" (click)="resendInitEmail()">{{'USER.RESENDINITIALEMAIL' |
translate}}</button>
</app-contact>
</app-card>
</cnsl-contact>
</cnsl-card>
<app-external-idps *ngIf="user && user.human && user.id" [userId]="user.id" [service]="mgmtUserService">
</app-external-idps>
<cnsl-external-idps *ngIf="user && user.human && user.id" [userId]="user.id" [service]="mgmtUserService">
</cnsl-external-idps>
<app-card *ngIf="user.machine" title="{{ 'USER.MACHINE.TITLE' | translate }}">
<app-detail-form-machine [disabled]="(canWrite$ | async) == false" [username]="user.userName"
<cnsl-card *ngIf="user.machine" title="{{ 'USER.MACHINE.TITLE' | translate }}">
<cnsl-detail-form-machine [disabled]="(canWrite$ | async) === false" [username]="user.userName"
[user]="user.machine" (submitData)="saveMachine($event)">
</app-detail-form-machine>
</app-card>
</cnsl-detail-form-machine>
</cnsl-card>
<app-card *ngIf="user.machine && user.id" title="{{ 'USER.MACHINE.KEYSTITLE' | translate }}"
<cnsl-card *ngIf="user.machine && user.id" title="{{ 'USER.MACHINE.KEYSTITLE' | translate }}"
description="{{ 'USER.MACHINE.KEYSDESC' | translate }}">
<app-machine-keys [userId]="user.id"></app-machine-keys>
</app-card>
<cnsl-machine-keys [userId]="user.id"></cnsl-machine-keys>
</cnsl-card>
</ng-template>
<app-passwordless *ngIf="user && user.human" [user]="user"></app-passwordless>
<cnsl-passwordless *ngIf="user && user.human" [user]="user"></cnsl-passwordless>
<app-user-mfa *ngIf="user && user.human" [user]="user"></app-user-mfa>
<cnsl-user-mfa *ngIf="user && user.human" [user]="user"></cnsl-user-mfa>
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
<cnsl-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
description="{{'GRANTS.USER.DESCRIPTION' | translate }}">
<app-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
<cnsl-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
[displayedColumns]="['select', 'projectId', 'dates', 'roleNamesList']"
[disableWrite]="((['user.grant.write$'] | hasRole) | async) == false"
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) == false">
</app-user-grants>
</app-card>
[disableWrite]="((['user.grant.write$'] | hasRole) | async) === false"
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) === false">
</cnsl-user-grants>
</cnsl-card>
<ng-template appHasFeature [appHasFeature]="['metadata.user']">
<ng-template cnslHasFeature [hasFeature]="['metadata.user']">
<cnsl-metadata *ngIf="user" [userId]="user.id"></cnsl-metadata>
</ng-template>
</div>
@@ -119,18 +122,18 @@
</div>
</div>
<mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true">
<mat-tab-group mat-stretch-tabs class="tab-group" [disablePagination]="true">
<mat-tab label="Details">
<div class="side-padding">
<ng-template appHasRole [appHasRole]="['user.membership.read']">
<app-memberships [user]="user" [disabled]="(canWrite$ | async) == false"></app-memberships>
<ng-template cnslHasRole [hasRole]="['user.membership.read']">
<cnsl-memberships [user]="user" [disabled]="(canWrite$ | async) === false"></cnsl-memberships>
</ng-template>
</div>
</mat-tab>
<mat-tab label="{{ 'CHANGES.PROJECT.TITLE' | translate }}" class="meta-flex-col">
<app-changes class="changes" [refresh]="refreshChanges$" [changeType]="ChangeType.USER" [id]="user.id">
</app-changes>
<cnsl-changes class="changes" [refresh]="refreshChanges$" [changeType]="ChangeType.USER" [id]="user.id">
</cnsl-changes>
</mat-tab>
</mat-tab-group>
</div>
</app-meta-layout>
</cnsl-meta-layout>
@@ -5,6 +5,7 @@ import { ActivatedRoute, Params, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { take } from 'rxjs/operators';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { SendHumanResetPasswordNotificationRequest, UnlockUserRequest } from 'src/app/proto/generated/zitadel/management_pb';
@@ -17,7 +18,7 @@ import { EditDialogComponent, EditDialogType } from '../auth-user-detail/edit-di
import { ResendEmailDialogComponent } from '../auth-user-detail/resend-email-dialog/resend-email-dialog.component';
@Component({
selector: 'app-user-detail',
selector: 'cnsl-user-detail',
templateUrl: './user-detail.component.html',
styleUrls: ['./user-detail.component.scss'],
})
@@ -36,6 +37,7 @@ export class UserDetailComponent implements OnInit {
public EditDialogType: any = EditDialogType;
public refreshChanges$: EventEmitter<void> = new EventEmitter();
public InfoSectionType: any = InfoSectionType;
public error: string = '';
@@ -1,8 +1,8 @@
<app-card title="{{'USER.MFA.TITLE' | translate}}" description="{{'USER.MFA.DESCRIPTION' | translate}}">
<cnsl-card title="{{'USER.MFA.TITLE' | translate}}" description="{{'USER.MFA.DESCRIPTION' | translate}}">
<button card-actions mat-icon-button (click)="getMFAs()" class="icon-button" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
<mat-icon class="icon">refresh</mat-icon>
</button>
<app-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getMFAs()" [dataSize]="dataSource?.data?.length">
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getMFAs()" [dataSize]="dataSource?.data?.length ?? 0">
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLETYPE' | translate }} </th>
@@ -44,10 +44,10 @@
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;">
</tr>
</table>
</app-refresh-table>
</cnsl-refresh-table>
<div class="table-wrapper">
<div class="spinner-container" *ngIf="loading$ | async">
<mat-spinner diameter="50"></mat-spinner>
</div>
</div>
</app-card>
</cnsl-card>
@@ -10,88 +10,88 @@ import { ToastService } from 'src/app/services/toast.service';
export interface MFAItem {
name: string;
verified: boolean;
name: string;
verified: boolean;
}
@Component({
selector: 'app-user-mfa',
templateUrl: './user-mfa.component.html',
styleUrls: ['./user-mfa.component.scss'],
selector: 'cnsl-user-mfa',
templateUrl: './user-mfa.component.html',
styleUrls: ['./user-mfa.component.scss'],
})
export class UserMfaComponent implements OnInit, OnDestroy {
public displayedColumns: string[] = ['type', 'name', 'state', 'actions'];
@Input() private user!: User.AsObject;
public mfaSubject: BehaviorSubject<AuthFactor.AsObject[]> = new BehaviorSubject<AuthFactor.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
public displayedColumns: string[] = ['type', 'name', 'state', 'actions'];
@Input() public user!: User.AsObject;
public mfaSubject: BehaviorSubject<AuthFactor.AsObject[]> = new BehaviorSubject<AuthFactor.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>;
@ViewChild(MatSort) public sort!: MatSort;
public dataSource!: MatTableDataSource<AuthFactor.AsObject>;
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>;
@ViewChild(MatSort) public sort!: MatSort;
public dataSource!: MatTableDataSource<AuthFactor.AsObject>;
public AuthFactorState: any = AuthFactorState;
public AuthFactorState: any = AuthFactorState;
public error: string = '';
constructor(private mgmtUserService: ManagementService, private dialog: MatDialog, private toast: ToastService) { }
public error: string = '';
constructor(private mgmtUserService: ManagementService, private dialog: MatDialog, private toast: ToastService) { }
public ngOnInit(): void {
this.getMFAs();
}
public ngOnInit(): void {
this.getMFAs();
}
public ngOnDestroy(): void {
this.mfaSubject.complete();
this.loadingSubject.complete();
}
public ngOnDestroy(): void {
this.mfaSubject.complete();
this.loadingSubject.complete();
}
public getMFAs(): void {
this.mgmtUserService.listHumanMultiFactors(this.user.id).then(mfas => {
this.dataSource = new MatTableDataSource(mfas.resultList);
this.dataSource.sort = this.sort;
}).catch(error => {
this.error = error.message;
});
}
public getMFAs(): void {
this.mgmtUserService.listHumanMultiFactors(this.user.id).then(mfas => {
this.dataSource = new MatTableDataSource(mfas.resultList);
this.dataSource.sort = this.sort;
}).catch(error => {
this.error = error.message;
});
}
public deleteMFA(factor: AuthFactor.AsObject): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.MFA.DIALOG.MFA_DELETE_TITLE',
descriptionKey: 'USER.MFA.DIALOG.MFA_DELETE_DESCRIPTION',
},
width: '400px',
});
public deleteMFA(factor: AuthFactor.AsObject): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.MFA.DIALOG.MFA_DELETE_TITLE',
descriptionKey: 'USER.MFA.DIALOG.MFA_DELETE_DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
if (factor.otp) {
this.mgmtUserService.removeHumanMultiFactorOTP(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.OTPREMOVED', true);
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
if (factor.otp) {
this.mgmtUserService.removeHumanMultiFactorOTP(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.OTPREMOVED', true);
const index = this.dataSource.data.findIndex(mfa => !!mfa.otp);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
this.getMFAs();
}).catch(error => {
this.toast.showError(error);
});
} else if (factor.u2f) {
this.mgmtUserService.removeHumanAuthFactorU2F(this.user.id, factor.u2f.id).then(() => {
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
const index = this.dataSource.data.findIndex(mfa => !!mfa.u2f);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
this.getMFAs();
}).catch(error => {
this.toast.showError(error);
});
}
const index = this.dataSource.data.findIndex(mfa => !!mfa.otp);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
});
}
this.getMFAs();
}).catch(error => {
this.toast.showError(error);
});
} else if (factor.u2f) {
this.mgmtUserService.removeHumanAuthFactorU2F(this.user.id, factor.u2f.id).then(() => {
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
const index = this.dataSource.data.findIndex(mfa => !!mfa.u2f);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
this.getMFAs();
}).catch(error => {
this.toast.showError(error);
});
}
}
});
}
}
@@ -3,17 +3,17 @@
<h1>{{ 'USER.PAGES.LIST' | translate }}</h1>
<p class="sub">{{ 'USER.PAGES.DESCRIPTION' | translate }}</p>
<app-user-table [type]="Type.TYPE_HUMAN" [disabled]="(['user.write$'] | hasRole | async) == false">
</app-user-table>
<cnsl-user-table [type]="Type.TYPE_HUMAN" [disabled]="(['user.write$'] | hasRole | async) === false">
</cnsl-user-table>
</ng-container>
<ng-container *ngSwitchCase="Type.TYPE_MACHINE">
<h1>{{ 'USER.PAGES.LISTMACHINE' | translate }}</h1>
<p class="sub">{{ 'USER.PAGES.DESCRIPTIONMACHINE' | translate }}</p>
<app-user-table [type]="Type.TYPE_MACHINE"
<cnsl-user-table [type]="Type.TYPE_MACHINE"
[displayedColumns]="['select','name', 'username', 'description','state', 'actions']"
[disabled]="(['user.write$'] | hasRole | async) == false">
</app-user-table>
[disabled]="(['user.write$'] | hasRole | async) === false">
</cnsl-user-table>
</ng-container>
</div>
@@ -5,18 +5,18 @@ import { take } from 'rxjs/operators';
import { Type } from 'src/app/proto/generated/zitadel/user_pb';
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.scss'],
selector: 'cnsl-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.scss'],
})
export class UserListComponent {
public Type: any = Type;
public type: Type = Type.TYPE_HUMAN;
public Type: any = Type;
public type: Type = Type.TYPE_HUMAN;
constructor(public translate: TranslateService, activatedRoute: ActivatedRoute) {
activatedRoute.data.pipe(take(1)).subscribe(params => {
const { type } = params;
this.type = type;
});
}
constructor(public translate: TranslateService, activatedRoute: ActivatedRoute) {
activatedRoute.data.pipe(take(1)).subscribe(params => {
const { type } = params;
this.type = type;
});
}
}
@@ -1,11 +1,11 @@
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="totalResult"
<cnsl-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="totalResult"
[timestamp]="viewTimestamp" [selection]="selection" [emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes">
<cnsl-form-field @appearfade *ngIf="userSearchKey != undefined" actions class="filtername">
<cnsl-form-field @appearfade *ngIf="userSearchKey !== undefined" actions class="filtername">
<input class="filterinput" cnslInput (keyup)="applyFilter($event)"
[placeholder]="('USER.TABLE.FILTER.' + userSearchKey.toString()) | translate" #input>
</cnsl-form-field>
<ng-template appHasRole [appHasRole]="['user.write']" actions>
<ng-template cnslHasRole [hasRole]="['user.write']" actions>
<button (click)="deactivateSelectedUsers()" matTooltip="{{'USER.TABLE.DEACTIVATE' | translate}}"
class="icon-button" mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
<mat-icon svgIcon="mdi_account_cancel"></mat-icon>
@@ -14,7 +14,7 @@
class="icon-button" mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
<mat-icon svgIcon="mdi_account_check_outline"></mat-icon>
</button>
<a [routerLink]="[ '/users',type == Type.TYPE_HUMAN ? 'create' : 'create-machine']" color="primary"
<a [routerLink]="[ '/users',type === Type.TYPE_HUMAN ? 'create' : 'create-machine']" color="primary"
mat-raised-button [disabled]="disabled">
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a>
@@ -33,10 +33,10 @@
<mat-checkbox [disabled]="disabled" color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(user) : null" [checked]="selection.isSelected(user)">
<app-avatar
<cnsl-avatar
*ngIf="user.human && user.human.profile.displayName && user.human?.profile.firstName && user.human?.profile.lastName; else cog"
class="avatar" [name]="user.human.profile.displayName" [avatarUrl]="user.human?.profile?.avatarUrl || ''" [forColor]="user?.preferredLoginName" [size]="32">
</app-avatar>
</cnsl-avatar>
<ng-template #cog>
<div class="sa-icon" *ngIf="user.machine">
<i class="las la-user-cog"></i>
@@ -48,7 +48,7 @@
<ng-container matColumnDef="displayName">
<th mat-header-cell *matHeaderCellDef
[ngClass]="{'search-active': this.userSearchKey == UserListSearchKey.DISPLAY_NAME}">
[ngClass]="{'search-active': this.userSearchKey === UserListSearchKey.DISPLAY_NAME}">
{{ 'USER.PROFILE.DISPLAYNAME' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserListSearchKey.DISPLAY_NAME}"></template>
@@ -78,7 +78,7 @@
<ng-container matColumnDef="username">
<th mat-header-cell *matHeaderCellDef
[ngClass]="{'search-active': this.userSearchKey == UserListSearchKey.USER_NAME}">
[ngClass]="{'search-active': this.userSearchKey === UserListSearchKey.USER_NAME}">
{{ 'USER.PROFILE.USERNAME' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserListSearchKey.USER_NAME}"></template>
@@ -89,7 +89,7 @@
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef
[ngClass]="{'search-active': this.UserListSearchKey == UserListSearchKey.EMAIL}">
[ngClass]="{'search-active': this.UserListSearchKey === UserListSearchKey.EMAIL}">
{{ 'USER.EMAIL' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserListSearchKey.EMAIL}"></template>
@@ -113,7 +113,7 @@
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let user">
<button class="dlt-button"
[disabled]="(['user.delete$', 'user.delete:'+user.id] | hasRole | async) == false" color="warn"
[disabled]="(['user.delete$', 'user.delete:'+user.id] | hasRole | async) === false" color="warn"
mat-icon-button matTooltip="{{'USER.PAGES.DELETE' | translate}}" (click)="deleteUser(user)">
<i class="las la-trash"></i>
</button>
@@ -124,18 +124,18 @@
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"> </tr>
</table>
<div *ngIf="(loading$ | async) == false && !dataSource?.data?.length" class="no-content-row">
<div *ngIf="(loading$ | async) === false && !dataSource?.data?.length" class="no-content-row">
<i class="las la-exclamation"></i>
<span>{{'USER.TABLE.EMPTY' | translate}}</span>
</div>
<cnsl-paginator #paginator class="paginator" [timestamp]="viewTimestamp" [length]="totalResult || 0" [pageSize]="10" [timestamp]="viewTimestamp"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></cnsl-paginator>
</div>
</app-refresh-table>
</cnsl-refresh-table>
<ng-template #templateRef let-key="key">
<button class="search-button" mat-icon-button (click)="setFilter(key)">
<mat-icon class="icon" *ngIf="this.userSearchKey != key">search</mat-icon>
<mat-icon class="icon" *ngIf="this.userSearchKey == key">search_off</mat-icon>
<mat-icon class="icon" *ngIf="this.userSearchKey !== key">search</mat-icon>
<mat-icon class="icon" *ngIf="this.userSearchKey === key">search_off</mat-icon>
</button>
</ng-template>
@@ -35,7 +35,7 @@ enum UserListSearchKey {
}
@Component({
selector: 'app-user-table',
selector: 'cnsl-user-table',
templateUrl: './user-table.component.html',
styleUrls: ['./user-table.component.scss'],
animations: [