fix: console v2 (#1454)

* some issues

* passwordless, mfa

* mfa, project fixes, login policy

* user table, auth service, interceptor
This commit is contained in:
Max Peintner 2021-03-23 09:50:58 +01:00 committed by GitHub
parent d5f0c2375a
commit 7768759906
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 261 additions and 157 deletions

View File

@ -97,9 +97,12 @@ export class MemberCreateDialogComponent {
} }
public selectProject(project: Project.AsObject | GrantedProject.AsObject | any): void { public selectProject(project: Project.AsObject | GrantedProject.AsObject | any): void {
this.projectId = project.projectId; if (project.projectId && project.grantId) {
if (project.id) { this.projectId = project.projectId;
this.grantId = project.id; this.grantId = project.grantId;
}
else if (project.id) {
this.projectId = project.id;
} }
} }

View File

@ -2,14 +2,15 @@
<div mat-dialog-content> <div mat-dialog-content>
<p class="desc">{{data.desc | translate}}</p> <p class="desc">{{data.desc | translate}}</p>
<!-- <cnsl-form-field class="form-field" label="Access Code" required="true"> <cnsl-form-field class="form-field" label="Access Code" required="true">
<cnsl-label>{{'MFA.TYPE' | translate}}</cnsl-label> <cnsl-label>{{'MFA.TYPE' | translate}}</cnsl-label>
<mat-select [(ngModel)]="newMfaType"> <mat-select [(ngModel)]="newMfaType">
<mat-option *ngFor="let mfa of []" [value]="mfa"> <mat-option *ngFor="let mfa of availableMfaTypes" [value]="mfa">
{{(data.componentType == LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.': LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}} {{(data.componentType == LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.':
LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}}
</mat-option> </mat-option>
</mat-select> </mat-select>
</cnsl-form-field> --> </cnsl-form-field>
</div> </div>
<div mat-dialog-actions class="action"> <div mat-dialog-actions class="action">
<button mat-button (click)="closeDialog()"><span>{{'ACTIONS.CLOSE' | translate}}</span></button> <button mat-button (click)="closeDialog()"><span>{{'ACTIONS.CLOSE' | translate}}</span></button>

View File

@ -1,5 +1,6 @@
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MultiFactorType, SecondFactorType } from 'src/app/proto/generated/zitadel/policy_pb';
enum LoginMethodComponentType { enum LoginMethodComponentType {
MultiFactor = 1, MultiFactor = 1,
@ -13,10 +14,11 @@ enum LoginMethodComponentType {
}) })
export class DialogAddTypeComponent { export class DialogAddTypeComponent {
public LoginMethodComponentType: any = LoginMethodComponentType; public LoginMethodComponentType: any = LoginMethodComponentType;
// public availableMfaTypes: Array<AdminMultiFactorType | MgmtMultiFactorType> = []; public availableMfaTypes: Array<MultiFactorType | SecondFactorType> = [];
public newMfaType!: MultiFactorType | SecondFactorType;
constructor(public dialogRef: MatDialogRef<DialogAddTypeComponent>, constructor(public dialogRef: MatDialogRef<DialogAddTypeComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { @Inject(MAT_DIALOG_DATA) public data: any) {
// this.availableMfaTypes = data.types; this.availableMfaTypes = data.types;
} }
public closeDialog(): void { public closeDialog(): void {
@ -24,6 +26,6 @@ export class DialogAddTypeComponent {
} }
public closeDialogWithCode(): void { public closeDialogWithCode(): void {
// this.dialogRef.close(this.newMfaType); this.dialogRef.close(this.newMfaType);
} }
} }

View File

@ -4,10 +4,14 @@ import { MatPaginator } from '@angular/material/paginator';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { import {
AddMultiFactorToLoginPolicyRequest as AdminAddMultiFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyRequest as AdminAddSecondFactorToLoginPolicyRequest,
RemoveMultiFactorFromLoginPolicyRequest as AdminRemoveMultiFactorFromLoginPolicyRequest, RemoveMultiFactorFromLoginPolicyRequest as AdminRemoveMultiFactorFromLoginPolicyRequest,
RemoveSecondFactorFromLoginPolicyRequest as AdminRemoveSecondFactorFromLoginPolicyRequest, RemoveSecondFactorFromLoginPolicyRequest as AdminRemoveSecondFactorFromLoginPolicyRequest,
} from 'src/app/proto/generated/zitadel/admin_pb'; } from 'src/app/proto/generated/zitadel/admin_pb';
import { import {
AddMultiFactorToLoginPolicyRequest as MgmtAddMultiFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyRequest as MgmtAddSecondFactorToLoginPolicyRequest,
RemoveMultiFactorFromLoginPolicyRequest as MgmtRemoveMultiFactorFromLoginPolicyRequest, RemoveMultiFactorFromLoginPolicyRequest as MgmtRemoveMultiFactorFromLoginPolicyRequest,
RemoveSecondFactorFromLoginPolicyRequest as MgmtRemoveSecondFactorFromLoginPolicyRequest, RemoveSecondFactorFromLoginPolicyRequest as MgmtRemoveSecondFactorFromLoginPolicyRequest,
} from 'src/app/proto/generated/zitadel/management_pb'; } from 'src/app/proto/generated/zitadel/management_pb';
@ -18,6 +22,7 @@ import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum'; import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component'; import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
import { DialogAddTypeComponent } from './dialog-add-type/dialog-add-type.component';
export enum LoginMethodComponentType { export enum LoginMethodComponentType {
MultiFactor = 1, MultiFactor = 1,
@ -116,57 +121,57 @@ export class MfaTableComponent implements OnInit {
} }
}); });
// const dialogRef = this.dialog.open(DialogAddTypeComponent, { const dialogRef = this.dialog.open(DialogAddTypeComponent, {
// data: { data: {
// title: 'MFA.CREATE.TITLE', title: 'MFA.CREATE.TITLE',
// desc: 'MFA.CREATE.DESCRIPTION', desc: 'MFA.CREATE.DESCRIPTION',
// componentType: this.componentType, componentType: this.componentType,
// types: selection, types: selection,
// }, },
// width: '400px', width: '400px',
// }); });
// dialogRef.afterClosed().subscribe((mfaType: ) => { dialogRef.afterClosed().subscribe((mfaType: MultiFactorType | SecondFactorType) => {
// if (mfaType) { if (mfaType) {
// if (this.serviceType === PolicyComponentServiceType.MGMT) { if (this.serviceType === PolicyComponentServiceType.MGMT) {
// if (this.componentType === LoginMethodComponentType.MultiFactor) { if (this.componentType === LoginMethodComponentType.MultiFactor) {
// const req = new MgmtAddMultiFactorToLoginPolicyRequest(); const req = new MgmtAddMultiFactorToLoginPolicyRequest();
// req.setType(mfaType as MultiFactorType); req.setType(mfaType as MultiFactorType);
// (this.service as ManagementService).addMultiFactorToLoginPolicy(req).then(() => { (this.service as ManagementService).addMultiFactorToLoginPolicy(req).then(() => {
// this.refreshPageAfterTimout(2000); this.refreshPageAfterTimout(2000);
// }).catch(error => { }).catch(error => {
// this.toast.showError(error); this.toast.showError(error);
// }); });
// } else if (this.componentType === LoginMethodComponentType.SecondFactor) { } else if (this.componentType === LoginMethodComponentType.SecondFactor) {
// const req = new MgmtAddSecondFactorToLoginPolicyRequest(); const req = new MgmtAddSecondFactorToLoginPolicyRequest();
// req.setType(mfaType as SecondFactorType); req.setType(mfaType as SecondFactorType);
// (this.service as ManagementService).addSecondFactorToLoginPolicy(req).then(() => { (this.service as ManagementService).addSecondFactorToLoginPolicy(req).then(() => {
// this.refreshPageAfterTimout(2000); this.refreshPageAfterTimout(2000);
// }).catch(error => { }).catch(error => {
// this.toast.showError(error); this.toast.showError(error);
// }); });
// } }
// } else if (this.serviceType === PolicyComponentServiceType.ADMIN) { } else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
// if (this.componentType === LoginMethodComponentType.MultiFactor) { if (this.componentType === LoginMethodComponentType.MultiFactor) {
// const req = new AdminAddMultiFactorToLoginPolicyRequest(); const req = new AdminAddMultiFactorToLoginPolicyRequest();
// req.setType(mfaType as MultiFactorType); req.setType(mfaType as MultiFactorType);
// (this.service as AdminService).addMultiFactorToLoginPolicy(req).then(() => { (this.service as AdminService).addMultiFactorToLoginPolicy(req).then(() => {
// this.refreshPageAfterTimout(2000); this.refreshPageAfterTimout(2000);
// }).catch(error => { }).catch(error => {
// this.toast.showError(error); this.toast.showError(error);
// }); });
// } else if (this.componentType === LoginMethodComponentType.SecondFactor) { } else if (this.componentType === LoginMethodComponentType.SecondFactor) {
// const req = new AdminAddSecondFactorToLoginPolicyRequest(); const req = new AdminAddSecondFactorToLoginPolicyRequest();
// req.setType(mfaType as SecondFactorType); req.setType(mfaType as SecondFactorType);
// (this.service as AdminService).addSecondFactorToLoginPolicy(req).then(() => { (this.service as AdminService).addSecondFactorToLoginPolicy(req).then(() => {
// this.refreshPageAfterTimout(2000); this.refreshPageAfterTimout(2000);
// }).catch(error => { }).catch(error => {
// this.toast.showError(error); this.toast.showError(error);
// }); });
// } }
// } }
// } }
// }); });
} }
private async getData(): Promise<void> { private async getData(): Promise<void> {

View File

@ -8,7 +8,7 @@
<mat-chip-list *ngIf="!singleOutput" #chipList aria-label="loginname selection"> <mat-chip-list *ngIf="!singleOutput" #chipList aria-label="loginname selection">
<mat-chip class="chip" *ngFor="let selecteduser of users" [selectable]="selectable" [removable]="removable" <mat-chip class="chip" *ngFor="let selecteduser of users" [selectable]="selectable" [removable]="removable"
(removed)="remove(selecteduser)"> (removed)="remove(selecteduser)">
{{ selecteduser?.human ? (selecteduser.human.firstName + ' ' + selecteduser.human.lastName) : {{ selecteduser?.human?.profile ? (selecteduser.human.profile.displayName) :
selecteduser?.machine?.name}} selecteduser?.machine?.name}}
| <small> | <small>
{{selecteduser.preferredLoginName}}</small> {{selecteduser.preferredLoginName}}</small>

View File

@ -62,6 +62,7 @@ export class SearchUserAutocompleteComponent implements OnInit, AfterContentChec
} else if (this.target === UserTarget.SELF) { } else if (this.target === UserTarget.SELF) {
this.getFilteredResults(); // new subscription this.getFilteredResults(); // new subscription
} }
console.log(this.users);
} }
public ngAfterContentChecked(): void { public ngAfterContentChecked(): void {

View File

@ -128,6 +128,7 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
catchError(() => of([])), catchError(() => of([])),
finalize(() => this.loadingSubject.next(false)), finalize(() => this.loadingSubject.next(false)),
).subscribe(grants => { ).subscribe(grants => {
console.log(grants);
this.grantsSubject.next(grants); this.grantsSubject.next(grants);
}); });
} }

