Merge branch 'main' into next-rc

This commit is contained in:
adlerhurst 2024-07-05 10:17:43 +02:00
commit 7a552bd495
61 changed files with 791 additions and 167 deletions

View File

@ -578,8 +578,7 @@ DefaultInstance:
Org:
Name: ZITADEL # ZITADEL_DEFAULTINSTANCE_ORG_NAME
# In the DefaultInstance.Org.Human section, the initial organization's admin user with the role IAM_OWNER is defined.
# ZITADEL either creates a human user or a machine user.
# If DefaultInstance.Org.Machine.Machine is defined, a service user is created with the IAM_OWNER role, not a human user.
# If DefaultInstance.Org.Machine.Machine is defined, a service user is created with the IAM_OWNER role.
Human:
# In case that UserLoginMustBeDomain is false (default) and if you don't overwrite the username with an email,
# it will be suffixed by the org domain (org-name + domain from config).
@ -599,8 +598,7 @@ DefaultInstance:
Verified: # ZITADEL_DEFAULTINSTANCE_ORG_HUMAN_PHONE_VERIFIED
Password: # ZITADEL_DEFAULTINSTANCE_ORG_HUMAN_PASSWORD
# In the DefaultInstance.Org.Machine section, the initial organization's admin user with the role IAM_OWNER is defined.
# ZITADEL either creates a human user or a machine user.
# If DefaultInstance.Org.Machine.Machine is defined, a service user is created with the IAM_OWNER role, not a human user.
# If DefaultInstance.Org.Machine.Machine is defined, a service user is created with the IAM_OWNER role.
Machine:
Machine:
Username: # ZITADEL_DEFAULTINSTANCE_ORG_MACHINE_MACHINE_USERNAME

42
cmd/setup/30.go Normal file
View File

@ -0,0 +1,42 @@
package setup
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/repository/instance"
)
type FillFieldsForOrgDomainVerified struct {
eventstore *eventstore.Eventstore
}
func (mig *FillFieldsForOrgDomainVerified) Execute(ctx context.Context, _ eventstore.Event) error {
instances, err := mig.eventstore.InstanceIDs(
ctx,
0,
true,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsInstanceIDs).
OrderDesc().
AddQuery().
AggregateTypes("instance").
EventTypes(instance.InstanceAddedEventType).
Builder(),
)
if err != nil {
return err
}
for _, instance := range instances {
ctx := authz.WithInstanceID(ctx, instance)
if err := projection.OrgDomainVerifiedFields.Trigger(ctx); err != nil {
return err
}
}
return nil
}
func (mig *FillFieldsForOrgDomainVerified) String() string {
return "30_fill_fields_for_org_domain_verified"
}

View File

@ -113,6 +113,7 @@ type Steps struct {
s27IDPTemplate6SAMLNameIDFormat *IDPTemplate6SAMLNameIDFormat
s28AddFieldTable *AddFieldTable
s29FillFieldsForProjectGrant *FillFieldsForProjectGrant
s30FillFieldsForOrgDomainVerified *FillFieldsForOrgDomainVerified
}
func MustNewSteps(v *viper.Viper) *Steps {

View File

@ -158,6 +158,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.s27IDPTemplate6SAMLNameIDFormat = &IDPTemplate6SAMLNameIDFormat{dbClient: esPusherDBClient}
steps.s28AddFieldTable = &AddFieldTable{dbClient: esPusherDBClient}
steps.s29FillFieldsForProjectGrant = &FillFieldsForProjectGrant{eventstore: eventstoreClient}
steps.s30FillFieldsForOrgDomainVerified = &FillFieldsForOrgDomainVerified{eventstore: eventstoreClient}
err = projection.Create(ctx, projectionDBClient, eventstoreClient, config.Projections, nil, nil, nil)
logging.OnError(err).Fatal("unable to start projections")
@ -198,6 +199,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.s24AddActorToAuthTokens,
steps.s26AuthUsers3,
steps.s29FillFieldsForProjectGrant,
steps.s30FillFieldsForOrgDomainVerified,
} {
mustExecuteMigration(ctx, eventstoreClient, step, "migration failed")
}

View File

@ -11,8 +11,7 @@ FirstInstance:
Org:
Name: ZITADEL # ZITADEL_FIRSTINSTANCE_ORG_NAME
# In the FirstInstance.Org.Human section, the initial organization's admin user with the role IAM_OWNER is defined.
# ZITADEL either creates a human user or a machine user.
# If FirstInstance.Org.Machine.Machine is defined, a service user is created with the IAM_OWNER role, not a human user.
# If FirstInstance.Org.Machine.Machine is defined, a service user is created with the IAM_OWNER role.
Human:
# In case UserLoginMustBeDomain is false (default) and you don't overwrite the username with an email,
# it will be suffixed by the org domain (org-name + domain from config).
@ -34,8 +33,7 @@ FirstInstance:
Password: Password1! # ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD
PasswordChangeRequired: true # ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED
# In the FirstInstance.Org.Machine section, the initial organization's admin user with the role IAM_OWNER is defined.
# ZITADEL either creates a human user or a machine user.
# If FirstInstance.Org.Machine.Machine is defined, a service user is created with the IAM_OWNER role, not a human user.
# If FirstInstance.Org.Machine.Machine is defined, a service user is created with the IAM_OWNER role.
Machine:
Machine:
Username: # ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME

View File

@ -45,6 +45,7 @@
featureData.loginDefaultOrg?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot enabled"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.ENABLED' | translate }}"
></div>
<div
*ngIf="
@ -53,6 +54,7 @@
featureData.loginDefaultOrg?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot disabled"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.DISABLED' | translate }}"
></div>
</div>
</mat-button-toggle>
@ -99,6 +101,7 @@
featureData.oidcLegacyIntrospection?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot enabled"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.ENABLED' | translate }}"
></div>
<div
*ngIf="
@ -107,6 +110,7 @@
featureData.oidcLegacyIntrospection?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot disabled"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.DISABLED' | translate }}"
></div>
</div>
</mat-button-toggle>
@ -152,6 +156,7 @@
featureData.oidcTokenExchange?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot enabled"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.ENABLED' | translate }}"
></div>
<div
*ngIf="
@ -160,6 +165,7 @@
featureData.oidcTokenExchange?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot disabled"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.DISABLED' | translate }}"
></div>
</div>
</mat-button-toggle>
@ -205,6 +211,7 @@
featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot enabled"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.ENABLED' | translate }}"
></div>
<div
*ngIf="
@ -213,6 +220,7 @@
featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot disabled"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.DISABLED' | translate }}"
></div>
</div>
</mat-button-toggle>
@ -258,6 +266,7 @@
featureData.userSchema?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot enabled"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.ENABLED' | translate }}"
></div>
<div
*ngIf="
@ -266,6 +275,7 @@
featureData.userSchema?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot disabled"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.DISABLED' | translate }}"
></div>
</div>
</mat-button-toggle>
@ -311,6 +321,7 @@
featureData.actions?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot enabled"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.ENABLED' | translate }}"
></div>
<div
*ngIf="
@ -319,6 +330,7 @@
featureData.actions?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot disabled"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.DISABLED' | translate }}"
></div>
</div>
</mat-button-toggle>

View File

