import { Component, Injector, OnDestroy, Type } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs'; import { GetOrgFeaturesResponse, SetDefaultFeaturesRequest, SetOrgFeaturesRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; import { ActionsAllowed, Features } from 'src/app/proto/generated/zitadel/features_pb'; import { GetFeaturesResponse } from 'src/app/proto/generated/zitadel/management_pb'; import { Org } from 'src/app/proto/generated/zitadel/org_pb'; import { AdminService } from 'src/app/services/admin.service'; import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service'; import { ManagementService } from 'src/app/services/mgmt.service'; import { StorageKey, StorageLocation, StorageService } from 'src/app/services/storage.service'; import { StripeCustomer, SubscriptionService } from 'src/app/services/subscription.service'; import { ToastService } from 'src/app/services/toast.service'; import { COUNTRIES, Country } from './country'; import { PaymentInfoDialogComponent } from './payment-info-dialog/payment-info-dialog.component'; export enum FeatureServiceType { MGMT, ADMIN, } @Component({ selector: 'cnsl-features', templateUrl: './features.component.html', styleUrls: ['./features.component.scss'], }) export class FeaturesComponent implements OnDestroy { private managementService!: ManagementService; public serviceType!: FeatureServiceType; public features!: Features.AsObject; private sub: Subscription = new Subscription(); public org!: Org.AsObject; public FeatureServiceType: any = FeatureServiceType; public customerLoading: boolean = false; public stripeLoading: boolean = false; public stripeURL: string = ''; public stripeCustomer!: StripeCustomer; public actionsSelection: any = [ ActionsAllowed.ACTIONS_ALLOWED_NOT_ALLOWED, ActionsAllowed.ACTIONS_ALLOWED_MAX, ActionsAllowed.ACTIONS_ALLOWED_UNLIMITED, ]; public ActionsAllowed: any = ActionsAllowed; constructor( private route: ActivatedRoute, private toast: ToastService, private storage: StorageService, private injector: Injector, private adminService: AdminService, private subService: SubscriptionService, private dialog: MatDialog, breadcrumbService: BreadcrumbService, ) { const iamBread = new Breadcrumb({ type: BreadcrumbType.IAM, name: 'IAM', routerLink: ['/system'], }); const bread: Breadcrumb = { type: BreadcrumbType.ORG, routerLink: ['/org'], }; breadcrumbService.setBreadcrumb([iamBread, bread]); const temporg: Org.AsObject | null = this.storage.getItem(StorageKey.organization, StorageLocation.session); if (temporg) { this.org = temporg; } const serviceType = this.route.snapshot.data.serviceType; if (serviceType !== undefined) { this.serviceType = serviceType; if (this.serviceType === FeatureServiceType.MGMT) { this.managementService = this.injector.get(ManagementService as Type); } if (this.serviceType === FeatureServiceType.MGMT) { this.customerLoading = true; this.subService .getCustomer(this.org.id) .then((payload) => { this.customerLoading = false; this.stripeCustomer = payload; if (this.customerValid) { this.getLinkToStripe(); } }) .catch((error) => { this.customerLoading = false; console.error(error); }); } } this.fetchData(); } public ngOnDestroy(): void { this.sub.unsubscribe(); } public setCustomer(): void { const dialogRefPhone = this.dialog.open(PaymentInfoDialogComponent, { data: { customer: this.stripeCustomer, }, width: '400px', }); dialogRefPhone.afterClosed().subscribe((customer) => { if (customer) { this.stripeCustomer = customer; this.subService .setCustomer(this.org.id, customer) .then(() => { this.getLinkToStripe(); }) .catch(console.error); } }); } public getLinkToStripe(): void { if (this.serviceType === FeatureServiceType.MGMT) { this.stripeLoading = true; this.subService .getLink(this.org.id, window.location.href) .then((payload) => { this.stripeLoading = false; this.stripeURL = payload.redirect_url; }) .catch((error) => { this.stripeLoading = false; console.error(error); }); } } public fetchData(): void { this.getData().then((resp) => { if (resp?.features) { this.features = resp.features; } }); } private async getData(): Promise { switch (this.serviceType) { case FeatureServiceType.MGMT: return this.managementService.getFeatures(); case FeatureServiceType.ADMIN: if (this.org?.id) { return this.adminService.getDefaultFeatures(); } else { return Promise.reject(); } } } public savePolicy(): void { switch (this.serviceType) { case FeatureServiceType.MGMT: const req = new SetOrgFeaturesRequest(); req.setOrgId(this.org.id); req.setLoginPolicyUsernameLogin(this.features.loginPolicyUsernameLogin); req.setLoginPolicyPasswordReset(this.features.loginPolicyPasswordReset); req.setLoginPolicyRegistration(this.features.loginPolicyRegistration); req.setLoginPolicyIdp(this.features.loginPolicyIdp); req.setLoginPolicyFactors(this.features.loginPolicyFactors); req.setLoginPolicyPasswordless(this.features.loginPolicyPasswordless); req.setPasswordComplexityPolicy(this.features.passwordComplexityPolicy); req.setLabelPolicyPrivateLabel(this.features.labelPolicyPrivateLabel); req.setLabelPolicyWatermark(this.features.labelPolicyWatermark); req.setCustomDomain(this.features.customDomain); req.setCustomTextLogin(this.features.customTextLogin); req.setCustomTextMessage(this.features.customTextMessage); req.setPrivacyPolicy(this.features.privacyPolicy); req.setMetadataUser(this.features.metadataUser); req.setLockoutPolicy(this.features.lockoutPolicy); req.setActionsAllowed(this.features.actionsAllowed); req.setMaxActions(this.features.maxActions); this.adminService .setOrgFeatures(req) .then(() => { this.toast.showInfo('POLICY.TOAST.SET', true); }) .catch((error) => { this.toast.showError(error); }); break; case FeatureServiceType.ADMIN: // update Default org iam policy? const dreq = new SetDefaultFeaturesRequest(); dreq.setLoginPolicyUsernameLogin(this.features.loginPolicyUsernameLogin); dreq.setLoginPolicyPasswordReset(this.features.loginPolicyPasswordReset); dreq.setLoginPolicyRegistration(this.features.loginPolicyRegistration); dreq.setLoginPolicyIdp(this.features.loginPolicyIdp); dreq.setLoginPolicyFactors(this.features.loginPolicyFactors); dreq.setLoginPolicyPasswordless(this.features.loginPolicyPasswordless); dreq.setPasswordComplexityPolicy(this.features.passwordComplexityPolicy); dreq.setLabelPolicyPrivateLabel(this.features.labelPolicyPrivateLabel); dreq.setLabelPolicyWatermark(this.features.labelPolicyWatermark); dreq.setCustomDomain(this.features.customDomain); dreq.setCustomTextLogin(this.features.customTextLogin); dreq.setCustomTextMessage(this.features.customTextMessage); dreq.setMetadataUser(this.features.metadataUser); dreq.setLockoutPolicy(this.features.lockoutPolicy); dreq.setActionsAllowed(this.features.actionsAllowed); dreq.setMaxActions(this.features.maxActions); this.adminService .setDefaultFeatures(dreq) .then(() => { this.toast.showInfo('POLICY.TOAST.SET', true); }) .catch((error) => { this.toast.showError(error); }); break; } } public resetFeatures(): void { if (this.serviceType === FeatureServiceType.MGMT) { this.adminService .resetOrgFeatures(this.org.id) .then(() => { this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true); setTimeout(() => { this.fetchData(); }, 1000); }) .catch((error) => { this.toast.showError(error); }); } } public get isDefault(): boolean { if (this.features && this.serviceType === FeatureServiceType.MGMT) { return this.features.isDefault; } else { return false; } } get customerValid(): boolean { return ( !!this.stripeCustomer?.contact && !!this.stripeCustomer?.address && !!this.stripeCustomer?.city && !!this.stripeCustomer?.postal_code ); } get customerCountry(): Country | undefined { return COUNTRIES.find((country) => country.isoCode === this.stripeCustomer.country); } }