View File

@ -67,13 +67,13 @@
<ng-container matColumnDef="dates"> <ng-container matColumnDef="dates">
<th mat-header-cell *matHeaderCellDef> DATES </th> <th mat-header-cell *matHeaderCellDef> DATES </th>
<td mat-cell *matCellDef="let grant"> <td mat-cell *matCellDef="let grant">
<div class="date-block"> <div class="date-block" *ngIf="grant.details?.creationDate">
<span class="date-sub">{{ 'PROJECT.GRANT.CREATIONDATE' | translate }}:</span> <span class="date-sub">{{ 'PROJECT.GRANT.CREATIONDATE' | translate }}:</span>
<span>{{grant.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span> <span>{{grant.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</div> </div>
<div class="date-block"> <div class="date-block" *ngIf="grant.details?.changeDate">
<span class="date-sub">{{ 'PROJECT.GRANT.CHANGEDATE' | translate }}</span> <span class="date-sub">{{ 'PROJECT.GRANT.CHANGEDATE' | translate }}</span>
<span>{{grant.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span> <span>{{grant.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</div> </div>
</td> </td>
</ng-container> </ng-container>
@ -86,14 +86,14 @@
</th> </th>
<td mat-cell *matCellDef="let grant; let i = index" class="role-data"> <td mat-cell *matCellDef="let grant; let i = index" class="role-data">
<ng-container <ng-container
*ngIf="(context === UserGrantContext.USER || context === UserGrantContext.NONE) && (grant.grantId && grantToEdit !== grant.id) || (grantToEdit !== grant.id)"> *ngIf="(context === UserGrantContext.USER || context === UserGrantContext.NONE) && (grant.grantId && grantToEdit !== grant.grantId) || (grantToEdit !== grant.grantId)">
<div class="flex-row"> <div class="flex-row">
<div class="role"> <div class="role">
<span *ngFor="let role of grant.roleKeysList">{{ role }}</span> <span *ngFor="let role of grant.roleKeysList">{{ role }}</span>
</div> </div>
<span class="fill-space"></span> <span class="fill-space"></span>
<button mat-stroked-button <button mat-stroked-button
*ngIf="grant.grantId ? grantToEdit !== grant.id : grantToEdit !== grant.id" *ngIf="grant.grantId ? grantToEdit !== grant.grantId : grantToEdit !== grant.grantId"
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + grant?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + grant?.grantId] : []) | hasRole | async))" [disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + grant?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + grant?.grantId] : []) | hasRole | async))"
(click)="loadGrantOptions(grant)" matTooltip="{{'ACTIONS.CHANGE' | translate}}"> (click)="loadGrantOptions(grant)" matTooltip="{{'ACTIONS.CHANGE' | translate}}">
<i class="las la-edit"></i> <i class="las la-edit"></i>
@ -104,7 +104,7 @@
<div class="row-form"> <div class="row-form">
<ng-container <ng-container
*ngIf="(context === UserGrantContext.OWNED_PROJECT || context === UserGrantContext.USER || context === UserGrantContext.NONE) && grantToEdit == grant.id && loadedProjectId && loadedProjectId === grant.projectId"> *ngIf="(context === UserGrantContext.OWNED_PROJECT || context === UserGrantContext.USER || context === UserGrantContext.NONE) && grantToEdit == grant.grantId && loadedProjectId && loadedProjectId === grant.projectId">
<cnsl-form-field class="form-field" appearance="outline"> <cnsl-form-field class="form-field" appearance="outline">
<!-- <cnsl-label>{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}</cnsl-label> --> <!-- <cnsl-label>{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}</cnsl-label> -->
<mat-select [(ngModel)]="grant.roleKeysList" multiple <mat-select [(ngModel)]="grant.roleKeysList" multiple
@ -123,7 +123,7 @@
</ng-container> </ng-container>
<ng-container <ng-container
*ngIf="(context === UserGrantContext.GRANTED_PROJECT || context === UserGrantContext.USER || context === UserGrantContext.NONE) && loadedGrantId && loadedGrantId === grant.grantId && grantToEdit == grant.id"> *ngIf="(context === UserGrantContext.GRANTED_PROJECT || context === UserGrantContext.USER || context === UserGrantContext.NONE) && loadedGrantId && loadedGrantId === grant.grantId && grantToEdit == grant.grantId">
<cnsl-form-field class="form-field" appearance="outline"> <cnsl-form-field class="form-field" appearance="outline">
<!-- <cnsl-label>{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}</cnsl-label> --> <!-- <cnsl-label>{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}</cnsl-label> -->
<mat-select [(ngModel)]="grant.roleKeysList" multiple <mat-select [(ngModel)]="grant.roleKeysList" multiple

View File

@ -182,12 +182,14 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
} }
private getGrantRoleOptions(grantId: string, projectId: string): void { private getGrantRoleOptions(grantId: string, projectId: string): void {
console.log(projectId, grantId);
this.mgmtService.getGrantedProjectByID(projectId, grantId).then(resp => { this.mgmtService.getGrantedProjectByID(projectId, grantId).then(resp => {
if (resp.grantedProject) { if (resp.grantedProject) {
this.loadedGrantId = grantId; this.loadedGrantId = grantId;
this.grantRoleOptions = resp.grantedProject?.grantedRoleKeysList; this.grantRoleOptions = resp.grantedProject?.grantedRoleKeysList;
} }
}).catch(error => { }).catch(error => {
this.grantToEdit = '';
this.toast.showError(error); this.toast.showError(error);
}); });
} }

View File

@ -112,8 +112,9 @@ export class OrgCreateComponent {
this.adminService this.adminService
.SetUpOrg(createOrgRequest, humanRequest) .SetUpOrg(createOrgRequest, humanRequest)
.then(() => { .then((resp) => {
this.router.navigate(['/org/overview']); this.router.navigate(['/org/overview']);
// const orgResp = org.getOrg(); // const orgResp = org.getOrg();
// if (orgResp) { // if (orgResp) {
// this.authService.setActiveOrg(orgResp.toObject()); // this.authService.setActiveOrg(orgResp.toObject());
@ -194,6 +195,8 @@ export class OrgCreateComponent {
this.orgForm = this.fb.group({ this.orgForm = this.fb.group({
name: ['', [Validators.required]], name: ['', [Validators.required]],
}); });
console.log(this.orgForm);
} else { } else {
this.createSteps = 2; this.createSteps = 2;

View File

@ -70,6 +70,7 @@ export class OrgDetailComponent implements OnInit {
public loadDomains(): void { public loadDomains(): void {
this.mgmtService.listOrgDomains().then(result => { this.mgmtService.listOrgDomains().then(result => {
this.domains = result.resultList; this.domains = result.resultList;
console.log(this.domains);
this.primaryDomain = this.domains.find(domain => domain.isPrimary)?.domainName ?? ''; this.primaryDomain = this.domains.find(domain => domain.isPrimary)?.domainName ?? '';
}); });
} }

View File

@ -266,6 +266,6 @@
</div> </div>
</div> </div>
<app-changes *ngIf="app" [changeType]="ChangeType.APP" [id]="projectId" [secId]="app.id"></app-changes> <app-changes *ngIf="app" [changeType]="ChangeType.APP" [id]="app.id" [secId]="projectId"></app-changes>
</div> </div>
</app-meta-layout> </app-meta-layout>

View File

@ -9,17 +9,18 @@
<p class="n-items" *ngIf="!loading && selection.selected.length > 0">{{'PROJECT.PAGES.PINNED' | translate}}</p> <p class="n-items" *ngIf="!loading && selection.selected.length > 0">{{'PROJECT.PAGES.PINNED' | translate}}</p>
<div class="item card" *ngFor="let item of selection.selected; index as i" <div class="item card" *ngFor="let item of selection.selected; index as i"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECTSTATE_ACTIVE}" [ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}"
(click)="navigateToProject(item.projectId,item.id, $event)"> (click)="navigateToProject(item.projectId,item.grantId, $event)">
<div class="text-part"> <div class="text-part">
<span *ngIf="item.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}} <span *ngIf="item.details.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
{{ {{
item.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span> }}</span>
<span class="name" *ngIf="item.projectName">{{ item.projectName }}</span> <span class="name" *ngIf="item.projectName">{{ item.projectName }}</span>
<span class="description" *ngIf="item.resourceOwnerName">{{item.resourceOwnerName}}</span> <span class="description" *ngIf="item.resourceOwnerName">{{item.resourceOwnerName}}</span>
<span *ngIf="item.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}} <span *ngIf="item.details.creationDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
{{ item.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span> {{ item.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
</div> </div>
@ -33,17 +34,17 @@
<p class="n-items" *ngIf="!loading && notPinned.length > 0">{{'PROJECT.PAGES.ALL' | translate}}</p> <p class="n-items" *ngIf="!loading && notPinned.length > 0">{{'PROJECT.PAGES.ALL' | translate}}</p>
<div class="item card" *ngFor="let item of notPinned; index as i" <div class="item card" *ngFor="let item of notPinned; index as i"
(click)="navigateToProject(item.projectId,item.id, $event)" (click)="navigateToProject(item.projectId,item.grantId, $event)"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECTSTATE_ACTIVE}"> [ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}">
<div class="text-part"> <div class="text-part">
<span *ngIf="item.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}} <span *ngIf="item.details.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
{{ {{
item.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span> }}</span>
<span class="name" *ngIf="item.projectName">{{ item.projectName }}</span> <span class="name" *ngIf="item.projectName">{{ item.projectName }}</span>
<span class="description" *ngIf="item.resourceOwnerName">{{item.resourceOwnerName}}</span> <span class="description" *ngIf="item.resourceOwnerName">{{item.resourceOwnerName}}</span>
<span *ngIf="item.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}} <span *ngIf="item.details.creationDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
{{ item.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span> {{ item.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
</div> </div>
<button [ngClass]="{ selected: selection.isSelected(item)}" <button [ngClass]="{ selected: selection.isSelected(item)}"

View File

@ -37,7 +37,7 @@
position: relative; position: relative;
z-index: 100; z-index: 100;
margin: 1rem; margin: 1rem;
flex-basis: 250px; flex-basis: 260px;
display: flex; display: flex;
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
@ -65,7 +65,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 70px; min-height: 70px;
padding: .5rem 0; padding: .5rem 1rem 0 0;
.top { .top {
font-size: .8rem; font-size: .8rem;

View File

@ -91,6 +91,7 @@ export class GrantedProjectListComponent implements OnInit, OnDestroy {
this.loadingSubject.next(true); this.loadingSubject.next(true);
this.mgmtService.listGrantedProjects(limit, offset).then(resp => { this.mgmtService.listGrantedProjects(limit, offset).then(resp => {
this.grantedProjectList = resp.resultList; this.grantedProjectList = resp.resultList;
console.log(this.grantedProjectList);
if (resp.details?.totalResult) { if (resp.details?.totalResult) {
this.totalResult = resp.details.totalResult; this.totalResult = resp.details.totalResult;
} }
@ -101,6 +102,7 @@ export class GrantedProjectListComponent implements OnInit, OnDestroy {
this.grid = false; this.grid = false;
} }
this.dataSource.data = this.grantedProjectList; this.dataSource.data = this.grantedProjectList;
this.loadingSubject.next(false); this.loadingSubject.next(false);
}).catch(error => { }).catch(error => {
console.error(error); console.error(error);

View File

@ -13,15 +13,15 @@
(click)="navigateToProject(item.id, $event)" (click)="navigateToProject(item.id, $event)"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}"> [ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}">
<div class="text-part"> <div class="text-part">
<span *ngIf="item.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}} <span *ngIf="item.details.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
{{ {{
item.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span> }}</span>
<span class="name" *ngIf="item.name">{{ item.name }}</span> <span class="name" *ngIf="item.name">{{ item.name }}</span>
<span *ngIf="item.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}} <span *ngIf="item.details.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
{{ {{
item.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' item.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span> }}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
</div> </div>

View File

@ -37,7 +37,7 @@
position: relative; position: relative;
z-index: 100; z-index: 100;
margin: 1rem; margin: 1rem;
flex-basis: 250px; flex-basis: 260px;
display: flex; display: flex;
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
@ -65,7 +65,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 70px; min-height: 70px;
padding: .5rem 0; padding: .5rem 1rem 0 0;
.top { .top {
font-size: .8rem; font-size: .8rem;
@ -168,7 +168,7 @@
.add-project-button { .add-project-button {
z-index: 100; z-index: 100;
flex-basis: 250px; flex-basis: 260px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -16,7 +16,7 @@ const routes: Routes = [
loadChildren: () => import('../project-create/project-create.module').then(m => m.ProjectCreateModule), loadChildren: () => import('../project-create/project-create.module').then(m => m.ProjectCreateModule),
canActivate: [RoleGuard], canActivate: [RoleGuard],
data: { data: {
roles: ['project.write'], roles: ['project.create'],
}, },
}, },
{ {

View File

@ -1,26 +1,38 @@
<h1 mat-dialog-title> <h1 mat-dialog-title>
<span class="title">{{'USER.CODEDIALOG.TITLE' | translate}} {{data?.number}}</span> <span class="title">{{'USER.MFA.DIALOG.ADD_MFA_TITLE' | translate}} {{data?.number}}</span>
</h1> </h1>
<p class="desc">{{'USER.CODEDIALOG.DESCRIPTION' | translate}}</p>
<div mat-dialog-content> <div mat-dialog-content>
<div class="type-selection"> <ng-container *ngIf="selectedType == undefined">
<button class="otp" (click)="selectType(AuthFactorType.OTP)"> <p class="desc">{{'USER.MFA.DIALOG.ADD_MFA_DESCRIPTION' | translate}}</p>
<mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>
<span>{{'USER.MFA.OTP' | translate}}</span> <div class="type-selection">
</button> <button mat-raised-button [disabled]="data.otpDisabled" (click)="selectType(AuthFactorType.OTP)">
<button class="u2f" (click)="selectType(AuthFactorType.U2F)"> <div class="otp-btn">
<i class="las la-fingerprint"></i> <mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>
<span>{{'USER.MFA.U2F' | translate}}</span> <span>{{'USER.MFA.OTP' | translate}}</span>
</button> </div>
</div> </button>
<button mat-raised-button (click)="selectType(AuthFactorType.U2F)">
<div class="u2f-btn">
<div class="icon-row">
<i matTooltip="Fingerprint" class="las la-fingerprint"></i>
<i matTooltip="Security Key" class="lab la-usb"></i>
<mat-icon matTooltip="NFC">nfc</mat-icon>
</div>
<span>{{'USER.MFA.U2F' | translate}}</span>
</div>
</button>
</div>
</ng-container>
<div class="otp" *ngIf="selectedType == AuthFactorType.OTP"> <div class="otp" *ngIf="selectedType == AuthFactorType.OTP">
<p>{{'USER.MFA.OTP_DIALOG_DESCRIPTION' | translate}}</p> <p class="desc">{{'USER.MFA.OTP_DIALOG_DESCRIPTION' | translate}}</p>
<div class="qrcode-wrapper"> <div class="qrcode-wrapper">
<qrcode *ngIf="otpurl" class="qrcode" [qrdata]="otpurl" [width]="150" [errorCorrectionLevel]="'M'"></qrcode> <qrcode *ngIf="otpurl" class="qrcode" [qrdata]="otpurl" [width]="150" [errorCorrectionLevel]="'M'"></qrcode>
</div> </div>
<cnsl-form-field class="form-field" label="Access Code" required="true"> <cnsl-form-field class="formfield" label="Access Code" required="true">
<cnsl-label>Code</cnsl-label> <cnsl-label>Code</cnsl-label>
<input cnslInput [(ngModel)]="otpcode" /> <input cnslInput [(ngModel)]="otpcode" />
</cnsl-form-field> </cnsl-form-field>
@ -44,7 +56,8 @@
{{'ACTIONS.CLOSE' | translate}} {{'ACTIONS.CLOSE' | translate}}
</button> </button>
<button cdkFocusInitial color="primary" mat-raised-button class="ok-button" (click)="submitAuth()"> <button *ngIf="selectedType !== undefined" cdkFocusInitial color="primary" mat-raised-button class="ok-button"
(click)="submitAuth()">
{{'ACTIONS.CREATE' | translate}} {{'ACTIONS.CREATE' | translate}}
</button> </button>
</div> </div>

View File

@ -2,21 +2,44 @@
display: flex; display: flex;
margin: 0 -0.5rem; margin: 0 -0.5rem;
.otp, .otp-btn,
.u2f { .u2f-btn {
flex: 1; flex: 1;
min-height: 100px; min-height: 100px;
border-radius: .5rem; border-radius: .5rem;
border: 1px solid var(--grey);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 1rem; margin: 1rem;
box-sizing: border-box;
.icon-row {
display: flex;
}
}
button {
margin: .5rem; margin: .5rem;
} }
} }
.desc {
font-size: 14px;
color: var(--grey);
}
.otp {
max-width: 400px;
display: flex;
flex-direction: column;
align-items: center;
}
.u2f {
max-width: 400px;
}
.formfield { .formfield {
width: 100%; width: 100%;
} }

View File

@ -42,6 +42,8 @@ export class AuthFactorDialogComponent {
} }
public selectType(type: AuthFactorType): void { public selectType(type: AuthFactorType): void {
this.selectedType = type;
if (type == AuthFactorType.OTP) { if (type == AuthFactorType.OTP) {
this.authService.addMyMultiFactorOTP().then((otpresp) => { this.authService.addMyMultiFactorOTP().then((otpresp) => {
this.otpurl = otpresp.url; this.otpurl = otpresp.url;
@ -95,6 +97,7 @@ export class AuthFactorDialogComponent {
(resp as any).response.clientDataJSON && (resp as any).response.clientDataJSON &&
(resp as any).rawId) { (resp as any).rawId) {
console.log(resp);
const attestationObject = (resp as any).response.attestationObject; const attestationObject = (resp as any).response.attestationObject;
const clientDataJSON = (resp as any).response.clientDataJSON; const clientDataJSON = (resp as any).response.clientDataJSON;
const rawId = (resp as any).rawId; const rawId = (resp as any).rawId;

View File

@ -36,9 +36,9 @@
</table> </table>
</app-refresh-table> </app-refresh-table>
<div class="add-row"> <div class="add-row">
<button class="button" (click)="addPasswordless()" mat-stroked-button color="primary" <button class="button" (click)="addPasswordless()" mat-raised-button color="primary"
matTooltip="{{'ACTIONS.NEW' | translate}}"> matTooltip="{{'ACTIONS.NEW' | translate}}">
<i class="las la-fingerprint"></i> <i class="icon las la-fingerprint"></i>
{{'USER.PASSWORDLESS.U2F' | translate}} {{'USER.PASSWORDLESS.U2F' | translate}}
</button> </button>
</div> </div>

View File

@ -3,10 +3,13 @@
display: flex; display: flex;
margin: -.5rem; margin: -.5rem;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: flex-end;
.button { .button {
margin: .5rem; margin: .5rem;
margin-top: 1rem; margin-top: 1rem;
display: flex;
align-items: center;
.icon { .icon {
margin-right: .5rem; margin-right: .5rem;

View File

@ -53,6 +53,7 @@ export class AuthPasswordlessComponent implements OnInit, OnDestroy {
public addPasswordless(): void { public addPasswordless(): void {
this.service.addMyPasswordless().then((resp) => { this.service.addMyPasswordless().then((resp) => {
if (resp.key) { if (resp.key) {
console.log(resp.key);
const credOptions: CredentialCreationOptions = JSON.parse(atob(resp.key.publicKey as string)); const credOptions: CredentialCreationOptions = JSON.parse(atob(resp.key.publicKey as string));
if (credOptions.publicKey?.challenge) { if (credOptions.publicKey?.challenge) {

View File

@ -27,10 +27,9 @@
</div> </div>
</app-card> </app-card>
<app-card *ngIf="user && user.human?.profile?.userName" class=" app-card" <app-card *ngIf="user && user.human?.profile" class=" app-card" title="{{ 'USER.PROFILE.TITLE' | translate }}">
title="{{ 'USER.PROFILE.TITLE' | translate }}"> <app-detail-form [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
<app-detail-form [genders]="genders" [languages]="languages" [username]="user.human?.profile?.userName" (changedLanguage)="changedLanguage($event)" (submitData)="saveProfile($event)">
[user]="user.human" (changedLanguage)="changedLanguage($event)" (submitData)="saveProfile($event)">
</app-detail-form> </app-detail-form>
</app-card> </app-card>

View File

@ -69,6 +69,7 @@ export class AuthUserDetailComponent implements OnDestroy {
this.user.human.profile?.firstName, this.user.human.profile?.firstName,
this.user.human.profile?.lastName, this.user.human.profile?.lastName,
this.user.human.profile?.nickName, this.user.human.profile?.nickName,
this.user.human.profile?.displayName,
this.user.human.profile?.preferredLanguage, this.user.human.profile?.preferredLanguage,
this.user.human.profile?.gender, this.user.human.profile?.gender,
) )
@ -169,6 +170,7 @@ export class AuthUserDetailComponent implements OnDestroy {
titleKey: 'USER.LOGINMETHODS.PHONE.EDITTITLE', titleKey: 'USER.LOGINMETHODS.PHONE.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.PHONE.EDITDESC', descriptionKey: 'USER.LOGINMETHODS.PHONE.EDITDESC',
value: this.user.human?.phone?.phone, value: this.user.human?.phone?.phone,
type: type,
}, },
width: '400px', width: '400px',
}); });
@ -180,6 +182,7 @@ export class AuthUserDetailComponent implements OnDestroy {
}); });
break; break;
case EditDialogType.EMAIL: case EditDialogType.EMAIL:
console.log('email');
const dialogRefEmail = this.dialog.open(EditDialogComponent, { const dialogRefEmail = this.dialog.open(EditDialogComponent, {
data: { data: {
confirmKey: 'ACTIONS.SAVE', confirmKey: 'ACTIONS.SAVE',
@ -188,6 +191,7 @@ export class AuthUserDetailComponent implements OnDestroy {
titleKey: 'USER.LOGINMETHODS.EMAIL.EDITTITLE', titleKey: 'USER.LOGINMETHODS.EMAIL.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.EMAIL.EDITDESC', descriptionKey: 'USER.LOGINMETHODS.EMAIL.EDITDESC',
value: this.user.human?.email?.email, value: this.user.human?.email?.email,
type: type
}, },
width: '400px', width: '400px',
}); });

View File

@ -3,7 +3,10 @@
<table class="table" mat-table [dataSource]="dataSource"> <table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="type"> <ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLETYPE' | translate }} </th> <th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLETYPE' | translate }} </th>
<td mat-cell *matCellDef="let mfa"> {{'USER.MFA.TYPE.'+ mfa.type | translate}} </td> <td mat-cell *matCellDef="let mfa">
<span *ngIf="mfa.otp !== undefined">OTP (One-Time Password)</span>
<span *ngIf="mfa.u2f !== undefined">U2F (Universal 2nd Factor)</span>
</td>
</ng-container> </ng-container>
<ng-container matColumnDef="attr"> <ng-container matColumnDef="attr">
@ -39,10 +42,10 @@
</table> </table>
</app-refresh-table> </app-refresh-table>
<div class="add-row"> <div class="add-row">
<button class="button" *ngIf="otpAvailable" (click)="addAuthFactor()" mat-raised-button color="primary" <button class="button" (click)="addAuthFactor()" mat-raised-button color="primary"
matTooltip="{{'ACTIONS.NEW' | translate}}"> matTooltip="{{'ACTIONS.NEW' | translate}}">
<mat-icon class="icon">add</mat-icon> <mat-icon class="icon">add</mat-icon>
{{'USER.MFA.OTP' | translate}} {{'USER.MFA.ADD' | translate}}
</button> </button>
</div> </div>
<div class="table-wrapper"> <div class="table-wrapper">

View File

@ -3,6 +3,7 @@
display: flex; display: flex;
margin: -.5rem; margin: -.5rem;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: flex-end;
.button { .button {
margin: .5rem; margin: .5rem;

View File

@ -55,7 +55,9 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
public addAuthFactor(): void { public addAuthFactor(): void {
const dialogRef = this.dialog.open(AuthFactorDialogComponent, { const dialogRef = this.dialog.open(AuthFactorDialogComponent, {
width: '400px', data: {
otpDisabled: !this.otpAvailable
}
}); });
dialogRef.afterClosed().subscribe((code) => { dialogRef.afterClosed().subscribe((code) => {
@ -70,6 +72,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
public getMFAs(): void { public getMFAs(): void {
this.service.listMyMultiFactors().then(mfas => { this.service.listMyMultiFactors().then(mfas => {
const list = mfas.resultList; const list = mfas.resultList;
console.log(list);
this.dataSource = new MatTableDataSource(list); this.dataSource = new MatTableDataSource(list);
this.dataSource.sort = this.sort; this.dataSource.sort = this.sort;

View File

@ -4,9 +4,10 @@
<p class="desc">{{data.descriptionKey | translate}}</p> <p class="desc">{{data.descriptionKey | translate}}</p>
<div mat-dialog-content> <div mat-dialog-content>
<cnsl-form-field class="formfield"> <cnsl-form-field class="formfield">
<cnsl-label>{{data.labelKey | translate }} <span *ngIf="phoneCountry">({{ phoneCountry }})</span></cnsl-label> <cnsl-label>{{data.labelKey | translate }} <span *ngIf="isPhone && phoneCountry">({{ phoneCountry }})</span>
<input cnslInput [(ngModel)]="value" (change)="changeValue($event)" </cnsl-label>
(keydown.enter)="value ? closeDialogWithValue(value) : null" /> <input [formControl]="valueControl" cnslInput
(keydown.enter)="valueControl.valid ? closeDialogWithValue() : null" />
</cnsl-form-field> </cnsl-form-field>
</div> </div>
<div mat-dialog-actions class="action"> <div mat-dialog-actions class="action">
@ -14,8 +15,8 @@
{{data.cancelKey | translate}} {{data.cancelKey | translate}}
</button> </button>
<button [disabled]="!value" cdkFocusInitial color="primary" mat-raised-button class="ok-button" <button [disabled]="valueControl.invalid" cdkFocusInitial color="primary" mat-raised-button class="ok-button"
(click)="closeDialogWithValue(value)"> (click)="closeDialogWithValue()">
{{data.confirmKey | translate}} {{data.confirmKey | translate}}
</button> </button>
</div> </div>

View File

@ -1,4 +1,5 @@
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { parsePhoneNumber } from 'libphonenumber-js'; import { parsePhoneNumber } from 'libphonenumber-js';
@ -13,34 +14,46 @@ export enum EditDialogType {
styleUrls: ['./edit-dialog.component.scss'], styleUrls: ['./edit-dialog.component.scss'],
}) })
export class EditDialogComponent { export class EditDialogComponent {
public value: string = '';
public isPhone: boolean = false; public isPhone: boolean = false;
public phoneCountry: string = 'CH'; public phoneCountry: string = 'CH';
public valueControl: FormControl = new FormControl(['', [Validators.required]]);
constructor(public dialogRef: MatDialogRef<EditDialogComponent>, constructor(public dialogRef: MatDialogRef<EditDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { @Inject(MAT_DIALOG_DATA) public data: any) {
this.value = data.value; this.valueControl.setValue(data.value);
if (data.type == EditDialogType.PHONE) { if (data.type == EditDialogType.PHONE) {
this.isPhone = true; this.isPhone = true;
} }
this.valueControl.valueChanges.subscribe(value => {
console.log(value);
if (value && value.length > 1) {
this.changeValue(value);
}
});
} }
changeValue(change: any) { changeValue(changedValue: string) {
const value = change.target.value; if (this.isPhone && changedValue) {
if (this.isPhone && value) { try {
const phoneNumber = parsePhoneNumber(value ?? '', 'CH'); const phoneNumber = parsePhoneNumber(changedValue ?? '', 'CH');
if (phoneNumber) { if (phoneNumber) {
const formmatted = phoneNumber.formatInternational(); const formmatted = phoneNumber.formatInternational();
this.phoneCountry = phoneNumber.country || ''; this.phoneCountry = phoneNumber.country || '';
this.value = formmatted; if (formmatted !== this.valueControl.value) {
this.valueControl.setValue(formmatted);
}
}
} catch (error) {
console.error(error);
} }
} }
} }
closeDialog(email: string = ''): void { closeDialog(): void {
this.dialogRef.close(email); this.dialogRef.close();
} }
closeDialogWithValue(value: string = ''): void { closeDialogWithValue(): void {
this.dialogRef.close(value); this.dialogRef.close(this.valueControl.value);
} }
} }

View File

@ -16,6 +16,10 @@
<cnsl-label>{{ 'USER.PROFILE.NICKNAME' | translate }}</cnsl-label> <cnsl-label>{{ 'USER.PROFILE.NICKNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="nickName" /> <input cnslInput formControlName="nickName" />
</cnsl-form-field> </cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.DISPLAYNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="displayName" />
</cnsl-form-field>
<cnsl-form-field class="formfield"> <cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.GENDER' | translate }}</cnsl-label> <cnsl-label>{{ 'USER.PROFILE.GENDER' | translate }}</cnsl-label>
<mat-select formControlName="gender"> <mat-select formControlName="gender">
@ -34,7 +38,7 @@
</cnsl-form-field> </cnsl-form-field>
</div> </div>
<div class="btn-container"> <div class="btn-container">
<button [disabled]="disabled" class="submit-button" type="submit" color="primary" <button [disabled]="disabled" class="submit-button" type="submit" color="primary" mat-raised-button>{{
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button> 'ACTIONS.SAVE' | translate }}</button>
</div> </div>
</form> </form>

View File

@ -30,6 +30,7 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
firstName: [{ value: '', disabled: this.disabled }, Validators.required], firstName: [{ value: '', disabled: this.disabled }, Validators.required],
lastName: [{ value: '', disabled: this.disabled }, Validators.required], lastName: [{ value: '', disabled: this.disabled }, Validators.required],
nickName: [{ value: '', disabled: this.disabled }], nickName: [{ value: '', disabled: this.disabled }],
displayName: [{ value: '', disabled: this.disabled }, Validators.required],
gender: [{ value: 0, disabled: this.disabled }], gender: [{ value: 0, disabled: this.disabled }],
preferredLanguage: [{ value: '', disabled: this.disabled }], preferredLanguage: [{ value: '', disabled: this.disabled }],
}); });
@ -43,6 +44,7 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
firstName: [{ value: '', disabled: this.disabled }, Validators.required], firstName: [{ value: '', disabled: this.disabled }, Validators.required],
lastName: [{ value: '', disabled: this.disabled }, Validators.required], lastName: [{ value: '', disabled: this.disabled }, Validators.required],
nickName: [{ value: '', disabled: this.disabled }], nickName: [{ value: '', disabled: this.disabled }],
displayName: [{ value: '', disabled: this.disabled }, Validators.required],
gender: [{ value: 0, disabled: this.disabled }], gender: [{ value: 0, disabled: this.disabled }],
preferredLanguage: [{ value: '', disabled: this.disabled }], preferredLanguage: [{ value: '', disabled: this.disabled }],
}); });
@ -77,6 +79,9 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
public get nickName(): AbstractControl | null { public get nickName(): AbstractControl | null {
return this.profileForm.get('nickName'); return this.profileForm.get('nickName');
} }
public get displayName(): AbstractControl | null {
return this.profileForm.get('displayName');
}
public get gender(): AbstractControl | null { public get gender(): AbstractControl | null {
return this.profileForm.get('gender'); return this.profileForm.get('gender');
} }

View File

@ -38,7 +38,7 @@
class="avatar" [name]="user.human.profile.displayName" [size]="32"> class="avatar" [name]="user.human.profile.displayName" [size]="32">
</app-avatar> </app-avatar>
<ng-template #cog> <ng-template #cog>
<div class="sa-icon"> <div class="sa-icon" *ngIf="user.machine">
<i class="las la-user-cog"></i> <i class="las la-user-cog"></i>
</div> </div>
</ng-template> </ng-template>

View File

@ -37,8 +37,6 @@ export class AuthenticationService {
public async authenticate( public async authenticate(
partialConfig?: Partial<AuthConfig>, partialConfig?: Partial<AuthConfig>,
setState: boolean = true,
force: boolean = false,
): Promise<boolean> { ): Promise<boolean> {
if (partialConfig) { if (partialConfig) {
Object.assign(this.authConfig, partialConfig); Object.assign(this.authConfig, partialConfig);
@ -50,8 +48,8 @@ export class AuthenticationService {
this._authenticated = this.oauthService.hasValidAccessToken(); this._authenticated = this.oauthService.hasValidAccessToken();
if (!this.oauthService.hasValidIdToken() || !this.authenticated || partialConfig || force) { if (!this.oauthService.hasValidIdToken() || !this.authenticated || partialConfig) {
const newState = setState ? await this.statehandler.createState().toPromise() : undefined; const newState = await this.statehandler.createState().toPromise();
this.oauthService.initCodeFlow(newState); this.oauthService.initCodeFlow(newState);
} }
this._authenticationChanged.next(this.authenticated); this._authenticationChanged.next(this.authenticated);

View File

@ -253,6 +253,7 @@ export class GrpcAuthService {
firstName?: string, firstName?: string,
lastName?: string, lastName?: string,
nickName?: string, nickName?: string,
displayName?: string,
preferredLanguage?: string, preferredLanguage?: string,
gender?: Gender, gender?: Gender,
): Promise<UpdateMyProfileResponse.AsObject> { ): Promise<UpdateMyProfileResponse.AsObject> {
@ -266,6 +267,9 @@ export class GrpcAuthService {
if (nickName) { if (nickName) {
req.setNickName(nickName); req.setNickName(nickName);
} }
if (displayName) {
req.setDisplayName(displayName);
}
if (gender) { if (gender) {
req.setGender(gender); req.setGender(gender);
} }

View File

@ -61,7 +61,7 @@ export class AuthInterceptor<TReq = unknown, TResp = unknown> implements UnaryIn
dialogRef.afterClosed().pipe(take(1)).subscribe(resp => { dialogRef.afterClosed().pipe(take(1)).subscribe(resp => {
if (resp) { if (resp) {
this.authenticationService.authenticate(undefined, true, true); this.authenticationService.authenticate(undefined);
} }
}); });
} }

View File

@ -3,5 +3,5 @@
"mgmtServiceUrl": "https://api.zitadel.io", "mgmtServiceUrl": "https://api.zitadel.io",
"adminServiceUrl":"https://api.zitadel.io", "adminServiceUrl":"https://api.zitadel.io",
"issuer": "https://issuer.zitadel.io", "issuer": "https://issuer.zitadel.io",
"clientid": "98804911164221523@zitadel" "clientid": "100129365194565743@zitadel"
} }

View File

@ -222,10 +222,11 @@
"TITLE": "Multifaktor-Authentisierung", "TITLE": "Multifaktor-Authentisierung",
"DESCRIPTION": "Füge einen zusätzlichen Faktor hinzu, um Dein Konto optimal zu schützen.", "DESCRIPTION": "Füge einen zusätzlichen Faktor hinzu, um Dein Konto optimal zu schützen.",
"MANAGE_DESCRIPTION": "Verwalte die Multifaktor-Merkmale Deiner Benutzer.", "MANAGE_DESCRIPTION": "Verwalte die Multifaktor-Merkmale Deiner Benutzer.",
"OTP": "OTP konfigurieren", "ADD":"Faktor hinzufügen",
"OTP": "OTP (One-Time Password)",
"OTP_DIALOG_TITLE": "OTP hinzufügen", "OTP_DIALOG_TITLE": "OTP hinzufügen",
"OTP_DIALOG_DESCRIPTION": "Scanne den QR-Code mit einer Authenticator App und verifiziere den erhaltenen Code, um OTP zu aktivieren.", "OTP_DIALOG_DESCRIPTION": "Scanne den QR-Code mit einer Authenticator App und verifiziere den erhaltenen Code, um OTP zu aktivieren.",
"U2F":"U2F hinzufügen", "U2F":"U2F (Universal 2nd Factor)",
"U2F_DIALOG_TITLE": "U2F hinzufügen", "U2F_DIALOG_TITLE": "U2F hinzufügen",
"U2F_DIALOG_DESCRIPTION": "Gib einen Namen für den von dir verwendeten Universellen Multifaktor an.", "U2F_DIALOG_DESCRIPTION": "Gib einen Namen für den von dir verwendeten Universellen Multifaktor an.",
"U2F_SUCCESS":"U2F erfolgreich erstellt!", "U2F_SUCCESS":"U2F erfolgreich erstellt!",
@ -244,7 +245,9 @@
}, },
"DIALOG": { "DIALOG": {
"MFA_DELETE_TITLE":"Zweiten Faktor entfernen", "MFA_DELETE_TITLE":"Zweiten Faktor entfernen",
"MFA_DELETE_DESCRIPTION":"Sie sind im Begriff eine Zweitfaktormethode zu entfernen. Sind sie sicher?" "MFA_DELETE_DESCRIPTION":"Sie sind im Begriff eine Zweitfaktormethode zu entfernen. Sind sie sicher?",
"ADD_MFA_TITLE":"Zweiten Faktor hinzufügen",
"ADD_MFA_DESCRIPTION":"Wählen Sie einen der verfügbaren Optionen."
} }
}, },
"EXTERNALIDP": { "EXTERNALIDP": {

View File

@ -222,10 +222,11 @@
"TITLE": "Multifactor Authentication", "TITLE": "Multifactor Authentication",
"DESCRIPTION": "Add a second factor to ensure optimal security for your account.", "DESCRIPTION": "Add a second factor to ensure optimal security for your account.",
"MANAGE_DESCRIPTION": "Manage the second factor methods of your users.", "MANAGE_DESCRIPTION": "Manage the second factor methods of your users.",
"OTP": "Configure OTP", "ADD":"Add AuthFactor",
"OTP": "OTP (One-Time Password)",
"OTP_DIALOG_TITLE": "Add OTP", "OTP_DIALOG_TITLE": "Add OTP",
"OTP_DIALOG_DESCRIPTION": "Scan the QR code with an authenticator app and enter the code below to verify and activate the OTP method.", "OTP_DIALOG_DESCRIPTION": "Scan the QR code with an authenticator app and enter the code below to verify and activate the OTP method.",
"U2F":"Add U2F", "U2F":"U2F (Universal 2nd Factor)",
"U2F_DIALOG_TITLE": "Verify U2F", "U2F_DIALOG_TITLE": "Verify U2F",
"U2F_DIALOG_DESCRIPTION": "Enter a name for your used universal Multifactor.", "U2F_DIALOG_DESCRIPTION": "Enter a name for your used universal Multifactor.",
"U2F_SUCCESS":"U2F created successfully!", "U2F_SUCCESS":"U2F created successfully!",