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 {
this.projectId = project.projectId;
if (project.id) {
this.grantId = project.id;
if (project.projectId && project.grantId) {
this.projectId = project.projectId;
this.grantId = project.grantId;
}
else if (project.id) {
this.projectId = project.id;
}
}

View File

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

View File

@ -1,5 +1,6 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MultiFactorType, SecondFactorType } from 'src/app/proto/generated/zitadel/policy_pb';
enum LoginMethodComponentType {
MultiFactor = 1,
@ -13,10 +14,11 @@ enum LoginMethodComponentType {
})
export class DialogAddTypeComponent {
public LoginMethodComponentType: any = LoginMethodComponentType;
// public availableMfaTypes: Array<AdminMultiFactorType | MgmtMultiFactorType> = [];
public availableMfaTypes: Array<MultiFactorType | SecondFactorType> = [];
public newMfaType!: MultiFactorType | SecondFactorType;
constructor(public dialogRef: MatDialogRef<DialogAddTypeComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
// this.availableMfaTypes = data.types;
this.availableMfaTypes = data.types;
}
public closeDialog(): void {
@ -24,6 +26,6 @@ export class DialogAddTypeComponent {
}
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 { BehaviorSubject, Observable } from 'rxjs';
import {
AddMultiFactorToLoginPolicyRequest as AdminAddMultiFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyRequest as AdminAddSecondFactorToLoginPolicyRequest,
RemoveMultiFactorFromLoginPolicyRequest as AdminRemoveMultiFactorFromLoginPolicyRequest,
RemoveSecondFactorFromLoginPolicyRequest as AdminRemoveSecondFactorFromLoginPolicyRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import {
AddMultiFactorToLoginPolicyRequest as MgmtAddMultiFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyRequest as MgmtAddSecondFactorToLoginPolicyRequest,
RemoveMultiFactorFromLoginPolicyRequest as MgmtRemoveMultiFactorFromLoginPolicyRequest,
RemoveSecondFactorFromLoginPolicyRequest as MgmtRemoveSecondFactorFromLoginPolicyRequest,
} 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 { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
import { DialogAddTypeComponent } from './dialog-add-type/dialog-add-type.component';
export enum LoginMethodComponentType {
MultiFactor = 1,
@ -116,57 +121,57 @@ export class MfaTableComponent implements OnInit {
}
});
// const dialogRef = this.dialog.open(DialogAddTypeComponent, {
// data: {
// title: 'MFA.CREATE.TITLE',
// desc: 'MFA.CREATE.DESCRIPTION',
// componentType: this.componentType,
// types: selection,
// },
// width: '400px',
// });
const dialogRef = this.dialog.open(DialogAddTypeComponent, {
data: {
title: 'MFA.CREATE.TITLE',
desc: 'MFA.CREATE.DESCRIPTION',
componentType: this.componentType,
types: selection,
},
width: '400px',
});
// dialogRef.afterClosed().subscribe((mfaType: ) => {
// if (mfaType) {
// if (this.serviceType === PolicyComponentServiceType.MGMT) {
// if (this.componentType === LoginMethodComponentType.MultiFactor) {
// const req = new MgmtAddMultiFactorToLoginPolicyRequest();
// req.setType(mfaType as MultiFactorType);
// (this.service as ManagementService).addMultiFactorToLoginPolicy(req).then(() => {
// this.refreshPageAfterTimout(2000);
// }).catch(error => {
// this.toast.showError(error);
// });
// } else if (this.componentType === LoginMethodComponentType.SecondFactor) {
// const req = new MgmtAddSecondFactorToLoginPolicyRequest();
// req.setType(mfaType as SecondFactorType);
// (this.service as ManagementService).addSecondFactorToLoginPolicy(req).then(() => {
// this.refreshPageAfterTimout(2000);
// }).catch(error => {
// this.toast.showError(error);
// });
// }
// } else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
// if (this.componentType === LoginMethodComponentType.MultiFactor) {
// const req = new AdminAddMultiFactorToLoginPolicyRequest();
// req.setType(mfaType as MultiFactorType);
// (this.service as AdminService).addMultiFactorToLoginPolicy(req).then(() => {
// this.refreshPageAfterTimout(2000);
// }).catch(error => {
// this.toast.showError(error);
// });
// } else if (this.componentType === LoginMethodComponentType.SecondFactor) {
// const req = new AdminAddSecondFactorToLoginPolicyRequest();
// req.setType(mfaType as SecondFactorType);
// (this.service as AdminService).addSecondFactorToLoginPolicy(req).then(() => {
// this.refreshPageAfterTimout(2000);
// }).catch(error => {
// this.toast.showError(error);
// });
// }
// }
// }
// });
dialogRef.afterClosed().subscribe((mfaType: MultiFactorType | SecondFactorType) => {
if (mfaType) {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new MgmtAddMultiFactorToLoginPolicyRequest();
req.setType(mfaType as MultiFactorType);
(this.service as ManagementService).addMultiFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new MgmtAddSecondFactorToLoginPolicyRequest();
req.setType(mfaType as SecondFactorType);
(this.service as ManagementService).addSecondFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new AdminAddMultiFactorToLoginPolicyRequest();
req.setType(mfaType as MultiFactorType);
(this.service as AdminService).addMultiFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new AdminAddSecondFactorToLoginPolicyRequest();
req.setType(mfaType as SecondFactorType);
(this.service as AdminService).addSecondFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
}
}
}
});
}
private async getData(): Promise<void> {

View File

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

View File

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

View File

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

View File

@ -67,13 +67,13 @@
<ng-container matColumnDef="dates">
<th mat-header-cell *matHeaderCellDef> DATES </th>
<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>{{grant.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
<span>{{grant.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</div>
<div class="date-block">
<div class="date-block" *ngIf="grant.details?.changeDate">
<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>
</td>
</ng-container>
@ -86,14 +86,14 @@
</th>
<td mat-cell *matCellDef="let grant; let i = index" class="role-data">
<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="role">
<span *ngFor="let role of grant.roleKeysList">{{ role }}</span>
</div>
<span class="fill-space"></span>
<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))"
(click)="loadGrantOptions(grant)" matTooltip="{{'ACTIONS.CHANGE' | translate}}">
<i class="las la-edit"></i>
@ -104,7 +104,7 @@
<div class="row-form">
<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-label>{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}</cnsl-label> -->
<mat-select [(ngModel)]="grant.roleKeysList" multiple
@ -123,7 +123,7 @@
</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-label>{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}</cnsl-label> -->
<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 {
console.log(projectId, grantId);
this.mgmtService.getGrantedProjectByID(projectId, grantId).then(resp => {
if (resp.grantedProject) {
this.loadedGrantId = grantId;
this.grantRoleOptions = resp.grantedProject?.grantedRoleKeysList;
}
}).catch(error => {
this.grantToEdit = '';
this.toast.showError(error);
});
}

View File

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

View File

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

View File

@ -266,6 +266,6 @@
</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>
</app-meta-layout>

View File

@ -9,17 +9,18 @@
<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"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECTSTATE_ACTIVE}"
(click)="navigateToProject(item.projectId,item.id, $event)">
[ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}"
(click)="navigateToProject(item.projectId,item.grantId, $event)">
<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'
}}</span>
item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
<span class="name" *ngIf="item.projectName">{{ item.projectName }}</span>
<span class="description" *ngIf="item.resourceOwnerName">{{item.resourceOwnerName}}</span>
<span *ngIf="item.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
{{ item.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
<span *ngIf="item.details.creationDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
{{ item.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
<span class="fill-space"></span>
</div>
@ -33,17 +34,17 @@
<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"
(click)="navigateToProject(item.projectId,item.id, $event)"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECTSTATE_ACTIVE}">
(click)="navigateToProject(item.projectId,item.grantId, $event)"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}">
<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'
}}</span>
item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
<span class="name" *ngIf="item.projectName">{{ item.projectName }}</span>
<span class="description" *ngIf="item.resourceOwnerName">{{item.resourceOwnerName}}</span>
<span *ngIf="item.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
{{ item.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
<span *ngIf="item.details.creationDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
{{ item.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
<span class="fill-space"></span>
</div>
<button [ngClass]="{ selected: selection.isSelected(item)}"

View File

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

View File

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

View File

@ -13,15 +13,15 @@
(click)="navigateToProject(item.id, $event)"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}">
<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 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 class="fill-space"></span>
</div>

View File

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

View File

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

View File

@ -1,26 +1,38 @@
<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>
<p class="desc">{{'USER.CODEDIALOG.DESCRIPTION' | translate}}</p>
<div mat-dialog-content>
<div class="type-selection">
<button class="otp" (click)="selectType(AuthFactorType.OTP)">
<mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>
<span>{{'USER.MFA.OTP' | translate}}</span>
</button>
<button class="u2f" (click)="selectType(AuthFactorType.U2F)">
<i class="las la-fingerprint"></i>
<span>{{'USER.MFA.U2F' | translate}}</span>
</button>
</div>
<ng-container *ngIf="selectedType == undefined">
<p class="desc">{{'USER.MFA.DIALOG.ADD_MFA_DESCRIPTION' | translate}}</p>
<div class="type-selection">
<button mat-raised-button [disabled]="data.otpDisabled" (click)="selectType(AuthFactorType.OTP)">
<div class="otp-btn">
<mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>
<span>{{'USER.MFA.OTP' | translate}}</span>
</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">
<p>{{'USER.MFA.OTP_DIALOG_DESCRIPTION' | translate}}</p>
<p class="desc">{{'USER.MFA.OTP_DIALOG_DESCRIPTION' | translate}}</p>
<div class="qrcode-wrapper">
<qrcode *ngIf="otpurl" class="qrcode" [qrdata]="otpurl" [width]="150" [errorCorrectionLevel]="'M'"></qrcode>
</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>
<input cnslInput [(ngModel)]="otpcode" />
</cnsl-form-field>
@ -44,7 +56,8 @@
{{'ACTIONS.CLOSE' | translate}}
</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}}
</button>
</div>

View File

@ -2,21 +2,44 @@
display: flex;
margin: 0 -0.5rem;
.otp,
.u2f {
.otp-btn,
.u2f-btn {
flex: 1;
min-height: 100px;
border-radius: .5rem;
border: 1px solid var(--grey);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 1rem;
margin: 1rem;
box-sizing: border-box;
.icon-row {
display: flex;
}
}
button {
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 {
width: 100%;
}

View File

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

View File

@ -36,9 +36,9 @@
</table>
</app-refresh-table>
<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}}">
<i class="las la-fingerprint"></i>
<i class="icon las la-fingerprint"></i>
{{'USER.PASSWORDLESS.U2F' | translate}}
</button>
</div>

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,10 @@
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="type">
<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 matColumnDef="attr">
@ -39,10 +42,10 @@
</table>
</app-refresh-table>
<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}}">
<mat-icon class="icon">add</mat-icon>
{{'USER.MFA.OTP' | translate}}
{{'USER.MFA.ADD' | translate}}
</button>
</div>
<div class="table-wrapper">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -253,6 +253,7 @@ export class GrpcAuthService {
firstName?: string,
lastName?: string,
nickName?: string,
displayName?: string,
preferredLanguage?: string,
gender?: Gender,
): Promise<UpdateMyProfileResponse.AsObject> {
@ -266,6 +267,9 @@ export class GrpcAuthService {
if (nickName) {
req.setNickName(nickName);
}
if (displayName) {
req.setDisplayName(displayName);
}
if (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 => {
if (resp) {
this.authenticationService.authenticate(undefined, true, true);
this.authenticationService.authenticate(undefined);
}
});
}

View File

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

View File

@ -222,10 +222,11 @@
"TITLE": "Multifaktor-Authentisierung",
"DESCRIPTION": "Füge einen zusätzlichen Faktor hinzu, um Dein Konto optimal zu schützen.",
"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_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_DESCRIPTION": "Gib einen Namen für den von dir verwendeten Universellen Multifaktor an.",
"U2F_SUCCESS":"U2F erfolgreich erstellt!",
@ -244,7 +245,9 @@
},
"DIALOG": {
"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": {

View File

@ -222,10 +222,11 @@
"TITLE": "Multifactor Authentication",
"DESCRIPTION": "Add a second factor to ensure optimal security for your account.",
"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_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_DESCRIPTION": "Enter a name for your used universal Multifactor.",
"U2F_SUCCESS":"U2F created successfully!",