mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-01 03:52:14 +00:00
fix(console): move org domains into settings page of the organization (#6612)
* fix: hide domains settings for unauthorized users * refine sidenav object mapping * move domains to settings * change docs * set anchor to list element * remove canwrite check in ngif --------- Co-authored-by: Miguel A. C <[email protected]>
This commit is contained in:
co-authored by
Miguel A. C
parent
eb31c2a3be
commit
d01f4d229f
@@ -158,14 +158,6 @@ const routes: Routes = [
|
||||
requiresAll: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'domains',
|
||||
loadChildren: () => import('./pages/domains/domains.module'),
|
||||
canActivate: [AuthGuard, RoleGuard],
|
||||
data: {
|
||||
roles: ['org.read'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'org-settings',
|
||||
loadChildren: () => import('./pages/org-settings/org-settings.module'),
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<ng-container *ngIf="['org.write$'] | hasRole as canwrite$">
|
||||
<div class="domain-top-view">
|
||||
<div>
|
||||
<div class="domain-title-row">
|
||||
<h2>{{ 'ORG.DOMAINS.TITLE' | translate }}</h2>
|
||||
<a
|
||||
mat-icon-button
|
||||
href="https://zitadel.com/docs/guides/manage/console/organizations#how-zitadel-handles-usernames"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<i class="las la-info-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
<p class="desc cnsl-secondary-text">{{ 'ORG.DOMAINS.DESCRIPTION' | translate }}</p>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
|
||||
<button
|
||||
[disabled]="(canwrite$ | async) === false"
|
||||
matTooltip="Add domain"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
class="cnsl-action-button"
|
||||
(click)="addNewDomain()"
|
||||
>
|
||||
<mat-icon>add</mat-icon>
|
||||
<span>{{ 'ACTIONS.NEW' | translate }}</span>
|
||||
<cnsl-action-keys (actionTriggered)="addNewDomain()"> </cnsl-action-keys>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<cnsl-card *ngFor="let domain of domains" class="domain-card">
|
||||
<div class="domain">
|
||||
<span class="title">{{ domain.domainName }}</span>
|
||||
|
||||
<i matTooltip="verified" *ngIf="domain.isVerified" class="verified las la-check-circle"></i>
|
||||
<i matTooltip="primary" *ngIf="domain.isPrimary" class="primary las la-star"></i>
|
||||
<a
|
||||
*ngIf="domain.isVerified && !domain.isPrimary && (canwrite$ | async)"
|
||||
class="primaryset"
|
||||
(click)="setPrimary(domain)"
|
||||
>{{ 'ORG.DOMAINS.SETPRIMARY' | translate }}</a
|
||||
>
|
||||
|
||||
<span class="fill-space"></span>
|
||||
<button
|
||||
mat-icon-button
|
||||
[disabled]="(canwrite$ | async) === false || domain.isVerified"
|
||||
*ngIf="verifyOrgDomains"
|
||||
(click)="verifyDomain(domain)"
|
||||
>
|
||||
<i class="las la-pen"></i>
|
||||
</button>
|
||||
<button
|
||||
class="domain-rem-button"
|
||||
[disabled]="(canwrite$ | async) === false || domain.isPrimary"
|
||||
matTooltip="Remove domain"
|
||||
color="warn"
|
||||
mat-icon-button
|
||||
(click)="removeDomain(domain.domainName)"
|
||||
>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</cnsl-card>
|
||||
</ng-container>
|
||||
-1
@@ -1,7 +1,6 @@
|
||||
.domain-top-view {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 2rem;
|
||||
|
||||
.domain-title-row {
|
||||
display: flex;
|
||||
+1
-2
@@ -14,13 +14,11 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
|
||||
|
||||
import { AddDomainDialogModule } from './add-domain-dialog/add-domain-dialog.module';
|
||||
import { DomainVerificationComponent } from './domain-verification/domain-verification.component';
|
||||
import { DomainsRoutingModule } from './domains-routing.module';
|
||||
import { DomainsComponent } from './domains.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [DomainsComponent, DomainVerificationComponent],
|
||||
imports: [
|
||||
DomainsRoutingModule,
|
||||
AddDomainDialogModule,
|
||||
CommonModule,
|
||||
MatIconModule,
|
||||
@@ -36,5 +34,6 @@ import { DomainsComponent } from './domains.component';
|
||||
InfoSectionModule,
|
||||
MatProgressSpinnerModule,
|
||||
],
|
||||
exports: [DomainsComponent],
|
||||
})
|
||||
export default class DomainsModule {}
|
||||
@@ -177,17 +177,6 @@
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<ng-template cnslHasRole [hasRole]="['org.read']">
|
||||
<a
|
||||
class="nav-item"
|
||||
[routerLinkActive]="['active']"
|
||||
[routerLink]="['/domains']"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<span class="label">{{ 'MENU.DOMAINS' | translate }}</span>
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<ng-template cnslHasRole [hasRole]="['org.read']">
|
||||
<a
|
||||
class="nav-item"
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<h2>{{ 'POLICY.DOMAIN_POLICY.TITLE' | translate }}</h2>
|
||||
<cnsl-info-section *ngIf="isDefault"> {{ 'POLICY.DEFAULTLABEL' | translate }}</cnsl-info-section>
|
||||
|
||||
<!-- <ng-template cnslHasRole [hasRole]="['domain.policy.delete']"> -->
|
||||
<button
|
||||
*ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
|
||||
matTooltip="{{ 'POLICY.RESET' | translate }}"
|
||||
@@ -26,7 +25,6 @@
|
||||
>
|
||||
{{ 'POLICY.RESET' | translate }}
|
||||
</button>
|
||||
<!-- </ng-template> -->
|
||||
|
||||
<div class="domain-policy-content" *ngIf="domainData">
|
||||
<div class="row">
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
<ng-container *ngIf="currentSetting === 'login'">
|
||||
<cnsl-login-policy [serviceType]="serviceType"></cnsl-login-policy>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="currentSetting === 'verified_domains'">
|
||||
<cnsl-domains></cnsl-domains>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="currentSetting === 'domain' && (['iam.policy.write'] | hasRole | async) === true">
|
||||
<cnsl-domain-policy [serviceType]="serviceType"></cnsl-domain-policy>
|
||||
</ng-container>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||
|
||||
import { CardModule } from '../card/card.module';
|
||||
import DomainsModule from '../domains/domains.module';
|
||||
import { DomainPolicyModule } from '../policies/domain-policy/domain-policy.module';
|
||||
import { GeneralSettingsModule } from '../policies/general-settings/general-settings.module';
|
||||
import { IdpSettingsModule } from '../policies/idp-settings/idp-settings.module';
|
||||
@@ -40,6 +41,7 @@ import { SettingsListComponent } from './settings-list.component';
|
||||
PrivacyPolicyModule,
|
||||
MessageTextsPolicyModule,
|
||||
SecurityPolicyModule,
|
||||
DomainsModule,
|
||||
LoginTextsPolicyModule,
|
||||
DomainPolicyModule,
|
||||
TranslateModule,
|
||||
|
||||
@@ -43,6 +43,15 @@ export const LOGIN: SidenavSetting = {
|
||||
},
|
||||
};
|
||||
|
||||
export const VERIFIED_DOMAINS: SidenavSetting = {
|
||||
id: 'verified_domains',
|
||||
i18nKey: 'SETTINGS.LIST.VERIFIED_DOMAINS',
|
||||
groupI18nKey: 'SETTINGS.GROUPS.DOMAIN',
|
||||
requiredRoles: {
|
||||
[PolicyComponentServiceType.MGMT]: ['org.read'],
|
||||
},
|
||||
};
|
||||
|
||||
export const DOMAIN: SidenavSetting = {
|
||||
id: 'domain',
|
||||
i18nKey: 'SETTINGS.LIST.DOMAIN',
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { DomainsComponent } from './domains.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: DomainsComponent,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class DomainsRoutingModule {}
|
||||
@@ -1,69 +0,0 @@
|
||||
<div class="max-width-container">
|
||||
<ng-container *ngIf="['org.write$'] | hasRole as canwrite$">
|
||||
<div class="domain-top-view">
|
||||
<div>
|
||||
<div class="domain-title-row">
|
||||
<h1>{{ 'ORG.DOMAINS.TITLE' | translate }}</h1>
|
||||
<a
|
||||
mat-icon-button
|
||||
href="https://zitadel.com/docs/guides/manage/console/organizations#how-zitadel-handles-usernames"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<i class="las la-info-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
<p class="desc cnsl-secondary-text">{{ 'ORG.DOMAINS.DESCRIPTION' | translate }}</p>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
|
||||
<button
|
||||
[disabled]="(canwrite$ | async) === false"
|
||||
matTooltip="Add domain"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
class="cnsl-action-button"
|
||||
(click)="addNewDomain()"
|
||||
>
|
||||
<mat-icon>add</mat-icon>
|
||||
<span>{{ 'ACTIONS.NEW' | translate }}</span>
|
||||
<cnsl-action-keys (actionTriggered)="addNewDomain()"> </cnsl-action-keys>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<cnsl-card *ngFor="let domain of domains" class="domain-card">
|
||||
<div class="domain">
|
||||
<span class="title">{{ domain.domainName }}</span>
|
||||
|
||||
<i matTooltip="verified" *ngIf="domain.isVerified" class="verified las la-check-circle"></i>
|
||||
<i matTooltip="primary" *ngIf="domain.isPrimary" class="primary las la-star"></i>
|
||||
<a
|
||||
*ngIf="domain.isVerified && !domain.isPrimary && (canwrite$ | async)"
|
||||
class="primaryset"
|
||||
(click)="setPrimary(domain)"
|
||||
>{{ 'ORG.DOMAINS.SETPRIMARY' | translate }}</a
|
||||
>
|
||||
|
||||
<span class="fill-space"></span>
|
||||
<button
|
||||
mat-icon-button
|
||||
[disabled]="(canwrite$ | async) === false || domain.isVerified"
|
||||
*ngIf="(canwrite$ | async) && verifyOrgDomains"
|
||||
(click)="verifyDomain(domain)"
|
||||
>
|
||||
<i class="las la-pen"></i>
|
||||
</button>
|
||||
<button
|
||||
class="domain-rem-button"
|
||||
[disabled]="(canwrite$ | async) === false || domain.isPrimary"
|
||||
matTooltip="Remove domain"
|
||||
color="warn"
|
||||
mat-icon-button
|
||||
(click)="removeDomain(domain.domainName)"
|
||||
>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</cnsl-card>
|
||||
</ng-container>
|
||||
</div>
|
||||
@@ -8,9 +8,11 @@
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
</div>
|
||||
<cnsl-settings-list
|
||||
[selectedId]="id"
|
||||
[serviceType]="PolicyComponentServiceType.ADMIN"
|
||||
[settingsList]="settingsList"
|
||||
></cnsl-settings-list>
|
||||
<ng-container *ngIf="settingsList | async as list">
|
||||
<cnsl-settings-list
|
||||
[selectedId]="id"
|
||||
[serviceType]="PolicyComponentServiceType.ADMIN"
|
||||
[settingsList]="list"
|
||||
></cnsl-settings-list>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { Observable, of, Subject, takeUntil } from 'rxjs';
|
||||
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
|
||||
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
SECRETS,
|
||||
SECURITY,
|
||||
} from '../../modules/settings-list/settings';
|
||||
import { checkSettingsPermissions } from '../org-settings/org-settings.component';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-instance-settings',
|
||||
@@ -55,7 +54,7 @@ export class InstanceSettingsComponent implements OnInit, OnDestroy {
|
||||
SECURITY,
|
||||
];
|
||||
|
||||
public settingsList: SidenavSetting[] = [];
|
||||
public settingsList: Observable<SidenavSetting[]> = of([]);
|
||||
|
||||
private destroy$: Subject<void> = new Subject();
|
||||
constructor(
|
||||
@@ -81,13 +80,7 @@ export class InstanceSettingsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
checkSettingsPermissions(this.defaultSettingsList, PolicyComponentServiceType.ADMIN, this.authService).subscribe(
|
||||
(allowed) => {
|
||||
this.settingsList = this.defaultSettingsList.filter((setting, index) => {
|
||||
return allowed[index];
|
||||
});
|
||||
},
|
||||
);
|
||||
this.settingsList = this.authService.isAllowedMapper(this.defaultSettingsList, (setting) => setting.requiredRoles.admin);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
</div>
|
||||
<cnsl-settings-list
|
||||
[selectedId]="id"
|
||||
[serviceType]="PolicyComponentServiceType.MGMT"
|
||||
[settingsList]="settingsList"
|
||||
></cnsl-settings-list>
|
||||
<ng-container *ngIf="settingsList | async as list">
|
||||
<cnsl-settings-list
|
||||
[selectedId]="id"
|
||||
[serviceType]="PolicyComponentServiceType.MGMT"
|
||||
[settingsList]="list"
|
||||
></cnsl-settings-list>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { forkJoin, of, take } from 'rxjs';
|
||||
import { Observable, of, take } from 'rxjs';
|
||||
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
|
||||
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
MESSAGETEXTS,
|
||||
NOTIFICATION_POLICY,
|
||||
PRIVACYPOLICY,
|
||||
VERIFIED_DOMAINS,
|
||||
} from '../../modules/settings-list/settings';
|
||||
|
||||
@Component({
|
||||
@@ -34,6 +35,7 @@ export class OrgSettingsComponent implements OnInit {
|
||||
COMPLEXITY,
|
||||
LOCKOUT,
|
||||
NOTIFICATION_POLICY,
|
||||
VERIFIED_DOMAINS,
|
||||
DOMAIN,
|
||||
BRANDING,
|
||||
MESSAGETEXTS,
|
||||
@@ -41,7 +43,7 @@ export class OrgSettingsComponent implements OnInit {
|
||||
PRIVACYPOLICY,
|
||||
];
|
||||
|
||||
public settingsList: SidenavSetting[] = [];
|
||||
public settingsList: Observable<Array<SidenavSetting>> = of([]);
|
||||
|
||||
constructor(
|
||||
breadcrumbService: BreadcrumbService,
|
||||
@@ -65,40 +67,8 @@ export class OrgSettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
checkSettingsPermissions(this.defaultSettingsList, PolicyComponentServiceType.MGMT, this.authService).subscribe(
|
||||
(allowed) => {
|
||||
this.settingsList = this.defaultSettingsList.filter((setting, index) => {
|
||||
return allowed[index];
|
||||
});
|
||||
},
|
||||
);
|
||||
this.settingsList = this.authService
|
||||
.isAllowedMapper(this.defaultSettingsList, (setting) => setting.requiredRoles.mgmt)
|
||||
.pipe(take(1));
|
||||
}
|
||||
}
|
||||
|
||||
// Return a Observables<boolean>[] that will wait till all service calls are finished to then check if user is allowed to see a setting
|
||||
export function checkSettingsPermissions(settings: SidenavSetting[], serviceType: string, authService: GrpcAuthService) {
|
||||
return forkJoin(
|
||||
settings
|
||||
.filter((setting) => {
|
||||
if (serviceType === PolicyComponentServiceType.ADMIN) {
|
||||
return setting.requiredRoles && setting.requiredRoles.admin;
|
||||
} else {
|
||||
return setting.requiredRoles && setting.requiredRoles.mgmt;
|
||||
}
|
||||
})
|
||||
.map((setting) => {
|
||||
if (!setting.requiredRoles) {
|
||||
return of(true);
|
||||
}
|
||||
|
||||
if (!setting.requiredRoles.mgmt) {
|
||||
return of(true);
|
||||
}
|
||||
|
||||
if (setting.requiredRoles.mgmt) {
|
||||
return authService.isAllowed(setting.requiredRoles.mgmt).pipe(take(1));
|
||||
}
|
||||
return of(false);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -309,10 +309,7 @@ export class GrpcAuthService {
|
||||
filter(([hL, p]) => {
|
||||
return hL === true && !!p.length;
|
||||
}),
|
||||
map(([_, zroles]) => {
|
||||
const what = this.hasRoles(zroles, roles, requiresAll);
|
||||
return what;
|
||||
}),
|
||||
map(([_, zroles]) => this.hasRoles(zroles, roles, requiresAll)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
} else {
|
||||
@@ -320,6 +317,31 @@ export class GrpcAuthService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* filters objects based on roles
|
||||
* @param objects array of objects
|
||||
* @param mapper mapping function which maps to a string[] or Regexp[] of roles
|
||||
* @param requiresAll wheter all, or just a single roles is required to fulfill
|
||||
*/
|
||||
public isAllowedMapper<T>(
|
||||
objects: T[],
|
||||
mapper: (attr: any) => string[] | RegExp[],
|
||||
requiresAll: boolean = false,
|
||||
): Observable<T[]> {
|
||||
return this.fetchedZitadelPermissions.pipe(
|
||||
withLatestFrom(this.zitadelPermissions),
|
||||
filter(([hL, p]) => {
|
||||
return hL === true && !!p.length;
|
||||
}),
|
||||
map(([_, zroles]) => {
|
||||
return objects.filter((obj) => {
|
||||
const roles = mapper(obj);
|
||||
return this.hasRoles(zroles, roles, requiresAll);
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if user has one of the provided roles
|
||||
* @param userRoles roles of the user
|
||||
|
||||
Reference in New Issue
Block a user