feat: label policy (#1708)

* feat: label policy proto extension

* feat: label policy and activate event

* feat: label policy asset events

* feat: label policy asset commands

* feat: add storage key

* feat: storage key validation

* feat: label policy asset tests

* feat: label policy query side

* feat: avatar

* feat: avatar event

* feat: human avatar

* feat: avatar read side

* feat: font on iam label policy

* feat: label policy font

* feat: possiblity to create bucket on put file

* uplaoder

* login policy logo

* set bucket prefix

* feat: avatar upload

* feat: avatar upload

* feat: use assets on command side

* feat: fix human avatar removed event

* feat: remove human avatar

* feat: mock asset storage

* feat: remove human avatar

* fix(operator): add configuration of asset storage to zitadel operator

* feat(console): private labeling policy (#1697)

* private labeling component, routing, preview

* font, colors, upload, i18n

* show logo

* fix: uniqueness (#1710)

* fix: uniqueconstraint to lower

* feat: change org

* feat: org change test

* feat: change org

* fix: tests

* fix: handle domain claims correctly

* feat: update org

Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>

* fix: handle domain claimed event correctly for service users (#1711)

* fix: handle domain claimed event correctly on user view

* fix: ignore domain claimed events for email notifications

* fix: change org

* handle org changed in read models correctly

* fix: change org in user grant handler

Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>

* fix: correct value (#1695)

* docs(api): correct link (#1712)

* upload service

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
Co-authored-by: Florian Forster <florian@caos.ch>

* feat: fix tests,

* feat: remove assets from label policy

* fix npm, set environment

* lint ts

* remove stylelinting

* fix(operator): add mapping for console with changed unit tests

* fix(operator): add secrets as env variables to pod

* feat: remove human avatar

* fix(operator): add secrets as env variables to pod

* feat: map label policy

* feat: labelpolicy, admin, mgmt, adv settings (#1715)

* fetch label policy, mgmt, admin service

* feat: advanced beh, links, add, update

* lint ts

* feat: watermark

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: custom css

* css

* css

* css

* css

* css

* getobject

* feat: dynamic handler

* feat: varibale css

* content info

* css overwrite

* feat: variablen css

* feat: generate css file

* feat: dark mode

* feat: dark mode

* fix logo css

* feat: upload logos

* dark mode with cookie

* feat: handle images in login

* avatar css and begin font

* feat: avatar

* feat: user avatar

* caching of static assets in login

* add avatar.js to main.html

* feat: header dont show logo if no url

* feat: label policy colors

* feat: mock asset storage

* feat: mock asset storage

* feat: fix tests

* feat: user avatar

* feat: header logo

* avatar

* avatar

* make it compatible with go 1.15

* feat: remove unused logos

* fix handler

* fix: styling error handling

* fonts

* fix: download func

* switch to mux

* fix: change upload api to assets

* fix build

* fix: download avatar

* fix: download logos

* fix: my avatar

* font

* fix: remove error msg popup possibility

* fix: docs

* fix: svalidate colors

* rem msg popup from frontend

* fix: email with private labeling

* fix: tests

* fix: email templates

* fix: change migration version

* fix: fix duplicate imports

* fix(console): assets, service url, upload, policy current and preview  (#1781)

* upload endpoint, layout

* fetch current, preview, fix upload

* cleanup private labeling

* fix linting

* begin generated asset handler

* generate asset api in dockerfile

* features for label policy

* features for label policy

* features

* flag for asset generator

* change asset generator flag

* fix label policy view in grpc

* fix: layout, activate policy (#1786)

* theme switcher up on top

* change layout

* activate policy

* feat(console): label policy back color, layout (#1788)

* theme switcher up on top

* change layout

* activate policy

* fix overwrite value fc

* reset policy, reset service

* autosave policy, preview desc, layout impv

* layout, i18n

* background colors, inject material styles

* load images

* clean, lint

* fix layout

* set custom hex

* fix content size conversion

* remove font format in generated css

* fix features for assets

* fix(console): label policy colors, image downloads, preview (#1804)

* load images

* colors, images binding

* lint

* refresh emitter

* lint

* propagate font colors

* upload error handling

* label policy feature check

* add blob in csp for console

* log

* fix: feature edits for label policy, refresh state on upload (#1807)

* show error on load image, stop spinner

* fix merge

* fix migration versions

* fix assets

* fix csp

* fix background color

* scss

* fix build

* lint scss

* fix statik for console

* fix features check for label policy

* cleanup

* lint

* public links

* fix notifications

* public links

* feat: merge main

* feat: fix translation files

* fix migration

* set api domain

* fix logo in email

* font face in email

* font face in email

* validate assets on upload

* cleanup

* add missing translations

* add missing translations

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Stefan Benz <stefan@caos.ch>
Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Florian Forster <florian@caos.ch>
This commit is contained in:
Fabi 2021-06-04 14:53:51 +02:00 committed by GitHub
parent c0d9d86b09
commit 73d37459bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
257 changed files with 18248 additions and 7178 deletions

View File

@ -134,9 +134,13 @@ COPY --from=base /usr/local/bin /usr/local/bin/.
COPY build/zitadel/generate-grpc.sh build/zitadel/generate-grpc.sh
COPY internal/protoc internal/protoc
COPY openapi/statik openapi/statik
COPY internal/api/assets/generator internal/api/assets/generator
COPY internal/config internal/config
COPY internal/errors internal/errors
RUN build/zitadel/generate-grpc.sh \
&& go generate openapi/statik/generate.go
&& go generate openapi/statik/generate.go \
&& go run internal/api/assets/generator/asset_generator.go -directory=internal/api/assets/generator/
#######################
@ -163,6 +167,9 @@ COPY --from=go-stub /go/src/github.com/caos/zitadel/internal/protoc/protoc-gen-a
COPY --from=go-stub /go/src/github.com/caos/zitadel/internal/protoc/protoc-gen-authoption/authoption/options.pb.go internal/protoc/protoc-gen-authoption/authoption/options.pb.go
COPY --from=go-stub /go/src/github.com/caos/zitadel/docs/apis/proto docs/docs/apis/proto
COPY --from=go-stub /go/src/github.com/caos/zitadel/internal/api/assets/authz.go ./internal/api/assets/authz.go
COPY --from=go-stub /go/src/github.com/caos/zitadel/internal/api/assets/router.go ./internal/api/assets/router.go
#######################
## Go test

View File

@ -45,6 +45,7 @@ ZITADEL_AUTHORIZE=http://localhost:50002/oauth/v2
ZITADEL_OAUTH=http://localhost:50002/oauth/v2
ZITADEL_CONSOLE=http://localhost:4200
ZITADEL_COOKIE_DOMAIN=localhost
ZITADEL_API_DOMAIN=http://localhost:50002
#caching is used in UI's and API's
ZITADEL_CACHE_MAXAGE=12h

View File

@ -5,8 +5,10 @@ import (
"flag"
"github.com/caos/logging"
admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
"github.com/caos/zitadel/internal/api"
"github.com/caos/zitadel/internal/api/assets"
internal_authz "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/admin"
"github.com/caos/zitadel/internal/api/grpc/auth"
@ -20,11 +22,13 @@ import (
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/id"
mgmt_es "github.com/caos/zitadel/internal/management/repository/eventsourcing"
"github.com/caos/zitadel/internal/notification"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/setup"
"github.com/caos/zitadel/internal/static/s3"
"github.com/caos/zitadel/internal/static"
static_config "github.com/caos/zitadel/internal/static/config"
metrics "github.com/caos/zitadel/internal/telemetry/metrics/config"
tracing "github.com/caos/zitadel/internal/telemetry/tracing/config"
"github.com/caos/zitadel/internal/ui"
@ -37,7 +41,7 @@ type Config struct {
Log logging.Config
Tracing tracing.TracingConfig
Metrics metrics.MetricsConfig
AssetStorage s3.AssetStorage
AssetStorage static_config.AssetStorageConfig
InternalAuthZ internal_authz.Config
SystemDefaults sd.SystemDefaults
@ -72,6 +76,7 @@ var (
managementEnabled = flag.Bool("management", true, "enable management api")
authEnabled = flag.Bool("auth", true, "enable auth api")
oidcEnabled = flag.Bool("oidc", true, "enable oidc api")
assetsEnabled = flag.Bool("assets", true, "enable assets api")
loginEnabled = flag.Bool("login", true, "enable login ui")
consoleEnabled = flag.Bool("console", true, "enable console ui")
notificationEnabled = flag.Bool("notification", true, "enable notification handler")
@ -114,11 +119,15 @@ func startZitadel(configPaths []string) {
}
authZRepo, err := authz.Start(ctx, conf.AuthZ, conf.InternalAuthZ, conf.SystemDefaults, queries)
logging.Log("MAIN-s9KOw").OnError(err).Fatal("error starting authz repo")
verifier := internal_authz.Start(authZRepo)
esCommands, err := eventstore.StartWithUser(conf.EventstoreBase, conf.Commands.Eventstore)
if err != nil {
logging.Log("ZITAD-iRCMm").OnError(err).Fatal("cannot start eventstore for commands")
}
commands, err := command.StartCommands(esCommands, conf.SystemDefaults, conf.InternalAuthZ, authZRepo)
store, err := conf.AssetStorage.Config.NewStorage()
logging.Log("ZITAD-Bfhe2").OnError(err).Fatal("Unable to start asset storage")
commands, err := command.StartCommands(esCommands, conf.SystemDefaults, conf.InternalAuthZ, store, authZRepo)
if err != nil {
logging.Log("ZITAD-bmNiJ").OnError(err).Fatal("cannot start commands")
}
@ -128,21 +137,21 @@ func startZitadel(configPaths []string) {
logging.Log("MAIN-9oRw6").OnError(err).Fatal("error starting auth repo")
}
startAPI(ctx, conf, authZRepo, authRepo, commands, queries)
startUI(ctx, conf, authRepo, commands, queries)
startAPI(ctx, conf, verifier, authZRepo, authRepo, commands, queries, store)
startUI(ctx, conf, authRepo, commands, queries, store)
if *notificationEnabled {
notification.Start(ctx, conf.Notification, conf.SystemDefaults, commands)
notification.Start(ctx, conf.Notification, conf.SystemDefaults, commands, store != nil)
}
<-ctx.Done()
logging.Log("MAIN-s8d2h").Info("stopping zitadel")
}
func startUI(ctx context.Context, conf *Config, authRepo *auth_es.EsRepository, command *command.Commands, query *query.Queries) {
func startUI(ctx context.Context, conf *Config, authRepo *auth_es.EsRepository, command *command.Commands, query *query.Queries, staticStorage static.Storage) {
uis := ui.Create(conf.UI)
if *loginEnabled {
login, prefix := login.Start(conf.UI.Login, command, query, authRepo, conf.SystemDefaults, *localDevMode)
login, prefix := login.Start(conf.UI.Login, command, query, authRepo, staticStorage, conf.SystemDefaults, *localDevMode)
uis.RegisterHandler(prefix, login.Handler())
}
if *consoleEnabled {
@ -153,12 +162,12 @@ func startUI(ctx context.Context, conf *Config, authRepo *auth_es.EsRepository,
uis.Start(ctx)
}
func startAPI(ctx context.Context, conf *Config, authZRepo *authz_repo.EsRepository, authRepo *auth_es.EsRepository, command *command.Commands, query *query.Queries) {
func startAPI(ctx context.Context, conf *Config, verifier *internal_authz.TokenVerifier, authZRepo *authz_repo.EsRepository, authRepo *auth_es.EsRepository, command *command.Commands, query *query.Queries, static static.Storage) {
roles := make([]string, len(conf.InternalAuthZ.RolePermissionMappings))
for i, role := range conf.InternalAuthZ.RolePermissionMappings {
roles[i] = role.Role
}
repo, err := admin_es.Start(ctx, conf.Admin, conf.SystemDefaults, roles)
repo, err := admin_es.Start(ctx, conf.Admin, conf.SystemDefaults, static, roles, *localDevMode)
logging.Log("API-D42tq").OnError(err).Fatal("error starting auth repo")
apis := api.Create(conf.API, conf.InternalAuthZ, authZRepo, authRepo, repo, conf.SystemDefaults)
@ -166,9 +175,9 @@ func startAPI(ctx context.Context, conf *Config, authZRepo *authz_repo.EsReposit
if *adminEnabled {
apis.RegisterServer(ctx, admin.CreateServer(command, query, repo, conf.SystemDefaults.Domain))
}
managementRepo, err := mgmt_es.Start(conf.Mgmt, conf.SystemDefaults, roles, query, static)
logging.Log("API-Gd2qq").OnError(err).Fatal("error starting management repo")
if *managementEnabled {
managementRepo, err := mgmt_es.Start(conf.Mgmt, conf.SystemDefaults, roles, query)
logging.Log("API-Gd2qq").OnError(err).Fatal("error starting management repo")
apis.RegisterServer(ctx, management.CreateServer(command, query, managementRepo, conf.SystemDefaults))
}
if *authEnabled {
@ -178,6 +187,10 @@ func startAPI(ctx context.Context, conf *Config, authZRepo *authz_repo.EsReposit
op := oidc.NewProvider(ctx, conf.API.OIDC, command, query, authRepo, conf.SystemDefaults.KeyConfig.EncryptionConfig, *localDevMode)
apis.RegisterHandler("/oauth/v2", op.HttpHandler())
}
if *assetsEnabled {
assetsHandler := assets.NewHandler(command, verifier, conf.InternalAuthZ, id.SonyFlakeGenerator, static, managementRepo)
apis.RegisterHandler("/assets/v1", assetsHandler)
}
openAPIHandler, err := openapi.Start()
logging.Log("ZITAD-8pRk1").OnError(err).Fatal("Unable to start openapi handler")
@ -196,7 +209,7 @@ func startSetup(configPaths []string) {
es, err := eventstore.Start(conf.Eventstore)
logging.Log("MAIN-Ddt3").OnError(err).Fatal("cannot start eventstore")
commands, err := command.StartCommands(es, conf.SystemDefaults, conf.InternalAuthZ, nil)
commands, err := command.StartCommands(es, conf.SystemDefaults, conf.InternalAuthZ, nil, nil)
logging.Log("MAIN-dsjrr").OnError(err).Fatal("cannot start command side")
err = setup.Execute(ctx, conf.SetUp, conf.SystemDefaults.IamID, commands)

File diff suppressed because one or more lines are too long

View File

@ -19,6 +19,7 @@ AssetStorage:
SecretAccessKey: $ZITADEL_ASSET_STORAGE_SECRET_ACCESS_KEY
SSL: $ZITADEL_ASSET_STORAGE_SSL
Location: $ZITADEL_ASSET_STORAGE_LOCATION
BucketPrefix: $ZITADEL_ASSET_STORAGE_BUCKET_PREFIX
Metrics:
Type: 'otel'
@ -279,6 +280,10 @@ UI:
Cache:
MaxAge: $ZITADEL_CACHE_MAXAGE
SharedMaxAge: $ZITADEL_CACHE_SHARED_MAXAGE
StaticCache:
Type: bigcache
Config:
MaxCacheSizeInByte: 52428800 #50MB
Console:
EnvOverwriteDir: $ZITADEL_CONSOLE_ENV_DIR
ShortCache:
@ -290,6 +295,7 @@ UI:
CSPDomain: $ZITADEL_DEFAULT_DOMAIN
Notification:
APIDomain: $ZITADEL_API_DOMAIN
Repository:
DefaultLanguage: 'de'
Domain: $ZITADEL_DEFAULT_DOMAIN

View File

@ -26,6 +26,7 @@ import { from, Observable } from 'rxjs';
import { OnboardingModule } from 'src/app/modules/onboarding/onboarding.module';
import { RegExpPipeModule } from 'src/app/pipes/regexp-pipe/regexp-pipe.module';
import { SubscriptionService } from 'src/app/services/subscription.service';
import { UploadService } from 'src/app/services/upload.service';
import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
@ -180,6 +181,7 @@ const authConfig: AuthConfig = {
AuthenticationService,
GrpcAuthService,
SubscriptionService,
UploadService,
{ provide: 'windowObject', useValue: window },
],
bootstrap: [AppComponent],

View File

@ -0,0 +1,28 @@
import { Directive, EventEmitter, HostListener, Output } from '@angular/core';
@Directive({
selector: '[cnslDropzone]',
})
export class DropzoneDirective {
@Output() dropped: EventEmitter<FileList> = new EventEmitter<FileList>();
@Output() hovered: EventEmitter<boolean> = new EventEmitter<boolean>();
@HostListener('drop', ['$event'])
onDrop($event: DragEvent): void {
$event.preventDefault();
this.dropped.emit($event.dataTransfer?.files);
this.hovered.emit(false);
}
@HostListener('dragover', ['$event'])
onDragOver($event: any): void {
$event.preventDefault();
this.hovered.emit(true);
}
@HostListener('dragleave', ['$event'])
onDragLeave($event: any): void {
$event.preventDefault();
this.hovered.emit(false);
}
}

View File

@ -0,0 +1,19 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { DropzoneDirective } from './dropzone.directive';
@NgModule({
declarations: [
DropzoneDirective,
],
imports: [
CommonModule,
],
exports: [
DropzoneDirective,
],
})
export class DropzoneModule { }

View File

@ -120,9 +120,17 @@
</div>
<div class="row">
<span class="left-desc">{{'FEATURES.DATA.LABELPOLICY' | translate}}</span>
<span class="left-desc">{{'FEATURES.DATA.LABELPOLICYPRIVATELABEL' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="features.labelPolicy"
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="features.labelPolicyPrivateLabel"
[disabled]="(['iam.features.write'] | hasRole | async) == false">
</mat-slide-toggle>
</div>
<div class="row">
<span class="left-desc">{{'FEATURES.DATA.LABELPOLICYWATERMARK' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="features.labelPolicyWatermark"
[disabled]="(['iam.features.write'] | hasRole | async) == false">
</mat-slide-toggle>
</div>

View File

@ -158,7 +158,8 @@ export class FeaturesComponent implements OnDestroy {
req.setLoginPolicyFactors(this.features.loginPolicyFactors);
req.setLoginPolicyPasswordless(this.features.loginPolicyPasswordless);
req.setPasswordComplexityPolicy(this.features.passwordComplexityPolicy);
req.setLabelPolicy(this.features.labelPolicy);
req.setLabelPolicyPrivateLabel(this.features.labelPolicyPrivateLabel);
req.setLabelPolicyWatermark(this.features.labelPolicyWatermark);
req.setCustomDomain(this.features.customDomain);
this.adminService.setOrgFeatures(req).then(() => {
@ -177,7 +178,8 @@ export class FeaturesComponent implements OnDestroy {
dreq.setLoginPolicyFactors(this.features.loginPolicyFactors);
dreq.setLoginPolicyPasswordless(this.features.loginPolicyPasswordless);
dreq.setPasswordComplexityPolicy(this.features.passwordComplexityPolicy);
dreq.setLabelPolicy(this.features.labelPolicy);
dreq.setLabelPolicyPrivateLabel(this.features.labelPolicyPrivateLabel);
dreq.setLabelPolicyWatermark(this.features.labelPolicyWatermark);
dreq.setCustomDomain(this.features.customDomain);
this.adminService.setDefaultFeatures(dreq).then(() => {

View File

@ -4,6 +4,7 @@
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$is-dark-theme: map-get($theme, is-dark);
$foreground: map-get($theme, foreground);
.ng-untouched {
.cnsl-error {
@ -29,6 +30,10 @@
vertical-align: middle;
}
input {
color: mat.get-color-from-palette($foreground, text);
}
// Wrapper for the hints and error messages.
.cnsl-form-field-subscript-wrapper {
position: absolute;

View File

@ -8,17 +8,18 @@
}
.row {
width: 100%;
display: flex;
overflow-x: auto;
flex-wrap: wrap;
padding-bottom: .5rem;
margin: 0 -.5rem;
margin-bottom: 2rem;
.step {
min-width: 220px;
max-width: 280px;
padding: 1rem;
margin: 0 .5rem;
border: 1px solid var(--grey);
margin: 1rem .5rem;
border: 1px solid #8795a150;
border-radius: .5rem;
display: flex;
flex-direction: column;
@ -26,6 +27,10 @@
box-sizing: border-box;
flex: 1;
@media only screen and (min-width: 899px) {
max-width: 280px;
}
h6 {
font-size: 1rem;
text-align: center;
@ -46,14 +51,6 @@
display: block;
margin: auto;
}
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
}
}
}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LabelPolicyComponent } from './label-policy.component';
const routes: Routes = [
{
path: '',
component: LabelPolicyComponent,
data: {
animation: 'DetailPage',
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class LabelPolicyRoutingModule { }

View File

@ -1,19 +0,0 @@
<app-detail-layout [backRouterLink]="['/iam/policies']" [title]="'POLICY.LABEL.TITLE' | translate"
[description]="'POLICY.LABEL.DESCRIPTION' | translate">
<p class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</p>
<div class="content" *ngIf="labelData">
<mat-slide-toggle class="toggle" color="primary" ngDefaultControl
[(ngModel)]="labelData.hideLoginNameSuffix">
{{'POLICY.DATA.HIDELOGINNAMESUFFIX' | translate}}
</mat-slide-toggle>
</div>
<div class="btn-container">
<button (click)="savePolicy()" color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}</button>
</div>
<cnsl-links *ngIf="nextLinks" [links]="nextLinks"></cnsl-links>
</app-detail-layout>

View File

@ -1,23 +0,0 @@
.content {
padding-top: 1rem;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.default {
color: var(--color-main);
margin-top: 0;
}
.btn-container {
display: flex;
justify-content: flex-end;
width: 100%;
button {
margin-top: 3rem;
display: block;
padding: .5rem 4rem;
}
}

View File

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { LabelPolicyComponent } from './label-policy.component';
describe('LabelPolicyComponent', () => {
let component: LabelPolicyComponent;
let fixture: ComponentFixture<LabelPolicyComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [LabelPolicyComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LabelPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,71 +0,0 @@
import { Component, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { GetLabelPolicyResponse, UpdateLabelPolicyRequest } from 'src/app/proto/generated/zitadel/admin_pb';
import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ToastService } from 'src/app/services/toast.service';
import { CnslLinks } from '../../links/links.component';
import { IAM_COMPLEXITY_LINK, IAM_LOGIN_POLICY_LINK, IAM_POLICY_LINK } from '../../policy-grid/policy-links';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'app-label-policy',
templateUrl: './label-policy.component.html',
styleUrls: ['./label-policy.component.scss'],
})
export class LabelPolicyComponent implements OnDestroy {
public labelData!: LabelPolicy.AsObject;
private sub: Subscription = new Subscription();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public nextLinks: CnslLinks[] = [
IAM_COMPLEXITY_LINK,
IAM_POLICY_LINK,
IAM_LOGIN_POLICY_LINK,
];
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private adminService: AdminService,
) {
this.route.params.subscribe(() => {
this.getData().then(data => {
if (data?.policy) {
this.labelData = data.policy;
}
});
});
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
private async getData(): Promise<GetLabelPolicyResponse.AsObject> {
return this.adminService.getLabelPolicy();
}
public savePolicy(): void {
const req = new UpdateLabelPolicyRequest();
req.setPrimaryColor(this.labelData.primaryColor);
req.setSecondaryColor(this.labelData.secondaryColor);
req.setHideLoginNameSuffix(this.labelData.hideLoginNameSuffix);
this.adminService.updateLabelPolicy(req).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
}
public get isDefault(): boolean {
if (this.labelData) {
return (this.labelData as LabelPolicy.AsObject).isDefault;
} else {
return false;
}
}
}

View File

@ -1,36 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { InfoSectionModule } from '../../info-section/info-section.module';
import { LinksModule } from '../../links/links.module';
import { LabelPolicyRoutingModule } from './label-policy-routing.module';
import { LabelPolicyComponent } from './label-policy.component';
@NgModule({
declarations: [LabelPolicyComponent],
imports: [
LabelPolicyRoutingModule,
CommonModule,
FormsModule,
InputModule,
MatButtonModule,
MatSlideToggleModule,
MatIconModule,
HasRoleModule,
MatTooltipModule,
TranslateModule,
DetailLayoutModule,
LinksModule,
InfoSectionModule,
],
})
export class LabelPolicyModule { }

View File

@ -22,10 +22,11 @@ import { ToastService } from 'src/app/services/toast.service';
import { CnslLinks } from '../../links/links.component';
import {
IAM_COMPLEXITY_LINK,
IAM_LABEL_LINK,
IAM_POLICY_LINK,
IAM_PRIVATELABEL_LINK,
ORG_COMPLEXITY_LINK,
ORG_IAM_POLICY_LINK,
ORG_PRIVATELABEL_LINK,
} from '../../policy-grid/policy-links';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
import { AddIdpDialogComponent } from './add-idp-dialog/add-idp-dialog.component';
@ -69,6 +70,7 @@ export class LoginPolicyComponent implements OnDestroy {
this.nextLinks = [
ORG_COMPLEXITY_LINK,
ORG_IAM_POLICY_LINK,
ORG_PRIVATELABEL_LINK,
];
break;
case PolicyComponentServiceType.ADMIN:
@ -80,7 +82,7 @@ export class LoginPolicyComponent implements OnDestroy {
this.nextLinks = [
IAM_COMPLEXITY_LINK,
IAM_POLICY_LINK,
IAM_LABEL_LINK,
IAM_PRIVATELABEL_LINK,
];
break;
}
@ -96,7 +98,7 @@ export class LoginPolicyComponent implements OnDestroy {
if (resp.policy) {
this.loginData = resp.policy;
this.loading = false;
this.disabled = this.isDefault ?? false;
this.disabled = ((this.loginData as LoginPolicy.AsObject)?.isDefault) ?? false;
}
});
this.getIdps().then(resp => {

View File

@ -13,141 +13,143 @@ import { ToastService } from 'src/app/services/toast.service';
import { CnslLinks } from '../../links/links.component';
import {
IAM_COMPLEXITY_LINK,
IAM_LABEL_LINK,
IAM_LOGIN_POLICY_LINK,
ORG_COMPLEXITY_LINK,
ORG_LOGIN_POLICY_LINK,
IAM_COMPLEXITY_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_PRIVATELABEL_LINK,
ORG_COMPLEXITY_LINK,
ORG_LOGIN_POLICY_LINK,
ORG_PRIVATELABEL_LINK,
} from '../../policy-grid/policy-links';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'app-org-iam-policy',
templateUrl: './org-iam-policy.component.html',
styleUrls: ['./org-iam-policy.component.scss'],
selector: 'app-org-iam-policy',
templateUrl: './org-iam-policy.component.html',
styleUrls: ['./org-iam-policy.component.scss'],
})
export class OrgIamPolicyComponent implements OnDestroy {
private managementService!: ManagementService;
public serviceType!: PolicyComponentServiceType;
private managementService!: ManagementService;
public serviceType!: PolicyComponentServiceType;
public iamData!: OrgIAMPolicy.AsObject;
public iamData!: OrgIAMPolicy.AsObject;
private sub: Subscription = new Subscription();
private org!: Org.AsObject;
private sub: Subscription = new Subscription();
private org!: Org.AsObject;
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public nextLinks: Array<CnslLinks> = [];
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private sessionStorage: StorageService,
private injector: Injector,
private adminService: AdminService,
) {
const temporg = this.sessionStorage.getItem('organization') as Org.AsObject;
if (temporg) {
this.org = temporg;
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public nextLinks: Array<CnslLinks> = [];
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private sessionStorage: StorageService,
private injector: Injector,
private adminService: AdminService,
) {
const temporg = this.sessionStorage.getItem('organization') as Org.AsObject;
if (temporg) {
this.org = temporg;
}
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
if (this.serviceType === PolicyComponentServiceType.MGMT) {
this.managementService = this.injector.get(ManagementService as Type<ManagementService>);
this.nextLinks = [
ORG_COMPLEXITY_LINK,
ORG_LOGIN_POLICY_LINK,
ORG_PRIVATELABEL_LINK,
];
} else {
this.nextLinks = [
IAM_COMPLEXITY_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_PRIVATELABEL_LINK,
];
}
return this.route.params;
})).subscribe(_ => {
this.fetchData();
});
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
public fetchData(): void {
this.getData().then(resp => {
if (resp?.policy) {
this.iamData = resp.policy;
}
});
}
private async getData(): Promise<GetCustomOrgIAMPolicyResponse.AsObject | GetOrgIAMPolicyResponse.AsObject | undefined> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.managementService.getOrgIAMPolicy();
case PolicyComponentServiceType.ADMIN:
if (this.org?.id) {
return this.adminService.getCustomOrgIAMPolicy(this.org.id);
}
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
if (this.serviceType === PolicyComponentServiceType.MGMT) {
this.managementService = this.injector.get(ManagementService as Type<ManagementService>);
this.nextLinks = [
ORG_COMPLEXITY_LINK,
ORG_LOGIN_POLICY_LINK,
];
} else {
this.nextLinks = [
IAM_COMPLEXITY_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_LABEL_LINK,
];
}
return this.route.params;
})).subscribe(_ => {
this.fetchData();
});
break;
}
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
public fetchData(): void {
this.getData().then(resp => {
if (resp?.policy) {
this.iamData = resp.policy;
}
});
}
private async getData(): Promise<GetCustomOrgIAMPolicyResponse.AsObject | GetOrgIAMPolicyResponse.AsObject | undefined> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.managementService.getOrgIAMPolicy();
case PolicyComponentServiceType.ADMIN:
if (this.org?.id) {
return this.adminService.getCustomOrgIAMPolicy(this.org.id);
}
break;
}
}
public savePolicy(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.iamData as OrgIAMPolicy.AsObject).isDefault) {
this.adminService.addCustomOrgIAMPolicy(
this.org.id,
this.iamData.userLoginMustBeDomain,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
} else {
this.adminService.updateCustomOrgIAMPolicy(
this.org.id,
this.iamData.userLoginMustBeDomain,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
case PolicyComponentServiceType.ADMIN:
// update Default org iam policy?
this.adminService.updateOrgIAMPolicy(
this.iamData.userLoginMustBeDomain,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public removePolicy(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
this.adminService.resetCustomOrgIAMPolicyToDefault(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.iamData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.iamData as OrgIAMPolicy.AsObject).isDefault;
public savePolicy(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.iamData as OrgIAMPolicy.AsObject).isDefault) {
this.adminService.addCustomOrgIAMPolicy(
this.org.id,
this.iamData.userLoginMustBeDomain,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
} else {
return false;
this.adminService.updateCustomOrgIAMPolicy(
this.org.id,
this.iamData.userLoginMustBeDomain,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
case PolicyComponentServiceType.ADMIN:
// update Default org iam policy?
this.adminService.updateOrgIAMPolicy(
this.iamData.userLoginMustBeDomain,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public removePolicy(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
this.adminService.resetCustomOrgIAMPolicyToDefault(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.iamData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.iamData as OrgIAMPolicy.AsObject).isDefault;
} else {
return false;
}
}
}

View File

@ -3,10 +3,10 @@ import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import {
GetPasswordComplexityPolicyResponse as AdminGetPasswordComplexityPolicyResponse,
GetPasswordComplexityPolicyResponse as AdminGetPasswordComplexityPolicyResponse,
} from 'src/app/proto/generated/zitadel/admin_pb';
import {
GetPasswordComplexityPolicyResponse as MgmtGetPasswordComplexityPolicyResponse,
GetPasswordComplexityPolicyResponse as MgmtGetPasswordComplexityPolicyResponse,
} from 'src/app/proto/generated/zitadel/management_pb';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
@ -15,163 +15,165 @@ import { ToastService } from 'src/app/services/toast.service';
import { CnslLinks } from '../../links/links.component';
import {
IAM_LABEL_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_POLICY_LINK,
ORG_IAM_POLICY_LINK,
ORG_LOGIN_POLICY_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_POLICY_LINK,
IAM_PRIVATELABEL_LINK,
ORG_IAM_POLICY_LINK,
ORG_LOGIN_POLICY_LINK,
ORG_PRIVATELABEL_LINK,
} from '../../policy-grid/policy-links';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'app-password-policy',
templateUrl: './password-complexity-policy.component.html',
styleUrls: ['./password-complexity-policy.component.scss'],
selector: 'app-password-policy',
templateUrl: './password-complexity-policy.component.html',
styleUrls: ['./password-complexity-policy.component.scss'],
})
export class PasswordComplexityPolicyComponent implements OnDestroy {
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public service!: ManagementService | AdminService;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public service!: ManagementService | AdminService;
public complexityData!: PasswordComplexityPolicy.AsObject;
public complexityData!: PasswordComplexityPolicy.AsObject;
private sub: Subscription = new Subscription();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
private sub: Subscription = new Subscription();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public loading: boolean = false;
public nextLinks: CnslLinks[] = [];
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private injector: Injector,
) {
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
public loading: boolean = false;
public nextLinks: CnslLinks[] = [];
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private injector: Injector,
) {
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
this.nextLinks = [
ORG_IAM_POLICY_LINK,
ORG_LOGIN_POLICY_LINK,
];
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
this.nextLinks = [
IAM_POLICY_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_LABEL_LINK,
];
break;
}
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
this.nextLinks = [
ORG_IAM_POLICY_LINK,
ORG_LOGIN_POLICY_LINK,
ORG_PRIVATELABEL_LINK,
];
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
this.nextLinks = [
IAM_POLICY_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_PRIVATELABEL_LINK,
];
break;
}
return this.route.params;
})).subscribe(() => {
this.fetchData();
});
return this.route.params;
})).subscribe(() => {
this.fetchData();
});
}
public fetchData(): void {
this.loading = true;
this.getData().then(data => {
if (data.policy) {
this.complexityData = data.policy;
this.loading = false;
}
});
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
private async getData():
Promise<MgmtGetPasswordComplexityPolicyResponse.AsObject | AdminGetPasswordComplexityPolicyResponse.AsObject> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).getPasswordComplexityPolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).getPasswordComplexityPolicy();
}
}
public fetchData(): void {
this.loading = true;
this.getData().then(data => {
if (data.policy) {
this.complexityData = data.policy;
this.loading = false;
}
});
public removePolicy(): void {
if (this.service instanceof ManagementService) {
this.service.resetPasswordComplexityPolicyToDefault().then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.fetchData();
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
public incrementLength(): void {
if (this.complexityData?.minLength !== undefined && this.complexityData?.minLength <= 72) {
this.complexityData.minLength++;
}
}
private async getData():
Promise<MgmtGetPasswordComplexityPolicyResponse.AsObject | AdminGetPasswordComplexityPolicyResponse.AsObject> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).getPasswordComplexityPolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).getPasswordComplexityPolicy();
}
public decrementLength(): void {
if (this.complexityData?.minLength && this.complexityData?.minLength > 1) {
this.complexityData.minLength--;
}
}
public removePolicy(): void {
if (this.service instanceof ManagementService) {
this.service.resetPasswordComplexityPolicyToDefault().then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.fetchData();
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
public savePolicy(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.complexityData as PasswordComplexityPolicy.AsObject).isDefault) {
(this.service as ManagementService).addCustomPasswordComplexityPolicy(
public incrementLength(): void {
if (this.complexityData?.minLength !== undefined && this.complexityData?.minLength <= 72) {
this.complexityData.minLength++;
}
}
public decrementLength(): void {
if (this.complexityData?.minLength && this.complexityData?.minLength > 1) {
this.complexityData.minLength--;
}
}
public savePolicy(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.complexityData as PasswordComplexityPolicy.AsObject).isDefault) {
(this.service as ManagementService).addCustomPasswordComplexityPolicy(
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
} else {
(this.service as ManagementService).updateCustomPasswordComplexityPolicy(
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
}
break;
case PolicyComponentServiceType.ADMIN:
(this.service as AdminService).updatePasswordComplexityPolicy(
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public get isDefault(): boolean {
if (this.complexityData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.complexityData as PasswordComplexityPolicy.AsObject).isDefault;
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
} else {
return false;
(this.service as ManagementService).updateCustomPasswordComplexityPolicy(
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
}
break;
case PolicyComponentServiceType.ADMIN:
(this.service as AdminService).updatePasswordComplexityPolicy(
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public get isDefault(): boolean {
if (this.complexityData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.complexityData as PasswordComplexityPolicy.AsObject).isDefault;
} else {
return false;
}
}
}

View File

@ -1,12 +1,12 @@
export enum PolicyComponentType {
LOCKOUT = 'lockout',
AGE = 'age',
COMPLEXITY = 'complexity',
IAM = 'iam',
LOGIN = 'login',
LABEL = 'label',
LOCKOUT = 'lockout',
AGE = 'age',
COMPLEXITY = 'complexity',
IAM = 'iam',
LOGIN = 'login',
PRIVATELABEL = 'privatelabel',
}
export enum PolicyComponentServiceType {
MGMT = 'mgmt',
ADMIN = 'admin',
MGMT = 'mgmt',
ADMIN = 'admin',
}

View File

@ -0,0 +1,15 @@
<div class="form-row">
<cnsl-form-field class="formfield">
<cnsl-label>{{name}} (current {{color}})</cnsl-label>
<input cnslInput [(ngModel)]="previewColor" (keydown.enter)="name ? emitPreview(previewColor) : null" />
</cnsl-form-field>
<button matTooltip="{{'ACTIONS.SET' | translate}}" (click)="emitPreview(previewColor)" mat-icon-button><mat-icon>check</mat-icon></button>
</div>
<div class="color-selector">
<div *ngFor="let c of colors" class="circle mat-elevation-z3"
[ngClass]="{'active': color == c.color || previewColor == c.color}"
(click)="emitPreview(c.color)" [ngStyle]="{'background-color': c.color}">
<span *ngIf="previewColor == c.color">P</span>
<span *ngIf="color == c.color">C</span>
</div>
</div>

View File

@ -0,0 +1,52 @@
.title {
// font-size: 1rem;
display: block;
font-size: 18px;
&.border {
border-top: 1px solid var(--grey);
padding-top: 1.5rem;
}
}
.form-row {
display: flex;
align-items: flex-end;
.formfield {
flex: 1;
}
button {
margin-bottom: .9rem;
}
}
.color-selector {
display: flex;
align-items: center;
flex-wrap: wrap;
margin: 0 -.25rem;
width: 100%;
padding-bottom: 1rem;
.circle {
height: 30px;
width: 30px;
background-color: #cd5c5c;
border-radius: 50%;
margin: .25rem;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
span {
font-size: 18px;
}
&.active {
border: 3px solid #ffffff90;
}
}
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ColorComponent } from './color.component';
describe('ColorComponent', () => {
let component: ColorComponent;
let fixture: ComponentFixture<ColorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ColorComponent],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ColorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,118 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ColorType } from '../private-labeling-policy.component';
@Component({
selector: 'cnsl-color',
templateUrl: './color.component.html',
styleUrls: ['./color.component.scss'],
})
export class ColorComponent implements OnInit {
public PRIMARY: Array<{ name: string; color: string; }> = [
{ name: 'red', color: '#f44336' },
{ name: 'pink', color: '#e91e63' },
{ name: 'purple', color: '#9c27b0' },
{ name: 'deeppurple', color: '#673ab7' },
{ name: 'indigo', color: '#3f51b5' },
{ name: 'blue', color: '#2196f3' },
{ name: 'lightblue', color: '#03a9f4' },
{ name: 'cyan', color: '#00bcd4' },
{ name: 'teal', color: '#009688' },
{ name: 'green', color: '#4caf50' },
{ name: 'lightgreen', color: '#8bc34a' },
{ name: 'lime', color: '#cddc39' },
{ name: 'yellow', color: '#ffeb3b' },
{ name: 'amber', color: '#ffc107' },
{ name: 'orange', color: '#fb8c00' },
{ name: 'deeporange', color: '#ff5722' },
{ name: 'brown', color: '#795548' },
{ name: 'grey', color: '#9e9e9e' },
{ name: 'bluegrey', color: '#607d8b' },
{ name: 'white', color: '#ffffff' },
];
public WARN: Array<{ name: string; color: string; }> = [
{ name: 'red', color: '#f44336' },
{ name: 'pink', color: '#e91e63' },
{ name: 'purple', color: '#9c27b0' },
{ name: 'deeppurple', color: '#673ab7' },
];
public FONTLIGHT: Array<{ name: string; color: string; }> = [
{ name: 'gray-500', color: '#6b7280' },
{ name: 'gray-600', color: '#4b5563' },
{ name: 'gray-700', color: '#374151' },
{ name: 'gray-800', color: '#1f2937' },
{ name: 'gray-900', color: '#111827' },
{ name: 'black', color: '#000000' },
];
public FONTDARK: Array<{ name: string; color: string; }> = [
{ name: 'white', color: '#ffffff' },
{ name: 'gray-50', color: '#f9fafb' },
{ name: 'gray-100', color: '#f3f4f6' },
{ name: 'gray-200', color: '#e5e7eb' },
{ name: 'gray-300', color: '#d1d5db' },
{ name: 'gray-400', color: '#9ca3af' },
{ name: 'gray-500', color: '#6b7280' },
];
public BACKGROUNDLIGHT: Array<{ name: string; color: string; }> = [
{ name: 'white', color: '#ffffff' },
{ name: 'gray-50', color: '#f9fafb' },
{ name: 'gray-100', color: '#f3f4f6' },
{ name: 'gray-200', color: '#e5e7eb' },
{ name: 'gray-300', color: '#d1d5db' },
{ name: 'gray-400', color: '#9ca3af' },
{ name: 'gray-500', color: '#6b7280' },
];
public BACKGROUNDDARK: Array<{ name: string; color: string; }> = [
{ name: 'gray-500', color: '#6b7280' },
{ name: 'gray-600', color: '#4b5563' },
{ name: 'gray-700', color: '#374151' },
{ name: 'gray-800', color: '#1f2937' },
{ name: 'gray-900', color: '#111827' },
{ name: 'black', color: '#000000' },
];
public colors: Array<{ name: string; color: string; }> = this.PRIMARY;
@Input() colorType: ColorType = ColorType.PRIMARY;
@Input() color: string = '';
@Input() previewColor: string = '';
@Input() name: string = '';
@Output() previewChanged: EventEmitter<string> = new EventEmitter();
public emitPreview(color: string): void {
this.previewColor = color;
this.previewChanged.emit(this.previewColor);
}
ngOnInit(): void {
switch (this.colorType) {
case ColorType.PRIMARY:
this.colors = this.PRIMARY;
break;
case ColorType.WARN:
this.colors = this.WARN;
break;
case ColorType.FONTDARK:
this.colors = this.FONTDARK;
break;
case ColorType.FONTLIGHT:
this.colors = this.FONTLIGHT;
break;
case ColorType.BACKGROUNDDARK:
this.colors = this.BACKGROUNDDARK;
break;
case ColorType.BACKGROUNDLIGHT:
this.colors = this.BACKGROUNDLIGHT;
break;
default:
this.colors = this.PRIMARY;
break;
}
}
}

View File

@ -0,0 +1,30 @@
<div class="preview" *ngIf="policy">
<span class="label">{{label}}</span>
<div class="dashed" [ngClass]="{'dark': theme === Theme.DARK, 'light': theme === Theme.LIGHT}" [style.background]="theme == Theme.DARK ? policy.backgroundColorDark : policy.backgroundColor">
<div class="login-wrapper" [style.color]="theme == Theme.DARK ? policy.fontColorDark : policy.fontColor">
<img *ngIf="images['previewLogo'] && theme == Theme.LIGHT" [src]="images['previewLogo']" alt="logo-mock" />
<img *ngIf="!images['previewLogo'] && theme == Theme.LIGHT" src="../../../../assets/images/zitadel-logo-dark.svg" alt="logo-mock" />
<img *ngIf="images['previewDarkLogo'] && theme == Theme.DARK" [src]="images['previewDarkLogo']" alt="logo-mock" />
<img *ngIf="!images['previewDarkLogo'] && theme == Theme.DARK" src="../../../../assets/images/zitadel-logo-light.svg" alt="logo-mock" />
<h1>{{'POLICY.PRIVATELABELING.PREVIEW.TITLE' | translate}}</h1>
<p class="desc-text">{{'POLICY.PRIVATELABELING.PREVIEW.SECOND' | translate}}</p>
<cnsl-form-field class="formfield">
<cnsl-label>Loginname</cnsl-label>
<input cnslInput value="road.runner"/>
</cnsl-form-field>
<div class="error-msg" [style.color]="theme == Theme.DARK ? policy.warnColorDark : policy.warnColor">
<i class="las la-exclamation-circle" [style.color]="theme == Theme.DARK ? policy.warnColorDark : policy.warnColor"></i>
<span [style.color]="theme == Theme.DARK ? policy.warnColorDark : policy.warnColor">{{'POLICY.PRIVATELABELING.PREVIEW.ERROR' | translate}}</span>
</div>
<div class="btn-wrapper">
<button mat-stroked-button>{{'POLICY.PRIVATELABELING.PREVIEW.SECONDARYBUTTON' | translate}}</button>
<button *ngIf="theme == Theme.DARK" mat-raised-button [style.background]="policy.primaryColorDark" [style.color]="policy.primaryColorDark == '#ffffff' ? '#000000' : '#ffffff'">{{'POLICY.PRIVATELABELING.PREVIEW.PRIMARYBUTTON' | translate}}</button>
<button *ngIf="theme == Theme.LIGHT" mat-raised-button [style.background]="policy.primaryColor" [style.color]="policy.primaryColor == '#ffffff' ? '#000000' : '#ffffff'">{{'POLICY.PRIVATELABELING.PREVIEW.PRIMARYBUTTON' | translate}}</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,92 @@
.preview {
position: absolute;
left: 0;
width: 100%;
pointer-events: none;
margin-bottom: 2rem;
* {
pointer-events: none;
}
.label {
color: var(--grey);
font-size: 12px;
position: absolute;
right: 1rem;
top: 1rem;
}
.dashed {
border: 2px dashed rgba(#697386, .5);
border-radius: 1rem;
padding: 100px 20px;
.login-wrapper {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 360px;
margin: auto;
@media only screen and (min-width: 1000px) {
width: 360px;
}
img {
max-width: 160px;
max-height: 150px;
margin-bottom: 1rem;
}
h1 {
font-size: 20px;
}
.formfield {
width: 100%;
}
.btn-wrapper {
display: flex;
width: 100%;
justify-content: space-between;
}
.error-msg {
align-self: flex-start;
display: flex;
flex-direction: row;
align-items: center;
outline: none;
justify-content: flex-start;
i {
margin-right: .5rem;
}
span {
font-size: 14px;
margin: 1rem 0;
}
}
}
&.light {
background: #fff;
.login-wrapper *:not(.cnsl-label):not(.mat-button-wrapper) {
color: #000;
}
}
&.dark {
background: #212224;
.login-wrapper *:not(.cnsl-label):not(.mat-button-wrapper) {
color: #fff;
}
}
}
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PreviewComponent } from './preview.component';
describe('PreviewComponent', () => {
let component: PreviewComponent;
let fixture: ComponentFixture<PreviewComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [PreviewComponent],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PreviewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,35 @@
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Observable, of, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { Preview, Theme } from '../private-labeling-policy.component';
@Component({
selector: 'cnsl-preview',
templateUrl: './preview.component.html',
styleUrls: ['./preview.component.scss'],
})
export class PreviewComponent implements OnInit, OnDestroy {
@Input() preview: Preview = Preview.PREVIEW;
@Input() policy!: LabelPolicy.AsObject;
@Input() label: string = 'PREVIEW';
@Input() images: { [imagekey: string]: any; } = {};
@Input() theme: Theme = Theme.DARK;
@Input() refresh: Observable<void> = of();
private destroyed$: Subject<void> = new Subject();
public Theme: any = Theme;
public Preview: any = Preview;
constructor(private chd: ChangeDetectorRef) { }
public ngOnInit(): void {
this.refresh.pipe(takeUntil(this.destroyed$)).subscribe(() => {
this.chd.detectChanges();
});
}
public ngOnDestroy(): void {
this.destroyed$.next();
this.destroyed$.complete();
}
}

View File

@ -0,0 +1,20 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PrivateLabelingPolicyComponent } from './private-labeling-policy.component';
const routes: Routes = [
{
path: '',
component: PrivateLabelingPolicyComponent,
data: {
animation: 'DetailPage',
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class PrivateLabelingPolicyRoutingModule { }

View File

@ -0,0 +1,254 @@
<div class="policy enlarged-container">
<div class="header">
<a [routerLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
<div class="col">
<h1>{{'POLICY.PRIVATELABELING.TITLE' | translate}}</h1>
<p>{{'POLICY.PRIVATELABELING.DESCRIPTION' | translate}}</p>
</div>
</div>
<p class="desc">{{'POLICY.PRIVATELABELING.PREVIEW_DESCRIPTION' | translate}}</p>
<p class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</p>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<ng-template appHasRole [appHasRole]="['policy.delete']">
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
</button>
</ng-template>
<div class="top-row">
<div>
<p>{{'POLICY.PRIVATELABELING.THEME' | translate}}</p>
<div class="theme-changer">
<button (click)="theme = Theme.LIGHT" matTooltip="{{'POLICY.PRIVATELABELING.LIGHT' | translate}}" class="light" [ngClass]="{'active': theme === Theme.LIGHT}" >
<i class="icon las la-edit"></i>
</button>
<button (click)="theme = Theme.DARK" matTooltip="{{'POLICY.PRIVATELABELING.DARK' | translate}}" class="dark" [ngClass]="{'active': theme === Theme.DARK}" >
<i class="icon las la-edit"></i>
</button>
</div>
</div>
<button class="activate-button" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button color="primary" (click)="activatePolicy()">
{{'POLICY.PRIVATELABELING.ACTIVATEPREVIEW' | translate}}
</button>
</div>
<div *ngIf="previewData && data" class="lab-policy-content">
<mat-accordion class="settings">
<mat-expansion-panel class="expansion">
<mat-expansion-panel-header>
<mat-panel-title>
<div class="panel-title">
<i class="icon las la-image"></i>
Logo</div>
</mat-panel-title>
<mat-panel-description>
</mat-panel-description>
</mat-expansion-panel-header>
<p class="description">Your Logo will be used in the Login itself, while the icon is used for smaller UI elements like in the organisation switcher in console</p>
<!-- <span class="title">{{ theme === Theme.DARK ? ('POLICY.PRIVATELABELING.DARK' | translate) : ('POLICY.PRIVATELABELING.LIGHT' | translate)}}</span> -->
<div class="logo-setup-wrapper">
<div class="part">
<span class="label">Logo</span>
<mat-spinner class="spinner" color="primary" diameter="25" *ngIf="loadingImages"></mat-spinner>
<container [ngSwitch]="theme">
<div class="logo-view" *ngSwitchCase="Theme.DARK">
<img matTooltip="Preview" class="prev" *ngIf="images['previewDarkLogo']" [src]="images['previewDarkLogo']" alt="dark logo preview"/>
<span class="fill-space"></span>
<img matTooltip="Current" class="curr" *ngIf="images['darkLogo']" [src]="images['darkLogo']" alt="dark logo"/>
</div>
<div class="logo-view" *ngSwitchCase="Theme.LIGHT">
<img matTooltip="Preview" class="prev" *ngIf="images['previewLogo']" [src]="images['previewLogo']" alt="logo preview"/>
<span class="fill-space"></span>
<img matTooltip="Current" class="curr" *ngIf="images['logo']" [src]="images['logo']" alt="logo"/>
</div>
</container>
<div class="dropzone" cnslDropzone (hovered)="toggleHoverLogo(theme, $event)"
(dropped)="onDropLogo(theme, $event)"
[class.hovering]="isHoveringOverDarkLogo">
<label class="file-label">
<input #selectedFile style="display: none;" class="file-input" type="file" (change)="onDropLogo(theme, $event.target.files)">
<input class="btn" mat-raised-button type="button" [value]="'POLICY.PRIVATELABELING.BTN' | translate" (click)="selectedFile.click();" />
<i class="icon las la-cloud-upload-alt"></i>
<span>{{isHoveringOverDarkLogo ? 'Release': 'Drop your Logo here'}}</span>
</label>
</div>
</div>
<div class="part">
<span class="label">Icon</span>
<mat-spinner class="spinner" color="primary" diameter="25" *ngIf="loadingImages"></mat-spinner>
<container [ngSwitch]="theme">
<div class="logo-view" *ngSwitchCase="Theme.DARK">
<img matTooltip="Preview" class="prev" *ngIf="images['previewDarkIcon']" [src]="images['previewDarkIcon']" alt="dark icon preview"/>
<span class="fill-space"></span>
<img matTooltip="Current" class="curr" *ngIf="images['darkIcon']" [src]="images['darkIcon']" alt="dark icon"/>
</div>
<div class="logo-view" *ngSwitchCase="Theme.LIGHT">
<img matTooltip="Preview" class="prev" *ngIf="images['previewIcon']" [src]="images['previewIcon']" alt="icon preview"/>
<span class="fill-space"></span>
<img matTooltip="Current" class="curr" *ngIf="images['icon']" [src]="images['icon']" alt="icon"/>
</div>
</container>
<div class="dropzone" cnslDropzone (hovered)="toggleHoverIcon(theme, $event)"
(dropped)="onDropIcon(theme, $event)"
[class.hovering]="isHoveringOverDarkIcon">
<label class="file-label">
<input #selectedFileIcon style="display: none;" class="file-input" type="file" (change)="onDropIcon(theme, $event.target.files)">
<input class="btn" mat-raised-button type="button" [value]="'POLICY.PRIVATELABELING.BTN' | translate" (click)="selectedFileIcon.click();" />
<i class="icon las la-cloud-upload-alt"></i>
<span>{{isHoveringOverDarkIcon ? 'Release': 'Drop your Logo here'}}</span>
</label>
</div>
</div>
</div>
</mat-expansion-panel>
<mat-expansion-panel class="expansion" [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div class="panel-title">
<i class="icon las la-palette"></i>
{{'POLICY.PRIVATELABELING.COLORS' | translate}}</div>
</mat-panel-title>
</mat-expansion-panel-header>
<!-- <span class="title">{{ theme === Theme.DARK ? ('POLICY.PRIVATELABELING.DARK' | translate) : ('POLICY.PRIVATELABELING.LIGHT' | translate)}}</span> -->
<ng-container *ngIf="theme==Theme.DARK">
<div class="colors" *ngIf="data && previewData">
<div class="color">
<cnsl-color [colorType]="ColorType.BACKGROUNDDARK"(previewChanged)="previewData.backgroundColorDark = $event; savePolicy()" name="Background Color Dark" [color]="data.backgroundColorDark" [previewColor]="previewData.backgroundColorDark"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.PRIMARY"(previewChanged)="previewData.primaryColorDark = $event; savePolicy()" name="Preview Primary Color Dark" [color]="data.primaryColorDark" [previewColor]="previewData.primaryColorDark"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.WARN" (previewChanged)="previewData.warnColorDark = $event; savePolicy()" name="Preview Warn Color Dark" [color]="data.warnColorDark" [previewColor]="previewData.warnColorDark"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.FONTDARK"(previewChanged)="previewData.fontColorDark = $event; savePolicy()" name="Font Color Dark" [color]="data.fontColorDark" [previewColor]="previewData.fontColorDark"></cnsl-color>
</div>
</div>
</ng-container>
<ng-container *ngIf="theme==Theme.LIGHT">
<div class="colors" *ngIf="data && previewData">
<div class="color">
<cnsl-color [colorType]="ColorType.BACKGROUNDLIGHT" (previewChanged)="previewData.backgroundColor = $event; savePolicy()" name="Background Color Light" [color]="data.backgroundColor" [previewColor]="previewData.backgroundColor"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.PRIMARY" (previewChanged)="previewData.primaryColor = $event; savePolicy()" name="Preview Primary Color Light" [color]="data.primaryColor" [previewColor]="previewData.primaryColor"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.WARN" name="Preview Warn Color Light" (previewChanged)="previewData.warnColor= $event; savePolicy()" [color]="data.warnColor" [previewColor]="previewData.warnColor"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.FONTLIGHT" (previewChanged)="previewData.fontColor = $event; savePolicy()" name="Font Color" [color]="data.fontColor" [previewColor]="previewData.fontColor"></cnsl-color>
</div>
</div>
</ng-container>
</mat-expansion-panel>
<mat-expansion-panel class="expansion">
<mat-expansion-panel-header class="header">
<mat-panel-title>
<div class="panel-title">
<i class="icon las la-font"></i>
{{'POLICY.PRIVATELABELING.FONT' | translate}}</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div class="fonts">
<div class="font-preview">
<mat-icon>text_fields</mat-icon>
<span>ABC • abc • 123</span>
<span>_</span>
<span>Upload your favorite font for the UI</span>
</div>
<div class="dropzone" cnslDropzone (hovered)="toggleHoverFont($event)"
(dropped)="onDropFont($event)"
[class.hovering]="isHoveringOverFont">
<label class="file-label">
<input #selectedFontFile style="display: none;" class="file-input" type="file" (change)="onDropFont($event.target.files)">
<input class="btn" mat-raised-button type="button" [value]="'POLICY.PRIVATELABELING.BTN' | translate" (click)="selectedFontFile.click();" />
<i class="icon las la-cloud-upload-alt"></i>
<span >{{isHoveringOverFont ? 'Release': 'Drop your Logo here'}}</span>
</label>
</div>
</div>
</mat-expansion-panel>
<mat-expansion-panel class="expansion">
<mat-expansion-panel-header>
<mat-panel-title>
<div class="panel-title">
<i class="icon las la-universal-access"></i>
{{'POLICY.PRIVATELABELING.ADVANCEDBEHAVIOR' | translate}}
</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div class="adv-container" *ngIf="previewData">
<ng-container
*ngIf="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false">
<cnsl-info-section class="info" type="WARN">{{'FEATURES.NOTAVAILABLE' | translate: ({value:
'label_policy.private_label'})}}
</cnsl-info-section>
</ng-container>
<mat-slide-toggle class="toggle" color="primary" ngDefaultControl
[(ngModel)]="previewData.hideLoginNameSuffix" (change)="savePolicy()">
{{'POLICY.DATA.HIDELOGINNAMESUFFIX' | translate}}
</mat-slide-toggle>
<ng-container
*ngIf="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.watermark'] | hasFeature | async) == false">
<cnsl-info-section class="info" type="WARN">{{'FEATURES.NOTAVAILABLE' | translate: ({value:
'label_policy.watermark'})}}
</cnsl-info-section>
</ng-container>
<mat-slide-toggle class="toggle" color="primary" ngDefaultControl [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.watermark'] | hasFeature | async) == false"
[(ngModel)]="previewData.disableWatermark" (change)="saveWatermark()">
{{'POLICY.DATA.DISABLEWATERMARK' | translate}}
</mat-slide-toggle>
</div>
</mat-expansion-panel>
</mat-accordion>
<div class="preview-wrapper">
<!-- <cnsl-preview class="prev" label="CURRENT CONFIG" [policy]="data"></cnsl-preview> -->
<div class="col">
<button color="primary" mat-raised-button class="preview-changer" (click)="preview = preview == Preview.PREVIEW ? Preview.CURRENT : Preview.PREVIEW" matTooltip="{{'POLICY.PRIVATELABELING.CHANGEVIEW' | translate}}" [ngClass]="{'active': preview === Preview.PREVIEW}" >
<span><span [ngClass]="{'strong': preview == Preview.PREVIEW}">P</span> / <span [ngClass]="{'strong': preview == Preview.CURRENT}">C</span></span>
</button>
<cnsl-preview *ngIf="preview === Preview.CURRENT" [refresh]="refreshPreview" [images]="images" [preview]="preview" [theme]="theme" class="prev" label="CURRENT" [policy]="data"></cnsl-preview>
<cnsl-preview *ngIf="preview === Preview.PREVIEW" [refresh]="refreshPreview" [images]="images" [preview]="preview" [theme]="theme" class="prev" label="PREVIEW" [policy]="previewData"></cnsl-preview>
</div>
</div>
</div>
<cnsl-links *ngIf="nextLinks" [links]="nextLinks"></cnsl-links>
</div>

View File

@ -0,0 +1,398 @@
@use '~@angular/material' as mat;
@import './preview/preview.component.scss';
@mixin private-label-theme($theme) {
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$is-dark-theme: map-get($theme, is-dark);
.policy {
.header {
display: flex;
a {
margin: 0 1rem;
}
.col {
display: flex;
flex-direction: column;
margin-left: 1rem;
h1 {
margin: 0;
}
}
}
.default {
color: var(--color-main);
margin-top: 0;
}
.desc {
font-size: 14px;
color: var(--grey);
max-width: 800px;
margin-bottom: 2rem;
}
.spinner-wr {
margin: .5rem 0;
}
.theme-changer {
display: flex;
margin: 1rem -.5rem;
button {
height: 40px;
width: 40px;
border-radius: 50%;
border: 1px solid #81868a50;
margin: 0 .5rem;
.icon {
visibility: hidden;
}
}
.dark {
background-color: black;
&.active {
.icon {
visibility: visible;
color: white;
}
}
}
.light {
background-color: white;
&.active {
.icon {
visibility: visible;
color: black;
}
}
}
}
.top-row {
display: flex;
justify-content: space-between;
.activate-button {
border-radius: 50%;
align-self: flex-end;
}
.theme-changer {
display: flex;
margin: 0 -.25rem;
button {
height: 40px;
width: 40px;
border-radius: 50%;
border: 1px solid #81868a50;
margin: 0 .25rem;
display: flex;
align-items: center;
justify-content: center;
.icon {
visibility: hidden;
}
}
.dark {
background-color: black;
&.active {
transform: scale(1.1);
.icon {
visibility: visible;
color: white;
}
}
}
.light {
background-color: white;
&.active {
transform: scale(1.1);
.icon {
visibility: visible;
color: black;
}
}
}
}
}
.lab-policy-content {
padding-top: 1rem;
display: flex;
flex-direction: column;
margin: 0 -1rem;
.settings {
flex: 1;
margin: 0 1rem;
margin-bottom: 2rem;
.expansion {
background: if($is-dark-theme, #2d2e30, #fff) !important;
.header {
justify-content: flex-start;
}
.panel-title {
display: flex;
align-items: center;
.icon {
margin-right: .5rem;
}
}
}
.description {
width: 100%;
}
.title {
margin-top: 2rem;
margin-bottom: .5rem;
display: block;
font-size: 18px;
}
.dropzone {
outline: none;
height: 150px;
width: 100%;
border-radius: 16px;
background: if($is-dark-theme, #2d2e30, #fff);
border: 1px solid if($is-dark-theme, #4a4b4b, #ddd);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
transition: all .2s ease-in-out;
.file-label {
display: flex;
flex-direction: column;
align-items: center;
.btn {
cursor: pointer;
border-radius: 6px;
padding: .5rem 1rem;
background-color: inherit;
border: 1px solid if($is-dark-theme, #ffffff20, #000);
color: if($is-dark-theme, white, #000);
margin-bottom: .5rem;
}
}
.icon {
font-size: 1.5rem;
color: var(--grey);
}
.desc {
font-size: 14px;
color: var(--grey);
}
&.hovering {
border-radius: 16px;
box-shadow:
if(
$is-dark-theme,
(inset 26px 26px 52px #252628, inset -26px -26px 52px #353638),
(inset 26px 26px 52px #d4d4d4, inset -26px -26px 52px #fff)
);
.desc {
color: if($is-dark-theme, white, black);
}
.icon {
color: if($is-dark-theme, white, black);
}
}
}
.logo-setup-wrapper {
display: flex;
flex-direction: column;
.part {
padding-bottom: 1rem;
.spinner {
margin-bottom: 1rem;
}
.label {
font-size: 14px;
color: var(--grey);
margin-bottom: 1rem;
display: block;
}
.logo-view {
width: 100%;
display: flex;
margin-bottom: 1rem;
.prev,
.curr {
height: 50px;
object-fit: contain;
border-radius: .5rem;
}
.fill-space {
flex: 1;
}
}
}
}
.colors {
display: flex;
flex-direction: column;
.color {
padding-bottom: 1rem;
}
}
.fonts {
.title {
display: block;
font-size: 14px;
}
.font-preview {
display: flex;
flex-direction: column;
align-items: center;
padding: 30px 50px;
text-align: center;
}
.font-selector {
display: flex;
align-items: center;
flex-wrap: wrap;
margin: 0 -.25rem;
width: 100%;
padding-bottom: 1rem;
.font {
height: 50px;
width: 50px;
display: flex;
justify-content: center;
align-items: center;
border-radius: .5rem;
margin: .25rem;
box-sizing: border-box;
border: 2px solid if($is-dark-theme, #ffffff30, #00000030);
font-size: 1.5rem;
background-color: inherit;
color: inherit;
cursor: pointer;
&.active {
border: 2px solid if($is-dark-theme, #fff, #000);
}
}
}
}
.adv-container {
display: flex;
flex-direction: column;
padding-bottom: 50px;
.info {
margin-bottom: .5rem;
}
.toggle {
margin-bottom: 1rem;
}
}
}
.preview-wrapper {
margin: 0 1rem;
flex: 1;
.col {
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 2rem;
position: relative;
min-height: 600px;
.preview-changer {
position: absolute;
top: .5rem;
left: .5rem;
border-radius: 10px !important;
z-index: 1;
span {
color: if($is-dark-theme, #ffffff50, #00000050);
}
.strong {
color: if($is-dark-theme, #fff, #000);
font-weight: bold;
font-size: 18px;
}
}
}
}
@media only screen and (min-width: 1000px) {
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
.preview-wrapper {
.col {
min-width: 400px;
}
}
}
}
.btn-container {
display: flex;
justify-content: flex-end;
width: 100%;
button {
margin-top: 3rem;
display: block;
}
}
}
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { PrivateLabelingPolicyComponent } from './private-labeling-policy.component';
describe('PrivateLabelingPolicyComponent', () => {
let component: PrivateLabelingPolicyComponent;
let fixture: ComponentFixture<PrivateLabelingPolicyComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [PrivateLabelingPolicyComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PrivateLabelingPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,511 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Component, EventEmitter, Injector, OnDestroy, Type } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import {
GetLabelPolicyResponse as AdminGetLabelPolicyResponse,
GetPreviewLabelPolicyResponse as AdminGetPreviewLabelPolicyResponse,
UpdateLabelPolicyRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import {
AddCustomLabelPolicyRequest,
GetLabelPolicyResponse as MgmtGetLabelPolicyResponse,
GetPreviewLabelPolicyResponse as MgmtGetPreviewLabelPolicyResponse,
UpdateCustomLabelPolicyRequest,
} from 'src/app/proto/generated/zitadel/management_pb';
import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { DownloadEndpoint, UploadEndpoint, UploadService } from 'src/app/services/upload.service';
import { CnslLinks } from '../../links/links.component';
import { IAM_COMPLEXITY_LINK, IAM_LOGIN_POLICY_LINK, IAM_POLICY_LINK } from '../../policy-grid/policy-links';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
export enum Theme {
DARK,
LIGHT,
}
export enum Preview {
CURRENT,
PREVIEW,
}
export enum ColorType {
BACKGROUND,
PRIMARY,
WARN,
FONTDARK,
FONTLIGHT,
BACKGROUNDDARK,
BACKGROUNDLIGHT,
}
@Component({
selector: 'app-private-labeling-policy',
templateUrl: './private-labeling-policy.component.html',
styleUrls: ['./private-labeling-policy.component.scss'],
})
export class PrivateLabelingPolicyComponent implements OnDestroy {
public theme: Theme = Theme.LIGHT;
public preview: Preview = Preview.PREVIEW;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public service!: ManagementService | AdminService;
public previewData!: LabelPolicy.AsObject;
public data!: LabelPolicy.AsObject;
public images: { [key: string]: any; } = {};
public panelOpenState: boolean = false;
public isHoveringOverDarkLogo: boolean = false;
public isHoveringOverDarkIcon: boolean = false;
public isHoveringOverLightLogo: boolean = false;
public isHoveringOverLightIcon: boolean = false;
public isHoveringOverFont: boolean = false;
private sub: Subscription = new Subscription();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public loading: boolean = false;
public nextLinks: CnslLinks[] = [
IAM_COMPLEXITY_LINK,
IAM_POLICY_LINK,
IAM_LOGIN_POLICY_LINK,
];
public Theme: any = Theme;
public Preview: any = Preview;
public ColorType: any = ColorType;
public refreshPreview: EventEmitter<void> = new EventEmitter();
public loadingImages: boolean = false;
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private injector: Injector,
private uploadService: UploadService,
private sanitizer: DomSanitizer,
) {
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
break;
}
return this.route.params;
})).subscribe(() => {
this.fetchData();
});
}
public toggleHoverLogo(theme: Theme, isHovering: boolean): void {
if (theme === Theme.DARK) {
this.isHoveringOverDarkLogo = isHovering;
}
if (theme === Theme.LIGHT) {
this.isHoveringOverLightLogo = isHovering;
}
}
public toggleHoverFont(isHovering: boolean): void {
this.isHoveringOverFont = isHovering;
}
public onDropLogo(theme: Theme, filelist: FileList): Promise<any> | void {
const file = filelist.item(0);
if (file) {
const formData = new FormData();
formData.append('file', file);
if (theme === Theme.DARK) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.MGMTDARKLOGO, formData));
case PolicyComponentServiceType.ADMIN:
return this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.IAMDARKLOGO, formData));
}
}
if (theme === Theme.LIGHT) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.MGMTLIGHTLOGO, formData));
case PolicyComponentServiceType.ADMIN:
return this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.IAMLIGHTLOGO, formData));
}
}
}
}
public onDropFont(filelist: FileList): Promise<any> | void {
const file = filelist.item(0);
if (file) {
const formData = new FormData();
formData.append('file', file);
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.uploadService.upload(UploadEndpoint.MGMTFONT, formData);
case PolicyComponentServiceType.ADMIN:
return this.uploadService.upload(UploadEndpoint.IAMFONT, formData);
}
}
}
public toggleHoverIcon(theme: Theme, isHovering: boolean): void {
if (theme === Theme.DARK) {
this.isHoveringOverDarkIcon = isHovering;
}
if (theme === Theme.LIGHT) {
this.isHoveringOverLightIcon = isHovering;
}
}
public onDropIcon(theme: Theme, filelist: FileList): void {
const file = filelist.item(0);
if (file) {
const formData = new FormData();
formData.append('file', file);
if (theme === Theme.DARK) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.MGMTDARKICON, formData));
break;
case PolicyComponentServiceType.ADMIN:
this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.IAMDARKICON, formData));
break;
}
}
if (theme === Theme.LIGHT) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.MGMTLIGHTICON, formData));
break;
case PolicyComponentServiceType.ADMIN:
this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.IAMLIGHTICON, formData));
break;
}
}
}
}
private handleUploadPromise(task: Promise<any>): Promise<any> {
return task.then(() => {
this.toast.showInfo('POLICY.TOAST.UPLOADSUCCESS', true);
setTimeout(() => {
this.loadingImages = true;
this.getPreviewData().then(data => {
if (data.policy) {
this.previewData = data.policy;
this.loadPreviewImages();
}
});
}, 1000);
}).catch(error => this.toast.showError(error));
}
public fetchData(): void {
this.loading = true;
this.getPreviewData().then(data => {
console.log('preview', data);
this.loadingImages = true;
if (data.policy) {
this.previewData = data.policy;
this.loading = false;
this.loadPreviewImages();
}
}).catch(error => {
this.toast.showError(error);
});
this.getData().then(data => {
console.log('data', data);
if (data.policy) {
this.data = data.policy;
this.loading = false;
this.loadImages();
}
}).catch(error => {
this.toast.showError(error);
});
}
private loadImages(): void {
if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.data.logoUrlDark) {
this.loadAsset('darkLogo', DownloadEndpoint.IAMDARKLOGOPREVIEW);
}
if (this.data.iconUrlDark) {
this.loadAsset('darkIcon', DownloadEndpoint.IAMDARKICONPREVIEW);
}
if (this.data.logoUrl) {
this.loadAsset('logo', DownloadEndpoint.IAMLOGOPREVIEW);
}
if (this.data.iconUrl) {
this.loadAsset('icon', DownloadEndpoint.IAMICONPREVIEW);
}
} else if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.data.logoUrlDark) {
this.loadAsset('darkLogo', DownloadEndpoint.MGMTDARKLOGOPREVIEW);
}
if (this.data.iconUrlDark) {
this.loadAsset('darkIcon', DownloadEndpoint.MGMTDARKICONPREVIEW);
}
if (this.data.logoUrl) {
this.loadAsset('logo', DownloadEndpoint.MGMTLOGOPREVIEW);
}
if (this.data.iconUrl) {
this.loadAsset('icon', DownloadEndpoint.MGMTICONPREVIEW);
}
}
}
private loadPreviewImages(): void {
if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.previewData.logoUrlDark) {
this.loadAsset('previewDarkLogo', DownloadEndpoint.IAMDARKLOGOPREVIEW);
}
if (this.previewData.iconUrlDark) {
this.loadAsset('previewDarkIcon', DownloadEndpoint.IAMDARKICONPREVIEW);
}
if (this.previewData.logoUrl) {
this.loadAsset('previewLogo', DownloadEndpoint.IAMLOGOPREVIEW);
}
if (this.previewData.iconUrl) {
this.loadAsset('previewIcon', DownloadEndpoint.IAMICONPREVIEW);
}
} else if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.previewData.logoUrlDark) {
this.loadAsset('previewDarkLogo', DownloadEndpoint.MGMTDARKLOGOPREVIEW);
}
if (this.previewData.iconUrlDark) {
this.loadAsset('previewDarkIcon', DownloadEndpoint.MGMTDARKICONPREVIEW);
}
if (this.previewData.logoUrl) {
this.loadAsset('previewLogo', DownloadEndpoint.MGMTLOGOPREVIEW);
}
if (this.previewData.iconUrl) {
this.loadAsset('previewIcon', DownloadEndpoint.MGMTICONPREVIEW);
}
}
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
private async getPreviewData():
Promise<MgmtGetPreviewLabelPolicyResponse.AsObject |
AdminGetPreviewLabelPolicyResponse.AsObject |
MgmtGetLabelPolicyResponse.AsObject |
AdminGetLabelPolicyResponse.AsObject> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).getPreviewLabelPolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).getPreviewLabelPolicy();
}
}
private async getData():
Promise<MgmtGetPreviewLabelPolicyResponse.AsObject |
AdminGetPreviewLabelPolicyResponse.AsObject |
MgmtGetLabelPolicyResponse.AsObject |
AdminGetLabelPolicyResponse.AsObject> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).getLabelPolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).getLabelPolicy();
}
}
private loadAsset(imagekey: string, url: string): Promise<any> {
return this.uploadService.load(`${url}`).then(data => {
const objectURL = URL.createObjectURL(data);
this.images[imagekey] = this.sanitizer.bypassSecurityTrustUrl(objectURL);
this.refreshPreview.emit();
this.loadingImages = false;
}).catch(error => {
this.toast.showError(error);
this.loadingImages = false;
});
}
public removePolicy(): void {
if (this.service instanceof ManagementService) {
this.service.resetPasswordComplexityPolicyToDefault().then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.fetchData();
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
public savePolicy(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.previewData as LabelPolicy.AsObject).isDefault) {
const req0 = new AddCustomLabelPolicyRequest();
this.overwriteValues(req0);
(this.service as ManagementService).addCustomLabelPolicy(req0).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch((error: HttpErrorResponse) => {
this.toast.showError(error);
});
} else {
const req1 = new UpdateCustomLabelPolicyRequest();
this.overwriteValues(req1);
(this.service as ManagementService).updateCustomLabelPolicy(req1).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
}
break;
case PolicyComponentServiceType.ADMIN:
const req = new UpdateLabelPolicyRequest();
this.overwriteValues(req);
(this.service as AdminService).updateLabelPolicy(req).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public saveWatermark(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.previewData as LabelPolicy.AsObject).isDefault) {
const req0 = new AddCustomLabelPolicyRequest();
req0.setDisableWatermark(this.previewData.disableWatermark);
(this.service as ManagementService).addCustomLabelPolicy(req0).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch((error: HttpErrorResponse) => {
this.toast.showError(error);
});
} else {
const req1 = new UpdateCustomLabelPolicyRequest();
req1.setDisableWatermark(this.previewData.disableWatermark);
(this.service as ManagementService).updateCustomLabelPolicy(req1).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
}
break;
case PolicyComponentServiceType.ADMIN:
const req = new UpdateLabelPolicyRequest();
req.setDisableWatermark(this.data.disableWatermark);
(this.service as AdminService).updateLabelPolicy(req).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public get isDefault(): boolean {
if (this.previewData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.previewData as LabelPolicy.AsObject).isDefault;
} else {
return false;
}
}
public overwriteValues(req: AddCustomLabelPolicyRequest | UpdateCustomLabelPolicyRequest): void {
req.setBackgroundColorDark(this.previewData.backgroundColorDark);
req.setBackgroundColor(this.previewData.backgroundColor);
req.setFontColorDark(this.previewData.fontColorDark);
req.setFontColor(this.previewData.fontColor);
req.setPrimaryColorDark(this.previewData.primaryColorDark);
req.setPrimaryColor(this.previewData.primaryColor);
req.setWarnColorDark(this.previewData.warnColorDark);
req.setWarnColor(this.previewData.warnColor);
req.setDisableWatermark(this.previewData.disableWatermark);
req.setHideLoginNameSuffix(this.previewData.hideLoginNameSuffix);
}
public activatePolicy(): Promise<any> {
// dialog warning
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).activateCustomLabelPolicy().then(() => {
this.toast.showInfo('POLICY.PRIVATELABELING.ACTIVATED', true);
setTimeout(() => {
this.loadingImages = true;
this.getData().then(data => {
if (data.policy) {
this.data = data.policy;
this.loadImages();
}
});
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).activateLabelPolicy().then(() => {
this.toast.showInfo('POLICY.PRIVATELABELING.ACTIVATED', true);
setTimeout(() => {
this.loadingImages = true;
this.getData().then(data => {
if (data.policy) {
this.data = data.policy;
this.loadImages();
}
});
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
public resetPolicy(): Promise<any> {
return (this.service as ManagementService).resetLabelPolicyToDefault().then(() => {
this.toast.showInfo('POLICY.PRIVATELABELING.RESET', true);
setTimeout(() => {
this.fetchData();
});
}).catch(error => {
this.toast.showError(error);
});
}
}

View File

@ -0,0 +1,50 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module';
import { DropzoneModule } from '../../../directives/dropzone/dropzone.module';
import { DetailLayoutModule } from '../../detail-layout/detail-layout.module';
import { InfoSectionModule } from '../../info-section/info-section.module';
import { InputModule } from '../../input/input.module';
import { LinksModule } from '../../links/links.module';
import { ColorComponent } from './color/color.component';
import { PreviewComponent } from './preview/preview.component';
import { PrivateLabelingPolicyRoutingModule } from './private-labeling-policy-routing.module';
import { PrivateLabelingPolicyComponent } from './private-labeling-policy.component';
@NgModule({
declarations: [
PrivateLabelingPolicyComponent,
PreviewComponent,
ColorComponent,
],
imports: [
PrivateLabelingPolicyRoutingModule,
CommonModule,
FormsModule,
InputModule,
MatButtonModule,
MatSlideToggleModule,
MatIconModule,
HasRoleModule,
MatTooltipModule,
TranslateModule,
DetailLayoutModule,
DropzoneModule,
MatProgressSpinnerModule,
LinksModule,
MatExpansionModule,
InfoSectionModule,
HasFeaturePipeModule,
],
})
export class PrivateLabelingPolicyModule { }

View File

@ -75,6 +75,38 @@
</div>
</ng-template>
<ng-template appHasRole [appHasRole]="['iam.policy.read']">
<div class="p-item card">
<div class="avatar">
<i class="icon las la-gem"></i>
</div>
<div class="title">
<span>{{'POLICY.PRIVATELABELING_POLICY.TITLE' | translate}}</span>
<button mat-icon-button disabled>
<i class="icon las la-check-circle"></i>
</button>
</div>
<p class="desc">
{{'POLICY.PRIVATELABELING_POLICY.DESCRIPTION' | translate}}</p>
<!-- <cnsl-info-section class="warn"
*ngIf="type == PolicyGridType.ORG && (['password_complexity_policy'] | hasFeature | async) == false"
type="WARN">
{{'FEATURES.NOTAVAILABLE' | translate: ({value:
'password_complexity_policy'})}}
</cnsl-info-section> -->
<span class="fill-space"></span>
<div class="btn-wrapper">
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
<button
[routerLink]="[type == PolicyGridType.IAM ? '/iam' : type == PolicyGridType.ORG ? '/org' : '','policy', PolicyComponentType.PRIVATELABEL ]"
mat-stroked-button>{{'POLICY.BTN_EDIT' | translate}}</button>
</ng-template>
</div>
</div>
</ng-template>
<ng-template appHasRole
[appHasRole]="type == PolicyGridType.IAM ? ['iam.policy.read'] : type == PolicyGridType.ORG ? ['policy.read'] : []">
<div class="p-item card">
@ -101,40 +133,4 @@
</div>
</div>
</ng-template>
<ng-container *ngIf="type === PolicyGridType.IAM">
<ng-template appHasRole [appHasRole]="['iam.policy.read']">
<div class="p-item card">
<div class="avatar">
<i class="icon las la-envelope"></i>
</div>
<div class="title">
<span>{{'POLICY.LABEL.TITLE' | translate}}</span>
<button mat-icon-button disabled>
<i class="icon las la-check-circle"></i>
</button>
</div>
<p class="desc">
{{'POLICY.LABEL.DESCRIPTION' | translate}}</p>
<cnsl-info-section class="warn"
*ngIf="type == PolicyGridType.ORG && (['label_policy'] | hasFeature | async) == false" type="WARN">
{{'FEATURES.NOTAVAILABLE' | translate: ({value:
'label_policy'})}}
</cnsl-info-section>
<span class="fill-space"></span>
<div class="btn-wrapper">
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
<button
[routerLink]="[type == PolicyGridType.IAM ? '/iam' : type == PolicyGridType.ORG ? '/org' : '','policy', PolicyComponentType.LABEL ]"
mat-stroked-button
[disabled]="type == PolicyGridType.ORG && (['label_policy'] | hasFeature | async) == false">
{{'POLICY.BTN_EDIT' | translate}}</button>
</ng-template>
</div>
</div>
</ng-template>
</ng-container>
</div>

View File

@ -1,51 +1,58 @@
import { PolicyComponentType } from '../policies/policy-component-types.enum';
export const IAM_COMPLEXITY_LINK = {
i18nTitle: 'POLICY.PWD_COMPLEXITY.TITLE',
i18nDesc: 'POLICY.PWD_COMPLEXITY.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.COMPLEXITY],
withRole: ['iam.policy.read'],
i18nTitle: 'POLICY.PWD_COMPLEXITY.TITLE',
i18nDesc: 'POLICY.PWD_COMPLEXITY.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.COMPLEXITY],
withRole: ['iam.policy.read'],
};
export const IAM_POLICY_LINK = {
i18nTitle: 'POLICY.IAM_POLICY.TITLE',
i18nDesc: 'POLICY.IAM_POLICY.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.IAM],
withRole: ['iam.policy.read'],
i18nTitle: 'POLICY.IAM_POLICY.TITLE',
i18nDesc: 'POLICY.IAM_POLICY.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.IAM],
withRole: ['iam.policy.read'],
};
export const IAM_LOGIN_POLICY_LINK = {
i18nTitle: 'POLICY.LOGIN_POLICY.TITLE',
i18nDesc: 'POLICY.LOGIN_POLICY.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.LOGIN],
withRole: ['iam.policy.read'],
i18nTitle: 'POLICY.LOGIN_POLICY.TITLE',
i18nDesc: 'POLICY.LOGIN_POLICY.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.LOGIN],
withRole: ['iam.policy.read'],
};
export const IAM_LABEL_LINK = {
i18nTitle: 'POLICY.LABEL.TITLE',
i18nDesc: 'POLICY.LABEL.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.LABEL],
withRole: ['iam.policy.read'],
export const IAM_PRIVATELABEL_LINK = {
i18nTitle: 'POLICY.LABEL.TITLE',
i18nDesc: 'POLICY.LABEL.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.PRIVATELABEL],
withRole: ['iam.policy.read'],
};
export const ORG_COMPLEXITY_LINK = {
i18nTitle: 'POLICY.PWD_COMPLEXITY.TITLE',
i18nDesc: 'POLICY.PWD_COMPLEXITY.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.COMPLEXITY],
withRole: ['policy.read'],
i18nTitle: 'POLICY.PWD_COMPLEXITY.TITLE',
i18nDesc: 'POLICY.PWD_COMPLEXITY.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.COMPLEXITY],
withRole: ['policy.read'],
};
export const ORG_IAM_POLICY_LINK = {
i18nTitle: 'POLICY.IAM_POLICY.TITLE',
i18nDesc: 'POLICY.IAM_POLICY.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.IAM],
withRole: ['iam.policy.read'],
i18nTitle: 'POLICY.IAM_POLICY.TITLE',
i18nDesc: 'POLICY.IAM_POLICY.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.IAM],
withRole: ['iam.policy.read'],
};
export const ORG_LOGIN_POLICY_LINK = {
i18nTitle: 'POLICY.LOGIN_POLICY.TITLE',
i18nDesc: 'POLICY.LOGIN_POLICY.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.LOGIN],
withRole: ['policy.read'],
i18nTitle: 'POLICY.LOGIN_POLICY.TITLE',
i18nDesc: 'POLICY.LOGIN_POLICY.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.LOGIN],
withRole: ['policy.read'],
};
export const ORG_PRIVATELABEL_LINK = {
i18nTitle: 'POLICY.LABEL.TITLE',
i18nDesc: 'POLICY.LABEL.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.PRIVATELABEL],
withRole: ['policy.read'],
};

View File

@ -9,119 +9,119 @@ import { EventstoreComponent } from './eventstore/eventstore.component';
import { IamComponent } from './iam.component';
const routes: Routes = [
{
path: 'policies',
component: IamComponent,
{
path: 'policies',
component: IamComponent,
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.read'],
},
},
{
path: 'eventstore',
component: EventstoreComponent,
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.read'],
},
},
{
path: 'members',
loadChildren: () => import('./iam-members/iam-members.module').then(m => m.IamMembersModule),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.member.read'],
},
},
{
path: 'features',
loadChildren: () => import('src/app/modules/features/features.module').then(m => m.FeaturesModule),
// canActivate: [RoleGuard],
data: {
roles: ['iam.features.read'],
serviceType: FeatureServiceType.ADMIN,
},
},
{
path: 'idp',
children: [
{
path: 'create',
loadChildren: () => import('src/app/modules/idp-create/idp-create.module').then(m => m.IdpCreateModule),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.read'],
roles: ['iam.idp.write'],
serviceType: PolicyComponentServiceType.ADMIN,
},
},
{
path: 'eventstore',
component: EventstoreComponent,
},
{
path: ':id',
loadChildren: () => import('src/app/modules/idp/idp.module').then(m => m.IdpModule),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.read'],
roles: ['iam.idp.read'],
serviceType: PolicyComponentServiceType.ADMIN,
},
},
{
path: 'members',
loadChildren: () => import('./iam-members/iam-members.module').then(m => m.IamMembersModule),
canActivate: [AuthGuard, RoleGuard],
},
],
},
{
path: 'policy',
children: [
{
path: PolicyComponentType.AGE,
data: {
roles: ['iam.member.read'],
serviceType: PolicyComponentServiceType.ADMIN,
},
},
{
path: 'features',
loadChildren: () => import('src/app/modules/features/features.module').then(m => m.FeaturesModule),
// canActivate: [RoleGuard],
loadChildren: () => import('src/app/modules/policies/password-age-policy/password-age-policy.module')
.then(m => m.PasswordAgePolicyModule),
},
{
path: PolicyComponentType.LOCKOUT,
data: {
roles: ['iam.features.read'],
serviceType: FeatureServiceType.ADMIN,
serviceType: PolicyComponentServiceType.ADMIN,
},
},
{
path: 'idp',
children: [
{
path: 'create',
loadChildren: () => import('src/app/modules/idp-create/idp-create.module').then(m => m.IdpCreateModule),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.idp.write'],
serviceType: PolicyComponentServiceType.ADMIN,
},
},
{
path: ':id',
loadChildren: () => import('src/app/modules/idp/idp.module').then(m => m.IdpModule),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.idp.read'],
serviceType: PolicyComponentServiceType.ADMIN,
},
},
],
},
{
path: 'policy',
children: [
{
path: PolicyComponentType.AGE,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/password-age-policy/password-age-policy.module')
.then(m => m.PasswordAgePolicyModule),
},
{
path: PolicyComponentType.LOCKOUT,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/password-lockout-policy/password-lockout-policy.module')
.then(m => m.PasswordLockoutPolicyModule),
},
{
path: PolicyComponentType.COMPLEXITY,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/password-complexity-policy/password-complexity-policy.module')
.then(m => m.PasswordComplexityPolicyModule),
},
{
path: PolicyComponentType.IAM,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/org-iam-policy/org-iam-policy.module')
.then(m => m.OrgIamPolicyModule),
},
{
path: PolicyComponentType.LOGIN,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/login-policy/login-policy.module')
.then(m => m.LoginPolicyModule),
},
{
path: PolicyComponentType.LABEL,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/label-policy/label-policy.module')
.then(m => m.LabelPolicyModule),
},
],
},
loadChildren: () => import('src/app/modules/policies/password-lockout-policy/password-lockout-policy.module')
.then(m => m.PasswordLockoutPolicyModule),
},
{
path: PolicyComponentType.PRIVATELABEL,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/private-labeling-policy/private-labeling-policy.module')
.then(m => m.PrivateLabelingPolicyModule),
},
{
path: PolicyComponentType.COMPLEXITY,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/password-complexity-policy/password-complexity-policy.module')
.then(m => m.PasswordComplexityPolicyModule),
},
{
path: PolicyComponentType.IAM,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/org-iam-policy/org-iam-policy.module')
.then(m => m.OrgIamPolicyModule),
},
{
path: PolicyComponentType.LOGIN,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/login-policy/login-policy.module')
.then(m => m.LoginPolicyModule),
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class IamRoutingModule { }

View File

@ -73,7 +73,7 @@ h2 {
.new-desc {
font-size: 14px;
color: #818a8a;
color: var(--grey);
}
.custom-domain-deactivated {

View File

@ -8,108 +8,116 @@ import { OrgCreateComponent } from './org-create/org-create.component';
import { OrgDetailComponent } from './org-detail/org-detail.component';
const routes: Routes = [
{
{
path: 'create',
component: OrgCreateComponent,
canActivate: [RoleGuard],
data: {
roles: ['(org.create)?(iam.write)?'],
},
loadChildren: () => import('./org-create/org-create.module').then(m => m.OrgCreateModule),
},
{
path: 'idp',
children: [
{
path: 'create',
component: OrgCreateComponent,
loadChildren: () => import('src/app/modules/idp-create/idp-create.module').then(m => m.IdpCreateModule),
canActivate: [RoleGuard],
data: {
roles: ['(org.create)?(iam.write)?'],
roles: ['org.idp.write'],
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('./org-create/org-create.module').then(m => m.OrgCreateModule),
},
{
path: 'idp',
children: [
{
path: 'create',
loadChildren: () => import('src/app/modules/idp-create/idp-create.module').then(m => m.IdpCreateModule),
canActivate: [RoleGuard],
data: {
roles: ['org.idp.write'],
serviceType: PolicyComponentServiceType.MGMT,
},
},
{
path: ':id',
loadChildren: () => import('src/app/modules/idp/idp.module').then(m => m.IdpModule),
canActivate: [RoleGuard],
data: {
roles: ['iam.idp.read'],
serviceType: PolicyComponentServiceType.MGMT,
},
},
],
},
{
path: 'features',
loadChildren: () => import('src/app/modules/features/features.module').then(m => m.FeaturesModule),
},
{
path: ':id',
loadChildren: () => import('src/app/modules/idp/idp.module').then(m => m.IdpModule),
canActivate: [RoleGuard],
data: {
roles: ['features.read'],
serviceType: FeatureServiceType.MGMT,
roles: ['iam.idp.read'],
serviceType: PolicyComponentServiceType.MGMT,
},
},
],
},
{
path: 'features',
loadChildren: () => import('src/app/modules/features/features.module').then(m => m.FeaturesModule),
canActivate: [RoleGuard],
data: {
roles: ['features.read'],
serviceType: FeatureServiceType.MGMT,
},
{
path: 'policy',
children: [
{
path: PolicyComponentType.AGE,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/password-age-policy/password-age-policy.module')
.then(m => m.PasswordAgePolicyModule),
},
{
path: PolicyComponentType.LOCKOUT,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/password-lockout-policy/password-lockout-policy.module')
.then(m => m.PasswordLockoutPolicyModule),
},
{
path: PolicyComponentType.COMPLEXITY,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/password-complexity-policy/password-complexity-policy.module')
.then(m => m.PasswordComplexityPolicyModule),
},
{
path: PolicyComponentType.IAM,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/org-iam-policy/org-iam-policy.module')
.then(m => m.OrgIamPolicyModule),
},
{
path: PolicyComponentType.LOGIN,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/login-policy/login-policy.module')
.then(m => m.LoginPolicyModule),
},
],
},
{
path: 'members',
loadChildren: () => import('./org-members/org-members.module').then(m => m.OrgMembersModule),
},
{
path: '',
component: OrgDetailComponent,
},
{
path: 'overview',
loadChildren: () => import('./org-list/org-list.module').then(m => m.OrgListModule),
},
},
{
path: 'policy',
children: [
{
path: PolicyComponentType.AGE,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/password-age-policy/password-age-policy.module')
.then(m => m.PasswordAgePolicyModule),
},
{
path: PolicyComponentType.LOCKOUT,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/password-lockout-policy/password-lockout-policy.module')
.then(m => m.PasswordLockoutPolicyModule),
},
{
path: PolicyComponentType.PRIVATELABEL,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/private-labeling-policy/private-labeling-policy.module')
.then(m => m.PrivateLabelingPolicyModule),
},
{
path: PolicyComponentType.COMPLEXITY,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/password-complexity-policy/password-complexity-policy.module')
.then(m => m.PasswordComplexityPolicyModule),
},
{
path: PolicyComponentType.IAM,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/org-iam-policy/org-iam-policy.module')
.then(m => m.OrgIamPolicyModule),
},
{
path: PolicyComponentType.LOGIN,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/login-policy/login-policy.module')
.then(m => m.LoginPolicyModule),
},
],
},
{
path: 'members',
loadChildren: () => import('./org-members/org-members.module').then(m => m.OrgMembersModule),
},
{
path: '',
component: OrgDetailComponent,
},
{
path: 'overview',
loadChildren: () => import('./org-list/org-list.module').then(m => m.OrgListModule),
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class OrgsRoutingModule { }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,106 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Org } from '../proto/generated/zitadel/org_pb';
import { StorageService } from './storage.service';
const ORG_STORAGE_KEY = 'organization';
const authorizationKey = 'Authorization';
const orgKey = 'x-zitadel-orgid';
const bearerPrefix = 'Bearer';
const accessTokenStorageKey = 'access_token';
export enum UploadEndpoint {
IAMFONT = 'iam/policy/label/font',
MGMTFONT = 'org/policy/label/font',
IAMDARKLOGO = 'iam/policy/label/logo/dark',
IAMLIGHTLOGO = 'iam/policy/label/logo',
IAMDARKICON = 'iam/policy/label/icon/dark',
IAMLIGHTICON = 'iam/policy/label/icon',
MGMTDARKLOGO = 'org/policy/label/logo/dark',
MGMTLIGHTLOGO = 'org/policy/label/logo',
MGMTDARKICON = 'org/policy/label/icon/dark',
MGMTLIGHTICON = 'org/policy/label/icon',
}
export enum DownloadEndpoint {
IAMFONT = 'iam/policy/label/font',
MGMTFONT = 'org/policy/label/font',
IAMDARKLOGO = 'iam/policy/label/logo/dark',
IAMLOGO = 'iam/policy/label/logo',
IAMDARKICON = 'iam/policy/label/icon/dark',
IAMICON = 'iam/policy/label/icon',
MGMTDARKLOGO = 'org/policy/label/logo/dark',
MGMTLOGO = 'org/policy/label/logo',
MGMTDARKICON = 'org/policy/label/icon/dark',
MGMTICON = 'org/policy/label/icon',
IAMDARKLOGOPREVIEW = 'iam/policy/label/logo/dark/_preview',
IAMLOGOPREVIEW = 'iam/policy/label/logo/_preview',
IAMDARKICONPREVIEW = 'iam/policy/label/icon/dark/_preview',
IAMICONPREVIEW = 'iam/policy/label/icon/_preview',
MGMTDARKLOGOPREVIEW = 'org/policy/label/logo/dark/_preview',
MGMTLOGOPREVIEW = 'org/policy/label/logo/_preview',
MGMTDARKICONPREVIEW = 'org/policy/label/icon/dark/_preview',
MGMTICONPREVIEW = 'org/policy/label/icon/_preview',
}
@Injectable({
providedIn: 'root',
})
export class UploadService {
private serviceUrl: string = '';
private accessToken: string = '';
private org!: Org.AsObject;
constructor(private http: HttpClient, private storageService: StorageService) {
http.get('./assets/environment.json')
.toPromise().then((data: any) => {
if (data && data.uploadServiceUrl) {
this.serviceUrl = data.uploadServiceUrl;
const aT = this.storageService.getItem(accessTokenStorageKey);
if (aT) {
this.accessToken = aT;
}
const org: Org.AsObject | null = (this.storageService.getItem(ORG_STORAGE_KEY));
if (org) {
this.org = org;
}
}
}).catch(error => {
console.error(error);
});
}
public upload(endpoint: UploadEndpoint, body: any): Promise<any> {
return this.http.post(`${this.serviceUrl}/assets/v1/${endpoint}`,
body,
{
headers: {
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
[orgKey]: `${this.org.id}`,
},
}).toPromise();
}
public load(endpoint: string): Promise<any> {
return this.http.get(`${this.serviceUrl}/assets/v1/${endpoint}`,
{
responseType: 'blob',
headers: {
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
[orgKey]: `${this.org.id}`,
},
}).toPromise();
}
}

View File

@ -1,8 +1,9 @@
{
"authServiceUrl": "https://api.zitadel.dev",
"mgmtServiceUrl": "https://api.zitadel.dev",
"adminServiceUrl":"https://api.zitadel.dev",
"subscriptionServiceUrl":"https://sub.zitadel.dev",
"issuer": "https://issuer.zitadel.dev",
"clientid": "70669160379706195@zitadel"
"authServiceUrl": "https://api.zitadel.io",
"mgmtServiceUrl": "https://api.zitadel.io",
"adminServiceUrl":"https://api.zitadel.io",
"subscriptionServiceUrl":"https://sub.zitadel.io",
"uploadServiceUrl":"https://api.zitadel.io",
"issuer": "https://issuer.zitadel.io",
"clientid": "69234247558357051@zitadel"
}

View File

@ -104,8 +104,9 @@
}
},
"ACTIONS": {
"SHOW":"Aufklappen",
"HIDE":"Zuklappen",
"SET":"Übernehmen",
"SHOW":"Aufklappen",
"HIDE":"Zuklappen",
"SAVE": "Speichern",
"SAVENOW": "Speichern",
"NEW": "Neu",
@ -613,7 +614,8 @@
"LOGINPOLICYFACTORS": "Login Richtlinie: Mltifaktoren - benutzerdefiniert",
"LOGINPOLICYPASSWORDLESS": "Login Richtlinie: Passwortlose Authentifizierung - benutzerdefiniert",
"LOGINPOLICYCOMPLEXITYPOLICY": "Passwortkomplexitäts Richtlinie - benutzerdefiniert",
"LABELPOLICY": "Label Richtlinie - benutzerdefiniert",
"LABELPOLICYPRIVATELABEL": "Label Richtlinie - benutzerdefiniert",
"LABELPOLICYWATERMARK": "Label Richtlinie - Wasserzeichen",
"CUSTOMDOMAIN": "Domänen Verifikation - verfügbar"
},
"TIERSTATES": {
@ -636,6 +638,28 @@
"NUMBERERROR": "Das Password muss eine Ziffer beinhalten.",
"PATTERNERROR": "Das Passwort erfüllt nicht die vorgeschriebene Richtlinie."
},
"PRIVATELABELING": {
"TITLE":"Private Labeling",
"DESCRIPTION":"Verleihe dem Login deinen benutzerdefinierten Style und passe das Verhalten an.",
"PREVIEW_DESCRIPTION":"Änderungen dieser Richtlinie werden automatisch in der Preview Umgebung verfügbar. Um die Preview zu Testen muss dem login flow der scope 'x-preview' mitgegeben werden.",
"BTN":"Datei auswählen",
"ACTIVATEPREVIEW":"Konfiguration übernehmen",
"DARK":"Dunkler Modus",
"LIGHT":"Heller Modus",
"CHANGEVIEW":"Ansicht wechseln",
"ACTIVATED":"Richtlinie wurde LIVE geschaltet",
"THEME":"Modus",
"COLORS":"Farben",
"FONT":"Schrift",
"ADVANCEDBEHAVIOR":"Erweitertes Verhalten",
"PREVIEW": {
"TITLE":"Anmeldung",
"SECOND":"mit ZITADEL-Konto anmelden.",
"ERROR":"Benutzer konnte nicht gefunden werden!",
"PRIMARYBUTTON":"weiter",
"SECONDARYBUTTON":"registrieren"
}
},
"PWD_AGE": {
"TITLE": "Gültigkeitsdauer für Passwörter",
"DESCRIPTION": "Du kannst eine Richtlinie für die maximale Gültigkeitsdauer von Passwörtern festlegen. Diese Richtlinie löst eine Warnung nach Ablauf einer festgelegten Gültigkeitsdauer aus."
@ -648,6 +672,10 @@
"TITLE": "Zugangseinstellungen IAM",
"DESCRIPTION": "Definiere die Zugangseistellungen für Benutzer."
},
"PRIVATELABELING_POLICY": {
"TITLE": "Private Labeling",
"DESCRIPTION": "Definiere das Erscheinungsbild des Logins."
},
"LOGIN_POLICY": {
"TITLE": "Login Richtlinien",
"DESCRIPTION": "Definiere die Loginmethoden für Benutzer",
@ -655,13 +683,6 @@
"DESCRIPTIONCREATEMGMT": "Nutzer können sich mit den verfügbaren Idps authentifizieren. Achtung: Es kann zwischen System- und organisationsspezifischen Providern gewählt werden.",
"SAVED": "Erfolgreich gespeichert."
},
"LABEL": {
"TITLE": "Email Labelling Einstellungen",
"DESCRIPTION": "Definieren Sie das Erscheinungsbild Ihrer Benachrichtigungs-Mails",
"PRIMARYCOLOR": "Hintergrundfarbe",
"SECONDARYCOLOR": "Schriftfarbe",
"SAVED": "Erfolgreich gespeichert."
},
"DEFAULTLABEL": "Die aktuelle Richtlinie entspricht der IAM-Standard Einstellung.",
"BTN_INSTALL": "Installieren",
"BTN_EDIT": "Modifizieren",
@ -685,14 +706,19 @@
"ALLOWREGISTER_DESC": "Ist die Option gewählt, erscheint im Login ein zusätzlicher Schritt zum Registrieren eines Benutzers.",
"FORCEMFA": "Mfa erzwingen",
"FORCEMFA_DESC": "Ist die Option gewählt, müssen Benutzer einen zweiten Faktor für den Login verwenden.",
"HIDEPASSWORDRESET": "Passwort vergessen, nicht anzeigen",
"FORCEMFA_DESC": "Ist die Option gewählt, ist es nicht möglich im Login das Passwort zurück zusetzen via Passwort vergessen Link."
"HIDEPASSWORDRESET": "Passwort vergessen ausblenden",
"HIDEPASSWORDRESET_DESC": "Ist die Option gewählt, ist es nicht möglich im Login das Passwort zurück zusetzen via Passwort vergessen Link.",
"HIDELOGINNAMESUFFIX":"Loginname Suffix ausblenden",
"ERRORMSGPOPUP":"Fehler als Dialog Fenster",
"DISABLEWATERMARK":"Wasserzeichen ausblenden"
},
"RESET": "Richtlinie zurücksetzen",
"CREATECUSTOM": "Benutzerdefinierte Richtlinie erstellen",
"TOAST": {
"SET": "Richtline erfolgreich gesetzt!",
"RESETSUCCESS": "Richtline zurückgesetzt!"
"RESETSUCCESS": "Richtline zurückgesetzt!",
"UPLOADSUCCESS": "Upload erfolgreich",
"UPLOADFAILED":"Upload fehlgeschlagen!"
}
},
"ORG_DETAIL": {

View File

@ -104,6 +104,7 @@
}
},
"ACTIONS": {
"SET":"Set",
"SHOW":"Show",
"HIDE":"Hide",
"SAVE": "Save",
@ -613,7 +614,8 @@
"LOGINPOLICYFACTORS": "Login Policy: Multifactors - custom",
"LOGINPOLICYPASSWORDLESS": "Login Policy: Passwordless Authentication - custom",
"LOGINPOLICYCOMPLEXITYPOLICY": "Password Complexity Policy - custom",
"LABELPOLICY": "Labeling Policy - custom",
"LABELPOLICYPRIVATELABEL": "Label Richtlinie - benutzerdefiniert",
"LABELPOLICYWATERMARK": "Label Richtlinie - Wasserzeichen",
"CUSTOMDOMAIN": "Domain Verification - available"
},
"TIERSTATES": {
@ -636,6 +638,28 @@
"NUMBERERROR": "The password must include a digit.",
"PATTERNERROR": "The password does not meet the required pattern."
},
"PRIVATELABELING": {
"TITLE":"Private Labeling",
"DESCRIPTION":"Give the login your personalized style and modify its behavior.",
"PREVIEW_DESCRIPTION":"Changes of the policy will automatically deployed to preview environment. To view those changes, a 'x-preview' scope will have to be added to your login scopes.",
"BTN":"Select File",
"ACTIVATEPREVIEW":"Set preview as current configuration",
"DARK":"Dark Mode",
"LIGHT":"Lighg Mode",
"CHANGEVIEW":"Change View",
"ACTIVATED":"Policy changes are now LIVE",
"THEME":"Theme",
"COLORS":"Colors",
"FONT":"Font",
"ADVANCEDBEHAVIOR":"Advanced Behavior",
"PREVIEW": {
"TITLE":"Login",
"SECOND":"login with your ZITADEL-Account.",
"ERROR":"User could not be found!",
"PRIMARYBUTTON":"next",
"SECONDARYBUTTON":"register"
}
},
"PWD_AGE": {
"TITLE": "Password Aging",
"DESCRIPTION": "You can set a policy for the aging of passwords. This policy emits a warning after the specific aging time has elapsed."
@ -648,6 +672,12 @@
"TITLE": "IAM Access Preferences",
"DESCRIPTION": "Define access properties of your users."
},
"PRIVATELABELING_POLICY": {
"TITLE": "Private Labeling",
"BTN":"Select File",
"DESCRIPTION": "Customize the appearance of the Login",
"ACTIVATEPREVIEW":"Activate Configuration"
},
"LOGIN_POLICY": {
"TITLE": "Login Policy",
"DESCRIPTION": "Define how Users can be authenticated and configure Identity Providers",
@ -655,13 +685,6 @@
"DESCRIPTIONCREATEMGMT": "Users can choose from the available identity providers below. Note: You can use System-set providers as well as providers set for your organisation only.",
"SAVED": "Saved successfully!"
},
"LABEL": {
"TITLE": "Email Labelling Settings",
"DESCRIPTION": "Change the look of your emails.",
"PRIMARYCOLOR": "Background color",
"SECONDARYCOLOR": "Font color",
"SAVED": "Saved successfully"
},
"DEFAULTLABEL": "The currently set guideline corresponds to the standard setting set by the IAM Administrator.",
"BTN_INSTALL": "Setup",
"BTN_EDIT": "Modify",
@ -686,13 +709,18 @@
"FORCEMFA": "Force MFA",
"FORCEMFA_DESC": "If the option is selected, users have to configure a second factor for login.",
"HIDEPASSWORDRESET": "Hide Password reset",
"FORCEMFA_DESC": "If the option is selected, the user can't reset his password in the login process."
"HIDEPASSWORDRESET_DESC": "If the option is selected, the user can't reset his password in the login process.",
"HIDELOGINNAMESUFFIX":"Hide Loginname suffix",
"ERRORMSGPOPUP":"Show Error in Dialog",
"DISABLEWATERMARK":"Disable Watermark"
},
"RESET": "Reset Policy",
"CREATECUSTOM": "Create Custom Policy",
"TOAST": {
"SET": "Policy set successfully!",
"RESETSUCCESS": "Policy reset successfully!"
"RESETSUCCESS": "Policy reset successfully!",
"UPLOADSUCCESS": "Uploaded successfully!",
"UPLOADFAILED":"Upload failed!"
}
},
"ORG_DETAIL": {

View File

@ -19,6 +19,7 @@
@import 'src/app/modules/meta-layout/meta.scss';
@import 'src/app/modules/zitadel-tier/zitadel-tier.component.scss';
@import 'src/app/modules/onboarding/onboarding.component.scss';
@import 'src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.scss';
@mixin component-themes($theme) {
@include avatar-theme($theme);
@ -42,4 +43,5 @@
@include info-section-theme($theme);
@include onboarding-theme($theme);
@include tier-theme($theme);
@include private-label-theme($theme);
}

View File

@ -146,7 +146,6 @@ $dark-accent: mat.define-palette(mat.$pink-palette);
$dark-warn: mat.define-palette(mat.$red-palette);
$light-theme: mat.define-light-theme($light-primary, $light-accent, $light-warn);
$dark-theme: mat.define-dark-theme($dark-primary, $dark-accent, $dark-warn);
$custom-typography: mat.define-typography-config($font-family: 'Lato');

View File

@ -266,6 +266,16 @@ Returns the label policy defined by the administrators of ZITADEL
### GetPreviewLabelPolicy
> **rpc** GetPreviewLabelPolicy([GetPreviewLabelPolicyRequest](#getpreviewlabelpolicyrequest))
[GetPreviewLabelPolicyResponse](#getpreviewlabelpolicyresponse)
Returns the preview label policy defined by the administrators of ZITADEL
### UpdateLabelPolicy
> **rpc** UpdateLabelPolicy([UpdateLabelPolicyRequest](#updatelabelpolicyrequest))
@ -277,6 +287,66 @@ it impacts all organisations without a customised policy
### ActivateLabelPolicy
> **rpc** ActivateLabelPolicy([ActivateLabelPolicyRequest](#activatelabelpolicyrequest))
[ActivateLabelPolicyResponse](#activatelabelpolicyresponse)
Activates all changes of the label policy
### RemoveLabelPolicyLogo
> **rpc** RemoveLabelPolicyLogo([RemoveLabelPolicyLogoRequest](#removelabelpolicylogorequest))
[RemoveLabelPolicyLogoResponse](#removelabelpolicylogoresponse)
Removes the logo of the label policy
### RemoveLabelPolicyLogoDark
> **rpc** RemoveLabelPolicyLogoDark([RemoveLabelPolicyLogoDarkRequest](#removelabelpolicylogodarkrequest))
[RemoveLabelPolicyLogoDarkResponse](#removelabelpolicylogodarkresponse)
Removes the logo dark of the label policy
### RemoveLabelPolicyIcon
> **rpc** RemoveLabelPolicyIcon([RemoveLabelPolicyIconRequest](#removelabelpolicyiconrequest))
[RemoveLabelPolicyIconResponse](#removelabelpolicyiconresponse)
Removes the icon of the label policy
### RemoveLabelPolicyIconDark
> **rpc** RemoveLabelPolicyIconDark([RemoveLabelPolicyIconDarkRequest](#removelabelpolicyicondarkrequest))
[RemoveLabelPolicyIconDarkResponse](#removelabelpolicyicondarkresponse)
Removes the logo dark of the label policy
### RemoveLabelPolicyFont
> **rpc** RemoveLabelPolicyFont([RemoveLabelPolicyFontRequest](#removelabelpolicyfontrequest))
[RemoveLabelPolicyFontResponse](#removelabelpolicyfontresponse)
Removes the font of the label policy
### GetLoginPolicy
> **rpc** GetLoginPolicy([GetLoginPolicyRequest](#getloginpolicyrequest))
@ -570,6 +640,23 @@ failed event. You can find out if it worked on the `failure_count`
## Messages
### ActivateLabelPolicyRequest
This is an empty request
### ActivateLabelPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### AddCustomOrgIAMPolicyRequest
@ -975,6 +1062,23 @@ This is an empty request
### GetPreviewLabelPolicyRequest
This is an empty request
### GetPreviewLabelPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| policy | zitadel.policy.v1.LabelPolicy | - | |
### HealthzRequest
This is an empty request
@ -1316,6 +1420,91 @@ This is an empty response
### RemoveLabelPolicyFontRequest
This is an empty request
### RemoveLabelPolicyFontResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveLabelPolicyIconDarkRequest
This is an empty request
### RemoveLabelPolicyIconDarkResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveLabelPolicyIconRequest
This is an empty request
### RemoveLabelPolicyIconResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveLabelPolicyLogoDarkRequest
This is an empty request
### RemoveLabelPolicyLogoDarkResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveLabelPolicyLogoRequest
This is an empty request
### RemoveLabelPolicyLogoResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveMultiFactorFromLoginPolicyRequest
@ -1422,6 +1611,8 @@ This is an empty response
| label_policy | bool | - | |
| custom_domain | bool | - | |
| login_policy_password_reset | bool | - | |
| label_policy_private_label | bool | - | |
| label_policy_watermark | bool | - | |
@ -1458,6 +1649,8 @@ This is an empty response
| label_policy | bool | - | |
| custom_domain | bool | - | |
| login_policy_password_reset | bool | - | |
| label_policy_private_label | bool | - | |
| label_policy_watermark | bool | - | |
@ -1669,9 +1862,16 @@ This is an empty response
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| primary_color | string | - | string.min_len: 1<br /> string.max_len: 50<br /> |
| secondary_color | string | - | string.min_len: 1<br /> string.max_len: 50<br /> |
| primary_color | string | - | string.max_len: 50<br /> |
| hide_login_name_suffix | bool | - | |
| warn_color | string | - | string.max_len: 50<br /> |
| background_color | string | - | string.max_len: 50<br /> |
| font_color | string | - | string.max_len: 50<br /> |
| primary_color_dark | string | - | string.max_len: 50<br /> |
| background_color_dark | string | - | string.max_len: 50<br /> |
| warn_color_dark | string | - | string.max_len: 50<br /> |
| font_color_dark | string | - | string.max_len: 50<br /> |
| disable_watermark | bool | - | |

View File

@ -220,6 +220,16 @@ Removed the phone number of the authorized user
### RemoveMyAvatar
> **rpc** RemoveMyAvatar([RemoveMyAvatarRequest](#removemyavatarrequest))
[RemoveMyAvatarResponse](#removemyavatarresponse)
Remove my avatar
### ListMyLinkedIDPs
> **rpc** ListMyLinkedIDPs([ListMyLinkedIDPsRequest](#listmylinkedidpsrequest))
@ -820,6 +830,23 @@ This is an empty request
### RemoveMyAvatarRequest
This is an empty request
### RemoveMyAvatarResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveMyLinkedIDPRequest

View File

@ -293,6 +293,16 @@ An sms will be sent to the given phone number to finish the phone verification p
### RemoveMyAvatar
> **rpc** RemoveMyAvatar([RemoveHumanAvatarRequest](#removehumanavatarrequest))
[RemoveHumanAvatarResponse](#removehumanavatarresponse)
Removes the avatar number of the human
### SetHumanInitialPassword
> **rpc** SetHumanInitialPassword([SetHumanInitialPasswordRequest](#sethumaninitialpasswordrequest))
@ -1581,7 +1591,18 @@ The password lockout policy is not used at the moment
> **rpc** GetLabelPolicy([GetLabelPolicyRequest](#getlabelpolicyrequest))
[GetLabelPolicyResponse](#getlabelpolicyresponse)
Returns the label policy of the organisation
Returns the active label policy of the organisation
With this policy the private labeling can be configured (colors, etc.)
### GetPreviewLabelPolicy
> **rpc** GetPreviewLabelPolicy([GetPreviewLabelPolicyRequest](#getpreviewlabelpolicyrequest))
[GetPreviewLabelPolicyResponse](#getpreviewlabelpolicyresponse)
Returns the preview label policy of the organisation
With this policy the private labeling can be configured (colors, etc.)
@ -1620,6 +1641,66 @@ With this policy the private labeling can be configured (colors, etc.)
### ActivateCustomLabelPolicy
> **rpc** ActivateCustomLabelPolicy([ActivateCustomLabelPolicyRequest](#activatecustomlabelpolicyrequest))
[ActivateCustomLabelPolicyResponse](#activatecustomlabelpolicyresponse)
Activates all changes of the label policy
### RemoveCustomLabelPolicyLogo
> **rpc** RemoveCustomLabelPolicyLogo([RemoveCustomLabelPolicyLogoRequest](#removecustomlabelpolicylogorequest))
[RemoveCustomLabelPolicyLogoResponse](#removecustomlabelpolicylogoresponse)
Removes the logo of the label policy
### RemoveCustomLabelPolicyLogoDark
> **rpc** RemoveCustomLabelPolicyLogoDark([RemoveCustomLabelPolicyLogoDarkRequest](#removecustomlabelpolicylogodarkrequest))
[RemoveCustomLabelPolicyLogoDarkResponse](#removecustomlabelpolicylogodarkresponse)
Removes the logo dark of the label policy
### RemoveCustomLabelPolicyIcon
> **rpc** RemoveCustomLabelPolicyIcon([RemoveCustomLabelPolicyIconRequest](#removecustomlabelpolicyiconrequest))
[RemoveCustomLabelPolicyIconResponse](#removecustomlabelpolicyiconresponse)
Removes the icon of the label policy
### RemoveCustomLabelPolicyIconDark
> **rpc** RemoveCustomLabelPolicyIconDark([RemoveCustomLabelPolicyIconDarkRequest](#removecustomlabelpolicyicondarkrequest))
[RemoveCustomLabelPolicyIconDarkResponse](#removecustomlabelpolicyicondarkresponse)
Removes the logo dark of the label policy
### RemoveCustomLabelPolicyFont
> **rpc** RemoveCustomLabelPolicyFont([RemoveCustomLabelPolicyFontRequest](#removecustomlabelpolicyfontrequest))
[RemoveCustomLabelPolicyFontResponse](#removecustomlabelpolicyfontresponse)
Removes the font of the label policy
### ResetLabelPolicyToDefault
> **rpc** ResetLabelPolicyToDefault([ResetLabelPolicyToDefaultRequest](#resetlabelpolicytodefaultrequest))
@ -1725,6 +1806,23 @@ Change OIDC identity provider configuration of the organisation
## Messages
### ActivateCustomLabelPolicyRequest
This is an empty request
### ActivateCustomLabelPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### AddAPIAppRequest
@ -1785,9 +1883,16 @@ Change OIDC identity provider configuration of the organisation
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| primary_color | string | - | string.min_len: 1<br /> string.max_len: 50<br /> |
| secondary_color | string | - | string.min_len: 1<br /> string.max_len: 50<br /> |
| hide_login_name_suffix | bool | - | |
| primary_color | string | - | string.max_len: 50<br /> |
| hide_login_name_suffix | bool | hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set. Details about this scope in https://docs.zitadel.ch/concepts#Reserved_Scopes | |
| warn_color | string | - | string.max_len: 50<br /> |
| background_color | string | - | string.max_len: 50<br /> |
| font_color | string | - | string.max_len: 50<br /> |
| primary_color_dark | string | - | string.max_len: 50<br /> |
| background_color_dark | string | - | string.max_len: 50<br /> |
| warn_color_dark | string | - | string.max_len: 50<br /> |
| font_color_dark | string | - | string.max_len: 50<br /> |
| disable_watermark | bool | - | |
@ -3074,6 +3179,24 @@ This is an empty request
### GetPreviewLabelPolicyRequest
This is an empty request
### GetPreviewLabelPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| policy | zitadel.policy.v1.LabelPolicy | - | |
| is_default | bool | - | |
### GetProjectByIDRequest
@ -4251,6 +4374,91 @@ This is an empty request
### RemoveCustomLabelPolicyFontRequest
This is an empty request
### RemoveCustomLabelPolicyFontResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveCustomLabelPolicyIconDarkRequest
This is an empty request
### RemoveCustomLabelPolicyIconDarkResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveCustomLabelPolicyIconRequest
This is an empty request
### RemoveCustomLabelPolicyIconResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveCustomLabelPolicyLogoDarkRequest
This is an empty request
### RemoveCustomLabelPolicyLogoDarkResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveCustomLabelPolicyLogoRequest
This is an empty request
### RemoveCustomLabelPolicyLogoResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveHumanAuthFactorOTPRequest
@ -4296,6 +4504,28 @@ This is an empty request
### RemoveHumanAvatarRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| user_id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### RemoveHumanAvatarResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### RemoveHumanLinkedIDPRequest
@ -4995,9 +5225,16 @@ This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| primary_color | string | - | string.min_len: 1<br /> string.max_len: 50<br /> |
| secondary_color | string | - | string.min_len: 1<br /> string.max_len: 50<br /> |
| primary_color | string | - | string.max_len: 50<br /> |
| hide_login_name_suffix | bool | - | |
| warn_color | string | - | string.max_len: 50<br /> |
| background_color | string | - | string.max_len: 50<br /> |
| font_color | string | - | string.max_len: 50<br /> |
| primary_color_dark | string | - | string.max_len: 50<br /> |
| background_color_dark | string | - | string.max_len: 50<br /> |
| warn_color_dark | string | - | string.max_len: 50<br /> |
| font_color_dark | string | - | string.max_len: 50<br /> |
| disable_watermark | bool | - | |

View File

@ -16,10 +16,22 @@ title: zitadel/policy.proto
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
| primary_color | string | - | |
| secondary_color | string | - | |
| is_default | bool | - | |
| hide_login_name_suffix | bool | - | |
| primary_color | string | hex value for primary color | |
| is_default | bool | defines if the organisation's admin changed the policy | |
| hide_login_name_suffix | bool | hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set. Details about this scope in https://docs.zitadel.ch/concepts#Reserved_Scopes | |
| warn_color | string | hex value for secondary color | |
| background_color | string | hex value for background color | |
| font_color | string | hex value for font color | |
| primary_color_dark | string | hex value for primary color dark theme | |
| background_color_dark | string | hex value for background color dark theme | |
| warn_color_dark | string | hex value for warn color dark theme | |
| font_color_dark | string | hex value for font color dark theme | |
| disable_watermark | bool | - | |
| logo_url | string | - | |
| icon_url | string | - | |
| logo_url_dark | string | - | |
| icon_url_dark | string | - | |
| font_url | string | - | |

2
go.mod
View File

@ -43,10 +43,12 @@ require (
github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8 // indirect
github.com/kevinburke/twilio-go v0.0.0-20200810163702-320748330fac
github.com/lib/pq v1.9.0
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/manifoldco/promptui v0.7.0
github.com/mattn/go-colorable v0.1.8 // indirect; indirect github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/minio/minio-go/v7 v7.0.10
github.com/mitchellh/copystructure v1.1.2 // indirect
github.com/muesli/gamut v0.2.0
github.com/nicksnyder/go-i18n/v2 v2.1.2
github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.2.0

20
go.sum
View File

@ -60,6 +60,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DataDog/sketches-go v0.0.1 h1:RtG+76WKgZuz6FIaGsjoPePmadDBkuD/KC6+ZWu78b8=
@ -342,6 +343,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -632,6 +635,10 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/lyft/protoc-gen-star v0.5.1/go.mod h1:9toiA3cC7z5uVbODF7kEQ91Xn7XNFkVUl+SrEe+ZORU=
@ -704,6 +711,13 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/muesli/clusters v0.0.0-20180605185049-a07a36e67d36/go.mod h1:mw5KDqUj0eLj/6DUNINLVJNoPTFkEuGMHtJsXLviLkY=
github.com/muesli/clusters v0.0.0-20200529215643-2700303c1762 h1:p4A2Jx7Lm3NV98VRMKlyWd3nqf8obft8NfXlAUmqd3I=
github.com/muesli/clusters v0.0.0-20200529215643-2700303c1762/go.mod h1:mw5KDqUj0eLj/6DUNINLVJNoPTFkEuGMHtJsXLviLkY=
github.com/muesli/gamut v0.2.0 h1:IZbl/hQzChTXtqDSXL8CDtjdRt58LivY03bGCm1yDyU=
github.com/muesli/gamut v0.2.0/go.mod h1:kz1+UJqI1thNtocJlowyqG2o0FNsN0W534VoMVsR9/Y=
github.com/muesli/kmeans v0.2.1 h1:ja5AnwfyDCVBCANrAfXr2pOh292FQnSeu1lySACDJU0=
github.com/muesli/kmeans v0.2.1/go.mod h1:eNyybq0tX9/iBEP6EMU4Y7dpmGK0uEhODdZpnG1a/iQ=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@ -901,6 +915,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I=
github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
@ -908,6 +924,8 @@ github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0B
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xrash/smetrics v0.0.0-20200730060457-89a2a8a1fb0b h1:tnWgqoOBmInkt5pbLjagwNVjjT4RdJhFHzL1ebCSRh8=
github.com/xrash/smetrics v0.0.0-20200730060457-89a2a8a1fb0b/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -1007,6 +1025,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

View File

@ -300,7 +300,15 @@ func (repo *IAMRepository) GetOrgIAMPolicy(ctx context.Context) (*iam_model.OrgI
}
func (repo *IAMRepository) GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
policy, err := repo.View.LabelPolicyByAggregateID(repo.SystemDefaults.IamID)
policy, err := repo.View.LabelPolicyByAggregateIDAndState(repo.SystemDefaults.IamID, int32(domain.LabelPolicyStateActive))
if err != nil {
return nil, err
}
return iam_es_model.LabelPolicyViewToModel(policy), err
}
func (repo *IAMRepository) GetDefaultPreviewLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
policy, err := repo.View.LabelPolicyByAggregateIDAndState(repo.SystemDefaults.IamID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return nil, err
}

View File

@ -4,6 +4,7 @@ import (
"time"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/config/systemdefaults"
@ -30,8 +31,8 @@ func (h *handler) Eventstore() v1.Eventstore {
return h.es
}
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, defaults systemdefaults.SystemDefaults) []query.Handler {
return []query.Handler{
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, defaults systemdefaults.SystemDefaults, static static.Storage, localDevMode bool) []query.Handler {
handlers := []query.Handler{
newOrg(
handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount, es}),
newIAMMember(
@ -66,6 +67,13 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
newFeatures(
handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}),
}
if static != nil {
handlers = append(handlers, newStyling(
handler{view, bulkLimit, configs.cycleDuration("Styling"), errorCount, es},
static,
localDevMode))
}
return handlers
}
func (configs Configs) cycleDuration(viewModel string) time.Duration {

View File

@ -2,6 +2,8 @@ package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore/v1"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/eventstore/v1/query"
@ -77,8 +79,25 @@ func (p *LabelPolicy) processLabelPolicy(event *es_models.Event) (err error) {
switch event.Type {
case model.LabelPolicyAdded:
err = policy.AppendEvent(event)
case model.LabelPolicyChanged:
policy, err = p.view.LabelPolicyByAggregateID(event.AggregateID)
case model.LabelPolicyChanged,
model.LabelPolicyLogoAdded,
model.LabelPolicyLogoRemoved,
model.LabelPolicyIconAdded,
model.LabelPolicyIconRemoved,
model.LabelPolicyLogoDarkAdded,
model.LabelPolicyLogoDarkRemoved,
model.LabelPolicyIconDarkAdded,
model.LabelPolicyIconDarkRemoved,
model.LabelPolicyFontAdded,
model.LabelPolicyFontRemoved,
model.LabelPolicyAssetsRemoved:
policy, err = p.view.LabelPolicyByAggregateIDAndState(event.AggregateID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return err
}
err = policy.AppendEvent(event)
case model.LabelPolicyActivated:
policy, err = p.view.LabelPolicyByAggregateIDAndState(event.AggregateID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return err
}

View File

@ -0,0 +1,298 @@
package handler
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"github.com/caos/logging"
"github.com/lucasb-eyer/go-colorful"
"github.com/muesli/gamut"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore/v1"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/eventstore/v1/query"
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/static"
)
const (
stylingTable = "adminapi.styling"
)
type Styling struct {
handler
static static.Storage
subscription *v1.Subscription
devMode bool
resourceUrl string
}
func newStyling(handler handler, static static.Storage, localDevMode bool) *Styling {
h := &Styling{
handler: handler,
static: static,
}
prefix := ""
if localDevMode {
prefix = "/login"
}
h.resourceUrl = prefix + "/resources/dynamic" //TODO: ?
h.subscribe()
return h
}
func (m *Styling) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
func (m *Styling) ViewModel() string {
return stylingTable
}
func (_ *Styling) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (m *Styling) CurrentSequence() (uint64, error) {
sequence, err := m.view.GetLatestStylingSequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *Styling) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := m.view.GetLatestStylingSequence()
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *Styling) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.OrgAggregate, iam_es_model.IAMAggregate:
err = m.processLabelPolicy(event)
}
return err
}
func (m *Styling) processLabelPolicy(event *es_models.Event) (err error) {
policy := new(iam_model.LabelPolicyView)
switch event.Type {
case iam_es_model.LabelPolicyAdded, model.LabelPolicyAdded:
err = policy.AppendEvent(event)
case iam_es_model.LabelPolicyChanged, model.LabelPolicyChanged,
iam_es_model.LabelPolicyLogoAdded, model.LabelPolicyLogoAdded,
iam_es_model.LabelPolicyLogoRemoved, model.LabelPolicyLogoRemoved,
iam_es_model.LabelPolicyIconAdded, model.LabelPolicyIconAdded,
iam_es_model.LabelPolicyIconRemoved, model.LabelPolicyIconRemoved,
iam_es_model.LabelPolicyLogoDarkAdded, model.LabelPolicyLogoDarkAdded,
iam_es_model.LabelPolicyLogoDarkRemoved, model.LabelPolicyLogoDarkRemoved,
iam_es_model.LabelPolicyIconDarkAdded, model.LabelPolicyIconDarkAdded,
iam_es_model.LabelPolicyIconDarkRemoved, model.LabelPolicyIconDarkRemoved,
iam_es_model.LabelPolicyFontAdded, model.LabelPolicyFontAdded,
iam_es_model.LabelPolicyFontRemoved, model.LabelPolicyFontRemoved,
iam_es_model.LabelPolicyAssetsRemoved, model.LabelPolicyAssetsRemoved:
policy, err = m.view.StylingByAggregateIDAndState(event.AggregateID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return err
}
err = policy.AppendEvent(event)
case iam_es_model.LabelPolicyActivated, model.LabelPolicyActivated:
policy, err = m.view.StylingByAggregateIDAndState(event.AggregateID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return err
}
err = policy.AppendEvent(event)
if err != nil {
return err
}
err = m.generateStylingFile(policy)
default:
return m.view.ProcessedStylingSequence(event)
}
if err != nil {
return err
}
return m.view.PutStyling(policy, event)
}
func (m *Styling) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-2m9fs", "id", event.AggregateID).WithError(err).Warn("something went wrong in label policy handler")
return spooler.HandleError(event, err, m.view.GetLatestLabelPolicyFailedEvent, m.view.ProcessedLabelPolicyFailedEvent, m.view.ProcessedLabelPolicySequence, m.errorCountUntilSkip)
}
func (m *Styling) OnSuccess() error {
return spooler.HandleSuccess(m.view.UpdateLabelPolicySpoolerRunTimestamp)
}
func (m *Styling) generateStylingFile(policy *iam_model.LabelPolicyView) error {
reader, size, err := m.writeFile(policy)
if err != nil {
return err
}
return m.uploadFilesToBucket(policy.AggregateID, "text/css", reader, size)
}
func (m *Styling) writeFile(policy *iam_model.LabelPolicyView) (io.Reader, int64, error) {
cssContent := ""
cssContent += fmt.Sprint(":root {")
if policy.PrimaryColor != "" {
palette := m.generateColorPaletteRGBA255(policy.PrimaryColor)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-primary-%v: %s;", i, color)
}
}
if policy.BackgroundColor != "" {
palette := m.generateColorPaletteRGBA255(policy.BackgroundColor)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-background-%v: %s;", i, color)
}
}
if policy.WarnColor != "" {
palette := m.generateColorPaletteRGBA255(policy.WarnColor)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-warn-%v: %s;", i, color)
}
}
var fontname string
if policy.FontURL != "" {
split := strings.Split(policy.FontURL, "/")
fontname = split[len(split)-1]
cssContent += fmt.Sprintf("--zitadel-font-family: %s;", fontname)
}
cssContent += fmt.Sprint("}")
if policy.FontURL != "" {
cssContent += fmt.Sprintf(fontFaceTemplate, fontname, m.resourceUrl, policy.AggregateID, policy.FontURL)
}
cssContent += fmt.Sprint(".lgn-dark-theme {")
if policy.PrimaryColorDark != "" {
palette := m.generateColorPaletteRGBA255(policy.PrimaryColorDark)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-primary-%v: %s;", i, color)
}
}
if policy.BackgroundColorDark != "" {
palette := m.generateColorPaletteRGBA255(policy.BackgroundColorDark)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-background-%v: %s;", i, color)
}
}
if policy.WarnColorDark != "" {
palette := m.generateColorPaletteRGBA255(policy.WarnColorDark)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-warn-%v: %s;", i, color)
}
}
if policy.FontColorDark != "" {
palette := m.generateColorPaletteRGBA255(policy.FontColorDark)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-font-%v: %s;", i, color)
}
}
cssContent += fmt.Sprint("}")
data := []byte(cssContent)
buffer := bytes.NewBuffer(data)
return buffer, int64(buffer.Len()), nil
}
const fontFaceTemplate = `
@font-face {
font-family: '%s';
font-style: normal;
font-display: swap;
src: url(%s?orgId=%s&filename=%s);
}
`
func (m *Styling) uploadFilesToBucket(aggregateID, contentType string, reader io.Reader, size int64) error {
fileName := domain.CssPath + "/" + domain.CssVariablesFileName
_, err := m.static.PutObject(context.Background(), aggregateID, fileName, contentType, reader, size, true)
return err
}
func (m *Styling) generateColorPaletteRGBA255(hex string) map[string]string {
palette := make(map[string]string)
defaultColor := gamut.Hex(hex)
color50, ok := colorful.MakeColor(gamut.Lighter(defaultColor, 1.0))
if ok {
palette["50"] = cssRGB(color50.RGB255())
}
color100, ok := colorful.MakeColor(gamut.Lighter(defaultColor, 0.8))
if ok {
palette["100"] = cssRGB(color100.RGB255())
}
color200, ok := colorful.MakeColor(gamut.Lighter(defaultColor, 0.6))
if ok {
palette["200"] = cssRGB(color200.RGB255())
}
color300, ok := colorful.MakeColor(gamut.Lighter(defaultColor, 0.4))
if ok {
palette["300"] = cssRGB(color300.RGB255())
}
color400, ok := colorful.MakeColor(gamut.Lighter(defaultColor, 0.1))
if ok {
palette["400"] = cssRGB(color400.RGB255())
}
color500, ok := colorful.MakeColor(defaultColor)
if ok {
palette["500"] = cssRGB(color500.RGB255())
}
color600, ok := colorful.MakeColor(gamut.Darker(defaultColor, 0.1))
if ok {
palette["600"] = cssRGB(color600.RGB255())
}
color700, ok := colorful.MakeColor(gamut.Darker(defaultColor, 0.2))
if ok {
palette["700"] = cssRGB(color700.RGB255())
}
color800, ok := colorful.MakeColor(gamut.Darker(defaultColor, 0.3))
if ok {
palette["800"] = cssRGB(color800.RGB255())
}
color900, ok := colorful.MakeColor(gamut.Darker(defaultColor, 0.4))
if ok {
palette["900"] = cssRGB(color900.RGB255())
}
colorContrast, ok := colorful.MakeColor(gamut.Contrast(defaultColor))
if ok {
palette["contrast"] = cssRGB(colorContrast.RGB255())
}
return palette
}
func cssRGB(r, g, b uint8) string {
return fmt.Sprintf("rgb(%v, %v, %v)", r, g, b)
}

View File

@ -9,6 +9,7 @@ import (
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/eventstore/v1"
es_spol "github.com/caos/zitadel/internal/eventstore/v1/spooler"
"github.com/caos/zitadel/internal/static"
)
type Config struct {
@ -28,7 +29,7 @@ type EsRepository struct {
eventstore.UserRepo
}
func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, roles []string) (*EsRepository, error) {
func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, static static.Storage, roles []string, localDevMode bool) (*EsRepository, error) {
es, err := v1.Start(conf.Eventstore)
if err != nil {
return nil, err
@ -42,7 +43,7 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, r
return nil, err
}
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, systemDefaults)
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, systemDefaults, static, localDevMode)
return &EsRepository{
spooler: spool,

View File

@ -4,6 +4,7 @@ import (
"database/sql"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/handler"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
@ -17,12 +18,12 @@ type SpoolerConfig struct {
Handlers handler.Configs
}
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, defaults systemdefaults.SystemDefaults) *spooler.Spooler {
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, defaults systemdefaults.SystemDefaults, static static.Storage, localDevMode bool) *spooler.Spooler {
spoolerConfig := spooler.Config{
Eventstore: es,
Locker: &locker{dbClient: sql},
ConcurrentWorkers: c.ConcurrentWorkers,
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, defaults),
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, defaults, static, localDevMode),
}
spool := spoolerConfig.New()
spool.Start()

View File

@ -11,8 +11,8 @@ const (
labelPolicyTable = "adminapi.label_policies"
)
func (v *View) LabelPolicyByAggregateID(aggregateID string) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateID(v.Db, labelPolicyTable, aggregateID)
func (v *View) LabelPolicyByAggregateIDAndState(aggregateID string, state int32) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateIDAndState(v.Db, labelPolicyTable, aggregateID, state)
}
func (v *View) PutLabelPolicy(policy *model.LabelPolicyView, event *models.Event) error {

View File

@ -0,0 +1,44 @@
package view
import (
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
stylingTyble = "adminapi.styling"
)
func (v *View) StylingByAggregateIDAndState(aggregateID string, state int32) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateIDAndState(v.Db, stylingTyble, aggregateID, state)
}
func (v *View) PutStyling(policy *model.LabelPolicyView, event *models.Event) error {
err := view.PutLabelPolicy(v.Db, stylingTyble, policy)
if err != nil {
return err
}
return v.ProcessedStylingSequence(event)
}
func (v *View) GetLatestStylingSequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(stylingTyble)
}
func (v *View) ProcessedStylingSequence(event *models.Event) error {
return v.saveCurrentSequence(stylingTyble, event)
}
func (v *View) UpdateStylingSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(stylingTyble)
}
func (v *View) GetLatestStylingFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(stylingTyble, sequence)
}
func (v *View) ProcessedStylingFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@ -24,6 +24,7 @@ type IAMRepository interface {
ExternalIDPsByIDPConfigIDFromDefaultPolicy(ctx context.Context, idpConfigID string) ([]*usr_model.ExternalIDPView, error)
GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
GetDefaultPreviewLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)

View File

@ -0,0 +1,194 @@
package assets
import (
"context"
"io/ioutil"
"net/http"
"strconv"
"strings"
"github.com/caos/logging"
"github.com/gorilla/mux"
"github.com/caos/zitadel/internal/api/authz"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/management/repository"
"github.com/caos/zitadel/internal/static"
)
type Handler struct {
errorHandler ErrorHandler
storage static.Storage
commands *command.Commands
authInterceptor *http_mw.AuthInterceptor
idGenerator id.Generator
orgRepo repository.OrgRepository
}
func (h *Handler) AuthInterceptor() *http_mw.AuthInterceptor {
return h.authInterceptor
}
func (h *Handler) Commands() *command.Commands {
return h.commands
}
func (h *Handler) ErrorHandler() ErrorHandler {
return DefaultErrorHandler
}
func (h *Handler) Storage() static.Storage {
return h.storage
}
type Uploader interface {
Callback(ctx context.Context, info *domain.AssetInfo, orgID string, commands *command.Commands) error
ObjectName(data authz.CtxData) (string, error)
BucketName(data authz.CtxData) string
ContentTypeAllowed(contentType string) bool
MaxFileSize() int64
}
type Downloader interface {
ObjectName(ctx context.Context, path string) (string, error)
BucketName(ctx context.Context, id string) string
}
type ErrorHandler func(http.ResponseWriter, *http.Request, error)
func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error) {
logging.Log("ASSET-g5ef1").WithError(err).WithField("uri", r.RequestURI).Error("error occurred on asset api")
http.Error(w, err.Error(), http.StatusInternalServerError)
}
func NewHandler(
commands *command.Commands,
verifier *authz.TokenVerifier,
authConfig authz.Config,
idGenerator id.Generator,
storage static.Storage,
orgRepo repository.OrgRepository,
) http.Handler {
h := &Handler{
commands: commands,
errorHandler: DefaultErrorHandler,
authInterceptor: http_mw.AuthorizationInterceptor(verifier, authConfig),
idGenerator: idGenerator,
storage: storage,
orgRepo: orgRepo,
}
verifier.RegisterServer("Management-API", "assets", AssetsService_AuthMethods) //TODO: separate api?
router := mux.NewRouter()
RegisterRoutes(router, h)
router.PathPrefix("/{id}").Methods("GET").HandlerFunc(DownloadHandleFunc(h, h.GetFile()))
return router
}
func (h *Handler) GetFile() Downloader {
return &publicFileDownloader{}
}
type publicFileDownloader struct{}
func (l *publicFileDownloader) ObjectName(_ context.Context, path string) (string, error) {
return path, nil
}
func (l *publicFileDownloader) BucketName(_ context.Context, id string) string {
return id
}
const maxMemory = 2 << 20
const paramFile = "file"
func UploadHandleFunc(s AssetsService, uploader Uploader) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctxData := authz.GetCtxData(ctx)
err := r.ParseMultipartForm(maxMemory)
file, handler, err := r.FormFile(paramFile)
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
defer func() {
err = file.Close()
logging.Log("UPLOAD-GDg34").OnError(err).Warn("could not close file")
}()
contentType := handler.Header.Get("content-type")
size := handler.Size
if !uploader.ContentTypeAllowed(contentType) {
s.ErrorHandler()(w, r, caos_errs.ThrowInvalidArgument(nil, "UPLOAD-Dbvfs", "invalid content-type"))
return
}
if size > uploader.MaxFileSize() {
s.ErrorHandler()(w, r, caos_errs.ThrowInvalidArgumentf(nil, "UPLOAD-Bfb32", "file to big, max file size is %v", uploader.MaxFileSize()))
return
}
bucketName := uploader.BucketName(ctxData)
objectName, err := uploader.ObjectName(ctxData)
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
info, err := s.Commands().UploadAsset(ctx, bucketName, objectName, contentType, file, size)
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
err = uploader.Callback(ctx, info, ctxData.OrgID, s.Commands())
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
}
}
func DownloadHandleFunc(s AssetsService, downloader Downloader) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if s.Storage() == nil {
return
}
ctx := r.Context()
id := mux.Vars(r)["id"]
bucketName := downloader.BucketName(ctx, id)
path := ""
if id != "" {
path = strings.Split(r.RequestURI, id+"/")[1]
}
objectName, err := downloader.ObjectName(ctx, path)
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
if objectName == "" {
s.ErrorHandler()(w, r, caos_errs.ThrowNotFound(nil, "UPLOAD-adf4f", "file not found"))
return
}
reader, getInfo, err := s.Storage().GetObject(ctx, bucketName, objectName)
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
data, err := ioutil.ReadAll(reader)
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
info, err := getInfo()
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
w.Header().Set("content-length", strconv.FormatInt(info.Size, 10))
w.Header().Set("content-type", info.ContentType)
w.Header().Set("ETag", info.ETag)
w.Write(data)
}
}

View File

@ -0,0 +1,119 @@
Services:
IAM:
Prefix: "/iam"
Methods:
DefaultLabelPolicyLogo:
Path: "/policy/label/logo"
HasDarkMode: true
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: iam.policy.write
- Name: Get
Comment:
Type: download
Permission: iam.policy.read
- Name: GetPreview
Comment:
Type: preview
Permission: iam.policy.read
DefaultLabelPolicyIcon:
Path: "/policy/label/icon"
HasDarkMode: true
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: iam.policy.write
- Name: Get
Comment:
Type: download
Permission: iam.policy.read
- Name: GetPreview
Comment:
Type: preview
Permission: iam.policy.read
DefaultLabelPolicyFont:
Path: "/policy/label/font"
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: iam.policy.write
- Name: Get
Comment:
Type: download
Permission: iam.policy.read
- Name: GetPreview
Comment:
Type: preview
Permission: iam.policy.read
Org:
Prefix: "/org"
Methods:
OrgLabelPolicyLogo:
Path: "/policy/label/logo"
Feature: "label_policy.private_label"
HasDarkMode: true
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: policy.write
- Name: Get
Comment:
Type: download
Permission: policy.read
- Name: GetPreview
Comment:
Type: preview
Permission: policy.read
OrgLabelPolicyIcon:
Path: "/policy/label/icon"
Feature: "label_policy.private_label"
HasDarkMode: true
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: policy.write
- Name: Get
Comment:
Type: download
Permission: policy.read
- Name: GetPreview
Comment:
Type: preview
Permission: policy.read
OrgLabelPolicyFont:
Path: "/policy/label/font"
Feature: "label_policy.private_label"
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: policy.write
- Name: Get
Comment:
Type: download
Permission: policy.read
- Name: GetPreview
Comment:
Type: preview
Permission: policy.read
Users:
Prefix: "/users"
Methods:
MyUserAvatar:
Path: "/me/avatar"
Features: "label_policy.private_label"
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: authenticated
- Name: Get
Comment:
Type: download
Permission: authenticated

View File

@ -0,0 +1,200 @@
package main
import (
"flag"
"io"
"os"
"text/template"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/config"
)
var (
directory = flag.String("directory", "./", "working directory: asset.yaml must be in this directory, files will be generated into parent directory")
)
func main() {
flag.Parse()
configFile := *directory + "asset.yaml"
authz, err := os.OpenFile(*directory+"../authz.go", os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0755)
logging.Log("ASSETS-Gn31f").OnError(err).Fatal("cannot open authz file")
router, err := os.OpenFile(*directory+"../router.go", os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0755)
logging.Log("ASSETS-ABen3").OnError(err).Fatal("cannot open router file")
GenerateAssetHandler(configFile, authz, router)
}
type Method struct {
Path string
Feature string
HasDarkMode bool
Handlers []Handler
}
type Handler struct {
Name string
Comment string
Type HandlerType
Permission string
}
func (a Handler) Method() string {
if a.Type == MethodTypeUpload {
return "POST"
}
return "GET"
}
func (a Handler) PathSuffix() string {
if a.Type == MethodTypePreview {
return "/_preview"
}
return ""
}
func (a Handler) MethodReturn() string {
if a.Type == MethodTypeUpload {
return "Uploader"
}
if a.Type == MethodTypeDownload {
return "Downloader"
}
if a.Type == MethodTypePreview {
return "Downloader"
}
return ""
}
func (a Handler) HandlerType() string {
if a.Type == MethodTypeUpload {
return "UploadHandleFunc"
}
if a.Type == MethodTypeDownload {
return "DownloadHandleFunc"
}
if a.Type == MethodTypePreview {
return "DownloadHandleFunc"
}
return ""
}
type HandlerType string
const (
MethodTypeUpload = "upload"
MethodTypeDownload = "download"
MethodTypePreview = "preview"
)
type Services map[string]Service
type Service struct {
Prefix string
Methods map[string]Method
}
func GenerateAssetHandler(configFilePath string, output io.Writer, output2 io.Writer) {
conf := new(struct {
Services Services
})
err := config.Read(conf, configFilePath)
logging.Log("ASSETS-Dgbn4").OnError(err).Fatal("cannot read config")
tmplAuthz, err := template.New("").Parse(authzTmpl)
logging.Log("ASSETS-BGbbg").OnError(err).Fatal("cannot parse authz template")
tmplRouter, err := template.New("").Parse(routerTmpl)
logging.Log("ASSETS-gh4rq").OnError(err).Fatal("cannot parse router template")
data := &struct {
GoPkgName string
Name string
Prefix string
Services Services
}{
GoPkgName: "assets",
Name: "AssetsService",
Prefix: "/assets/v1",
Services: conf.Services,
}
err = tmplAuthz.Execute(output, data)
logging.Log("ASSETS-BHngj").OnError(err).Fatal("cannot generate authz")
err = tmplRouter.Execute(output2, data)
logging.Log("ASSETS-Bfd41").OnError(err).Fatal("cannot generate router")
}
const authzTmpl = `package {{.GoPkgName}}
import (
"github.com/caos/zitadel/internal/api/authz"
)
/**
* {{.Name}}
*/
{{ $prefix := .Prefix }}
var {{.Name}}_AuthMethods = authz.MethodMapping {
{{ range $service := .Services}}
{{ range $method := .Methods}}
{{ range $handler := .Handlers}}
{{ if (or $method.Feature $handler.Permission) }}
"{{$handler.Method}}:{{$prefix}}{{$service.Prefix}}{{$method.Path}}{{$handler.PathSuffix}}": authz.Option{
Permission: "{{$handler.Permission}}",
Feature: "{{$method.Feature}}",
},
{{ if $method.HasDarkMode }}
"{{$handler.Method}}:{{$prefix}}{{$service.Prefix}}{{$method.Path}}/dark{{$handler.PathSuffix}}": authz.Option{
Permission: "{{$handler.Permission}}",
Feature: "{{$method.Feature}}",
},
{{end}}
{{end}}
{{end}}
{{end}}
{{end}}
}
`
const routerTmpl = `package {{.GoPkgName}}
import (
"github.com/gorilla/mux"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/static"
)
type {{.Name}} interface {
AuthInterceptor() *http_mw.AuthInterceptor
Commands() *command.Commands
ErrorHandler() ErrorHandler
Storage() static.Storage
{{ range $service := .Services}}
{{ range $methodName, $method := .Methods}}
{{ range $handler := .Handlers}}
{{$handler.Name}}{{$methodName}}() {{if $handler.MethodReturn}}{{$handler.MethodReturn}}{{end}}
{{ if $method.HasDarkMode }}
{{$handler.Name}}{{$methodName}}Dark() {{if $handler.MethodReturn}}{{$handler.MethodReturn}}{{end}}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
}
func RegisterRoutes(router *mux.Router, s {{.Name}}) {
router.Use(s.AuthInterceptor().Handler)
{{ range $service := .Services}}
{{ range $methodName, $method := .Methods}}
{{ range $handler := .Handlers}}
router.Path("{{$service.Prefix}}{{$method.Path}}{{$handler.PathSuffix}}").Methods("{{$handler.Method}}").HandlerFunc({{if $handler.HandlerType}}{{$handler.HandlerType}}(s, {{end}}s.{{$handler.Name}}{{$methodName}}(){{if $handler.HandlerType}}){{end}})
{{ if $method.HasDarkMode }}
router.Path("{{$service.Prefix}}{{$method.Path}}/dark{{$handler.PathSuffix}}").Methods("{{$handler.Method}}").HandlerFunc({{if $handler.HandlerType}}{{$handler.HandlerType}}(s, {{end}}s.{{$handler.Name}}{{$methodName}}Dark(){{if $handler.HandlerType}}){{end}})
{{ end }}
{{ end }}
{{ end }}
{{ end }}
}
`

View File

@ -0,0 +1,377 @@
package assets
import (
"context"
"strings"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/management/repository"
)
func (h *Handler) UploadDefaultLabelPolicyLogo() Uploader {
return &labelPolicyLogoUploader{h.idGenerator, false, true, []string{"image/"}, 1 << 19}
}
func (h *Handler) UploadDefaultLabelPolicyLogoDark() Uploader {
return &labelPolicyLogoUploader{h.idGenerator, true, true, []string{"image/"}, 1 << 19}
}
func (h *Handler) UploadOrgLabelPolicyLogo() Uploader {
return &labelPolicyLogoUploader{h.idGenerator, false, false, []string{"image/"}, 1 << 19}
}
func (h *Handler) UploadOrgLabelPolicyLogoDark() Uploader {
return &labelPolicyLogoUploader{h.idGenerator, true, false, []string{"image/"}, 1 << 19}
}
type labelPolicyLogoUploader struct {
idGenerator id.Generator
darkMode bool
defaultPolicy bool
contentTypes []string
maxSize int64
}
func (l *labelPolicyLogoUploader) ContentTypeAllowed(contentType string) bool {
for _, ct := range l.contentTypes {
if strings.HasPrefix(contentType, ct) {
return true
}
}
return false
}
func (l *labelPolicyLogoUploader) MaxFileSize() int64 {
return l.maxSize
}
func (l *labelPolicyLogoUploader) ObjectName(_ authz.CtxData) (string, error) {
suffixID, err := l.idGenerator.Next()
if err != nil {
return "", err
}
prefix := domain.LabelPolicyLogoPath
if l.darkMode {
return prefix + "-" + domain.Dark + "-" + suffixID, nil
}
return prefix + "-" + suffixID, nil
}
func (l *labelPolicyLogoUploader) BucketName(ctxData authz.CtxData) string {
if l.defaultPolicy {
return domain.IAMID
}
return ctxData.OrgID
}
func (l *labelPolicyLogoUploader) Callback(ctx context.Context, info *domain.AssetInfo, orgID string, commands *command.Commands) error {
if l.defaultPolicy {
if l.darkMode {
_, err := commands.AddLogoDarkDefaultLabelPolicy(ctx, info.Key)
return err
}
_, err := commands.AddLogoDefaultLabelPolicy(ctx, info.Key)
return err
}
if l.darkMode {
_, err := commands.AddLogoDarkLabelPolicy(ctx, orgID, info.Key)
return err
}
_, err := commands.AddLogoLabelPolicy(ctx, orgID, info.Key)
return err
}
func (h *Handler) GetDefaultLabelPolicyLogo() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: true, preview: false}
}
func (h *Handler) GetDefaultLabelPolicyLogoDark() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: true, preview: false}
}
func (h *Handler) GetPreviewDefaultLabelPolicyLogo() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: true, preview: true}
}
func (h *Handler) GetPreviewDefaultLabelPolicyLogoDark() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: true, preview: true}
}
func (h *Handler) GetOrgLabelPolicyLogo() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: false, preview: false}
}
func (h *Handler) GetOrgLabelPolicyLogoDark() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: false, preview: false}
}
func (h *Handler) GetPreviewOrgLabelPolicyLogo() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: false, preview: true}
}
func (h *Handler) GetPreviewOrgLabelPolicyLogoDark() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: false, preview: true}
}
type labelPolicyLogoDownloader struct {
org repository.OrgRepository
darkMode bool
defaultPolicy bool
preview bool
}
func (l *labelPolicyLogoDownloader) ObjectName(ctx context.Context, path string) (string, error) {
policy, err := getLabelPolicy(ctx, l.defaultPolicy, l.preview, l.org)
if err != nil {
return "", nil
}
if l.darkMode {
return policy.LogoDarkURL, nil
}
return policy.LogoURL, nil
}
func (l *labelPolicyLogoDownloader) BucketName(ctx context.Context, id string) string {
if l.defaultPolicy {
return domain.IAMID
}
return authz.GetCtxData(ctx).OrgID
}
func (h *Handler) UploadDefaultLabelPolicyIcon() Uploader {
return &labelPolicyIconUploader{h.idGenerator, false, true, []string{"image/"}, 1 << 19}
}
func (h *Handler) UploadDefaultLabelPolicyIconDark() Uploader {
return &labelPolicyIconUploader{h.idGenerator, true, true, []string{"image/"}, 1 << 19}
}
func (h *Handler) UploadOrgLabelPolicyIcon() Uploader {
return &labelPolicyIconUploader{h.idGenerator, false, false, []string{"image/"}, 1 << 19}
}
func (h *Handler) UploadOrgLabelPolicyIconDark() Uploader {
return &labelPolicyIconUploader{h.idGenerator, true, false, []string{"image/"}, 1 << 19}
}
type labelPolicyIconUploader struct {
idGenerator id.Generator
darkMode bool
defaultPolicy bool
contentTypes []string
maxSize int64
}
func (l *labelPolicyIconUploader) ContentTypeAllowed(contentType string) bool {
for _, ct := range l.contentTypes {
if strings.HasPrefix(contentType, ct) {
return true
}
}
return false
}
func (l *labelPolicyIconUploader) MaxFileSize() int64 {
return l.maxSize
}
func (l *labelPolicyIconUploader) ObjectName(_ authz.CtxData) (string, error) {
suffixID, err := l.idGenerator.Next()
if err != nil {
return "", err
}
prefix := domain.LabelPolicyIconPath
if l.darkMode {
return prefix + "-" + domain.Dark + "-" + suffixID, nil
}
return prefix + "-" + suffixID, nil
}
func (l *labelPolicyIconUploader) BucketName(ctxData authz.CtxData) string {
if l.defaultPolicy {
return domain.IAMID
}
return ctxData.OrgID
}
func (l *labelPolicyIconUploader) Callback(ctx context.Context, info *domain.AssetInfo, orgID string, commands *command.Commands) error {
if l.defaultPolicy {
if l.darkMode {
_, err := commands.AddIconDarkDefaultLabelPolicy(ctx, info.Key)
return err
}
_, err := commands.AddIconDefaultLabelPolicy(ctx, info.Key)
return err
}
if l.darkMode {
_, err := commands.AddIconDarkLabelPolicy(ctx, orgID, info.Key)
return err
}
_, err := commands.AddIconLabelPolicy(ctx, orgID, info.Key)
return err
}
func (h *Handler) GetDefaultLabelPolicyIcon() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: true, preview: false}
}
func (h *Handler) GetDefaultLabelPolicyIconDark() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: true, preview: false}
}
func (h *Handler) GetPreviewDefaultLabelPolicyIcon() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: true, preview: true}
}
func (h *Handler) GetPreviewDefaultLabelPolicyIconDark() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: true, preview: true}
}
func (h *Handler) GetOrgLabelPolicyIcon() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: false, preview: false}
}
func (h *Handler) GetOrgLabelPolicyIconDark() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: false, preview: false}
}
func (h *Handler) GetPreviewOrgLabelPolicyIcon() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: false, preview: true}
}
func (h *Handler) GetPreviewOrgLabelPolicyIconDark() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: false, preview: true}
}
type labelPolicyIconDownloader struct {
org repository.OrgRepository
darkMode bool
defaultPolicy bool
preview bool
}
func (l *labelPolicyIconDownloader) ObjectName(ctx context.Context, path string) (string, error) {
policy, err := getLabelPolicy(ctx, l.defaultPolicy, l.preview, l.org)
if err != nil {
return "", nil
}
if l.darkMode {
return policy.IconDarkURL, nil
}
return policy.IconURL, nil
}
func (l *labelPolicyIconDownloader) BucketName(ctx context.Context, id string) string {
if l.defaultPolicy {
return domain.IAMID
}
return authz.GetCtxData(ctx).OrgID
}
func (h *Handler) UploadDefaultLabelPolicyFont() Uploader {
return &labelPolicyFontUploader{h.idGenerator, true, []string{"font/"}, 1 << 19}
}
func (h *Handler) UploadOrgLabelPolicyFont() Uploader {
return &labelPolicyFontUploader{h.idGenerator, false, []string{"font/"}, 1 << 19}
}
type labelPolicyFontUploader struct {
idGenerator id.Generator
defaultPolicy bool
contentTypes []string
maxSize int64
}
func (l *labelPolicyFontUploader) ContentTypeAllowed(contentType string) bool {
for _, ct := range l.contentTypes {
if strings.HasPrefix(contentType, ct) {
return true
}
}
return false
}
func (l *labelPolicyFontUploader) MaxFileSize() int64 {
return l.maxSize
}
func (l *labelPolicyFontUploader) ObjectName(_ authz.CtxData) (string, error) {
suffixID, err := l.idGenerator.Next()
if err != nil {
return "", err
}
prefix := domain.LabelPolicyFontPath
return prefix + "-" + suffixID, nil
}
func (l *labelPolicyFontUploader) BucketName(ctxData authz.CtxData) string {
if l.defaultPolicy {
return domain.IAMID
}
return ctxData.OrgID
}
func (l *labelPolicyFontUploader) Callback(ctx context.Context, info *domain.AssetInfo, orgID string, commands *command.Commands) error {
if l.defaultPolicy {
_, err := commands.AddFontDefaultLabelPolicy(ctx, info.Key)
return err
}
_, err := commands.AddFontLabelPolicy(ctx, orgID, info.Key)
return err
}
func (h *Handler) GetDefaultLabelPolicyFont() Downloader {
return &labelPolicyFontDownloader{org: h.orgRepo, defaultPolicy: true, preview: false}
}
func (h *Handler) GetPreviewDefaultLabelPolicyFont() Downloader {
return &labelPolicyFontDownloader{org: h.orgRepo, defaultPolicy: true, preview: true}
}
func (h *Handler) GetOrgLabelPolicyFont() Downloader {
return &labelPolicyFontDownloader{org: h.orgRepo, defaultPolicy: false, preview: false}
}
func (h *Handler) GetPreviewOrgLabelPolicyFont() Downloader {
return &labelPolicyFontDownloader{org: h.orgRepo, defaultPolicy: true, preview: true}
}
type labelPolicyFontDownloader struct {
org repository.OrgRepository
defaultPolicy bool
preview bool
}
func (l *labelPolicyFontDownloader) ObjectName(ctx context.Context, path string) (string, error) {
policy, err := getLabelPolicy(ctx, l.defaultPolicy, l.preview, l.org)
if err != nil {
return "", nil
}
return policy.FontURL, nil
}
func (l *labelPolicyFontDownloader) BucketName(ctx context.Context, id string) string {
if l.defaultPolicy {
return domain.IAMID
}
return authz.GetCtxData(ctx).OrgID
}
func getLabelPolicy(ctx context.Context, defaultPolicy, preview bool, orgRepo repository.OrgRepository) (*model.LabelPolicyView, error) {
if defaultPolicy {
if preview {
return orgRepo.GetPreviewDefaultLabelPolicy(ctx)
}
return orgRepo.GetDefaultLabelPolicy(ctx)
}
if preview {
return orgRepo.GetPreviewLabelPolicy(ctx)
}
return orgRepo.GetLabelPolicy(ctx)
}

View File

@ -0,0 +1,59 @@
package assets
import (
"context"
"strings"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/domain"
)
func (h *Handler) UploadMyUserAvatar() Uploader {
return &myHumanAvatarUploader{[]string{"image/"}, 1 << 19}
}
type myHumanAvatarUploader struct {
contentTypes []string
maxSize int64
}
func (l *myHumanAvatarUploader) ContentTypeAllowed(contentType string) bool {
for _, ct := range l.contentTypes {
if strings.HasPrefix(contentType, ct) {
return true
}
}
return false
}
func (l *myHumanAvatarUploader) MaxFileSize() int64 {
return l.maxSize
}
func (l *myHumanAvatarUploader) ObjectName(ctxData authz.CtxData) (string, error) {
return domain.GetHumanAvatarAssetPath(ctxData.UserID), nil
}
func (l *myHumanAvatarUploader) BucketName(ctxData authz.CtxData) string {
return ctxData.OrgID
}
func (l *myHumanAvatarUploader) Callback(ctx context.Context, info *domain.AssetInfo, orgID string, commands *command.Commands) error {
_, err := commands.AddHumanAvatar(ctx, orgID, authz.GetCtxData(ctx).UserID, info.Key)
return err
}
func (h *Handler) GetMyUserAvatar() Downloader {
return &myHumanAvatarDownloader{}
}
type myHumanAvatarDownloader struct{}
func (l *myHumanAvatarDownloader) ObjectName(ctx context.Context, path string) (string, error) {
return domain.GetHumanAvatarAssetPath(authz.GetCtxData(ctx).UserID), nil
}
func (l *myHumanAvatarDownloader) BucketName(ctx context.Context, id string) string {
return authz.GetCtxData(ctx).OrgID
}

View File

@ -71,7 +71,8 @@ func setDefaultFeaturesRequestToDomain(req *admin_pb.SetDefaultFeaturesRequest)
LoginPolicyUsernameLogin: req.LoginPolicyUsernameLogin,
LoginPolicyPasswordReset: req.LoginPolicyPasswordReset,
PasswordComplexityPolicy: req.PasswordComplexityPolicy,
LabelPolicy: req.LabelPolicy,
LabelPolicyPrivateLabel: req.LabelPolicy || req.LabelPolicyPrivateLabel,
LabelPolicyWatermark: req.LabelPolicyWatermark,
CustomDomain: req.CustomDomain,
}
}
@ -90,7 +91,8 @@ func setOrgFeaturesRequestToDomain(req *admin_pb.SetOrgFeaturesRequest) *domain.
LoginPolicyUsernameLogin: req.LoginPolicyUsernameLogin,
LoginPolicyPasswordReset: req.LoginPolicyPasswordReset,
PasswordComplexityPolicy: req.PasswordComplexityPolicy,
LabelPolicy: req.LabelPolicy,
LabelPolicyPrivateLabel: req.LabelPolicy || req.LabelPolicyPrivateLabel,
LabelPolicyWatermark: req.LabelPolicyWatermark,
CustomDomain: req.CustomDomain,
}
}

View File

@ -16,6 +16,14 @@ func (s *Server) GetLabelPolicy(ctx context.Context, req *admin_pb.GetLabelPolic
return &admin_pb.GetLabelPolicyResponse{Policy: policy_grpc.ModelLabelPolicyToPb(policy)}, nil
}
func (s *Server) GetPreviewLabelPolicy(ctx context.Context, req *admin_pb.GetPreviewLabelPolicyRequest) (*admin_pb.GetPreviewLabelPolicyResponse, error) {
policy, err := s.iam.GetDefaultPreviewLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.GetPreviewLabelPolicyResponse{Policy: policy_grpc.ModelLabelPolicyToPb(policy)}, nil
}
func (s *Server) UpdateLabelPolicy(ctx context.Context, req *admin_pb.UpdateLabelPolicyRequest) (*admin_pb.UpdateLabelPolicyResponse, error) {
policy, err := s.command.ChangeDefaultLabelPolicy(ctx, updateLabelPolicyToDomain(req))
if err != nil {
@ -29,3 +37,87 @@ func (s *Server) UpdateLabelPolicy(ctx context.Context, req *admin_pb.UpdateLabe
),
}, nil
}
func (s *Server) ActivateLabelPolicy(ctx context.Context, req *admin_pb.ActivateLabelPolicyRequest) (*admin_pb.ActivateLabelPolicyResponse, error) {
policy, err := s.command.ActivateDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.ActivateLabelPolicyResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveLabelPolicyLogo(ctx context.Context, req *admin_pb.RemoveLabelPolicyLogoRequest) (*admin_pb.RemoveLabelPolicyLogoResponse, error) {
policy, err := s.command.RemoveLogoDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.RemoveLabelPolicyLogoResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveLabelPolicyLogoDark(ctx context.Context, req *admin_pb.RemoveLabelPolicyLogoDarkRequest) (*admin_pb.RemoveLabelPolicyLogoDarkResponse, error) {
policy, err := s.command.RemoveLogoDarkDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.RemoveLabelPolicyLogoDarkResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveLabelPolicyIcon(ctx context.Context, req *admin_pb.RemoveLabelPolicyIconRequest) (*admin_pb.RemoveLabelPolicyIconResponse, error) {
policy, err := s.command.RemoveIconDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.RemoveLabelPolicyIconResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveLabelPolicyIconDark(ctx context.Context, req *admin_pb.RemoveLabelPolicyIconDarkRequest) (*admin_pb.RemoveLabelPolicyIconDarkResponse, error) {
policy, err := s.command.RemoveIconDarkDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.RemoveLabelPolicyIconDarkResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveLabelPolicyFont(ctx context.Context, req *admin_pb.RemoveLabelPolicyFontRequest) (*admin_pb.RemoveLabelPolicyFontResponse, error) {
policy, err := s.command.RemoveFontDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.RemoveLabelPolicyFontResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}

View File

@ -8,7 +8,14 @@ import (
func updateLabelPolicyToDomain(policy *admin_pb.UpdateLabelPolicyRequest) *domain.LabelPolicy {
return &domain.LabelPolicy{
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
BackgroundColor: policy.BackgroundColor,
WarnColor: policy.WarnColor,
FontColor: policy.FontColor,
PrimaryColorDark: policy.PrimaryColorDark,
BackgroundColorDark: policy.BackgroundColorDark,
WarnColorDark: policy.WarnColorDark,
FontColorDark: policy.FontColorDark,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
DisableWatermark: policy.DisableWatermark,
}
}

View File

@ -0,0 +1,20 @@
package auth
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/object"
auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
)
func (s *Server) RemoveMyAvatar(ctx context.Context, req *auth_pb.RemoveMyAvatarRequest) (*auth_pb.RemoveMyAvatarResponse, error) {
ctxData := authz.GetCtxData(ctx)
objectDetails, err := s.command.RemoveHumanAvatar(ctx, ctxData.ResourceOwner, ctxData.UserID)
if err != nil {
return nil, err
}
return &auth_pb.RemoveMyAvatarResponse{
Details: object.DomainToChangeDetailsPb(objectDetails),
}, nil
}

View File

@ -1,6 +1,8 @@
package auth
import (
"google.golang.org/grpc"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/server"
"github.com/caos/zitadel/internal/auth/repository"
@ -8,7 +10,6 @@ import (
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/pkg/grpc/auth"
"google.golang.org/grpc"
)
var _ auth.AuthServiceServer = (*Server)(nil)

View File

@ -23,8 +23,10 @@ func FeaturesFromModel(features *features_model.FeaturesView) *features_pb.Featu
LoginPolicyUsernameLogin: features.LoginPolicyUsernameLogin,
LoginPolicyPasswordReset: features.LoginPolicyPasswordReset,
PasswordComplexityPolicy: features.PasswordComplexityPolicy,
LabelPolicy: features.LabelPolicy,
LabelPolicy: features.LabelPolicyPrivateLabel,
CustomDomain: features.CustomDomain,
LabelPolicyPrivateLabel: features.LabelPolicyPrivateLabel,
LabelPolicyWatermark: features.LabelPolicyWatermark,
}
}

View File

@ -14,7 +14,15 @@ func (s *Server) GetLabelPolicy(ctx context.Context, req *mgmt_pb.GetLabelPolicy
if err != nil {
return nil, err
}
return &mgmt_pb.GetLabelPolicyResponse{Policy: policy_grpc.ModelLabelPolicyToPb(policy)}, nil
return &mgmt_pb.GetLabelPolicyResponse{Policy: policy_grpc.ModelLabelPolicyToPb(policy), IsDefault: policy.Default}, nil
}
func (s *Server) GetPreviewLabelPolicy(ctx context.Context, req *mgmt_pb.GetPreviewLabelPolicyRequest) (*mgmt_pb.GetPreviewLabelPolicyResponse, error) {
policy, err := s.org.GetPreviewLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.GetPreviewLabelPolicyResponse{Policy: policy_grpc.ModelLabelPolicyToPb(policy)}, nil
}
func (s *Server) GetDefaultLabelPolicy(ctx context.Context, req *mgmt_pb.GetDefaultLabelPolicyRequest) (*mgmt_pb.GetDefaultLabelPolicyResponse, error) {
@ -53,6 +61,20 @@ func (s *Server) UpdateCustomLabelPolicy(ctx context.Context, req *mgmt_pb.Updat
}, nil
}
func (s *Server) ActivateCustomLabelPolicy(ctx context.Context, req *mgmt_pb.ActivateCustomLabelPolicyRequest) (*mgmt_pb.ActivateCustomLabelPolicyResponse, error) {
policy, err := s.command.ActivateLabelPolicy(ctx, authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
return &mgmt_pb.ActivateCustomLabelPolicyResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) ResetLabelPolicyToDefault(ctx context.Context, req *mgmt_pb.ResetLabelPolicyToDefaultRequest) (*mgmt_pb.ResetLabelPolicyToDefaultResponse, error) {
objectDetails, err := s.command.RemoveLabelPolicy(ctx, authz.GetCtxData(ctx).OrgID)
if err != nil {
@ -62,3 +84,73 @@ func (s *Server) ResetLabelPolicyToDefault(ctx context.Context, req *mgmt_pb.Res
Details: object.DomainToChangeDetailsPb(objectDetails),
}, nil
}
func (s *Server) RemoveCustomLabelPolicyLogo(ctx context.Context, req *mgmt_pb.RemoveCustomLabelPolicyLogoRequest) (*mgmt_pb.RemoveCustomLabelPolicyLogoResponse, error) {
policy, err := s.command.RemoveLogoDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveCustomLabelPolicyLogoResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveCustomLabelPolicyLogoDark(ctx context.Context, req *mgmt_pb.RemoveCustomLabelPolicyLogoDarkRequest) (*mgmt_pb.RemoveCustomLabelPolicyLogoDarkResponse, error) {
policy, err := s.command.RemoveLogoDarkDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveCustomLabelPolicyLogoDarkResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveCustomLabelPolicyIcon(ctx context.Context, req *mgmt_pb.RemoveCustomLabelPolicyIconRequest) (*mgmt_pb.RemoveCustomLabelPolicyIconResponse, error) {
policy, err := s.command.RemoveIconDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveCustomLabelPolicyIconResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveCustomLabelPolicyIconDark(ctx context.Context, req *mgmt_pb.RemoveCustomLabelPolicyIconDarkRequest) (*mgmt_pb.RemoveCustomLabelPolicyIconDarkResponse, error) {
policy, err := s.command.RemoveIconDarkDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveCustomLabelPolicyIconDarkResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveCustomLabelPolicyFont(ctx context.Context, req *mgmt_pb.RemoveCustomLabelPolicyFontRequest) (*mgmt_pb.RemoveCustomLabelPolicyFontResponse, error) {
policy, err := s.command.RemoveFontDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveCustomLabelPolicyFontResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}

View File

@ -8,15 +8,27 @@ import (
func addLabelPolicyToDomain(p *mgmt_pb.AddCustomLabelPolicyRequest) *domain.LabelPolicy {
return &domain.LabelPolicy{
PrimaryColor: p.PrimaryColor,
SecondaryColor: p.SecondaryColor,
BackgroundColor: p.BackgroundColor,
WarnColor: p.WarnColor,
PrimaryColorDark: p.PrimaryColorDark,
BackgroundColorDark: p.BackgroundColorDark,
WarnColorDark: p.WarnColorDark,
HideLoginNameSuffix: p.HideLoginNameSuffix,
DisableWatermark: p.DisableWatermark,
}
}
func updateLabelPolicyToDomain(p *mgmt_pb.UpdateCustomLabelPolicyRequest) *domain.LabelPolicy {
return &domain.LabelPolicy{
PrimaryColor: p.PrimaryColor,
SecondaryColor: p.SecondaryColor,
BackgroundColor: p.BackgroundColor,
WarnColor: p.WarnColor,
FontColor: p.FontColor,
PrimaryColorDark: p.PrimaryColorDark,
BackgroundColorDark: p.BackgroundColorDark,
WarnColorDark: p.WarnColorDark,
FontColorDark: p.FontColorDark,
HideLoginNameSuffix: p.HideLoginNameSuffix,
DisableWatermark: p.DisableWatermark,
}
}

View File

@ -18,7 +18,7 @@ func (s *Server) GetLoginPolicy(ctx context.Context, req *mgmt_pb.GetLoginPolicy
if err != nil {
return nil, err
}
return &mgmt_pb.GetLoginPolicyResponse{Policy: policy_grpc.ModelLoginPolicyToPb(policy)}, nil
return &mgmt_pb.GetLoginPolicyResponse{Policy: policy_grpc.ModelLoginPolicyToPb(policy), IsDefault: policy.Default}, nil
}
func (s *Server) GetDefaultLoginPolicy(ctx context.Context, req *mgmt_pb.GetDefaultLoginPolicyRequest) (*mgmt_pb.GetDefaultLoginPolicyResponse, error) {

View File

@ -14,7 +14,8 @@ func (s *Server) GetPasswordAgePolicy(ctx context.Context, req *mgmt_pb.GetPassw
return nil, err
}
return &mgmt_pb.GetPasswordAgePolicyResponse{
Policy: policy_grpc.ModelPasswordAgePolicyToPb(policy),
Policy: policy_grpc.ModelPasswordAgePolicyToPb(policy),
IsDefault: policy.Default,
}, nil
}

View File

@ -13,7 +13,7 @@ func (s *Server) GetPasswordComplexityPolicy(ctx context.Context, req *mgmt_pb.G
if err != nil {
return nil, err
}
return &mgmt_pb.GetPasswordComplexityPolicyResponse{Policy: policy_grpc.ModelPasswordComplexityPolicyToPb(policy)}, nil
return &mgmt_pb.GetPasswordComplexityPolicyResponse{Policy: policy_grpc.ModelPasswordComplexityPolicyToPb(policy), IsDefault: policy.Default}, nil
}
func (s *Server) GetDefaultPasswordComplexityPolicy(ctx context.Context, req *mgmt_pb.GetDefaultPasswordComplexityPolicyRequest) (*mgmt_pb.GetDefaultPasswordComplexityPolicyResponse, error) {

View File

@ -13,7 +13,7 @@ func (s *Server) GetPasswordLockoutPolicy(ctx context.Context, req *mgmt_pb.GetP
if err != nil {
return nil, err
}
return &mgmt_pb.GetPasswordLockoutPolicyResponse{Policy: policy_grpc.ModelPasswordLockoutPolicyToPb(policy)}, nil
return &mgmt_pb.GetPasswordLockoutPolicyResponse{Policy: policy_grpc.ModelPasswordLockoutPolicyToPb(policy), IsDefault: policy.Default}, nil
}
func (s *Server) GetDefaultPasswordLockoutPolicy(ctx context.Context, req *mgmt_pb.GetDefaultPasswordLockoutPolicyRequest) (*mgmt_pb.GetDefaultPasswordLockoutPolicyResponse, error) {

View File

@ -323,6 +323,17 @@ func (s *Server) ResendHumanPhoneVerification(ctx context.Context, req *mgmt_pb.
}, nil
}
func (s *Server) RemoveHumanAvatar(ctx context.Context, req *mgmt_pb.RemoveHumanAvatarRequest) (*mgmt_pb.RemoveHumanAvatarResponse, error) {
ctxData := authz.GetCtxData(ctx)
objectDetails, err := s.command.RemoveHumanAvatar(ctx, ctxData.OrgID, req.UserId)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveHumanAvatarResponse{
Details: object.DomainToChangeDetailsPb(objectDetails),
}, nil
}
func (s *Server) SetHumanInitialPassword(ctx context.Context, req *mgmt_pb.SetHumanInitialPasswordRequest) (*mgmt_pb.SetHumanInitialPasswordResponse, error) {
objectDetails, err := s.command.SetPassword(ctx, authz.GetCtxData(ctx).OrgID, req.UserId, req.Password, true)
if err != nil {

View File

@ -10,7 +10,20 @@ func ModelLabelPolicyToPb(policy *model.LabelPolicyView) *policy_pb.LabelPolicy
return &policy_pb.LabelPolicy{
IsDefault: policy.Default,
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
BackgroundColor: policy.BackgroundColor,
FontColor: policy.FontColor,
WarnColor: policy.WarnColor,
PrimaryColorDark: policy.PrimaryColorDark,
BackgroundColorDark: policy.BackgroundColorDark,
WarnColorDark: policy.WarnColorDark,
FontColorDark: policy.FontColorDark,
FontUrl: policy.FontURL,
LogoUrl: policy.LogoURL,
LogoUrlDark: policy.LogoDarkURL,
IconUrl: policy.IconURL,
IconUrlDark: policy.IconDarkURL,
DisableWatermark: policy.DisableWatermark,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
Details: object.ToViewDetailsPb(
policy.Sequence,

View File

@ -80,6 +80,14 @@ func RemoteIPStringFromRequest(r *http.Request) string {
return r.RemoteAddr
}
func GetAuthorization(r *http.Request) string {
return r.Header.Get(Authorization)
}
func GetOrgID(r *http.Request) string {
return r.Header.Get(ZitadelOrgID)
}
func GetForwardedFor(headers http.Header) (string, bool) {
forwarded, ok := headers[ForwardedFor]
if ok {

View File

@ -0,0 +1,71 @@
package middleware
import (
"context"
"errors"
"net/http"
"github.com/caos/zitadel/internal/api/authz"
http_util "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
type AuthInterceptor struct {
verifier *authz.TokenVerifier
authConfig authz.Config
}
func AuthorizationInterceptor(verifier *authz.TokenVerifier, authConfig authz.Config) *AuthInterceptor {
return &AuthInterceptor{
verifier: verifier,
authConfig: authConfig,
}
}
func (a *AuthInterceptor) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, err := authorize(r, a.verifier, a.authConfig)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
func (a *AuthInterceptor) HandlerFunc(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, err := authorize(r, a.verifier, a.authConfig)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
}
}
type httpReq struct{}
func authorize(r *http.Request, verifier *authz.TokenVerifier, authConfig authz.Config) (_ context.Context, err error) {
ctx := r.Context()
authOpt, needsToken := verifier.CheckAuthMethod(r.Method + ":" + r.RequestURI)
if !needsToken {
return ctx, nil
}
authCtx, span := tracing.NewServerInterceptorSpan(ctx)
defer func() { span.EndWithError(err) }()
authToken := http_util.GetAuthorization(r)
if authToken == "" {
return nil, errors.New("auth header missing")
}
ctxSetter, err := authz.CheckUserAuthorization(authCtx, &httpReq{}, authToken, http_util.GetOrgID(r), verifier, authConfig, authOpt, r.RequestURI) //TODO: permission
if err != nil {
return nil, err
}
span.End()
return ctxSetter(ctx), nil
}

View File

@ -248,7 +248,7 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAge
if request.RequestedOrgID == "" {
username = user.PreferredLoginName
}
request.SetUserInfo(user.ID, username, user.PreferredLoginName, user.DisplayName, user.ResourceOwner)
request.SetUserInfo(user.ID, username, user.PreferredLoginName, user.DisplayName, user.AvatarKey, user.ResourceOwner)
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
@ -466,7 +466,7 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain
return err
}
request.SetUserInfo(user.ID, loginName, user.PreferredLoginName, "", user.ResourceOwner)
request.SetUserInfo(user.ID, loginName, user.PreferredLoginName, "", "", user.ResourceOwner)
return nil
}
@ -509,7 +509,7 @@ func (repo *AuthRequestRepo) checkExternalUserLogin(request *domain.AuthRequest,
if err != nil {
return err
}
request.SetUserInfo(externalIDP.UserID, "", "", "", externalIDP.ResourceOwner)
request.SetUserInfo(externalIDP.UserID, "", "", "", "", externalIDP.ResourceOwner)
return nil
}
@ -614,6 +614,7 @@ func (repo *AuthRequestRepo) usersForUserSelection(request *domain.AuthRequest)
DisplayName: session.DisplayName,
UserName: session.UserName,
LoginName: session.LoginName,
AvatarKey: session.AvatarKey,
UserSessionState: auth_req_model.UserSessionStateToDomain(session.State),
SelectionPossible: request.RequestedOrgID == "" || request.RequestedOrgID == session.ResourceOwner,
}
@ -710,9 +711,9 @@ func (repo *AuthRequestRepo) getLoginPolicy(ctx context.Context, orgID string) (
}
func (repo *AuthRequestRepo) getLabelPolicy(ctx context.Context, orgID string) (*domain.LabelPolicy, error) {
policy, err := repo.View.LabelPolicyByAggregateID(orgID)
policy, err := repo.View.LabelPolicyByAggregateIDAndState(orgID, int32(domain.LabelPolicyStateActive))
if errors.IsNotFound(err) {
policy, err = repo.View.LabelPolicyByAggregateID(repo.IAMID)
policy, err = repo.View.LabelPolicyByAggregateIDAndState(repo.IAMID, int32(domain.LabelPolicyStateActive))
if err != nil {
return nil, err
}

View File

@ -7,6 +7,7 @@ import (
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
iam_model "github.com/caos/zitadel/internal/iam/model"
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
@ -103,3 +104,14 @@ func (repo *OrgRepository) GetMyPasswordComplexityPolicy(ctx context.Context) (*
}
return iam_view_model.PasswordComplexityViewToModel(policy), err
}
func (repo *OrgRepository) GetLabelPolicy(ctx context.Context, orgID string) (*iam_model.LabelPolicyView, error) {
orgPolicy, err := repo.View.LabelPolicyByAggregateIDAndState(orgID, int32(domain.LabelPolicyStateActive))
if errors.IsNotFound(err) {
orgPolicy, err = repo.View.LabelPolicyByAggregateIDAndState(repo.SystemDefaults.IamID, int32(domain.LabelPolicyStateActive))
}
if err != nil {
return nil, err
}
return iam_view_model.LabelPolicyViewToModel(orgPolicy), nil
}

View File

@ -2,6 +2,8 @@ package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
v1 "github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/models"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
@ -79,8 +81,19 @@ func (m *LabelPolicy) processLabelPolicy(event *models.Event) (err error) {
switch event.Type {
case iam_es_model.LabelPolicyAdded, model.LabelPolicyAdded:
err = policy.AppendEvent(event)
case iam_es_model.LabelPolicyChanged, model.LabelPolicyChanged:
policy, err = m.view.LabelPolicyByAggregateID(event.AggregateID)
case iam_es_model.LabelPolicyChanged, model.LabelPolicyChanged,
iam_es_model.LabelPolicyActivated, model.LabelPolicyActivated,
iam_es_model.LabelPolicyLogoAdded, model.LabelPolicyLogoAdded,
iam_es_model.LabelPolicyLogoRemoved, model.LabelPolicyLogoRemoved,
iam_es_model.LabelPolicyIconAdded, model.LabelPolicyIconAdded,
iam_es_model.LabelPolicyIconRemoved, model.LabelPolicyIconRemoved,
iam_es_model.LabelPolicyLogoDarkAdded, model.LabelPolicyLogoDarkAdded,
iam_es_model.LabelPolicyLogoDarkRemoved, model.LabelPolicyLogoDarkRemoved,
iam_es_model.LabelPolicyIconDarkAdded, model.LabelPolicyIconDarkAdded,
iam_es_model.LabelPolicyIconDarkRemoved, model.LabelPolicyIconDarkRemoved,
iam_es_model.LabelPolicyFontAdded, model.LabelPolicyFontAdded,
iam_es_model.LabelPolicyFontRemoved, model.LabelPolicyFontRemoved:
policy, err = m.view.LabelPolicyByAggregateIDAndState(event.AggregateID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return err
}

View File

@ -122,6 +122,8 @@ func (u *User) ProcessUser(event *es_models.Event) (err error) {
es_model.HumanProfileChanged,
es_model.HumanEmailChanged,
es_model.HumanEmailVerified,
es_model.HumanAvatarAdded,
es_model.HumanAvatarRemoved,
es_model.HumanPhoneChanged,
es_model.HumanPhoneVerified,
es_model.HumanPhoneRemoved,

View File

@ -111,6 +111,8 @@ func (u *UserSession) Reduce(event *models.Event) (err error) {
es_model.HumanPasswordChanged,
es_model.HumanMFAOTPRemoved,
es_model.HumanProfileChanged,
es_model.HumanAvatarAdded,
es_model.HumanAvatarRemoved,
es_model.DomainClaimed,
es_model.UserUserNameChanged,
es_model.HumanExternalIDPRemoved,
@ -167,5 +169,6 @@ func (u *UserSession) fillUserInfo(session *view_model.UserSessionView, id strin
session.UserName = user.UserName
session.LoginName = user.PreferredLoginName
session.DisplayName = user.DisplayName
session.AvatarKey = user.AvatarKey
return nil
}

View File

@ -12,8 +12,8 @@ const (
labelPolicyTable = "auth.label_policies"
)
func (v *View) LabelPolicyByAggregateID(aggregateID string) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateID(v.Db, labelPolicyTable, aggregateID)
func (v *View) LabelPolicyByAggregateIDAndState(aggregateID string, state int32) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateIDAndState(v.Db, labelPolicyTable, aggregateID, state)
}
func (v *View) PutLabelPolicy(policy *model.LabelPolicyView, event *models.Event) error {

View File

@ -12,4 +12,5 @@ type OrgRepository interface {
GetDefaultOrgIAMPolicy(ctx context.Context) (*iam_model.OrgIAMPolicyView, error)
GetIDPConfigByID(ctx context.Context, idpConfigID string) (*iam_model.IDPConfigView, error)
GetMyPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error)
GetLabelPolicy(ctx context.Context, orgID string) (*iam_model.LabelPolicyView, error)
}

View File

@ -7,6 +7,7 @@ import (
"time"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/authz/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
@ -130,13 +131,21 @@ func checkFeatures(features *features_view_model.FeaturesView, requiredFeatures
if err := checkLoginPolicyFeatures(features, requiredFeature); err != nil {
return err
}
continue
}
if requiredFeature == domain.FeaturePasswordComplexityPolicy && !features.PasswordComplexityPolicy {
return MissingFeatureErr(requiredFeature)
if requiredFeature == domain.FeaturePasswordComplexityPolicy {
if !features.PasswordComplexityPolicy {
return MissingFeatureErr(requiredFeature)
}
continue
}
if requiredFeature == domain.FeatureLabelPolicy && !features.PasswordComplexityPolicy {
return MissingFeatureErr(requiredFeature)
if strings.HasPrefix(requiredFeature, domain.FeatureLabelPolicy) {
if err := checkLabelPolicyFeatures(features, requiredFeature); err != nil {
return err
}
continue
}
return MissingFeatureErr(requiredFeature)
}
return nil
}
@ -175,6 +184,20 @@ func checkLoginPolicyFeatures(features *features_view_model.FeaturesView, requir
return nil
}
func checkLabelPolicyFeatures(features *features_view_model.FeaturesView, requiredFeature string) error {
switch requiredFeature {
case domain.FeatureLabelPolicyPrivateLabel:
if !features.LabelPolicyPrivateLabel {
return MissingFeatureErr(requiredFeature)
}
case domain.FeatureLabelPolicyWatermark:
if !features.LabelPolicyWatermark {
return MissingFeatureErr(requiredFeature)
}
}
return nil
}
func MissingFeatureErr(feature string) error {
return caos_errs.ThrowPermissionDeniedf(nil, "AUTH-Dvgsf", "missing feature %v", feature)
}

View File

@ -20,12 +20,14 @@ import (
proj_repo "github.com/caos/zitadel/internal/repository/project"
usr_repo "github.com/caos/zitadel/internal/repository/user"
usr_grant_repo "github.com/caos/zitadel/internal/repository/usergrant"
"github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/telemetry/tracing"
webauthn_helper "github.com/caos/zitadel/internal/webauthn"
)
type Commands struct {
eventstore *eventstore.Eventstore
static static.Storage
idGenerator id.Generator
iamDomain string
zitadelRoles []authz.RoleMapping
@ -58,9 +60,10 @@ type Config struct {
Eventstore types.SQLUser
}
func StartCommands(eventstore *eventstore.Eventstore, defaults sd.SystemDefaults, authZConfig authz.Config, authZRepo *authz_repo.EsRepository) (repo *Commands, err error) {
func StartCommands(eventstore *eventstore.Eventstore, defaults sd.SystemDefaults, authZConfig authz.Config, staticStore static.Storage, authZRepo *authz_repo.EsRepository) (repo *Commands, err error) {
repo = &Commands{
eventstore: eventstore,
static: staticStore,
idGenerator: id.SonyFlakeGenerator,
iamDomain: defaults.Domain,
zitadelRoles: authZConfig.RolePermissionMappings,

View File

@ -0,0 +1,54 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/org"
)
type ExistingLabelPoliciesReadModel struct {
eventstore.WriteModel
aggregateIDs []string
}
func NewExistingLabelPoliciesReadModel(ctx context.Context) *ExistingLabelPoliciesReadModel {
return &ExistingLabelPoliciesReadModel{}
}
func (rm *ExistingLabelPoliciesReadModel) AppendEvents(events ...eventstore.EventReader) {
rm.WriteModel.AppendEvents(events...)
}
func (rm *ExistingLabelPoliciesReadModel) Reduce() error {
for _, event := range rm.Events {
switch e := event.(type) {
case *iam.LabelPolicyAddedEvent,
*org.LabelPolicyAddedEvent:
rm.aggregateIDs = append(rm.aggregateIDs, e.Aggregate().ID)
case *org.LabelPolicyRemovedEvent:
for i := len(rm.aggregateIDs) - 1; i >= 0; i-- {
if rm.aggregateIDs[i] == e.Aggregate().ID {
copy(rm.aggregateIDs[i:], rm.aggregateIDs[i+1:])
rm.aggregateIDs[len(rm.aggregateIDs)-1] = ""
rm.aggregateIDs = rm.aggregateIDs[:len(rm.aggregateIDs)-1]
}
}
}
}
return nil
}
func (rm *ExistingLabelPoliciesReadModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(
eventstore.ColumnsEvent,
iam.AggregateType,
org.AggregateType).
EventTypes(
iam.LabelPolicyAddedEventType,
org.LabelPolicyAddedEventType,
org.LabelPolicyRemovedEventType,
)
}

View File

@ -23,7 +23,8 @@ type FeaturesWriteModel struct {
LoginPolicyUsernameLogin bool
LoginPolicyPasswordReset bool
PasswordComplexityPolicy bool
LabelPolicy bool
LabelPolicyPrivateLabel bool
LabelPolicyWatermark bool
CustomDomain bool
}
@ -69,7 +70,13 @@ func (wm *FeaturesWriteModel) Reduce() error {
wm.PasswordComplexityPolicy = *e.PasswordComplexityPolicy
}
if e.LabelPolicy != nil {
wm.LabelPolicy = *e.LabelPolicy
wm.LabelPolicyPrivateLabel = *e.LabelPolicy
}
if e.LabelPolicyPrivateLabel != nil {
wm.LabelPolicyPrivateLabel = *e.LabelPolicyPrivateLabel
}
if e.LabelPolicyWatermark != nil {
wm.LabelPolicyWatermark = *e.LabelPolicyWatermark
}
if e.CustomDomain != nil {
wm.CustomDomain = *e.CustomDomain

View File

@ -49,8 +49,16 @@ func writeModelToLabelPolicy(wm *LabelPolicyWriteModel) *domain.LabelPolicy {
return &domain.LabelPolicy{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
PrimaryColor: wm.PrimaryColor,
SecondaryColor: wm.SecondaryColor,
BackgroundColor: wm.BackgroundColor,
WarnColor: wm.WarnColor,
FontColor: wm.FontColor,
PrimaryColorDark: wm.PrimaryColorDark,
BackgroundColorDark: wm.BackgroundColorDark,
WarnColorDark: wm.WarnColorDark,
FontColorDark: wm.FontColorDark,
HideLoginNameSuffix: wm.HideLoginNameSuffix,
ErrorMsgPopup: wm.ErrorMsgPopup,
DisableWatermark: wm.DisableWatermark,
}
}
@ -176,6 +184,8 @@ func writeModelToFeatures(wm *FeaturesWriteModel) *domain.Features {
LoginPolicyRegistration: wm.LoginPolicyRegistration,
LoginPolicyUsernameLogin: wm.LoginPolicyUsernameLogin,
PasswordComplexityPolicy: wm.PasswordComplexityPolicy,
LabelPolicy: wm.LabelPolicy,
LabelPolicyPrivateLabel: wm.LabelPolicyPrivateLabel,
LabelPolicyWatermark: wm.LabelPolicyWatermark,
CustomDomain: wm.CustomDomain,
}
}

View File

@ -46,7 +46,8 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *IAM
features.LoginPolicyRegistration,
features.LoginPolicyUsernameLogin,
features.PasswordComplexityPolicy,
features.LabelPolicy,
features.LabelPolicyPrivateLabel,
features.LabelPolicyWatermark,
features.CustomDomain,
)
if !hasChanged {
@ -61,5 +62,7 @@ func (c *Commands) getDefaultFeatures(ctx context.Context) (*domain.Features, er
if err != nil {
return nil, err
}
return writeModelToFeatures(&existingFeatures.FeaturesWriteModel), nil
features := writeModelToFeatures(&existingFeatures.FeaturesWriteModel)
features.IsDefault = true
return features, nil
}

View File

@ -62,7 +62,8 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
loginPolicyRegistration,
loginPolicyUsernameLogin,
passwordComplexityPolicy,
labelPolicy,
labelPolicyPrivateLabel,
labelPolicyWatermark,
customDomain bool,
) (*iam.FeaturesSetEvent, bool) {
@ -101,8 +102,11 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
if wm.PasswordComplexityPolicy != passwordComplexityPolicy {
changes = append(changes, features.ChangePasswordComplexityPolicy(passwordComplexityPolicy))
}
if wm.LabelPolicy != labelPolicy {
changes = append(changes, features.ChangeLabelPolicy(labelPolicy))
if wm.LabelPolicyPrivateLabel != labelPolicyPrivateLabel {
changes = append(changes, features.ChangeLabelPolicyPrivateLabel(labelPolicyPrivateLabel))
}
if wm.LabelPolicyWatermark != labelPolicyWatermark {
changes = append(changes, features.ChangeLabelPolicyWatermark(labelPolicyWatermark))
}
if wm.CustomDomain != customDomain {
changes = append(changes, features.ChangeCustomDomain(customDomain))

View File

@ -29,8 +29,8 @@ func (c *Commands) AddDefaultLabelPolicy(ctx context.Context, policy *domain.Lab
}
func (c *Commands) addDefaultLabelPolicy(ctx context.Context, iamAgg *eventstore.Aggregate, addedPolicy *IAMLabelPolicyWriteModel, policy *domain.LabelPolicy) (eventstore.EventPusher, error) {
if !policy.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-3m9fo", "Errors.IAM.LabelPolicy.Invalid")
if err := policy.IsValid(); err != nil {
return nil, err
}
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
@ -40,13 +40,26 @@ func (c *Commands) addDefaultLabelPolicy(ctx context.Context, iamAgg *eventstore
return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-2B0ps", "Errors.IAM.LabelPolicy.AlreadyExists")
}
return iam_repo.NewLabelPolicyAddedEvent(ctx, iamAgg, policy.PrimaryColor, policy.SecondaryColor, policy.HideLoginNameSuffix), nil
return iam_repo.NewLabelPolicyAddedEvent(
ctx,
iamAgg,
policy.PrimaryColor,
policy.BackgroundColor,
policy.WarnColor,
policy.FontColor,
policy.PrimaryColorDark,
policy.BackgroundColorDark,
policy.WarnColorDark,
policy.FontColorDark,
policy.HideLoginNameSuffix,
policy.ErrorMsgPopup,
policy.DisableWatermark), nil
}
func (c *Commands) ChangeDefaultLabelPolicy(ctx context.Context, policy *domain.LabelPolicy) (*domain.LabelPolicy, error) {
if !policy.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-33m8f", "Errors.IAM.LabelPolicy.Invalid")
if err := policy.IsValid(); err != nil {
return nil, err
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
@ -57,7 +70,20 @@ func (c *Commands) ChangeDefaultLabelPolicy(ctx context.Context, policy *domain.
return nil, caos_errs.ThrowNotFound(nil, "IAM-0K9dq", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, iamAgg, policy.PrimaryColor, policy.SecondaryColor, policy.HideLoginNameSuffix)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(
ctx,
iamAgg,
policy.PrimaryColor,
policy.BackgroundColor,
policy.WarnColor,
policy.FontColor,
policy.PrimaryColorDark,
policy.BackgroundColorDark,
policy.WarnColorDark,
policy.FontColorDark,
policy.HideLoginNameSuffix,
policy.ErrorMsgPopup,
policy.DisableWatermark)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged")
}
@ -73,6 +99,275 @@ func (c *Commands) ChangeDefaultLabelPolicy(ctx context.Context, policy *domain.
return writeModelToLabelPolicy(&existingPolicy.LabelPolicyWriteModel), nil
}
func (c *Commands) ActivateDefaultLabelPolicy(ctx context.Context) (*domain.ObjectDetails, error) {
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-6M23e", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyActivatedEvent(ctx, iamAgg))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddLogoDefaultLabelPolicy(ctx context.Context, storageKey string) (*domain.ObjectDetails, error) {
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-3m20c", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-Qw0pd", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyLogoAddedEvent(ctx, iamAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveLogoDefaultLabelPolicy(ctx context.Context) (*domain.ObjectDetails, error) {
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-Xc8Kf", "Errors.IAM.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, domain.IAMID, existingPolicy.LogoKey)
if err != nil {
return nil, err
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyLogoRemovedEvent(ctx, iamAgg, existingPolicy.LogoKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddIconDefaultLabelPolicy(ctx context.Context, storageKey string) (*domain.ObjectDetails, error) {
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-yxE4f", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-1yMx0", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyIconAddedEvent(ctx, iamAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveIconDefaultLabelPolicy(ctx context.Context) (*domain.ObjectDetails, error) {
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-4M0qw", "Errors.IAM.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, domain.IAMID, existingPolicy.IconKey)
if err != nil {
return nil, err
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyIconRemovedEvent(ctx, iamAgg, existingPolicy.IconKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddLogoDarkDefaultLabelPolicy(ctx context.Context, storageKey string) (*domain.ObjectDetails, error) {
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-4fMs9", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-ZR9fs", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyLogoDarkAddedEvent(ctx, iamAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveLogoDarkDefaultLabelPolicy(ctx context.Context) (*domain.ObjectDetails, error) {
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-3FGds", "Errors.IAM.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, domain.IAMID, existingPolicy.LogoDarkKey)
if err != nil {
return nil, err
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyLogoDarkRemovedEvent(ctx, iamAgg, existingPolicy.LogoDarkKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddIconDarkDefaultLabelPolicy(ctx context.Context, storageKey string) (*domain.ObjectDetails, error) {
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-1cxM3", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-vMsf9", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyIconDarkAddedEvent(ctx, iamAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveIconDarkDefaultLabelPolicy(ctx context.Context) (*domain.ObjectDetails, error) {
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-2nc7F", "Errors.IAM.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, domain.IAMID, existingPolicy.IconDarkKey)
if err != nil {
return nil, err
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyIconDarkRemovedEvent(ctx, iamAgg, existingPolicy.IconDarkKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddFontDefaultLabelPolicy(ctx context.Context, storageKey string) (*domain.ObjectDetails, error) {
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-1N8fs", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-1N8fE", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyFontAddedEvent(ctx, iamAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveFontDefaultLabelPolicy(ctx context.Context) (*domain.ObjectDetails, error) {
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-Tk0gw", "Errors.IAM.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, domain.IAMID, existingPolicy.FontKey)
if err != nil {
return nil, err
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyFontRemovedEvent(ctx, iamAgg, existingPolicy.FontKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) defaultLabelPolicyWriteModelByID(ctx context.Context) (policy *IAMLabelPolicyWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@ -84,3 +379,13 @@ func (c *Commands) defaultLabelPolicyWriteModelByID(ctx context.Context) (policy
}
return writeModel, nil
}
func (c *Commands) getDefaultLabelPolicy(ctx context.Context) (*domain.LabelPolicy, error) {
policyWriteModel, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
policy := writeModelToLabelPolicy(&policyWriteModel.LabelPolicyWriteModel)
policy.Default = true
return policy, nil
}

Some files were not shown because too many files have changed in this diff Show More