diff --git a/.gitignore b/.gitignore
index b5f00620af..9f5525c78a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,7 +58,7 @@ openapi/**/*.json
/internal/api/ui/console/static/*
# local
-build/local/cloud.env
+build/local/*.env
migrations/cockroach/migrate_cloud.go
.notifications
.artifacts
diff --git a/cmd/admin/setup/03.go b/cmd/admin/setup/03.go
index 76ba6b31c9..248f861755 100644
--- a/cmd/admin/setup/03.go
+++ b/cmd/admin/setup/03.go
@@ -24,6 +24,8 @@ type DefaultInstance struct {
domain string
defaults systemdefaults.SystemDefaults
zitadelRoles []authz.RoleMapping
+ baseURL string
+ externalSecure bool
}
func (mig *DefaultInstance) Execute(ctx context.Context) error {
@@ -45,7 +47,8 @@ func (mig *DefaultInstance) Execute(ctx context.Context) error {
mig.zitadelRoles,
nil,
nil,
- webauthn_helper.Config{},
+ //TODO: Livio will fix this, but it ZITADEL doesn't run without this
+ webauthn_helper.Config{DisplayName: "HELLO LIVIO", ID: "RPID"},
nil,
nil,
nil,
@@ -54,8 +57,12 @@ func (mig *DefaultInstance) Execute(ctx context.Context) error {
nil,
nil)
+ if err != nil {
+ return err
+ }
ctx = authz.WithRequestedDomain(ctx, mig.domain)
- _, err = cmd.SetUpInstance(ctx, &mig.InstanceSetup)
+
+ _, _, err = cmd.SetUpInstance(ctx, &mig.InstanceSetup, mig.externalSecure, mig.baseURL)
return err
}
diff --git a/cmd/admin/setup/config.go b/cmd/admin/setup/config.go
index 75676484a5..57c520ac9e 100644
--- a/cmd/admin/setup/config.go
+++ b/cmd/admin/setup/config.go
@@ -4,6 +4,7 @@ import (
"bytes"
"github.com/caos/logging"
+ "github.com/caos/zitadel/internal/command"
"github.com/mitchellh/mapstructure"
"github.com/spf13/viper"
@@ -15,20 +16,27 @@ import (
)
type Config struct {
- Database database.Config
- SystemDefaults systemdefaults.SystemDefaults
- InternalAuthZ authz.Config
- ExternalPort uint16
- ExternalDomain string
- ExternalSecure bool
- Log *logging.Config
- EncryptionKeys *encryptionKeyConfig
+ Database database.Config
+ SystemDefaults systemdefaults.SystemDefaults
+ InternalAuthZ authz.Config
+ ExternalPort uint16
+ ExternalDomain string
+ ExternalSecure bool
+ Log *logging.Config
+ EncryptionKeys *encryptionKeyConfig
+ DefaultInstance command.InstanceSetup
}
func MustNewConfig(v *viper.Viper) *Config {
config := new(Config)
- err := v.Unmarshal(config)
- logging.OnError(err).Fatal("unable to read config")
+ err := v.Unmarshal(config,
+ viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
+ hook.Base64ToBytesHookFunc(),
+ hook.TagToLanguageHookFunc(),
+ mapstructure.StringToTimeDurationHookFunc(),
+ mapstructure.StringToSliceHookFunc(","),
+ )),
+ )
err = config.Log.SetLogger()
logging.OnError(err).Fatal("unable to set logger")
diff --git a/cmd/admin/setup/setup.go b/cmd/admin/setup/setup.go
index 6604958985..dd05bd3c2d 100644
--- a/cmd/admin/setup/setup.go
+++ b/cmd/admin/setup/setup.go
@@ -51,6 +51,12 @@ func Setup(config *Config, steps *Steps, masterKey string) {
steps.s1ProjectionTable = &ProjectionTable{dbClient: dbClient}
steps.s2AssetsTable = &AssetTable{dbClient: dbClient}
+ instanceSetup := config.DefaultInstance
+ instanceSetup.InstanceName = steps.S3DefaultInstance.InstanceSetup.InstanceName
+ instanceSetup.CustomDomain = steps.S3DefaultInstance.InstanceSetup.CustomDomain
+ instanceSetup.Org = steps.S3DefaultInstance.InstanceSetup.Org
+ steps.S3DefaultInstance.InstanceSetup = instanceSetup
+
steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address = strings.TrimSpace(steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address)
if steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address == "" {
steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address = "admin@" + config.ExternalDomain
@@ -63,13 +69,14 @@ func Setup(config *Config, steps *Steps, masterKey string) {
steps.S3DefaultInstance.domain = config.ExternalDomain
steps.S3DefaultInstance.zitadelRoles = config.InternalAuthZ.RolePermissionMappings
steps.S3DefaultInstance.userEncryptionKey = config.EncryptionKeys.User
- steps.S3DefaultInstance.InstanceSetup.Zitadel.IsDevMode = !config.ExternalSecure
- steps.S3DefaultInstance.InstanceSetup.Zitadel.BaseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
- steps.S3DefaultInstance.InstanceSetup.Zitadel.IsDevMode = !config.ExternalSecure
- steps.S3DefaultInstance.InstanceSetup.Zitadel.BaseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
+ steps.S3DefaultInstance.externalSecure = config.ExternalSecure
+ steps.S3DefaultInstance.baseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
ctx := context.Background()
- migration.Migrate(ctx, eventstoreClient, steps.s1ProjectionTable)
- migration.Migrate(ctx, eventstoreClient, steps.s2AssetsTable)
- migration.Migrate(ctx, eventstoreClient, steps.S3DefaultInstance)
+ err = migration.Migrate(ctx, eventstoreClient, steps.s1ProjectionTable)
+ logging.OnError(err).Fatal("unable to migrate step 1")
+ err = migration.Migrate(ctx, eventstoreClient, steps.s2AssetsTable)
+ logging.OnError(err).Fatal("unable to migrate step 3")
+ err = migration.Migrate(ctx, eventstoreClient, steps.S3DefaultInstance)
+ logging.OnError(err).Fatal("unable to migrate step 4")
}
diff --git a/cmd/admin/setup/steps.yaml b/cmd/admin/setup/steps.yaml
index f391897ad8..05b1131978 100644
--- a/cmd/admin/setup/steps.yaml
+++ b/cmd/admin/setup/steps.yaml
@@ -1,5 +1,7 @@
S3DefaultInstance:
InstanceSetup:
+ InstanceName: Localhost
+ CustomDomain: localhost
Org:
Name: ZITADEL
Human:
@@ -8,7 +10,7 @@ S3DefaultInstance:
LastName: Admin
NickName:
DisplayName:
- Email:
+ Email:
Address: #autogenerated if empty. uses domain from config and prefixes admin@. for example: admin@domain.tdl
Verified: true
PreferredLanguage:
@@ -17,200 +19,3 @@ S3DefaultInstance:
Number:
Verified:
Password: Password1!
- SecretGenerators:
- PasswordSaltCost: 14
- ClientSecret:
- Length: 64
- IncludeLowerLetters: true
- IncludeUpperLetters: true
- IncludeDigits: true
- IncludeSymbols: false
- InitializeUserCode:
- Length: 6
- Expiry: '72h'
- IncludeLowerLetters: false
- IncludeUpperLetters: true
- IncludeDigits: true
- IncludeSymbols: false
- EmailVerificationCode:
- Length: 6
- Expiry: '1h'
- IncludeLowerLetters: false
- IncludeUpperLetters: true
- IncludeDigits: true
- IncludeSymbols: false
- PhoneVerificationCode:
- Length: 6
- Expiry: '1h'
- IncludeLowerLetters: false
- IncludeUpperLetters: true
- IncludeDigits: true
- IncludeSymbols: false
- PasswordVerificationCode:
- Length: 6
- Expiry: '1h'
- IncludeLowerLetters: false
- IncludeUpperLetters: true
- IncludeDigits: true
- IncludeSymbols: false
- PasswordlessInitCode:
- Length: 12
- Expiry: '1h'
- IncludeLowerLetters: true
- IncludeUpperLetters: true
- IncludeDigits: true
- IncludeSymbols: false
- DomainVerification:
- Length: 32
- IncludeLowerLetters: true
- IncludeUpperLetters: true
- IncludeDigits: true
- IncludeSymbols: false
- Features:
- TierName: Default Tier
- TierDescription: ""
- State: 1 #active
- StateDescription: ""
- Retention: 8760h #1year
- LoginPolicyFactors: true
- LoginPolicyIDP: true
- LoginPolicyPasswordless: true
- LoginPolicyRegistration: true
- LoginPolicyUsernameLogin: true
- LoginPolicyPasswordReset: true
- PasswordComplexityPolicy: true
- LabelPolicyPrivateLabel: true
- LabelPolicyWatermark: true
- CustomDomain: true
- PrivacyPolicy: true
- MetadataUser: true
- CustomTextMessage: true
- CustomTextLogin: true
- LockoutPolicy: true
- ActionsAllowed: 2 #ActionsAllowedUnlimited
- MaxActions: #not necessary because of ActionsAllowedUnlimited
- PasswordComplexityPolicy:
- MinLength: 8
- HasLowercase: true
- HasUppercase: true
- HasNumber: true
- HasSymbol: true
- PasswordAgePolicy:
- ExpireWarnDays: 0
- MaxAgeDays: 0
- DomainPolicy:
- UserLoginMustBeDomain: true
- ValidateOrgDomains: true
- LoginPolicy:
- AllowUsernamePassword: true
- AllowRegister: true
- AllowExternalIDP: true
- ForceMFA: false
- HidePasswordReset: false
- PasswordlessType: 1 #1: allowed 0: not allowed
- PasswordCheckLifetime: 240h #10d
- ExternalLoginCheckLifetime: 240h #10d
- MfaInitSkipLifetime: 720h #30d
- SecondFactorCheckLifetime: 18h
- MultiFactorCheckLifetime: 12h
- PrivacyPolicy:
- TOSLink: https://docs.zitadel.ch/docs/legal/terms-of-service
- PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy
- HelpLink: ''
- LabelPolicy:
- PrimaryColor: '#5469d4'
- BackgroundColor: '#fafafa'
- WarnColor: '#f44336'
- FontColor: '#000000'
- PrimaryColorDark: '#5469d4'
- BackgroundColorDark: '#212121'
- WarnColorDark: '#f44336'
- FontColorDark: '#ffffff'
- HideLoginNameSuffix: false
- ErrorMsgPopup: false
- DisableWatermark: false
- LockoutPolicy:
- MaxAttempts: 0
- ShouldShowLockoutFailure: true
- EmailTemplate: CjwhZG9jdHlwZSBodG1sPgo8aHRtbCB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSI+CjxoZWFkPgogIDx0aXRsZT4KCiAgPC90aXRsZT4KICA8IS0tW2lmICFtc29dPjwhLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIj4KICA8IS0tPCFbZW5kaWZdLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgiPgogIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSI+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KICAgICNvdXRsb29rIGEgeyBwYWRkaW5nOjA7IH0KICAgIGJvZHkgeyBtYXJnaW46MDtwYWRkaW5nOjA7LXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OjEwMCU7LW1zLXRleHQtc2l6ZS1hZGp1c3Q6MTAwJTsgfQogICAgdGFibGUsIHRkIHsgYm9yZGVyLWNvbGxhcHNlOmNvbGxhcHNlO21zby10YWJsZS1sc3BhY2U6MHB0O21zby10YWJsZS1yc3BhY2U6MHB0OyB9CiAgICBpbWcgeyBib3JkZXI6MDtoZWlnaHQ6YXV0bztsaW5lLWhlaWdodDoxMDAlOyBvdXRsaW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uOm5vbmU7LW1zLWludGVycG9sYXRpb24tbW9kZTpiaWN1YmljOyB9CiAgICBwIHsgZGlzcGxheTpibG9jazttYXJnaW46MTNweCAwOyB9CiAgPC9zdHlsZT4KICA8IS0tW2lmIG1zb10+CiAgPHhtbD4KICAgIDxvOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgICAgIDxvOkFsbG93UE5HLz4KICAgICAgPG86UGl4ZWxzUGVySW5jaD45NjwvbzpQaXhlbHNQZXJJbmNoPgogICAgPC9vOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgPC94bWw+CiAgPCFbZW5kaWZdLS0+CiAgPCEtLVtpZiBsdGUgbXNvIDExXT4KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgLm1qLW91dGxvb2stZ3JvdXAtZml4IHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyB9CiAgPC9zdHlsZT4KICA8IVtlbmRpZl0tLT4KCiAgPCEtLVtpZiAhbXNvXT48IS0tPgogIDxsaW5rIGhyZWY9Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzP2ZhbWlseT1VYnVudHU6MzAwLDQwMCw1MDAsNzAwIiByZWw9InN0eWxlc2hlZXQiIHR5cGU9InRleHQvY3NzIj4KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgQGltcG9ydCB1cmwoaHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PVVidW50dTozMDAsNDAwLDUwMCw3MDApOwogIDwvc3R5bGU+CiAgPCEtLTwhW2VuZGlmXS0tPgoKCgogIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CiAgICBAbWVkaWEgb25seSBzY3JlZW4gYW5kIChtaW4td2lkdGg6NDgwcHgpIHsKICAgICAgLm1qLWNvbHVtbi1wZXItMTAwIHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyBtYXgtd2lkdGg6IDEwMCU7IH0KICAgICAgLm1qLWNvbHVtbi1wZXItNjAgeyB3aWR0aDo2MCUgIWltcG9ydGFudDsgbWF4LXdpZHRoOiA2MCU7IH0KICAgIH0KICA8L3N0eWxlPgoKCiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCgoKICAgIEBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDo0ODBweCkgewogICAgICB0YWJsZS5tai1mdWxsLXdpZHRoLW1vYmlsZSB7IHdpZHRoOiAxMDAlICFpbXBvcnRhbnQ7IH0KICAgICAgdGQubWotZnVsbC13aWR0aC1tb2JpbGUgeyB3aWR0aDogYXV0byAhaW1wb3J0YW50OyB9CiAgICB9CgogIDwvc3R5bGU+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc2hhZG93IGEgewogICAgYm94LXNoYWRvdzogMHB4IDNweCAxcHggLTJweCByZ2JhKDAsIDAsIDAsIDAuMiksIDBweCAycHggMnB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLCAwcHggMXB4IDVweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKTsKICB9PC9zdHlsZT4KCiAge3tpZiAuRm9udFVSTH19CiAgPHN0eWxlPgogICAgQGZvbnQtZmFjZSB7CiAgICAgIGZvbnQtZmFtaWx5OiAne3suRm9udEZhbWlseX19JzsKICAgICAgZm9udC1zdHlsZTogbm9ybWFsOwogICAgICBmb250LWRpc3BsYXk6IHN3YXA7CiAgICAgIHNyYzogdXJsKHt7LkZvbnRVUkx9fSk7CiAgICB9CiAgPC9zdHlsZT4KICB7e2VuZH19Cgo8L2hlYWQ+Cjxib2R5IHN0eWxlPSJ3b3JkLXNwYWNpbmc6bm9ybWFsOyI+CgoKPGRpdgogICAgICAgIHN0eWxlPSIiCj4KCiAgPHRhYmxlCiAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYmFja2dyb3VuZDp7ey5CYWNrZ3JvdW5kQ29sb3J9fTtiYWNrZ3JvdW5kLWNvbG9yOnt7LkJhY2tncm91bmRDb2xvcn19O3dpZHRoOjEwMCU7Ym9yZGVyLXJhZGl1czoxNnB4OyIKICA+CiAgICA8dGJvZHk+CiAgICA8dHI+CiAgICAgIDx0ZD4KCgogICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgY2xhc3M9IiIgc3R5bGU9IndpZHRoOjgwMHB4OyIgd2lkdGg9IjgwMCIgPjx0cj48dGQgc3R5bGU9ImxpbmUtaGVpZ2h0OjBweDtmb250LXNpemU6MHB4O21zby1saW5lLWhlaWdodC1ydWxlOmV4YWN0bHk7Ij48IVtlbmRpZl0tLT4KCgogICAgICAgIDxkaXYgIHN0eWxlPSJtYXJnaW46MHB4IGF1dG87Ym9yZGVyLXJhZGl1czoxNnB4O21heC13aWR0aDo4MDBweDsiPgoKICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTtib3JkZXItcmFkaXVzOjE2cHg7IgogICAgICAgICAgPgogICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgIHN0eWxlPSJkaXJlY3Rpb246bHRyO2ZvbnQtc2l6ZTowcHg7cGFkZGluZzoyMHB4IDA7cGFkZGluZy1sZWZ0OjA7dGV4dC1hbGlnbjpjZW50ZXI7IgogICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGNsYXNzPSIiIHdpZHRoPSI4MDBweCIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTsiCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD4KCgogICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PHRhYmxlIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IiB3aWR0aD0iODAwIiA+PHRyPjx0ZCBzdHlsZT0ibGluZS1oZWlnaHQ6MHB4O2ZvbnQtc2l6ZTowcHg7bXNvLWxpbmUtaGVpZ2h0LXJ1bGU6ZXhhY3RseTsiPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICA8ZGl2ICBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO21heC13aWR0aDo4MDBweDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZGlyZWN0aW9uOmx0cjtmb250LXNpemU6MHB4O3BhZGRpbmc6MDt0ZXh0LWFsaWduOmNlbnRlcjsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGNsYXNzPSIiIHN0eWxlPSJ3aWR0aDo4MDBweDsiID48IVtlbmRpZl0tLT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcz0ibWotY29sdW1uLXBlci0xMDAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MDtsaW5lLWhlaWdodDowO3RleHQtYWxpZ246bGVmdDtkaXNwbGF5OmlubGluZS1ibG9jazt3aWR0aDoxMDAlO2RpcmVjdGlvbjpsdHI7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PHRhYmxlIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iID48dHI+PHRkIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6ODAwcHg7IiA+PCFbZW5kaWZdLS0+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzPSJtai1jb2x1bW4tcGVyLTEwMCBtai1vdXRsb29rLWdyb3VwLWZpeCIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7dGV4dC1hbGlnbjpsZWZ0O2RpcmVjdGlvbjpsdHI7ZGlzcGxheTppbmxpbmUtYmxvY2s7dmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgd2lkdGg9IjEwMCUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCAgc3R5bGU9InZlcnRpY2FsLWFsaWduOnRvcDtwYWRkaW5nOjA7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IiIgd2lkdGg9IjEwMCUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6NTBweCAwIDMwcHggMDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJvcmRlci1jb2xsYXBzZTpjb2xsYXBzZTtib3JkZXItc3BhY2luZzowcHg7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgIHN0eWxlPSJ3aWR0aDoxODBweDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxpbWcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVpZ2h0PSJhdXRvIiBzcmM9Int7LkxvZ29VUkx9fSIgc3R5bGU9ImJvcmRlcjowO2JvcmRlci1yYWRpdXM6OHB4O2Rpc3BsYXk6YmxvY2s7b3V0bGluZTpub25lO3RleHQtZGVjb3JhdGlvbjpub25lO2hlaWdodDphdXRvO3dpZHRoOjEwMCU7Zm9udC1zaXplOjEzcHg7IiB3aWR0aD0iMTgwIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLz4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKCiAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48dHI+PHRkIGNsYXNzPSIiIHdpZHRoPSI4MDBweCIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTsiCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD4KCgogICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PHRhYmxlIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IiB3aWR0aD0iODAwIiA+PHRyPjx0ZCBzdHlsZT0ibGluZS1oZWlnaHQ6MHB4O2ZvbnQtc2l6ZTowcHg7bXNvLWxpbmUtaGVpZ2h0LXJ1bGU6ZXhhY3RseTsiPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICA8ZGl2ICBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO21heC13aWR0aDo4MDBweDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZGlyZWN0aW9uOmx0cjtmb250LXNpemU6MHB4O3BhZGRpbmc6MDt0ZXh0LWFsaWduOmNlbnRlcjsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGNsYXNzPSIiIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6NDgwcHg7IiA+PCFbZW5kaWZdLS0+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3M9Im1qLWNvbHVtbi1wZXItNjAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MHB4O3RleHQtYWxpZ246bGVmdDtkaXJlY3Rpb246bHRyO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3ZlcnRpY2FsLWFsaWduOnRvcDt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHdpZHRoPSIxMDAlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkICBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3BhZGRpbmc6MDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSIiIHdpZHRoPSIxMDAlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MTBweCAyNXB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MjRweDtmb250LXdlaWdodDo1MDA7bGluZS1oZWlnaHQ6MTt0ZXh0LWFsaWduOmNlbnRlcjtjb2xvcjp7ey5Gb250Q29sb3J9fTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPnt7LkdyZWV0aW5nfX08L2Rpdj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7d29yZC1icmVhazpicmVhay13b3JkOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZm9udC1mYW1pbHk6e3suRm9udEZhbWlseX19O2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OmxpZ2h0O2xpbmUtaGVpZ2h0OjEuNTt0ZXh0LWFsaWduOmNlbnRlcjtjb2xvcjp7ey5Gb250Q29sb3J9fTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPnt7LlRleHR9fTwvZGl2PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgdmVydGljYWwtYWxpZ249Im1pZGRsZSIgY2xhc3M9InNoYWRvdyIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7d29yZC1icmVhazpicmVhay13b3JkOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJib3JkZXItY29sbGFwc2U6c2VwYXJhdGU7bGluZS1oZWlnaHQ6MTAwJTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBiZ2NvbG9yPSJ7ey5QcmltYXJ5Q29sb3J9fSIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYm9yZGVyOm5vbmU7Ym9yZGVyLXJhZGl1czo2cHg7Y3Vyc29yOmF1dG87bXNvLXBhZGRpbmctYWx0OjEwcHggMjVweDtiYWNrZ3JvdW5kOnt7LlByaW1hcnlDb2xvcn19OyIgdmFsaWduPSJtaWRkbGUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaHJlZj0ie3suVVJMfX0iIHJlbD0ibm9vcGVuZXIgbm9yZWZlcnJlciIgc3R5bGU9ImRpc3BsYXk6aW5saW5lLWJsb2NrO2JhY2tncm91bmQ6e3suUHJpbWFyeUNvbG9yfX07Y29sb3I6I2ZmZmZmZjtmb250LWZhbWlseTpVYnVudHUsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwO2xpbmUtaGVpZ2h0OjEyMCU7bWFyZ2luOjA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7dGV4dC10cmFuc2Zvcm06bm9uZTtwYWRkaW5nOjEwcHggMjVweDttc28tcGFkZGluZy1hbHQ6MHB4O2JvcmRlci1yYWRpdXM6NnB4OyIgdGFyZ2V0PSJfYmxhbmsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge3suQnV0dG9uVGV4dH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9hPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7e2lmIC5JbmNsdWRlRm9vdGVyfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7cGFkZGluZy10b3A6MjBweDtwYWRkaW5nLXJpZ2h0OjIwcHg7cGFkZGluZy1ib3R0b206MjBweDtwYWRkaW5nLWxlZnQ6MjBweDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iYm9yZGVyLXRvcDpzb2xpZCAycHggI2RiZGJkYjtmb250LXNpemU6MXB4O21hcmdpbjowcHggYXV0bzt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9wPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHN0eWxlPSJib3JkZXItdG9wOnNvbGlkIDJweCAjZGJkYmRiO2ZvbnQtc2l6ZToxcHg7bWFyZ2luOjBweCBhdXRvO3dpZHRoOjQ0MHB4OyIgcm9sZT0icHJlc2VudGF0aW9uIiB3aWR0aD0iNDQwcHgiID48dHI+PHRkIHN0eWxlPSJoZWlnaHQ6MDtsaW5lLWhlaWdodDowOyI+ICZuYnNwOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxNnB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MTNweDtsaW5lLWhlaWdodDoxO3RleHQtYWxpZ246Y2VudGVyO2NvbG9yOnt7LkZvbnRDb2xvcn19OyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+e3suRm9vdGVyVGV4dH19PC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHt7ZW5kfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKCiAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgogICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICA8L2Rpdj4KCgogICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgIDwvdGQ+CiAgICA8L3RyPgogICAgPC90Ym9keT4KICA8L3RhYmxlPgoKPC9kaXY+Cgo8L2JvZHk+CjwvaHRtbD4KICA=
- MessageTexts:
- - MessageTextType: InitCode
- Language: de
- Title: Zitadel - User initialisieren
- PreHeader: User initialisieren
- Subject: User initialisieren
- Greeting: Hallo {{.FirstName}} {{.LastName}},
- Text: Dieser Benutzer wurde soeben im Zitadel erstellt. Mit dem Benutzernamen <br><strong>{{.PreferredLoginName}}</strong><br> kannst du dich anmelden. Nutze den untenstehenden Button, um die Initialisierung abzuschliessen <br>(Code <strong>{{.Code}}</strong>).<br> Falls du dieses Mail nicht angefordert hast, kannst du es einfach ignorieren.
- ButtonText: Initialisierung abschliessen
- - MessageTextType: PasswordReset
- Language: de
- Title: Zitadel - Passwort zurücksetzen
- PreHeader: Passwort zurücksetzen
- Subject: Passwort zurücksetzen
- Greeting: Hallo {{.FirstName}} {{.LastName}},
- Text: Wir haben eine Anfrage für das Zurücksetzen deines Passwortes bekommen. Du kannst den untenstehenden Button verwenden, um dein Passwort zurückzusetzen <br>(Code <strong>{{.Code}}</strong>).<br> Falls du dieses Mail nicht angefordert hast, kannst du es ignorieren.
- ButtonText: Passwort zurücksetzen
- - MessageTextType: VerifyEmail
- Language: de
- Title: Zitadel - Email verifizieren
- PreHeader: Email verifizieren
- Subject: Email verifizieren
- Greeting: Hallo {{.FirstName}} {{.LastName}},
- Text: Eine neue E-Mail Adresse wurde hinzugefügt. Bitte verwende den untenstehenden Button um diese zu verifizieren <br>(Code <strong>{{.Code}}</strong>).<br> Falls du deine E-Mail Adresse nicht selber hinzugefügt hast, kannst du dieses E-Mail ignorieren.
- ButtonText: Email verifizieren
- - MessageTextType: VerifyPhone
- Language: de
- Title: Zitadel - Telefonnummer verifizieren
- PreHeader: Telefonnummer verifizieren
- Subject: Telefonnummer verifizieren
- Greeting: Hallo {{.FirstName}} {{.LastName}},
- Text: Eine Telefonnummer wurde hinzugefügt. Bitte verifiziere diese in dem du folgenden Code eingibst (Code {{.Code}})
- ButtonText: Telefon verifizieren
- - MessageTextType: DomainClaimed
- Language: de
- Title: Zitadel - Domain wurde beansprucht
- PreHeader: Email / Username ändern
- Subject: Domain wurde beansprucht
- Greeting: Hallo {{.FirstName}} {{.LastName}},
- Text: Die Domain {{.Domain}} wurde von einer Organisation beansprucht. Dein derzeitiger User {{.Username}} ist nicht Teil dieser Organisation. Daher musst du beim nächsten Login eine neue Email hinterlegen. Für diesen Login haben wir dir einen temporären Usernamen ({{.TempUsername}}) erstellt.
- ButtonText: Login
- - MessageTextType: InitCode
- Language: en
- Title: Zitadel - Initialize User
- PreHeader: Initialize User
- Subject: Initialize User
- Greeting: Hello {{.FirstName}} {{.LastName}},
- Text: This user was created in Zitadel. Use the username {{.PreferredLoginName}} to login. Please click the button below to finish the initialization process. (Code {{.Code}}) If you didn't ask for this mail, please ignore it.
- ButtonText: Finish initialization
- - MessageTextType: PasswordReset
- Language: en
- Title: Zitadel - Reset password
- PreHeader: Reset password
- Subject: Reset password
- Greeting: Hello {{.FirstName}} {{.LastName}},
- Text: We received a password reset request. Please use the button below to reset your password. (Code {{.Code}}) If you didn't ask for this mail, please ignore it.
- ButtonText: Reset password
- - MessageTextType: VerifyEmail
- Language: en
- Title: Zitadel - Verify email
- PreHeader: Verify email
- Subject: Verify email
- Greeting: Hello {{.FirstName}} {{.LastName}},
- Text: A new email has been added. Please use the button below to verify your mail. (Code {{.Code}}) If you din't add a new email, please ignore this email.
- ButtonText: Verify email
- - MessageTextType: VerifyPhone
- Language: en
- Title: Zitadel - Verify phone
- PreHeader: Verify phone
- Subject: Verify phone
- Greeting: Hello {{.FirstName}} {{.LastName}},
- Text: A new phonenumber has been added. Please use the following code to verify it {{.Code}}.
- ButtonText: Verify phone
- - MessageTextType: DomainClaimed
- Language: en
- Title: Zitadel - Domain has been claimed
- PreHeader: Change email / username
- Subject: Domain has been claimed
- Greeting: Hello {{.FirstName}} {{.LastName}},
- Text: The domain {{.Domain}} has been claimed by an organisation. Your current user {{.UserName}} is not part of this organisation. Therefore you'll have to change your email when you login. We have created a temporary username ({{.TempUsername}}) for this login.
- ButtonText: Login
diff --git a/cmd/admin/start/config.go b/cmd/admin/start/config.go
index be4f0a5b24..63e8c6e045 100644
--- a/cmd/admin/start/config.go
+++ b/cmd/admin/start/config.go
@@ -2,6 +2,9 @@ package start
import (
"github.com/caos/logging"
+ "github.com/caos/zitadel/internal/command"
+ "github.com/caos/zitadel/internal/config/hook"
+ "github.com/mitchellh/mapstructure"
"github.com/spf13/viper"
admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
@@ -42,14 +45,20 @@ type Config struct {
InternalAuthZ internal_authz.Config
SystemDefaults systemdefaults.SystemDefaults
EncryptionKeys *encryptionKeyConfig
+ DefaultInstance command.InstanceSetup
}
func MustNewConfig(v *viper.Viper) *Config {
config := new(Config)
- err := v.Unmarshal(config)
- logging.OnError(err).Fatal("unable to read config")
-
+ err := v.Unmarshal(config,
+ viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
+ hook.Base64ToBytesHookFunc(),
+ hook.TagToLanguageHookFunc(),
+ mapstructure.StringToTimeDurationHookFunc(),
+ mapstructure.StringToSliceHookFunc(","),
+ )),
+ )
err = config.Log.SetLogger()
logging.OnError(err).Fatal("unable to set logger")
diff --git a/cmd/admin/start/start.go b/cmd/admin/start/start.go
index 7b89d657e2..8e9baa78be 100644
--- a/cmd/admin/start/start.go
+++ b/cmd/admin/start/start.go
@@ -14,6 +14,7 @@ import (
"github.com/caos/logging"
"github.com/caos/oidc/pkg/op"
+ "github.com/caos/zitadel/internal/api/grpc/system"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -142,7 +143,7 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
}
verifier := internal_authz.Start(repo)
- apis := api.New(config.Port, router, &repo, config.InternalAuthZ, config.ExternalSecure, config.HTTP2HostHeader)
+ authenticatedAPIs := api.New(config.Port, router, &repo, config.InternalAuthZ, config.ExternalSecure, config.HTTP2HostHeader)
authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, keys.OIDC, keys.User)
if err != nil {
return fmt.Errorf("error starting auth repo: %w", err)
@@ -151,18 +152,21 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
if err != nil {
return fmt.Errorf("error starting admin repo: %w", err)
}
- if err := apis.RegisterServer(ctx, admin.CreateServer(commands, queries, adminRepo, config.SystemDefaults.Domain, assets.HandlerPrefix, keys.User)); err != nil {
+ if err := authenticatedAPIs.RegisterServer(ctx, system.CreateServer(commands, queries, adminRepo, config.DefaultInstance, config.ExternalPort, config.ExternalDomain, config.ExternalSecure)); err != nil {
return err
}
- if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, assets.HandlerPrefix, keys.User)); err != nil {
+ if err := authenticatedAPIs.RegisterServer(ctx, admin.CreateServer(commands, queries, adminRepo, config.SystemDefaults.Domain, assets.HandlerPrefix, keys.User)); err != nil {
return err
}
- if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, assets.HandlerPrefix, keys.User)); err != nil {
+ if err := authenticatedAPIs.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, assets.HandlerPrefix, keys.User)); err != nil {
+ return err
+ }
+ if err := authenticatedAPIs.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, assets.HandlerPrefix, keys.User)); err != nil {
return err
}
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader)
- apis.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator, store, queries, instanceInterceptor.Handler))
+ authenticatedAPIs.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator, store, queries, instanceInterceptor.Handler))
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, config.ExternalDomain, id.SonyFlakeGenerator, config.ExternalSecure)
if err != nil {
@@ -174,26 +178,26 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
if err != nil {
return fmt.Errorf("unable to start oidc provider: %w", err)
}
- apis.RegisterHandler(oidc.HandlerPrefix, oidcProvider.HttpHandler())
+ authenticatedAPIs.RegisterHandler(oidc.HandlerPrefix, oidcProvider.HttpHandler())
openAPIHandler, err := openapi.Start()
if err != nil {
return fmt.Errorf("unable to start openapi handler: %w", err)
}
- apis.RegisterHandler(openapi.HandlerPrefix, openAPIHandler)
+ authenticatedAPIs.RegisterHandler(openapi.HandlerPrefix, openAPIHandler)
baseURL := http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
c, err := console.Start(config.Console, config.ExternalDomain, baseURL, issuer, instanceInterceptor.Handler)
if err != nil {
return fmt.Errorf("unable to start console: %w", err)
}
- apis.RegisterHandler(console.HandlerPrefix, c)
+ authenticatedAPIs.RegisterHandler(console.HandlerPrefix, c)
l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, config.SystemDefaults, console.HandlerPrefix+"/", config.ExternalDomain, baseURL, op.AuthCallbackURL(oidcProvider), config.ExternalSecure, userAgentInterceptor, instanceInterceptor.Handler, keys.User, keys.IDPConfig, keys.CSRFCookieKey)
if err != nil {
return fmt.Errorf("unable to start login: %w", err)
}
- apis.RegisterHandler(login.HandlerPrefix, l.Handler())
+ authenticatedAPIs.RegisterHandler(login.HandlerPrefix, l.Handler())
return nil
}
diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml
index 5f25a723fa..00cb1f9802 100644
--- a/cmd/defaults.yaml
+++ b/cmd/defaults.yaml
@@ -184,6 +184,223 @@ SystemDefaults:
SigningKeyRotationCheck: 10s
SigningKeyGracefulPeriod: 10m
+DefaultInstance:
+ InstanceName:
+ Org:
+ Name:
+ Human:
+ UserName: zitadel-admin
+ FirstName: ZITADEL
+ LastName: Admin
+ NickName:
+ DisplayName:
+ Email:
+ Address:
+ Verified: false
+ PreferredLanguage:
+ Gender:
+ Phone:
+ Number:
+ Verified:
+ Password:
+ SecretGenerators:
+ PasswordSaltCost: 14
+ ClientSecret:
+ Length: 64
+ IncludeLowerLetters: true
+ IncludeUpperLetters: true
+ IncludeDigits: true
+ IncludeSymbols: false
+ InitializeUserCode:
+ Length: 6
+ Expiry: '72h'
+ IncludeLowerLetters: false
+ IncludeUpperLetters: true
+ IncludeDigits: true
+ IncludeSymbols: false
+ EmailVerificationCode:
+ Length: 6
+ Expiry: '1h'
+ IncludeLowerLetters: false
+ IncludeUpperLetters: true
+ IncludeDigits: true
+ IncludeSymbols: false
+ PhoneVerificationCode:
+ Length: 6
+ Expiry: '1h'
+ IncludeLowerLetters: false
+ IncludeUpperLetters: true
+ IncludeDigits: true
+ IncludeSymbols: false
+ PasswordVerificationCode:
+ Length: 6
+ Expiry: '1h'
+ IncludeLowerLetters: false
+ IncludeUpperLetters: true
+ IncludeDigits: true
+ IncludeSymbols: false
+ PasswordlessInitCode:
+ Length: 12
+ Expiry: '1h'
+ IncludeLowerLetters: true
+ IncludeUpperLetters: true
+ IncludeDigits: true
+ IncludeSymbols: false
+ DomainVerification:
+ Length: 32
+ IncludeLowerLetters: true
+ IncludeUpperLetters: true
+ IncludeDigits: true
+ IncludeSymbols: false
+ Features:
+ TierName: Default Tier
+ TierDescription: ""
+ State: 1 #active
+ StateDescription: ""
+ Retention: 8760h #1year
+ LoginPolicyFactors: true
+ LoginPolicyIDP: true
+ LoginPolicyPasswordless: true
+ LoginPolicyRegistration: true
+ LoginPolicyUsernameLogin: true
+ LoginPolicyPasswordReset: true
+ PasswordComplexityPolicy: true
+ LabelPolicyPrivateLabel: true
+ LabelPolicyWatermark: true
+ CustomDomain: true
+ PrivacyPolicy: true
+ MetadataUser: true
+ CustomTextMessage: true
+ CustomTextLogin: true
+ LockoutPolicy: true
+ ActionsAllowed: 2 #ActionsAllowedUnlimited
+ MaxActions: #not necessary because of ActionsAllowedUnlimited
+ PasswordComplexityPolicy:
+ MinLength: 8
+ HasLowercase: true
+ HasUppercase: true
+ HasNumber: true
+ HasSymbol: true
+ PasswordAgePolicy:
+ ExpireWarnDays: 0
+ MaxAgeDays: 0
+ DomainPolicy:
+ UserLoginMustBeDomain: true
+ ValidateOrgDomains: true
+ LoginPolicy:
+ AllowUsernamePassword: true
+ AllowRegister: true
+ AllowExternalIDP: true
+ ForceMFA: false
+ HidePasswordReset: false
+ PasswordlessType: 1 #1: allowed 0: not allowed
+ PasswordCheckLifetime: 240h #10d
+ ExternalLoginCheckLifetime: 240h #10d
+ MfaInitSkipLifetime: 720h #30d
+ SecondFactorCheckLifetime: 18h
+ MultiFactorCheckLifetime: 12h
+ PrivacyPolicy:
+ TOSLink: https://docs.zitadel.ch/docs/legal/terms-of-service
+ PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy
+ HelpLink: ''
+ LabelPolicy:
+ PrimaryColor: '#5469d4'
+ BackgroundColor: '#fafafa'
+ WarnColor: '#f44336'
+ FontColor: '#000000'
+ PrimaryColorDark: '#5469d4'
+ BackgroundColorDark: '#212121'
+ WarnColorDark: '#f44336'
+ FontColorDark: '#ffffff'
+ HideLoginNameSuffix: false
+ ErrorMsgPopup: false
+ DisableWatermark: false
+ LockoutPolicy:
+ MaxAttempts: 0
+ ShouldShowLockoutFailure: true
+ EmailTemplate: CjwhZG9jdHlwZSBodG1sPgo8aHRtbCB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSI+CjxoZWFkPgogIDx0aXRsZT4KCiAgPC90aXRsZT4KICA8IS0tW2lmICFtc29dPjwhLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIj4KICA8IS0tPCFbZW5kaWZdLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgiPgogIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSI+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KICAgICNvdXRsb29rIGEgeyBwYWRkaW5nOjA7IH0KICAgIGJvZHkgeyBtYXJnaW46MDtwYWRkaW5nOjA7LXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OjEwMCU7LW1zLXRleHQtc2l6ZS1hZGp1c3Q6MTAwJTsgfQogICAgdGFibGUsIHRkIHsgYm9yZGVyLWNvbGxhcHNlOmNvbGxhcHNlO21zby10YWJsZS1sc3BhY2U6MHB0O21zby10YWJsZS1yc3BhY2U6MHB0OyB9CiAgICBpbWcgeyBib3JkZXI6MDtoZWlnaHQ6YXV0bztsaW5lLWhlaWdodDoxMDAlOyBvdXRsaW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uOm5vbmU7LW1zLWludGVycG9sYXRpb24tbW9kZTpiaWN1YmljOyB9CiAgICBwIHsgZGlzcGxheTpibG9jazttYXJnaW46MTNweCAwOyB9CiAgPC9zdHlsZT4KICA8IS0tW2lmIG1zb10+CiAgPHhtbD4KICAgIDxvOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgICAgIDxvOkFsbG93UE5HLz4KICAgICAgPG86UGl4ZWxzUGVySW5jaD45NjwvbzpQaXhlbHNQZXJJbmNoPgogICAgPC9vOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgPC94bWw+CiAgPCFbZW5kaWZdLS0+CiAgPCEtLVtpZiBsdGUgbXNvIDExXT4KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgLm1qLW91dGxvb2stZ3JvdXAtZml4IHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyB9CiAgPC9zdHlsZT4KICA8IVtlbmRpZl0tLT4KCiAgPCEtLVtpZiAhbXNvXT48IS0tPgogIDxsaW5rIGhyZWY9Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzP2ZhbWlseT1VYnVudHU6MzAwLDQwMCw1MDAsNzAwIiByZWw9InN0eWxlc2hlZXQiIHR5cGU9InRleHQvY3NzIj4KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgQGltcG9ydCB1cmwoaHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PVVidW50dTozMDAsNDAwLDUwMCw3MDApOwogIDwvc3R5bGU+CiAgPCEtLTwhW2VuZGlmXS0tPgoKCgogIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CiAgICBAbWVkaWEgb25seSBzY3JlZW4gYW5kIChtaW4td2lkdGg6NDgwcHgpIHsKICAgICAgLm1qLWNvbHVtbi1wZXItMTAwIHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyBtYXgtd2lkdGg6IDEwMCU7IH0KICAgICAgLm1qLWNvbHVtbi1wZXItNjAgeyB3aWR0aDo2MCUgIWltcG9ydGFudDsgbWF4LXdpZHRoOiA2MCU7IH0KICAgIH0KICA8L3N0eWxlPgoKCiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCgoKICAgIEBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDo0ODBweCkgewogICAgICB0YWJsZS5tai1mdWxsLXdpZHRoLW1vYmlsZSB7IHdpZHRoOiAxMDAlICFpbXBvcnRhbnQ7IH0KICAgICAgdGQubWotZnVsbC13aWR0aC1tb2JpbGUgeyB3aWR0aDogYXV0byAhaW1wb3J0YW50OyB9CiAgICB9CgogIDwvc3R5bGU+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc2hhZG93IGEgewogICAgYm94LXNoYWRvdzogMHB4IDNweCAxcHggLTJweCByZ2JhKDAsIDAsIDAsIDAuMiksIDBweCAycHggMnB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLCAwcHggMXB4IDVweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKTsKICB9PC9zdHlsZT4KCiAge3tpZiAuRm9udFVSTH19CiAgPHN0eWxlPgogICAgQGZvbnQtZmFjZSB7CiAgICAgIGZvbnQtZmFtaWx5OiAne3suRm9udEZhbWlseX19JzsKICAgICAgZm9udC1zdHlsZTogbm9ybWFsOwogICAgICBmb250LWRpc3BsYXk6IHN3YXA7CiAgICAgIHNyYzogdXJsKHt7LkZvbnRVUkx9fSk7CiAgICB9CiAgPC9zdHlsZT4KICB7e2VuZH19Cgo8L2hlYWQ+Cjxib2R5IHN0eWxlPSJ3b3JkLXNwYWNpbmc6bm9ybWFsOyI+CgoKPGRpdgogICAgICAgIHN0eWxlPSIiCj4KCiAgPHRhYmxlCiAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYmFja2dyb3VuZDp7ey5CYWNrZ3JvdW5kQ29sb3J9fTtiYWNrZ3JvdW5kLWNvbG9yOnt7LkJhY2tncm91bmRDb2xvcn19O3dpZHRoOjEwMCU7Ym9yZGVyLXJhZGl1czoxNnB4OyIKICA+CiAgICA8dGJvZHk+CiAgICA8dHI+CiAgICAgIDx0ZD4KCgogICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgY2xhc3M9IiIgc3R5bGU9IndpZHRoOjgwMHB4OyIgd2lkdGg9IjgwMCIgPjx0cj48dGQgc3R5bGU9ImxpbmUtaGVpZ2h0OjBweDtmb250LXNpemU6MHB4O21zby1saW5lLWhlaWdodC1ydWxlOmV4YWN0bHk7Ij48IVtlbmRpZl0tLT4KCgogICAgICAgIDxkaXYgIHN0eWxlPSJtYXJnaW46MHB4IGF1dG87Ym9yZGVyLXJhZGl1czoxNnB4O21heC13aWR0aDo4MDBweDsiPgoKICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTtib3JkZXItcmFkaXVzOjE2cHg7IgogICAgICAgICAgPgogICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgIHN0eWxlPSJkaXJlY3Rpb246bHRyO2ZvbnQtc2l6ZTowcHg7cGFkZGluZzoyMHB4IDA7cGFkZGluZy1sZWZ0OjA7dGV4dC1hbGlnbjpjZW50ZXI7IgogICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGNsYXNzPSIiIHdpZHRoPSI4MDBweCIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTsiCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD4KCgogICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PHRhYmxlIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IiB3aWR0aD0iODAwIiA+PHRyPjx0ZCBzdHlsZT0ibGluZS1oZWlnaHQ6MHB4O2ZvbnQtc2l6ZTowcHg7bXNvLWxpbmUtaGVpZ2h0LXJ1bGU6ZXhhY3RseTsiPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICA8ZGl2ICBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO21heC13aWR0aDo4MDBweDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZGlyZWN0aW9uOmx0cjtmb250LXNpemU6MHB4O3BhZGRpbmc6MDt0ZXh0LWFsaWduOmNlbnRlcjsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGNsYXNzPSIiIHN0eWxlPSJ3aWR0aDo4MDBweDsiID48IVtlbmRpZl0tLT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcz0ibWotY29sdW1uLXBlci0xMDAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MDtsaW5lLWhlaWdodDowO3RleHQtYWxpZ246bGVmdDtkaXNwbGF5OmlubGluZS1ibG9jazt3aWR0aDoxMDAlO2RpcmVjdGlvbjpsdHI7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PHRhYmxlIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iID48dHI+PHRkIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6ODAwcHg7IiA+PCFbZW5kaWZdLS0+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzPSJtai1jb2x1bW4tcGVyLTEwMCBtai1vdXRsb29rLWdyb3VwLWZpeCIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7dGV4dC1hbGlnbjpsZWZ0O2RpcmVjdGlvbjpsdHI7ZGlzcGxheTppbmxpbmUtYmxvY2s7dmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgd2lkdGg9IjEwMCUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCAgc3R5bGU9InZlcnRpY2FsLWFsaWduOnRvcDtwYWRkaW5nOjA7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IiIgd2lkdGg9IjEwMCUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6NTBweCAwIDMwcHggMDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJvcmRlci1jb2xsYXBzZTpjb2xsYXBzZTtib3JkZXItc3BhY2luZzowcHg7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgIHN0eWxlPSJ3aWR0aDoxODBweDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxpbWcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVpZ2h0PSJhdXRvIiBzcmM9Int7LkxvZ29VUkx9fSIgc3R5bGU9ImJvcmRlcjowO2JvcmRlci1yYWRpdXM6OHB4O2Rpc3BsYXk6YmxvY2s7b3V0bGluZTpub25lO3RleHQtZGVjb3JhdGlvbjpub25lO2hlaWdodDphdXRvO3dpZHRoOjEwMCU7Zm9udC1zaXplOjEzcHg7IiB3aWR0aD0iMTgwIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLz4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKCiAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48dHI+PHRkIGNsYXNzPSIiIHdpZHRoPSI4MDBweCIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTsiCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD4KCgogICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PHRhYmxlIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IiB3aWR0aD0iODAwIiA+PHRyPjx0ZCBzdHlsZT0ibGluZS1oZWlnaHQ6MHB4O2ZvbnQtc2l6ZTowcHg7bXNvLWxpbmUtaGVpZ2h0LXJ1bGU6ZXhhY3RseTsiPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICA8ZGl2ICBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO21heC13aWR0aDo4MDBweDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZGlyZWN0aW9uOmx0cjtmb250LXNpemU6MHB4O3BhZGRpbmc6MDt0ZXh0LWFsaWduOmNlbnRlcjsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGNsYXNzPSIiIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6NDgwcHg7IiA+PCFbZW5kaWZdLS0+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3M9Im1qLWNvbHVtbi1wZXItNjAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MHB4O3RleHQtYWxpZ246bGVmdDtkaXJlY3Rpb246bHRyO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3ZlcnRpY2FsLWFsaWduOnRvcDt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHdpZHRoPSIxMDAlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkICBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3BhZGRpbmc6MDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSIiIHdpZHRoPSIxMDAlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MTBweCAyNXB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MjRweDtmb250LXdlaWdodDo1MDA7bGluZS1oZWlnaHQ6MTt0ZXh0LWFsaWduOmNlbnRlcjtjb2xvcjp7ey5Gb250Q29sb3J9fTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPnt7LkdyZWV0aW5nfX08L2Rpdj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7d29yZC1icmVhazpicmVhay13b3JkOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZm9udC1mYW1pbHk6e3suRm9udEZhbWlseX19O2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OmxpZ2h0O2xpbmUtaGVpZ2h0OjEuNTt0ZXh0LWFsaWduOmNlbnRlcjtjb2xvcjp7ey5Gb250Q29sb3J9fTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPnt7LlRleHR9fTwvZGl2PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgdmVydGljYWwtYWxpZ249Im1pZGRsZSIgY2xhc3M9InNoYWRvdyIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7d29yZC1icmVhazpicmVhay13b3JkOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJib3JkZXItY29sbGFwc2U6c2VwYXJhdGU7bGluZS1oZWlnaHQ6MTAwJTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBiZ2NvbG9yPSJ7ey5QcmltYXJ5Q29sb3J9fSIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYm9yZGVyOm5vbmU7Ym9yZGVyLXJhZGl1czo2cHg7Y3Vyc29yOmF1dG87bXNvLXBhZGRpbmctYWx0OjEwcHggMjVweDtiYWNrZ3JvdW5kOnt7LlByaW1hcnlDb2xvcn19OyIgdmFsaWduPSJtaWRkbGUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaHJlZj0ie3suVVJMfX0iIHJlbD0ibm9vcGVuZXIgbm9yZWZlcnJlciIgc3R5bGU9ImRpc3BsYXk6aW5saW5lLWJsb2NrO2JhY2tncm91bmQ6e3suUHJpbWFyeUNvbG9yfX07Y29sb3I6I2ZmZmZmZjtmb250LWZhbWlseTpVYnVudHUsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwO2xpbmUtaGVpZ2h0OjEyMCU7bWFyZ2luOjA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7dGV4dC10cmFuc2Zvcm06bm9uZTtwYWRkaW5nOjEwcHggMjVweDttc28tcGFkZGluZy1hbHQ6MHB4O2JvcmRlci1yYWRpdXM6NnB4OyIgdGFyZ2V0PSJfYmxhbmsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge3suQnV0dG9uVGV4dH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9hPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7e2lmIC5JbmNsdWRlRm9vdGVyfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7cGFkZGluZy10b3A6MjBweDtwYWRkaW5nLXJpZ2h0OjIwcHg7cGFkZGluZy1ib3R0b206MjBweDtwYWRkaW5nLWxlZnQ6MjBweDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iYm9yZGVyLXRvcDpzb2xpZCAycHggI2RiZGJkYjtmb250LXNpemU6MXB4O21hcmdpbjowcHggYXV0bzt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9wPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHN0eWxlPSJib3JkZXItdG9wOnNvbGlkIDJweCAjZGJkYmRiO2ZvbnQtc2l6ZToxcHg7bWFyZ2luOjBweCBhdXRvO3dpZHRoOjQ0MHB4OyIgcm9sZT0icHJlc2VudGF0aW9uIiB3aWR0aD0iNDQwcHgiID48dHI+PHRkIHN0eWxlPSJoZWlnaHQ6MDtsaW5lLWhlaWdodDowOyI+ICZuYnNwOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxNnB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MTNweDtsaW5lLWhlaWdodDoxO3RleHQtYWxpZ246Y2VudGVyO2NvbG9yOnt7LkZvbnRDb2xvcn19OyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+e3suRm9vdGVyVGV4dH19PC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHt7ZW5kfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKCiAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgogICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICA8L2Rpdj4KCgogICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgIDwvdGQ+CiAgICA8L3RyPgogICAgPC90Ym9keT4KICA8L3RhYmxlPgoKPC9kaXY+Cgo8L2JvZHk+CjwvaHRtbD4KICA=
+ MessageTexts:
+ - MessageTextType: InitCode
+ Language: de
+ Title: Zitadel - User initialisieren
+ PreHeader: User initialisieren
+ Subject: User initialisieren
+ Greeting: Hallo {{.FirstName}} {{.LastName}},
+ Text: Dieser Benutzer wurde soeben im Zitadel erstellt. Mit dem Benutzernamen <br><strong>{{.PreferredLoginName}}</strong><br> kannst du dich anmelden. Nutze den untenstehenden Button, um die Initialisierung abzuschliessen <br>(Code <strong>{{.Code}}</strong>).<br> Falls du dieses Mail nicht angefordert hast, kannst du es einfach ignorieren.
+ ButtonText: Initialisierung abschliessen
+ - MessageTextType: PasswordReset
+ Language: de
+ Title: Zitadel - Passwort zurücksetzen
+ PreHeader: Passwort zurücksetzen
+ Subject: Passwort zurücksetzen
+ Greeting: Hallo {{.FirstName}} {{.LastName}},
+ Text: Wir haben eine Anfrage für das Zurücksetzen deines Passwortes bekommen. Du kannst den untenstehenden Button verwenden, um dein Passwort zurückzusetzen <br>(Code <strong>{{.Code}}</strong>).<br> Falls du dieses Mail nicht angefordert hast, kannst du es ignorieren.
+ ButtonText: Passwort zurücksetzen
+ - MessageTextType: VerifyEmail
+ Language: de
+ Title: Zitadel - Email verifizieren
+ PreHeader: Email verifizieren
+ Subject: Email verifizieren
+ Greeting: Hallo {{.FirstName}} {{.LastName}},
+ Text: Eine neue E-Mail Adresse wurde hinzugefügt. Bitte verwende den untenstehenden Button um diese zu verifizieren <br>(Code <strong>{{.Code}}</strong>).<br> Falls du deine E-Mail Adresse nicht selber hinzugefügt hast, kannst du dieses E-Mail ignorieren.
+ ButtonText: Email verifizieren
+ - MessageTextType: VerifyPhone
+ Language: de
+ Title: Zitadel - Telefonnummer verifizieren
+ PreHeader: Telefonnummer verifizieren
+ Subject: Telefonnummer verifizieren
+ Greeting: Hallo {{.FirstName}} {{.LastName}},
+ Text: Eine Telefonnummer wurde hinzugefügt. Bitte verifiziere diese in dem du folgenden Code eingibst (Code {{.Code}})
+ ButtonText: Telefon verifizieren
+ - MessageTextType: DomainClaimed
+ Language: de
+ Title: Zitadel - Domain wurde beansprucht
+ PreHeader: Email / Username ändern
+ Subject: Domain wurde beansprucht
+ Greeting: Hallo {{.FirstName}} {{.LastName}},
+ Text: Die Domain {{.Domain}} wurde von einer Organisation beansprucht. Dein derzeitiger User {{.Username}} ist nicht Teil dieser Organisation. Daher musst du beim nächsten Login eine neue Email hinterlegen. Für diesen Login haben wir dir einen temporären Usernamen ({{.TempUsername}}) erstellt.
+ ButtonText: Login
+ - MessageTextType: InitCode
+ Language: en
+ Title: Zitadel - Initialize User
+ PreHeader: Initialize User
+ Subject: Initialize User
+ Greeting: Hello {{.FirstName}} {{.LastName}},
+ Text: This user was created in Zitadel. Use the username {{.PreferredLoginName}} to login. Please click the button below to finish the initialization process. (Code {{.Code}}) If you didn't ask for this mail, please ignore it.
+ ButtonText: Finish initialization
+ - MessageTextType: PasswordReset
+ Language: en
+ Title: Zitadel - Reset password
+ PreHeader: Reset password
+ Subject: Reset password
+ Greeting: Hello {{.FirstName}} {{.LastName}},
+ Text: We received a password reset request. Please use the button below to reset your password. (Code {{.Code}}) If you didn't ask for this mail, please ignore it.
+ ButtonText: Reset password
+ - MessageTextType: VerifyEmail
+ Language: en
+ Title: Zitadel - Verify email
+ PreHeader: Verify email
+ Subject: Verify email
+ Greeting: Hello {{.FirstName}} {{.LastName}},
+ Text: A new email has been added. Please use the button below to verify your mail. (Code {{.Code}}) If you din't add a new email, please ignore this email.
+ ButtonText: Verify email
+ - MessageTextType: VerifyPhone
+ Language: en
+ Title: Zitadel - Verify phone
+ PreHeader: Verify phone
+ Subject: Verify phone
+ Greeting: Hello {{.FirstName}} {{.LastName}},
+ Text: A new phonenumber has been added. Please use the following code to verify it {{.Code}}.
+ ButtonText: Verify phone
+ - MessageTextType: DomainClaimed
+ Language: en
+ Title: Zitadel - Domain has been claimed
+ PreHeader: Change email / username
+ Subject: Domain has been claimed
+ Greeting: Hello {{.FirstName}} {{.LastName}},
+ Text: The domain {{.Domain}} has been claimed by an organisation. Your current user {{.UserName}} is not part of this organisation. Therefore you'll have to change your email when you login. We have created a temporary username ({{.TempUsername}}) for this login.
+ ButtonText: Login
+
InternalAuthZ:
RolePermissionMappings:
- Role: 'IAM_OWNER'
diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md
index 89d07061b4..300300fdcc 100644
--- a/docs/docs/apis/proto/admin.md
+++ b/docs/docs/apis/proto/admin.md
@@ -1427,21 +1427,6 @@ they represent the delta of the event happend on the objects
POST: /views/_search
-### ClearView
-
-> **rpc** ClearView([ClearViewRequest](#clearviewrequest))
-[ClearViewResponse](#clearviewresponse)
-
-Truncates the delta of the change stream
-be carefull with this function because ZITADEL has to
-recompute the deltas after they got cleared.
-Search requests will return wrong results until all deltas are recomputed
-
-
-
- POST: /views/{database}/{view_name}
-
-
### ListFailedEvents
> **rpc** ListFailedEvents([ListFailedEventsRequest](#listfailedeventsrequest))
@@ -1463,7 +1448,7 @@ For example if the SMTP-API wasn't able to send an email at the first time
Deletes the event from failed events view.
the event is not removed from the change stream
-This call is usefull if the system was able to process the event later.
+This call is usefull if the system was able to process the event later.
e.g. if the second try of sending an email was successful. the first try produced a
failed event. You can find out if it worked on the `failure_count`
@@ -1718,24 +1703,6 @@ This is an empty request
-### ClearViewRequest
-
-
-
-| Field | Type | Description | Validation |
-| ----- | ---- | ----------- | ----------- |
-| database | string | - | string.min_len: 1
string.max_len: 200
|
-| view_name | string | - | string.min_len: 1
string.max_len: 200
|
-
-
-
-
-### ClearViewResponse
-This is an empty response
-
-
-
-
### DeactivateIDPRequest
diff --git a/docs/docs/apis/proto/instance.md b/docs/docs/apis/proto/instance.md
index dacfab4921..a20b1f0802 100644
--- a/docs/docs/apis/proto/instance.md
+++ b/docs/docs/apis/proto/instance.md
@@ -70,13 +70,13 @@ DomainPrimaryQuery is always equals
-### IdQuery
+### IdsQuery
IdQuery is always equals
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
-| id | string | - | string.max_len: 200
|
+| ids | repeated string | - | |
@@ -91,7 +91,6 @@ IdQuery is always equals
| details | zitadel.v1.ObjectDetails | - | |
| state | State | - | |
| name | string | - | |
-| version | string | - | |
@@ -102,19 +101,7 @@ IdQuery is always equals
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
-| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.id_query | IdQuery | - | |
-| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.state_query | StateQuery | - | |
-
-
-
-
-### StateQuery
-StateQuery is always equals
-
-
-| Field | Type | Description | Validation |
-| ----- | ---- | ----------- | ----------- |
-| state | State | - | enum.defined_only: true
|
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.id_query | IdsQuery | - | |
diff --git a/docs/docs/apis/proto/system.md b/docs/docs/apis/proto/system.md
index aae6b40457..caf4c73ecc 100644
--- a/docs/docs/apis/proto/system.md
+++ b/docs/docs/apis/proto/system.md
@@ -29,7 +29,7 @@ Returns a list of ZITADEL instances/tenants
- POST: /instances
+ POST: /instances/_search
### GetInstance
@@ -70,18 +70,6 @@ This might take some time
DELETE: /instances/{id}
-### GetUsage
-
-> **rpc** GetUsage([GetUsageRequest](#getusagerequest))
-[GetUsageResponse](#getusageresponse)
-
-Returns the usage metrics of an instance
-
-
-
- GET: /instances/{id}/usage
-
-
### ListDomains
> **rpc** ListDomains([ListDomainsRequest](#listdomainsrequest))
@@ -91,7 +79,7 @@ Returns the custom domains of an instance
- GET: /instances/{id}/domains
+ POST: /instances/{id}/domains/_search
### AddDomain
@@ -227,13 +215,12 @@ failed event. You can find out if it worked on the `failure_count`
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| instance_name | string | - | string.min_len: 1
string.max_len: 200
|
-| first_org_name | string | - | string.min_len: 1
string.max_len: 200
|
+| first_org_name | string | - | string.max_len: 200
|
| custom_domain | string | - | string.max_len: 200
|
-| owner_first_name | string | - | string.min_len: 1
string.max_len: 200
|
-| owner_last_name | string | - | string.min_len: 1
string.max_len: 200
|
+| owner_first_name | string | - | string.max_len: 200
|
+| owner_last_name | string | - | string.max_len: 200
|
| owner_email | string | - | string.min_len: 1
string.max_len: 200
|
-| owner_username | string | - | string.min_len: 1
string.max_len: 200
|
-| password | string | - | string.min_len: 1
string.max_len: 200
|
+| owner_username | string | - | string.max_len: 200
|
| request_limit | uint64 | - | |
| action_mins_limit | uint64 | - | |
@@ -247,6 +234,7 @@ failed event. You can find out if it worked on the `failure_count`
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| id | string | - | |
+| details | zitadel.v1.ObjectDetails | - | |
diff --git a/internal/api/api.go b/internal/api/api.go
index 50070f87bd..f6ac0377c2 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -69,7 +69,9 @@ func (a *API) RegisterServer(ctx context.Context, grpcServer server.Server) erro
return err
}
a.RegisterHandler(prefix, handler)
- a.verifier.RegisterServer(grpcServer.AppName(), grpcServer.MethodPrefix(), grpcServer.AuthMethods())
+ if a.verifier != nil {
+ a.verifier.RegisterServer(grpcServer.AppName(), grpcServer.MethodPrefix(), grpcServer.AuthMethods())
+ }
return nil
}
diff --git a/internal/api/grpc/admin/failed_event_converter_test.go b/internal/api/grpc/admin/failed_event_converter_test.go
index f653f4bd14..2179bf2386 100644
--- a/internal/api/grpc/admin/failed_event_converter_test.go
+++ b/internal/api/grpc/admin/failed_event_converter_test.go
@@ -1,9 +1,8 @@
-package admin_test
+package admin
import (
"testing"
- admin_grpc "github.com/caos/zitadel/internal/api/grpc/admin"
"github.com/caos/zitadel/internal/test"
"github.com/caos/zitadel/internal/view/model"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
@@ -34,7 +33,7 @@ func TestFailedEventsToPbFields(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got := admin_grpc.FailedEventsViewToPb(tt.args.failedEvents)
+ got := FailedEventsViewToPb(tt.args.failedEvents)
for _, g := range got {
test.AssertFieldsMapped(t, g)
}
@@ -64,7 +63,7 @@ func TestFailedEventToPbFields(t *testing.T) {
},
}
for _, tt := range tests {
- converted := admin_grpc.FailedEventViewToPb(tt.args.failedEvent)
+ converted := FailedEventViewToPb(tt.args.failedEvent)
test.AssertFieldsMapped(t, converted)
}
}
@@ -89,7 +88,7 @@ func TestRemoveFailedEventRequestToModelFields(t *testing.T) {
},
}
for _, tt := range tests {
- converted := admin_grpc.RemoveFailedEventRequestToModel(tt.args.req)
+ converted := RemoveFailedEventRequestToModel(tt.args.req)
test.AssertFieldsMapped(t, converted, "FailureCount", "ErrMsg")
}
}
diff --git a/internal/api/grpc/admin/view.go b/internal/api/grpc/admin/view.go
index 6b99bc447a..dcaf31a851 100644
--- a/internal/api/grpc/admin/view.go
+++ b/internal/api/grpc/admin/view.go
@@ -22,16 +22,3 @@ func (s *Server) ListViews(ctx context.Context, _ *admin_pb.ListViewsRequest) (*
convertedCurrentSequences = append(convertedCurrentSequences, convertedViews...)
return &admin_pb.ListViewsResponse{Result: convertedCurrentSequences}, nil
}
-
-func (s *Server) ClearView(ctx context.Context, req *admin_pb.ClearViewRequest) (*admin_pb.ClearViewResponse, error) {
- var err error
- if req.Database != "zitadel" {
- err = s.administrator.ClearView(ctx, req.Database, req.ViewName)
- } else {
- err = s.query.ClearCurrentSequence(ctx, req.ViewName)
- }
- if err != nil {
- return nil, err
- }
- return &admin_pb.ClearViewResponse{}, nil
-}
diff --git a/internal/api/grpc/instance/converter.go b/internal/api/grpc/instance/converter.go
index f944a1f18e..c809706f21 100644
--- a/internal/api/grpc/instance/converter.go
+++ b/internal/api/grpc/instance/converter.go
@@ -7,6 +7,46 @@ import (
instance_pb "github.com/caos/zitadel/pkg/grpc/instance"
)
+func InstancesToPb(instances []*query.Instance) []*instance_pb.Instance {
+ list := make([]*instance_pb.Instance, len(instances))
+ for i, instance := range instances {
+ list[i] = InstanceToPb(instance)
+ }
+ return list
+}
+
+func InstanceToPb(instance *query.Instance) *instance_pb.Instance {
+ return &instance_pb.Instance{
+ Details: object.ToViewDetailsPb(
+ instance.Sequence,
+ instance.CreationDate,
+ instance.ChangeDate,
+ instance.InstanceID(),
+ ),
+ Id: instance.InstanceID(),
+ }
+}
+
+func InstanceQueriesToModel(queries []*instance_pb.Query) (_ []query.SearchQuery, err error) {
+ q := make([]query.SearchQuery, len(queries))
+ for i, query := range queries {
+ q[i], err = InstanceQueryToModel(query)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return q, nil
+}
+
+func InstanceQueryToModel(searchQuery *instance_pb.Query) (query.SearchQuery, error) {
+ switch q := searchQuery.Query.(type) {
+ case *instance_pb.Query_IdQuery:
+ return query.NewInstanceIDsListSearchQuery(q.IdQuery.Ids...)
+ default:
+ return nil, errors.ThrowInvalidArgument(nil, "INST-3m0se", "List.Query.Invalid")
+ }
+}
+
func DomainQueriesToModel(queries []*instance_pb.DomainSearchQuery) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
@@ -27,7 +67,7 @@ func DomainQueryToModel(searchQuery *instance_pb.DomainSearchQuery) (query.Searc
case *instance_pb.DomainSearchQuery_PrimaryQuery:
return query.NewInstanceDomainPrimarySearchQuery(q.PrimaryQuery.Primary)
default:
- return nil, errors.ThrowInvalidArgument(nil, "ORG-Ags42", "List.Query.Invalid")
+ return nil, errors.ThrowInvalidArgument(nil, "INST-Ags42", "List.Query.Invalid")
}
}
diff --git a/internal/api/grpc/server/middleware/auth_interceptor.go b/internal/api/grpc/server/middleware/auth_interceptor.go
index 165be2a6f9..828631b651 100644
--- a/internal/api/grpc/server/middleware/auth_interceptor.go
+++ b/internal/api/grpc/server/middleware/auth_interceptor.go
@@ -15,6 +15,10 @@ import (
func AuthorizationInterceptor(verifier *authz.TokenVerifier, authConfig authz.Config) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
+ //TODO: Change as soon as we know how to authenticate system api
+ if verifier == nil {
+ return handler(ctx, req)
+ }
return authorize(ctx, req, info, handler, verifier, authConfig)
}
}
diff --git a/internal/api/grpc/server/middleware/instance_interceptor.go b/internal/api/grpc/server/middleware/instance_interceptor.go
index 3d4fa4af18..c2dc958a0b 100644
--- a/internal/api/grpc/server/middleware/instance_interceptor.go
+++ b/internal/api/grpc/server/middleware/instance_interceptor.go
@@ -3,6 +3,7 @@ package middleware
import (
"context"
"fmt"
+ "strings"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
@@ -16,13 +17,19 @@ type InstanceVerifier interface {
GetInstance(ctx context.Context)
}
-func InstanceInterceptor(verifier authz.InstanceVerifier, headerName string) grpc.UnaryServerInterceptor {
+func InstanceInterceptor(verifier authz.InstanceVerifier, headerName string, ignoredServices ...string) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
- return setInstance(ctx, req, info, handler, verifier, headerName)
+ return setInstance(ctx, req, info, handler, verifier, headerName, ignoredServices...)
}
}
-func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, headerName string) (_ interface{}, err error) {
+func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, headerName string, ignoredServices ...string) (_ interface{}, err error) {
+ for _, service := range ignoredServices {
+ if strings.HasPrefix(info.FullMethod, service) {
+ return handler(ctx, req)
+ }
+ }
+
host, err := hostNameFromContext(ctx, headerName)
if err != nil {
return nil, status.Error(codes.PermissionDenied, err.Error())
diff --git a/internal/api/grpc/server/server.go b/internal/api/grpc/server/server.go
index 2be5603e83..c0b7ec78c2 100644
--- a/internal/api/grpc/server/server.go
+++ b/internal/api/grpc/server/server.go
@@ -30,7 +30,8 @@ func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, querie
middleware.SentryHandler(),
middleware.NoCacheInterceptor(),
middleware.ErrorHandler(),
- middleware.InstanceInterceptor(queries, hostHeaderName),
+ //TODO: Handle Ignored Services
+ middleware.InstanceInterceptor(queries, hostHeaderName, "/zitadel.system.v1.SystemService"),
middleware.AuthorizationInterceptor(verifier, authConfig),
middleware.TranslationHandler(queries),
middleware.ValidationHandler(),
diff --git a/internal/api/grpc/system/failed_event.go b/internal/api/grpc/system/failed_event.go
new file mode 100644
index 0000000000..6e3ae82a8f
--- /dev/null
+++ b/internal/api/grpc/system/failed_event.go
@@ -0,0 +1,37 @@
+package system
+
+import (
+ "context"
+
+ "github.com/caos/zitadel/internal/query"
+ system_pb "github.com/caos/zitadel/pkg/grpc/system"
+)
+
+func (s *Server) ListFailedEvents(ctx context.Context, req *system_pb.ListFailedEventsRequest) (*system_pb.ListFailedEventsResponse, error) {
+ failedEventsOld, err := s.administrator.GetFailedEvents(ctx)
+ if err != nil {
+ return nil, err
+ }
+ convertedOld := FailedEventsViewToPb(failedEventsOld)
+
+ failedEvents, err := s.query.SearchFailedEvents(ctx, new(query.FailedEventSearchQueries))
+ if err != nil {
+ return nil, err
+ }
+ convertedNew := FailedEventsToPb(failedEvents)
+ convertedOld = append(convertedOld, convertedNew...)
+ return &system_pb.ListFailedEventsResponse{Result: convertedOld}, nil
+}
+
+func (s *Server) RemoveFailedEvent(ctx context.Context, req *system_pb.RemoveFailedEventRequest) (*system_pb.RemoveFailedEventResponse, error) {
+ var err error
+ if req.Database != "zitadel" {
+ err = s.administrator.RemoveFailedEvent(ctx, RemoveFailedEventRequestToModel(req))
+ } else {
+ err = s.query.RemoveFailedEvent(ctx, req.ViewName, req.FailedSequence)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return &system_pb.RemoveFailedEventResponse{}, nil
+}
diff --git a/internal/api/grpc/system/failed_event_converter.go b/internal/api/grpc/system/failed_event_converter.go
new file mode 100644
index 0000000000..2b0a87af76
--- /dev/null
+++ b/internal/api/grpc/system/failed_event_converter.go
@@ -0,0 +1,51 @@
+package system
+
+import (
+ "github.com/caos/zitadel/internal/query"
+ "github.com/caos/zitadel/internal/view/model"
+ system_pb "github.com/caos/zitadel/pkg/grpc/system"
+)
+
+func FailedEventsViewToPb(failedEvents []*model.FailedEvent) []*system_pb.FailedEvent {
+ events := make([]*system_pb.FailedEvent, len(failedEvents))
+ for i, failedEvent := range failedEvents {
+ events[i] = FailedEventViewToPb(failedEvent)
+ }
+ return events
+}
+
+func FailedEventViewToPb(failedEvent *model.FailedEvent) *system_pb.FailedEvent {
+ return &system_pb.FailedEvent{
+ Database: failedEvent.Database,
+ ViewName: failedEvent.ViewName,
+ FailedSequence: failedEvent.FailedSequence,
+ FailureCount: failedEvent.FailureCount,
+ ErrorMessage: failedEvent.ErrMsg,
+ }
+}
+
+func FailedEventsToPb(failedEvents *query.FailedEvents) []*system_pb.FailedEvent {
+ events := make([]*system_pb.FailedEvent, len(failedEvents.FailedEvents))
+ for i, failedEvent := range failedEvents.FailedEvents {
+ events[i] = FailedEventToPb(failedEvent)
+ }
+ return events
+}
+
+func FailedEventToPb(failedEvent *query.FailedEvent) *system_pb.FailedEvent {
+ return &system_pb.FailedEvent{
+ Database: "zitadel",
+ ViewName: failedEvent.ProjectionName,
+ FailedSequence: failedEvent.FailedSequence,
+ FailureCount: failedEvent.FailureCount,
+ ErrorMessage: failedEvent.Error,
+ }
+}
+
+func RemoveFailedEventRequestToModel(req *system_pb.RemoveFailedEventRequest) *model.FailedEvent {
+ return &model.FailedEvent{
+ Database: req.Database,
+ ViewName: req.ViewName,
+ FailedSequence: req.FailedSequence,
+ }
+}
diff --git a/internal/api/grpc/system/failed_event_converter_test.go b/internal/api/grpc/system/failed_event_converter_test.go
new file mode 100644
index 0000000000..7dc3897522
--- /dev/null
+++ b/internal/api/grpc/system/failed_event_converter_test.go
@@ -0,0 +1,95 @@
+package system_test
+
+import (
+ "testing"
+
+ system_grpc "github.com/caos/zitadel/internal/api/grpc/system"
+ "github.com/caos/zitadel/internal/test"
+ "github.com/caos/zitadel/internal/view/model"
+ system_pb "github.com/caos/zitadel/pkg/grpc/system"
+)
+
+func TestFailedEventsToPbFields(t *testing.T) {
+ type args struct {
+ failedEvents []*model.FailedEvent
+ }
+ tests := []struct {
+ name string
+ args args
+ }{
+ {
+ name: "all fields",
+ args: args{
+ failedEvents: []*model.FailedEvent{
+ {
+ Database: "admin",
+ ViewName: "users",
+ FailedSequence: 456,
+ FailureCount: 5,
+ ErrMsg: "some error",
+ },
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := system_grpc.FailedEventsViewToPb(tt.args.failedEvents)
+ for _, g := range got {
+ test.AssertFieldsMapped(t, g)
+ }
+ })
+ }
+}
+
+func TestFailedEventToPbFields(t *testing.T) {
+ type args struct {
+ failedEvent *model.FailedEvent
+ }
+ tests := []struct {
+ name string
+ args args
+ }{
+ {
+ "all fields",
+ args{
+ failedEvent: &model.FailedEvent{
+ Database: "admin",
+ ViewName: "users",
+ FailedSequence: 456,
+ FailureCount: 5,
+ ErrMsg: "some error",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ converted := system_grpc.FailedEventViewToPb(tt.args.failedEvent)
+ test.AssertFieldsMapped(t, converted)
+ }
+}
+
+func TestRemoveFailedEventRequestToModelFields(t *testing.T) {
+ type args struct {
+ req *system_pb.RemoveFailedEventRequest
+ }
+ tests := []struct {
+ name string
+ args args
+ }{
+ {
+ "all fields",
+ args{
+ req: &system_pb.RemoveFailedEventRequest{
+ Database: "admin",
+ ViewName: "users",
+ FailedSequence: 456,
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ converted := system_grpc.RemoveFailedEventRequestToModel(tt.args.req)
+ test.AssertFieldsMapped(t, converted, "FailureCount", "ErrMsg")
+ }
+}
diff --git a/internal/api/grpc/system/instance.go b/internal/api/grpc/system/instance.go
new file mode 100644
index 0000000000..053c25563f
--- /dev/null
+++ b/internal/api/grpc/system/instance.go
@@ -0,0 +1,127 @@
+package system
+
+import (
+ "context"
+
+ "github.com/caos/zitadel/internal/api/authz"
+ instance_grpc "github.com/caos/zitadel/internal/api/grpc/instance"
+ "github.com/caos/zitadel/internal/api/grpc/object"
+ object_pb "github.com/caos/zitadel/pkg/grpc/object"
+ system_pb "github.com/caos/zitadel/pkg/grpc/system"
+)
+
+func (s *Server) ListInstances(ctx context.Context, req *system_pb.ListInstancesRequest) (*system_pb.ListInstancesResponse, error) {
+ queries, err := ListInstancesRequestToModel(req)
+ if err != nil {
+ return nil, err
+ }
+
+ result, err := s.query.SearchInstances(ctx, queries)
+ if err != nil {
+ return nil, err
+ }
+ return &system_pb.ListInstancesResponse{
+ Result: instance_grpc.InstancesToPb(result.Instances),
+ Details: &object_pb.ListDetails{
+ TotalResult: result.Count,
+ },
+ }, nil
+}
+
+func (s *Server) GetInstance(ctx context.Context, req *system_pb.GetInstanceRequest) (*system_pb.GetInstanceResponse, error) {
+ ctx = authz.WithInstanceID(ctx, req.Id)
+ instance, err := s.query.Instance(ctx)
+ if err != nil {
+ return nil, err
+ }
+ return &system_pb.GetInstanceResponse{
+ Instance: instance_grpc.InstanceToPb(instance),
+ }, nil
+}
+
+func (s *Server) AddInstance(ctx context.Context, req *system_pb.AddInstanceRequest) (*system_pb.AddInstanceResponse, error) {
+ id, details, err := s.command.SetUpInstance(ctx, AddInstancePbToSetupInstance(req, s.DefaultInstance), s.ExternalSecure, s.BaseURL)
+ if err != nil {
+ return nil, err
+ }
+ return &system_pb.AddInstanceResponse{
+ Id: id,
+ Details: object.AddToDetailsPb(
+ details.Sequence,
+ details.EventDate,
+ details.ResourceOwner,
+ ),
+ }, nil
+ return nil, nil
+}
+
+func (s *Server) ListDomains(ctx context.Context, req *system_pb.ListDomainsRequest) (*system_pb.ListDomainsResponse, error) {
+ ctx = authz.WithInstanceID(ctx, req.Id)
+ queries, err := ListInstanceDomainsRequestToModel(req)
+ if err != nil {
+ return nil, err
+ }
+
+ domains, err := s.query.SearchInstanceDomains(ctx, queries)
+ if err != nil {
+ return nil, err
+ }
+ return &system_pb.ListDomainsResponse{
+ Result: instance_grpc.DomainsToPb(domains.Domains),
+ Details: object.ToListDetails(
+ domains.Count,
+ domains.Sequence,
+ domains.Timestamp,
+ ),
+ }, nil
+}
+
+func (s *Server) AddDomain(ctx context.Context, req *system_pb.AddDomainRequest) (*system_pb.AddDomainResponse, error) {
+ ctx = authz.WithInstanceID(ctx, req.Id)
+ instance, err := s.query.Instance(ctx)
+ if err != nil {
+ return nil, err
+ }
+ ctx = authz.WithInstance(ctx, instance)
+ details, err := s.command.AddInstanceDomain(ctx, req.Domain)
+ if err != nil {
+ return nil, err
+ }
+ return &system_pb.AddDomainResponse{
+ Details: object.AddToDetailsPb(
+ details.Sequence,
+ details.EventDate,
+ details.ResourceOwner,
+ ),
+ }, nil
+}
+
+func (s *Server) RemoveDomain(ctx context.Context, req *system_pb.RemoveDomainRequest) (*system_pb.RemoveDomainResponse, error) {
+ ctx = authz.WithInstanceID(ctx, req.Id)
+ details, err := s.command.RemoveInstanceDomain(ctx, req.Domain)
+ if err != nil {
+ return nil, err
+ }
+ return &system_pb.RemoveDomainResponse{
+ Details: object.ChangeToDetailsPb(
+ details.Sequence,
+ details.EventDate,
+ details.ResourceOwner,
+ ),
+ }, nil
+}
+
+func (s *Server) SetPrimaryDomain(ctx context.Context, req *system_pb.SetPrimaryDomainRequest) (*system_pb.SetPrimaryDomainResponse, error) {
+ ctx = authz.WithInstanceID(ctx, req.Id)
+ details, err := s.command.SetPrimaryInstanceDomain(ctx, req.Domain)
+ if err != nil {
+ return nil, err
+ }
+ return &system_pb.SetPrimaryDomainResponse{
+ Details: object.ChangeToDetailsPb(
+ details.Sequence,
+ details.EventDate,
+ details.ResourceOwner,
+ ),
+ }, nil
+}
diff --git a/internal/api/grpc/system/instance_converter.go b/internal/api/grpc/system/instance_converter.go
new file mode 100644
index 0000000000..958b4001ca
--- /dev/null
+++ b/internal/api/grpc/system/instance_converter.go
@@ -0,0 +1,97 @@
+package system
+
+import (
+ instance_grpc "github.com/caos/zitadel/internal/api/grpc/instance"
+ "github.com/caos/zitadel/internal/api/grpc/object"
+ "github.com/caos/zitadel/internal/command"
+ "github.com/caos/zitadel/internal/query"
+ instance_pb "github.com/caos/zitadel/pkg/grpc/instance"
+ system_pb "github.com/caos/zitadel/pkg/grpc/system"
+)
+
+func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInstance command.InstanceSetup) *command.InstanceSetup {
+ if req.InstanceName != "" {
+ defaultInstance.InstanceName = req.InstanceName
+ defaultInstance.Org.Name = req.InstanceName
+ }
+ if req.CustomDomain != "" {
+ defaultInstance.CustomDomain = req.CustomDomain
+ }
+ if req.FirstOrgName != "" {
+ defaultInstance.Org.Name = req.FirstOrgName
+ }
+ if req.OwnerEmail != "" {
+ defaultInstance.Org.Human.Email.Address = req.OwnerEmail
+ }
+ if req.OwnerUsername != "" {
+ defaultInstance.Org.Human.Username = req.OwnerUsername
+ }
+ if req.OwnerFirstName != "" {
+ defaultInstance.Org.Human.FirstName = req.OwnerFirstName
+ }
+ if req.OwnerLastName != "" {
+ defaultInstance.Org.Human.LastName = req.OwnerLastName
+ }
+ return &defaultInstance
+}
+func ListInstancesRequestToModel(req *system_pb.ListInstancesRequest) (*query.InstanceSearchQueries, error) {
+ offset, limit, asc := object.ListQueryToModel(req.Query)
+ queries, err := instance_grpc.InstanceQueriesToModel(req.Queries)
+ if err != nil {
+ return nil, err
+ }
+ return &query.InstanceSearchQueries{
+ SearchRequest: query.SearchRequest{
+ Offset: offset,
+ Limit: limit,
+ Asc: asc,
+ SortingColumn: fieldNameToInstanceColumn(req.SortingColumn),
+ },
+ Queries: queries,
+ }, nil
+}
+
+func fieldNameToInstanceColumn(fieldName instance_pb.FieldName) query.Column {
+ switch fieldName {
+ case instance_pb.FieldName_FIELD_NAME_ID:
+ return query.InstanceColumnID
+ case instance_pb.FieldName_FIELD_NAME_NAME:
+ return query.InstanceColumnName
+ case instance_pb.FieldName_FIELD_NAME_CREATION_DATE:
+ return query.InstanceColumnCreationDate
+ default:
+ return query.Column{}
+ }
+}
+
+func ListInstanceDomainsRequestToModel(req *system_pb.ListDomainsRequest) (*query.InstanceDomainSearchQueries, error) {
+ offset, limit, asc := object.ListQueryToModel(req.Query)
+ queries, err := instance_grpc.DomainQueriesToModel(req.Queries)
+ if err != nil {
+ return nil, err
+ }
+ return &query.InstanceDomainSearchQueries{
+ SearchRequest: query.SearchRequest{
+ Offset: offset,
+ Limit: limit,
+ Asc: asc,
+ SortingColumn: fieldNameToInstanceDomainColumn(req.SortingColumn),
+ },
+ Queries: queries,
+ }, nil
+}
+
+func fieldNameToInstanceDomainColumn(fieldName instance_pb.DomainFieldName) query.Column {
+ switch fieldName {
+ case instance_pb.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN:
+ return query.InstanceDomainDomainCol
+ case instance_pb.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED:
+ return query.InstanceDomainIsGeneratedCol
+ case instance_pb.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY:
+ return query.InstanceDomainIsPrimaryCol
+ case instance_pb.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE:
+ return query.InstanceDomainCreationDateCol
+ default:
+ return query.Column{}
+ }
+}
diff --git a/internal/api/grpc/system/server.go b/internal/api/grpc/system/server.go
new file mode 100644
index 0000000000..007dd0548b
--- /dev/null
+++ b/internal/api/grpc/system/server.go
@@ -0,0 +1,75 @@
+package system
+
+import (
+ "github.com/caos/zitadel/internal/admin/repository"
+ http_util "github.com/caos/zitadel/internal/api/http"
+ "google.golang.org/grpc"
+
+ "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
+ "github.com/caos/zitadel/internal/api/authz"
+ "github.com/caos/zitadel/internal/api/grpc/server"
+ "github.com/caos/zitadel/internal/command"
+ "github.com/caos/zitadel/internal/query"
+ "github.com/caos/zitadel/pkg/grpc/system"
+)
+
+const (
+ systemAPI = "System-API"
+)
+
+var _ system.SystemServiceServer = (*Server)(nil)
+
+type Server struct {
+ system.UnimplementedSystemServiceServer
+ command *command.Commands
+ query *query.Queries
+ administrator repository.AdministratorRepository
+ DefaultInstance command.InstanceSetup
+ ExternalSecure bool
+ BaseURL string
+}
+
+type Config struct {
+ Repository eventsourcing.Config
+}
+
+func CreateServer(command *command.Commands,
+ query *query.Queries,
+ repo repository.Repository,
+ defaultInstance command.InstanceSetup,
+ externalPort uint16,
+ externalDomain string,
+ externalSecure bool) *Server {
+ return &Server{
+ command: command,
+ query: query,
+ administrator: repo,
+ DefaultInstance: defaultInstance,
+ ExternalSecure: externalSecure,
+ BaseURL: http_util.BuildHTTP(externalDomain, externalPort, externalSecure),
+ }
+}
+
+func (s *Server) RegisterServer(grpcServer *grpc.Server) {
+ system.RegisterSystemServiceServer(grpcServer, s)
+}
+
+func (s *Server) AppName() string {
+ return systemAPI
+}
+
+func (s *Server) MethodPrefix() string {
+ return system.SystemService_MethodPrefix
+}
+
+func (s *Server) AuthMethods() authz.MethodMapping {
+ return system.SystemService_AuthMethods
+}
+
+func (s *Server) RegisterGateway() server.GatewayFunc {
+ return system.RegisterSystemServiceHandlerFromEndpoint
+}
+
+func (s *Server) GatewayPathPrefix() string {
+ return "/system/v1"
+}
diff --git a/internal/api/grpc/system/view.go b/internal/api/grpc/system/view.go
new file mode 100644
index 0000000000..2ae458651e
--- /dev/null
+++ b/internal/api/grpc/system/view.go
@@ -0,0 +1,37 @@
+package system
+
+import (
+ "context"
+
+ "github.com/caos/zitadel/internal/query"
+ system_pb "github.com/caos/zitadel/pkg/grpc/system"
+)
+
+func (s *Server) ListViews(ctx context.Context, _ *system_pb.ListViewsRequest) (*system_pb.ListViewsResponse, error) {
+ currentSequences, err := s.query.SearchCurrentSequences(ctx, new(query.CurrentSequencesSearchQueries))
+ if err != nil {
+ return nil, err
+ }
+ convertedCurrentSequences := CurrentSequencesToPb(currentSequences)
+ views, err := s.administrator.GetViews()
+ if err != nil {
+ return nil, err
+ }
+ convertedViews := ViewsToPb(views)
+
+ convertedCurrentSequences = append(convertedCurrentSequences, convertedViews...)
+ return &system_pb.ListViewsResponse{Result: convertedCurrentSequences}, nil
+}
+
+func (s *Server) ClearView(ctx context.Context, req *system_pb.ClearViewRequest) (*system_pb.ClearViewResponse, error) {
+ var err error
+ if req.Database != "zitadel" {
+ err = s.administrator.ClearView(ctx, req.Database, req.ViewName)
+ } else {
+ err = s.query.ClearCurrentSequence(ctx, req.ViewName)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return &system_pb.ClearViewResponse{}, nil
+}
diff --git a/internal/api/grpc/system/view_converter.go b/internal/api/grpc/system/view_converter.go
new file mode 100644
index 0000000000..5d2273aaf9
--- /dev/null
+++ b/internal/api/grpc/system/view_converter.go
@@ -0,0 +1,43 @@
+package system
+
+import (
+ "github.com/caos/zitadel/internal/query"
+ "github.com/caos/zitadel/internal/view/model"
+ system_pb "github.com/caos/zitadel/pkg/grpc/system"
+ "google.golang.org/protobuf/types/known/timestamppb"
+)
+
+func ViewsToPb(views []*model.View) []*system_pb.View {
+ v := make([]*system_pb.View, len(views))
+ for i, view := range views {
+ v[i] = ViewToPb(view)
+ }
+ return v
+}
+
+func ViewToPb(view *model.View) *system_pb.View {
+ return &system_pb.View{
+ Database: view.Database,
+ ViewName: view.ViewName,
+ LastSuccessfulSpoolerRun: timestamppb.New(view.LastSuccessfulSpoolerRun),
+ ProcessedSequence: view.CurrentSequence,
+ EventTimestamp: timestamppb.New(view.EventTimestamp),
+ }
+}
+
+func CurrentSequencesToPb(currentSequences *query.CurrentSequences) []*system_pb.View {
+ v := make([]*system_pb.View, len(currentSequences.CurrentSequences))
+ for i, currentSequence := range currentSequences.CurrentSequences {
+ v[i] = CurrentSequenceToPb(currentSequence)
+ }
+ return v
+}
+
+func CurrentSequenceToPb(currentSequence *query.CurrentSequence) *system_pb.View {
+ return &system_pb.View{
+ Database: "zitadel",
+ ViewName: currentSequence.ProjectionName,
+ ProcessedSequence: currentSequence.CurrentSequence,
+ EventTimestamp: timestamppb.New(currentSequence.Timestamp),
+ }
+}
diff --git a/internal/command/instance.go b/internal/command/instance.go
index d26aa57e9d..3037e1dc65 100644
--- a/internal/command/instance.go
+++ b/internal/command/instance.go
@@ -28,10 +28,22 @@ const (
consolePostLogoutPath = console.HandlerPrefix + "/signedout"
)
+type AddInstance struct {
+ InstanceName string
+ CustomDomain string
+ FirstOrgName string
+ OwnerEmail string
+ OwnerUsername string
+ OwnerFirstName string
+ OwnerLastName string
+}
+
type InstanceSetup struct {
- Org OrgSetup
- Zitadel ZitadelConfig
- Features struct {
+ zitadel ZitadelConfig
+ InstanceName string
+ CustomDomain string
+ Org OrgSetup
+ Features struct {
TierName string
TierDescription string
Retention time.Duration
@@ -120,9 +132,6 @@ type InstanceSetup struct {
}
type ZitadelConfig struct {
- IsDevMode bool
- BaseURL string
-
projectID string
mgmtAppID string
adminAppID string
@@ -131,41 +140,41 @@ type ZitadelConfig struct {
}
func (s *InstanceSetup) generateIDs() (err error) {
- s.Zitadel.projectID, err = id.SonyFlakeGenerator.Next()
+ s.zitadel.projectID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
- s.Zitadel.mgmtAppID, err = id.SonyFlakeGenerator.Next()
+ s.zitadel.mgmtAppID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
- s.Zitadel.adminAppID, err = id.SonyFlakeGenerator.Next()
+ s.zitadel.adminAppID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
- s.Zitadel.authAppID, err = id.SonyFlakeGenerator.Next()
+ s.zitadel.authAppID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
- s.Zitadel.consoleAppID, err = id.SonyFlakeGenerator.Next()
+ s.zitadel.consoleAppID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
return nil
}
-func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*domain.ObjectDetails, error) {
+func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup, externalSecure bool, baseURL string) (string, *domain.ObjectDetails, error) {
instanceID, err := id.SonyFlakeGenerator.Next()
if err != nil {
- return nil, err
+ return "", nil, err
}
if err = c.eventstore.NewInstance(ctx, instanceID); err != nil {
- return nil, err
+ return "", nil, err
}
ctx = authz.SetCtxData(authz.WithInstanceID(ctx, instanceID), authz.CtxData{OrgID: instanceID, ResourceOwner: instanceID})
@@ -174,16 +183,16 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
orgID, err := id.SonyFlakeGenerator.Next()
if err != nil {
- return nil, err
+ return "", nil, err
}
userID, err := id.SonyFlakeGenerator.Next()
if err != nil {
- return nil, err
+ return "", nil, err
}
if err = setup.generateIDs(); err != nil {
- return nil, err
+ return "", nil, err
}
setup.Org.Human.PasswordChangeRequired = true
@@ -191,9 +200,11 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
instanceAgg := instance.NewAggregate(instanceID)
orgAgg := org.NewAggregate(orgID)
userAgg := user.NewAggregate(userID, orgID)
- projectAgg := project.NewAggregate(setup.Zitadel.projectID, orgID)
+ projectAgg := project.NewAggregate(setup.zitadel.projectID, orgID)
validations := []preparation.Validation{
+ addInstance(instanceAgg, setup.InstanceName),
+ c.addGeneratedInstanceDomain(instanceAgg, setup.InstanceName),
SetDefaultFeatures(
instanceAgg,
setup.Features.TierName,
@@ -289,20 +300,24 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
validations = append(validations, SetInstanceCustomTexts(instanceAgg, msg))
}
+ if setup.CustomDomain != "" {
+ validations = append(validations, c.addInstanceDomain(instanceAgg, setup.CustomDomain, false))
+ }
+
console := &addOIDCApp{
AddApp: AddApp{
Aggregate: *projectAgg,
- ID: setup.Zitadel.consoleAppID,
+ ID: setup.zitadel.consoleAppID,
Name: consoleAppName,
},
Version: domain.OIDCVersionV1,
- RedirectUris: []string{setup.Zitadel.BaseURL + consoleRedirectPath},
+ RedirectUris: []string{baseURL + consoleRedirectPath},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeUserAgent,
AuthMethodType: domain.OIDCAuthMethodTypeNone,
- PostLogoutRedirectUris: []string{setup.Zitadel.BaseURL + consolePostLogoutPath},
- DevMode: setup.Zitadel.IsDevMode,
+ PostLogoutRedirectUris: []string{baseURL + consolePostLogoutPath},
+ DevMode: !externalSecure,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: false,
IDTokenRoleAssertion: false,
@@ -323,7 +338,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
&addAPIApp{
AddApp: AddApp{
Aggregate: *projectAgg,
- ID: setup.Zitadel.mgmtAppID,
+ ID: setup.zitadel.mgmtAppID,
Name: mgmtAppName,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
@@ -335,7 +350,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
&addAPIApp{
AddApp: AddApp{
Aggregate: *projectAgg,
- ID: setup.Zitadel.adminAppID,
+ ID: setup.zitadel.adminAppID,
Name: adminAppName,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
@@ -347,7 +362,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
&addAPIApp{
AddApp: AddApp{
Aggregate: *projectAgg,
- ID: setup.Zitadel.authAppID,
+ ID: setup.zitadel.authAppID,
Name: authAppName,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
@@ -356,25 +371,35 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*do
),
AddOIDCAppCommand(console, nil),
- SetIAMConsoleID(instanceAgg, &console.ClientID, &setup.Zitadel.consoleAppID),
+ SetIAMConsoleID(instanceAgg, &console.ClientID, &setup.zitadel.consoleAppID),
)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validations...)
if err != nil {
- return nil, err
+ return "", nil, err
}
events, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
- return nil, err
+ return "", nil, err
}
- return &domain.ObjectDetails{
+ return instanceID, &domain.ObjectDetails{
Sequence: events[len(events)-1].Sequence(),
EventDate: events[len(events)-1].CreationDate(),
ResourceOwner: orgID,
}, nil
}
+func addInstance(a *instance.Aggregate, instanceName string) preparation.Validation {
+ return func() (preparation.CreateCommands, error) {
+ return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
+ return []eventstore.Command{
+ instance.NewInstanceAddedEvent(ctx, &a.Aggregate, instanceName),
+ }, nil
+ }, nil
+ }
+}
+
//SetIAMProject defines the command to set the id of the IAM project onto the instance
func SetIAMProject(a *instance.Aggregate, projectID string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
diff --git a/internal/command/instance_domain.go b/internal/command/instance_domain.go
index 757df7abb7..686a0b2527 100644
--- a/internal/command/instance_domain.go
+++ b/internal/command/instance_domain.go
@@ -67,6 +67,11 @@ func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain stri
}, nil
}
+func (c *Commands) addGeneratedInstanceDomain(a *instance.Aggregate, instanceName string) preparation.Validation {
+ domain := domain.NewGeneratedInstanceDomain(instanceName, c.iamDomain)
+ return c.addInstanceDomain(a, domain, true)
+}
+
func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain string, generated bool) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
@@ -80,28 +85,32 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
if domainWriteModel.State == domain.InstanceDomainStateActive {
return nil, errors.ThrowAlreadyExists(nil, "INST-i2nl", "Errors.Instance.Domain.AlreadyExists")
}
+ events := []eventstore.Command{
+ instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated),
+ }
appWriteModel, err := c.getOIDCAppWriteModel(ctx, authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID(), "")
if err != nil {
return nil, err
}
- redirectUrls := append(appWriteModel.RedirectUris, instanceDomain+consoleRedirectPath)
- logoutUrls := append(appWriteModel.PostLogoutRedirectUris, instanceDomain+consolePostLogoutPath)
- consoleChangeEvent, err := project.NewOIDCConfigChangedEvent(
- ctx,
- ProjectAggregateFromWriteModel(&appWriteModel.WriteModel),
- appWriteModel.AppID,
- []project.OIDCConfigChanges{
- project.ChangeRedirectURIs(redirectUrls),
- project.ChangePostLogoutRedirectURIs(logoutUrls),
- },
- )
- if err != nil {
- return nil, err
+ if appWriteModel.State.Exists() {
+ redirectUrls := append(appWriteModel.RedirectUris, instanceDomain+consoleRedirectPath)
+ logoutUrls := append(appWriteModel.PostLogoutRedirectUris, instanceDomain+consolePostLogoutPath)
+ consoleChangeEvent, err := project.NewOIDCConfigChangedEvent(
+ ctx,
+ ProjectAggregateFromWriteModel(&appWriteModel.WriteModel),
+ appWriteModel.AppID,
+ []project.OIDCConfigChanges{
+ project.ChangeRedirectURIs(redirectUrls),
+ project.ChangePostLogoutRedirectURIs(logoutUrls),
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+ events = append(events, consoleChangeEvent)
}
- return []eventstore.Command{
- instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated),
- consoleChangeEvent,
- }, nil
+
+ return events, nil
}, nil
}
}
diff --git a/internal/command/project_application_oidc.go b/internal/command/project_application_oidc.go
index 6d987fb6c3..eada5bfcef 100644
--- a/internal/command/project_application_oidc.go
+++ b/internal/command/project_application_oidc.go
@@ -291,7 +291,7 @@ func (c *Commands) VerifyOIDCClientSecret(ctx context.Context, projectID, appID,
return err
}
if !app.State.Exists() {
- return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.NoExisting")
+ return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.NotExisting")
}
if !app.IsOIDC() {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-BHgn2", "Errors.Project.App.IsNotOIDC")
diff --git a/internal/domain/instance_domain.go b/internal/domain/instance_domain.go
index be1284ed6a..14ecbce5f0 100644
--- a/internal/domain/instance_domain.go
+++ b/internal/domain/instance_domain.go
@@ -23,5 +23,7 @@ func (f InstanceDomainState) Exists() bool {
}
func NewGeneratedInstanceDomain(instanceName, iamDomain string) string {
+ //TODO: Add random number/string to be unique
+ instanceName = strings.TrimSpace(instanceName)
return strings.ToLower(strings.ReplaceAll(instanceName, " ", "-") + "." + iamDomain)
}
diff --git a/internal/migration/migration.go b/internal/migration/migration.go
index 2aca242be5..93d7db1d37 100644
--- a/internal/migration/migration.go
+++ b/internal/migration/migration.go
@@ -34,8 +34,12 @@ func Migrate(ctx context.Context, es *eventstore.Eventstore, migration Migration
err = migration.Execute(ctx)
logging.OnError(err).Error("migration failed")
- _, err = es.Push(ctx, setupDoneCmd(migration, err))
- return err
+ _, pushErr := es.Push(ctx, setupDoneCmd(migration, err))
+ logging.OnError(pushErr).Error("migration failed")
+ if err != nil {
+ return err
+ }
+ return pushErr
}
func shouldExec(ctx context.Context, es *eventstore.Eventstore, migration Migration) (should bool, err error) {
diff --git a/internal/query/instance.go b/internal/query/instance.go
index c0356c2170..8e256a9814 100644
--- a/internal/query/instance.go
+++ b/internal/query/instance.go
@@ -23,6 +23,14 @@ var (
name: projection.InstanceColumnID,
table: instanceTable,
}
+ InstanceColumnName = Column{
+ name: projection.InstanceColumnName,
+ table: instanceTable,
+ }
+ InstanceColumnCreationDate = Column{
+ name: projection.InstanceColumnCreationDate,
+ table: instanceTable,
+ }
InstanceColumnChangeDate = Column{
name: projection.InstanceColumnChangeDate,
table: instanceTable,
@@ -62,9 +70,10 @@ var (
)
type Instance struct {
- ID string
- ChangeDate time.Time
- Sequence uint64
+ ID string
+ ChangeDate time.Time
+ CreationDate time.Time
+ Sequence uint64
GlobalOrgID string
IAMProjectID string
@@ -76,6 +85,11 @@ type Instance struct {
Host string
}
+type Instances struct {
+ SearchResponse
+ Instances []*Instance
+}
+
func (i *Instance) InstanceID() string {
return i.ID
}
@@ -101,6 +115,14 @@ type InstanceSearchQueries struct {
Queries []SearchQuery
}
+func NewInstanceIDsListSearchQuery(ids ...string) (SearchQuery, error) {
+ list := make([]interface{}, len(ids))
+ for i, value := range ids {
+ list[i] = value
+ }
+ return NewListQuery(InstanceColumnID, list, ListIn)
+}
+
func (q *InstanceSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
query = q.SearchRequest.toQuery(query)
for _, q := range q.Queries {
@@ -109,6 +131,24 @@ func (q *InstanceSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder
return query
}
+func (q *Queries) SearchInstances(ctx context.Context, queries *InstanceSearchQueries) (instances *Instances, err error) {
+ query, scan := prepareInstancesQuery()
+ stmt, args, err := queries.toQuery(query).ToSql()
+ if err != nil {
+ return nil, errors.ThrowInvalidArgument(err, "QUERY-M9fow", "Errors.Query.SQLStatement")
+ }
+
+ rows, err := q.client.QueryContext(ctx, stmt, args...)
+ if err != nil {
+ return nil, errors.ThrowInternal(err, "QUERY-3j98f", "Errors.Internal")
+ }
+ instances, err = scan(rows)
+ if err != nil {
+ return nil, err
+ }
+ return instances, err
+}
+
func (q *Queries) Instance(ctx context.Context) (*Instance, error) {
stmt, scan := prepareInstanceQuery(authz.GetInstance(ctx).RequestedDomain())
query, args, err := stmt.Where(sq.Eq{
@@ -146,6 +186,7 @@ func (q *Queries) GetDefaultLanguage(ctx context.Context) language.Tag {
func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
return sq.Select(
InstanceColumnID.identifier(),
+ InstanceColumnCreationDate.identifier(),
InstanceColumnChangeDate.identifier(),
InstanceColumnSequence.identifier(),
InstanceColumnGlobalOrgID.identifier(),
@@ -162,6 +203,7 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
lang := ""
err := row.Scan(
&instance.ID,
+ &instance.CreationDate,
&instance.ChangeDate,
&instance.Sequence,
&instance.GlobalOrgID,
@@ -182,3 +224,58 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
return instance, nil
}
}
+
+func prepareInstancesQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instances, error)) {
+ return sq.Select(
+ InstanceColumnID.identifier(),
+ InstanceColumnCreationDate.identifier(),
+ InstanceColumnChangeDate.identifier(),
+ InstanceColumnSequence.identifier(),
+ InstanceColumnGlobalOrgID.identifier(),
+ InstanceColumnProjectID.identifier(),
+ InstanceColumnConsoleID.identifier(),
+ InstanceColumnConsoleAppID.identifier(),
+ InstanceColumnSetupStarted.identifier(),
+ InstanceColumnSetupDone.identifier(),
+ InstanceColumnDefaultLanguage.identifier(),
+ countColumn.identifier(),
+ ).From(instanceTable.identifier()).PlaceholderFormat(sq.Dollar),
+ func(rows *sql.Rows) (*Instances, error) {
+ instances := make([]*Instance, 0)
+ var count uint64
+ for rows.Next() {
+ instance := new(Instance)
+ lang := ""
+ //TODO: Get Host
+ err := rows.Scan(
+ &instance.ID,
+ &instance.CreationDate,
+ &instance.ChangeDate,
+ &instance.Sequence,
+ &instance.GlobalOrgID,
+ &instance.IAMProjectID,
+ &instance.ConsoleID,
+ &instance.ConsoleAppID,
+ &instance.SetupStarted,
+ &instance.SetupDone,
+ &lang,
+ &count,
+ )
+ if err != nil {
+ return nil, err
+ }
+ instances = append(instances, instance)
+ }
+
+ if err := rows.Close(); err != nil {
+ return nil, errors.ThrowInternal(err, "QUERY-8nlWW", "Errors.Query.CloseRows")
+ }
+
+ return &Instances{
+ Instances: instances,
+ SearchResponse: SearchResponse{
+ Count: count,
+ },
+ }, nil
+ }
+}
diff --git a/internal/query/instance_test.go b/internal/query/instance_test.go
index 90561d88cd..5380e7e05b 100644
--- a/internal/query/instance_test.go
+++ b/internal/query/instance_test.go
@@ -34,6 +34,7 @@ func Test_InstancePrepares(t *testing.T) {
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.instances.id,`+
+ ` projections.instances.creation_date,`+
` projections.instances.change_date,`+
` projections.instances.sequence,`+
` projections.instances.global_org_id,`+
@@ -64,6 +65,7 @@ func Test_InstancePrepares(t *testing.T) {
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.instances.id,`+
+ ` projections.instances.creation_date,`+
` projections.instances.change_date,`+
` projections.instances.sequence,`+
` projections.instances.global_org_id,`+
@@ -76,6 +78,7 @@ func Test_InstancePrepares(t *testing.T) {
` FROM projections.instances`),
[]string{
"id",
+ "creation_date",
"change_date",
"sequence",
"global_org_id",
@@ -89,6 +92,7 @@ func Test_InstancePrepares(t *testing.T) {
[]driver.Value{
"id",
testNow,
+ testNow,
uint64(20211108),
"global-org-id",
"project-id",
@@ -102,6 +106,7 @@ func Test_InstancePrepares(t *testing.T) {
},
object: &Instance{
ID: "id",
+ CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211108,
GlobalOrgID: "global-org-id",
@@ -121,6 +126,7 @@ func Test_InstancePrepares(t *testing.T) {
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.instances.id,`+
+ ` projections.instances.creation_date,`+
` projections.instances.change_date,`+
` projections.instances.sequence,`+
` projections.instances.global_org_id,`+
diff --git a/internal/query/projection/instance.go b/internal/query/projection/instance.go
index ea00f7ba1f..dfae71ab81 100644
--- a/internal/query/projection/instance.go
+++ b/internal/query/projection/instance.go
@@ -14,7 +14,9 @@ const (
InstanceProjectionTable = "projections.instances"
InstanceColumnID = "id"
+ InstanceColumnName = "name"
InstanceColumnChangeDate = "change_date"
+ InstanceColumnCreationDate = "creation_date"
InstanceColumnGlobalOrgID = "global_org_id"
InstanceColumnProjectID = "iam_project_id"
InstanceColumnConsoleID = "console_client_id"
@@ -36,10 +38,13 @@ func NewInstanceProjection(ctx context.Context, config crdb.StatementHandlerConf
config.InitCheck = crdb.NewTableCheck(
crdb.NewTable([]*crdb.Column{
crdb.NewColumn(InstanceColumnID, crdb.ColumnTypeText),
+ crdb.NewColumn(InstanceColumnName, crdb.ColumnTypeText, crdb.Default("")),
crdb.NewColumn(InstanceColumnChangeDate, crdb.ColumnTypeTimestamp),
+ crdb.NewColumn(InstanceColumnCreationDate, crdb.ColumnTypeTimestamp),
crdb.NewColumn(InstanceColumnGlobalOrgID, crdb.ColumnTypeText, crdb.Default("")),
crdb.NewColumn(InstanceColumnProjectID, crdb.ColumnTypeText, crdb.Default("")),
crdb.NewColumn(InstanceColumnConsoleID, crdb.ColumnTypeText, crdb.Default("")),
+ crdb.NewColumn(InstanceColumnConsoleAppID, crdb.ColumnTypeText, crdb.Default("")),
crdb.NewColumn(InstanceColumnSequence, crdb.ColumnTypeInt64),
crdb.NewColumn(InstanceColumnSetUpStarted, crdb.ColumnTypeInt64, crdb.Default(0)),
crdb.NewColumn(InstanceColumnSetUpDone, crdb.ColumnTypeInt64, crdb.Default(0)),
@@ -57,6 +62,10 @@ func (p *InstanceProjection) reducers() []handler.AggregateReducer {
{
Aggregate: instance.AggregateType,
EventRedusers: []handler.EventReducer{
+ {
+ Event: instance.InstanceAddedEventType,
+ Reduce: p.reduceInstanceAdded,
+ },
{
Event: instance.GlobalOrgSetEventType,
Reduce: p.reduceGlobalOrgSet,
@@ -86,19 +95,38 @@ func (p *InstanceProjection) reducers() []handler.AggregateReducer {
}
}
+func (p *InstanceProjection) reduceInstanceAdded(event eventstore.Event) (*handler.Statement, error) {
+ e, ok := event.(*instance.InstanceAddedEvent)
+ if !ok {
+ return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-29nlS", "reduce.wrong.event.type %s", instance.InstanceAddedEventType)
+ }
+ return crdb.NewCreateStatement(
+ e,
+ []handler.Column{
+ handler.NewCol(InstanceColumnID, e.Aggregate().InstanceID),
+ handler.NewCol(InstanceColumnCreationDate, e.CreationDate()),
+ handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
+ handler.NewCol(InstanceColumnSequence, e.Sequence()),
+ handler.NewCol(InstanceColumnName, e.Name),
+ },
+ ), nil
+}
+
func (p *InstanceProjection) reduceGlobalOrgSet(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.GlobalOrgSetEvent)
if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-2n9f2", "reduce.wrong.event.type %s", instance.GlobalOrgSetEventType)
}
- return crdb.NewUpsertStatement(
+ return crdb.NewUpdateStatement(
e,
[]handler.Column{
- handler.NewCol(InstanceColumnID, e.Aggregate().InstanceID),
handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
handler.NewCol(InstanceColumnSequence, e.Sequence()),
handler.NewCol(InstanceColumnGlobalOrgID, e.OrgID),
},
+ []handler.Condition{
+ handler.NewCond(InstanceColumnID, e.Aggregate().InstanceID),
+ },
), nil
}
@@ -107,14 +135,16 @@ func (p *InstanceProjection) reduceIAMProjectSet(event eventstore.Event) (*handl
if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-30o0e", "reduce.wrong.event.type %s", instance.ProjectSetEventType)
}
- return crdb.NewUpsertStatement(
+ return crdb.NewUpdateStatement(
e,
[]handler.Column{
- handler.NewCol(InstanceColumnID, e.Aggregate().InstanceID),
handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
handler.NewCol(InstanceColumnSequence, e.Sequence()),
handler.NewCol(InstanceColumnProjectID, e.ProjectID),
},
+ []handler.Condition{
+ handler.NewCond(InstanceColumnID, e.Aggregate().InstanceID),
+ },
), nil
}
@@ -123,15 +153,17 @@ func (p *InstanceProjection) reduceConsoleSet(event eventstore.Event) (*handler.
if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-Dgf11", "reduce.wrong.event.type %s", instance.ConsoleSetEventType)
}
- return crdb.NewUpsertStatement(
+ return crdb.NewUpdateStatement(
e,
[]handler.Column{
- handler.NewCol(InstanceColumnID, e.Aggregate().InstanceID),
handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
handler.NewCol(InstanceColumnSequence, e.Sequence()),
handler.NewCol(InstanceColumnConsoleID, e.ClientID),
handler.NewCol(InstanceColumnConsoleAppID, e.AppID),
},
+ []handler.Condition{
+ handler.NewCond(InstanceColumnID, e.Aggregate().InstanceID),
+ },
), nil
}
@@ -140,14 +172,16 @@ func (p *InstanceProjection) reduceDefaultLanguageSet(event eventstore.Event) (*
if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-30o0e", "reduce.wrong.event.type %s", instance.DefaultLanguageSetEventType)
}
- return crdb.NewUpsertStatement(
+ return crdb.NewUpdateStatement(
e,
[]handler.Column{
- handler.NewCol(InstanceColumnID, e.Aggregate().InstanceID),
handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
handler.NewCol(InstanceColumnSequence, e.Sequence()),
handler.NewCol(InstanceColumnDefaultLanguage, e.Language.String()),
},
+ []handler.Condition{
+ handler.NewCond(InstanceColumnID, e.Aggregate().InstanceID),
+ },
), nil
}
diff --git a/internal/query/projection/instance_domain.go b/internal/query/projection/instance_domain.go
index 6e4b7f1e19..028848f6d2 100644
--- a/internal/query/projection/instance_domain.go
+++ b/internal/query/projection/instance_domain.go
@@ -57,7 +57,7 @@ func (p *InstanceDomainProjection) reducers() []handler.AggregateReducer {
Reduce: p.reduceDomainAdded,
},
{
- Event: instance.InstanceDomainAddedEventType,
+ Event: instance.InstanceDomainPrimarySetEventType,
Reduce: p.reduceDomainPrimarySet,
},
{
diff --git a/internal/query/projection/instance_test.go b/internal/query/projection/instance_test.go
index ecec5131c4..8ec26e0ee6 100644
--- a/internal/query/projection/instance_test.go
+++ b/internal/query/projection/instance_test.go
@@ -20,7 +20,37 @@ func TestInstanceProjection_reduces(t *testing.T) {
args args
reduce func(event eventstore.Event) (*handler.Statement, error)
want wantReduce
- }{
+ }{{
+ name: "reduceInstanceAdded",
+ args: args{
+ event: getEvent(testEvent(
+ repository.EventType(instance.InstanceAddedEventType),
+ instance.AggregateType,
+ []byte(`{"name": "Name"}`),
+ ), instance.InstanceAddedEventMapper),
+ },
+ reduce: (&InstanceProjection{}).reduceInstanceAdded,
+ want: wantReduce{
+ projection: InstanceProjectionTable,
+ aggregateType: eventstore.AggregateType("instance"),
+ sequence: 15,
+ previousSequence: 10,
+ executer: &testExecuter{
+ executions: []execution{
+ {
+ expectedStmt: "INSERT INTO projections.instances (id, creation_date, change_date, sequence, name) VALUES ($1, $2, $3, $4, $5)",
+ expectedArgs: []interface{}{
+ "instance-id",
+ anyArg{},
+ anyArg{},
+ uint64(15),
+ "Name",
+ },
+ },
+ },
+ },
+ },
+ },
{
name: "reduceGlobalOrgSet",
args: args{
@@ -39,12 +69,12 @@ func TestInstanceProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
- expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, global_org_id) VALUES ($1, $2, $3, $4)",
+ expectedStmt: "UPDATE projections.instances SET (change_date, sequence, global_org_id) = ($1, $2, $3) WHERE (id = $4)",
expectedArgs: []interface{}{
- "instance-id",
anyArg{},
uint64(15),
"orgid",
+ "instance-id",
},
},
},
@@ -69,12 +99,12 @@ func TestInstanceProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
- expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, iam_project_id) VALUES ($1, $2, $3, $4)",
+ expectedStmt: "UPDATE projections.instances SET (change_date, sequence, iam_project_id) = ($1, $2, $3) WHERE (id = $4)",
expectedArgs: []interface{}{
- "instance-id",
anyArg{},
uint64(15),
"project-id",
+ "instance-id",
},
},
},
@@ -99,12 +129,12 @@ func TestInstanceProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
- expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, default_language) VALUES ($1, $2, $3, $4)",
+ expectedStmt: "UPDATE projections.instances SET (change_date, sequence, default_language) = ($1, $2, $3) WHERE (id = $4)",
expectedArgs: []interface{}{
- "instance-id",
anyArg{},
uint64(15),
"en",
+ "instance-id",
},
},
},
diff --git a/internal/repository/instance/domain.go b/internal/repository/instance/domain.go
index c1e8a8784a..78c4a35f03 100644
--- a/internal/repository/instance/domain.go
+++ b/internal/repository/instance/domain.go
@@ -96,7 +96,7 @@ func NewDomainPrimarySetEvent(ctx context.Context, aggregate *eventstore.Aggrega
}
func DomainPrimarySetEventMapper(event *repository.Event) (eventstore.Event, error) {
- orgDomainAdded := &DomainAddedEvent{
+ orgDomainAdded := &DomainPrimarySetEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, orgDomainAdded)
diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto
index 539ff0131d..59c3a7e697 100644
--- a/proto/zitadel/admin.proto
+++ b/proto/zitadel/admin.proto
@@ -2525,34 +2525,6 @@ service AdminService {
};
}
- //Truncates the delta of the change stream
- // be carefull with this function because ZITADEL has to
- // recompute the deltas after they got cleared.
- // Search requests will return wrong results until all deltas are recomputed
- rpc ClearView(ClearViewRequest) returns (ClearViewResponse) {
- option (google.api.http) = {
- post: "/views/{database}/{view_name}";
- };
-
- option (zitadel.v1.auth_option) = {
- permission: "iam.write";
- };
-
- option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
- tags: "views";
- external_docs: {
- url: "https://docs.zitadel.ch/concepts#Software_Architecture";
- description: "details of ZITADEL's event driven software concepts";
- };
- responses: {
- key: "200";
- value: {
- description: "View cleared";
- };
- };
- };
- }
-
//Returns event descriptions which cannot be processed.
// It's possible that some events need some retries.
// For example if the SMTP-API wasn't able to send an email at the first time
@@ -2582,7 +2554,7 @@ service AdminService {
//Deletes the event from failed events view.
// the event is not removed from the change stream
- // This call is usefull if the system was able to process the event later.
+ // This call is usefull if the system was able to process the event later.
// e.g. if the second try of sending an email was successful. the first try produced a
// failed event. You can find out if it worked on the `failure_count`
rpc RemoveFailedEvent(RemoveFailedEventRequest) returns (RemoveFailedEventResponse) {
@@ -4512,34 +4484,6 @@ message ListViewsResponse {
repeated View result = 1;
}
-message ClearViewRequest {
- option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
- json_schema: {
- required: ["database", "view_name"]
- };
- };
-
- string database = 1 [
- (validate.rules).string = {min_len: 1, max_len: 200},
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"adminapi\"";
- min_length: 1;
- max_length: 200;
- }
- ];
- string view_name = 2 [
- (validate.rules).string = {min_len: 1, max_len: 200},
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"iam_members\"";
- min_length: 1;
- max_length: 200;
- }
- ];
-}
-
-//This is an empty response
-message ClearViewResponse {}
-
//This is an empty request
message ListFailedEventsRequest {}
diff --git a/proto/zitadel/instance.proto b/proto/zitadel/instance.proto
index 8e138788cc..075a601f03 100644
--- a/proto/zitadel/instance.proto
+++ b/proto/zitadel/instance.proto
@@ -25,11 +25,6 @@ message Instance {
example: "\"ZITADEL\"";
}
];
- string version = 5 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"v1.0.0\"";
- }
- ];
}
enum State {
@@ -44,31 +39,19 @@ message Query {
oneof query {
option (validate.required) = true;
- IdQuery id_query = 1;
- StateQuery state_query = 2;
+ IdsQuery id_query = 1;
}
}
//IdQuery is always equals
-message IdQuery {
- string id = 1 [
- (validate.rules).string = {max_len: 200},
+message IdsQuery {
+ repeated string ids = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "4820840938402429";
}
];
}
-//StateQuery is always equals
-message StateQuery {
- State state = 1 [
- (validate.rules).enum.defined_only = true,
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- description: "current state of the instance";
- }
- ];
-}
-
enum FieldName {
FIELD_NAME_UNSPECIFIED = 0;
FIELD_NAME_ID = 1;
diff --git a/proto/zitadel/system.proto b/proto/zitadel/system.proto
index 0618d105cb..94b8582ef4 100644
--- a/proto/zitadel/system.proto
+++ b/proto/zitadel/system.proto
@@ -105,7 +105,7 @@ service SystemService {
// Returns a list of ZITADEL instances/tenants
rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse) {
option (google.api.http) = {
- post: "/instances"
+ post: "/instances/_search"
body: "*"
};
}
@@ -134,17 +134,11 @@ service SystemService {
};
}
- // Returns the usage metrics of an instance
- rpc GetUsage(GetUsageRequest) returns (GetUsageResponse) {
- option (google.api.http) = {
- get: "/instances/{id}/usage";
- };
- }
-
// Returns the custom domains of an instance
rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {
option (google.api.http) = {
- get: "/instances/{id}/domains";
+ post: "/instances/{id}/domains/_search";
+ body: "*"
};
}
@@ -178,6 +172,7 @@ service SystemService {
rpc ListViews(ListViewsRequest) returns (ListViewsResponse) {
option (google.api.http) = {
post: "/views/_search";
+ body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
@@ -229,6 +224,7 @@ service SystemService {
rpc ListFailedEvents(ListFailedEventsRequest) returns (ListFailedEventsResponse) {
option (google.api.http) = {
post: "/failedevents/_search";
+ body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
@@ -322,19 +318,19 @@ message GetInstanceResponse {
message AddInstanceRequest {
string instance_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string first_org_name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string first_org_name = 2 [(validate.rules).string = {max_len: 200}];
string custom_domain = 3 [(validate.rules).string = {max_len: 200}];
- string owner_first_name = 4 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string owner_last_name = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string owner_first_name = 4 [(validate.rules).string = {max_len: 200}];
+ string owner_last_name = 5 [(validate.rules).string = {max_len: 200}];
string owner_email = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string owner_username = 7 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string password = 8 [(validate.rules).string = {min_len: 1, max_len: 200}];
- uint64 request_limit = 9;
- uint64 action_mins_limit = 10;
+ string owner_username = 7 [(validate.rules).string = {max_len: 200}];
+ uint64 request_limit = 8;
+ uint64 action_mins_limit = 9;
}
message AddInstanceResponse {
string id = 1;
+ zitadel.v1.ObjectDetails details = 2;
}
message RemoveInstanceRequest {