feat: protos refactoring

* start with user

* user first try done in all services

* user, org, idp for discussion

* remove unused stuff

* bla

* dockerbuild

* rename search, get multiple to list...

* add annotation

* update proto dependencies

* update proto dependencies

* change proto imports

* replace all old imports

* fix go out

* remove unused lines

* correct protoc flags

* grpc and openapi flags

* go out source path relative

* -p

* remove dead code

* sourcepath relative

* ls

* is onenapi the problem?

* hobla

* authoption output

* wrong field name

* gopf

* correct option, add correct flags

* small improvments

* SIMPLYFY

* relative path

* gopf bin ich en tubel

* correct path

* default policies in admin

* grpc generation in one file

* remove non ascii

* metadata on manipulations

* correct auth_option import

* fixes

* larry

* idp provider to idp

* fix generate

* admin and auth nearly done

* admin and auth nearly done

* gen

* healthz

* imports

* deleted too much imports

* fix org

* add import

* imports

* import

* naming

* auth_opt

* gopf

* management

* imports

* _TYPE_UNSPECIFIED

* improts

* auth opts

* management policies

* imports

* passwordlessType to MFAType

* auth_opt

* add user grant calls

* add missing messages

* result

* fix option

* improvements

* ids

* fix http

* imports

* fixes

* fields

* body

* add fields

* remove wrong member query

* fix request response

* fixes

* add copy files

* variable versions

* generate all files

* improvements

* add dependencies

* factors

* user session

* oidc information, iam

* remove unused file

* changes

* enums

* dockerfile

* fix build

* remove unused folder

* update readme for build

* move old server impl

* add event type to change

* some changes

* start admin

* remove wrong field

* admin only list calls missing

* fix proto numbers

* surprisingly it compiles

* service ts changes

* admin mgmt

* mgmt

* auth manipulation and gets done, lists missing

* validations and some field changes

* validations

* enum validations

* remove todo

* move proto files to proto/zitadel

* change proto path in dockerfile

* it compiles!

* add validate import

* remove duplicate import

* fix protos

* fix import

* tests

* cleanup

* remove unimplemented methods

* iam member multiple queries

* all auth and admin calls

* add initial password on crate human

* message names

* management user server

* machine done