@ -491,6 +491,8 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.verifiedPhone', value: '{{.VerifiedPhone}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.loginnames', value: '{{.LoginNames}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.changedate', value: '{{.ChangeDate}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.expiry', value: '{{.Expiry}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.domain', value: '{{.Domain}}' },
],
[MESSAGETYPES.VERIFYEMAILOTP]: [
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.otp', value: '{{.OTP}}' },
@ -507,6 +509,8 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.verifiedPhone', value: '{{.VerifiedPhone}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.loginnames', value: '{{.LoginNames}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.changedate', value: '{{.ChangeDate}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.expiry', value: '{{.Expiry}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.domain', value: '{{.Domain}}' },
],
[MESSAGETYPES.PASSWORDLESS]: [
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.preferredLoginName', value: '{{.PreferredLoginName}}' },

View File

@ -1474,6 +1474,10 @@
"DISABLED": "Деактивирано"
},
"INHERITED_DESCRIPTION": "Тази настройка задава стойността по подразбиране на системата.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "„Активирано“ се наследява",
"DISABLED": "„Деактивирано“ се наследява"
},
"RESET": "Задай всички на наследено"
},
"DIALOG": {
@ -1679,7 +1683,8 @@
"username": "Потребителско име",
"tempUsername": "Временно потребителско име",
"otp": "Еднократна парола",
"verifyUrl": "URL за потвърждаване на еднократна парола"
"verifyUrl": "URL за потвърждаване на еднократна парола",
"expiry": "Изтичане"
},
"TOAST": {
"UPDATED": "Персонализираните текстове са запазени."

View File

@ -1475,6 +1475,10 @@
"DISABLED": "Zakázáno"
},
"INHERITED_DESCRIPTION": "Toto nastavení nastaví hodnotu na výchozí hodnotu systému.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "„Povoleno“ je zděděno",
"DISABLED": "„Zakázáno“ je zděděno"
},
"RESET": "Nastavit vše na děděné"
},
"DIALOG": {
@ -1680,7 +1684,8 @@
"username": "Uživatelské jméno",
"tempUsername": "Dočasné uživatelské jméno",
"otp": "Jednorázové heslo",
"verifyUrl": "Ověřovací URL jednorázového hesla"
"verifyUrl": "Ověřovací URL jednorázového hesla",
"expiry": "Expirace"
},
"TOAST": {
"UPDATED": "Vlastní texty uloženy."

View File

@ -1475,6 +1475,10 @@
"DISABLED": "Deaktiviert"
},
"INHERITED_DESCRIPTION": "Diese Einstellung setzt den Wert auf den Standardwert des Systems.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "„Aktiviert“ wird vererbt",
"DISABLED": "„Deaktiviert“ wird vererbt"
},
"RESET": "Alle auf Erben setzen"
},
"DIALOG": {
@ -1680,7 +1684,8 @@
"username": "Username",
"tempUsername": "Temp. Username",
"otp": "Einmalpasswort",
"verifyUrl": "URL zur Überprüfung des Einmalpassworts"
"verifyUrl": "URL zur Überprüfung des Einmalpassworts",
"expiry": "Ablauf"
},
"TOAST": {
"UPDATED": "Benutzerdefinierte Texte gespeichert."

View File

@ -1475,6 +1475,10 @@
"DISABLED": "Disabled"
},
"INHERITED_DESCRIPTION": "This sets the value to the default value of the system.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "\"Enabled\" is inherited",
"DISABLED": "\"Disabled\" is inherited"
},
"RESET": "Set all to inherit"
},
"DIALOG": {
@ -1680,7 +1684,8 @@
"username": "Username",
"tempUsername": "Temp username",
"otp": "One-time password",
"verifyUrl": "Verify One-time-password URL"
"verifyUrl": "Verify One-time-password URL",
"expiry": "Expiry"
},
"TOAST": {
"UPDATED": "Custom Texts saved."

View File

@ -1476,6 +1476,10 @@
"DISABLED": "Deshabilitado"
},
"INHERITED_DESCRIPTION": "Esta configuración establece el valor al valor predeterminado del sistema.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "\"Habilitado\" se hereda",
"DISABLED": "\"Deshabilitado\" se hereda"
},
"RESET": "Establecer todo a heredado"
},
"DIALOG": {
@ -1681,7 +1685,8 @@
"username": "Nombre de usuario",
"tempUsername": "Nombre de usuario temporal",
"otp": "Contraseña de un solo uso",
"verifyUrl": "URL para verificar la contraseña de un solo uso"
"verifyUrl": "URL para verificar la contraseña de un solo uso",
"expiry": "Expiración"
},
"TOAST": {
"UPDATED": "Textos personalizados guardados."

View File

@ -1475,6 +1475,10 @@
"DISABLED": "Désactivé"
},
"INHERITED_DESCRIPTION": "Ce paramètre définit la valeur par défaut du système.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "\"Activé\" est hérité",
"DISABLED": "\"Désactivé\" est hérité"
},
"RESET": "Réinitialiser tout sur hérité"
},
"DIALOG": {
@ -1680,7 +1684,8 @@
"username": "Nom d'utilisateur",
"tempUsername": "Nom d'utilisateur temporaire",
"otp": "Mot de passe à usage unique",
"verifyUrl": "URL pour vérifier le mot de passe à usage unique"
"verifyUrl": "URL pour vérifier le mot de passe à usage unique",
"expiry": "Expiration"
},
"TOAST": {
"UPDATED": "Textes personnalisés enregistrés."

View File

@ -1475,6 +1475,10 @@
"DISABLED": "Disabilitato"
},
"INHERITED_DESCRIPTION": "Questa impostazione imposta il valore predefinito del sistema.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "\"Abilitato\" viene ereditato",
"DISABLED": "\"Disabilitato\" viene ereditato"
},
"RESET": "Imposta tutto su predefinito"
},
"DIALOG": {
@ -1680,7 +1684,8 @@
"username": "Nome utente",
"tempUsername": "Nome utente temporaneo",
"otp": "Password monouso",
"verifyUrl": "URL per verificare la password monouso"
"verifyUrl": "URL per verificare la password monouso",
"expiry": "Scadenza"
},
"TOAST": {
"UPDATED": "Testi personalizzati salvati."

View File

@ -1475,6 +1475,10 @@
"DISABLED": "無効"
},
"INHERITED_DESCRIPTION": "この設定は、値をシステムのデフォルト値に設定します。",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "有効は継承されます",
"DISABLED": "無効は継承されます"
},
"RESET": "すべて継承に設定"
},
"DIALOG": {
@ -1676,7 +1680,8 @@
"username": "ユーザー名",
"tempUsername": "一時ユーザー名",
"otp": "ワンタイムパスワード",
"verifyUrl": "ワンタイムパスワードを確認するURL"
"verifyUrl": "ワンタイムパスワードを確認するURL",
"expiry": "有効期限"
},
"TOAST": {
"UPDATED": "カスタムテキストが保存されました。"

View File

@ -1476,6 +1476,10 @@
"DISABLED": "Оневозможено"
},
"INHERITED_DESCRIPTION": "Оваа поставка ја постави вредноста на стандардната вредност на системот.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "„Овозможено“ е наследено",
"DISABLED": "„Оневозможено“ е наследено"
},
"RESET": "Поставете ги сите да наследат"
},
"DIALOG": {
@ -1681,7 +1685,8 @@
"username": "Корисничко име",
"tempUsername": "Привремено корисничко име",
"otp": "Еднократна лозинка",
"verifyUrl": "URL за потврдување на еднократна лозинка"
"verifyUrl": "URL за потврдување на еднократна лозинка",
"expiry": "Истекување"
},
"TOAST": {
"UPDATED": "Прилагодените текстови се зачувани."

View File

@ -1475,6 +1475,10 @@
"DISABLED": "Uitgeschakeld"
},
"INHERITED_DESCRIPTION": "Deze instelling stelt de waarde in op de standaardwaarde van het systeem.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "\"Ingeschakeld\" wordt overgenomen",
"DISABLED": "\"Uitgeschakeld\" wordt overgenomen"
},
"RESET": "Alles instellen op overgenomen"
},
"DIALOG": {
@ -1680,7 +1684,8 @@
"username": "Gebruikersnaam",
"tempUsername": "Tijdelijke gebruikersnaam",
"otp": "Eenmalig wachtwoord",
"verifyUrl": "Verifieer Eenmalig-wachtwoord URL"
"verifyUrl": "Verifieer Eenmalig-wachtwoord URL",
"expiry": "Vervaldatum"
},
"TOAST": {
"UPDATED": "Aangepaste Teksten opgeslagen."

View File

@ -1474,6 +1474,10 @@
"DISABLED": "Wyłączony"
},
"INHERITED_DESCRIPTION": "To ustawienie przypisuje wartość do wartości domyślnej systemu.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "„Włączony” jest dziedziczone",
"DISABLED": "„Wyłączony” jest dziedziczone"
},
"RESET": "Ustaw wszystko na dziedziczone"
},
"DIALOG": {
@ -1679,7 +1683,8 @@
"username": "Nazwa użytkownika",
"tempUsername": "Tymczasowa nazwa użytkownika",
"otp": "Hasło jednorazowe",
"verifyUrl": "URL do weryfikacji hasła jednorazowego"
"verifyUrl": "URL do weryfikacji hasła jednorazowego",
"expiry": "Wygaśnięcie"
},
"TOAST": {
"UPDATED": "Teksty niestandardowe zapisane."

View File

@ -1476,6 +1476,10 @@
"DISABLED": "Desabilitado"
},
"INHERITED_DESCRIPTION": "Essa configuração define o valor para o padrão do sistema.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "\"Habilitado\" é herdado",
"DISABLED": "\"Desabilitado\" é herdado"
},
"RESET": "Definir tudo para herdar"
},
"DIALOG": {
@ -1681,7 +1685,8 @@
"username": "Nome de usuário",
"tempUsername": "Nome de usuário temporário",
"otp": "Senha de uso único",
"verifyUrl": "URL para verificar a senha de uso único"
"verifyUrl": "URL para verificar a senha de uso único",
"expiry": "Data de expiração"
},
"TOAST": {
"UPDATED": "Textos personalizados salvos."

View File

@ -1526,6 +1526,10 @@
"DISABLED": "Выключено"
},
"INHERITED_DESCRIPTION": "Эта настройка устанавливает значение по умолчанию для системы.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "«Включено» наследуется",
"DISABLED": "«Выключено» передается по наследству"
},
"RESET": "Установить все по умолчанию"
},
"DIALOG": {
@ -1747,7 +1751,8 @@
"username": "Имя пользователя",
"tempUsername": "Временное имя пользователя",
"otp": "Одноразовый пароль",
"verifyUrl": "Проверка URL-адреса с одноразовым паролем"
"verifyUrl": "Проверка URL-адреса с одноразовым паролем",
"expiry": "Срок действия"
},
"TOAST": {
"UPDATED": "Тексты сохранены."

View File

@ -1479,6 +1479,10 @@
"DISABLED": "Inaktiverad"
},
"INHERITED_DESCRIPTION": "Detta ställer in värdet till systemets standardvärde.",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "\"Aktiverad\" ärvs",
"DISABLED": "\"Inaktiverad\" ärvs"
},
"RESET": "Återställ allt till arv"
},
"DIALOG": {
@ -1684,7 +1688,8 @@
"username": "Användarnamn",
"tempUsername": "Tillfälligt användarnamn",
"otp": "Engångslösenord",
"verifyUrl": "Verifiera Engångslösenord URL"
"verifyUrl": "Verifiera Engångslösenord URL",
"expiry": "Utgångsdatum"
},
"TOAST": {
"UPDATED": "Anpassade Texter sparade."

View File

@ -1475,6 +1475,10 @@
"DISABLED": "已禁用"
},
"INHERITED_DESCRIPTION": "此设置将值设置为系统默认值。",
"INHERITEDINDICATOR_DESCRIPTION": {
"ENABLED": "“已启用” 是继承的",
"DISABLED": "“已禁用” 是继承的"
},
"RESET": "全部设置为继承"
},
"DIALOG": {
@ -1679,7 +1683,8 @@
"username": "用户名",
"tempUsername": "临时用户名",
"otp": "一次性密码",
"verifyUrl": "验证一次性密码的URL"
"verifyUrl": "验证一次性密码的URL",
"expiry": "过期时间"
},
"TOAST": {
"UPDATED": "自定义文本已保存。"

View File

@ -21,6 +21,7 @@ When you configure your default settings, you can set the following:
- [**Login Behavior and Access**](#login-behavior-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface.
- [**Identity Providers**](#identity-providers): Define IDPs which are available for all organizations
- [**Password Complexity**](#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more.
- [**Password Expiry**](#password-expiry): Set an expiry for passwords. After the expiration, a user will be prompted to change their password during the next login.
- [**Lockout**](#lockout): Set the maximum attempts a user can try to enter the password or any (T)OTP method. When the number is exceeded, the user gets locked out and has to be unlocked.
- [**Domain settings**](#domain-settings): Whether users use their email or the generated username to login. Other Validation, SMTP settings
- [**Branding**](#branding): Appearance of the login interface.
@ -222,6 +223,24 @@ The following properties can be set:
width="600px"
/>
## Password Expiry
With the password expiry policy you can set an expiration for user password.
After the expiration, a user will be prompted to change their password during the next authentication.
Note, that ZITADEL will not warn or notify the user about the expiry, yet. If you want your users to be notified, you can read this setting and send the notification yourself.
The following properties can be set:
- Maximum validity in days
- Expiration warning after days
<img
src="/docs/img/guides/console/expiry.png"
alt="Password Expiry Policy"
width="600px"
/>
## Lockout
Define when an account should be locked.

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@ -37,7 +37,7 @@ describe('applications', () => {
cy.get('[data-e2e="create-button"]').click();
cy.get('[id*=overlay]').should('exist');
cy.shouldConfirmSuccess();
const expectClientId = new RegExp(`^.*[0-9]+\\@${testProjectName}.*$`);
const expectClientId = new RegExp(`^.*[0-9]+.*$`);
cy.get('[data-e2e="client-id-copy"]').click();
cy.contains('[data-e2e="client-id"]', expectClientId);
cy.clipboardMatches(expectClientId);
@ -64,7 +64,7 @@ describe('applications', () => {
cy.get('[data-e2e="create-button"]').click();
cy.get('[id*=overlay]').should('exist');
cy.shouldConfirmSuccess();
const expectClientId = new RegExp(`^.*[0-9]+\\@${testProjectName}.*$`);
const expectClientId = new RegExp(`^.*[0-9]+.*$`);
cy.get('[data-e2e="client-id-copy"]').click();
cy.contains('[data-e2e="client-id"]', expectClientId);
cy.clipboardMatches(expectClientId);

View File

@ -39,7 +39,10 @@ func (s *Server) RemoveOrg(ctx context.Context, req *admin_pb.RemoveOrgRequest)
func (s *Server) GetDefaultOrg(ctx context.Context, _ *admin_pb.GetDefaultOrgRequest) (*admin_pb.GetDefaultOrgResponse, error) {
org, err := s.query.OrgByID(ctx, true, authz.GetInstance(ctx).DefaultOrganisationID())
return &admin_pb.GetDefaultOrgResponse{Org: org_grpc.OrgToPb(org)}, err
if err != nil {
return nil, err
}
return &admin_pb.GetDefaultOrgResponse{Org: org_grpc.OrgToPb(org)}, nil
}
func (s *Server) GetOrgByID(ctx context.Context, req *admin_pb.GetOrgByIDRequest) (*admin_pb.GetOrgByIDResponse, error) {

View File

@ -113,6 +113,10 @@ func improvedPerformanceTypeToPb(typ feature.ImprovedPerformanceType) feature_pb
return feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_PROJECT_GRANT
case feature.ImprovedPerformanceTypeProject:
return feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_PROJECT
case feature.ImprovedPerformanceTypeUserGrant:
return feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_USER_GRANT
case feature.ImprovedPerformanceTypeOrgDomainVerified:
return feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_ORG_DOMAIN_VERIFIED
default:
return feature_pb.ImprovedPerformance(typ)
}
@ -141,6 +145,10 @@ func improvedPerformanceToDomain(typ feature_pb.ImprovedPerformance) feature.Imp
return feature.ImprovedPerformanceTypeProjectGrant
case feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_PROJECT:
return feature.ImprovedPerformanceTypeProject
case feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_USER_GRANT:
return feature.ImprovedPerformanceTypeUserGrant
case feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_ORG_DOMAIN_VERIFIED:
return feature.ImprovedPerformanceTypeOrgDomainVerified
default:
return feature.ImprovedPerformanceTypeUnknown
}

View File

@ -65,13 +65,16 @@ func (s *Server) UpdateAction(ctx context.Context, req *mgmt_pb.UpdateActionRequ
func (s *Server) DeactivateAction(ctx context.Context, req *mgmt_pb.DeactivateActionRequest) (*mgmt_pb.DeactivateActionResponse, error) {
details, err := s.command.DeactivateAction(ctx, req.Id, authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
return &mgmt_pb.DeactivateActionResponse{
Details: obj_grpc.AddToDetailsPb(
details.Sequence,
details.EventDate,
details.ResourceOwner,
),
}, err
}, nil
}
func (s *Server) ReactivateAction(ctx context.Context, req *mgmt_pb.ReactivateActionRequest) (*mgmt_pb.ReactivateActionResponse, error) {

View File

@ -20,8 +20,10 @@ import (
client_middleware "github.com/zitadel/zitadel/internal/api/grpc/client/middleware"
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
http_utils "github.com/zitadel/zitadel/internal/api/http"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/metrics"
)
const (
@ -206,7 +208,10 @@ func addInterceptors(
// For some non-obvious reason, the exhaustedCookieInterceptor sends the SetCookie header
// only if it follows the http_mw.DefaultTelemetryHandler
handler = exhaustedCookieInterceptor(handler, accessInterceptor)
handler = http_mw.DefaultMetricsHandler(handler)
handler = http_mw.MetricsHandler([]metrics.MetricType{
metrics.MetricTypeTotalCount,
metrics.MetricTypeStatusCode,
}, http_utils.Probes...)(handler)
return handler
}

View File

@ -3,15 +3,9 @@ package middleware
import (
"net/http"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/telemetry/metrics"
)
func DefaultMetricsHandler(handler http.Handler) http.Handler {
metricTypes := []metrics.MetricType{metrics.MetricTypeTotalCount}
return MetricsHandler(metricTypes, http_utils.Probes...)(handler)
}
func MetricsHandler(metricTypes []metrics.MetricType, ignoredMethods ...string) func(http.Handler) http.Handler {
return func(handler http.Handler) http.Handler {
return metrics.NewMetricsHandler(handler, metricTypes, ignoredMethods...)

View File

@ -513,6 +513,11 @@ func (s *Server) authorizeCallbackHandler(w http.ResponseWriter, r *http.Request
return authReq, s.authResponse(authReq, authorizer, w, r)
}(r.Context())
if err != nil {
// we need to make sure there's no empty interface passed
if authReq == nil {
op.AuthRequestError(w, r, nil, err, authorizer)
return
}
op.AuthRequestError(w, r, authReq, err, authorizer)
}
}

View File

@ -111,6 +111,9 @@ func (s *Server) userInfo(
}
rawUserInfo = userInfoToOIDC(qu, userInfoAssertion, scope, s.assetAPIPrefix(ctx))
})
if err != nil {
return nil, err
}
// copy the userinfo to make sure the assert roles and actions use their own copy (e.g. map)
userInfo := &oidc.UserInfo{
Subject: rawUserInfo.Subject,

View File

@ -126,7 +126,8 @@ func (c *Commands) GetActiveIntent(ctx context.Context, intentID string) (*IDPIn
return nil, zerrors.ThrowNotFound(nil, "IDP-gy3ctgkqe7", "Errors.Intent.NotStarted")
}
if intent.State != domain.IDPIntentStateStarted {
return nil, zerrors.ThrowInvalidArgument(nil, "IDP-Sfrgs", "Errors.Intent.NotStarted")
// we still need to return the intent to be able to redirect to the failure url
return intent, zerrors.ThrowInvalidArgument(nil, "IDP-Sfrgs", "Errors.Intent.NotStarted")
}
return intent, nil
}

View File

@ -63,7 +63,7 @@ func projectAddedEvents(ctx context.Context, instanceID, orgID, id, owner string
events = append(events, apiAppEvents(ctx, orgID, id, "auth-id", "Auth-API")...)
consoleAppID := "console-id"
consoleClientID := "clientID@zitadel"
consoleClientID := "clientID"
events = append(events, oidcAppEvents(ctx, orgID, id, consoleAppID, "Console", consoleClientID, externalSecure)...)
events = append(events,
instance.NewIAMConsoleSetEvent(ctx,
@ -90,7 +90,7 @@ func apiAppEvents(ctx context.Context, orgID, projectID, id, name string) []even
project.NewAPIConfigAddedEvent(ctx,
&project.NewAggregate(projectID, orgID).Aggregate,
id,
"clientID@zitadel",
"clientID",
"",
domain.APIAuthMethodTypePrivateKeyJWT,
),

View File

@ -13,6 +13,8 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/feature"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
@ -390,3 +392,65 @@ func (c *Commands) getOrgDomainWriteModel(ctx context.Context, orgID, domain str
}
return domainWriteModel, nil
}
type OrgDomainVerified struct {
OrgID string
Domain string
Verified bool
}
func (c *Commands) searchOrgDomainVerifiedByDomain(ctx context.Context, domain string) (_ *OrgDomainVerified, err error) {
if !authz.GetFeatures(ctx).ShouldUseImprovedPerformance(feature.ImprovedPerformanceTypeOrgDomainVerified) {
return c.searchOrgDomainVerifiedByDomainOld(ctx, domain)
}
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
condition := map[eventstore.FieldType]any{
eventstore.FieldTypeAggregateType: org.AggregateType,
eventstore.FieldTypeObjectType: org.OrgDomainSearchType,
eventstore.FieldTypeObjectID: domain,
eventstore.FieldTypeObjectRevision: org.OrgDomainObjectRevision,
eventstore.FieldTypeFieldName: org.OrgDomainVerifiedSearchField,
}
results, err := c.eventstore.Search(ctx, condition)
if err != nil {
return nil, err
}
if len(results) == 0 {
_ = projection.OrgDomainVerifiedFields.Trigger(ctx)
results, err = c.eventstore.Search(ctx, condition)
if err != nil {
return nil, err
}
}
orgDomain := new(OrgDomainVerified)
for _, result := range results {
orgDomain.OrgID = result.Aggregate.ID
if err = result.Value.Unmarshal(&orgDomain.Verified); err != nil {
return nil, err
}
}
return orgDomain, nil
}
func (c *Commands) searchOrgDomainVerifiedByDomainOld(ctx context.Context, domain string) (_ *OrgDomainVerified, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel := NewOrgDomainVerifiedWriteModel(domain)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return &OrgDomainVerified{
OrgID: writeModel.ResourceOwner,
Domain: writeModel.Domain,
Verified: writeModel.Verified,
}, nil
}

View File

@ -35,7 +35,7 @@ func (c *Commands) AddAPIAppCommand(app *addAPIApp) preparation.Validation {
return nil, zerrors.ThrowNotFound(err, "PROJE-Sf2gb", "Errors.Project.NotFound")
}
app.ClientID, err = domain.NewClientID(c.idGenerator, project.Name)
app.ClientID, err = c.idGenerator.Next()
if err != nil {
return nil, zerrors.ThrowInternal(err, "V2-f0pgP", "Errors.Internal")
}
@ -78,19 +78,19 @@ func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.A
if existingAPI.State != domain.AppStateUnspecified {
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-mabu12", "Errors.Project.App.AlreadyExisting")
}
project, err := c.getProjectByID(ctx, apiApp.AggregateID, resourceOwner)
_, err = c.getProjectByID(ctx, apiApp.AggregateID, resourceOwner)
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-9fnsa", "Errors.Project.NotFound")
}
return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID)
return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, appID)
}
func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (_ *domain.APIApp, err error) {
if apiApp == nil || apiApp.AggregateID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Project.App.Invalid")
}
project, err := c.getProjectByID(ctx, apiApp.AggregateID, resourceOwner)
_, err = c.getProjectByID(ctx, apiApp.AggregateID, resourceOwner)
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-9fnsf", "Errors.Project.NotFound")
}
@ -104,10 +104,10 @@ func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp,
return nil, err
}
return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID)
return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, appID)
}
func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, project *domain.Project, appID string) (_ *domain.APIApp, err error) {
func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, appID string) (_ *domain.APIApp, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@ -121,7 +121,7 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A
}
var plain string
err = domain.SetNewClientID(apiApp, c.idGenerator, project)
err = domain.SetNewClientID(apiApp, c.idGenerator)
if err != nil {
return nil, err
}

View File

@ -117,7 +117,7 @@ func TestAddAPIConfig(t *testing.T) {
),
project.NewAPIConfigAddedEvent(ctx, &agg.Aggregate,
"appID",
"clientID@project",
"clientID",
"",
domain.APIAuthMethodTypePrivateKeyJWT,
),
@ -252,7 +252,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
"client1",
"secret",
domain.APIAuthMethodTypeBasic),
),
@ -278,7 +278,61 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
ClientID: "client1",
ClientSecretString: "secret",
AuthMethodType: domain.APIAuthMethodTypeBasic,
State: domain.AppStateActive,
},
},
},
{
name: "create api app basic old ID format, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project", true, true, true,
domain.PrivateLabelingSettingUnspecified),
),
),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project1",
"secret",
domain.APIAuthMethodTypeBasic),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1@project1"),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypeBasic,
},
resourceOwner: "org1",
},
res: res{
want: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project1",
ClientSecretString: "secret",
AuthMethodType: domain.APIAuthMethodTypeBasic,
State: domain.AppStateActive,
@ -306,7 +360,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
"client1",
"",
domain.APIAuthMethodTypePrivateKeyJWT),
),
@ -332,7 +386,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
ClientID: "client1",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
State: domain.AppStateActive,
},

