mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 17:07:24 +00:00
fix: Create Human V1 (#9425)
# Which Problems Are Solved - Correctly load Avatar on first load # How the Problems Are Solved - The Avatar issue was mostly due to how we resolved the current user, I changed this behaviour # Additional Changes - Removed V2 create human code till seperate page is finished - Remove Console Use V2 API feature flag from features page (till new page is added) # Additional Context - Partially fixes #9382 - This will get implemented next week https://github.com/zitadel/zitadel/issues/9382#issuecomment-2681347477
This commit is contained in:
parent
3c471944c2
commit
83614562a2
@ -1,9 +1,8 @@
|
|||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
<ng-container *ngIf="(authService.user | async) || {} as user">
|
<ng-container *ngIf="authService.user | async as user">
|
||||||
<cnsl-header
|
<cnsl-header
|
||||||
*ngIf="user && user !== {}"
|
|
||||||
[org]="org"
|
[org]="org"
|
||||||
[user]="$any(user)"
|
[user]="user"
|
||||||
[isDarkTheme]="componentCssClass === 'dark-theme'"
|
[isDarkTheme]="componentCssClass === 'dark-theme'"
|
||||||
(changedActiveOrg)="changedOrg($event)"
|
(changedActiveOrg)="changedOrg($event)"
|
||||||
></cnsl-header>
|
></cnsl-header>
|
||||||
@ -12,9 +11,8 @@
|
|||||||
id="mainnav"
|
id="mainnav"
|
||||||
class="nav"
|
class="nav"
|
||||||
[ngClass]="{ shadow: yoffset > 60 }"
|
[ngClass]="{ shadow: yoffset > 60 }"
|
||||||
*ngIf="user && user !== {}"
|
|
||||||
[org]="org"
|
[org]="org"
|
||||||
[user]="$any(user)"
|
[user]="user"
|
||||||
[isDarkTheme]="componentCssClass === 'dark-theme'"
|
[isDarkTheme]="componentCssClass === 'dark-theme'"
|
||||||
></cnsl-nav>
|
></cnsl-nav>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -403,34 +403,6 @@
|
|||||||
'SETTING.FEATURES.OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION' | translate
|
'SETTING.FEATURES.OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION' | translate
|
||||||
}}</cnsl-info-section>
|
}}</cnsl-info-section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature-row" *ngIf="toggleStates.consoleUseV2UserApi">
|
|
||||||
<span>{{ 'SETTING.FEATURES.CONSOLEUSEV2USERAPI' | translate }}</span>
|
|
||||||
<div class="row">
|
|
||||||
<mat-button-toggle-group
|
|
||||||
class="theme-toggle"
|
|
||||||
class="buttongroup"
|
|
||||||
[(ngModel)]="toggleStates.consoleUseV2UserApi.state"
|
|
||||||
(change)="validateAndSave()"
|
|
||||||
name="displayview"
|
|
||||||
aria-label="Display View"
|
|
||||||
>
|
|
||||||
<mat-button-toggle [value]="ToggleState.DISABLED">
|
|
||||||
<div class="toggle-row">
|
|
||||||
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
|
|
||||||
</div>
|
|
||||||
</mat-button-toggle>
|
|
||||||
<mat-button-toggle [value]="ToggleState.ENABLED">
|
|
||||||
<div class="toggle-row">
|
|
||||||
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
|
|
||||||
</div>
|
|
||||||
</mat-button-toggle>
|
|
||||||
</mat-button-toggle-group>
|
|
||||||
</div>
|
|
||||||
<cnsl-info-section class="feature-info">{{
|
|
||||||
'SETTING.FEATURES.CONSOLEUSEV2USERAPI_DESCRIPTION' | translate
|
|
||||||
}}</cnsl-info-section>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</cnsl-card>
|
</cnsl-card>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component, OnDestroy } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { BehaviorSubject, Subject } from 'rxjs';
|
|
||||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||||
import { CardModule } from 'src/app/modules/card/card.module';
|
import { CardModule } from 'src/app/modules/card/card.module';
|
||||||
import { DisplayJsonDialogComponent } from 'src/app/modules/display-json-dialog/display-json-dialog.component';
|
|
||||||
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
|
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
|
||||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||||
import { Event } from 'src/app/proto/generated/zitadel/event_pb';
|
|
||||||
import { Source } from 'src/app/proto/generated/zitadel/feature/v2beta/feature_pb';
|
import { Source } from 'src/app/proto/generated/zitadel/feature/v2beta/feature_pb';
|
||||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||||
import { FeatureService } from 'src/app/services/feature.service';
|
import { FeatureService } from 'src/app/services/feature.service';
|
||||||
@ -22,8 +18,7 @@ import { ToastService } from 'src/app/services/toast.service';
|
|||||||
import {
|
import {
|
||||||
GetInstanceFeaturesResponse,
|
GetInstanceFeaturesResponse,
|
||||||
SetInstanceFeaturesRequest,
|
SetInstanceFeaturesRequest,
|
||||||
} from '../../proto/generated/zitadel/feature/v2/instance_pb';
|
} from 'src/app/proto/generated/zitadel/feature/v2/instance_pb';
|
||||||
import { withIdentifier } from 'codelyzer/util/astQuery';
|
|
||||||
|
|
||||||
enum ToggleState {
|
enum ToggleState {
|
||||||
ENABLED = 'ENABLED',
|
ENABLED = 'ENABLED',
|
||||||
@ -40,7 +35,6 @@ type ToggleStates = {
|
|||||||
oidcTokenExchange?: FeatureState;
|
oidcTokenExchange?: FeatureState;
|
||||||
actions?: FeatureState;
|
actions?: FeatureState;
|
||||||
oidcSingleV1SessionTermination?: FeatureState;
|
oidcSingleV1SessionTermination?: FeatureState;
|
||||||
consoleUseV2UserApi?: FeatureState;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -63,21 +57,17 @@ type ToggleStates = {
|
|||||||
templateUrl: './features.component.html',
|
templateUrl: './features.component.html',
|
||||||
styleUrls: ['./features.component.scss'],
|
styleUrls: ['./features.component.scss'],
|
||||||
})
|
})
|
||||||
export class FeaturesComponent implements OnDestroy {
|
export class FeaturesComponent {
|
||||||
private destroy$: Subject<void> = new Subject();
|
protected featureData: GetInstanceFeaturesResponse.AsObject | undefined;
|
||||||
|
|
||||||
public _loading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
protected toggleStates: ToggleStates | undefined;
|
||||||
public featureData: GetInstanceFeaturesResponse.AsObject | undefined = undefined;
|
protected Source: any = Source;
|
||||||
|
protected ToggleState: any = ToggleState;
|
||||||
public toggleStates: ToggleStates | undefined = undefined;
|
|
||||||
public Source: any = Source;
|
|
||||||
public ToggleState: any = ToggleState;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private featureService: FeatureService,
|
private featureService: FeatureService,
|
||||||
private breadcrumbService: BreadcrumbService,
|
private breadcrumbService: BreadcrumbService,
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private dialog: MatDialog,
|
|
||||||
) {
|
) {
|
||||||
const breadcrumbs = [
|
const breadcrumbs = [
|
||||||
new Breadcrumb({
|
new Breadcrumb({
|
||||||
@ -91,20 +81,6 @@ export class FeaturesComponent implements OnDestroy {
|
|||||||
this.getFeatures(true);
|
this.getFeatures(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.destroy$.next();
|
|
||||||
this.destroy$.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public openDialog(event: Event): void {
|
|
||||||
this.dialog.open(DisplayJsonDialogComponent, {
|
|
||||||
data: {
|
|
||||||
event: event,
|
|
||||||
},
|
|
||||||
width: '450px',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public validateAndSave() {
|
public validateAndSave() {
|
||||||
this.featureService.resetInstanceFeatures().then(() => {
|
this.featureService.resetInstanceFeatures().then(() => {
|
||||||
const req = new SetInstanceFeaturesRequest();
|
const req = new SetInstanceFeaturesRequest();
|
||||||
@ -144,7 +120,6 @@ export class FeaturesComponent implements OnDestroy {
|
|||||||
);
|
);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
req.setConsoleUseV2UserApi(this.toggleStates?.consoleUseV2UserApi?.state === ToggleState.ENABLED);
|
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
this.featureService
|
this.featureService
|
||||||
@ -235,10 +210,6 @@ export class FeaturesComponent implements OnDestroy {
|
|||||||
? ToggleState.ENABLED
|
? ToggleState.ENABLED
|
||||||
: ToggleState.DISABLED,
|
: ToggleState.DISABLED,
|
||||||
},
|
},
|
||||||
consoleUseV2UserApi: {
|
|
||||||
source: this.featureData.consoleUseV2UserApi?.source || Source.SOURCE_INSTANCE,
|
|
||||||
state: this.featureData.consoleUseV2UserApi?.enabled ? ToggleState.ENABLED : ToggleState.DISABLED,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { ConnectedPosition, ConnectionPositionPair } from '@angular/cdk/overlay';
|
import { ConnectedPosition, ConnectionPositionPair } from '@angular/cdk/overlay';
|
||||||
import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
|
|
||||||
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
|
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
|
||||||
import { User } from 'src/app/proto/generated/zitadel/user_pb';
|
import { User } from 'src/app/proto/generated/zitadel/user_pb';
|
||||||
import { AuthenticationService } from 'src/app/services/authentication.service';
|
|
||||||
import { BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
import { BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
@ -15,19 +13,14 @@ import { ActionKeysType } from '../action-keys/action-keys.component';
|
|||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
styleUrls: ['./header.component.scss'],
|
styleUrls: ['./header.component.scss'],
|
||||||
})
|
})
|
||||||
export class HeaderComponent implements OnDestroy {
|
export class HeaderComponent {
|
||||||
@ViewChild('input', { static: false }) input!: ElementRef;
|
|
||||||
|
|
||||||
@Input() public isDarkTheme: boolean = true;
|
@Input() public isDarkTheme: boolean = true;
|
||||||
@Input() public user?: User.AsObject;
|
@Input() public user?: User.AsObject;
|
||||||
public showOrgContext: boolean = false;
|
public showOrgContext: boolean = false;
|
||||||
|
|
||||||
public orgs$: Observable<Org.AsObject[]> = of([]);
|
|
||||||
@Input() public org!: Org.AsObject;
|
@Input() public org!: Org.AsObject;
|
||||||
@Output() public changedActiveOrg: EventEmitter<Org.AsObject> = new EventEmitter();
|
@Output() public changedActiveOrg: EventEmitter<Org.AsObject> = new EventEmitter();
|
||||||
public orgLoading$: BehaviorSubject<any> = new BehaviorSubject(false);
|
|
||||||
public showAccount: boolean = false;
|
public showAccount: boolean = false;
|
||||||
private destroy$: Subject<void> = new Subject();
|
|
||||||
public BreadcrumbType: any = BreadcrumbType;
|
public BreadcrumbType: any = BreadcrumbType;
|
||||||
public ActionKeysType: any = ActionKeysType;
|
public ActionKeysType: any = ActionKeysType;
|
||||||
|
|
||||||
@ -41,24 +34,12 @@ export class HeaderComponent implements OnDestroy {
|
|||||||
new ConnectionPositionPair({ originX: 'end', originY: 'bottom' }, { overlayX: 'end', overlayY: 'top' }, 0, 10),
|
new ConnectionPositionPair({ originX: 'end', originY: 'bottom' }, { overlayX: 'end', overlayY: 'top' }, 0, 10),
|
||||||
];
|
];
|
||||||
constructor(
|
constructor(
|
||||||
public authenticationService: AuthenticationService,
|
|
||||||
public authService: GrpcAuthService,
|
public authService: GrpcAuthService,
|
||||||
public mgmtService: ManagementService,
|
public mgmtService: ManagementService,
|
||||||
public breadcrumbService: BreadcrumbService,
|
public breadcrumbService: BreadcrumbService,
|
||||||
public router: Router,
|
public router: Router,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
|
||||||
this.destroy$.next();
|
|
||||||
this.destroy$.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeAccountCard(): void {
|
|
||||||
if (this.showAccount) {
|
|
||||||
this.showAccount = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public setActiveOrg(org: Org.AsObject): void {
|
public setActiveOrg(org: Org.AsObject): void {
|
||||||
this.org = org;
|
this.org = org;
|
||||||
this.authService.setActiveOrg(org);
|
this.authService.setActiveOrg(org);
|
||||||
|
@ -200,7 +200,7 @@ export class ProviderOIDCComponent {
|
|||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.service
|
this.service
|
||||||
.updateGenericOIDCProvider(req)
|
.updateGenericOIDCProvider(req)
|
||||||
.then((idp) => {
|
.then(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.close();
|
this.close();
|
||||||
|
@ -51,17 +51,10 @@
|
|||||||
<mat-checkbox class="block-checkbox" formControlName="emailVerified">
|
<mat-checkbox class="block-checkbox" formControlName="emailVerified">
|
||||||
{{ 'USER.LOGINMETHODS.EMAIL.ISVERIFIED' | translate }}
|
{{ 'USER.LOGINMETHODS.EMAIL.ISVERIFIED' | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
<mat-checkbox
|
|
||||||
*ngIf="((useV2Api$ | async) && !usePassword) || !userForm.controls.emailVerified.value"
|
|
||||||
class="block-checkbox"
|
|
||||||
formControlName="sendEmail"
|
|
||||||
>
|
|
||||||
{{ 'USER.PROFILE.SEND_EMAIL' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
<mat-checkbox class="block-checkbox" [(ngModel)]="usePassword" [ngModelOptions]="{ standalone: true }">
|
<mat-checkbox class="block-checkbox" [(ngModel)]="usePassword" [ngModelOptions]="{ standalone: true }">
|
||||||
{{ 'ORG.PAGES.USEPASSWORD' | translate }}
|
{{ 'ORG.PAGES.USEPASSWORD' | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
<cnsl-info-section *ngIf="(useV2Api$ | async) === false" class="full-width desc">
|
<cnsl-info-section class="full-width desc">
|
||||||
<span>{{ 'USER.CREATE.INITMAILDESCRIPTION' | translate }}</span>
|
<span>{{ 'USER.CREATE.INITMAILDESCRIPTION' | translate }}</span>
|
||||||
</cnsl-info-section>
|
</cnsl-info-section>
|
||||||
</div>
|
</div>
|
||||||
@ -145,18 +138,6 @@
|
|||||||
<input cnslInput formControlName="phone" matTooltip="{{ 'USER.PROFILE.PHONE_HINT' | translate }}" />
|
<input cnslInput formControlName="phone" matTooltip="{{ 'USER.PROFILE.PHONE_HINT' | translate }}" />
|
||||||
</cnsl-form-field>
|
</cnsl-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="phone-is-verified">
|
|
||||||
<mat-checkbox *ngIf="useV2Api$ | async" class="block-checkbox" formControlName="phoneVerified">
|
|
||||||
{{ 'USER.PROFILE.PHONE_VERIFIED' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
<mat-checkbox
|
|
||||||
*ngIf="(useV2Api$ | async) && !userForm.controls.phoneVerified.value"
|
|
||||||
class="block-checkbox"
|
|
||||||
formControlName="sendSms"
|
|
||||||
>
|
|
||||||
{{ 'USER.PROFILE.SEND_SMS' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="user-create-btn-container">
|
<div class="user-create-btn-container">
|
||||||
<button
|
<button
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.email-is-verified,
|
.email-is-verified,
|
||||||
.phone-is-verified,
|
|
||||||
.use-password-block {
|
.use-password-block {
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
|
@ -2,24 +2,12 @@ import { Location } from '@angular/common';
|
|||||||
import { Component, DestroyRef, ElementRef, OnInit, ViewChild } from '@angular/core';
|
import { Component, DestroyRef, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||||
import { FormBuilder, FormControl, ValidatorFn } from '@angular/forms';
|
import { FormBuilder, FormControl, ValidatorFn } from '@angular/forms';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import {
|
import { debounceTime, defer, of, Observable, shareReplay, forkJoin, ObservedValueOf, EMPTY, ReplaySubject } from 'rxjs';
|
||||||
debounceTime,
|
|
||||||
defer,
|
|
||||||
of,
|
|
||||||
Observable,
|
|
||||||
shareReplay,
|
|
||||||
firstValueFrom,
|
|
||||||
forkJoin,
|
|
||||||
ObservedValueOf,
|
|
||||||
EMPTY,
|
|
||||||
ReplaySubject,
|
|
||||||
} from 'rxjs';
|
|
||||||
import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
|
import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
|
||||||
import { Gender } from 'src/app/proto/generated/zitadel/user_pb';
|
import { Gender } from 'src/app/proto/generated/zitadel/user_pb';
|
||||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
import { CountryCallingCodesService, CountryPhoneCode } from 'src/app/services/country-calling-codes.service';
|
import { CountryCallingCodesService, CountryPhoneCode } from 'src/app/services/country-calling-codes.service';
|
||||||
import { formatPhone } from 'src/app/utils/formatPhone';
|
import { formatPhone } from 'src/app/utils/formatPhone';
|
||||||
import {
|
import {
|
||||||
@ -34,15 +22,8 @@ import {
|
|||||||
requiredValidator,
|
requiredValidator,
|
||||||
} from 'src/app/modules/form-field/validators/validators';
|
} from 'src/app/modules/form-field/validators/validators';
|
||||||
import { LanguagesService } from 'src/app/services/languages.service';
|
import { LanguagesService } from 'src/app/services/languages.service';
|
||||||
import { UserService } from 'src/app/services/user.service';
|
|
||||||
import { AddHumanUserRequest } from 'src/app/proto/generated/zitadel/management_pb';
|
import { AddHumanUserRequest } from 'src/app/proto/generated/zitadel/management_pb';
|
||||||
import { AddHumanUserRequestSchema } from '@zitadel/proto/zitadel/user/v2/user_service_pb';
|
import { catchError, map, startWith } from 'rxjs/operators';
|
||||||
import { create } from '@bufbuild/protobuf';
|
|
||||||
import { SetHumanPhoneSchema } from '@zitadel/proto/zitadel/user/v2/phone_pb';
|
|
||||||
import { PasswordSchema } from '@zitadel/proto/zitadel/user/v2/password_pb';
|
|
||||||
import { SetHumanEmailSchema } from '@zitadel/proto/zitadel/user/v2/email_pb';
|
|
||||||
import { FeatureService } from 'src/app/services/feature.service';
|
|
||||||
import { catchError, filter, map, startWith, timeout } from 'rxjs/operators';
|
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -51,27 +32,26 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|||||||
styleUrls: ['./user-create.component.scss'],
|
styleUrls: ['./user-create.component.scss'],
|
||||||
})
|
})
|
||||||
export class UserCreateComponent implements OnInit {
|
export class UserCreateComponent implements OnInit {
|
||||||
public readonly genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
|
protected readonly genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
|
||||||
public selected: CountryPhoneCode | undefined = {
|
protected selected: CountryPhoneCode | undefined = {
|
||||||
countryCallingCode: '1',
|
countryCallingCode: '1',
|
||||||
countryCode: 'US',
|
countryCode: 'US',
|
||||||
countryName: 'United States of America',
|
countryName: 'United States of America',
|
||||||
};
|
};
|
||||||
public readonly countryPhoneCodes: CountryPhoneCode[];
|
protected readonly countryPhoneCodes: CountryPhoneCode[];
|
||||||
|
|
||||||
public loading = false;
|
protected loading = false;
|
||||||
|
|
||||||
private readonly suffix$ = new ReplaySubject<HTMLSpanElement>(1);
|
private readonly suffix$ = new ReplaySubject<HTMLSpanElement>(1);
|
||||||
@ViewChild('suffix') public set suffix(suffix: ElementRef<HTMLSpanElement>) {
|
@ViewChild('suffix') public set suffix(suffix: ElementRef<HTMLSpanElement>) {
|
||||||
this.suffix$.next(suffix.nativeElement);
|
this.suffix$.next(suffix.nativeElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
public usePassword: boolean = false;
|
protected usePassword: boolean = false;
|
||||||
protected readonly useV2Api$: Observable<boolean>;
|
|
||||||
protected readonly envSuffix$: Observable<string>;
|
protected readonly envSuffix$: Observable<string>;
|
||||||
protected readonly userForm: ReturnType<typeof this.buildUserForm>;
|
protected readonly userForm: ReturnType<typeof this.buildUserForm>;
|
||||||
protected readonly pwdForm$: ReturnType<typeof this.buildPwdForm>;
|
protected readonly pwdForm$: ReturnType<typeof this.buildPwdForm>;
|
||||||
protected readonly passwordComplexityPolicy$: Observable<PasswordComplexityPolicy.AsObject>;
|
protected readonly passwordComplexityPolicy$: Observable<PasswordComplexityPolicy.AsObject | undefined>;
|
||||||
protected readonly suffixPadding$: Observable<string>;
|
protected readonly suffixPadding$: Observable<string>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -79,15 +59,12 @@ export class UserCreateComponent implements OnInit {
|
|||||||
private readonly toast: ToastService,
|
private readonly toast: ToastService,
|
||||||
private readonly fb: FormBuilder,
|
private readonly fb: FormBuilder,
|
||||||
private readonly mgmtService: ManagementService,
|
private readonly mgmtService: ManagementService,
|
||||||
private readonly userService: UserService,
|
|
||||||
public readonly langSvc: LanguagesService,
|
|
||||||
private readonly featureService: FeatureService,
|
|
||||||
private readonly destroyRef: DestroyRef,
|
private readonly destroyRef: DestroyRef,
|
||||||
private readonly breadcrumbService: BreadcrumbService,
|
private readonly breadcrumbService: BreadcrumbService,
|
||||||
protected readonly location: Location,
|
protected readonly location: Location,
|
||||||
|
protected readonly langSvc: LanguagesService,
|
||||||
countryCallingCodesService: CountryCallingCodesService,
|
countryCallingCodesService: CountryCallingCodesService,
|
||||||
) {
|
) {
|
||||||
this.useV2Api$ = this.getUseV2Api().pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
|
||||||
this.envSuffix$ = this.getEnvSuffix();
|
this.envSuffix$ = this.getEnvSuffix();
|
||||||
this.suffixPadding$ = this.getSuffixPadding();
|
this.suffixPadding$ = this.getSuffixPadding();
|
||||||
this.passwordComplexityPolicy$ = this.getPasswordComplexityPolicy().pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
this.passwordComplexityPolicy$ = this.getPasswordComplexityPolicy().pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
||||||
@ -99,8 +76,6 @@ export class UserCreateComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// already start loading if we should use v2 api
|
|
||||||
this.useV2Api$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe();
|
|
||||||
this.watchPhoneChanges();
|
this.watchPhoneChanges();
|
||||||
|
|
||||||
this.breadcrumbService.setBreadcrumb([
|
this.breadcrumbService.setBreadcrumb([
|
||||||
@ -111,14 +86,6 @@ export class UserCreateComponent implements OnInit {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getUseV2Api(): Observable<boolean> {
|
|
||||||
return defer(() => this.featureService.getInstanceFeatures(true)).pipe(
|
|
||||||
map((features) => !!features.getConsoleUseV2UserApi()?.getEnabled()),
|
|
||||||
timeout(1000),
|
|
||||||
catchError(() => of(false)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getEnvSuffix() {
|
private getEnvSuffix() {
|
||||||
const domainPolicy$ = defer(() => this.mgmtService.getDomainPolicy());
|
const domainPolicy$ = defer(() => this.mgmtService.getDomainPolicy());
|
||||||
const orgDomains$ = defer(() => this.mgmtService.listOrgDomains());
|
const orgDomains$ = defer(() => this.mgmtService.listOrgDomains());
|
||||||
@ -147,7 +114,6 @@ export class UserCreateComponent implements OnInit {
|
|||||||
private getPasswordComplexityPolicy() {
|
private getPasswordComplexityPolicy() {
|
||||||
return defer(() => this.mgmtService.getPasswordComplexityPolicy()).pipe(
|
return defer(() => this.mgmtService.getPasswordComplexityPolicy()).pipe(
|
||||||
map(({ policy }) => policy),
|
map(({ policy }) => policy),
|
||||||
filter(Boolean),
|
|
||||||
catchError((error) => {
|
catchError((error) => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
@ -155,7 +121,7 @@ export class UserCreateComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public buildUserForm() {
|
private buildUserForm() {
|
||||||
return this.fb.group({
|
return this.fb.group({
|
||||||
email: new FormControl('', { nonNullable: true, validators: [requiredValidator, emailValidator] }),
|
email: new FormControl('', { nonNullable: true, validators: [requiredValidator, emailValidator] }),
|
||||||
userName: new FormControl('', { nonNullable: true, validators: [requiredValidator, minLengthValidator(2)] }),
|
userName: new FormControl('', { nonNullable: true, validators: [requiredValidator, minLengthValidator(2)] }),
|
||||||
@ -166,29 +132,26 @@ export class UserCreateComponent implements OnInit {
|
|||||||
preferredLanguage: new FormControl('', { nonNullable: true }),
|
preferredLanguage: new FormControl('', { nonNullable: true }),
|
||||||
phone: new FormControl('', { nonNullable: true, validators: [phoneValidator] }),
|
phone: new FormControl('', { nonNullable: true, validators: [phoneValidator] }),
|
||||||
emailVerified: new FormControl(false, { nonNullable: true }),
|
emailVerified: new FormControl(false, { nonNullable: true }),
|
||||||
sendEmail: new FormControl(true, { nonNullable: true }),
|
|
||||||
phoneVerified: new FormControl(false, { nonNullable: true }),
|
|
||||||
sendSms: new FormControl(true, { nonNullable: true }),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public buildPwdForm(passwordComplexityPolicy$: Observable<PasswordComplexityPolicy.AsObject>) {
|
private buildPwdForm(passwordComplexityPolicy$: Observable<PasswordComplexityPolicy.AsObject | undefined>) {
|
||||||
return passwordComplexityPolicy$.pipe(
|
return passwordComplexityPolicy$.pipe(
|
||||||
map((policy) => {
|
map((policy) => {
|
||||||
const validators: [ValidatorFn] = [requiredValidator];
|
const validators: ValidatorFn[] = [requiredValidator];
|
||||||
if (policy.minLength) {
|
if (policy?.minLength) {
|
||||||
validators.push(minLengthValidator(policy.minLength));
|
validators.push(minLengthValidator(policy.minLength));
|
||||||
}
|
}
|
||||||
if (policy.hasLowercase) {
|
if (policy?.hasLowercase) {
|
||||||
validators.push(containsLowerCaseValidator);
|
validators.push(containsLowerCaseValidator);
|
||||||
}
|
}
|
||||||
if (policy.hasUppercase) {
|
if (policy?.hasUppercase) {
|
||||||
validators.push(containsUpperCaseValidator);
|
validators.push(containsUpperCaseValidator);
|
||||||
}
|
}
|
||||||
if (policy.hasNumber) {
|
if (policy?.hasNumber) {
|
||||||
validators.push(containsNumberValidator);
|
validators.push(containsNumberValidator);
|
||||||
}
|
}
|
||||||
if (policy.hasSymbol) {
|
if (policy?.hasSymbol) {
|
||||||
validators.push(containsSymbolValidator);
|
validators.push(containsSymbolValidator);
|
||||||
}
|
}
|
||||||
return this.fb.group({
|
return this.fb.group({
|
||||||
@ -214,15 +177,7 @@ export class UserCreateComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createUser(pwdForm: ObservedValueOf<typeof this.pwdForm$>): Promise<void> {
|
protected async createUser(pwdForm: ObservedValueOf<typeof this.pwdForm$>): Promise<void> {
|
||||||
if (await firstValueFrom(this.useV2Api$)) {
|
|
||||||
await this.createUserV2(pwdForm);
|
|
||||||
} else {
|
|
||||||
await this.createUserV1(pwdForm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createUserV1(pwdForm: ObservedValueOf<typeof this.pwdForm$>): Promise<void> {
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
const controls = this.userForm.controls;
|
const controls = this.userForm.controls;
|
||||||
@ -258,107 +213,6 @@ export class UserCreateComponent implements OnInit {
|
|||||||
try {
|
try {
|
||||||
const data = await this.mgmtService.addHumanUser(humanReq);
|
const data = await this.mgmtService.addHumanUser(humanReq);
|
||||||
this.toast.showInfo('USER.TOAST.CREATED', true);
|
this.toast.showInfo('USER.TOAST.CREATED', true);
|
||||||
this.router.navigate(['users', data.userId], { queryParams: { new: true } }).then();
|
|
||||||
} catch (error) {
|
|
||||||
this.toast.showError(error);
|
|
||||||
} finally {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createUserV2(pwdForm: ObservedValueOf<typeof this.pwdForm$>): Promise<void> {
|
|
||||||
this.loading = true;
|
|
||||||
|
|
||||||
const controls = this.userForm.controls;
|
|
||||||
const humanReq = create(AddHumanUserRequestSchema, {
|
|
||||||
username: controls.userName.value,
|
|
||||||
profile: {
|
|
||||||
givenName: controls.firstName.value,
|
|
||||||
familyName: controls.lastName.value,
|
|
||||||
nickName: controls.nickName.value,
|
|
||||||
preferredLanguage: controls.preferredLanguage.value,
|
|
||||||
// the enum numbers of v1 gender are the same as v2 gender
|
|
||||||
gender: controls.gender.value as unknown as any,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.usePassword) {
|
|
||||||
const password = create(PasswordSchema, { password: pwdForm.controls.password.value });
|
|
||||||
humanReq.passwordType = { case: 'password', value: password };
|
|
||||||
}
|
|
||||||
if (controls.emailVerified.value) {
|
|
||||||
humanReq.email = create(SetHumanEmailSchema, {
|
|
||||||
email: controls.email.value,
|
|
||||||
verification: {
|
|
||||||
value: true,
|
|
||||||
case: 'isVerified',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (controls.sendEmail.value) {
|
|
||||||
humanReq.email = create(SetHumanEmailSchema, {
|
|
||||||
email: controls.email.value,
|
|
||||||
verification: {
|
|
||||||
case: 'sendCode',
|
|
||||||
value: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
humanReq.email = create(SetHumanEmailSchema, {
|
|
||||||
email: controls.email.value,
|
|
||||||
verification: {
|
|
||||||
value: false,
|
|
||||||
case: 'isVerified',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const phoneNumber = formatPhone(controls.phone.value);
|
|
||||||
if (phoneNumber) {
|
|
||||||
const country = phoneNumber.country;
|
|
||||||
this.selected = this.countryPhoneCodes.find((code) => code.countryCode === country);
|
|
||||||
if (controls.phoneVerified.value) {
|
|
||||||
humanReq.phone = create(SetHumanPhoneSchema, {
|
|
||||||
phone: phoneNumber.phone,
|
|
||||||
verification: {
|
|
||||||
case: 'isVerified',
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (controls.sendSms.value) {
|
|
||||||
humanReq.phone = create(SetHumanPhoneSchema, {
|
|
||||||
phone: phoneNumber.phone,
|
|
||||||
verification: {
|
|
||||||
case: 'sendCode',
|
|
||||||
value: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
humanReq.phone = create(SetHumanPhoneSchema, {
|
|
||||||
phone: phoneNumber.phone,
|
|
||||||
verification: {
|
|
||||||
case: 'isVerified',
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await this.userService.addHumanUser(humanReq);
|
|
||||||
if (this.sendEmailAfterCreation) {
|
|
||||||
await this.userService.passwordReset({
|
|
||||||
userId: data.userId,
|
|
||||||
medium: {
|
|
||||||
case: 'sendLink',
|
|
||||||
value: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.toast.showInfo('USER.TOAST.CREATED', true);
|
|
||||||
await this.router.navigate(['users', data.userId], { queryParams: { new: true } });
|
await this.router.navigate(['users', data.userId], { queryParams: { new: true } });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
@ -367,14 +221,7 @@ export class UserCreateComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get sendEmailAfterCreation() {
|
protected setCountryCallingCode(): void {
|
||||||
const controls = this.userForm.controls;
|
|
||||||
|
|
||||||
const sendEmailAfterCreationIsAOption = controls.emailVerified.value && !this.usePassword;
|
|
||||||
return sendEmailAfterCreationIsAOption && controls.sendEmail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setCountryCallingCode(): void {
|
|
||||||
let value = this.userForm.controls.phone.value;
|
let value = this.userForm.controls.phone.value;
|
||||||
this.countryPhoneCodes.forEach((code) => (value = value.replace(`+${code.countryCallingCode}`, '')));
|
this.countryPhoneCodes.forEach((code) => (value = value.replace(`+${code.countryCallingCode}`, '')));
|
||||||
value = value.trim();
|
value = value.trim();
|
||||||
@ -382,7 +229,7 @@ export class UserCreateComponent implements OnInit {
|
|||||||
this.userForm.controls.phone.setValue('+' + this.selected?.countryCallingCode + ' ' + value);
|
this.userForm.controls.phone.setValue('+' + this.selected?.countryCallingCode + ' ' + value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public compareCountries(i1: CountryPhoneCode, i2: CountryPhoneCode) {
|
protected compareCountries(i1: CountryPhoneCode, i2: CountryPhoneCode) {
|
||||||
return (
|
return (
|
||||||
i1 &&
|
i1 &&
|
||||||
i2 &&
|
i2 &&
|
||||||
|
@ -164,24 +164,14 @@ export class GrpcAuthService {
|
|||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.user = forkJoin([
|
this.user = this.oauthService.events.pipe(
|
||||||
defer(() => of(this.oauthService.getAccessToken())),
|
filter((e) => e.type === 'token_received'),
|
||||||
this.oauthService.events.pipe(
|
map(() => this.oauthService.getAccessToken()),
|
||||||
filter((e) => e.type === 'token_received'),
|
startWith(this.oauthService.getAccessToken()),
|
||||||
timeout(this.oauthService.waitForTokenInMsec ?? 0),
|
filter(Boolean),
|
||||||
catchError((err) => {
|
distinctUntilChanged(),
|
||||||
if (err instanceof TimeoutError) {
|
switchMap(() => this.getMyUser()),
|
||||||
return of(null);
|
map((user) => user.user),
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}), // timeout is not an error
|
|
||||||
map((_) => this.oauthService.getAccessToken()),
|
|
||||||
),
|
|
||||||
]).pipe(
|
|
||||||
filter(([_, token]) => !!token),
|
|
||||||
distinctUntilKeyChanged(1),
|
|
||||||
switchMap(() => this.getMyUser().then((resp) => resp.user)),
|
|
||||||
startWith(undefined),
|
|
||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user