mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-13 21:40:45 +00:00
fix: console v2 (#1454)
* some issues * passwordless, mfa * mfa, project fixes, login policy * user table, auth service, interceptor
This commit is contained in:
@@ -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>
|
||||
@@ -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%;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
display: flex;
|
||||
margin: -.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
|
||||
.button {
|
||||
margin: .5rem;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user