View File

@ -68,7 +68,7 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp) preparation.Validation {
return nil, zerrors.ThrowNotFound(err, "PROJE-6swVG", "Errors.Project.NotFound")
}
app.ClientID, err = domain.NewClientID(c.idGenerator, project.Name)
app.ClientID, err = c.idGenerator.Next()
if err != nil {
return nil, zerrors.ThrowInternal(err, "V2-VMSQ1", "Errors.Internal")
}
@ -126,19 +126,19 @@ func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-lxowmp", "Errors.Project.App.AlreadyExisting")
}
project, err := c.getProjectByID(ctx, oidcApp.AggregateID, resourceOwner)
_, err = c.getProjectByID(ctx, oidcApp.AggregateID, resourceOwner)
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-3m9s2", "Errors.Project.NotFound")
}
return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID)
return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, appID)
}
func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string) (_ *domain.OIDCApp, err error) {
if oidcApp == nil || oidcApp.AggregateID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Project.App.Invalid")
}
project, err := c.getProjectByID(ctx, oidcApp.AggregateID, resourceOwner)
_, err = c.getProjectByID(ctx, oidcApp.AggregateID, resourceOwner)
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-3m9ss", "Errors.Project.NotFound")
}
@ -152,10 +152,10 @@ func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCA
return nil, err
}
return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID)
return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, appID)
}
func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, project *domain.Project, appID string) (_ *domain.OIDCApp, err error) {
func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, appID string) (_ *domain.OIDCApp, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@ -169,7 +169,7 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
}
var plain string
err = domain.SetNewClientID(oidcApp, c.idGenerator, project)
err = domain.SetNewClientID(oidcApp, c.idGenerator)
if err != nil {
return nil, err
}