* fix: todos (#1346)

* fix: pub sub in new eventstore

* fix: todos

* fix: todos

* fix: todos

* fix: todos

* fix: todos

* fix tests

* fix: search method domain

* admin service, user import type typescript

* admin changes

* admin changes

* fix: search method domain

* more user grpc and begin org, fix configs

* fix: return object details

* org grpc

* remove creation date add details

* app

* fix: return object details

* fix: return object details

* mgmt service, project members

* app

* fix: convert policies

* project, members, granted projects, searches

* fix: convert usergrants

* fix: convert usergrants

* auth user detail, user detail, mfa, second factor, auth

* fix: convert usergrants

* mfa, memberships, password, owned proj detail

* fix: convert usergrants

* project grant

* missing details

* changes, userview

* idp table, keys

* org list and user table filter

* unify rest paths (#1381)

* unify rest paths

* post for all searches,
mfa to multi_factor,
secondfactor to second_factor

* remove v1

* fix tests

* rename api client key to app key

* machine keys, age policy

* user list, machine keys, changes

* fix: org states

* add default flag to policy

* second factor to type

* idp id

* app type

* unify ListQuery, ListDetails, ObjectDetails field names

* user grants, apps, memberships

* fix type params

* metadata to detail, linke idps

* api create, membership, app detail, create

* idp, app, policy

* queries, multi -> auth factors and missing fields

* update converters

* provider to user, remove old mgmt refs

* temp remove authfactor dialog, build finish

Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com>
This commit is contained in:
Silvan
2021-03-09 10:30:11 +01:00
committed by GitHub
parent 9f417f3957
commit dabd5920dc
372 changed files with 17881 additions and 22036 deletions

View File

@@ -2,36 +2,17 @@ import { Component, OnDestroy } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { CreateMachineRequest } from 'src/app/proto/generated/admin_pb';
import { UserResponse } from 'src/app/proto/generated/management_pb';
import { AddMachineUserRequest } from 'src/app/proto/generated/zitadel/management_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
function noEmailValidator(c: AbstractControl): any {
const EMAIL_REGEXP: RegExp = /^((?!@).)*$/gm;
if (!c.parent || !c) {
return;
}
const username = c.parent.get('userName');
if (!username) {
return;
}
return EMAIL_REGEXP.test(username.value) ? null : {
noEmailValidator: {
valid: false,
},
};
}
@Component({
selector: 'app-user-create-machine',
templateUrl: './user-create-machine.component.html',
styleUrls: ['./user-create-machine.component.scss'],
})
export class UserCreateMachineComponent implements OnDestroy {
public user: CreateMachineRequest.AsObject = new CreateMachineRequest().toObject();
public user: AddMachineUserRequest.AsObject = new AddMachineUserRequest().toObject();
public userForm!: FormGroup;
private sub: Subscription = new Subscription();
@@ -64,16 +45,17 @@ export class UserCreateMachineComponent implements OnDestroy {
this.loading = true;
const machineReq = new CreateMachineRequest();
const machineReq = new AddMachineUserRequest();
machineReq.setDescription(this.description?.value);
machineReq.setName(this.name?.value);
machineReq.setUserName(this.userName?.value);
this.userService
.CreateUserMachine(this.userName?.value, machineReq)
.then((data: UserResponse) => {
.addMachineUser(machineReq)
.then((data) => {
this.loading = false;
this.toast.showInfo('USER.TOAST.CREATED', true);
const id = data.getId();
const id = data.userId;
if (id) {
this.router.navigate(['users', id]);
}

View File

@@ -2,13 +2,9 @@ import { ChangeDetectorRef, Component, OnDestroy, ViewChild } from '@angular/cor
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs';
import {
CreateHumanRequest,
CreateUserRequest,
Gender,
OrgDomain,
UserResponse,
} from 'src/app/proto/generated/management_pb';
import { AddHumanUserRequest } from 'src/app/proto/generated/zitadel/management_pb';
import { Domain } from 'src/app/proto/generated/zitadel/org_pb';
import { Gender } from 'src/app/proto/generated/zitadel/user_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -36,7 +32,7 @@ function noEmailValidator(c: AbstractControl): any {
styleUrls: ['./user-create.component.scss'],
})
export class UserCreateComponent implements OnDestroy {
public user: CreateUserRequest.AsObject = new CreateUserRequest().toObject();
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;
@@ -47,7 +43,7 @@ export class UserCreateComponent implements OnDestroy {
public loading: boolean = false;
@ViewChild('suffix') public suffix!: any;
private primaryDomain!: OrgDomain.AsObject;
private primaryDomain!: Domain.AsObject;
constructor(
private router: Router,
@@ -58,8 +54,10 @@ export class UserCreateComponent implements OnDestroy {
) {
this.loading = true;
this.loadOrg();
this.mgmtService.GetMyOrgIamPolicy().then((iampolicy) => {
this.userLoginMustBeDomain = iampolicy.toObject().userLoginMustBeDomain;
this.mgmtService.getOrgIAMPolicy().then((resp) => {
if (resp.policy?.userLoginMustBeDomain) {
this.userLoginMustBeDomain = resp.policy.userLoginMustBeDomain;
}
this.initForm();
this.loading = false;
this.envSuffixLabel = this.envSuffix();
@@ -74,8 +72,8 @@ export class UserCreateComponent implements OnDestroy {
}
private async loadOrg(): Promise<void> {
const domains = (await this.mgmtService.SearchMyOrgDomains().then(doms => doms.toObject()));
const found = domains.resultList.find(domain => domain.primary);
const domains = (await this.mgmtService.listOrgDomains());
const found = domains.resultList.find(resp => resp.isPrimary);
if (found) {
this.primaryDomain = found;
}
@@ -110,22 +108,26 @@ export class UserCreateComponent implements OnDestroy {
this.loading = true;
const humanReq = new CreateHumanRequest();
humanReq.setFirstName(this.firstName?.value);
humanReq.setLastName(this.lastName?.value);
humanReq.setNickName(this.nickName?.value);
humanReq.setPreferredLanguage(this.preferredLanguage?.value);
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(this.email?.value);
humanReq.setPhone(this.phone?.value);
humanReq.setGender(this.gender?.value);
humanReq.setCountry(this.country?.value);
this.mgmtService
.CreateUserHuman(this.userName?.value, humanReq)
.then((data: UserResponse) => {
.addHumanUser(humanReq)
.then((data) => {
this.loading = false;
this.toast.showInfo('USER.TOAST.CREATED', true);
this.router.navigate(['users', data.getId()]);
this.router.navigate(['users', data.userId]);
})
.catch(error => {
this.loading = false;
@@ -161,25 +163,10 @@ export class UserCreateComponent implements OnDestroy {
public get phone(): AbstractControl | null {
return this.userForm.get('phone');
}
public get streetAddress(): AbstractControl | null {
return this.userForm.get('streetAddress');
}
public get postalCode(): AbstractControl | null {
return this.userForm.get('postalCode');
}
public get locality(): AbstractControl | null {
return this.userForm.get('locality');
}
public get region(): AbstractControl | null {
return this.userForm.get('region');
}
public get country(): AbstractControl | null {
return this.userForm.get('country');
}
private envSuffix(): string {
if (this.userLoginMustBeDomain && this.primaryDomain?.domain) {
return `@${this.primaryDomain.domain}`;
if (this.userLoginMustBeDomain && this.primaryDomain?.domainName) {
return `@${this.primaryDomain.domainName}`;
} else {
return '';
}

View File

@@ -15,7 +15,7 @@
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.TABLESTATE' | translate }} </th>
<td mat-cell *matCellDef="let mfa"><span class="centered">
{{'USER.PASSWORDLESS.STATE.'+ mfa.state | translate}}
<i matTooltip="verified" *ngIf="mfa.state === MFAState.MFASTATE_READY"
<i matTooltip="verified" *ngIf="mfa.state === AuthFactorState.AUTH_FACTOR_STATE_READY"
class="verified las la-check-circle"></i>
</span>
</td>

View File

@@ -4,7 +4,7 @@ import { MatSort } from '@angular/material/sort';
import { MatTable, MatTableDataSource } from '@angular/material/table';
import { BehaviorSubject, Observable } from 'rxjs';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { MFAState, WebAuthNResponse, WebAuthNToken } from 'src/app/proto/generated/auth_pb';
import { AuthFactorState, WebAuthNToken } from 'src/app/proto/generated/zitadel/user_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -35,7 +35,7 @@ export class AuthPasswordlessComponent implements OnInit, OnDestroy {
@ViewChild(MatSort) public sort!: MatSort;
public dataSource!: MatTableDataSource<WebAuthNToken.AsObject>;
public MFAState: any = MFAState;
public AuthFactorState: any = AuthFactorState;
public error: string = '';
constructor(private service: GrpcAuthService,
@@ -51,45 +51,44 @@ export class AuthPasswordlessComponent implements OnInit, OnDestroy {
}
public addPasswordless(): void {
this.service.AddMyPasswordless().then((u2fresp) => {
const webauthn: WebAuthNResponse.AsObject = u2fresp.toObject();
const credOptions: CredentialCreationOptions = JSON.parse(atob(webauthn.publicKey as string));
this.service.addMyPasswordless().then((resp) => {
if (resp.key) {
const credOptions: CredentialCreationOptions = JSON.parse(atob(resp.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;
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;
});
}
const dialogRef = this.dialog.open(DialogU2FComponent, {
width: '400px',
data: {
credOptions,
type: U2FComponentDestination.PASSWORDLESS,
},
});
dialogRef.afterClosed().subscribe(done => {
if (done) {
this.getPasswordless();
} else {
this.getPasswordless();
}
});
}
console.log(credOptions);
const dialogRef = this.dialog.open(DialogU2FComponent, {
width: '400px',
data: {
credOptions,
type: U2FComponentDestination.PASSWORDLESS,
},
});
dialogRef.afterClosed().subscribe(done => {
if (done) {
this.getPasswordless();
} else {
this.getPasswordless();
}
});
}
}, error => {
this.toast.showError(error);
});
}
public getPasswordless(): void {
this.service.GetMyPasswordless().then(passwordless => {
this.dataSource = new MatTableDataSource(passwordless.toObject().tokensList);
this.service.listMyPasswordless().then(passwordless => {
this.dataSource = new MatTableDataSource(passwordless.resultList);
this.dataSource.sort = this.sort;
}).catch(error => {
this.error = error.message;
@@ -109,7 +108,7 @@ export class AuthPasswordlessComponent implements OnInit, OnDestroy {
dialogRef.afterClosed().subscribe(resp => {
if (resp && id) {
this.service.RemoveMyPasswordless(id).then(() => {
this.service.removeMyPasswordless(id).then(() => {
this.toast.showInfo('USER.TOAST.PASSWORDLESSREMOVED', true);
this.getPasswordless();
}).catch(error => {

View File

@@ -4,15 +4,7 @@ import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import {
Gender,
UserAddress,
UserEmail,
UserPhone,
UserProfile,
UserState,
UserView,
} from 'src/app/proto/generated/auth_pb';
import { Email, Gender, Phone, Profile, User, UserState } from 'src/app/proto/generated/zitadel/user_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -25,8 +17,7 @@ import { EditDialogComponent } from './edit-dialog/edit-dialog.component';
styleUrls: ['./auth-user-detail.component.scss'],
})
export class AuthUserDetailComponent implements OnDestroy {
public user!: UserView.AsObject;
public address: UserAddress.AsObject = { id: '' } as any;
public user!: User.AsObject;
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en'];
@@ -55,8 +46,10 @@ export class AuthUserDetailComponent implements OnDestroy {
refreshUser(): void {
this.refreshChanges$.emit();
this.userService.GetMyUser().then(user => {
this.user = user.toObject();
this.userService.getMyUser().then(resp => {
if (resp.user) {
this.user = resp.user;
}
this.loading = false;
}).catch(error => {
this.toast.showError(error);
@@ -68,26 +61,20 @@ export class AuthUserDetailComponent implements OnDestroy {
this.subscription.unsubscribe();
}
public saveProfile(profileData: UserProfile.AsObject): void {
public saveProfile(profileData: Profile.AsObject): void {
if (this.user.human) {
this.user.human.firstName = profileData.firstName;
this.user.human.lastName = profileData.lastName;
this.user.human.nickName = profileData.nickName;
this.user.human.displayName = profileData.displayName;
this.user.human.gender = profileData.gender;
this.user.human.preferredLanguage = profileData.preferredLanguage;
this.user.human.profile = profileData;
this.userService
.SaveMyUserProfile(
this.user.human.firstName,
this.user.human.lastName,
this.user.human.nickName,
this.user.human.preferredLanguage,
this.user.human.gender,
.updateMyProfile(
this.user.human.profile?.firstName,
this.user.human.profile?.lastName,
this.user.human.profile?.nickName,
this.user.human.profile?.preferredLanguage,
this.user.human.profile?.gender,
)
.then((data: UserProfile) => {
.then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true);
this.user = Object.assign(this.user, data.toObject());
this.refreshChanges$.emit();
})
.catch(error => {
@@ -98,10 +85,12 @@ export class AuthUserDetailComponent implements OnDestroy {
public saveEmail(email: string): void {
this.userService
.SaveMyUserEmail(email).then((data: UserEmail) => {
.setMyPhone(email).then(() => {
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
if (this.user.human) {
this.user.human.email = data.toObject().email;
const mailToSet = new Email();
mailToSet.setEmail(email);
this.user.human.email = mailToSet.toObject();
this.refreshUser();
}
}).catch(error => {
@@ -110,7 +99,7 @@ export class AuthUserDetailComponent implements OnDestroy {
}
public enteredPhoneCode(code: string): void {
this.userService.VerifyMyUserPhone(code).then(() => {
this.userService.verifyMyPhone(code).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
this.refreshUser();
}).catch(error => {
@@ -123,7 +112,7 @@ export class AuthUserDetailComponent implements OnDestroy {
}
public resendPhoneVerification(): void {
this.userService.ResendPhoneVerification().then(() => {
this.userService.resendMyPhoneVerification().then(() => {
this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
@@ -132,7 +121,7 @@ export class AuthUserDetailComponent implements OnDestroy {
}
public resendEmailVerification(): void {
this.userService.ResendMyEmailVerificationMail().then(() => {
this.userService.resendMyEmailVerification().then(() => {
this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
@@ -141,10 +130,11 @@ export class AuthUserDetailComponent implements OnDestroy {
}
public deletePhone(): void {
this.userService.RemoveMyUserPhone().then(() => {
this.userService.removeMyPhone().then(() => {
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
if (this.user.human) {
this.user.human.phone = '';
if (this.user.human?.phone) {
const phone = new Phone();
this.user.human.phone = phone.toObject();
this.refreshUser();
}
}).catch(error => {
@@ -155,10 +145,12 @@ export class AuthUserDetailComponent implements OnDestroy {
public savePhone(phone: string): void {
if (this.user.human) {
this.userService
.SaveMyUserPhone(phone).then((data: UserPhone) => {
.setMyPhone(phone).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
if (this.user.human) {
this.user.human.phone = data.toObject().phone;
const phoneToSet = new Phone();
phoneToSet.setPhone(phone);
this.user.human.phone = phoneToSet.toObject();
this.refreshUser();
}
}).catch(error => {

View File

@@ -18,7 +18,7 @@
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLESTATE' | translate }} </th>
<td mat-cell *matCellDef="let mfa"><span class="centered">
{{'USER.MFA.STATE.'+ mfa.state | translate}}
<i matTooltip="verified" *ngIf="mfa.state === MFAState.MFASTATE_READY"
<i matTooltip="verified" *ngIf="mfa.state === AuthFactorState.AUTH_FACTOR_STATE_READY"
class="verified las la-check-circle"></i>
</span>
</td>
@@ -28,7 +28,7 @@
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLEACTIONS' | translate }} </th>
<td mat-cell *matCellDef="let mfa">
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" mat-icon-button
(click)="deleteMFA(mfa.type, mfa.id)">
(click)="deleteMFA(mfa)">
<i class="las la-trash"></i>
</button>
</td>

View File

@@ -4,7 +4,7 @@ import { MatSort } from '@angular/material/sort';
import { MatTable, MatTableDataSource } from '@angular/material/table';
import { BehaviorSubject, Observable } from 'rxjs';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { MfaOtpResponse, MFAState, MfaType, MultiFactor, WebAuthNResponse } from 'src/app/proto/generated/auth_pb';
import { AuthFactor, AuthFactorState } from 'src/app/proto/generated/zitadel/user_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -32,19 +32,20 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ViewChild(MatTable) public table!: MatTable<MultiFactor.AsObject>;
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>;
@ViewChild(MatSort) public sort!: MatSort;
public dataSource!: MatTableDataSource<MultiFactor.AsObject>;
public dataSource!: MatTableDataSource<AuthFactor.AsObject>;
public MfaType: any = MfaType;
public MFAState: any = MFAState;
public AuthFactorState: any = AuthFactorState;
public error: string = '';
public otpAvailable: boolean = false;
constructor(private service: GrpcAuthService,
constructor(
private service: GrpcAuthService,
private toast: ToastService,
private dialog: MatDialog) { }
private dialog: MatDialog
) { }
public ngOnInit(): void {
this.getMFAs();
@@ -55,8 +56,8 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
}
public addOTP(): void {
this.service.AddMfaOTP().then((otpresp) => {
const otp: MfaOtpResponse.AsObject = otpresp.toObject();
this.service.addMyMultiFactorOTP().then((otpresp) => {
const otp = otpresp;
const dialogRef = this.dialog.open(DialogOtpComponent, {
data: otp.url,
width: '400px',
@@ -64,7 +65,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
dialogRef.afterClosed().subscribe((code) => {
if (code) {
this.service.VerifyMfaOTP(code).then(() => {
this.service.verifyMyMultiFactorOTP(code).then(() => {
this.getMFAs();
});
}
@@ -75,9 +76,8 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
}
public addU2F(): void {
this.service.AddMyMfaU2F().then((u2fresp) => {
const webauthn: WebAuthNResponse.AsObject = u2fresp.toObject();
const credOptions: CredentialCreationOptions = JSON.parse(atob(webauthn.publicKey as string));
this.service.addMyMultiFactorU2F().then((u2fresp) => {
const credOptions: CredentialCreationOptions = JSON.parse(atob(u2fresp.key?.publicKey as string));
if (credOptions.publicKey?.challenge) {
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
@@ -112,11 +112,12 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
}
public getMFAs(): void {
this.service.GetMyMfas().then(mfas => {
this.dataSource = new MatTableDataSource(mfas.toObject().mfasList);
this.service.listMyMultiFactors().then(mfas => {
const list = mfas.resultList;
this.dataSource = new MatTableDataSource(list);
this.dataSource.sort = this.sort;
const index = mfas.toObject().mfasList.findIndex(mfa => mfa.type === MfaType.MFATYPE_OTP);
const index = list.findIndex(mfa => mfa.otp);
if (index === -1) {
this.otpAvailable = true;
}
@@ -125,7 +126,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
});
}
public deleteMFA(type: MfaType, id?: string): void {
public deleteMFA(factor: AuthFactor.AsObject): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
@@ -138,11 +139,11 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
if (type === MfaType.MFATYPE_OTP) {
this.service.RemoveMfaOTP().then(() => {
if (factor.otp) {
this.service.removeMyMultiFactorOTP().then(() => {
this.toast.showInfo('USER.TOAST.OTPREMOVED', true);
const index = this.dataSource.data.findIndex(mfa => mfa.type === type);
const index = this.dataSource.data.findIndex(mfa => !!mfa.otp);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
@@ -150,19 +151,20 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
}).catch(error => {
this.toast.showError(error);
});
} else if (type === MfaType.MFATYPE_U2F && id) {
this.service.RemoveMyMfaU2F(id).then(() => {
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
} 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.type === type);
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);
});
}
}
});
}

View File

@@ -70,7 +70,7 @@ export class DialogU2FComponent {
const base64 = btoa(data);
if (this.type === U2FComponentDestination.MFA) {
this.service.VerifyMyMfaU2F(base64, this.name).then(() => {
this.service.verifyMyMultiFactorU2F(base64, this.name).then(() => {
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});

View File

@@ -19,9 +19,9 @@
<div class="left">
<span class="label">{{ 'USER.EMAIL' | translate }}</span>
<span class="name">{{human?.email}}</span>
<span *ngIf="human?.isEmailVerified" class="contact-state verified">{{'USER.EMAILVERIFIED' |
<span *ngIf="human?.profile.email.isEmailVerified" class="contact-state verified">{{'USER.EMAILVERIFIED' |
translate}}</span>
<div *ngIf="!human?.isEmailVerified" class="block">
<div *ngIf="!human?.profile.email.isEmailVerified" class="block">
<span class="contact-state notverified">{{'USER.NOTVERIFIED' | translate}}</span>
<ng-container *ngIf="human?.email">
@@ -46,9 +46,9 @@
<div class="left">
<span class="label">{{ 'USER.PHONE' | translate }}</span>
<span class="name">{{human?.phone ? human.phone : ('USER.PHONEEMPTY' | translate)}}</span>
<span *ngIf="human?.isPhoneVerified" class="contact-state verified">{{'USER.PHONEVERIFIED' |
<span *ngIf="human?.profile.phone.isPhoneVerified" class="contact-state verified">{{'USER.PHONEVERIFIED' |
translate}}</span>
<div *ngIf="!human?.isPhoneVerified" class="block">
<div *ngIf="!human?.profile.phone.isPhoneVerified" class="block">
<span class="contact-state notverified">{{'USER.NOTVERIFIED' | translate}}</span>
<ng-container *ngIf="human?.phone">

View File

@@ -1,8 +1,7 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { HumanView as AuthHumanView, UserState as AuthUserState } from 'src/app/proto/generated/auth_pb';
import { HumanView as MgmtHumanView, UserState as MgmtUserState } from 'src/app/proto/generated/management_pb';
import { Human, UserState } from 'src/app/proto/generated/zitadel/user_pb';
import { CodeDialogComponent } from '../auth-user-detail/code-dialog/code-dialog.component';
import { EditDialogType } from '../user-detail/user-detail.component';
@@ -15,8 +14,8 @@ import { EditDialogType } from '../user-detail/user-detail.component';
export class ContactComponent {
@Input() disablePhoneCode: boolean = false;
@Input() canWrite: boolean = false;
@Input() human!: AuthHumanView.AsObject | MgmtHumanView.AsObject;
@Input() state!: AuthUserState | MgmtUserState;
@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();

View File

@@ -1,8 +1,7 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Subscription } from 'rxjs';
import { UserView } from '../../../../proto/generated/management_pb';
import { User } from 'src/app/proto/generated/zitadel/user_pb';
@Component({
selector: 'app-detail-form-machine',
@@ -11,7 +10,7 @@ import { UserView } from '../../../../proto/generated/management_pb';
})
export class DetailFormMachineComponent implements OnInit, OnDestroy {
@Input() public username!: string;
@Input() public user!: UserView;
@Input() public user!: User;
@Input() public disabled: boolean = false;
@Output() public submitData: EventEmitter<any> = new EventEmitter<any>();

View File

@@ -1,8 +1,7 @@
import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Subscription } from 'rxjs';
import { Gender as authGender, UserProfile as authUP, UserView as authUV } from 'src/app/proto/generated/auth_pb';
import { Gender as mgmtGender, UserProfile as mgmtUP, UserView as mgmtUV } from 'src/app/proto/generated/management_pb';
import { Gender, User } from 'src/app/proto/generated/zitadel/user_pb';
@Component({
@@ -12,11 +11,11 @@ import { Gender as mgmtGender, UserProfile as mgmtUP, UserView as mgmtUV } from
})
export class DetailFormComponent implements OnDestroy, OnChanges {
@Input() public username!: string;
@Input() public user!: mgmtUV | authUV;
@Input() public user!: User;
@Input() public disabled: boolean = false;
@Input() public genders: mgmtGender[] | authGender[] = [];
@Input() public genders: Gender[] = [];
@Input() public languages: string[] = ['de', 'en'];
@Output() public submitData: EventEmitter<mgmtUP | authUP> = new EventEmitter<mgmtUP | authUP>();
@Output() public submitData: EventEmitter<User> = new EventEmitter<User>();
@Output() public changedLanguage: EventEmitter<string> = new EventEmitter<string>();
public profileForm!: FormGroup;

View File

@@ -1,5 +1,5 @@
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
[timestamp]="externalIdpResult?.viewTimestamp" [selection]="selection">
[timestamp]="viewTimestamp" [selection]="selection">
<div class="table-wrapper">
<table class="table" mat-table [dataSource]="dataSource">
@@ -52,7 +52,7 @@
</tr>
</table>
<mat-paginator #paginator class="paginator" [length]="externalIdpResult?.totalResult || 0" [pageSize]="10"
<mat-paginator #paginator class="paginator" [length]="totalResult || 0" [pageSize]="10"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></mat-paginator>
</div>

View File

@@ -3,14 +3,11 @@ import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { BehaviorSubject, Observable } from 'rxjs';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { IDPUserLink } from 'src/app/proto/generated/zitadel/idp_pb';
import { ExternalIDPView as AuthExternalIDPView } from '../../../../proto/generated/auth_pb';
import {
ExternalIDPSearchResponse,
ExternalIDPView as MgmtExternalIDPView,
} from '../../../../proto/generated/management_pb';
import { GrpcAuthService } from '../../../../services/grpc-auth.service';
import { ManagementService } from '../../../../services/mgmt.service';
import { ToastService } from '../../../../services/toast.service';
@@ -24,11 +21,12 @@ export class ExternalIdpsComponent implements OnInit {
@Input() service!: GrpcAuthService | ManagementService;
@Input() userId!: string;
@ViewChild(MatPaginator) public paginator!: MatPaginator;
public externalIdpResult!: ExternalIDPSearchResponse.AsObject;
public dataSource: MatTableDataSource<MgmtExternalIDPView.AsObject | AuthExternalIDPView.AsObject>
= new MatTableDataSource<MgmtExternalIDPView.AsObject | AuthExternalIDPView.AsObject>();
public selection: SelectionModel<MgmtExternalIDPView.AsObject | AuthExternalIDPView.AsObject>
= new SelectionModel<MgmtExternalIDPView.AsObject | AuthExternalIDPView.AsObject>(true, []);
public totalResult: number = 0;
public viewTimestamp!: Timestamp.AsObject;
public dataSource: MatTableDataSource<IDPUserLink.AsObject>
= new MatTableDataSource<IDPUserLink.AsObject>();
public selection: SelectionModel<IDPUserLink.AsObject>
= new SelectionModel<IDPUserLink.AsObject>(true, []);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@Input() public displayedColumns: string[] = ['idpConfigId', 'idpName', 'externalUserId', 'externalUserDisplayName', 'actions'];
@@ -60,15 +58,20 @@ export class ExternalIdpsComponent implements OnInit {
let promise;
if (this.service instanceof ManagementService) {
promise = (this.service as ManagementService).SearchUserExternalIDPs(limit, offset, this.userId);
promise = (this.service as ManagementService).listHumanLinkedIDPs(this.userId, limit, offset);
} else if (this.service instanceof GrpcAuthService) {
promise = (this.service as GrpcAuthService).SearchMyExternalIdps(limit, offset);
promise = (this.service as GrpcAuthService).listMyLinkedIDPs(limit, offset);
}
if (promise) {
promise.then(resp => {
this.externalIdpResult = resp.toObject();
this.dataSource.data = this.externalIdpResult.resultList;
this.dataSource.data = resp.resultList;
if (resp.details?.viewTimestamp) {
this.viewTimestamp = resp.details.viewTimestamp;
}
if (resp.details?.totalResult) {
this.totalResult = resp.details?.totalResult;
}
this.loadingSubject.next(false);
}).catch((error: any) => {
this.toast.showError(error);
@@ -81,7 +84,7 @@ export class ExternalIdpsComponent implements OnInit {
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
}
public removeExternalIdp(idp: AuthExternalIDPView.AsObject | MgmtExternalIDPView.AsObject): void {
public removeExternalIdp(idp: IDPUserLink.AsObject): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.REMOVE',
@@ -97,10 +100,10 @@ export class ExternalIdpsComponent implements OnInit {
let promise;
if (this.service instanceof ManagementService) {
promise = (this.service as ManagementService)
.RemoveExternalIDP(idp.externalUserId, idp.idpConfigId, idp.userId);
.removeHumanLinkedIDP(idp.providedUserId, idp.idpId, idp.userId);
} else if (this.service instanceof GrpcAuthService) {
promise = (this.service as GrpcAuthService)
.RemoveExternalIDP(idp.externalUserId, idp.idpConfigId);
.removeMyLinkedIDP(idp.providedUserId, idp.idpId);
}
if (promise) {

View File

@@ -2,14 +2,14 @@ import { DataSource } from '@angular/cdk/collections';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { UserMembershipView } from 'src/app/proto/generated/management_pb';
import { Membership } from 'src/app/proto/generated/zitadel/user_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
export class MembershipDetailDataSource extends DataSource<UserMembershipView.AsObject> {
export class MembershipDetailDataSource extends DataSource<Membership.AsObject> {
public totalResult: number = 0;
public viewTimestamp!: Timestamp.AsObject;
public membersSubject: BehaviorSubject<UserMembershipView.AsObject[]>
= new BehaviorSubject<UserMembershipView.AsObject[]>([]);
public membersSubject: BehaviorSubject<Membership.AsObject[]>
= new BehaviorSubject<Membership.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@@ -21,14 +21,13 @@ export class MembershipDetailDataSource extends DataSource<UserMembershipView.As
const offset = pageIndex * pageSize;
this.loadingSubject.next(true);
from(this.mgmtUserService.SearchUserMemberships(userId, pageSize, offset)).pipe(
from(this.mgmtUserService.listUserMemberships(userId, pageSize, offset)).pipe(
map(resp => {
const response = resp.toObject();
this.totalResult = response.totalResult;
if (response.viewTimestamp) {
this.viewTimestamp = response.viewTimestamp;
this.totalResult = resp.details?.totalResult || 0;
if (resp.details?.viewTimestamp) {
this.viewTimestamp = resp.details.viewTimestamp;
}
return response.resultList;
return resp.resultList;
}),
catchError(() => of([])),
finalize(() => this.loadingSubject.next(false)),
@@ -43,7 +42,7 @@ export class MembershipDetailDataSource extends DataSource<UserMembershipView.As
* the returned stream emits new items.
* @returns A stream of the items to be rendered.
*/
public connect(): Observable<UserMembershipView.AsObject[]> {
public connect(): Observable<Membership.AsObject[]> {
return this.membersSubject.asObservable();
}

View File

@@ -6,7 +6,7 @@ import { MatTable } from '@angular/material/table';
import { ActivatedRoute } from '@angular/router';
import { tap } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { UserMembershipSearchResponse, UserMembershipView, UserView } from 'src/app/proto/generated/management_pb';
import { Membership, User } from 'src/app/proto/generated/zitadel/user_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -19,13 +19,13 @@ import { MembershipDetailDataSource } from './membership-detail-datasource';
styleUrls: ['./membership-detail.component.scss'],
})
export class MembershipDetailComponent implements AfterViewInit {
public user!: UserView.AsObject;
public user!: User.AsObject;
@ViewChild(MatPaginator) public paginator!: MatPaginator;
@ViewChild(MatTable) public table!: MatTable<UserMembershipView.AsObject>;
@ViewChild(MatTable) public table!: MatTable<Membership.AsObject>;
public dataSource!: MembershipDetailDataSource;
public selection: SelectionModel<UserMembershipView.AsObject>
= new SelectionModel<UserMembershipView.AsObject>(true, []);
public selection: SelectionModel<Membership.AsObject>
= new SelectionModel<Membership.AsObject>(true, []);
public memberRoleOptions: string[] = [];
@@ -33,7 +33,7 @@ export class MembershipDetailComponent implements AfterViewInit {
public displayedColumns: string[] = ['select', 'memberType', 'displayName', 'creationDate', 'changeDate', 'roles'];
public loading: boolean = false;
public memberships!: UserMembershipSearchResponse.AsObject;
public memberships!: Membership.AsObject[];
constructor(
activatedRoute: ActivatedRoute,
@@ -45,14 +45,16 @@ export class MembershipDetailComponent implements AfterViewInit {
activatedRoute.params.subscribe(data => {
const { id } = data;
if (id) {
this.mgmtService.GetUserByID(id).then(user => {
this.user = user.toObject();
this.dataSource = new MembershipDetailDataSource(this.mgmtService);
this.dataSource.loadMemberships(
this.user.id,
0,
50,
);
this.mgmtService.getUserByID(id).then(resp => {
if (resp.user) {
this.user = resp.user;
this.dataSource = new MembershipDetailDataSource(this.mgmtService);
this.dataSource.loadMemberships(
this.user.id,
0,
50,
);
}
}).catch(err => {
console.error(err);
});
@@ -117,19 +119,19 @@ export class MembershipDetailComponent implements AfterViewInit {
}
public async loadManager(userId: string): Promise<void> {
this.mgmtService.SearchUserMemberships(userId, 100, 0, []).then(response => {
this.memberships = response.toObject();
this.mgmtService.listUserMemberships(userId, 100, 0, []).then(response => {
this.memberships = response.resultList;
this.loading = false;
});
}
public createIamMember(response: any): void {
const users: UserView.AsObject[] = response.users;
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
return this.adminService.AddIamMember(user.id, roles);
return this.adminService.addIAMMember(user.id, roles);
})).then(() => {
this.toast.showInfo('IAM.TOAST.MEMBERADDED', true);
setTimeout(() => {
@@ -142,12 +144,12 @@ export class MembershipDetailComponent implements AfterViewInit {
}
private createOrgMember(response: any): void {
const users: UserView.AsObject[] = response.users;
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
return this.mgmtService.AddMyOrgMember(user.id, roles);
return this.mgmtService.addOrgMember(user.id, roles);
})).then(() => {
this.toast.showInfo('ORG.TOAST.MEMBERADDED', true);
setTimeout(() => {
@@ -160,12 +162,12 @@ export class MembershipDetailComponent implements AfterViewInit {
}
private createGrantedProjectMember(response: any): void {
const users: UserView.AsObject[] = response.users;
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
users.forEach(user => {
return this.mgmtService.AddProjectGrantMember(
return this.mgmtService.addProjectGrantMember(
response.projectId,
response.grantId,
user.id,
@@ -183,12 +185,12 @@ export class MembershipDetailComponent implements AfterViewInit {
}
private createOwnedProjectMember(response: any): void {
const users: UserView.AsObject[] = response.users;
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
users.forEach(user => {
return this.mgmtService.AddProjectMember(response.projectId, user.id, roles)
return this.mgmtService.addProjectMember(response.projectId, user.id, roles)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
setTimeout(() => {

View File

@@ -1,21 +1,17 @@
<div class="membership-groups">
<span class="me-header">{{ 'USER.MEMBERSHIPS.TITLE' | translate }}</span>
<div class="people" *ngIf="memberships">
<div class="img-list" [@cardAnimation]="memberships.totalResult">
<div class="img-list" [@cardAnimation]="totalResult">
<mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
<ng-container *ngIf="memberships.totalResult < 8; else compact">
<ng-container *ngFor="let membership of memberships.resultList; index as i">
<ng-container *ngIf="totalResult < 8; 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(' ')}}"
[ngStyle]="{'z-index': 100 - i}">
<div class="membership-avatar"
[ngStyle]="{'background-color': getColor(membership.memberType)}">
<i *ngIf="membership.memberType == MemberType.MEMBERTYPE_ORGANISATION"
class="las la-archway"></i>
<i *ngIf="membership.memberType == MemberType.MEMBERTYPE_PROJECT"
class="icon las la-layer-group"></i>
<i *ngIf="membership.memberType == MemberType.MEMBERTYPE_PROJECT_GRANT"
class="icon las la-layer-group"></i>
<div class="membership-avatar" [ngStyle]="{'background-color': getColor(membership)}">
<i *ngIf="membership.orgId" class="las la-archway"></i>
<i *ngIf="membership.projectId && !membership.grantId" class="icon las la-layer-group"></i>
<i *ngIf="membership.projectId && membership.grantId" class="icon las la-layer-group"></i>
<span>{{membership.displayName}}</span>
</div>
@@ -25,7 +21,7 @@
<ng-template #compact>
<div class="avatar-circle" matTooltip="Click to show detail" (click)="navigateToObject()" role="button">
<div class="membership-avatar">
<span style="font-size: 16px;">{{memberships.totalResult}}</span>
<span style="font-size: 16px;">{{totalResult}}</span>
</div>
</div>
</ng-template>

View File

@@ -3,8 +3,8 @@ import { Component, Input, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { AuthServiceClient } from 'src/app/proto/generated/auth_grpc_web_pb';
import { MemberType, UserMembershipSearchResponse, UserView } from 'src/app/proto/generated/management_pb';
import { UserGrant } from 'src/app/proto/generated/zitadel/auth_pb';
import { Membership, User } from 'src/app/proto/generated/zitadel/user_pb';
import { AdminService } from 'src/app/services/admin.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service';
@@ -33,14 +33,13 @@ import { ToastService } from 'src/app/services/toast.service';
})
export class MembershipsComponent implements OnInit {
public loading: boolean = false;
public memberships!: UserMembershipSearchResponse.AsObject;
public memberships!: Membership.AsObject[] | UserGrant.AsObject[];
public totalResult: number = 0;
@Input() public auth: boolean = false;
@Input() public user!: UserView.AsObject;
@Input() public user!: User.AsObject;
@Input() public disabled: boolean = false;
public MemberType: any = MemberType;
constructor(
private authService: GrpcAuthService,
private mgmtService: ManagementService,
@@ -56,13 +55,14 @@ export class MembershipsComponent implements OnInit {
public async loadManager(userId: string): Promise<void> {
if (this.auth) {
this.authService.SearchUserMemberships(100, 0, []).then(response => {
this.memberships = response.toObject();
this.authService.listMyUserGrants(100, 0, []).then(resp => {
this.memberships = resp.resultList;
this.totalResult = resp.details?.totalResult || 0;
this.loading = false;
});
} else {
this.mgmtService.SearchUserMemberships(userId, 100, 0, []).then(response => {
this.memberships = response.toObject();
this.mgmtService.listUserMemberships(userId, 100, 0, []).then(resp => {
this.memberships = resp.resultList;
this.loading = false;
});
}
@@ -103,12 +103,12 @@ export class MembershipsComponent implements OnInit {
}
public createIamMember(response: any): void {
const users: UserView.AsObject[] = response.users;
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
return this.adminService.AddIamMember(user.id, roles);
return this.adminService.addIAMMember(user.id, roles);
})).then(() => {
this.toast.showInfo('IAM.TOAST.MEMBERADDED', true);
setTimeout(() => {
@@ -121,12 +121,12 @@ export class MembershipsComponent implements OnInit {
}
private createOrgMember(response: any): void {
const users: UserView.AsObject[] = response.users;
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
return this.mgmtService.AddMyOrgMember(user.id, roles);
return this.mgmtService.addOrgMember(user.id, roles);
})).then(() => {
this.toast.showInfo('ORG.TOAST.MEMBERADDED', true);
setTimeout(() => {
@@ -139,12 +139,12 @@ export class MembershipsComponent implements OnInit {
}
private createGrantedProjectMember(response: any): void {
const users: UserView.AsObject[] = response.users;
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
users.forEach(user => {
return this.mgmtService.AddProjectGrantMember(
return this.mgmtService.addProjectGrantMember(
response.projectId,
response.grantId,
user.id,
@@ -162,12 +162,12 @@ export class MembershipsComponent implements OnInit {
}
private createOwnedProjectMember(response: any): void {
const users: UserView.AsObject[] = response.users;
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
users.forEach(user => {
return this.mgmtService.AddProjectMember(response.projectId, user.id, roles)
return this.mgmtService.addProjectMember(response.projectId, user.id, roles)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
setTimeout(() => {
@@ -180,7 +180,7 @@ export class MembershipsComponent implements OnInit {
}
}
getColor(type: MemberType): string {
getColor(type: Membership.AsObject[] | UserGrant.AsObject[]): string {
const gen = type.toString();
const colors = [
'rgb(201, 115, 88)',

View File

@@ -3,7 +3,7 @@ import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/fo
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from 'src/app/pages/validators';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/auth_pb';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -50,8 +50,10 @@ export class PasswordComponent implements OnDestroy {
}
const validators: Validators[] = [Validators.required];
this.authService.GetMyPasswordComplexityPolicy().then(complexity => {
this.policy = complexity.toObject();
this.authService.getMyPasswordComplexityPolicy().then(resp => {
if (resp.policy) {
this.policy = resp.policy;
}
if (this.policy.minLength) {
validators.push(Validators.minLength(this.policy.minLength));
}
@@ -96,7 +98,7 @@ export class PasswordComponent implements OnDestroy {
public setInitialPassword(userId: string): void {
if (this.passwordForm.valid && this.password && this.password.value) {
this.mgmtUserService.SetInitialPassword(userId, this.password.value).then((data: any) => {
this.mgmtUserService.setHumanInitialPassword(userId, this.password.value).then((data: any) => {
this.toast.showInfo('USER.TOAST.INITIALPASSWORDSET', true);
window.history.back();
}).catch(error => {
@@ -109,7 +111,7 @@ export class PasswordComponent implements OnDestroy {
if (this.passwordForm.valid && this.currentPassword &&
this.currentPassword.value &&
this.newPassword && this.newPassword.value && this.newPassword.valid) {
this.authService.ChangeMyPassword(this.currentPassword.value, this.newPassword.value)
this.authService.updateMyPassword(this.currentPassword.value, this.newPassword.value)
.then((data: any) => {
this.toast.showInfo('USER.TOAST.PASSWORDCHANGED', true);
window.history.back();

View File

@@ -15,7 +15,7 @@
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.TABLESTATE' | translate }} </th>
<td mat-cell *matCellDef="let mfa"><span class="centered">
{{'USER.PASSWORDLESS.STATE.'+ mfa.state | translate}}
<i matTooltip="verified" *ngIf="mfa.state === MFAState.MFASTATE_READY"
<i matTooltip="verified" *ngIf="mfa.state === AuthFactorState.AUTH_FACTOR_STATE_READY"
class="verified las la-check-circle"></i>
</span>
</td>

View File

@@ -4,8 +4,7 @@ import { MatSort } from '@angular/material/sort';
import { MatTable, MatTableDataSource } from '@angular/material/table';
import { BehaviorSubject, Observable } from 'rxjs';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { MFAState, WebAuthNToken } from 'src/app/proto/generated/auth_pb';
import { UserView } from 'src/app/proto/generated/management_pb';
import { AuthFactorState, User, WebAuthNToken } from 'src/app/proto/generated/zitadel/user_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -25,7 +24,7 @@ export interface WebAuthNOptions {
styleUrls: ['./passwordless.component.scss'],
})
export class PasswordlessComponent implements OnInit, OnDestroy {
@Input() private user!: UserView.AsObject;
@Input() private user!: User.AsObject;
public displayedColumns: string[] = ['name', 'state', 'actions'];
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@@ -34,7 +33,7 @@ export class PasswordlessComponent implements OnInit, OnDestroy {
@ViewChild(MatSort) public sort!: MatSort;
public dataSource!: MatTableDataSource<WebAuthNToken.AsObject>;
public MFAState: any = MFAState;
public AuthFactorState: any = AuthFactorState;
public error: string = '';
constructor(private service: ManagementService,
@@ -50,8 +49,8 @@ export class PasswordlessComponent implements OnInit, OnDestroy {
}
public getPasswordless(): void {
this.service.GetPasswordless(this.user.id).then(passwordless => {
this.dataSource = new MatTableDataSource(passwordless.toObject().tokensList);
this.service.listHumanPasswordless(this.user.id).then(passwordless => {
this.dataSource = new MatTableDataSource(passwordless.resultList);
this.dataSource.sort = this.sort;
}).catch(error => {
this.error = error.message;
@@ -71,7 +70,7 @@ export class PasswordlessComponent implements OnInit, OnDestroy {
dialogRef.afterClosed().subscribe(resp => {
if (resp && id) {
this.service.RemovePasswordless(id, this.user.id).then(() => {
this.service.removeHumanPasswordless(id, this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.PASSWORDLESSREMOVED', true);
this.getPasswordless();
}).catch(error => {

View File

@@ -4,7 +4,7 @@
<a (click)="navigateBack()" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
<h1>{{user.human ? user.human?.displayName : user.machine?.name}}</h1>
<h1>{{user.human ? user.human?.profile?.displayName : user.machine?.name}}</h1>
<ng-template appHasRole [appHasRole]="['user.delete$', 'user.delete:'+user?.id]">
<button mat-icon-button color="warn" matTooltip="{{'USER.PAGES.DELETE' | translate}}"

View File

@@ -7,17 +7,8 @@ import { take } from 'rxjs/operators';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import {
Gender,
MachineResponse,
MachineView,
NotificationType,
UserEmail,
UserPhone,
UserProfile,
UserState,
UserView,
} from 'src/app/proto/generated/management_pb';
import { SendHumanResetPasswordNotificationRequest } from 'src/app/proto/generated/zitadel/management_pb';
import { Email, Gender, Machine, Phone, Profile, User, UserState } from 'src/app/proto/generated/zitadel/user_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -35,7 +26,7 @@ export enum EditDialogType {
styleUrls: ['./user-detail.component.scss'],
})
export class UserDetailComponent implements OnInit {
public user!: UserView.AsObject;
public user!: User.AsObject;
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en'];
@@ -63,8 +54,10 @@ export class UserDetailComponent implements OnInit {
this.refreshChanges$.emit();
this.route.params.pipe(take(1)).subscribe(params => {
const { id } = params;
this.mgmtUserService.GetUserByID(id).then(user => {
this.user = user.toObject();
this.mgmtUserService.getUserByID(id).then(resp => {
if (resp.user) {
this.user = resp.user;
}
}).catch(err => {
console.error(err);
});
@@ -76,15 +69,15 @@ export class UserDetailComponent implements OnInit {
}
public changeState(newState: UserState): void {
if (newState === UserState.USERSTATE_ACTIVE) {
this.mgmtUserService.ReactivateUser(this.user.id).then(() => {
if (newState === UserState.USER_STATE_ACTIVE) {
this.mgmtUserService.reactivateUser(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.REACTIVATED', true);
this.user.state = newState;
}).catch(error => {
this.toast.showError(error);
});
} else if (newState === UserState.USERSTATE_INACTIVE) {
this.mgmtUserService.DeactivateUser(this.user.id).then(() => {
} else if (newState === UserState.USER_STATE_INACTIVE) {
this.mgmtUserService.deactivateUser(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.DEACTIVATED', true);
this.user.state = newState;
}).catch(error => {
@@ -93,25 +86,19 @@ export class UserDetailComponent implements OnInit {
}
}
public saveProfile(profileData: UserProfile.AsObject): void {
public saveProfile(profileData: Profile.AsObject): void {
if (this.user.human) {
this.user.human.firstName = profileData.firstName;
this.user.human.lastName = profileData.lastName;
this.user.human.nickName = profileData.nickName;
this.user.human.displayName = profileData.displayName;
this.user.human.gender = profileData.gender;
this.user.human.preferredLanguage = profileData.preferredLanguage;
this.user.human.profile = profileData;
this.mgmtUserService
.SaveUserProfile(
.updateHumanProfile(
this.user.id,
this.user.human.firstName,
this.user.human.lastName,
this.user.human.nickName,
this.user.human.preferredLanguage,
this.user.human.gender)
.then((data: UserProfile) => {
this.user.human.profile.firstName,
this.user.human.profile.lastName,
this.user.human.profile.nickName,
this.user.human.profile.preferredLanguage,
this.user.human.profile.gender)
.then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true);
this.user = Object.assign(this.user, data.toObject());
this.refreshChanges$.emit();
})
.catch(error => {
@@ -120,18 +107,17 @@ export class UserDetailComponent implements OnInit {
}
}
public saveMachine(machineData: MachineView.AsObject): void {
public saveMachine(machineData: Machine.AsObject): void {
if (this.user.machine) {
this.user.machine.name = machineData.name;
this.user.machine.description = machineData.description;
this.mgmtUserService
.UpdateUserMachine(
.updateMachine(
this.user.id,
this.user.machine.description)
.then((data: MachineResponse) => {
.then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true);
this.user = Object.assign(this.user, data.toObject());
this.refreshChanges$.emit();
})
.catch(error => {
@@ -141,7 +127,7 @@ export class UserDetailComponent implements OnInit {
}
public resendEmailVerification(): void {
this.mgmtUserService.ResendEmailVerification(this.user.id).then(() => {
this.mgmtUserService.resendHumanEmailVerification(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
@@ -150,7 +136,7 @@ export class UserDetailComponent implements OnInit {
}
public resendPhoneVerification(): void {
this.mgmtUserService.ResendPhoneVerification(this.user.id).then(() => {
this.mgmtUserService.resendHumanPhoneVerification(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
@@ -159,10 +145,10 @@ export class UserDetailComponent implements OnInit {
}
public deletePhone(): void {
this.mgmtUserService.RemoveUserPhone(this.user.id).then(() => {
this.mgmtUserService.removeHumanPhone(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
if (this.user.human) {
this.user.human.phone = '';
this.user.human.phone = new Phone().setPhone('').toObject();
this.refreshUser();
}
}).catch(error => {
@@ -172,10 +158,10 @@ export class UserDetailComponent implements OnInit {
public saveEmail(email: string): void {
if (this.user.id && email) {
this.mgmtUserService.SaveUserEmail(this.user.id, email).then((data: UserEmail) => {
this.mgmtUserService.updateHumanEmail(this.user.id, email).then(() => {
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
if (this.user.human) {
this.user.human.email = data.toObject().email;
this.user.human.email = new Email().setEmail(email).toObject();
this.refreshUser();
}
}).catch(error => {
@@ -187,10 +173,10 @@ export class UserDetailComponent implements OnInit {
public savePhone(phone: string): void {
if (this.user.id && phone) {
this.mgmtUserService
.SaveUserPhone(this.user.id, phone).then((data: UserPhone) => {
.updateHumanPhone(this.user.id, phone).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
if (this.user.human) {
this.user.human.phone = data.toObject().phone;
this.user.human.phone = new Phone().setPhone(phone).toObject();
this.refreshUser();
}
}).catch(error => {
@@ -204,7 +190,7 @@ export class UserDetailComponent implements OnInit {
}
public sendSetPasswordNotification(): void {
this.mgmtUserService.SendSetPasswordNotification(this.user.id, NotificationType.NOTIFICATIONTYPE_EMAIL)
this.mgmtUserService.sendHumanResetPasswordNotification(this.user.id, SendHumanResetPasswordNotificationRequest.Type.TYPE_EMAIL)
.then(() => {
this.toast.showInfo('USER.TOAST.PASSWORDNOTIFICATIONSENT', true);
this.refreshChanges$.emit();
@@ -226,7 +212,7 @@ export class UserDetailComponent implements OnInit {
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
this.mgmtUserService.DeleteUser(this.user.id).then(() => {
this.mgmtUserService.removeUser(this.user.id).then(() => {
const params: Params = {
'deferredReload': true,
};
@@ -246,7 +232,7 @@ export class UserDetailComponent implements OnInit {
dialogRef.afterClosed().subscribe(resp => {
if (resp.send && this.user.id) {
this.mgmtUserService.ResendInitialMail(this.user.id, resp.email ?? '').then(() => {
this.mgmtUserService.resendHumanInitialization(this.user.id, resp.email ?? '').then(() => {
this.toast.showInfo('USER.TOAST.INITEMAILSENT', true);
this.refreshChanges$.emit();
}).catch(error => {

View File

@@ -19,8 +19,6 @@
<td mat-cell *matCellDef="let mfa">
<span class="centered">
{{'USER.MFA.STATE.'+ mfa.state | translate}}
<i matTooltip="verified" *ngIf="mfa.state === MFAState.MFASTATE_READY"
class="verified las la-check-circle"></i>
</span>
</td>
</ng-container>
@@ -29,7 +27,7 @@
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLEACTIONS' | translate }} </th>
<td mat-cell *matCellDef="let mfa">
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" mat-icon-button
(click)="deleteMFA(mfa.type, mfa.id)">
(click)="deleteMFA(mfa)">
<i class="las la-trash"></i>
</button>
</td>

View File

@@ -4,7 +4,7 @@ import { MatSort } from '@angular/material/sort';
import { MatTable, MatTableDataSource } from '@angular/material/table';
import { BehaviorSubject, Observable } from 'rxjs';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { MFAState, MfaType, UserMultiFactor, UserView } from 'src/app/proto/generated/management_pb';
import { AuthFactor, AuthFactorState, User } from 'src/app/proto/generated/zitadel/user_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -21,17 +21,16 @@ export interface MFAItem {
})
export class UserMfaComponent implements OnInit, OnDestroy {
public displayedColumns: string[] = ['type', 'attr', 'state', 'actions'];
@Input() private user!: UserView.AsObject;
public mfaSubject: BehaviorSubject<UserMultiFactor.AsObject[]> = new BehaviorSubject<UserMultiFactor.AsObject[]>([]);
@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();
@ViewChild(MatTable) public table!: MatTable<UserMultiFactor.AsObject>;
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>;
@ViewChild(MatSort) public sort!: MatSort;
public dataSource!: MatTableDataSource<UserMultiFactor.AsObject>;
public dataSource!: MatTableDataSource<AuthFactor.AsObject>;
public MfaType: any = MfaType;
public MFAState: any = MFAState;
public AuthFactorState: any = AuthFactorState;
public error: string = '';
constructor(private mgmtUserService: ManagementService, private dialog: MatDialog, private toast: ToastService) { }
@@ -46,15 +45,15 @@ export class UserMfaComponent implements OnInit, OnDestroy {
}
public getMFAs(): void {
this.mgmtUserService.getUserMfas(this.user.id).then(mfas => {
this.dataSource = new MatTableDataSource(mfas.toObject().mfasList);
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(type: MfaType, id?: string): void {
public deleteMFA(factor: AuthFactor.AsObject): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
@@ -67,11 +66,11 @@ export class UserMfaComponent implements OnInit, OnDestroy {
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
if (type === MfaType.MFATYPE_OTP) {
this.mgmtUserService.removeMfaOTP(this.user.id).then(() => {
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.type === type);
const index = this.dataSource.data.findIndex(mfa => !!mfa.otp);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
@@ -79,11 +78,11 @@ export class UserMfaComponent implements OnInit, OnDestroy {
}).catch(error => {
this.toast.showError(error);
});
} else if (type === MfaType.MFATYPE_U2F && id) {
this.mgmtUserService.RemoveMfaU2F(this.user.id, id).then(() => {
} else if (factor.u2f) {
this.mgmtUserService.removeHumanAuthFactorU2F(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
const index = this.dataSource.data.findIndex(mfa => mfa.type === type);
const index = this.dataSource.data.findIndex(mfa => !!mfa.u2f);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}

View File

@@ -1,7 +1,8 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { Type } from 'src/app/proto/generated/zitadel/user_pb';
import { UserListComponent, UserType } from './user-list.component';
import { UserListComponent } from './user-list.component';
const routes: Routes = [
@@ -10,7 +11,7 @@ const routes: Routes = [
component: UserListComponent,
data: {
animation: 'HomePage',
type: UserType.HUMAN,
type: Type.TYPE_HUMAN,
},
},
{
@@ -18,7 +19,7 @@ const routes: Routes = [
component: UserListComponent,
data: {
animation: 'HomePage',
type: UserType.MACHINE,
type: Type.TYPE_MACHINE,
},
},
];

View File

@@ -1,17 +1,17 @@
<div class="max-width-container" [ngSwitch]="type">
<ng-container *ngSwitchCase="UserType.HUMAN">
<ng-container *ngSwitchCase="Type.TYPE_HUMAN">
<h1>{{ 'USER.PAGES.LIST' | translate }}</h1>
<p class="sub">{{ 'USER.PAGES.DESCRIPTION' | translate }}</p>
<app-user-table [userType]="UserType.HUMAN" [disabled]="(['user.write$'] | hasRole | async) == false">
<app-user-table [type]="Type.TYPE_HUMAN" [disabled]="(['user.write$'] | hasRole | async) == false">
</app-user-table>
</ng-container>
<ng-container *ngSwitchCase="UserType.MACHINE">
<ng-container *ngSwitchCase="Type.TYPE_MACHINE">
<h1>{{ 'USER.PAGES.LISTMACHINE' | translate }}</h1>
<p class="sub">{{ 'USER.PAGES.DESCRIPTIONMACHINE' | translate }}</p>
<app-user-table [userType]="UserType.MACHINE"
<app-user-table [type]="Type.TYPE_MACHINE"
[displayedColumns]="['select','name', 'username', 'description','state', 'actions']"
[disabled]="(['user.write$'] | hasRole | async) == false">
</app-user-table>

View File

@@ -2,19 +2,17 @@ import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { take } from 'rxjs/operators';
import { Type } from 'src/app/proto/generated/zitadel/user_pb';
export enum UserType {
HUMAN = 'human',
MACHINE = 'machine',
}
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.scss'],
})
export class UserListComponent {
public UserType: any = UserType;
public type: UserType = UserType.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;

View File

@@ -1,6 +1,5 @@
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="userResult?.totalResult"
[timestamp]="userResult?.viewTimestamp" [selection]="selection"
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes">
<app-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">
<input cnslInput (keyup)="applyFilter($event)"
[placeholder]="('USER.TABLE.FILTER.' + userSearchKey.toString()) | translate" #input>
@@ -15,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',userType == UserType.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>
@@ -34,8 +33,8 @@
<mat-checkbox [disabled]="disabled" color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(user) : null" [checked]="selection.isSelected(user)">
<app-avatar
*ngIf="user[userType] && user[userType].displayName && user[userType]?.firstName && user[userType]?.lastName; else cog"
class="avatar" [name]="user[userType].displayName" [size]="32">
*ngIf="user[type] && user[type].displayName && user[type]?.firstName && user[type]?.lastName; else cog"
class="avatar" [name]="user[type].displayName" [size]="32">
</app-avatar>
<ng-template #cog>
<div class="sa-icon">
@@ -48,35 +47,35 @@
<ng-container matColumnDef="firstname">
<th mat-header-cell *matHeaderCellDef
[ngClass]="{'search-active': this.userSearchKey == UserSearchKey.USERSEARCHKEY_FIRST_NAME}">
[ngClass]="{'search-active': this.userSearchKey == UserListSearchKey.FIRST_NAME}">
{{ 'USER.PROFILE.FIRSTNAME' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserSearchKey.USERSEARCHKEY_FIRST_NAME}"></template>
[ngTemplateOutletContext]="{key: UserListSearchKey.FIRST_NAME}"></template>
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user[userType]?.firstName}} </td>
{{user[type]?.firstName}} </td>
</ng-container>
<ng-container matColumnDef="lastname">
<th mat-header-cell *matHeaderCellDef
[ngClass]="{'search-active': this.userSearchKey == UserSearchKey.USERSEARCHKEY_LAST_NAME}">
[ngClass]="{'search-active': this.userSearchKey == UserListSearchKey.LAST_NAME}">
{{ 'USER.PROFILE.LASTNAME' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserSearchKey.USERSEARCHKEY_LAST_NAME}"></template>
[ngTemplateOutletContext]="{key: UserListSearchKey.LAST_NAME}"></template>
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user[userType]?.lastName}} </td>
{{user[type]?.lastName}} </td>
</ng-container>
<ng-container matColumnDef="displayName">
<th mat-header-cell *matHeaderCellDef
[ngClass]="{'search-active': this.userSearchKey == UserSearchKey.USERSEARCHKEY_DISPLAY_NAME}">
[ngClass]="{'search-active': this.userSearchKey == UserListSearchKey.USERSEARCHKEY_DISPLAY_NAME}">
{{ 'USER.PROFILE.DISPLAYNAME' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserSearchKey.USERSEARCHKEY_DISPLAY_NAME}"></template>
[ngTemplateOutletContext]="{key: UserListSearchKey.USERSEARCHKEY_DISPLAY_NAME}"></template>
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user[userType]?.displayName}} </td>
{{user[type]?.displayName}} </td>
</ng-container>
<ng-container matColumnDef="name">
@@ -84,21 +83,21 @@
{{ 'USER.MACHINE.NAME' | translate }}
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user[userType]?.name}} </td>
{{user[type]?.name}} </td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MACHINE.DESCRIPTION' | translate }} </th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user[userType]?.description}} </td>
{{user[type]?.description}} </td>
</ng-container>
<ng-container matColumnDef="username">
<th mat-header-cell *matHeaderCellDef
[ngClass]="{'search-active': this.userSearchKey == UserSearchKey.USERSEARCHKEY_USER_NAME}">
[ngClass]="{'search-active': this.userSearchKey == UserListSearchKey.USER_NAME}">
{{ 'USER.PROFILE.USERNAME' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserSearchKey.USERSEARCHKEY_USER_NAME}"></template>
[ngTemplateOutletContext]="{key: UserListSearchKey.USER_NAME}"></template>
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user.userName}} </td>
@@ -106,20 +105,21 @@
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef
[ngClass]="{'search-active': this.userSearchKey == UserSearchKey.USERSEARCHKEY_EMAIL}">
[ngClass]="{'search-active': this.UserListSearchKey == UserListSearchKey.EMAIL}">
{{ 'USER.EMAIL' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserSearchKey.USERSEARCHKEY_EMAIL}"></template>
[ngTemplateOutletContext]="{key: UserListSearchKey.EMAIL}"></template>
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user[userType]?.email}} </td>
{{user[type]?.email}} </td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.DATA.STATE' | translate }} </th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
<span class="state"
[ngClass]="{'active': user.state === UserState.USERSTATE_ACTIVE, 'inactive': user.state === UserState.USERSTATE_INACTIVE}">{{ 'USER.DATA.STATE'+user.state | translate }}</span>
[ngClass]="{'active': user.state === UserState.USERSTATE_ACTIVE, 'inactive': user.state === UserState.USERSTATE_INACTIVE}">{{
'USER.DATA.STATE'+user.state | translate }}</span>
</td>
</ng-container>
@@ -142,7 +142,7 @@
<i class="las la-exclamation"></i>
<span>{{'USER.TABLE.EMPTY' | translate}}</span>
</div>
<mat-paginator #paginator class="paginator" [length]="userResult?.totalResult || 0" [pageSize]="10"
<mat-paginator #paginator class="paginator" [length]="totalResult || 0" [pageSize]="10"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></mat-paginator>
</div>
</app-refresh-table>

View File

@@ -9,18 +9,30 @@ import { BehaviorSubject, Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { enterAnimations } from 'src/app/animations';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { UserView } from 'src/app/proto/generated/auth_pb';
import { Timestamp } from 'src/app/proto/generated/google/protobuf/timestamp_pb';
import { TextQueryMethod } from 'src/app/proto/generated/zitadel/object_pb';
import {
SearchMethod,
UserSearchKey,
UserSearchQuery,
UserSearchResponse,
DisplayNameQuery,
EmailQuery,
FirstNameQuery,
LastNameQuery,
SearchQuery,
Type,
TypeQuery,
User,
UserNameQuery,
UserState,
} from 'src/app/proto/generated/management_pb';
} from 'src/app/proto/generated/zitadel/user_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { UserType } from '../user-list.component';
enum UserListSearchKey {
FIRST_NAME,
LAST_NAME,
DISPLAY_NAME,
USER_NAME,
EMAIL,
}
@Component({
selector: 'app-user-table',
@@ -31,24 +43,26 @@ import { UserType } from '../user-list.component';
],
})
export class UserTableComponent implements OnInit {
public userSearchKey: UserSearchKey | undefined = undefined;
public UserType: any = UserType;
@Input() userType: UserType = UserType.HUMAN;
public userSearchKey: UserListSearchKey | undefined = undefined;
public Type: any = Type;
@Input() type: Type = Type.TYPE_HUMAN;
@Input() refreshOnPreviousRoutes: string[] = [];
@Input() disabled: boolean = false;
@ViewChild(MatPaginator) public paginator!: MatPaginator;
@ViewChild('input') public filter!: Input;
public dataSource: MatTableDataSource<UserView.AsObject> = new MatTableDataSource<UserView.AsObject>();
public selection: SelectionModel<UserView.AsObject> = new SelectionModel<UserView.AsObject>(true, []);
public userResult!: UserSearchResponse.AsObject;
public viewTimestamp!: Timestamp.AsObject;
public totalResult: number = 0;
public dataSource: MatTableDataSource<User.AsObject> = new MatTableDataSource<User.AsObject>();
public selection: SelectionModel<User.AsObject> = new SelectionModel<User.AsObject>(true, []);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@Input() public displayedColumns: string[] = ['select', 'displayName', 'username', 'email', 'state', 'actions'];
@Output() public changedSelection: EventEmitter<Array<UserView.AsObject>> = new EventEmitter();
UserSearchKey: any = UserSearchKey;
@Output() public changedSelection: EventEmitter<Array<User.AsObject>> = new EventEmitter();
public UserState: any = UserState;
public UserListSearchKey: any = UserListSearchKey;
constructor(
public translate: TranslateService,
@@ -64,10 +78,10 @@ export class UserTableComponent implements OnInit {
ngOnInit(): void {
this.route.queryParams.pipe(take(1)).subscribe(params => {
this.getData(10, 0, this.userType);
this.getData(10, 0, this.type);
if (params.deferredReload) {
setTimeout(() => {
this.getData(10, 0, this.userType);
this.getData(10, 0, this.type);
}, 2000);
}
});
@@ -87,12 +101,12 @@ export class UserTableComponent implements OnInit {
public changePage(event: PageEvent): void {
this.getData(event.pageSize, event.pageIndex * event.pageSize, this.userType);
this.getData(event.pageSize, event.pageIndex * event.pageSize, this.type);
}
public deactivateSelectedUsers(): void {
Promise.all(this.selection.selected.map(value => {
return this.userService.DeactivateUser(value.id);
return this.userService.deactivateUser(value.id);
})).then(() => {
this.toast.showInfo('USER.TOAST.SELECTEDDEACTIVATED', true);
this.selection.clear();
@@ -106,7 +120,7 @@ export class UserTableComponent implements OnInit {
public reactivateSelectedUsers(): void {
Promise.all(this.selection.selected.map(value => {
return this.userService.ReactivateUser(value.id);
return this.userService.reactivateUser(value.id);
})).then(() => {
this.toast.showInfo('USER.TOAST.SELECTEDREACTIVATED', true);
this.selection.clear();
@@ -118,24 +132,61 @@ export class UserTableComponent implements OnInit {
});
}
private async getData(limit: number, offset: number, filterTypeValue: UserType, filterName?: string): Promise<void> {
private async getData(limit: number, offset: number, type: Type, searchValue?: string): Promise<void> {
this.loadingSubject.next(true);
const query = new UserSearchQuery();
query.setKey(UserSearchKey.USERSEARCHKEY_TYPE);
query.setMethod(SearchMethod.SEARCHMETHOD_EQUALS);
query.setValue(filterTypeValue);
const query = new SearchQuery();
const typeQuery = new TypeQuery();
typeQuery.setType(type);
query.setTypeQuery(typeQuery);
let namequery;
if (filterName && this.userSearchKey !== undefined) {
namequery = new UserSearchQuery();
namequery.setMethod(SearchMethod.SEARCHMETHOD_CONTAINS_IGNORE_CASE);
namequery.setKey(this.userSearchKey);
namequery.setValue(filterName.toLowerCase());
if (searchValue && this.userSearchKey !== undefined) {
switch (this.userSearchKey) {
case UserListSearchKey.DISPLAY_NAME:
const dNQuery = new DisplayNameQuery();
dNQuery.setDisplayName(searchValue);
dNQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
query.setDisplayNameQuery(dNQuery);
break;
case UserListSearchKey.USER_NAME:
const uNQuery = new UserNameQuery();
uNQuery.setUserName(searchValue);
uNQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
query.setUserNameQuery(uNQuery);
break;
case UserListSearchKey.FIRST_NAME:
const fNQuery = new FirstNameQuery();
fNQuery.setFirstName(searchValue);
fNQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
query.setFirstNameQuery(fNQuery);
break;
case UserListSearchKey.FIRST_NAME:
const lNQuery = new LastNameQuery();
lNQuery.setLastName(searchValue);
lNQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
query.setLastNameQuery(lNQuery);
break;
case UserListSearchKey.EMAIL:
const eQuery = new EmailQuery();
eQuery.setEmailAddress(searchValue);
eQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
query.setEmailQuery(eQuery);
break;
}
}
this.userService.SearchUsers(limit, offset, namequery ? [query, namequery] : [query]).then(resp => {
this.userResult = resp.toObject();
this.dataSource.data = this.userResult.resultList;
this.userService.listUsers(limit, offset, [query]).then(resp => {
if (resp.details?.totalResult) {
this.totalResult = resp.details?.totalResult;
}
if (resp.details?.viewTimestamp) {
this.viewTimestamp = resp.details?.viewTimestamp;
}
this.dataSource.data = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
@@ -144,7 +195,7 @@ export class UserTableComponent implements OnInit {
}
public refreshPage(): void {
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize, this.userType);
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize, this.type);
}
public applyFilter(event: Event): void {
@@ -154,12 +205,12 @@ export class UserTableComponent implements OnInit {
this.getData(
this.paginator.pageSize,
this.paginator.pageIndex * this.paginator.pageSize,
this.userType,
this.type,
filterValue,
);
}
public setFilter(key: UserSearchKey): void {
public setFilter(key: UserListSearchKey): void {
setTimeout(() => {
if (this.filter) {
(this.filter as any).nativeElement.focus();
@@ -174,7 +225,7 @@ export class UserTableComponent implements OnInit {
}
}
public deleteUser(user: UserView.AsObject): void {
public deleteUser(user: User.AsObject): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
@@ -187,7 +238,7 @@ export class UserTableComponent implements OnInit {
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
this.userService.DeleteUser(user.id).then(() => {
this.userService.removeUser(user.id).then(() => {
setTimeout(() => {
this.refreshPage();
}, 1000);