View File

@ -158,7 +158,7 @@ func TestAddOIDCApp(t *testing.T) {
project.NewOIDCConfigAddedEvent(ctx, &agg.Aggregate,
domain.OIDCVersionV1,
"id",
"clientID@project",
"clientID",
"",
[]string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
@ -214,6 +214,71 @@ func TestAddOIDCApp(t *testing.T) {
}).
Filter(),
},
want: Want{
Commands: []eventstore.Command{
project.NewApplicationAddedEvent(ctx, &agg.Aggregate,
"id",
"name",
),
project.NewOIDCConfigAddedEvent(ctx, &agg.Aggregate,
domain.OIDCVersionV1,
"id",
"clientID",
"",
nil,
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypeNone,
nil,
false,
domain.OIDCTokenTypeBearer,
false,
false,
false,
0,
nil,
false,
),
},
},
},
{
name: "correct with old ID format",
fields: fields{
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "clientID@project"),
},
args: args{
app: &addOIDCApp{
AddApp: AddApp{
Aggregate: *agg,
ID: "id",
Name: "name",
},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
Version: domain.OIDCVersionV1,
ApplicationType: domain.OIDCApplicationTypeWeb,
AuthMethodType: domain.OIDCAuthMethodTypeNone,
AccessTokenType: domain.OIDCTokenTypeBearer,
},
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
project.NewProjectAddedEvent(
ctx,
&agg.Aggregate,
"project",
false,
false,
false,
domain.PrivateLabelingSettingUnspecified,
),
}, nil
}).
Filter(),
},
want: Want{
Commands: []eventstore.Command{
project.NewApplicationAddedEvent(ctx, &agg.Aggregate,
@ -288,7 +353,7 @@ func TestAddOIDCApp(t *testing.T) {
project.NewOIDCConfigAddedEvent(ctx, &agg.Aggregate,
domain.OIDCVersionV1,
"id",
"clientID@project",
"clientID",
"secret",
nil,
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
@ -434,7 +499,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate,
domain.OIDCVersionV1,
"app1",
"client1@project",
"client1",
"secret",
[]string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
@ -488,7 +553,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
ClientID: "client1",
ClientSecretString: "secret",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
@ -532,7 +597,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate,
domain.OIDCVersionV1,
"app1",
"client1@project",
"client1",
"secret",
[]string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
@ -586,7 +651,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
ClientID: "client1",
ClientSecretString: "secret",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,

View File

@ -259,38 +259,49 @@ func (c *Commands) checkProjectGrantPreCondition(ctx context.Context, projectGra
if !authz.GetFeatures(ctx).ShouldUseImprovedPerformance(feature.ImprovedPerformanceTypeProjectGrant) {
return c.checkProjectGrantPreConditionOld(ctx, projectGrant)
}
existingRoleKeys, err := c.searchProjectGrantState(ctx, projectGrant.AggregateID, projectGrant.GrantedOrgID)
if err != nil {
return err
}
if projectGrant.HasInvalidRoles(existingRoleKeys) {
return zerrors.ThrowPreconditionFailed(err, "COMMAND-6m9gd", "Errors.Project.Role.NotFound")
}
return nil
}
func (c *Commands) searchProjectGrantState(ctx context.Context, projectID, grantedOrgID string) (existingRoleKeys []string, err error) {
results, err := c.eventstore.Search(
ctx,
// project state query
map[eventstore.FieldType]any{
eventstore.FieldTypeAggregateType: project.AggregateType,
eventstore.FieldTypeAggregateID: projectGrant.AggregateID,
eventstore.FieldTypeAggregateID: projectID,
eventstore.FieldTypeFieldName: project.ProjectStateSearchField,
eventstore.FieldTypeObjectType: project.ProjectSearchType,
},
// granted org query
map[eventstore.FieldType]any{
eventstore.FieldTypeAggregateType: org.AggregateType,
eventstore.FieldTypeAggregateID: projectGrant.GrantedOrgID,
eventstore.FieldTypeAggregateID: grantedOrgID,
eventstore.FieldTypeFieldName: org.OrgStateSearchField,
eventstore.FieldTypeObjectType: org.OrgSearchType,
},
// role query
map[eventstore.FieldType]any{
eventstore.FieldTypeAggregateType: project.AggregateType,
eventstore.FieldTypeAggregateID: projectGrant.AggregateID,
eventstore.FieldTypeAggregateID: projectID,
eventstore.FieldTypeFieldName: project.ProjectRoleKeySearchField,
eventstore.FieldTypeObjectType: project.ProjectRoleSearchType,
},
)
if err != nil {
return err
return nil, err
}
var (
existsProject bool
existsGrantedOrg bool
existingRoleKeys []string
)
for _, result := range results {
@ -299,34 +310,31 @@ func (c *Commands) checkProjectGrantPreCondition(ctx context.Context, projectGra
var role string
err := result.Value.Unmarshal(&role)
if err != nil {
return err
return nil, err
}
existingRoleKeys = append(existingRoleKeys, role)
case org.OrgSearchType:
var state domain.OrgState
err := result.Value.Unmarshal(&state)
if err != nil {
return err
return nil, err
}
existsGrantedOrg = state.Valid() && state != domain.OrgStateRemoved
case project.ProjectSearchType:
var state domain.ProjectState
err := result.Value.Unmarshal(&state)
if err != nil {
return err
return nil, err
}
existsProject = state.Valid() && state != domain.ProjectStateRemoved
}
}
if !existsProject {
return zerrors.ThrowPreconditionFailed(err, "COMMAND-m9gsd", "Errors.Project.NotFound")
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-m9gsd", "Errors.Project.NotFound")
}
if !existsGrantedOrg {
return zerrors.ThrowPreconditionFailed(err, "COMMAND-3m9gg", "Errors.Org.NotFound")
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-3m9gg", "Errors.Org.NotFound")
}
if projectGrant.HasInvalidRoles(existingRoleKeys) {
return zerrors.ThrowPreconditionFailed(err, "COMMAND-6m9gd", "Errors.Project.Role.NotFound")
}
return nil
return existingRoleKeys, nil
}

View File

@ -40,17 +40,8 @@ func (c *Commands) ChangeUsername(ctx context.Context, orgID, userID, userName s
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-38fnu", "Errors.Org.DomainPolicy.NotExisting")
}
if !domainPolicy.UserLoginMustBeDomain {
index := strings.LastIndex(userName, "@")
if index > 1 {
domainCheck := NewOrgDomainVerifiedWriteModel(userName[index+1:])
if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil {
return nil, err
}
if domainCheck.Verified && domainCheck.ResourceOwner != orgID {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Di2ei", "Errors.User.DomainNotAllowedAsUsername")
}
}
if err = c.userValidateDomain(ctx, orgID, userName, domainPolicy.UserLoginMustBeDomain); err != nil {
return nil, err
}
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)

View File

@ -4,8 +4,12 @@ import (
"context"
"reflect"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/feature"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/project"
"github.com/zitadel/zitadel/internal/repository/usergrant"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
@ -288,6 +292,140 @@ func (c *Commands) userGrantWriteModelByID(ctx context.Context, userGrantID, res
}
func (c *Commands) checkUserGrantPreCondition(ctx context.Context, usergrant *domain.UserGrant, resourceOwner string) (err error) {
if !authz.GetFeatures(ctx).ShouldUseImprovedPerformance(feature.ImprovedPerformanceTypeUserGrant) {
return c.checkUserGrantPreConditionOld(ctx, usergrant, resourceOwner)
}
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if err := c.checkUserExists(ctx, usergrant.UserID, ""); err != nil {
return err
}
existingRoleKeys, err := c.searchUserGrantPreConditionState(ctx, usergrant, resourceOwner)
if err != nil {
return err
}
if usergrant.HasInvalidRoles(existingRoleKeys) {
return zerrors.ThrowPreconditionFailed(err, "COMMAND-mm9F4", "Errors.Project.Role.NotFound")
}
return nil
}
// this code needs to be rewritten anyways as soon as we improved the fields handling
//
//nolint:gocognit
func (c *Commands) searchUserGrantPreConditionState(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (existingRoleKeys []string, err error) {
criteria := []map[eventstore.FieldType]any{
// project state query
{
eventstore.FieldTypeAggregateType: project.AggregateType,
eventstore.FieldTypeAggregateID: userGrant.ProjectID,
eventstore.FieldTypeFieldName: project.ProjectStateSearchField,
eventstore.FieldTypeObjectType: project.ProjectSearchType,
},
// granted org query
{
eventstore.FieldTypeAggregateType: org.AggregateType,
eventstore.FieldTypeAggregateID: resourceOwner,
eventstore.FieldTypeFieldName: org.OrgStateSearchField,
eventstore.FieldTypeObjectType: org.OrgSearchType,
},
}
if userGrant.ProjectGrantID != "" {
criteria = append(criteria, map[eventstore.FieldType]any{
eventstore.FieldTypeAggregateType: project.AggregateType,
eventstore.FieldTypeAggregateID: userGrant.ProjectID,
eventstore.FieldTypeObjectType: project.ProjectGrantSearchType,
eventstore.FieldTypeObjectID: userGrant.ProjectGrantID,
})
} else {
criteria = append(criteria, map[eventstore.FieldType]any{
eventstore.FieldTypeAggregateType: project.AggregateType,
eventstore.FieldTypeAggregateID: userGrant.ProjectID,
eventstore.FieldTypeObjectType: project.ProjectRoleSearchType,
eventstore.FieldTypeFieldName: project.ProjectRoleKeySearchField,
})
}
results, err := c.eventstore.Search(ctx, criteria...)
if err != nil {
return nil, err
}
var (
existsProject bool
existsGrantedOrg bool
existsGrant bool
)
for _, result := range results {
switch result.Object.Type {
case project.ProjectRoleSearchType:
var role string
err := result.Value.Unmarshal(&role)
if err != nil {
return nil, err
}
existingRoleKeys = append(existingRoleKeys, role)
case org.OrgSearchType:
var state domain.OrgState
err := result.Value.Unmarshal(&state)
if err != nil {
return nil, err
}
existsGrantedOrg = state.Valid() && state != domain.OrgStateRemoved
case project.ProjectSearchType:
var state domain.ProjectState
err := result.Value.Unmarshal(&state)
if err != nil {
return nil, err
}
existsProject = state.Valid() && state != domain.ProjectStateRemoved
case project.ProjectGrantSearchType:
switch result.FieldName {
case project.ProjectGrantGrantedOrgIDSearchField:
var orgID string
err := result.Value.Unmarshal(&orgID)
if err != nil || orgID != resourceOwner {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-3m9gg", "Errors.Org.NotFound")
}
case project.ProjectGrantStateSearchField:
var state domain.ProjectGrantState
err := result.Value.Unmarshal(&state)
if err != nil {
return nil, err
}
existsGrant = state.Valid() && state != domain.ProjectGrantStateRemoved
case project.ProjectGrantRoleKeySearchField:
var role string
err := result.Value.Unmarshal(&role)
if err != nil {
return nil, err
}
existingRoleKeys = append(existingRoleKeys, role)
case project.ProjectGrantGrantIDSearchField:
var grantID string
err := result.Value.Unmarshal(&grantID)
if err != nil || grantID != userGrant.ProjectGrantID {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-huvKF", "Errors.Project.Grant.NotFound")
}
}
}
}
if !existsProject {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-m9gsd", "Errors.Project.NotFound")
}
if !existsGrantedOrg {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-3m9gg", "Errors.Org.NotFound")
}
if userGrant.ProjectGrantID != "" && !existsGrant {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-huvKF", "Errors.Project.Grant.NotFound")
}
return existingRoleKeys, nil
}
func (c *Commands) checkUserGrantPreConditionOld(ctx context.Context, usergrant *domain.UserGrant, resourceOwner string) (err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()

View File

@ -362,7 +362,7 @@ func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQue
return nil
}
func (c *Commands) userValidateDomain(ctx context.Context, resourceOwner string, username string, mustBeDomain bool) error {
func (c *Commands) userValidateDomain(ctx context.Context, resourceOwner string, username string, mustBeDomain bool) (err error) {
if mustBeDomain {
return nil
}
@ -372,12 +372,12 @@ func (c *Commands) userValidateDomain(ctx context.Context, resourceOwner string,
return nil
}
domainCheck, err := c.orgDomainVerifiedWriteModel(ctx, username[index+1:])
domainCheck, err := c.searchOrgDomainVerifiedByDomain(ctx, username[index+1:])
if err != nil {
return err
}
if domainCheck.Verified && domainCheck.ResourceOwner != resourceOwner {
if domainCheck.Verified && domainCheck.OrgID != resourceOwner {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
}
@ -479,7 +479,7 @@ func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.
if orgID == "" {
return nil, nil, nil, "", zerrors.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.Org.Empty")
}
if err := human.Normalize(); err != nil {
if err = human.Normalize(); err != nil {
return nil, nil, nil, "", err
}
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, links, passwordless, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
@ -497,24 +497,17 @@ func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.
return events, humanWriteModel, passwordlessCodeWriteModel, code, nil
}
//nolint:gocognit
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, links []*domain.UserIDPLink, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) (events []eventstore.Command, addedHuman *HumanWriteModel, err error) {
if err := human.CheckDomainPolicy(domainPolicy); err != nil {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if err = human.CheckDomainPolicy(domainPolicy); err != nil {
return nil, nil, err
}
human.Username = strings.TrimSpace(human.Username)
human.EmailAddress = human.EmailAddress.Normalize()
if !domainPolicy.UserLoginMustBeDomain {
index := strings.LastIndex(human.Username, "@")
if index > 1 {
domainCheck := NewOrgDomainVerifiedWriteModel(human.Username[index+1:])
if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil {
return nil, nil, err
}
if domainCheck.Verified && domainCheck.ResourceOwner != orgID {
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
}
}
if err = c.userValidateDomain(ctx, orgID, human.Username, domainPolicy.UserLoginMustBeDomain); err != nil {
return nil, nil, err
}
if human.AggregateID == "" {

View File

@ -434,15 +434,3 @@ func (c *Commands) userHumanWriteModel(ctx context.Context, userID string, profi
}
return writeModel, nil
}
func (c *Commands) orgDomainVerifiedWriteModel(ctx context.Context, domain string) (writeModel *OrgDomainVerifiedWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = NewOrgDomainVerifiedWriteModel(domain)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -2,7 +2,6 @@ package command
import (
"context"
"strings"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/user"
@ -19,17 +18,8 @@ func (c *Commands) changeUsername(ctx context.Context, cmds []eventstore.Command
if err != nil {
return cmds, zerrors.ThrowPreconditionFailed(err, "COMMAND-79pv6e1q62", "Errors.Org.DomainPolicy.NotExisting")
}
if !domainPolicy.UserLoginMustBeDomain {
index := strings.LastIndex(userName, "@")
if index > 1 {
domainCheck := NewOrgDomainVerifiedWriteModel(userName[index+1:])
if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil {
return cmds, err
}
if domainCheck.Verified && domainCheck.ResourceOwner != orgID {
return cmds, zerrors.ThrowInvalidArgument(nil, "COMMAND-Di2ei", "Errors.User.DomainNotAllowedAsUsername")
}
}
if err = c.userValidateDomain(ctx, orgID, userName, domainPolicy.UserLoginMustBeDomain); err != nil {
return cmds, err
}
return append(cmds,
user.NewUsernameChangedEvent(ctx, &wm.Aggregate().Aggregate, wm.UserName, userName, domainPolicy.UserLoginMustBeDomain),

View File

@ -1,9 +1,6 @@
package domain
import (
"fmt"
"strings"
"github.com/zitadel/zitadel/internal/id"
)
@ -13,9 +10,9 @@ type oAuthApplication interface {
requiresClientSecret() bool
}
// ClientID random_number@projectname (eg. 495894098234@zitadel)
func SetNewClientID(a oAuthApplication, idGenerator id.Generator, project *Project) error {
clientID, err := NewClientID(idGenerator, project.Name)
// ClientID random_number (eg. 495894098234)
func SetNewClientID(a oAuthApplication, idGenerator id.Generator) error {
clientID, err := idGenerator.Next()
if err != nil {
return err
}
@ -24,15 +21,6 @@ func SetNewClientID(a oAuthApplication, idGenerator id.Generator, project *Proje
return nil
}
func NewClientID(idGenerator id.Generator, projectName string) (string, error) {
rndID, err := idGenerator.Next()
if err != nil {
return "", err
}
return fmt.Sprintf("%s@%s", rndID, strings.ReplaceAll(strings.ToLower(projectName), " ", "_")), nil
}
func SetNewClientSecretIfNeeded(a oAuthApplication, generate func() (encodedHash, plain string, err error)) (string, error) {
if !a.requiresClientSecret() {
return "", nil

View File

@ -18,8 +18,14 @@ const (
ProjectGrantStateActive
ProjectGrantStateInactive
ProjectGrantStateRemoved
projectGrantStateMax
)
func (s ProjectGrantState) Valid() bool {
return s > ProjectGrantStateUnspecified && s < projectGrantStateMax
}
func (p *ProjectGrant) IsValid() bool {
return p.GrantedOrgID != ""
}

View File

@ -149,8 +149,8 @@ func (h *FieldHandler) processEvents(ctx context.Context, config *triggerConfig)
func (h *FieldHandler) fetchEvents(ctx context.Context, tx *sql.Tx, currentState *state) (_ []eventstore.FillFieldsEvent, additionalIteration bool, err error) {
events, err := h.es.Filter(ctx, h.eventQuery(currentState).SetTx(tx))
if err != nil {
h.log().WithError(err).Debug("filter eventstore failed")
if err != nil || len(events) == 0 {
h.log().OnError(err).Debug("filter eventstore failed")
return nil, false, err
}
eventAmount := len(events)

View File

@ -1,5 +1,7 @@
package feature
import "slices"
//go:generate enumer -type Key -transform snake -trimprefix Key
type Key int
@ -44,13 +46,10 @@ const (
ImprovedPerformanceTypeOrgByID
ImprovedPerformanceTypeProjectGrant
ImprovedPerformanceTypeProject
ImprovedPerformanceTypeUserGrant
ImprovedPerformanceTypeOrgDomainVerified
)
func (f Features) ShouldUseImprovedPerformance(typ ImprovedPerformanceType) bool {
for _, improvedType := range f.ImprovedPerformance {
if improvedType == typ {
return true
}
}
return false
return slices.Contains(f.ImprovedPerformance, typ)
}

View File

@ -21,6 +21,8 @@ var (
testdataOidcClientJWT string
//go:embed testdata/oidc_client_public.json
testdataOidcClientPublic string
//go:embed testdata/oidc_client_public_old_id.json
testdataOidcClientPublicOldId string
//go:embed testdata/oidc_client_secret.json
testdataOidcClientSecret string
//go:embed testdata/oidc_client_no_settings.json
@ -64,7 +66,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx
InstanceID: "230690539048009730",
AppID: "236647088211886082",
State: domain.AppStateActive,
ClientID: "236647088211951618@tests",
ClientID: "236647088211951618",
HashedSecret: "",
RedirectURIs: []string{"http://localhost:9999/auth/callback"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
@ -92,6 +94,38 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx
{
name: "public client",
mock: mockQuery(expQuery, cols, []driver.Value{testdataOidcClientPublic}, "instanceID", "clientID", true),
want: &OIDCClient{
InstanceID: "230690539048009730",
AppID: "236646457053020162",
State: domain.AppStateActive,
ClientID: "236646457053085698",
HashedSecret: "",
RedirectURIs: []string{"http://localhost:9999/auth/callback"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
AuthMethodType: domain.OIDCAuthMethodTypeNone,
PostLogoutRedirectURIs: nil,
IsDevMode: true,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: false,
IDTokenRoleAssertion: false,
IDTokenUserinfoAssertion: false,
ClockSkew: 0,
AdditionalOrigins: nil,
PublicKeys: nil,
ProjectID: "236645808328409090",
ProjectRoleAssertion: true,
ProjectRoleKeys: []string{"role1", "role2"},
Settings: &OIDCSettings{
AccessTokenLifetime: 43200000000000,
IdTokenLifetime: 43200000000000,
},
},
},
{
name: "public client",
mock: mockQuery(expQuery, cols, []driver.Value{testdataOidcClientPublicOldId}, "instanceID", "clientID", true),
want: &OIDCClient{
InstanceID: "230690539048009730",
AppID: "236646457053020162",
@ -128,7 +162,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx
InstanceID: "230690539048009730",
AppID: "236646858984783874",
State: domain.AppStateActive,
ClientID: "236646858984849410@tests",
ClientID: "236646858984849410",
HashedSecret: "$2a$14$OzZ0XEZZEtD13py/EPba2evsS6WcKZ5orVMj9pWHEGEHmLu2h3PFq",
RedirectURIs: []string{"http://localhost:9999/auth/callback"},
ResponseTypes: []domain.OIDCResponseType{0},
@ -160,7 +194,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx
InstanceID: "239520764275982338",
AppID: "239520764276441090",
State: domain.AppStateActive,
ClientID: "239520764779364354@zitadel",
ClientID: "239520764779364354",
HashedSecret: "",
RedirectURIs: []string{
"http://test2-qucuh5.localhost:9000/ui/console/auth/callback",

View File

@ -7,13 +7,32 @@ import (
"github.com/zitadel/zitadel/internal/repository/project"
)
const (
fieldsProjectGrant = "project_grant_fields"
fieldsOrgDomainVerified = "org_domain_verified_fields"
)
func newFillProjectGrantFields(config handler.Config) *handler.FieldHandler {
return handler.NewFieldHandler(
&config,
"project_grant_fields",
fieldsProjectGrant,
map[eventstore.AggregateType][]eventstore.EventType{
org.AggregateType: nil,
project.AggregateType: nil,
},
)
}
func newFillOrgDomainVerifiedFields(config handler.Config) *handler.FieldHandler {
return handler.NewFieldHandler(
&config,
fieldsOrgDomainVerified,
map[eventstore.AggregateType][]eventstore.EventType{
org.AggregateType: {
org.OrgDomainAddedEventType,
org.OrgDomainVerifiedEventType,
org.OrgDomainRemovedEventType,
},
},
)
}

View File

@ -78,7 +78,8 @@ var (
ExecutionProjection *handler.Handler
UserSchemaProjection *handler.Handler
ProjectGrantFields *handler.FieldHandler
ProjectGrantFields *handler.FieldHandler
OrgDomainVerifiedFields *handler.FieldHandler
)
type projection interface {
@ -161,7 +162,8 @@ func Create(ctx context.Context, sqlClient *database.DB, es handler.EventStore,
ExecutionProjection = newExecutionProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["executions"]))
UserSchemaProjection = newUserSchemaProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["user_schemas"]))
ProjectGrantFields = newFillProjectGrantFields(applyCustomConfig(projectionConfig, config.Customizations["project_grant_fields"]))
ProjectGrantFields = newFillProjectGrantFields(applyCustomConfig(projectionConfig, config.Customizations[fieldsProjectGrant]))
OrgDomainVerifiedFields = newFillOrgDomainVerifiedFields(applyCustomConfig(projectionConfig, config.Customizations[fieldsOrgDomainVerified]))
newProjectionsList()
return nil

View File

@ -2,7 +2,7 @@
"instance_id": "230690539048009730",
"app_id": "236647088211886082",
"state": 1,
"client_id": "236647088211951618@tests",
"client_id": "236647088211951618",
"client_secret": null,
"redirect_uris": ["http://localhost:9999/auth/callback"],
"response_types": [0],

View File

@ -2,7 +2,7 @@
"instance_id": "239520764275982338",
"app_id": "239520764276441090",
"state": 1,
"client_id": "239520764779364354@zitadel",
"client_id": "239520764779364354",
"client_secret": null,
"redirect_uris": [
"http://test2-qucuh5.localhost:9000/ui/console/auth/callback",

View File

@ -2,7 +2,7 @@
"instance_id": "230690539048009730",
"app_id": "236646457053020162",
"state": 1,
"client_id": "236646457053085698@tests",
"client_id": "236646457053085698",
"client_secret": null,
"redirect_uris": ["http://localhost:9999/auth/callback"],
"response_types": [0],

View File

@ -0,0 +1,28 @@
{
"instance_id": "230690539048009730",
"app_id": "236646457053020162",
"state": 1,
"client_id": "236646457053085698@tests",
"client_secret": null,
"redirect_uris": ["http://localhost:9999/auth/callback"],
"response_types": [0],
"grant_types": [0],
"application_type": 0,
"auth_method_type": 2,
"post_logout_redirect_uris": null,
"is_dev_mode": true,
"access_token_type": 0,
"access_token_role_assertion": false,
"id_token_role_assertion": false,
"id_token_userinfo_assertion": false,
"clock_skew": 0,
"additional_origins": null,
"project_id": "236645808328409090",
"project_role_assertion": true,
"project_role_keys": ["role1", "role2"],
"public_keys": null,
"settings": {
"access_token_lifetime": 43200000000000,
"id_token_lifetime": 43200000000000
}
}

View File

@ -2,7 +2,7 @@
"instance_id": "230690539048009730",
"app_id": "236646858984783874",
"state": 1,
"client_id": "236646858984849410@tests",
"client_id": "236646858984849410",
"client_secret": "$2a$14$OzZ0XEZZEtD13py/EPba2evsS6WcKZ5orVMj9pWHEGEHmLu2h3PFq",
"redirect_uris": ["http://localhost:9999/auth/callback"],
"response_types": [0],

View File

@ -18,6 +18,10 @@ const (
OrgDomainVerifiedEventType = domainEventPrefix + "verified"
OrgDomainPrimarySetEventType = domainEventPrefix + "primary.set"
OrgDomainRemovedEventType = domainEventPrefix + "removed"
OrgDomainSearchType = "org_domain"
OrgDomainVerifiedSearchField = "verified"
OrgDomainObjectRevision = uint8(1)
)
func NewAddOrgDomainUniqueConstraint(orgDomain string) *eventstore.UniqueConstraint {
@ -47,6 +51,28 @@ func (e *DomainAddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
}
func (e *DomainAddedEvent) Fields() []*eventstore.FieldOperation {
return []*eventstore.FieldOperation{
eventstore.SetField(
e.Aggregate(),
domainSearchObject(e.Domain),
OrgDomainVerifiedSearchField,
&eventstore.Value{
Value: false,
ShouldIndex: false,
},
eventstore.FieldTypeInstanceID,
eventstore.FieldTypeResourceOwner,
eventstore.FieldTypeAggregateType,
eventstore.FieldTypeAggregateID,
eventstore.FieldTypeObjectType,
eventstore.FieldTypeObjectID,
eventstore.FieldTypeFieldName,
),
}
}
func NewDomainAddedEvent(ctx context.Context, aggregate *eventstore.Aggregate, domain string) *DomainAddedEvent {
return &DomainAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@ -167,6 +193,28 @@ func (e *DomainVerifiedEvent) UniqueConstraints() []*eventstore.UniqueConstraint
return []*eventstore.UniqueConstraint{NewAddOrgDomainUniqueConstraint(e.Domain)}
}
func (e *DomainVerifiedEvent) Fields() []*eventstore.FieldOperation {
return []*eventstore.FieldOperation{
eventstore.SetField(
e.Aggregate(),
domainSearchObject(e.Domain),
OrgDomainVerifiedSearchField,
&eventstore.Value{
Value: true,
ShouldIndex: false,
},
eventstore.FieldTypeInstanceID,
eventstore.FieldTypeResourceOwner,
eventstore.FieldTypeAggregateType,
eventstore.FieldTypeAggregateID,
eventstore.FieldTypeObjectType,
eventstore.FieldTypeObjectID,
eventstore.FieldTypeFieldName,
),
}
}
func NewDomainVerifiedEvent(ctx context.Context, aggregate *eventstore.Aggregate, domain string) *DomainVerifiedEvent {
return &DomainVerifiedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@ -245,6 +293,28 @@ func (e *DomainRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint
return []*eventstore.UniqueConstraint{NewRemoveOrgDomainUniqueConstraint(e.Domain)}
}
func (e *DomainRemovedEvent) Fields() []*eventstore.FieldOperation {
return []*eventstore.FieldOperation{
eventstore.SetField(
e.Aggregate(),
domainSearchObject(e.Domain),
OrgDomainVerifiedSearchField,
&eventstore.Value{
Value: false,
ShouldIndex: false,
},
eventstore.FieldTypeInstanceID,
eventstore.FieldTypeResourceOwner,
eventstore.FieldTypeAggregateType,
eventstore.FieldTypeAggregateID,
eventstore.FieldTypeObjectType,
eventstore.FieldTypeObjectID,
eventstore.FieldTypeFieldName,
),
}
}
func NewDomainRemovedEvent(ctx context.Context, aggregate *eventstore.Aggregate, domain string, verified bool) *DomainRemovedEvent {
return &DomainRemovedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@ -268,3 +338,11 @@ func DomainRemovedEventMapper(event eventstore.Event) (eventstore.Event, error)
return orgDomainRemoved, nil
}
func domainSearchObject(domain string) eventstore.Object {
return eventstore.Object{
Type: OrgDomainSearchType,
ID: domain,
Revision: OrgDomainObjectRevision,
}
}

View File

@ -8,7 +8,7 @@ import (
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
const (
UniqueRoleType = "project_role"
roleEventTypePrefix = projectEventTypePrefix + "role."
RoleAddedType = roleEventTypePrefix + "added"

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.2
// protoc (unknown)
// source: zitadel/protoc_gen_zitadel/v2/options.proto
@ -250,7 +250,7 @@ func file_zitadel_protoc_gen_zitadel_v2_options_proto_rawDescGZIP() []byte {
}
var file_zitadel_protoc_gen_zitadel_v2_options_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_zitadel_protoc_gen_zitadel_v2_options_proto_goTypes = []interface{}{
var file_zitadel_protoc_gen_zitadel_v2_options_proto_goTypes = []any{
(*Options)(nil), // 0: zitadel.protoc_gen_zitadel.v2.Options
(*AuthOption)(nil), // 1: zitadel.protoc_gen_zitadel.v2.AuthOption
(*CustomHTTPResponse)(nil), // 2: zitadel.protoc_gen_zitadel.v2.CustomHTTPResponse
@ -274,7 +274,7 @@ func file_zitadel_protoc_gen_zitadel_v2_options_proto_init() {
return
}
if !protoimpl.UnsafeEnabled {
file_zitadel_protoc_gen_zitadel_v2_options_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
file_zitadel_protoc_gen_zitadel_v2_options_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*Options); i {
case 0:
return &v.state
@ -286,7 +286,7 @@ func file_zitadel_protoc_gen_zitadel_v2_options_proto_init() {
return nil
}
}
file_zitadel_protoc_gen_zitadel_v2_options_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
file_zitadel_protoc_gen_zitadel_v2_options_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*AuthOption); i {
case 0:
return &v.state
@ -298,7 +298,7 @@ func file_zitadel_protoc_gen_zitadel_v2_options_proto_init() {
return nil
}
}
file_zitadel_protoc_gen_zitadel_v2_options_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
file_zitadel_protoc_gen_zitadel_v2_options_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*CustomHTTPResponse); i {
case 0:
return &v.state

View File

@ -59,4 +59,10 @@ enum ImprovedPerformance {
// correctnes of data.
IMPROVED_PERFORMANCE_PROJECT_GRANT = 2;
IMPROVED_PERFORMANCE_PROJECT = 3;
IMPROVED_PERFORMANCE_USER_GRANT = 4;
// Improve performance on write side when
// users are checked against verified domains
// from other organizations.
IMPROVED_PERFORMANCE_ORG_DOMAIN_VERIFIED = 5;
}