feat(login): additionally use email/phone for authentication (#4563)

* feat: add ability to disable login by email and phone

* feat: check login by email and phone

* fix: set verified email / phone correctly on notify users

* update projection version

* fix merge

* fix email/phone verified reduce tests

* fix user tests

* loginname check

* cleanup

* fix: update user projection version to handle fixed statement
This commit is contained in:
Livio Spring 2022-10-17 21:19:15 +02:00 committed by GitHub
parent 9ae58b62fd
commit b0b1e94090
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 1245 additions and 768 deletions

View File

@ -329,6 +329,50 @@
</mat-checkbox> </mat-checkbox>
</div> </div>
<div class="login-policy-row">
<mat-checkbox
class="login-policy-toggle"
color="primary"
ngDefaultControl
[(ngModel)]="loginData.disableLoginWithEmail"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.DISABLELOGINWITHEMAIL' | translate }}
</mat-checkbox>
</div>
<div class="login-policy-row">
<mat-checkbox
class="login-policy-toggle"
color="primary"
ngDefaultControl
[(ngModel)]="loginData.disableLoginWithPhone"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.DISABLELOGINWITHPHONE' | translate }}
</mat-checkbox>
</div>
<div class="login-policy-row"> <div class="login-policy-row">
<cnsl-form-field class="form-field" label="Access Code" required="true"> <cnsl-form-field class="form-field" label="Access Code" required="true">
<cnsl-label>{{ 'POLICY.DATA.DEFAULTREDIRECTURI' | translate }}</cnsl-label> <cnsl-label>{{ 'POLICY.DATA.DEFAULTREDIRECTURI' | translate }}</cnsl-label>

View File

@ -155,6 +155,8 @@ export class LoginPolicyComponent implements OnInit {
mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset); mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
mgmtreq.setMultiFactorsList(this.loginData.multiFactorsList); mgmtreq.setMultiFactorsList(this.loginData.multiFactorsList);
mgmtreq.setSecondFactorsList(this.loginData.secondFactorsList); mgmtreq.setSecondFactorsList(this.loginData.secondFactorsList);
mgmtreq.setDisableLoginWithEmail(this.loginData.disableLoginWithEmail);
mgmtreq.setDisableLoginWithPhone(this.loginData.disableLoginWithPhone);
const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60); const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60);
mgmtreq.setPasswordCheckLifetime(pcl); mgmtreq.setPasswordCheckLifetime(pcl);
@ -184,6 +186,8 @@ export class LoginPolicyComponent implements OnInit {
mgmtreq.setForceMfa(this.loginData.forceMfa); mgmtreq.setForceMfa(this.loginData.forceMfa);
mgmtreq.setPasswordlessType(this.loginData.passwordlessType); mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset); mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
mgmtreq.setDisableLoginWithEmail(this.loginData.disableLoginWithEmail);
mgmtreq.setDisableLoginWithPhone(this.loginData.disableLoginWithPhone);
const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60); const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60);
mgmtreq.setPasswordCheckLifetime(pcl); mgmtreq.setPasswordCheckLifetime(pcl);
@ -214,6 +218,8 @@ export class LoginPolicyComponent implements OnInit {
adminreq.setForceMfa(this.loginData.forceMfa); adminreq.setForceMfa(this.loginData.forceMfa);
adminreq.setPasswordlessType(this.loginData.passwordlessType); adminreq.setPasswordlessType(this.loginData.passwordlessType);
adminreq.setHidePasswordReset(this.loginData.hidePasswordReset); adminreq.setHidePasswordReset(this.loginData.hidePasswordReset);
adminreq.setDisableLoginWithEmail(this.loginData.disableLoginWithEmail);
adminreq.setDisableLoginWithPhone(this.loginData.disableLoginWithPhone);
const admin_pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60); const admin_pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60);
adminreq.setPasswordCheckLifetime(admin_pcl); adminreq.setPasswordCheckLifetime(admin_pcl);

View File

@ -1173,6 +1173,8 @@
"IGNOREUNKNOWNUSERNAMES_DESC": "Ist die Option gewählt, wird der Passwort Schritt im Login auch angezeigt wenn der User nicht gefunden wurde. Dem Benutzer wird auf bei der Passwortprüfung nicht angezeigt ob der Username oder das Passwort falsch war.", "IGNOREUNKNOWNUSERNAMES_DESC": "Ist die Option gewählt, wird der Passwort Schritt im Login auch angezeigt wenn der User nicht gefunden wurde. Dem Benutzer wird auf bei der Passwortprüfung nicht angezeigt ob der Username oder das Passwort falsch war.",
"ALLOWDOMAINDISCOVERY": "Domänenentdeckung erlauben", "ALLOWDOMAINDISCOVERY": "Domänenentdeckung erlauben",
"ALLOWDOMAINDISCOVERY_DESC": "Ist die Option gewählt, wird die Endung (@domain.com) eines unbekannten Benutzernamens im Login mit den Organisationsdomänen verglichen. Bei Übereinstimmung wird der Benutzer auf die Registrierung dieser Organisation weitergeleitet.", "ALLOWDOMAINDISCOVERY_DESC": "Ist die Option gewählt, wird die Endung (@domain.com) eines unbekannten Benutzernamens im Login mit den Organisationsdomänen verglichen. Bei Übereinstimmung wird der Benutzer auf die Registrierung dieser Organisation weitergeleitet.",
"DISABLELOGINWITHEMAIL": "Login mittels E-Mailadresse deaktivieren",
"DISABLELOGINWITHPHONE": "Login mittels Telefonnummer deaktivieren",
"DEFAULTREDIRECTURI": "Default Redirect URI", "DEFAULTREDIRECTURI": "Default Redirect URI",
"DEFAULTREDIRECTURI_DESC": "Definiert, wohin der Benutzer umgeleitet wird, wenn die Anmeldung ohne App-Kontext gestartet wurde (z. B. von Mail)", "DEFAULTREDIRECTURI_DESC": "Definiert, wohin der Benutzer umgeleitet wird, wenn die Anmeldung ohne App-Kontext gestartet wurde (z. B. von Mail)",
"ERRORMSGPOPUP": "Fehler als Dialog Fenster", "ERRORMSGPOPUP": "Fehler als Dialog Fenster",

View File

@ -1173,6 +1173,8 @@
"IGNOREUNKNOWNUSERNAMES_DESC": "If the option is selected, the password screen will be displayed in the login process even if the user was not found. The error on the password check will not disclose if the username or password was wrong.", "IGNOREUNKNOWNUSERNAMES_DESC": "If the option is selected, the password screen will be displayed in the login process even if the user was not found. The error on the password check will not disclose if the username or password was wrong.",
"ALLOWDOMAINDISCOVERY": "Domain discovery allowed", "ALLOWDOMAINDISCOVERY": "Domain discovery allowed",
"ALLOWDOMAINDISCOVERY_DESC": "If the option is selected, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the organization domains and will redirect to the registration of that organisation on success.", "ALLOWDOMAINDISCOVERY_DESC": "If the option is selected, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the organization domains and will redirect to the registration of that organisation on success.",
"DISABLELOGINWITHEMAIL": "Disable login with email address",
"DISABLELOGINWITHPHONE": "Disable login with phone number",
"DEFAULTREDIRECTURI": "Default Redirect URI", "DEFAULTREDIRECTURI": "Default Redirect URI",
"DEFAULTREDIRECTURI_DESC": "Defines where the user will be redirected to if the login has started without an app context (e.g. from mail)", "DEFAULTREDIRECTURI_DESC": "Defines where the user will be redirected to if the login has started without an app context (e.g. from mail)",
"ERRORMSGPOPUP": "Show Error in Dialog", "ERRORMSGPOPUP": "Show Error in Dialog",

View File

@ -1173,6 +1173,8 @@
"IGNOREUNKNOWNUSERNAMES_DESC": "Si l'option est sélectionnée, l'écran du mot de passe sera affiché dans le processus de connexion même si l'utilisateur n'a pas été trouvé. L'erreur sur la vérification du mot de passe ne révélera pas si le nom d'utilisateur ou le mot de passe était erroné.", "IGNOREUNKNOWNUSERNAMES_DESC": "Si l'option est sélectionnée, l'écran du mot de passe sera affiché dans le processus de connexion même si l'utilisateur n'a pas été trouvé. L'erreur sur la vérification du mot de passe ne révélera pas si le nom d'utilisateur ou le mot de passe était erroné.",
"ALLOWDOMAINDISCOVERY": "Découverte du domaine autorisée", "ALLOWDOMAINDISCOVERY": "Découverte du domaine autorisée",
"ALLOWDOMAINDISCOVERY_DESC": "Si l'option est sélectionnée, le suffixe (@domain.com) d'un nom d'utilisateur inconnu saisi sur l'écran de connexion sera comparé aux domaines organisation et redirigera vers l'enregistrement de cette organisation en cas de succès.", "ALLOWDOMAINDISCOVERY_DESC": "Si l'option est sélectionnée, le suffixe (@domain.com) d'un nom d'utilisateur inconnu saisi sur l'écran de connexion sera comparé aux domaines organisation et redirigera vers l'enregistrement de cette organisation en cas de succès.",
"DISABLELOGINWITHEMAIL": "Désactiver la connexion avec l'adresse e-mail",
"DISABLELOGINWITHPHONE": "Désactiver la connexion avec le numéro de téléphone",
"DEFAULTREDIRECTURI": "URI de redirection par défaut", "DEFAULTREDIRECTURI": "URI de redirection par défaut",
"DEFAULTREDIRECTURI_DESC": "Définit l'endroit où l'utilisateur sera redirigé si la connexion a commencé sans contexte d'application (par exemple, à partir du courrier électronique).", "DEFAULTREDIRECTURI_DESC": "Définit l'endroit où l'utilisateur sera redirigé si la connexion a commencé sans contexte d'application (par exemple, à partir du courrier électronique).",
"ERRORMSGPOPUP": "Afficher l'erreur dans la boîte de dialogue", "ERRORMSGPOPUP": "Afficher l'erreur dans la boîte de dialogue",

View File

@ -1173,6 +1173,8 @@
"IGNOREUNKNOWNUSERNAMES_DESC": "Se l'opzione \u00e8 selezionata, l'inserimento della password viene mostrato anche se nessun utente è stato trovato. Nota che dopo il controllo della password, non viene mostrato se il nome utente o la password erano errati.", "IGNOREUNKNOWNUSERNAMES_DESC": "Se l'opzione \u00e8 selezionata, l'inserimento della password viene mostrato anche se nessun utente è stato trovato. Nota che dopo il controllo della password, non viene mostrato se il nome utente o la password erano errati.",
"ALLOWDOMAINDISCOVERY": "Scoperta del dominio consentita", "ALLOWDOMAINDISCOVERY": "Scoperta del dominio consentita",
"ALLOWDOMAINDISCOVERY_DESC": "Se l'opzione è selezionata, il suffisso (@domain.com) di un nome utente sconosciuto inserito nel login verrà confrontato con i domini organizzazione e, in caso di successo, verrà reindirizzato alla registrazione di tale organizzazione", "ALLOWDOMAINDISCOVERY_DESC": "Se l'opzione è selezionata, il suffisso (@domain.com) di un nome utente sconosciuto inserito nel login verrà confrontato con i domini organizzazione e, in caso di successo, verrà reindirizzato alla registrazione di tale organizzazione",
"DISABLELOGINWITHEMAIL": "Disabilita il login con l'indirizzo e-mail",
"DISABLELOGINWITHPHONE": "Disabilita l'accesso con il numero di telefono",
"DEFAULTREDIRECTURI": "Default Redirect URI", "DEFAULTREDIRECTURI": "Default Redirect URI",
"DEFAULTREDIRECTURI_DESC": "Definisce dove verrà reindirizzato l'utente se l'accesso è stato avviato senza un contesto dell'app (ad es. dall' email)", "DEFAULTREDIRECTURI_DESC": "Definisce dove verrà reindirizzato l'utente se l'accesso è stato avviato senza un contesto dell'app (ad es. dall' email)",
"ERRORMSGPOPUP": "Mostra l'errore nella finestra di dialogo", "ERRORMSGPOPUP": "Mostra l'errore nella finestra di dialogo",

View File

@ -1172,6 +1172,8 @@
"IGNOREUNKNOWNUSERNAMES_DESC": "如果选择该选项,即使未找到用户,登录过程中也会显示密码屏幕。如果用户名或密码错误,密码检查的错误不会透露。", "IGNOREUNKNOWNUSERNAMES_DESC": "如果选择该选项,即使未找到用户,登录过程中也会显示密码屏幕。如果用户名或密码错误,密码检查的错误不会透露。",
"ALLOWDOMAINDISCOVERY": "允许域名发现", "ALLOWDOMAINDISCOVERY": "允许域名发现",
"ALLOWDOMAINDISCOVERY_DESC": "如果选择该选项,在登录屏幕上输入的未知用户名的后缀(@domain.com将与组织的域名进行匹配成功后将重定向到组织的注册。", "ALLOWDOMAINDISCOVERY_DESC": "如果选择该选项,在登录屏幕上输入的未知用户名的后缀(@domain.com将与组织的域名进行匹配成功后将重定向到组织的注册。",
"DISABLELOGINWITHEMAIL": "禁止用电子邮件地址登录",
"DISABLELOGINWITHPHONE": "禁止用电话号码登录",
"DEFAULTREDIRECTURI": "默认重定向 URI", "DEFAULTREDIRECTURI": "默认重定向 URI",
"DEFAULTREDIRECTURI_DESC": "定义如果在没有应用程序上下文的情况下开始登录(例如来自邮件),用户将被重定向到哪里。", "DEFAULTREDIRECTURI_DESC": "定义如果在没有应用程序上下文的情况下开始登录(例如来自邮件),用户将被重定向到哪里。",
"ERRORMSGPOPUP": "在对话框中显示错误", "ERRORMSGPOPUP": "在对话框中显示错误",

View File

@ -4360,6 +4360,8 @@ this is en empty request
| second_factor_check_lifetime | google.protobuf.Duration | - | | | second_factor_check_lifetime | google.protobuf.Duration | - | |
| multi_factor_check_lifetime | google.protobuf.Duration | - | | | multi_factor_check_lifetime | google.protobuf.Duration | - | |
| allow_domain_discovery | bool | If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success. | | | allow_domain_discovery | bool | If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success. | |
| disable_login_with_email | bool | - | |
| disable_login_with_phone | bool | - | |

View File

@ -3184,6 +3184,8 @@ This is an empty request
| multi_factors | repeated zitadel.policy.v1.MultiFactorType | - | | | multi_factors | repeated zitadel.policy.v1.MultiFactorType | - | |
| idps | repeated AddCustomLoginPolicyRequest.IDP | - | | | idps | repeated AddCustomLoginPolicyRequest.IDP | - | |
| allow_domain_discovery | bool | If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success. | | | allow_domain_discovery | bool | If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success. | |
| disable_login_with_email | bool | - | |
| disable_login_with_phone | bool | - | |
@ -8171,6 +8173,8 @@ This is an empty request
| second_factor_check_lifetime | google.protobuf.Duration | - | | | second_factor_check_lifetime | google.protobuf.Duration | - | |
| multi_factor_check_lifetime | google.protobuf.Duration | - | | | multi_factor_check_lifetime | google.protobuf.Duration | - | |
| allow_domain_discovery | bool | If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success. | | | allow_domain_discovery | bool | If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success. | |
| disable_login_with_email | bool | - | |
| disable_login_with_phone | bool | - | |

View File

@ -89,6 +89,8 @@ title: zitadel/policy.proto
| multi_factors | repeated MultiFactorType | - | | | multi_factors | repeated MultiFactorType | - | |
| idps | repeated zitadel.idp.v1.IDPLoginPolicyLink | - | | | idps | repeated zitadel.idp.v1.IDPLoginPolicyLink | - | |
| allow_domain_discovery | bool | If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success. | | | allow_domain_discovery | bool | If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success. | |
| disable_login_with_email | bool | - | |
| disable_login_with_phone | bool | - | |

View File

@ -442,7 +442,7 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm
} }
} }
if org.LoginPolicy != nil { if org.LoginPolicy != nil {
_, err = s.command.AddLoginPolicy(ctx, org.GetOrgId(), management.AddLoginPolicyToDomain(org.GetLoginPolicy())) _, err = s.command.AddLoginPolicy(ctx, org.GetOrgId(), management.AddLoginPolicyToCommand(org.GetLoginPolicy()))
if err != nil { if err != nil {
errors = append(errors, &admin_pb.ImportDataError{Type: "login_policy", Id: org.GetOrgId(), Message: err.Error()}) errors = append(errors, &admin_pb.ImportDataError{Type: "login_policy", Id: org.GetOrgId(), Message: err.Error()})
} }

View File

@ -22,14 +22,14 @@ func (s *Server) GetLoginPolicy(ctx context.Context, _ *admin_pb.GetLoginPolicyR
} }
func (s *Server) UpdateLoginPolicy(ctx context.Context, p *admin_pb.UpdateLoginPolicyRequest) (*admin_pb.UpdateLoginPolicyResponse, error) { func (s *Server) UpdateLoginPolicy(ctx context.Context, p *admin_pb.UpdateLoginPolicyRequest) (*admin_pb.UpdateLoginPolicyResponse, error) {
policy, err := s.command.ChangeDefaultLoginPolicy(ctx, updateLoginPolicyToDomain(p)) policy, err := s.command.ChangeDefaultLoginPolicy(ctx, updateLoginPolicyToCommand(p))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &admin_pb.UpdateLoginPolicyResponse{ return &admin_pb.UpdateLoginPolicyResponse{
Details: object.ChangeToDetailsPb( Details: object.ChangeToDetailsPb(
policy.Sequence, policy.Sequence,
policy.ChangeDate, policy.EventDate,
policy.ResourceOwner, policy.ResourceOwner,
), ),
}, nil }, nil

View File

@ -3,13 +3,13 @@ package admin
import ( import (
"github.com/zitadel/zitadel/internal/api/grpc/object" "github.com/zitadel/zitadel/internal/api/grpc/object"
policy_grpc "github.com/zitadel/zitadel/internal/api/grpc/policy" policy_grpc "github.com/zitadel/zitadel/internal/api/grpc/policy"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
) )
func updateLoginPolicyToDomain(p *admin_pb.UpdateLoginPolicyRequest) *domain.LoginPolicy { func updateLoginPolicyToCommand(p *admin_pb.UpdateLoginPolicyRequest) *command.ChangeLoginPolicy {
return &domain.LoginPolicy{ return &command.ChangeLoginPolicy{
AllowUsernamePassword: p.AllowUsernamePassword, AllowUsernamePassword: p.AllowUsernamePassword,
AllowRegister: p.AllowRegister, AllowRegister: p.AllowRegister,
AllowExternalIDP: p.AllowExternalIdp, AllowExternalIDP: p.AllowExternalIdp,
@ -18,6 +18,8 @@ func updateLoginPolicyToDomain(p *admin_pb.UpdateLoginPolicyRequest) *domain.Log
HidePasswordReset: p.HidePasswordReset, HidePasswordReset: p.HidePasswordReset,
IgnoreUnknownUsernames: p.IgnoreUnknownUsernames, IgnoreUnknownUsernames: p.IgnoreUnknownUsernames,
AllowDomainDiscovery: p.AllowDomainDiscovery, AllowDomainDiscovery: p.AllowDomainDiscovery,
DisableLoginWithEmail: p.DisableLoginWithEmail,
DisableLoginWithPhone: p.DisableLoginWithPhone,
DefaultRedirectURI: p.DefaultRedirectUri, DefaultRedirectURI: p.DefaultRedirectUri,
PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(), PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(),
ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(), ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(),

View File

@ -30,28 +30,28 @@ func (s *Server) GetDefaultLoginPolicy(ctx context.Context, req *mgmt_pb.GetDefa
} }
func (s *Server) AddCustomLoginPolicy(ctx context.Context, req *mgmt_pb.AddCustomLoginPolicyRequest) (*mgmt_pb.AddCustomLoginPolicyResponse, error) { func (s *Server) AddCustomLoginPolicy(ctx context.Context, req *mgmt_pb.AddCustomLoginPolicyRequest) (*mgmt_pb.AddCustomLoginPolicyResponse, error) {
policy, err := s.command.AddLoginPolicy(ctx, authz.GetCtxData(ctx).OrgID, AddLoginPolicyToDomain(req)) policy, err := s.command.AddLoginPolicy(ctx, authz.GetCtxData(ctx).OrgID, AddLoginPolicyToCommand(req))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &mgmt_pb.AddCustomLoginPolicyResponse{ return &mgmt_pb.AddCustomLoginPolicyResponse{
Details: object.AddToDetailsPb( Details: object.AddToDetailsPb(
policy.Sequence, policy.Sequence,
policy.ChangeDate, policy.EventDate,
policy.ResourceOwner, policy.ResourceOwner,
), ),
}, nil }, nil
} }
func (s *Server) UpdateCustomLoginPolicy(ctx context.Context, req *mgmt_pb.UpdateCustomLoginPolicyRequest) (*mgmt_pb.UpdateCustomLoginPolicyResponse, error) { func (s *Server) UpdateCustomLoginPolicy(ctx context.Context, req *mgmt_pb.UpdateCustomLoginPolicyRequest) (*mgmt_pb.UpdateCustomLoginPolicyResponse, error) {
policy, err := s.command.ChangeLoginPolicy(ctx, authz.GetCtxData(ctx).OrgID, updateLoginPolicyToDomain(req)) policy, err := s.command.ChangeLoginPolicy(ctx, authz.GetCtxData(ctx).OrgID, updateLoginPolicyToCommand(req))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &mgmt_pb.UpdateCustomLoginPolicyResponse{ return &mgmt_pb.UpdateCustomLoginPolicyResponse{
Details: object.ChangeToDetailsPb( Details: object.ChangeToDetailsPb(
policy.Sequence, policy.Sequence,
policy.ChangeDate, policy.EventDate,
policy.ResourceOwner, policy.ResourceOwner,
), ),
}, nil }, nil

View File

@ -4,13 +4,13 @@ import (
idp_grpc "github.com/zitadel/zitadel/internal/api/grpc/idp" idp_grpc "github.com/zitadel/zitadel/internal/api/grpc/idp"
"github.com/zitadel/zitadel/internal/api/grpc/object" "github.com/zitadel/zitadel/internal/api/grpc/object"
policy_grpc "github.com/zitadel/zitadel/internal/api/grpc/policy" policy_grpc "github.com/zitadel/zitadel/internal/api/grpc/policy"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management" mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
) )
func AddLoginPolicyToDomain(p *mgmt_pb.AddCustomLoginPolicyRequest) *domain.LoginPolicy { func AddLoginPolicyToCommand(p *mgmt_pb.AddCustomLoginPolicyRequest) *command.AddLoginPolicy {
return &domain.LoginPolicy{ return &command.AddLoginPolicy{
AllowUsernamePassword: p.AllowUsernamePassword, AllowUsernamePassword: p.AllowUsernamePassword,
AllowRegister: p.AllowRegister, AllowRegister: p.AllowRegister,
AllowExternalIDP: p.AllowExternalIdp, AllowExternalIDP: p.AllowExternalIdp,
@ -27,22 +27,24 @@ func AddLoginPolicyToDomain(p *mgmt_pb.AddCustomLoginPolicyRequest) *domain.Logi
MultiFactorCheckLifetime: p.MultiFactorCheckLifetime.AsDuration(), MultiFactorCheckLifetime: p.MultiFactorCheckLifetime.AsDuration(),
SecondFactors: policy_grpc.SecondFactorsTypesToDomain(p.SecondFactors), SecondFactors: policy_grpc.SecondFactorsTypesToDomain(p.SecondFactors),
MultiFactors: policy_grpc.MultiFactorsTypesToDomain(p.MultiFactors), MultiFactors: policy_grpc.MultiFactorsTypesToDomain(p.MultiFactors),
IDPProviders: addLoginPolicyIDPsToDomain(p.Idps), IDPProviders: addLoginPolicyIDPsToCommand(p.Idps),
DisableLoginWithEmail: p.DisableLoginWithEmail,
DisableLoginWithPhone: p.DisableLoginWithPhone,
} }
} }
func addLoginPolicyIDPsToDomain(idps []*mgmt_pb.AddCustomLoginPolicyRequest_IDP) []*domain.IDPProvider { func addLoginPolicyIDPsToCommand(idps []*mgmt_pb.AddCustomLoginPolicyRequest_IDP) []*command.AddLoginPolicyIDP {
providers := make([]*domain.IDPProvider, len(idps)) providers := make([]*command.AddLoginPolicyIDP, len(idps))
for i, idp := range idps { for i, idp := range idps {
providers[i] = &domain.IDPProvider{ providers[i] = &command.AddLoginPolicyIDP{
Type: idp_grpc.IDPProviderTypeFromPb(idp.OwnerType), Type: idp_grpc.IDPProviderTypeFromPb(idp.OwnerType),
IDPConfigID: idp.IdpId, ConfigID: idp.IdpId,
} }
} }
return providers return providers
} }
func updateLoginPolicyToDomain(p *mgmt_pb.UpdateCustomLoginPolicyRequest) *domain.LoginPolicy { func updateLoginPolicyToCommand(p *mgmt_pb.UpdateCustomLoginPolicyRequest) *command.ChangeLoginPolicy {
return &domain.LoginPolicy{ return &command.ChangeLoginPolicy{
AllowUsernamePassword: p.AllowUsernamePassword, AllowUsernamePassword: p.AllowUsernamePassword,
AllowRegister: p.AllowRegister, AllowRegister: p.AllowRegister,
AllowExternalIDP: p.AllowExternalIdp, AllowExternalIDP: p.AllowExternalIdp,
@ -51,6 +53,8 @@ func updateLoginPolicyToDomain(p *mgmt_pb.UpdateCustomLoginPolicyRequest) *domai
HidePasswordReset: p.HidePasswordReset, HidePasswordReset: p.HidePasswordReset,
IgnoreUnknownUsernames: p.IgnoreUnknownUsernames, IgnoreUnknownUsernames: p.IgnoreUnknownUsernames,
AllowDomainDiscovery: p.AllowDomainDiscovery, AllowDomainDiscovery: p.AllowDomainDiscovery,
DisableLoginWithEmail: p.DisableLoginWithEmail,
DisableLoginWithPhone: p.DisableLoginWithPhone,
DefaultRedirectURI: p.DefaultRedirectUri, DefaultRedirectURI: p.DefaultRedirectUri,
PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(), PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(),
ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(), ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(),

View File

@ -22,6 +22,8 @@ func ModelLoginPolicyToPb(policy *query.LoginPolicy) *policy_pb.LoginPolicy {
HidePasswordReset: policy.HidePasswordReset, HidePasswordReset: policy.HidePasswordReset,
IgnoreUnknownUsernames: policy.IgnoreUnknownUsernames, IgnoreUnknownUsernames: policy.IgnoreUnknownUsernames,
AllowDomainDiscovery: policy.AllowDomainDiscovery, AllowDomainDiscovery: policy.AllowDomainDiscovery,
DisableLoginWithEmail: policy.DisableLoginWithEmail,
DisableLoginWithPhone: policy.DisableLoginWithPhone,
DefaultRedirectUri: policy.DefaultRedirectURI, DefaultRedirectUri: policy.DefaultRedirectURI,
PasswordCheckLifetime: durationpb.New(policy.PasswordCheckLifetime), PasswordCheckLifetime: durationpb.New(policy.PasswordCheckLifetime),
ExternalLoginCheckLifetime: durationpb.New(policy.ExternalLoginCheckLifetime), ExternalLoginCheckLifetime: durationpb.New(policy.ExternalLoginCheckLifetime),

View File

@ -641,15 +641,9 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain
preferredLoginName += "@" + request.RequestedPrimaryDomain preferredLoginName += "@" + request.RequestedPrimaryDomain
} }
} }
user, err = repo.View.UserByLoginNameAndResourceOwner(preferredLoginName, request.RequestedOrgID, request.InstanceID) user, err = repo.checkLoginNameInputForResourceOwner(request, preferredLoginName)
} else { } else {
user, err = repo.View.UserByLoginName(loginName, request.InstanceID) user, err = repo.checkLoginNameInput(ctx, request, preferredLoginName)
if err == nil {
err = repo.checkLoginPolicyWithResourceOwner(ctx, request, user)
if err != nil {
return err
}
}
} }
// return any error apart from not found ones directly // return any error apart from not found ones directly
if err != nil && !errors.IsNotFound(err) { if err != nil && !errors.IsNotFound(err) {
@ -720,8 +714,74 @@ func (repo *AuthRequestRepo) checkDomainDiscovery(ctx context.Context, request *
return true return true
} }
func (repo *AuthRequestRepo) checkLoginPolicyWithResourceOwner(ctx context.Context, request *domain.AuthRequest, user *user_view_model.UserView) error { func (repo *AuthRequestRepo) checkLoginNameInput(ctx context.Context, request *domain.AuthRequest, loginNameInput string) (*user_view_model.UserView, error) {
loginPolicy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, user.ResourceOwner) // always check the loginname first
user, err := repo.View.UserByLoginName(loginNameInput, request.InstanceID)
if err == nil {
// and take the user regardless if there would be a user with that email or phone
return user, repo.checkLoginPolicyWithResourceOwner(ctx, request, user.ResourceOwner)
}
user, emailErr := repo.View.UserByEmail(loginNameInput, request.InstanceID)
if emailErr == nil {
// if there was a single user with the specified email
// load and check the login policy
if emailErr = repo.checkLoginPolicyWithResourceOwner(ctx, request, user.ResourceOwner); emailErr != nil {
return nil, emailErr
}
// and in particular if the login with email is possible
// if so take the user (and ignore possible phone matches)
if !request.LoginPolicy.DisableLoginWithEmail {
return user, nil
}
}
user, phoneErr := repo.View.UserByPhone(loginNameInput, request.InstanceID)
if phoneErr == nil {
// if there was a single user with the specified phone
// load and check the login policy
if phoneErr = repo.checkLoginPolicyWithResourceOwner(ctx, request, user.ResourceOwner); phoneErr != nil {
return nil, phoneErr
}
// and in particular if the login with phone is possible
// if so take the user
if !request.LoginPolicy.DisableLoginWithPhone {
return user, nil
}
}
// if we get here the user was not found by loginname
// and either there was no match for email or phone as well, or they have been both disabled
return nil, err
}
func (repo *AuthRequestRepo) checkLoginNameInputForResourceOwner(request *domain.AuthRequest, loginNameInput string) (*user_view_model.UserView, error) {
// always check the loginname first
user, err := repo.View.UserByLoginNameAndResourceOwner(loginNameInput, request.RequestedOrgID, request.InstanceID)
if err == nil {
// and take the user regardless if there would be a user with that email or phone
return user, nil
}
if request.LoginPolicy != nil && !request.LoginPolicy.DisableLoginWithEmail {
// if login by email is allowed and there was a single user with the specified email
// take that user (and ignore possible phone number matches)
user, emailErr := repo.View.UserByEmailAndResourceOwner(loginNameInput, request.RequestedOrgID, request.InstanceID)
if emailErr == nil {
return user, nil
}
}
if request.LoginPolicy != nil && !request.LoginPolicy.DisableLoginWithPhone {
// if login by phone is allowed and there was a single user with the specified phone
// take that user
user, phoneErr := repo.View.UserByPhoneAndResourceOwner(loginNameInput, request.RequestedOrgID, request.InstanceID)
if phoneErr == nil {
return user, nil
}
}
// if we get here the user was not found by loginname
// and either there was no match for email or phone as well or they have been both disabled
return nil, err
}
func (repo *AuthRequestRepo) checkLoginPolicyWithResourceOwner(ctx context.Context, request *domain.AuthRequest, resourceOwner string) error {
loginPolicy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, resourceOwner)
if err != nil { if err != nil {
return err return err
} }
@ -758,11 +818,15 @@ func queryLoginPolicyToDomain(policy *query.LoginPolicy) *domain.LoginPolicy {
PasswordlessType: policy.PasswordlessType, PasswordlessType: policy.PasswordlessType,
HidePasswordReset: policy.HidePasswordReset, HidePasswordReset: policy.HidePasswordReset,
IgnoreUnknownUsernames: policy.IgnoreUnknownUsernames, IgnoreUnknownUsernames: policy.IgnoreUnknownUsernames,
AllowDomainDiscovery: policy.AllowDomainDiscovery,
DefaultRedirectURI: policy.DefaultRedirectURI,
PasswordCheckLifetime: policy.PasswordCheckLifetime, PasswordCheckLifetime: policy.PasswordCheckLifetime,
ExternalLoginCheckLifetime: policy.ExternalLoginCheckLifetime, ExternalLoginCheckLifetime: policy.ExternalLoginCheckLifetime,
MFAInitSkipLifetime: policy.MFAInitSkipLifetime, MFAInitSkipLifetime: policy.MFAInitSkipLifetime,
SecondFactorCheckLifetime: policy.SecondFactorCheckLifetime, SecondFactorCheckLifetime: policy.SecondFactorCheckLifetime,
MultiFactorCheckLifetime: policy.MultiFactorCheckLifetime, MultiFactorCheckLifetime: policy.MultiFactorCheckLifetime,
DisableLoginWithEmail: policy.DisableLoginWithEmail,
DisableLoginWithPhone: policy.DisableLoginWithPhone,
} }
} }

View File

@ -52,10 +52,52 @@ func (v *View) UserByLoginNameAndResourceOwner(loginName, resourceOwner, instanc
return v.userByID(instanceID, loginNameQuery, resourceOwnerQuery) return v.userByID(instanceID, loginNameQuery, resourceOwnerQuery)
} }
func (v *View) UserByEmail(email, instanceID string) (*model.UserView, error) {
emailQuery, err := query.NewUserVerifiedEmailSearchQuery(email, query.TextEquals)
if err != nil {
return nil, err
}
return v.userByID(instanceID, emailQuery)
}
func (v *View) UserByEmailAndResourceOwner(email, resourceOwner, instanceID string) (*model.UserView, error) {
emailQuery, err := query.NewUserVerifiedEmailSearchQuery(email, query.TextEquals)
if err != nil {
return nil, err
}
resourceOwnerQuery, err := query.NewUserResourceOwnerSearchQuery(resourceOwner, query.TextEquals)
if err != nil {
return nil, err
}
return v.userByID(instanceID, emailQuery, resourceOwnerQuery)
}
func (v *View) UserByPhone(phone, instanceID string) (*model.UserView, error) {
phoneQuery, err := query.NewUserVerifiedPhoneSearchQuery(phone, query.TextEquals)
if err != nil {
return nil, err
}
return v.userByID(instanceID, phoneQuery)
}
func (v *View) UserByPhoneAndResourceOwner(phone, resourceOwner, instanceID string) (*model.UserView, error) {
phoneQuery, err := query.NewUserVerifiedPhoneSearchQuery(phone, query.TextEquals)
if err != nil {
return nil, err
}
resourceOwnerQuery, err := query.NewUserResourceOwnerSearchQuery(resourceOwner, query.TextEquals)
if err != nil {
return nil, err
}
return v.userByID(instanceID, phoneQuery, resourceOwnerQuery)
}
func (v *View) userByID(instanceID string, queries ...query.SearchQuery) (*model.UserView, error) { func (v *View) userByID(instanceID string, queries ...query.SearchQuery) (*model.UserView, error) {
ctx := authz.WithInstanceID(context.Background(), instanceID) ctx := authz.WithInstanceID(context.Background(), instanceID)
queriedUser, err := v.query.GetUser(ctx, true, queries...) queriedUser, err := v.query.GetNotifyUser(ctx, true, queries...)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,11 +1,13 @@
package command package command
import ( import (
"context"
"net/http" "net/http"
"time" "time"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
api_http "github.com/zitadel/zitadel/internal/api/http" api_http "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/command/preparation"
sd "github.com/zitadel/zitadel/internal/config/systemdefaults" sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
@ -132,3 +134,28 @@ func AppendAndReduce(object interface {
object.AppendEvents(events...) object.AppendEvents(events...)
return object.Reduce() return object.Reduce()
} }
func queryAndReduce(ctx context.Context, filter preparation.FilterToQueryReducer, wm eventstore.QueryReducer) error {
events, err := filter(ctx, wm.Query())
if err != nil {
return err
}
if len(events) == 0 {
return nil
}
wm.AppendEvents(events...)
return wm.Reduce()
}
type existsWriteModel interface {
Exists() bool
eventstore.QueryReducer
}
func exists(ctx context.Context, filter preparation.FilterToQueryReducer, wm existsWriteModel) (bool, error) {
err := queryAndReduce(ctx, filter, wm)
if err != nil {
return false, err
}
return wm.Exists(), nil
}

View File

@ -62,3 +62,7 @@ func (rm *IDPConfigWriteModel) reduceConfigChangedEvent(e *idpconfig.IDPConfigCh
func (rm *IDPConfigWriteModel) reduceConfigStateChanged(configID string, state domain.IDPConfigState) { func (rm *IDPConfigWriteModel) reduceConfigStateChanged(configID string, state domain.IDPConfigState) {
rm.State = state rm.State = state
} }
func (rm *IDPConfigWriteModel) Exists() bool {
return rm.State.Exists()
}

View File

@ -72,6 +72,8 @@ type InstanceSetup struct {
HidePasswordReset bool HidePasswordReset bool
IgnoreUnknownUsername bool IgnoreUnknownUsername bool
AllowDomainDiscovery bool AllowDomainDiscovery bool
DisableLoginWithEmail bool
DisableLoginWithPhone bool
PasswordlessType domain.PasswordlessType PasswordlessType domain.PasswordlessType
DefaultRedirectURI string DefaultRedirectURI string
PasswordCheckLifetime time.Duration PasswordCheckLifetime time.Duration
@ -219,6 +221,8 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
setup.LoginPolicy.HidePasswordReset, setup.LoginPolicy.HidePasswordReset,
setup.LoginPolicy.IgnoreUnknownUsername, setup.LoginPolicy.IgnoreUnknownUsername,
setup.LoginPolicy.AllowDomainDiscovery, setup.LoginPolicy.AllowDomainDiscovery,
setup.LoginPolicy.DisableLoginWithEmail,
setup.LoginPolicy.DisableLoginWithPhone,
setup.LoginPolicy.PasswordlessType, setup.LoginPolicy.PasswordlessType,
setup.LoginPolicy.DefaultRedirectURI, setup.LoginPolicy.DefaultRedirectURI,
setup.LoginPolicy.PasswordCheckLifetime, setup.LoginPolicy.PasswordCheckLifetime,

View File

@ -15,56 +15,17 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/telemetry/tracing"
) )
func (c *Commands) ChangeDefaultLoginPolicy(ctx context.Context, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) { func (c *Commands) ChangeDefaultLoginPolicy(ctx context.Context, policy *ChangeLoginPolicy) (*domain.ObjectDetails, error) {
existingPolicy := NewInstanceLoginPolicyWriteModel(ctx) instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
instanceAgg := InstanceAggregateFromWriteModel(&existingPolicy.LoginPolicyWriteModel.WriteModel) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareChangeDefaultLoginPolicy(instanceAgg, policy))
event, err := c.changeDefaultLoginPolicy(ctx, instanceAgg, existingPolicy, policy)
if err != nil { if err != nil {
return nil, err return nil, err
} }
pushedEvents, err := c.eventstore.Push(ctx, event) pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = AppendAndReduce(existingPolicy, pushedEvents...) return pushedEventsToObjectDetails(pushedEvents), nil
if err != nil {
return nil, err
}
return writeModelToLoginPolicy(&existingPolicy.LoginPolicyWriteModel), nil
}
func (c *Commands) changeDefaultLoginPolicy(ctx context.Context, instanceAgg *eventstore.Aggregate, existingPolicy *InstanceLoginPolicyWriteModel, policy *domain.LoginPolicy) (eventstore.Command, error) {
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-SFdqd", "Errors.IAM.LoginPolicy.RedirectURIInvalid")
}
err := c.defaultLoginPolicyWriteModelByID(ctx, existingPolicy)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-M0sif", "Errors.IAM.LoginPolicy.NotFound")
}
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx,
instanceAgg,
policy.AllowUsernamePassword,
policy.AllowRegister,
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.AllowDomainDiscovery,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
policy.SecondFactorCheckLifetime,
policy.MultiFactorCheckLifetime,
)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged")
}
return changedEvent, nil
} }
func (c *Commands) AddIDPProviderToDefaultLoginPolicy(ctx context.Context, idpProvider *domain.IDPProvider) (*domain.IDPProvider, error) { func (c *Commands) AddIDPProviderToDefaultLoginPolicy(ctx context.Context, idpProvider *domain.IDPProvider) (*domain.IDPProvider, error) {
@ -255,6 +216,44 @@ func (c *Commands) getDefaultLoginPolicy(ctx context.Context) (*domain.LoginPoli
return policy, nil return policy, nil
} }
func prepareChangeDefaultLoginPolicy(a *instance.Aggregate, policy *ChangeLoginPolicy) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-SFdqd", "Errors.IAM.LoginPolicy.RedirectURIInvalid")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
wm := NewInstanceLoginPolicyWriteModel(ctx)
if err := queryAndReduce(ctx, filter, wm); err != nil {
return nil, err
}
if !wm.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-M0sif", "Errors.IAM.LoginPolicy.NotFound")
}
changedEvent, hasChanged := wm.NewChangedEvent(ctx, &a.Aggregate,
policy.AllowUsernamePassword,
policy.AllowRegister,
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.AllowDomainDiscovery,
policy.DisableLoginWithEmail,
policy.DisableLoginWithPhone,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
policy.SecondFactorCheckLifetime,
policy.MultiFactorCheckLifetime)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged")
}
return []eventstore.Command{changedEvent}, nil
}, nil
}
}
func prepareAddDefaultLoginPolicy( func prepareAddDefaultLoginPolicy(
a *instance.Aggregate, a *instance.Aggregate,
allowUsernamePassword bool, allowUsernamePassword bool,
@ -264,6 +263,8 @@ func prepareAddDefaultLoginPolicy(
hidePasswordReset bool, hidePasswordReset bool,
ignoreUnknownUsernames bool, ignoreUnknownUsernames bool,
allowDomainDiscovery bool, allowDomainDiscovery bool,
disableLoginWithEmail bool,
disableLoginWithPhone bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
defaultRedirectURI string, defaultRedirectURI string,
passwordCheckLifetime time.Duration, passwordCheckLifetime time.Duration,
@ -295,6 +296,8 @@ func prepareAddDefaultLoginPolicy(
hidePasswordReset, hidePasswordReset,
ignoreUnknownUsernames, ignoreUnknownUsernames,
allowDomainDiscovery, allowDomainDiscovery,
disableLoginWithEmail,
disableLoginWithPhone,
passwordlessType, passwordlessType,
defaultRedirectURI, defaultRedirectURI,
passwordCheckLifetime, passwordCheckLifetime,

View File

@ -67,7 +67,9 @@ func (wm *InstanceLoginPolicyWriteModel) NewChangedEvent(
forceMFA, forceMFA,
hidePasswordReset, hidePasswordReset,
ignoreUnknownUsernames, ignoreUnknownUsernames,
allowDomainDiscovery bool, allowDomainDiscovery,
disableLoginWithEmail,
disableLoginWithPhone bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
defaultRedirectURI string, defaultRedirectURI string,
passwordCheckLifetime, passwordCheckLifetime,
@ -120,6 +122,12 @@ func (wm *InstanceLoginPolicyWriteModel) NewChangedEvent(
if wm.MultiFactorCheckLifetime != multiFactorCheckLifetime { if wm.MultiFactorCheckLifetime != multiFactorCheckLifetime {
changes = append(changes, policy.ChangeMultiFactorCheckLifetime(multiFactorCheckLifetime)) changes = append(changes, policy.ChangeMultiFactorCheckLifetime(multiFactorCheckLifetime))
} }
if wm.DisableLoginWithEmail != disableLoginWithEmail {
changes = append(changes, policy.ChangeDisableLoginWithEmail(disableLoginWithEmail))
}
if wm.DisableLoginWithPhone != disableLoginWithPhone {
changes = append(changes, policy.ChangeDisableLoginWithPhone(disableLoginWithPhone))
}
if len(changes) == 0 { if len(changes) == 0 {
return nil, false return nil, false
} }

View File

@ -24,10 +24,10 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
policy *domain.LoginPolicy policy *ChangeLoginPolicy
} }
type res struct { type res struct {
want *domain.LoginPolicy want *domain.ObjectDetails
err func(error) bool err func(error) bool
} }
tests := []struct { tests := []struct {
@ -46,7 +46,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
policy: &domain.LoginPolicy{ policy: &ChangeLoginPolicy{
AllowRegister: true, AllowRegister: true,
AllowExternalIDP: true, AllowExternalIDP: true,
}, },
@ -71,6 +71,8 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"https://example.com/redirect", "https://example.com/redirect",
time.Hour*1, time.Hour*1,
@ -85,7 +87,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
policy: &domain.LoginPolicy{ policy: &ChangeLoginPolicy{
AllowRegister: true, AllowRegister: true,
AllowUsernamePassword: true, AllowUsernamePassword: true,
AllowExternalIDP: true, AllowExternalIDP: true,
@ -93,6 +95,8 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
HidePasswordReset: true, HidePasswordReset: true,
IgnoreUnknownUsernames: true, IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true, AllowDomainDiscovery: true,
DisableLoginWithEmail: true,
DisableLoginWithPhone: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect", DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1, PasswordCheckLifetime: time.Hour * 1,
@ -123,6 +127,8 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"https://example.com/redirect", "https://example.com/redirect",
time.Hour*1, time.Hour*1,
@ -145,6 +151,8 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*10, time.Hour*10,
@ -159,7 +167,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
}, },
args: args{ args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
policy: &domain.LoginPolicy{ policy: &ChangeLoginPolicy{
AllowRegister: false, AllowRegister: false,
AllowUsernamePassword: false, AllowUsernamePassword: false,
AllowExternalIDP: false, AllowExternalIDP: false,
@ -167,6 +175,8 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
HidePasswordReset: false, HidePasswordReset: false,
IgnoreUnknownUsernames: false, IgnoreUnknownUsernames: false,
AllowDomainDiscovery: false, AllowDomainDiscovery: false,
DisableLoginWithEmail: false,
DisableLoginWithPhone: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed, PasswordlessType: domain.PasswordlessTypeNotAllowed,
DefaultRedirectURI: "", DefaultRedirectURI: "",
PasswordCheckLifetime: time.Hour * 10, PasswordCheckLifetime: time.Hour * 10,
@ -177,26 +187,8 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
}, },
}, },
res: res{ res: res{
want: &domain.LoginPolicy{ want: &domain.ObjectDetails{
ObjectRoot: models.ObjectRoot{ ResourceOwner: "INSTANCE",
InstanceID: "INSTANCE",
AggregateID: "INSTANCE",
ResourceOwner: "INSTANCE",
},
AllowRegister: false,
AllowUsernamePassword: false,
AllowExternalIDP: false,
ForceMFA: false,
HidePasswordReset: false,
IgnoreUnknownUsernames: false,
AllowDomainDiscovery: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
DefaultRedirectURI: "",
PasswordCheckLifetime: time.Hour * 10,
ExternalLoginCheckLifetime: time.Hour * 20,
MFAInitSkipLifetime: time.Hour * 30,
SecondFactorCheckLifetime: time.Hour * 40,
MultiFactorCheckLifetime: time.Hour * 50,
}, },
}, },
}, },
@ -287,6 +279,8 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -326,6 +320,8 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -385,6 +381,8 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -526,6 +524,8 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -565,6 +565,8 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -617,6 +619,8 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -674,6 +678,8 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -739,6 +745,8 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1293,7 +1301,8 @@ func TestCommandSide_RemoveMultiFactorDefaultLoginPolicy(t *testing.T) {
} }
} }
func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA, hidePasswordReset, ignoreUnknownUsernames, allowDomainDiscovery bool, func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA,
hidePasswordReset, ignoreUnknownUsernames, allowDomainDiscovery, disableLoginWithEmail, disableLoginWithPhone bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
redirectURI string, redirectURI string,
passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime time.Duration) *instance.LoginPolicyChangedEvent { passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime time.Duration) *instance.LoginPolicyChangedEvent {
@ -1307,6 +1316,8 @@ func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allow
policy.ChangeHidePasswordReset(hidePasswordReset), policy.ChangeHidePasswordReset(hidePasswordReset),
policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames), policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames),
policy.ChangeAllowDomainDiscovery(allowDomainDiscovery), policy.ChangeAllowDomainDiscovery(allowDomainDiscovery),
policy.ChangeDisableLoginWithEmail(disableLoginWithEmail),
policy.ChangeDisableLoginWithPhone(disableLoginWithPhone),
policy.ChangePasswordlessType(passwordlessType), policy.ChangePasswordlessType(passwordlessType),
policy.ChangeDefaultRedirectURI(redirectURI), policy.ChangeDefaultRedirectURI(redirectURI),
policy.ChangePasswordCheckLifetime(passwordLifetime), policy.ChangePasswordCheckLifetime(passwordLifetime),

View File

@ -2,9 +2,12 @@ package command
import ( import (
"context" "context"
"time"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors" caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
@ -12,74 +15,63 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/telemetry/tracing"
) )
func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) { type AddLoginPolicy struct {
if resourceOwner == "" { AllowUsernamePassword bool
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Fn8ds", "Errors.ResourceOwnerMissing") AllowRegister bool
} AllowExternalIDP bool
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok { IDPProviders []*AddLoginPolicyIDP
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-WSfdq", "Errors.Org.LoginPolicy.RedirectURIInvalid") ForceMFA bool
} SecondFactors []domain.SecondFactorType
addedPolicy := NewOrgLoginPolicyWriteModel(resourceOwner) MultiFactors []domain.MultiFactorType
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy) PasswordlessType domain.PasswordlessType
HidePasswordReset bool
IgnoreUnknownUsernames bool
AllowDomainDiscovery bool
DefaultRedirectURI string
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration
MFAInitSkipLifetime time.Duration
SecondFactorCheckLifetime time.Duration
MultiFactorCheckLifetime time.Duration
DisableLoginWithEmail bool
DisableLoginWithPhone bool
}
type AddLoginPolicyIDP struct {
ConfigID string
Type domain.IdentityProviderType
}
type ChangeLoginPolicy struct {
AllowUsernamePassword bool
AllowRegister bool
AllowExternalIDP bool
ForceMFA bool
PasswordlessType domain.PasswordlessType
HidePasswordReset bool
IgnoreUnknownUsernames bool
AllowDomainDiscovery bool
DefaultRedirectURI string
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration
MFAInitSkipLifetime time.Duration
SecondFactorCheckLifetime time.Duration
MultiFactorCheckLifetime time.Duration
DisableLoginWithEmail bool
DisableLoginWithPhone bool
}
func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, policy *AddLoginPolicy) (*domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(resourceOwner)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddLoginPolicy(orgAgg, policy))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if addedPolicy.State == domain.PolicyStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "Org-Dgfb2", "Errors.Org.LoginPolicy.AlreadyExists")
}
orgAgg := OrgAggregateFromWriteModel(&addedPolicy.WriteModel)
cmds := []eventstore.Command{
org.NewLoginPolicyAddedEvent(
ctx,
orgAgg,
policy.AllowUsernamePassword,
policy.AllowRegister,
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.AllowDomainDiscovery,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
policy.SecondFactorCheckLifetime,
policy.MultiFactorCheckLifetime),
}
for _, factor := range policy.SecondFactors {
if !factor.Valid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-SFeea", "Errors.Org.LoginPolicy.MFA.Unspecified")
}
cmds = append(cmds, org.NewLoginPolicySecondFactorAddedEvent(ctx, orgAgg, factor))
}
for _, factor := range policy.MultiFactors {
if !factor.Valid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-WSfrg", "Errors.Org.LoginPolicy.MFA.Unspecified")
}
cmds = append(cmds, org.NewLoginPolicyMultiFactorAddedEvent(ctx, orgAgg, factor))
}
for _, provider := range policy.IDPProviders {
if provider.Type == domain.IdentityProviderTypeOrg {
_, err = c.getOrgIDPConfigByID(ctx, provider.IDPConfigID, resourceOwner)
} else {
_, err = c.getInstanceIDPConfigByID(ctx, provider.IDPConfigID)
}
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "Org-FEd32", "Errors.IDPConfig.NotExisting")
}
cmds = append(cmds, org.NewIdentityProviderAddedEvent(ctx, orgAgg, provider.IDPConfigID, provider.Type))
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...) pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = AppendAndReduce(addedPolicy, pushedEvents...) return pushedEventsToObjectDetails(pushedEvents), nil
if err != nil {
return nil, err
}
return writeModelToLoginPolicy(&addedPolicy.LoginPolicyWriteModel), nil
} }
func (c *Commands) orgLoginPolicyWriteModelByID(ctx context.Context, orgID string) (*OrgLoginPolicyWriteModel, error) { func (c *Commands) orgLoginPolicyWriteModelByID(ctx context.Context, orgID string) (*OrgLoginPolicyWriteModel, error) {
@ -102,54 +94,17 @@ func (c *Commands) getOrgLoginPolicy(ctx context.Context, orgID string) (*domain
return c.getDefaultLoginPolicy(ctx) return c.getDefaultLoginPolicy(ctx)
} }
func (c *Commands) ChangeLoginPolicy(ctx context.Context, resourceOwner string, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) { func (c *Commands) ChangeLoginPolicy(ctx context.Context, resourceOwner string, policy *ChangeLoginPolicy) (*domain.ObjectDetails, error) {
if resourceOwner == "" { orgAgg := org.NewAggregate(resourceOwner)
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Mf9sf", "Errors.ResourceOwnerMissing") cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareChangeLoginPolicy(orgAgg, policy))
}
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Sfd21", "Errors.Org.LoginPolicy.RedirectURIInvalid")
}
existingPolicy := NewOrgLoginPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { pushedEvents, err := c.eventstore.Push(ctx, cmds...)
return nil, caos_errs.ThrowNotFound(nil, "Org-M0sif", "Errors.Org.LoginPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LoginPolicyWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(
ctx,
orgAgg,
policy.AllowUsernamePassword,
policy.AllowRegister,
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.AllowDomainDiscovery,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
policy.SecondFactorCheckLifetime,
policy.MultiFactorCheckLifetime)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5M9vdd", "Errors.Org.LoginPolicy.NotChanged")
}
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = AppendAndReduce(existingPolicy, pushedEvents...) return pushedEventsToObjectDetails(pushedEvents), nil
if err != nil {
return nil, err
}
return writeModelToLoginPolicy(&existingPolicy.LoginPolicyWriteModel), nil
} }
func (c *Commands) RemoveLoginPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) { func (c *Commands) RemoveLoginPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
@ -437,3 +392,106 @@ func (c *Commands) orgLoginPolicyAuthFactorsWriteModel(ctx context.Context, orgI
} }
return writeModel, nil return writeModel, nil
} }
func prepareAddLoginPolicy(a *org.Aggregate, policy *AddLoginPolicy) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-WSfdq", "Errors.Org.LoginPolicy.RedirectURIInvalid")
}
for _, factor := range policy.SecondFactors {
if !factor.Valid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-SFeea", "Errors.Org.LoginPolicy.MFA.Unspecified")
}
}
for _, factor := range policy.MultiFactors {
if !factor.Valid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-WSfrg", "Errors.Org.LoginPolicy.MFA.Unspecified")
}
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
if exists, err := exists(ctx, filter, NewOrgLoginPolicyWriteModel(a.ID)); exists || err != nil {
return nil, caos_errs.ThrowAlreadyExists(nil, "Org-Dgfb2", "Errors.Org.LoginPolicy.AlreadyExists")
}
for _, idp := range policy.IDPProviders {
exists, err := idpExists(ctx, filter, idp)
if !exists || err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "Org-FEd32", "Errors.IDPConfig.NotExisting")
}
}
cmds := make([]eventstore.Command, 0, len(policy.SecondFactors)+len(policy.MultiFactors)+len(policy.IDPProviders)+1)
cmds = append(cmds, org.NewLoginPolicyAddedEvent(ctx, &a.Aggregate,
policy.AllowUsernamePassword,
policy.AllowRegister,
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.AllowDomainDiscovery,
policy.DisableLoginWithEmail,
policy.DisableLoginWithPhone,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
policy.SecondFactorCheckLifetime,
policy.MultiFactorCheckLifetime,
))
for _, factor := range policy.SecondFactors {
cmds = append(cmds, org.NewLoginPolicySecondFactorAddedEvent(ctx, &a.Aggregate, factor))
}
for _, factor := range policy.MultiFactors {
cmds = append(cmds, org.NewLoginPolicyMultiFactorAddedEvent(ctx, &a.Aggregate, factor))
}
for _, idp := range policy.IDPProviders {
cmds = append(cmds, org.NewIdentityProviderAddedEvent(ctx, &a.Aggregate, idp.ConfigID, idp.Type))
}
return cmds, nil
}, nil
}
}
func prepareChangeLoginPolicy(a *org.Aggregate, policy *ChangeLoginPolicy) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Sfd21", "Errors.Org.LoginPolicy.RedirectURIInvalid")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
wm := NewOrgLoginPolicyWriteModel(a.ID)
if err := queryAndReduce(ctx, filter, wm); err != nil {
return nil, err
}
if !wm.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "Org-M0sif", "Errors.Org.LoginPolicy.NotFound")
}
changedEvent, hasChanged := wm.NewChangedEvent(ctx, &a.Aggregate,
policy.AllowUsernamePassword,
policy.AllowRegister,
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.AllowDomainDiscovery,
policy.DisableLoginWithEmail,
policy.DisableLoginWithPhone,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
policy.SecondFactorCheckLifetime,
policy.MultiFactorCheckLifetime)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5M9vdd", "Errors.Org.LoginPolicy.NotChanged")
}
return []eventstore.Command{changedEvent}, nil
}, nil
}
}
func idpExists(ctx context.Context, filter preparation.FilterToQueryReducer, idp *AddLoginPolicyIDP) (bool, error) {
if idp.Type == domain.IdentityProviderTypeSystem {
return exists(ctx, filter, NewInstanceIDPConfigWriteModel(ctx, idp.ConfigID))
}
return exists(ctx, filter, NewOrgIDPConfigWriteModel(idp.ConfigID, authz.GetCtxData(ctx).ResourceOwner))
}

View File

@ -69,7 +69,9 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
forceMFA, forceMFA,
hidePasswordReset, hidePasswordReset,
ignoreUnknownUsernames, ignoreUnknownUsernames,
allowDomainDiscovery bool, allowDomainDiscovery,
disableLoginWithEmail,
disableLoginWithPhone bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
defaultRedirectURI string, defaultRedirectURI string,
passwordCheckLifetime, passwordCheckLifetime,
@ -122,6 +124,12 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
if wm.DefaultRedirectURI != defaultRedirectURI { if wm.DefaultRedirectURI != defaultRedirectURI {
changes = append(changes, policy.ChangeDefaultRedirectURI(defaultRedirectURI)) changes = append(changes, policy.ChangeDefaultRedirectURI(defaultRedirectURI))
} }
if wm.DisableLoginWithEmail != disableLoginWithEmail {
changes = append(changes, policy.ChangeDisableLoginWithEmail(disableLoginWithEmail))
}
if wm.DisableLoginWithPhone != disableLoginWithPhone {
changes = append(changes, policy.ChangeDisableLoginWithPhone(disableLoginWithPhone))
}
if len(changes) == 0 { if len(changes) == 0 {
return nil, false return nil, false
} }

View File

@ -33,10 +33,10 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
type args struct { type args struct {
ctx context.Context ctx context.Context
orgID string orgID string
policy *domain.LoginPolicy policy *AddLoginPolicy
} }
type res struct { type res struct {
want *domain.LoginPolicy want *domain.ObjectDetails
err func(error) bool err func(error) bool
} }
tests := []struct { tests := []struct {
@ -45,25 +45,6 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
args args args args
res res res res
}{ }{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.LoginPolicy{
AllowRegister: true,
AllowUsernamePassword: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{ {
name: "loginpolicy already existing, already exists error", name: "loginpolicy already existing, already exists error",
fields: fields{ fields: fields{
@ -80,6 +61,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
false,
false,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"https://example.com/redirect", "https://example.com/redirect",
time.Hour*1, time.Hour*1,
@ -95,7 +78,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.LoginPolicy{ policy: &AddLoginPolicy{
AllowRegister: true, AllowRegister: true,
AllowUsernamePassword: true, AllowUsernamePassword: true,
AllowExternalIDP: true, AllowExternalIDP: true,
@ -133,6 +116,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"https://example.com/redirect", "https://example.com/redirect",
time.Hour*1, time.Hour*1,
@ -149,7 +134,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.LoginPolicy{ policy: &AddLoginPolicy{
AllowRegister: true, AllowRegister: true,
AllowUsernamePassword: true, AllowUsernamePassword: true,
AllowExternalIDP: true, AllowExternalIDP: true,
@ -157,6 +142,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
HidePasswordReset: true, HidePasswordReset: true,
IgnoreUnknownUsernames: true, IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true, AllowDomainDiscovery: true,
DisableLoginWithEmail: true,
DisableLoginWithPhone: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect", DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1, PasswordCheckLifetime: time.Hour * 1,
@ -167,25 +154,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
}, },
}, },
res: res{ res: res{
want: &domain.LoginPolicy{ want: &domain.ObjectDetails{
ObjectRoot: models.ObjectRoot{ ResourceOwner: "org1",
AggregateID: "org1",
ResourceOwner: "org1",
},
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5,
}, },
}, },
}, },
@ -194,13 +164,12 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: eventstoreExpect(
t, t,
expectFilter(),
), ),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.LoginPolicy{ policy: &AddLoginPolicy{
AllowRegister: true, AllowRegister: true,
AllowUsernamePassword: true, AllowUsernamePassword: true,
AllowExternalIDP: true, AllowExternalIDP: true,
@ -208,6 +177,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
HidePasswordReset: true, HidePasswordReset: true,
IgnoreUnknownUsernames: true, IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true, AllowDomainDiscovery: true,
DisableLoginWithEmail: true,
DisableLoginWithPhone: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect", DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1, PasswordCheckLifetime: time.Hour * 1,
@ -240,6 +211,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"https://example.com/redirect", "https://example.com/redirect",
time.Hour*1, time.Hour*1,
@ -268,7 +241,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.LoginPolicy{ policy: &AddLoginPolicy{
AllowRegister: true, AllowRegister: true,
AllowUsernamePassword: true, AllowUsernamePassword: true,
AllowExternalIDP: true, AllowExternalIDP: true,
@ -276,6 +249,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
HidePasswordReset: true, HidePasswordReset: true,
IgnoreUnknownUsernames: true, IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true, AllowDomainDiscovery: true,
DisableLoginWithEmail: true,
DisableLoginWithPhone: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect", DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1, PasswordCheckLifetime: time.Hour * 1,
@ -288,25 +263,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
}, },
}, },
res: res{ res: res{
want: &domain.LoginPolicy{ want: &domain.ObjectDetails{
ObjectRoot: models.ObjectRoot{ ResourceOwner: "org1",
AggregateID: "org1",
ResourceOwner: "org1",
},
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5,
}, },
}, },
}, },
@ -322,7 +280,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.LoginPolicy{ policy: &AddLoginPolicy{
AllowRegister: true, AllowRegister: true,
AllowUsernamePassword: true, AllowUsernamePassword: true,
AllowExternalIDP: true, AllowExternalIDP: true,
@ -330,6 +288,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
HidePasswordReset: true, HidePasswordReset: true,
IgnoreUnknownUsernames: true, IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true, AllowDomainDiscovery: true,
DisableLoginWithEmail: true,
DisableLoginWithPhone: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect", DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1, PasswordCheckLifetime: time.Hour * 1,
@ -337,10 +297,10 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
MFAInitSkipLifetime: time.Hour * 3, MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4, SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5, MultiFactorCheckLifetime: time.Hour * 5,
IDPProviders: []*domain.IDPProvider{ IDPProviders: []*AddLoginPolicyIDP{
{ {
Type: domain.IdentityProviderTypeSystem, Type: domain.IdentityProviderTypeSystem,
IDPConfigID: "invalid", ConfigID: "invalid",
}, },
}, },
}, },
@ -379,6 +339,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"https://example.com/redirect", "https://example.com/redirect",
time.Hour*1, time.Hour*1,
@ -402,7 +364,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.LoginPolicy{ policy: &AddLoginPolicy{
AllowRegister: true, AllowRegister: true,
AllowUsernamePassword: true, AllowUsernamePassword: true,
AllowExternalIDP: true, AllowExternalIDP: true,
@ -410,6 +372,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
HidePasswordReset: true, HidePasswordReset: true,
IgnoreUnknownUsernames: true, IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true, AllowDomainDiscovery: true,
DisableLoginWithEmail: true,
DisableLoginWithPhone: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect", DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1, PasswordCheckLifetime: time.Hour * 1,
@ -417,34 +381,17 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
MFAInitSkipLifetime: time.Hour * 3, MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4, SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5, MultiFactorCheckLifetime: time.Hour * 5,
IDPProviders: []*domain.IDPProvider{ IDPProviders: []*AddLoginPolicyIDP{
{ {
Type: domain.IdentityProviderTypeSystem, Type: domain.IdentityProviderTypeSystem,
IDPConfigID: "config1", ConfigID: "config1",
}, },
}, },
}, },
}, },
res: res{ res: res{
want: &domain.LoginPolicy{ want: &domain.ObjectDetails{
ObjectRoot: models.ObjectRoot{ ResourceOwner: "org1",
AggregateID: "org1",
ResourceOwner: "org1",
},
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5,
}, },
}, },
}, },
@ -475,10 +422,10 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
type args struct { type args struct {
ctx context.Context ctx context.Context
orgID string orgID string
policy *domain.LoginPolicy policy *ChangeLoginPolicy
} }
type res struct { type res struct {
want *domain.LoginPolicy want *domain.ObjectDetails
err func(error) bool err func(error) bool
} }
tests := []struct { tests := []struct {
@ -487,30 +434,6 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
args args args args
res res res res
}{ }{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.LoginPolicy{
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{ {
name: "loginpolicy not existing, not found error", name: "loginpolicy not existing, not found error",
fields: fields{ fields: fields{
@ -522,13 +445,15 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.LoginPolicy{ policy: &ChangeLoginPolicy{
AllowRegister: true, AllowRegister: true,
AllowUsernamePassword: true, AllowUsernamePassword: true,
AllowExternalIDP: true, AllowExternalIDP: true,
ForceMFA: true, ForceMFA: true,
IgnoreUnknownUsernames: true, IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true, AllowDomainDiscovery: true,
DisableLoginWithEmail: true,
DisableLoginWithPhone: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect", DefaultRedirectURI: "https://example.com/redirect",
}, },
@ -553,6 +478,8 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"https://example.com/redirect", "https://example.com/redirect",
time.Hour*1, time.Hour*1,
@ -568,7 +495,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.LoginPolicy{ policy: &ChangeLoginPolicy{
AllowRegister: true, AllowRegister: true,
AllowUsernamePassword: true, AllowUsernamePassword: true,
AllowExternalIDP: true, AllowExternalIDP: true,
@ -576,6 +503,8 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
HidePasswordReset: true, HidePasswordReset: true,
IgnoreUnknownUsernames: true, IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true, AllowDomainDiscovery: true,
DisableLoginWithEmail: true,
DisableLoginWithPhone: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect", DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1, PasswordCheckLifetime: time.Hour * 1,
@ -605,6 +534,8 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"https://example.com/redirect", "https://example.com/redirect",
time.Hour*1, time.Hour*1,
@ -627,6 +558,8 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
&duration10, &duration10,
@ -643,13 +576,15 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.LoginPolicy{ policy: &ChangeLoginPolicy{
AllowRegister: false, AllowRegister: false,
AllowUsernamePassword: false, AllowUsernamePassword: false,
AllowExternalIDP: false, AllowExternalIDP: false,
ForceMFA: false, ForceMFA: false,
IgnoreUnknownUsernames: false, IgnoreUnknownUsernames: false,
AllowDomainDiscovery: false, AllowDomainDiscovery: false,
DisableLoginWithEmail: false,
DisableLoginWithPhone: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed, PasswordlessType: domain.PasswordlessTypeNotAllowed,
DefaultRedirectURI: "", DefaultRedirectURI: "",
PasswordCheckLifetime: time.Hour * 10, PasswordCheckLifetime: time.Hour * 10,
@ -660,25 +595,8 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
}, },
}, },
res: res{ res: res{
want: &domain.LoginPolicy{ want: &domain.ObjectDetails{
ObjectRoot: models.ObjectRoot{ ResourceOwner: "org1",
AggregateID: "org1",
ResourceOwner: "org1",
},
AllowRegister: false,
AllowUsernamePassword: false,
AllowExternalIDP: false,
ForceMFA: false,
HidePasswordReset: false,
IgnoreUnknownUsernames: false,
AllowDomainDiscovery: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
DefaultRedirectURI: "",
PasswordCheckLifetime: time.Hour * 10,
ExternalLoginCheckLifetime: time.Hour * 20,
MFAInitSkipLifetime: time.Hour * 30,
SecondFactorCheckLifetime: time.Hour * 40,
MultiFactorCheckLifetime: time.Hour * 50,
}, },
}, },
}, },
@ -766,6 +684,8 @@ func TestCommandSide_RemoveLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -907,6 +827,8 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -949,6 +871,8 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1011,6 +935,8 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1176,6 +1102,8 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1218,6 +1146,8 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1272,6 +1202,8 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1333,6 +1265,8 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1402,6 +1336,8 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -2020,7 +1956,8 @@ func TestCommandSide_RemoveMultiFactorLoginPolicy(t *testing.T) {
} }
} }
func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassword, register, externalIDP, mfa, passwordReset, ignoreUnknownUsernames, allowDomainDiscovery bool, func newLoginPolicyChangedEvent(ctx context.Context, orgID string,
usernamePassword, register, externalIDP, mfa, passwordReset, ignoreUnknownUsernames, allowDomainDiscovery, disableLoginWithEmail, disableLoginWithPhone bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
redirectURI string, redirectURI string,
passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime *time.Duration) *org.LoginPolicyChangedEvent { passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime *time.Duration) *org.LoginPolicyChangedEvent {
@ -2034,6 +1971,8 @@ func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassw
policy.ChangeAllowDomainDiscovery(allowDomainDiscovery), policy.ChangeAllowDomainDiscovery(allowDomainDiscovery),
policy.ChangePasswordlessType(passwordlessType), policy.ChangePasswordlessType(passwordlessType),
policy.ChangeDefaultRedirectURI(redirectURI), policy.ChangeDefaultRedirectURI(redirectURI),
policy.ChangeDisableLoginWithEmail(disableLoginWithEmail),
policy.ChangeDisableLoginWithPhone(disableLoginWithPhone),
} }
if passwordLifetime != nil { if passwordLifetime != nil {
changes = append(changes, policy.ChangePasswordCheckLifetime(*passwordLifetime)) changes = append(changes, policy.ChangePasswordCheckLifetime(*passwordLifetime))

View File

@ -18,6 +18,8 @@ type LoginPolicyWriteModel struct {
HidePasswordReset bool HidePasswordReset bool
IgnoreUnknownUsernames bool IgnoreUnknownUsernames bool
AllowDomainDiscovery bool AllowDomainDiscovery bool
DisableLoginWithEmail bool
DisableLoginWithPhone bool
PasswordlessType domain.PasswordlessType PasswordlessType domain.PasswordlessType
DefaultRedirectURI string DefaultRedirectURI string
PasswordCheckLifetime time.Duration PasswordCheckLifetime time.Duration
@ -40,6 +42,8 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
wm.HidePasswordReset = e.HidePasswordReset wm.HidePasswordReset = e.HidePasswordReset
wm.IgnoreUnknownUsernames = e.IgnoreUnknownUsernames wm.IgnoreUnknownUsernames = e.IgnoreUnknownUsernames
wm.AllowDomainDiscovery = e.AllowDomainDiscovery wm.AllowDomainDiscovery = e.AllowDomainDiscovery
wm.DisableLoginWithEmail = e.DisableLoginWithEmail
wm.DisableLoginWithPhone = e.DisableLoginWithPhone
wm.DefaultRedirectURI = e.DefaultRedirectURI wm.DefaultRedirectURI = e.DefaultRedirectURI
wm.PasswordCheckLifetime = e.PasswordCheckLifetime wm.PasswordCheckLifetime = e.PasswordCheckLifetime
wm.ExternalLoginCheckLifetime = e.ExternalLoginCheckLifetime wm.ExternalLoginCheckLifetime = e.ExternalLoginCheckLifetime
@ -90,9 +94,19 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
if e.MultiFactorCheckLifetime != nil { if e.MultiFactorCheckLifetime != nil {
wm.MultiFactorCheckLifetime = *e.MultiFactorCheckLifetime wm.MultiFactorCheckLifetime = *e.MultiFactorCheckLifetime
} }
if e.DisableLoginWithEmail != nil {
wm.DisableLoginWithEmail = *e.DisableLoginWithEmail
}
if e.DisableLoginWithPhone != nil {
wm.DisableLoginWithPhone = *e.DisableLoginWithPhone
}
case *policy.LoginPolicyRemovedEvent: case *policy.LoginPolicyRemovedEvent:
wm.State = domain.PolicyStateRemoved wm.State = domain.PolicyStateRemoved
} }
} }
return wm.WriteModel.Reduce() return wm.WriteModel.Reduce()
} }
func (wm *LoginPolicyWriteModel) Exists() bool {
return wm.State.Exists()
}

View File

@ -1158,6 +1158,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1196,6 +1198,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1235,6 +1239,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1290,6 +1296,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1379,6 +1387,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1475,6 +1485,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,

View File

@ -1680,6 +1680,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1747,6 +1749,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1814,6 +1818,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -1898,6 +1904,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -2040,6 +2048,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -2150,6 +2160,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -2254,6 +2266,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,
@ -2380,6 +2394,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
false, false,
false,
false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
"", "",
time.Hour*1, time.Hour*1,

View File

@ -101,7 +101,7 @@ func (s IDPConfigState) Valid() bool {
} }
func (s IDPConfigState) Exists() bool { func (s IDPConfigState) Exists() bool {
return s != IDPConfigStateUnspecified || s == IDPConfigStateRemoved return s != IDPConfigStateUnspecified && s != IDPConfigStateRemoved
} }
type IDPConfigStylingType int32 type IDPConfigStylingType int32

View File

@ -28,6 +28,8 @@ type LoginPolicy struct {
MFAInitSkipLifetime time.Duration MFAInitSkipLifetime time.Duration
SecondFactorCheckLifetime time.Duration SecondFactorCheckLifetime time.Duration
MultiFactorCheckLifetime time.Duration MultiFactorCheckLifetime time.Duration
DisableLoginWithEmail bool
DisableLoginWithPhone bool
} }
func ValidateDefaultRedirectURI(rawURL string) bool { func ValidateDefaultRedirectURI(rawURL string) bool {

View File

@ -123,7 +123,7 @@ func getUpdateCols(cols, conflictTarget []string) (updateCols, updateVals []stri
func NewUpdateStatement(event eventstore.Event, values []handler.Column, conditions []handler.Condition, opts ...execOption) *handler.Statement { func NewUpdateStatement(event eventstore.Event, values []handler.Column, conditions []handler.Condition, opts ...execOption) *handler.Statement {
cols, params, args := columnsToQuery(values) cols, params, args := columnsToQuery(values)
wheres, whereArgs := conditionsToWhere(conditions, len(params)) wheres, whereArgs := conditionsToWhere(conditions, len(args))
args = append(args, whereArgs...) args = append(args, whereArgs...)
config := execConfig{ config := execConfig{
@ -278,6 +278,13 @@ func NewArrayIntersectCol(column string, value interface{}) handler.Column {
} }
} }
func NewCopyCol(column, from string) handler.Column {
return handler.Column{
Name: column,
Value: handler.NewCol(from, nil),
}
}
// NewCopyStatement creates a new upsert statement which updates a column from an existing row // NewCopyStatement creates a new upsert statement which updates a column from an existing row
// cols represent the columns which are objective to change. // cols represent the columns which are objective to change.
// if the value of a col is empty the data will be copied from the selected row // if the value of a col is empty the data will be copied from the selected row
@ -359,15 +366,22 @@ func columnsToQuery(cols []handler.Column) (names []string, parameters []string,
names = make([]string, len(cols)) names = make([]string, len(cols))
values = make([]interface{}, len(cols)) values = make([]interface{}, len(cols))
parameters = make([]string, len(cols)) parameters = make([]string, len(cols))
var parameterIndex int
for i, col := range cols { for i, col := range cols {
names[i] = col.Name names[i] = col.Name
values[i] = col.Value if c, ok := col.Value.(handler.Column); ok {
parameters[i] = "$" + strconv.Itoa(i+1) parameters[i] = c.Name
continue
} else {
values[parameterIndex] = col.Value
}
parameters[i] = "$" + strconv.Itoa(parameterIndex+1)
if col.ParameterOpt != nil { if col.ParameterOpt != nil {
parameters[i] = col.ParameterOpt(parameters[i]) parameters[i] = col.ParameterOpt(parameters[i])
} }
parameterIndex++
} }
return names, parameters, values return names, parameters, values[:parameterIndex]
} }
func conditionsToWhere(cols []handler.Condition, paramOffset int) (wheres []string, values []interface{}) { func conditionsToWhere(cols []handler.Condition, paramOffset int) (wheres []string, values []interface{}) {

View File

@ -1352,6 +1352,32 @@ func Test_columnsToQuery(t *testing.T) {
values: []interface{}{1, 3.14}, values: []interface{}{1, 3.14},
}, },
}, },
{
name: "with copy column",
args: args{
cols: []handler.Column{
{
Name: "col1",
Value: 1,
},
{
Name: "col2",
Value: handler.Column{
Name: "col1",
},
},
{
Name: "col3",
Value: "something",
},
},
},
want: want{
names: []string{"col1", "col2", "col3"},
params: []string{"$1", "col1", "$2"},
values: []interface{}{1, "something"},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -169,7 +169,7 @@ func (p *notificationsProjection) reduceInitCodeAdded(event eventstore.Event) (*
return nil, err return nil, err
} }
notifyUser, err := p.queries.GeNotifyUser(ctx, true, e.Aggregate().ID) notifyUser, err := p.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -232,7 +232,7 @@ func (p *notificationsProjection) reduceEmailCodeAdded(event eventstore.Event) (
return nil, err return nil, err
} }
notifyUser, err := p.queries.GeNotifyUser(ctx, true, e.Aggregate().ID) notifyUser, err := p.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -295,7 +295,7 @@ func (p *notificationsProjection) reducePasswordCodeAdded(event eventstore.Event
return nil, err return nil, err
} }
notifyUser, err := p.queries.GeNotifyUser(ctx, true, e.Aggregate().ID) notifyUser, err := p.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -366,7 +366,7 @@ func (p *notificationsProjection) reduceDomainClaimed(event eventstore.Event) (*
return nil, err return nil, err
} }
notifyUser, err := p.queries.GeNotifyUser(ctx, true, e.Aggregate().ID) notifyUser, err := p.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -427,7 +427,7 @@ func (p *notificationsProjection) reducePasswordlessCodeRequested(event eventsto
return nil, err return nil, err
} }
notifyUser, err := p.queries.GeNotifyUser(ctx, true, e.Aggregate().ID) notifyUser, err := p.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -485,7 +485,7 @@ func (p *notificationsProjection) reducePhoneCodeAdded(event eventstore.Event) (
return nil, err return nil, err
} }
notifyUser, err := p.queries.GeNotifyUser(ctx, true, e.Aggregate().ID) notifyUser, err := p.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -20,18 +20,18 @@ var (
", members.user_id" + ", members.user_id" +
", members.roles" + ", members.roles" +
", projections.login_names.login_name" + ", projections.login_names.login_name" +
", projections.users3_humans.email" + ", projections.users4_humans.email" +
", projections.users3_humans.first_name" + ", projections.users4_humans.first_name" +
", projections.users3_humans.last_name" + ", projections.users4_humans.last_name" +
", projections.users3_humans.display_name" + ", projections.users4_humans.display_name" +
", projections.users3_machines.name" + ", projections.users4_machines.name" +
", projections.users3_humans.avatar_key" + ", projections.users4_humans.avatar_key" +
", COUNT(*) OVER () " + ", COUNT(*) OVER () " +
"FROM projections.instance_members2 AS members " + "FROM projections.instance_members2 AS members " +
"LEFT JOIN projections.users3_humans " + "LEFT JOIN projections.users4_humans " +
"ON members.user_id = projections.users3_humans.user_id " + "ON members.user_id = projections.users4_humans.user_id " +
"LEFT JOIN projections.users3_machines " + "LEFT JOIN projections.users4_machines " +
"ON members.user_id = projections.users3_machines.user_id " + "ON members.user_id = projections.users4_machines.user_id " +
"LEFT JOIN projections.login_names " + "LEFT JOIN projections.login_names " +
"ON members.user_id = projections.login_names.user_id " + "ON members.user_id = projections.login_names.user_id " +
"WHERE projections.login_names.is_primary = $1") "WHERE projections.login_names.is_primary = $1")

View File

@ -31,6 +31,8 @@ type LoginPolicy struct {
HidePasswordReset bool HidePasswordReset bool
IgnoreUnknownUsernames bool IgnoreUnknownUsernames bool
AllowDomainDiscovery bool AllowDomainDiscovery bool
DisableLoginWithEmail bool
DisableLoginWithPhone bool
DefaultRedirectURI string DefaultRedirectURI string
PasswordCheckLifetime time.Duration PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration ExternalLoginCheckLifetime time.Duration
@ -118,6 +120,14 @@ var (
name: projection.AllowDomainDiscovery, name: projection.AllowDomainDiscovery,
table: loginPolicyTable, table: loginPolicyTable,
} }
LoginPolicyColumnDisableLoginWithEmail = Column{
name: projection.DisableLoginWithEmail,
table: loginPolicyTable,
}
LoginPolicyColumnDisableLoginWithPhone = Column{
name: projection.DisableLoginWithPhone,
table: loginPolicyTable,
}
LoginPolicyColumnDefaultRedirectURI = Column{ LoginPolicyColumnDefaultRedirectURI = Column{
name: projection.DefaultRedirectURI, name: projection.DefaultRedirectURI,
table: loginPolicyTable, table: loginPolicyTable,
@ -311,6 +321,8 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Rows) (*LoginPolicy,
LoginPolicyColumnHidePasswordReset.identifier(), LoginPolicyColumnHidePasswordReset.identifier(),
LoginPolicyColumnIgnoreUnknownUsernames.identifier(), LoginPolicyColumnIgnoreUnknownUsernames.identifier(),
LoginPolicyColumnAllowDomainDiscovery.identifier(), LoginPolicyColumnAllowDomainDiscovery.identifier(),
LoginPolicyColumnDisableLoginWithEmail.identifier(),
LoginPolicyColumnDisableLoginWithPhone.identifier(),
LoginPolicyColumnDefaultRedirectURI.identifier(), LoginPolicyColumnDefaultRedirectURI.identifier(),
LoginPolicyColumnPasswordCheckLifetime.identifier(), LoginPolicyColumnPasswordCheckLifetime.identifier(),
LoginPolicyColumnExternalLoginCheckLifetime.identifier(), LoginPolicyColumnExternalLoginCheckLifetime.identifier(),
@ -350,6 +362,8 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Rows) (*LoginPolicy,
&p.HidePasswordReset, &p.HidePasswordReset,
&p.IgnoreUnknownUsernames, &p.IgnoreUnknownUsernames,
&p.AllowDomainDiscovery, &p.AllowDomainDiscovery,
&p.DisableLoginWithEmail,
&p.DisableLoginWithPhone,
&defaultRedirectURI, &defaultRedirectURI,
&p.PasswordCheckLifetime, &p.PasswordCheckLifetime,
&p.ExternalLoginCheckLifetime, &p.ExternalLoginCheckLifetime,

View File

@ -30,33 +30,35 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyQuery, prepare: prepareLoginPolicyQuery,
want: want{ want: want{
sqlExpectations: mockQueries( sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.login_policies2.aggregate_id,`+ regexp.QuoteMeta(`SELECT projections.login_policies3.aggregate_id,`+
` projections.login_policies2.creation_date,`+ ` projections.login_policies3.creation_date,`+
` projections.login_policies2.change_date,`+ ` projections.login_policies3.change_date,`+
` projections.login_policies2.sequence,`+ ` projections.login_policies3.sequence,`+
` projections.login_policies2.allow_register,`+ ` projections.login_policies3.allow_register,`+
` projections.login_policies2.allow_username_password,`+ ` projections.login_policies3.allow_username_password,`+
` projections.login_policies2.allow_external_idps,`+ ` projections.login_policies3.allow_external_idps,`+
` projections.login_policies2.force_mfa,`+ ` projections.login_policies3.force_mfa,`+
` projections.login_policies2.second_factors,`+ ` projections.login_policies3.second_factors,`+
` projections.login_policies2.multi_factors,`+ ` projections.login_policies3.multi_factors,`+
` projections.login_policies2.passwordless_type,`+ ` projections.login_policies3.passwordless_type,`+
` projections.login_policies2.is_default,`+ ` projections.login_policies3.is_default,`+
` projections.login_policies2.hide_password_reset,`+ ` projections.login_policies3.hide_password_reset,`+
` projections.login_policies2.ignore_unknown_usernames,`+ ` projections.login_policies3.ignore_unknown_usernames,`+
` projections.login_policies2.allow_domain_discovery,`+ ` projections.login_policies3.allow_domain_discovery,`+
` projections.login_policies2.default_redirect_uri,`+ ` projections.login_policies3.disable_login_with_email,`+
` projections.login_policies2.password_check_lifetime,`+ ` projections.login_policies3.disable_login_with_phone,`+
` projections.login_policies2.external_login_check_lifetime,`+ ` projections.login_policies3.default_redirect_uri,`+
` projections.login_policies2.mfa_init_skip_lifetime,`+ ` projections.login_policies3.password_check_lifetime,`+
` projections.login_policies2.second_factor_check_lifetime,`+ ` projections.login_policies3.external_login_check_lifetime,`+
` projections.login_policies2.multi_factor_check_lifetime,`+ ` projections.login_policies3.mfa_init_skip_lifetime,`+
` projections.login_policies3.second_factor_check_lifetime,`+
` projections.login_policies3.multi_factor_check_lifetime,`+
` projections.idp_login_policy_links3.idp_id,`+ ` projections.idp_login_policy_links3.idp_id,`+
` projections.idps2.name,`+ ` projections.idps2.name,`+
` projections.idps2.type`+ ` projections.idps2.type`+
` FROM projections.login_policies2`+ ` FROM projections.login_policies3`+
` LEFT JOIN projections.idp_login_policy_links3 ON `+ ` LEFT JOIN projections.idp_login_policy_links3 ON `+
` projections.login_policies2.aggregate_id = projections.idp_login_policy_links3.aggregate_id`+ ` projections.login_policies3.aggregate_id = projections.idp_login_policy_links3.aggregate_id`+
` LEFT JOIN projections.idps2 ON`+ ` LEFT JOIN projections.idps2 ON`+
` projections.idp_login_policy_links3.idp_id = projections.idps2.id`), ` projections.idp_login_policy_links3.idp_id = projections.idps2.id`),
nil, nil,
@ -76,33 +78,35 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyQuery, prepare: prepareLoginPolicyQuery,
want: want{ want: want{
sqlExpectations: mockQuery( sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies2.aggregate_id,`+ regexp.QuoteMeta(`SELECT projections.login_policies3.aggregate_id,`+
` projections.login_policies2.creation_date,`+ ` projections.login_policies3.creation_date,`+
` projections.login_policies2.change_date,`+ ` projections.login_policies3.change_date,`+
` projections.login_policies2.sequence,`+ ` projections.login_policies3.sequence,`+
` projections.login_policies2.allow_register,`+ ` projections.login_policies3.allow_register,`+
` projections.login_policies2.allow_username_password,`+ ` projections.login_policies3.allow_username_password,`+
` projections.login_policies2.allow_external_idps,`+ ` projections.login_policies3.allow_external_idps,`+
` projections.login_policies2.force_mfa,`+ ` projections.login_policies3.force_mfa,`+
` projections.login_policies2.second_factors,`+ ` projections.login_policies3.second_factors,`+
` projections.login_policies2.multi_factors,`+ ` projections.login_policies3.multi_factors,`+
` projections.login_policies2.passwordless_type,`+ ` projections.login_policies3.passwordless_type,`+
` projections.login_policies2.is_default,`+ ` projections.login_policies3.is_default,`+
` projections.login_policies2.hide_password_reset,`+ ` projections.login_policies3.hide_password_reset,`+
` projections.login_policies2.ignore_unknown_usernames,`+ ` projections.login_policies3.ignore_unknown_usernames,`+
` projections.login_policies2.allow_domain_discovery,`+ ` projections.login_policies3.allow_domain_discovery,`+
` projections.login_policies2.default_redirect_uri,`+ ` projections.login_policies3.disable_login_with_email,`+
` projections.login_policies2.password_check_lifetime,`+ ` projections.login_policies3.disable_login_with_phone,`+
` projections.login_policies2.external_login_check_lifetime,`+ ` projections.login_policies3.default_redirect_uri,`+
` projections.login_policies2.mfa_init_skip_lifetime,`+ ` projections.login_policies3.password_check_lifetime,`+
` projections.login_policies2.second_factor_check_lifetime,`+ ` projections.login_policies3.external_login_check_lifetime,`+
` projections.login_policies2.multi_factor_check_lifetime,`+ ` projections.login_policies3.mfa_init_skip_lifetime,`+
` projections.login_policies3.second_factor_check_lifetime,`+
` projections.login_policies3.multi_factor_check_lifetime,`+
` projections.idp_login_policy_links3.idp_id,`+ ` projections.idp_login_policy_links3.idp_id,`+
` projections.idps2.name,`+ ` projections.idps2.name,`+
` projections.idps2.type`+ ` projections.idps2.type`+
` FROM projections.login_policies2`+ ` FROM projections.login_policies3`+
` LEFT JOIN projections.idp_login_policy_links3 ON `+ ` LEFT JOIN projections.idp_login_policy_links3 ON `+
` projections.login_policies2.aggregate_id = projections.idp_login_policy_links3.aggregate_id`+ ` projections.login_policies3.aggregate_id = projections.idp_login_policy_links3.aggregate_id`+
` LEFT JOIN projections.idps2 ON`+ ` LEFT JOIN projections.idps2 ON`+
` projections.idp_login_policy_links3.idp_id = projections.idps2.id`), ` projections.idp_login_policy_links3.idp_id = projections.idps2.id`),
[]string{ []string{
@ -121,6 +125,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
"hide_password_reset", "hide_password_reset",
"ignore_unknown_usernames", "ignore_unknown_usernames",
"allow_domain_discovery", "allow_domain_discovery",
"disable_login_with_email",
"disable_login_with_phone",
"default_redirect_uri", "default_redirect_uri",
"password_check_lifetime", "password_check_lifetime",
"external_login_check_lifetime", "external_login_check_lifetime",
@ -147,6 +153,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
"https://example.com/redirect", "https://example.com/redirect",
time.Hour * 2, time.Hour * 2,
time.Hour * 2, time.Hour * 2,
@ -175,6 +183,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
HidePasswordReset: true, HidePasswordReset: true,
IgnoreUnknownUsernames: true, IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true, AllowDomainDiscovery: true,
DisableLoginWithEmail: true,
DisableLoginWithPhone: true,
DefaultRedirectURI: "https://example.com/redirect", DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 2, PasswordCheckLifetime: time.Hour * 2,
ExternalLoginCheckLifetime: time.Hour * 2, ExternalLoginCheckLifetime: time.Hour * 2,
@ -195,33 +205,35 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyQuery, prepare: prepareLoginPolicyQuery,
want: want{ want: want{
sqlExpectations: mockQueryErr( sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.login_policies2.aggregate_id,`+ regexp.QuoteMeta(`SELECT projections.login_policies3.aggregate_id,`+
` projections.login_policies2.creation_date,`+ ` projections.login_policies3.creation_date,`+
` projections.login_policies2.change_date,`+ ` projections.login_policies3.change_date,`+
` projections.login_policies2.sequence,`+ ` projections.login_policies3.sequence,`+
` projections.login_policies2.allow_register,`+ ` projections.login_policies3.allow_register,`+
` projections.login_policies2.allow_username_password,`+ ` projections.login_policies3.allow_username_password,`+
` projections.login_policies2.allow_external_idps,`+ ` projections.login_policies3.allow_external_idps,`+
` projections.login_policies2.force_mfa,`+ ` projections.login_policies3.force_mfa,`+
` projections.login_policies2.second_factors,`+ ` projections.login_policies3.second_factors,`+
` projections.login_policies2.multi_factors,`+ ` projections.login_policies3.multi_factors,`+
` projections.login_policies2.passwordless_type,`+ ` projections.login_policies3.passwordless_type,`+
` projections.login_policies2.is_default,`+ ` projections.login_policies3.is_default,`+
` projections.login_policies2.hide_password_reset,`+ ` projections.login_policies3.hide_password_reset,`+
` projections.login_policies2.ignore_unknown_usernames,`+ ` projections.login_policies3.ignore_unknown_usernames,`+
` projections.login_policies2.allow_domain_discovery,`+ ` projections.login_policies3.allow_domain_discovery,`+
` projections.login_policies2.default_redirect_uri,`+ ` projections.login_policies3.disable_login_with_email,`+
` projections.login_policies2.password_check_lifetime,`+ ` projections.login_policies3.disable_login_with_phone,`+
` projections.login_policies2.external_login_check_lifetime,`+ ` projections.login_policies3.default_redirect_uri,`+
` projections.login_policies2.mfa_init_skip_lifetime,`+ ` projections.login_policies3.password_check_lifetime,`+
` projections.login_policies2.second_factor_check_lifetime,`+ ` projections.login_policies3.external_login_check_lifetime,`+
` projections.login_policies2.multi_factor_check_lifetime,`+ ` projections.login_policies3.mfa_init_skip_lifetime,`+
` projections.login_policies3.second_factor_check_lifetime,`+
` projections.login_policies3.multi_factor_check_lifetime,`+
` projections.idp_login_policy_links3.idp_id,`+ ` projections.idp_login_policy_links3.idp_id,`+
` projections.idps2.name,`+ ` projections.idps2.name,`+
` projections.idps2.type`+ ` projections.idps2.type`+
` FROM projections.login_policies2`+ ` FROM projections.login_policies3`+
` LEFT JOIN projections.idp_login_policy_links3 ON `+ ` LEFT JOIN projections.idp_login_policy_links3 ON `+
` projections.login_policies2.aggregate_id = projections.idp_login_policy_links3.aggregate_id`+ ` projections.login_policies3.aggregate_id = projections.idp_login_policy_links3.aggregate_id`+
` LEFT JOIN projections.idps2 ON`+ ` LEFT JOIN projections.idps2 ON`+
` projections.idp_login_policy_links3.idp_id = projections.idps2.id`), ` projections.idp_login_policy_links3.idp_id = projections.idps2.id`),
sql.ErrConnDone, sql.ErrConnDone,
@ -240,8 +252,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicy2FAsQuery, prepare: prepareLoginPolicy2FAsQuery,
want: want{ want: want{
sqlExpectations: mockQuery( sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies2.second_factors`+ regexp.QuoteMeta(`SELECT projections.login_policies3.second_factors`+
` FROM projections.login_policies2`), ` FROM projections.login_policies3`),
[]string{ []string{
"second_factors", "second_factors",
}, },
@ -261,8 +273,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicy2FAsQuery, prepare: prepareLoginPolicy2FAsQuery,
want: want{ want: want{
sqlExpectations: mockQuery( sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies2.second_factors`+ regexp.QuoteMeta(`SELECT projections.login_policies3.second_factors`+
` FROM projections.login_policies2`), ` FROM projections.login_policies3`),
[]string{ []string{
"second_factors", "second_factors",
}, },
@ -283,8 +295,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicy2FAsQuery, prepare: prepareLoginPolicy2FAsQuery,
want: want{ want: want{
sqlExpectations: mockQuery( sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies2.second_factors`+ regexp.QuoteMeta(`SELECT projections.login_policies3.second_factors`+
` FROM projections.login_policies2`), ` FROM projections.login_policies3`),
[]string{ []string{
"second_factors", "second_factors",
}, },
@ -300,8 +312,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicy2FAsQuery, prepare: prepareLoginPolicy2FAsQuery,
want: want{ want: want{
sqlExpectations: mockQueryErr( sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.login_policies2.second_factors`+ regexp.QuoteMeta(`SELECT projections.login_policies3.second_factors`+
` FROM projections.login_policies2`), ` FROM projections.login_policies3`),
sql.ErrConnDone, sql.ErrConnDone,
), ),
err: func(err error) (error, bool) { err: func(err error) (error, bool) {
@ -318,8 +330,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyMFAsQuery, prepare: prepareLoginPolicyMFAsQuery,
want: want{ want: want{
sqlExpectations: mockQuery( sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies2.multi_factors`+ regexp.QuoteMeta(`SELECT projections.login_policies3.multi_factors`+
` FROM projections.login_policies2`), ` FROM projections.login_policies3`),
[]string{ []string{
"multi_factors", "multi_factors",
}, },
@ -339,8 +351,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyMFAsQuery, prepare: prepareLoginPolicyMFAsQuery,
want: want{ want: want{
sqlExpectations: mockQuery( sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies2.multi_factors`+ regexp.QuoteMeta(`SELECT projections.login_policies3.multi_factors`+
` FROM projections.login_policies2`), ` FROM projections.login_policies3`),
[]string{ []string{
"multi_factors", "multi_factors",
}, },
@ -361,8 +373,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyMFAsQuery, prepare: prepareLoginPolicyMFAsQuery,
want: want{ want: want{
sqlExpectations: mockQuery( sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies2.multi_factors`+ regexp.QuoteMeta(`SELECT projections.login_policies3.multi_factors`+
` FROM projections.login_policies2`), ` FROM projections.login_policies3`),
[]string{ []string{
"multi_factors", "multi_factors",
}, },
@ -378,8 +390,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyMFAsQuery, prepare: prepareLoginPolicyMFAsQuery,
want: want{ want: want{
sqlExpectations: mockQueryErr( sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.login_policies2.multi_factors`+ regexp.QuoteMeta(`SELECT projections.login_policies3.multi_factors`+
` FROM projections.login_policies2`), ` FROM projections.login_policies3`),
sql.ErrConnDone, sql.ErrConnDone,
), ),
err: func(err error) (error, bool) { err: func(err error) (error, bool) {

View File

@ -20,18 +20,18 @@ var (
", members.user_id" + ", members.user_id" +
", members.roles" + ", members.roles" +
", projections.login_names.login_name" + ", projections.login_names.login_name" +
", projections.users3_humans.email" + ", projections.users4_humans.email" +
", projections.users3_humans.first_name" + ", projections.users4_humans.first_name" +
", projections.users3_humans.last_name" + ", projections.users4_humans.last_name" +
", projections.users3_humans.display_name" + ", projections.users4_humans.display_name" +
", projections.users3_machines.name" + ", projections.users4_machines.name" +
", projections.users3_humans.avatar_key" + ", projections.users4_humans.avatar_key" +
", COUNT(*) OVER () " + ", COUNT(*) OVER () " +
"FROM projections.org_members2 AS members " + "FROM projections.org_members2 AS members " +
"LEFT JOIN projections.users3_humans " + "LEFT JOIN projections.users4_humans " +
"ON members.user_id = projections.users3_humans.user_id " + "ON members.user_id = projections.users4_humans.user_id " +
"LEFT JOIN projections.users3_machines " + "LEFT JOIN projections.users4_machines " +
"ON members.user_id = projections.users3_machines.user_id " + "ON members.user_id = projections.users4_machines.user_id " +
"LEFT JOIN projections.login_names " + "LEFT JOIN projections.login_names " +
"ON members.user_id = projections.login_names.user_id " + "ON members.user_id = projections.login_names.user_id " +
"WHERE projections.login_names.is_primary = $1") "WHERE projections.login_names.is_primary = $1")

View File

@ -20,18 +20,18 @@ var (
", members.user_id" + ", members.user_id" +
", members.roles" + ", members.roles" +
", projections.login_names.login_name" + ", projections.login_names.login_name" +
", projections.users3_humans.email" + ", projections.users4_humans.email" +
", projections.users3_humans.first_name" + ", projections.users4_humans.first_name" +
", projections.users3_humans.last_name" + ", projections.users4_humans.last_name" +
", projections.users3_humans.display_name" + ", projections.users4_humans.display_name" +
", projections.users3_machines.name" + ", projections.users4_machines.name" +
", projections.users3_humans.avatar_key" + ", projections.users4_humans.avatar_key" +
", COUNT(*) OVER () " + ", COUNT(*) OVER () " +
"FROM projections.project_grant_members2 AS members " + "FROM projections.project_grant_members2 AS members " +
"LEFT JOIN projections.users3_humans " + "LEFT JOIN projections.users4_humans " +
"ON members.user_id = projections.users3_humans.user_id " + "ON members.user_id = projections.users4_humans.user_id " +
"LEFT JOIN projections.users3_machines " + "LEFT JOIN projections.users4_machines " +
"ON members.user_id = projections.users3_machines.user_id " + "ON members.user_id = projections.users4_machines.user_id " +
"LEFT JOIN projections.login_names " + "LEFT JOIN projections.login_names " +
"ON members.user_id = projections.login_names.user_id " + "ON members.user_id = projections.login_names.user_id " +
"LEFT JOIN projections.project_grants2 " + "LEFT JOIN projections.project_grants2 " +

View File

@ -20,18 +20,18 @@ var (
", members.user_id" + ", members.user_id" +
", members.roles" + ", members.roles" +
", projections.login_names.login_name" + ", projections.login_names.login_name" +
", projections.users3_humans.email" + ", projections.users4_humans.email" +
", projections.users3_humans.first_name" + ", projections.users4_humans.first_name" +
", projections.users3_humans.last_name" + ", projections.users4_humans.last_name" +
", projections.users3_humans.display_name" + ", projections.users4_humans.display_name" +
", projections.users3_machines.name" + ", projections.users4_machines.name" +
", projections.users3_humans.avatar_key" + ", projections.users4_humans.avatar_key" +
", COUNT(*) OVER () " + ", COUNT(*) OVER () " +
"FROM projections.project_members2 AS members " + "FROM projections.project_members2 AS members " +
"LEFT JOIN projections.users3_humans " + "LEFT JOIN projections.users4_humans " +
"ON members.user_id = projections.users3_humans.user_id " + "ON members.user_id = projections.users4_humans.user_id " +
"LEFT JOIN projections.users3_machines " + "LEFT JOIN projections.users4_machines " +
"ON members.user_id = projections.users3_machines.user_id " + "ON members.user_id = projections.users4_machines.user_id " +
"LEFT JOIN projections.login_names " + "LEFT JOIN projections.login_names " +
"ON members.user_id = projections.login_names.user_id " + "ON members.user_id = projections.login_names.user_id " +
"WHERE projections.login_names.is_primary = $1") "WHERE projections.login_names.is_primary = $1")

View File

@ -13,7 +13,7 @@ import (
) )
const ( const (
LoginPolicyTable = "projections.login_policies2" LoginPolicyTable = "projections.login_policies3"
LoginPolicyIDCol = "aggregate_id" LoginPolicyIDCol = "aggregate_id"
LoginPolicyInstanceIDCol = "instance_id" LoginPolicyInstanceIDCol = "instance_id"
@ -31,6 +31,8 @@ const (
LoginPolicyHidePWResetCol = "hide_password_reset" LoginPolicyHidePWResetCol = "hide_password_reset"
IgnoreUnknownUsernames = "ignore_unknown_usernames" IgnoreUnknownUsernames = "ignore_unknown_usernames"
AllowDomainDiscovery = "allow_domain_discovery" AllowDomainDiscovery = "allow_domain_discovery"
DisableLoginWithEmail = "disable_login_with_email"
DisableLoginWithPhone = "disable_login_with_phone"
DefaultRedirectURI = "default_redirect_uri" DefaultRedirectURI = "default_redirect_uri"
PasswordCheckLifetimeCol = "password_check_lifetime" PasswordCheckLifetimeCol = "password_check_lifetime"
ExternalLoginCheckLifetimeCol = "external_login_check_lifetime" ExternalLoginCheckLifetimeCol = "external_login_check_lifetime"
@ -65,6 +67,8 @@ func newLoginPolicyProjection(ctx context.Context, config crdb.StatementHandlerC
crdb.NewColumn(LoginPolicyHidePWResetCol, crdb.ColumnTypeBool), crdb.NewColumn(LoginPolicyHidePWResetCol, crdb.ColumnTypeBool),
crdb.NewColumn(IgnoreUnknownUsernames, crdb.ColumnTypeBool), crdb.NewColumn(IgnoreUnknownUsernames, crdb.ColumnTypeBool),
crdb.NewColumn(AllowDomainDiscovery, crdb.ColumnTypeBool), crdb.NewColumn(AllowDomainDiscovery, crdb.ColumnTypeBool),
crdb.NewColumn(DisableLoginWithEmail, crdb.ColumnTypeBool),
crdb.NewColumn(DisableLoginWithPhone, crdb.ColumnTypeBool),
crdb.NewColumn(DefaultRedirectURI, crdb.ColumnTypeText, crdb.Nullable()), crdb.NewColumn(DefaultRedirectURI, crdb.ColumnTypeText, crdb.Nullable()),
crdb.NewColumn(PasswordCheckLifetimeCol, crdb.ColumnTypeInt64), crdb.NewColumn(PasswordCheckLifetimeCol, crdb.ColumnTypeInt64),
crdb.NewColumn(ExternalLoginCheckLifetimeCol, crdb.ColumnTypeInt64), crdb.NewColumn(ExternalLoginCheckLifetimeCol, crdb.ColumnTypeInt64),
@ -175,6 +179,8 @@ func (p *loginPolicyProjection) reduceLoginPolicyAdded(event eventstore.Event) (
handler.NewCol(LoginPolicyHidePWResetCol, policyEvent.HidePasswordReset), handler.NewCol(LoginPolicyHidePWResetCol, policyEvent.HidePasswordReset),
handler.NewCol(IgnoreUnknownUsernames, policyEvent.IgnoreUnknownUsernames), handler.NewCol(IgnoreUnknownUsernames, policyEvent.IgnoreUnknownUsernames),
handler.NewCol(AllowDomainDiscovery, policyEvent.AllowDomainDiscovery), handler.NewCol(AllowDomainDiscovery, policyEvent.AllowDomainDiscovery),
handler.NewCol(DisableLoginWithEmail, policyEvent.DisableLoginWithEmail),
handler.NewCol(DisableLoginWithPhone, policyEvent.DisableLoginWithPhone),
handler.NewCol(DefaultRedirectURI, policyEvent.DefaultRedirectURI), handler.NewCol(DefaultRedirectURI, policyEvent.DefaultRedirectURI),
handler.NewCol(PasswordCheckLifetimeCol, policyEvent.PasswordCheckLifetime), handler.NewCol(PasswordCheckLifetimeCol, policyEvent.PasswordCheckLifetime),
handler.NewCol(ExternalLoginCheckLifetimeCol, policyEvent.ExternalLoginCheckLifetime), handler.NewCol(ExternalLoginCheckLifetimeCol, policyEvent.ExternalLoginCheckLifetime),
@ -223,6 +229,12 @@ func (p *loginPolicyProjection) reduceLoginPolicyChanged(event eventstore.Event)
if policyEvent.AllowDomainDiscovery != nil { if policyEvent.AllowDomainDiscovery != nil {
cols = append(cols, handler.NewCol(AllowDomainDiscovery, *policyEvent.AllowDomainDiscovery)) cols = append(cols, handler.NewCol(AllowDomainDiscovery, *policyEvent.AllowDomainDiscovery))
} }
if policyEvent.DisableLoginWithEmail != nil {
cols = append(cols, handler.NewCol(DisableLoginWithEmail, *policyEvent.DisableLoginWithEmail))
}
if policyEvent.DisableLoginWithPhone != nil {
cols = append(cols, handler.NewCol(DisableLoginWithPhone, *policyEvent.DisableLoginWithPhone))
}
if policyEvent.DefaultRedirectURI != nil { if policyEvent.DefaultRedirectURI != nil {
cols = append(cols, handler.NewCol(DefaultRedirectURI, *policyEvent.DefaultRedirectURI)) cols = append(cols, handler.NewCol(DefaultRedirectURI, *policyEvent.DefaultRedirectURI))
} }

View File

@ -37,6 +37,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"hidePasswordReset": true, "hidePasswordReset": true,
"ignoreUnknownUsernames": true, "ignoreUnknownUsernames": true,
"allowDomainDiscovery": true, "allowDomainDiscovery": true,
"disableLoginWithEmail": true,
"disableLoginWithPhone": true,
"passwordlessType": 1, "passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect", "defaultRedirectURI": "https://example.com/redirect",
"passwordCheckLifetime": 10000000, "passwordCheckLifetime": 10000000,
@ -56,7 +58,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.login_policies2 (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)", expectedStmt: "INSERT INTO projections.login_policies3 (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -72,6 +74,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
"https://example.com/redirect", "https://example.com/redirect",
time.Millisecond * 10, time.Millisecond * 10,
time.Millisecond * 10, time.Millisecond * 10,
@ -99,6 +103,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"hidePasswordReset": true, "hidePasswordReset": true,
"ignoreUnknownUsernames": true, "ignoreUnknownUsernames": true,
"allowDomainDiscovery": true, "allowDomainDiscovery": true,
"disableLoginWithEmail": true,
"disableLoginWithPhone": true,
"passwordlessType": 1, "passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect", "defaultRedirectURI": "https://example.com/redirect",
"passwordCheckLifetime": 10000000, "passwordCheckLifetime": 10000000,
@ -117,7 +123,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) WHERE (aggregate_id = $17)", expectedStmt: "UPDATE projections.login_policies3 SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) WHERE (aggregate_id = $19)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -129,6 +135,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
"https://example.com/redirect", "https://example.com/redirect",
time.Millisecond * 10, time.Millisecond * 10,
time.Millisecond * 10, time.Millisecond * 10,
@ -162,7 +170,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4)", expectedStmt: "UPDATE projections.login_policies3 SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -194,7 +202,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4)", expectedStmt: "UPDATE projections.login_policies3 SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -224,7 +232,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.login_policies2 WHERE (aggregate_id = $1)", expectedStmt: "DELETE FROM projections.login_policies3 WHERE (aggregate_id = $1)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
}, },
@ -253,7 +261,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4)", expectedStmt: "UPDATE projections.login_policies3 SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -285,7 +293,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4)", expectedStmt: "UPDATE projections.login_policies3 SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -312,6 +320,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"hidePasswordReset": true, "hidePasswordReset": true,
"ignoreUnknownUsernames": true, "ignoreUnknownUsernames": true,
"allowDomainDiscovery": true, "allowDomainDiscovery": true,
"disableLoginWithEmail": true,
"disableLoginWithPhone": true,
"passwordlessType": 1, "passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect", "defaultRedirectURI": "https://example.com/redirect",
"passwordCheckLifetime": 10000000, "passwordCheckLifetime": 10000000,
@ -330,7 +340,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.login_policies2 (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)", expectedStmt: "INSERT INTO projections.login_policies3 (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -346,6 +356,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
"https://example.com/redirect", "https://example.com/redirect",
time.Millisecond * 10, time.Millisecond * 10,
time.Millisecond * 10, time.Millisecond * 10,
@ -373,6 +385,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"hidePasswordReset": true, "hidePasswordReset": true,
"ignoreUnknownUsernames": true, "ignoreUnknownUsernames": true,
"allowDomainDiscovery": true, "allowDomainDiscovery": true,
"disableLoginWithEmail": true,
"disableLoginWithPhone": true,
"passwordlessType": 1, "passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect" "defaultRedirectURI": "https://example.com/redirect"
}`), }`),
@ -386,7 +400,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, default_redirect_uri) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) WHERE (aggregate_id = $12)", expectedStmt: "UPDATE projections.login_policies3 SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) WHERE (aggregate_id = $14)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -398,6 +412,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
true, true,
true, true,
true, true,
true,
true,
"https://example.com/redirect", "https://example.com/redirect",
"agg-id", "agg-id",
}, },
@ -426,7 +442,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4)", expectedStmt: "UPDATE projections.login_policies3 SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -458,7 +474,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4)", expectedStmt: "UPDATE projections.login_policies3 SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -490,7 +506,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4)", expectedStmt: "UPDATE projections.login_policies3 SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -522,7 +538,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4)", expectedStmt: "UPDATE projections.login_policies3 SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),

View File

@ -17,7 +17,7 @@ type userProjection struct {
} }
const ( const (
UserTable = "projections.users3" UserTable = "projections.users4"
UserHumanTable = UserTable + "_" + UserHumanSuffix UserHumanTable = UserTable + "_" + UserHumanSuffix
UserMachineTable = UserTable + "_" + UserMachineSuffix UserMachineTable = UserTable + "_" + UserMachineSuffix
UserNotifyTable = UserTable + "_" + UserNotifySuffix UserNotifyTable = UserTable + "_" + UserNotifySuffix
@ -88,9 +88,9 @@ func newUserProjection(ctx context.Context, config crdb.StatementHandlerConfig)
crdb.NewColumn(UserTypeCol, crdb.ColumnTypeEnum), crdb.NewColumn(UserTypeCol, crdb.ColumnTypeEnum),
}, },
crdb.NewPrimaryKey(UserIDCol, UserInstanceIDCol), crdb.NewPrimaryKey(UserIDCol, UserInstanceIDCol),
crdb.WithIndex(crdb.NewIndex("username_idx", []string{UserUsernameCol})), crdb.WithIndex(crdb.NewIndex("username_idx4", []string{UserUsernameCol})),
crdb.WithIndex(crdb.NewIndex("user_ro_idx", []string{UserResourceOwnerCol})), crdb.WithIndex(crdb.NewIndex("user_ro_idx4", []string{UserResourceOwnerCol})),
crdb.WithConstraint(crdb.NewConstraint("user_id_unique", []string{UserIDCol})), crdb.WithConstraint(crdb.NewConstraint("user_id_unique4", []string{UserIDCol})),
), ),
crdb.NewSuffixedTable([]*crdb.Column{ crdb.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(HumanUserIDCol, crdb.ColumnTypeText), crdb.NewColumn(HumanUserIDCol, crdb.ColumnTypeText),
@ -109,7 +109,7 @@ func newUserProjection(ctx context.Context, config crdb.StatementHandlerConfig)
}, },
crdb.NewPrimaryKey(HumanUserIDCol, HumanUserInstanceIDCol), crdb.NewPrimaryKey(HumanUserIDCol, HumanUserInstanceIDCol),
UserHumanSuffix, UserHumanSuffix,
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_human_ref_user")), crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_human_ref_user4")),
), ),
crdb.NewSuffixedTable([]*crdb.Column{ crdb.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(MachineUserIDCol, crdb.ColumnTypeText), crdb.NewColumn(MachineUserIDCol, crdb.ColumnTypeText),
@ -119,7 +119,7 @@ func newUserProjection(ctx context.Context, config crdb.StatementHandlerConfig)
}, },
crdb.NewPrimaryKey(MachineUserIDCol, MachineUserInstanceIDCol), crdb.NewPrimaryKey(MachineUserIDCol, MachineUserInstanceIDCol),
UserMachineSuffix, UserMachineSuffix,
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_machine_ref_user")), crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_machine_ref_user4")),
), ),
crdb.NewSuffixedTable([]*crdb.Column{ crdb.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(NotifyUserIDCol, crdb.ColumnTypeText), crdb.NewColumn(NotifyUserIDCol, crdb.ColumnTypeText),
@ -132,7 +132,7 @@ func newUserProjection(ctx context.Context, config crdb.StatementHandlerConfig)
}, },
crdb.NewPrimaryKey(NotifyUserIDCol, NotifyInstanceIDCol), crdb.NewPrimaryKey(NotifyUserIDCol, NotifyInstanceIDCol),
UserNotifySuffix, UserNotifySuffix,
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_notify_ref_user")), crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_notify_ref_user4")),
), ),
) )
p.StatementHandler = crdb.NewStatementHandler(ctx, config) p.StatementHandler = crdb.NewStatementHandler(ctx, config)
@ -708,20 +708,9 @@ func (p *userProjection) reduceHumanPhoneVerified(event eventstore.Event) (*hand
}, },
crdb.WithTableSuffix(UserHumanSuffix), crdb.WithTableSuffix(UserHumanSuffix),
), ),
crdb.AddCopyStatement( crdb.AddUpdateStatement(
[]handler.Column{ []handler.Column{
handler.NewCol(NotifyUserIDCol, nil), crdb.NewCopyCol(NotifyVerifiedPhoneCol, NotifyLastPhoneCol),
handler.NewCol(NotifyInstanceIDCol, nil),
},
[]handler.Column{
handler.NewCol(NotifyUserIDCol, nil),
handler.NewCol(NotifyInstanceIDCol, nil),
handler.NewCol(NotifyLastPhoneCol, nil),
},
[]handler.Column{
handler.NewCol(NotifyUserIDCol, nil),
handler.NewCol(NotifyInstanceIDCol, nil),
handler.NewCol(NotifyVerifiedPhoneCol, nil),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond(NotifyUserIDCol, e.Aggregate().ID), handler.NewCond(NotifyUserIDCol, e.Aggregate().ID),
@ -802,20 +791,9 @@ func (p *userProjection) reduceHumanEmailVerified(event eventstore.Event) (*hand
}, },
crdb.WithTableSuffix(UserHumanSuffix), crdb.WithTableSuffix(UserHumanSuffix),
), ),
crdb.AddCopyStatement( crdb.AddUpdateStatement(
[]handler.Column{ []handler.Column{
handler.NewCol(NotifyUserIDCol, nil), crdb.NewCopyCol(NotifyVerifiedEmailCol, NotifyLastEmailCol),
handler.NewCol(NotifyInstanceIDCol, nil),
},
[]handler.Column{
handler.NewCol(NotifyUserIDCol, nil),
handler.NewCol(NotifyInstanceIDCol, nil),
handler.NewCol(NotifyLastEmailCol, nil),
},
[]handler.Column{
handler.NewCol(NotifyUserIDCol, nil),
handler.NewCol(NotifyInstanceIDCol, nil),
handler.NewCol(NotifyVerifiedEmailCol, nil),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond(NotifyUserIDCol, e.Aggregate().ID), handler.NewCond(NotifyUserIDCol, e.Aggregate().ID),

View File

@ -50,7 +50,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.users3 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedStmt: "INSERT INTO projections.users4 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
@ -64,7 +64,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", expectedStmt: "INSERT INTO projections.users4_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -79,7 +79,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedStmt: "INSERT INTO projections.users4_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -120,7 +120,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.users3 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedStmt: "INSERT INTO projections.users4 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
@ -134,7 +134,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", expectedStmt: "INSERT INTO projections.users4_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -149,7 +149,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedStmt: "INSERT INTO projections.users4_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -185,7 +185,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.users3 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedStmt: "INSERT INTO projections.users4 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
@ -199,7 +199,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", expectedStmt: "INSERT INTO projections.users4_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -214,7 +214,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedStmt: "INSERT INTO projections.users4_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -255,7 +255,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.users3 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedStmt: "INSERT INTO projections.users4 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
@ -269,7 +269,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", expectedStmt: "INSERT INTO projections.users4_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -284,7 +284,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedStmt: "INSERT INTO projections.users4_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -325,7 +325,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.users3 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedStmt: "INSERT INTO projections.users4 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
@ -339,7 +339,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", expectedStmt: "INSERT INTO projections.users4_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -354,7 +354,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedStmt: "INSERT INTO projections.users4_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -390,7 +390,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.users3 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedStmt: "INSERT INTO projections.users4 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
@ -404,7 +404,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", expectedStmt: "INSERT INTO projections.users4_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -419,7 +419,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedStmt: "INSERT INTO projections.users4_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -450,7 +450,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
domain.UserStateInitial, domain.UserStateInitial,
"agg-id", "agg-id",
@ -479,7 +479,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
domain.UserStateInitial, domain.UserStateInitial,
"agg-id", "agg-id",
@ -508,7 +508,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
domain.UserStateActive, domain.UserStateActive,
"agg-id", "agg-id",
@ -537,7 +537,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
domain.UserStateActive, domain.UserStateActive,
"agg-id", "agg-id",
@ -566,7 +566,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.users4 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
domain.UserStateLocked, domain.UserStateLocked,
@ -597,7 +597,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.users4 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
domain.UserStateActive, domain.UserStateActive,
@ -628,7 +628,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.users4 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
domain.UserStateInactive, domain.UserStateInactive,
@ -659,7 +659,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.users4 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
domain.UserStateActive, domain.UserStateActive,
@ -690,7 +690,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.users3 WHERE (id = $1) AND (instance_id = $2)", expectedStmt: "DELETE FROM projections.users4 WHERE (id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -720,7 +720,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.users4 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
"username", "username",
@ -753,7 +753,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.users4 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
"id@temporary.domain", "id@temporary.domain",
@ -791,7 +791,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -800,7 +800,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", expectedStmt: "UPDATE projections.users4_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"first-name", "first-name",
"last-name", "last-name",
@ -841,7 +841,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -850,7 +850,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", expectedStmt: "UPDATE projections.users4_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"first-name", "first-name",
"last-name", "last-name",
@ -886,7 +886,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -895,7 +895,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"+41 00 000 00 00", "+41 00 000 00 00",
false, false,
@ -904,7 +904,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
&sql.NullString{String: "+41 00 000 00 00", Valid: true}, &sql.NullString{String: "+41 00 000 00 00", Valid: true},
"agg-id", "agg-id",
@ -935,7 +935,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -944,7 +944,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"+41 00 000 00 00", "+41 00 000 00 00",
false, false,
@ -953,7 +953,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
&sql.NullString{String: "+41 00 000 00 00", Valid: true}, &sql.NullString{String: "+41 00 000 00 00", Valid: true},
"agg-id", "agg-id",
@ -982,7 +982,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -991,7 +991,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
nil, nil,
nil, nil,
@ -1000,7 +1000,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
nil, nil,
nil, nil,
@ -1030,7 +1030,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -1039,7 +1039,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
nil, nil,
nil, nil,
@ -1048,7 +1048,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
nil, nil,
nil, nil,
@ -1078,7 +1078,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -1087,7 +1087,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
true, true,
"agg-id", "agg-id",
@ -1095,7 +1095,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_notifications (user_id, instance_id, verified_phone) SELECT user_id, instance_id, last_phone FROM projections.users3_notifications AS copy_table WHERE copy_table.user_id = $1 AND copy_table.instance_id = $2 ON CONFLICT (user_id, instance_id) DO UPDATE SET (user_id, instance_id, verified_phone) = (EXCLUDED.user_id, EXCLUDED.instance_id, EXCLUDED.last_phone)", expectedStmt: "UPDATE projections.users4_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -1123,7 +1123,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -1132,7 +1132,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
true, true,
"agg-id", "agg-id",
@ -1140,7 +1140,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_notifications (user_id, instance_id, verified_phone) SELECT user_id, instance_id, last_phone FROM projections.users3_notifications AS copy_table WHERE copy_table.user_id = $1 AND copy_table.instance_id = $2 ON CONFLICT (user_id, instance_id) DO UPDATE SET (user_id, instance_id, verified_phone) = (EXCLUDED.user_id, EXCLUDED.instance_id, EXCLUDED.last_phone)", expectedStmt: "UPDATE projections.users4_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -1170,7 +1170,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -1179,7 +1179,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"email@zitadel.com", "email@zitadel.com",
false, false,
@ -1188,7 +1188,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
&sql.NullString{String: "email@zitadel.com", Valid: true}, &sql.NullString{String: "email@zitadel.com", Valid: true},
"agg-id", "agg-id",
@ -1219,7 +1219,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -1228,7 +1228,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"email@zitadel.com", "email@zitadel.com",
false, false,
@ -1237,7 +1237,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
&sql.NullString{String: "email@zitadel.com", Valid: true}, &sql.NullString{String: "email@zitadel.com", Valid: true},
"agg-id", "agg-id",
@ -1266,7 +1266,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -1275,7 +1275,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
true, true,
"agg-id", "agg-id",
@ -1283,7 +1283,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_notifications (user_id, instance_id, verified_email) SELECT user_id, instance_id, last_email FROM projections.users3_notifications AS copy_table WHERE copy_table.user_id = $1 AND copy_table.instance_id = $2 ON CONFLICT (user_id, instance_id) DO UPDATE SET (user_id, instance_id, verified_email) = (EXCLUDED.user_id, EXCLUDED.instance_id, EXCLUDED.last_email)", expectedStmt: "UPDATE projections.users4_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -1311,7 +1311,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -1320,7 +1320,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
true, true,
"agg-id", "agg-id",
@ -1328,7 +1328,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_notifications (user_id, instance_id, verified_email) SELECT user_id, instance_id, last_email FROM projections.users3_notifications AS copy_table WHERE copy_table.user_id = $1 AND copy_table.instance_id = $2 ON CONFLICT (user_id, instance_id) DO UPDATE SET (user_id, instance_id, verified_email) = (EXCLUDED.user_id, EXCLUDED.instance_id, EXCLUDED.last_email)", expectedStmt: "UPDATE projections.users4_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -1358,7 +1358,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -1367,7 +1367,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"users/agg-id/avatar", "users/agg-id/avatar",
"agg-id", "agg-id",
@ -1396,7 +1396,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -1405,7 +1405,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
nil, nil,
"agg-id", "agg-id",
@ -1437,7 +1437,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.users3 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedStmt: "INSERT INTO projections.users4 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
@ -1451,7 +1451,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_machines (user_id, instance_id, name, description) VALUES ($1, $2, $3, $4)", expectedStmt: "INSERT INTO projections.users4_machines (user_id, instance_id, name, description) VALUES ($1, $2, $3, $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -1485,7 +1485,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.users3 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedStmt: "INSERT INTO projections.users4 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
@ -1499,7 +1499,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.users3_machines (user_id, instance_id, name, description) VALUES ($1, $2, $3, $4)", expectedStmt: "INSERT INTO projections.users4_machines (user_id, instance_id, name, description) VALUES ($1, $2, $3, $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -1532,7 +1532,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -1541,7 +1541,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"machine-name", "machine-name",
"description", "description",
@ -1573,7 +1573,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -1582,7 +1582,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_machines SET name = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4_machines SET name = $1 WHERE (user_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"machine-name", "machine-name",
"agg-id", "agg-id",
@ -1613,7 +1613,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.users3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.users4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -1622,7 +1622,7 @@ func TestUserProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.users3_machines SET description = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.users4_machines SET description = $1 WHERE (user_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"description", "description",
"agg-id", "agg-id",

View File

@ -388,7 +388,7 @@ func (q *Queries) GetHumanPhone(ctx context.Context, userID string, queries ...S
return scan(row) return scan(row)
} }
func (q *Queries) GeNotifyUser(ctx context.Context, shouldTriggered bool, userID string, queries ...SearchQuery) (*NotifyUser, error) { func (q *Queries) GetNotifyUserByID(ctx context.Context, shouldTriggered bool, userID string, queries ...SearchQuery) (*NotifyUser, error) {
if shouldTriggered { if shouldTriggered {
projection.UserProjection.Trigger(ctx) projection.UserProjection.Trigger(ctx)
projection.LoginNameProjection.Trigger(ctx) projection.LoginNameProjection.Trigger(ctx)
@ -411,6 +411,28 @@ func (q *Queries) GeNotifyUser(ctx context.Context, shouldTriggered bool, userID
return scan(row) return scan(row)
} }
func (q *Queries) GetNotifyUser(ctx context.Context, shouldTriggered bool, queries ...SearchQuery) (*NotifyUser, error) {
if shouldTriggered {
projection.UserProjection.Trigger(ctx)
projection.LoginNameProjection.Trigger(ctx)
}
instanceID := authz.GetInstance(ctx).InstanceID()
query, scan := prepareNotifyUserQuery(instanceID)
for _, q := range queries {
query = q.toQuery(query)
}
stmt, args, err := query.Where(sq.Eq{
UserInstanceIDCol.identifier(): instanceID,
}).ToSql()
if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-Err3g", "Errors.Query.SQLStatment")
}
row := q.client.QueryRowContext(ctx, stmt, args...)
return scan(row)
}
func (q *Queries) SearchUsers(ctx context.Context, queries *UserSearchQueries) (*Users, error) { func (q *Queries) SearchUsers(ctx context.Context, queries *UserSearchQueries) (*Users, error) {
query, scan := prepareUsersQuery() query, scan := prepareUsersQuery()
stmt, args, err := queries.toQuery(query). stmt, args, err := queries.toQuery(query).
@ -492,27 +514,39 @@ func NewUserResourceOwnerSearchQuery(value string, comparison TextComparison) (S
} }
func NewUserUsernameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) { func NewUserUsernameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
return NewTextQuery(Column(UserUsernameCol), value, comparison) return NewTextQuery(UserUsernameCol, value, comparison)
} }
func NewUserFirstNameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) { func NewUserFirstNameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
return NewTextQuery(Column(HumanFirstNameCol), value, comparison) return NewTextQuery(HumanFirstNameCol, value, comparison)
} }
func NewUserLastNameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) { func NewUserLastNameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
return NewTextQuery(Column(HumanLastNameCol), value, comparison) return NewTextQuery(HumanLastNameCol, value, comparison)
} }
func NewUserNickNameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) { func NewUserNickNameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
return NewTextQuery(Column(HumanNickNameCol), value, comparison) return NewTextQuery(HumanNickNameCol, value, comparison)
} }
func NewUserDisplayNameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) { func NewUserDisplayNameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
return NewTextQuery(Column(HumanDisplayNameCol), value, comparison) return NewTextQuery(HumanDisplayNameCol, value, comparison)
} }
func NewUserEmailSearchQuery(value string, comparison TextComparison) (SearchQuery, error) { func NewUserEmailSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
return NewTextQuery(Column(HumanEmailCol), value, comparison) return NewTextQuery(HumanEmailCol, value, comparison)
}
func NewUserPhoneSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
return NewTextQuery(HumanPhoneCol, value, comparison)
}
func NewUserVerifiedEmailSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
return NewTextQuery(NotifyVerifiedEmailCol, value, comparison)
}
func NewUserVerifiedPhoneSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
return NewTextQuery(NotifyVerifiedPhoneCol, value, comparison)
} }
func NewUserStateSearchQuery(value int32) (SearchQuery, error) { func NewUserStateSearchQuery(value int32) (SearchQuery, error) {
@ -580,6 +614,7 @@ func prepareUserQuery(instanceID string) (sq.SelectBuilder, func(*sql.Row) (*Use
MachineUserIDCol.identifier(), MachineUserIDCol.identifier(),
MachineNameCol.identifier(), MachineNameCol.identifier(),
MachineDescriptionCol.identifier(), MachineDescriptionCol.identifier(),
countColumn.identifier(),
). ).
From(userTable.identifier()). From(userTable.identifier()).
LeftJoin(join(HumanUserIDCol, UserIDCol)). LeftJoin(join(HumanUserIDCol, UserIDCol)).
@ -589,6 +624,7 @@ func prepareUserQuery(instanceID string) (sq.SelectBuilder, func(*sql.Row) (*Use
PlaceholderFormat(sq.Dollar), PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*User, error) { func(row *sql.Row) (*User, error) {
u := new(User) u := new(User)
var count int
preferredLoginName := sql.NullString{} preferredLoginName := sql.NullString{}
humanID := sql.NullString{} humanID := sql.NullString{}
@ -634,10 +670,11 @@ func prepareUserQuery(instanceID string) (sq.SelectBuilder, func(*sql.Row) (*Use
&machineID, &machineID,
&name, &name,
&description, &description,
&count,
) )
if err != nil { if err != nil || count != 1 {
if errs.Is(err, sql.ErrNoRows) { if errs.Is(err, sql.ErrNoRows) || count != 1 {
return nil, errors.ThrowNotFound(err, "QUERY-Dfbg2", "Errors.User.NotFound") return nil, errors.ThrowNotFound(err, "QUERY-Dfbg2", "Errors.User.NotFound")
} }
return nil, errors.ThrowInternal(err, "QUERY-Bgah2", "Errors.Internal") return nil, errors.ThrowInternal(err, "QUERY-Bgah2", "Errors.Internal")
@ -877,6 +914,7 @@ func prepareNotifyUserQuery(instanceID string) (sq.SelectBuilder, func(*sql.Row)
NotifyPhoneCol.identifier(), NotifyPhoneCol.identifier(),
NotifyVerifiedPhoneCol.identifier(), NotifyVerifiedPhoneCol.identifier(),
NotifyPasswordSetCol.identifier(), NotifyPasswordSetCol.identifier(),
countColumn.identifier(),
). ).
From(userTable.identifier()). From(userTable.identifier()).
LeftJoin(join(HumanUserIDCol, UserIDCol)). LeftJoin(join(HumanUserIDCol, UserIDCol)).
@ -886,6 +924,7 @@ func prepareNotifyUserQuery(instanceID string) (sq.SelectBuilder, func(*sql.Row)
PlaceholderFormat(sq.Dollar), PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*NotifyUser, error) { func(row *sql.Row) (*NotifyUser, error) {
u := new(NotifyUser) u := new(NotifyUser)
var count int
loginNames := database.StringArray{} loginNames := database.StringArray{}
preferredLoginName := sql.NullString{} preferredLoginName := sql.NullString{}
@ -930,10 +969,11 @@ func prepareNotifyUserQuery(instanceID string) (sq.SelectBuilder, func(*sql.Row)
&notifyPhone, &notifyPhone,
&notifyVerifiedPhone, &notifyVerifiedPhone,
&notifyPasswordSet, &notifyPasswordSet,
&count,
) )
if err != nil { if err != nil || count != 1 {
if errs.Is(err, sql.ErrNoRows) { if errs.Is(err, sql.ErrNoRows) || count != 1 {
return nil, errors.ThrowNotFound(err, "QUERY-Dgqd2", "Errors.User.NotFound") return nil, errors.ThrowNotFound(err, "QUERY-Dgqd2", "Errors.User.NotFound")
} }
return nil, errors.ThrowInternal(err, "QUERY-Dbwsg", "Errors.Internal") return nil, errors.ThrowInternal(err, "QUERY-Dbwsg", "Errors.Internal")

View File

@ -23,14 +23,14 @@ var (
", projections.user_grants2.roles" + ", projections.user_grants2.roles" +
", projections.user_grants2.state" + ", projections.user_grants2.state" +
", projections.user_grants2.user_id" + ", projections.user_grants2.user_id" +
", projections.users3.username" + ", projections.users4.username" +
", projections.users3.type" + ", projections.users4.type" +
", projections.users3.resource_owner" + ", projections.users4.resource_owner" +
", projections.users3_humans.first_name" + ", projections.users4_humans.first_name" +
", projections.users3_humans.last_name" + ", projections.users4_humans.last_name" +
", projections.users3_humans.email" + ", projections.users4_humans.email" +
", projections.users3_humans.display_name" + ", projections.users4_humans.display_name" +
", projections.users3_humans.avatar_key" + ", projections.users4_humans.avatar_key" +
", projections.login_names.login_name" + ", projections.login_names.login_name" +
", projections.user_grants2.resource_owner" + ", projections.user_grants2.resource_owner" +
", projections.orgs.name" + ", projections.orgs.name" +
@ -38,8 +38,8 @@ var (
", projections.user_grants2.project_id" + ", projections.user_grants2.project_id" +
", projections.projects2.name" + ", projections.projects2.name" +
" FROM projections.user_grants2" + " FROM projections.user_grants2" +
" LEFT JOIN projections.users3 ON projections.user_grants2.user_id = projections.users3.id" + " LEFT JOIN projections.users4 ON projections.user_grants2.user_id = projections.users4.id" +
" LEFT JOIN projections.users3_humans ON projections.user_grants2.user_id = projections.users3_humans.user_id" + " LEFT JOIN projections.users4_humans ON projections.user_grants2.user_id = projections.users4_humans.user_id" +
" LEFT JOIN projections.orgs ON projections.user_grants2.resource_owner = projections.orgs.id" + " LEFT JOIN projections.orgs ON projections.user_grants2.resource_owner = projections.orgs.id" +
" LEFT JOIN projections.projects2 ON projections.user_grants2.project_id = projections.projects2.id" + " LEFT JOIN projections.projects2 ON projections.user_grants2.project_id = projections.projects2.id" +
" LEFT JOIN projections.login_names ON projections.user_grants2.user_id = projections.login_names.user_id" + " LEFT JOIN projections.login_names ON projections.user_grants2.user_id = projections.login_names.user_id" +
@ -77,14 +77,14 @@ var (
", projections.user_grants2.roles" + ", projections.user_grants2.roles" +
", projections.user_grants2.state" + ", projections.user_grants2.state" +
", projections.user_grants2.user_id" + ", projections.user_grants2.user_id" +
", projections.users3.username" + ", projections.users4.username" +
", projections.users3.type" + ", projections.users4.type" +
", projections.users3.resource_owner" + ", projections.users4.resource_owner" +
", projections.users3_humans.first_name" + ", projections.users4_humans.first_name" +
", projections.users3_humans.last_name" + ", projections.users4_humans.last_name" +
", projections.users3_humans.email" + ", projections.users4_humans.email" +
", projections.users3_humans.display_name" + ", projections.users4_humans.display_name" +
", projections.users3_humans.avatar_key" + ", projections.users4_humans.avatar_key" +
", projections.login_names.login_name" + ", projections.login_names.login_name" +
", projections.user_grants2.resource_owner" + ", projections.user_grants2.resource_owner" +
", projections.orgs.name" + ", projections.orgs.name" +
@ -93,8 +93,8 @@ var (
", projections.projects2.name" + ", projections.projects2.name" +
", COUNT(*) OVER ()" + ", COUNT(*) OVER ()" +
" FROM projections.user_grants2" + " FROM projections.user_grants2" +
" LEFT JOIN projections.users3 ON projections.user_grants2.user_id = projections.users3.id" + " LEFT JOIN projections.users4 ON projections.user_grants2.user_id = projections.users4.id" +
" LEFT JOIN projections.users3_humans ON projections.user_grants2.user_id = projections.users3_humans.user_id" + " LEFT JOIN projections.users4_humans ON projections.user_grants2.user_id = projections.users4_humans.user_id" +
" LEFT JOIN projections.orgs ON projections.user_grants2.resource_owner = projections.orgs.id" + " LEFT JOIN projections.orgs ON projections.user_grants2.resource_owner = projections.orgs.id" +
" LEFT JOIN projections.projects2 ON projections.user_grants2.project_id = projections.projects2.id" + " LEFT JOIN projections.projects2 ON projections.user_grants2.project_id = projections.projects2.id" +
" LEFT JOIN projections.login_names ON projections.user_grants2.user_id = projections.login_names.user_id" + " LEFT JOIN projections.login_names ON projections.user_grants2.user_id = projections.login_names.user_id" +

View File

@ -17,43 +17,44 @@ import (
) )
var ( var (
userQuery = `SELECT projections.users3.id,` + userQuery = `SELECT projections.users4.id,` +
` projections.users3.creation_date,` + ` projections.users4.creation_date,` +
` projections.users3.change_date,` + ` projections.users4.change_date,` +
` projections.users3.resource_owner,` + ` projections.users4.resource_owner,` +
` projections.users3.sequence,` + ` projections.users4.sequence,` +
` projections.users3.state,` + ` projections.users4.state,` +
` projections.users3.type,` + ` projections.users4.type,` +
` projections.users3.username,` + ` projections.users4.username,` +
` login_names.loginnames,` + ` login_names.loginnames,` +
` preferred_login_name.login_name,` + ` preferred_login_name.login_name,` +
` projections.users3_humans.user_id,` + ` projections.users4_humans.user_id,` +
` projections.users3_humans.first_name,` + ` projections.users4_humans.first_name,` +
` projections.users3_humans.last_name,` + ` projections.users4_humans.last_name,` +
` projections.users3_humans.nick_name,` + ` projections.users4_humans.nick_name,` +
` projections.users3_humans.display_name,` + ` projections.users4_humans.display_name,` +
` projections.users3_humans.preferred_language,` + ` projections.users4_humans.preferred_language,` +
` projections.users3_humans.gender,` + ` projections.users4_humans.gender,` +
` projections.users3_humans.avatar_key,` + ` projections.users4_humans.avatar_key,` +
` projections.users3_humans.email,` + ` projections.users4_humans.email,` +
` projections.users3_humans.is_email_verified,` + ` projections.users4_humans.is_email_verified,` +
` projections.users3_humans.phone,` + ` projections.users4_humans.phone,` +
` projections.users3_humans.is_phone_verified,` + ` projections.users4_humans.is_phone_verified,` +
` projections.users3_machines.user_id,` + ` projections.users4_machines.user_id,` +
` projections.users3_machines.name,` + ` projections.users4_machines.name,` +
` projections.users3_machines.description` + ` projections.users4_machines.description,` +
` FROM projections.users3` + ` COUNT(*) OVER ()` +
` LEFT JOIN projections.users3_humans ON projections.users3.id = projections.users3_humans.user_id` + ` FROM projections.users4` +
` LEFT JOIN projections.users3_machines ON projections.users3.id = projections.users3_machines.user_id` + ` LEFT JOIN projections.users4_humans ON projections.users4.id = projections.users4_humans.user_id` +
` LEFT JOIN projections.users4_machines ON projections.users4.id = projections.users4_machines.user_id` +
` LEFT JOIN` + ` LEFT JOIN` +
` (SELECT login_names.user_id, ARRAY_AGG(login_names.login_name)::TEXT[] AS loginnames` + ` (SELECT login_names.user_id, ARRAY_AGG(login_names.login_name)::TEXT[] AS loginnames` +
` FROM projections.login_names AS login_names` + ` FROM projections.login_names AS login_names` +
` WHERE login_names.instance_id = $1` + ` WHERE login_names.instance_id = $1` +
` GROUP BY login_names.user_id) AS login_names` + ` GROUP BY login_names.user_id) AS login_names` +
` ON login_names.user_id = projections.users3.id` + ` ON login_names.user_id = projections.users4.id` +
` LEFT JOIN` + ` LEFT JOIN` +
` (SELECT preferred_login_name.user_id, preferred_login_name.login_name FROM projections.login_names AS preferred_login_name WHERE preferred_login_name.instance_id = $2 AND preferred_login_name.is_primary = $3) AS preferred_login_name` + ` (SELECT preferred_login_name.user_id, preferred_login_name.login_name FROM projections.login_names AS preferred_login_name WHERE preferred_login_name.instance_id = $2 AND preferred_login_name.is_primary = $3) AS preferred_login_name` +
` ON preferred_login_name.user_id = projections.users3.id` ` ON preferred_login_name.user_id = projections.users4.id`
userCols = []string{ userCols = []string{
"id", "id",
"creation_date", "creation_date",
@ -82,22 +83,23 @@ var (
"user_id", "user_id",
"name", "name",
"description", "description",
"count",
} }
profileQuery = `SELECT projections.users3.id,` + profileQuery = `SELECT projections.users4.id,` +
` projections.users3.creation_date,` + ` projections.users4.creation_date,` +
` projections.users3.change_date,` + ` projections.users4.change_date,` +
` projections.users3.resource_owner,` + ` projections.users4.resource_owner,` +
` projections.users3.sequence,` + ` projections.users4.sequence,` +
` projections.users3_humans.user_id,` + ` projections.users4_humans.user_id,` +
` projections.users3_humans.first_name,` + ` projections.users4_humans.first_name,` +
` projections.users3_humans.last_name,` + ` projections.users4_humans.last_name,` +
` projections.users3_humans.nick_name,` + ` projections.users4_humans.nick_name,` +
` projections.users3_humans.display_name,` + ` projections.users4_humans.display_name,` +
` projections.users3_humans.preferred_language,` + ` projections.users4_humans.preferred_language,` +
` projections.users3_humans.gender,` + ` projections.users4_humans.gender,` +
` projections.users3_humans.avatar_key` + ` projections.users4_humans.avatar_key` +
` FROM projections.users3` + ` FROM projections.users4` +
` LEFT JOIN projections.users3_humans ON projections.users3.id = projections.users3_humans.user_id` ` LEFT JOIN projections.users4_humans ON projections.users4.id = projections.users4_humans.user_id`
profileCols = []string{ profileCols = []string{
"id", "id",
"creation_date", "creation_date",
@ -113,16 +115,16 @@ var (
"gender", "gender",
"avatar_key", "avatar_key",
} }
emailQuery = `SELECT projections.users3.id,` + emailQuery = `SELECT projections.users4.id,` +
` projections.users3.creation_date,` + ` projections.users4.creation_date,` +
` projections.users3.change_date,` + ` projections.users4.change_date,` +
` projections.users3.resource_owner,` + ` projections.users4.resource_owner,` +
` projections.users3.sequence,` + ` projections.users4.sequence,` +
` projections.users3_humans.user_id,` + ` projections.users4_humans.user_id,` +
` projections.users3_humans.email,` + ` projections.users4_humans.email,` +
` projections.users3_humans.is_email_verified` + ` projections.users4_humans.is_email_verified` +
` FROM projections.users3` + ` FROM projections.users4` +
` LEFT JOIN projections.users3_humans ON projections.users3.id = projections.users3_humans.user_id` ` LEFT JOIN projections.users4_humans ON projections.users4.id = projections.users4_humans.user_id`
emailCols = []string{ emailCols = []string{
"id", "id",
"creation_date", "creation_date",
@ -133,16 +135,16 @@ var (
"email", "email",
"is_email_verified", "is_email_verified",
} }
phoneQuery = `SELECT projections.users3.id,` + phoneQuery = `SELECT projections.users4.id,` +
` projections.users3.creation_date,` + ` projections.users4.creation_date,` +
` projections.users3.change_date,` + ` projections.users4.change_date,` +
` projections.users3.resource_owner,` + ` projections.users4.resource_owner,` +
` projections.users3.sequence,` + ` projections.users4.sequence,` +
` projections.users3_humans.user_id,` + ` projections.users4_humans.user_id,` +
` projections.users3_humans.phone,` + ` projections.users4_humans.phone,` +
` projections.users3_humans.is_phone_verified` + ` projections.users4_humans.is_phone_verified` +
` FROM projections.users3` + ` FROM projections.users4` +
` LEFT JOIN projections.users3_humans ON projections.users3.id = projections.users3_humans.user_id` ` LEFT JOIN projections.users4_humans ON projections.users4.id = projections.users4_humans.user_id`
phoneCols = []string{ phoneCols = []string{
"id", "id",
"creation_date", "creation_date",
@ -153,14 +155,14 @@ var (
"phone", "phone",
"is_phone_verified", "is_phone_verified",
} }
userUniqueQuery = `SELECT projections.users3.id,` + userUniqueQuery = `SELECT projections.users4.id,` +
` projections.users3.state,` + ` projections.users4.state,` +
` projections.users3.username,` + ` projections.users4.username,` +
` projections.users3_humans.user_id,` + ` projections.users4_humans.user_id,` +
` projections.users3_humans.email,` + ` projections.users4_humans.email,` +
` projections.users3_humans.is_email_verified` + ` projections.users4_humans.is_email_verified` +
` FROM projections.users3` + ` FROM projections.users4` +
` LEFT JOIN projections.users3_humans ON projections.users3.id = projections.users3_humans.user_id` ` LEFT JOIN projections.users4_humans ON projections.users4.id = projections.users4_humans.user_id`
userUniqueCols = []string{ userUniqueCols = []string{
"id", "id",
"state", "state",
@ -169,42 +171,43 @@ var (
"email", "email",
"is_email_verified", "is_email_verified",
} }
notifyUserQuery = `SELECT projections.users3.id,` + notifyUserQuery = `SELECT projections.users4.id,` +
` projections.users3.creation_date,` + ` projections.users4.creation_date,` +
` projections.users3.change_date,` + ` projections.users4.change_date,` +
` projections.users3.resource_owner,` + ` projections.users4.resource_owner,` +
` projections.users3.sequence,` + ` projections.users4.sequence,` +
` projections.users3.state,` + ` projections.users4.state,` +
` projections.users3.type,` + ` projections.users4.type,` +
` projections.users3.username,` + ` projections.users4.username,` +
` login_names.loginnames,` + ` login_names.loginnames,` +
` preferred_login_name.login_name,` + ` preferred_login_name.login_name,` +
` projections.users3_humans.user_id,` + ` projections.users4_humans.user_id,` +
` projections.users3_humans.first_name,` + ` projections.users4_humans.first_name,` +
` projections.users3_humans.last_name,` + ` projections.users4_humans.last_name,` +
` projections.users3_humans.nick_name,` + ` projections.users4_humans.nick_name,` +
` projections.users3_humans.display_name,` + ` projections.users4_humans.display_name,` +
` projections.users3_humans.preferred_language,` + ` projections.users4_humans.preferred_language,` +
` projections.users3_humans.gender,` + ` projections.users4_humans.gender,` +
` projections.users3_humans.avatar_key,` + ` projections.users4_humans.avatar_key,` +
` projections.users3_notifications.user_id,` + ` projections.users4_notifications.user_id,` +
` projections.users3_notifications.last_email,` + ` projections.users4_notifications.last_email,` +
` projections.users3_notifications.verified_email,` + ` projections.users4_notifications.verified_email,` +
` projections.users3_notifications.last_phone,` + ` projections.users4_notifications.last_phone,` +
` projections.users3_notifications.verified_phone,` + ` projections.users4_notifications.verified_phone,` +
` projections.users3_notifications.password_set` + ` projections.users4_notifications.password_set,` +
` FROM projections.users3` + ` COUNT(*) OVER ()` +
` LEFT JOIN projections.users3_humans ON projections.users3.id = projections.users3_humans.user_id` + ` FROM projections.users4` +
` LEFT JOIN projections.users3_notifications ON projections.users3.id = projections.users3_notifications.user_id` + ` LEFT JOIN projections.users4_humans ON projections.users4.id = projections.users4_humans.user_id` +
` LEFT JOIN projections.users4_notifications ON projections.users4.id = projections.users4_notifications.user_id` +
` LEFT JOIN` + ` LEFT JOIN` +
` (SELECT login_names.user_id, ARRAY_AGG(login_names.login_name) AS loginnames` + ` (SELECT login_names.user_id, ARRAY_AGG(login_names.login_name) AS loginnames` +
` FROM projections.login_names AS login_names` + ` FROM projections.login_names AS login_names` +
` WHERE login_names.instance_id = $1` + ` WHERE login_names.instance_id = $1` +
` GROUP BY login_names.user_id) AS login_names` + ` GROUP BY login_names.user_id) AS login_names` +
` ON login_names.user_id = projections.users3.id` + ` ON login_names.user_id = projections.users4.id` +
` LEFT JOIN` + ` LEFT JOIN` +
` (SELECT preferred_login_name.user_id, preferred_login_name.login_name FROM projections.login_names AS preferred_login_name WHERE preferred_login_name.instance_id = $2 AND preferred_login_name.is_primary = $3) AS preferred_login_name` + ` (SELECT preferred_login_name.user_id, preferred_login_name.login_name FROM projections.login_names AS preferred_login_name WHERE preferred_login_name.instance_id = $2 AND preferred_login_name.is_primary = $3) AS preferred_login_name` +
` ON preferred_login_name.user_id = projections.users3.id` ` ON preferred_login_name.user_id = projections.users4.id`
notifyUserCols = []string{ notifyUserCols = []string{
"id", "id",
"creation_date", "creation_date",
@ -232,44 +235,45 @@ var (
"last_phone", "last_phone",
"verified_phone", "verified_phone",
"password_set", "password_set",
"count",
} }
usersQuery = `SELECT projections.users3.id,` + usersQuery = `SELECT projections.users4.id,` +
` projections.users3.creation_date,` + ` projections.users4.creation_date,` +
` projections.users3.change_date,` + ` projections.users4.change_date,` +
` projections.users3.resource_owner,` + ` projections.users4.resource_owner,` +
` projections.users3.sequence,` + ` projections.users4.sequence,` +
` projections.users3.state,` + ` projections.users4.state,` +
` projections.users3.type,` + ` projections.users4.type,` +
` projections.users3.username,` + ` projections.users4.username,` +
` login_names.loginnames,` + ` login_names.loginnames,` +
` preferred_login_name.login_name,` + ` preferred_login_name.login_name,` +
` projections.users3_humans.user_id,` + ` projections.users4_humans.user_id,` +
` projections.users3_humans.first_name,` + ` projections.users4_humans.first_name,` +
` projections.users3_humans.last_name,` + ` projections.users4_humans.last_name,` +
` projections.users3_humans.nick_name,` + ` projections.users4_humans.nick_name,` +
` projections.users3_humans.display_name,` + ` projections.users4_humans.display_name,` +
` projections.users3_humans.preferred_language,` + ` projections.users4_humans.preferred_language,` +
` projections.users3_humans.gender,` + ` projections.users4_humans.gender,` +
` projections.users3_humans.avatar_key,` + ` projections.users4_humans.avatar_key,` +
` projections.users3_humans.email,` + ` projections.users4_humans.email,` +
` projections.users3_humans.is_email_verified,` + ` projections.users4_humans.is_email_verified,` +
` projections.users3_humans.phone,` + ` projections.users4_humans.phone,` +
` projections.users3_humans.is_phone_verified,` + ` projections.users4_humans.is_phone_verified,` +
` projections.users3_machines.user_id,` + ` projections.users4_machines.user_id,` +
` projections.users3_machines.name,` + ` projections.users4_machines.name,` +
` projections.users3_machines.description,` + ` projections.users4_machines.description,` +
` COUNT(*) OVER ()` + ` COUNT(*) OVER ()` +
` FROM projections.users3` + ` FROM projections.users4` +
` LEFT JOIN projections.users3_humans ON projections.users3.id = projections.users3_humans.user_id` + ` LEFT JOIN projections.users4_humans ON projections.users4.id = projections.users4_humans.user_id` +
` LEFT JOIN projections.users3_machines ON projections.users3.id = projections.users3_machines.user_id` + ` LEFT JOIN projections.users4_machines ON projections.users4.id = projections.users4_machines.user_id` +
` LEFT JOIN` + ` LEFT JOIN` +
` (SELECT login_names.user_id, ARRAY_AGG(login_names.login_name) AS loginnames` + ` (SELECT login_names.user_id, ARRAY_AGG(login_names.login_name) AS loginnames` +
` FROM projections.login_names AS login_names` + ` FROM projections.login_names AS login_names` +
` GROUP BY login_names.user_id) AS login_names` + ` GROUP BY login_names.user_id) AS login_names` +
` ON login_names.user_id = projections.users3.id` + ` ON login_names.user_id = projections.users4.id` +
` LEFT JOIN` + ` LEFT JOIN` +
` (SELECT preferred_login_name.user_id, preferred_login_name.login_name FROM projections.login_names AS preferred_login_name WHERE preferred_login_name.is_primary = $1) AS preferred_login_name` + ` (SELECT preferred_login_name.user_id, preferred_login_name.login_name FROM projections.login_names AS preferred_login_name WHERE preferred_login_name.is_primary = $1) AS preferred_login_name` +
` ON preferred_login_name.user_id = projections.users3.id` ` ON preferred_login_name.user_id = projections.users4.id`
usersCols = []string{ usersCols = []string{
"id", "id",
"creation_date", "creation_date",
@ -370,6 +374,7 @@ func Test_UserPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
1,
}, },
), ),
}, },
@ -436,6 +441,7 @@ func Test_UserPrepares(t *testing.T) {
"id", "id",
"name", "name",
"description", "description",
1,
}, },
), ),
}, },
@ -879,6 +885,7 @@ func Test_UserPrepares(t *testing.T) {
"lastPhone", "lastPhone",
"verifiedPhone", "verifiedPhone",
true, true,
1,
}, },
), ),
}, },
@ -942,6 +949,7 @@ func Test_UserPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
1,
}, },
), ),
err: func(err error) (error, bool) { err: func(err error) (error, bool) {

View File

@ -29,7 +29,9 @@ func NewLoginPolicyAddedEvent(
forceMFA, forceMFA,
hidePasswordReset, hidePasswordReset,
ignoreUnknownUsernames, ignoreUnknownUsernames,
allowDomainDiscovery bool, allowDomainDiscovery,
disableLoginWithEmail,
disableLoginWithPhone bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
defaultRedirectURI string, defaultRedirectURI string,
passwordCheckLifetime, passwordCheckLifetime,
@ -51,6 +53,8 @@ func NewLoginPolicyAddedEvent(
hidePasswordReset, hidePasswordReset,
ignoreUnknownUsernames, ignoreUnknownUsernames,
allowDomainDiscovery, allowDomainDiscovery,
disableLoginWithEmail,
disableLoginWithPhone,
passwordlessType, passwordlessType,
defaultRedirectURI, defaultRedirectURI,
passwordCheckLifetime, passwordCheckLifetime,

View File

@ -30,7 +30,9 @@ func NewLoginPolicyAddedEvent(
forceMFA, forceMFA,
hidePasswordReset, hidePasswordReset,
ignoreUnknownUsernames, ignoreUnknownUsernames,
allowDomainDiscovery bool, allowDomainDiscovery,
disableLoginWithEmail,
disableLoginWithPhone bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
defaultRedirectURI string, defaultRedirectURI string,
passwordCheckLifetime, passwordCheckLifetime,
@ -52,13 +54,16 @@ func NewLoginPolicyAddedEvent(
hidePasswordReset, hidePasswordReset,
ignoreUnknownUsernames, ignoreUnknownUsernames,
allowDomainDiscovery, allowDomainDiscovery,
disableLoginWithEmail,
disableLoginWithPhone,
passwordlessType, passwordlessType,
defaultRedirectURI, defaultRedirectURI,
passwordCheckLifetime, passwordCheckLifetime,
externalLoginCheckLifetime, externalLoginCheckLifetime,
mfaInitSkipLifetime, mfaInitSkipLifetime,
secondFactorCheckLifetime, secondFactorCheckLifetime,
multiFactorCheckLifetime), multiFactorCheckLifetime,
),
} }
} }

View File

@ -27,6 +27,8 @@ type LoginPolicyAddedEvent struct {
HidePasswordReset bool `json:"hidePasswordReset,omitempty"` HidePasswordReset bool `json:"hidePasswordReset,omitempty"`
IgnoreUnknownUsernames bool `json:"ignoreUnknownUsernames,omitempty"` IgnoreUnknownUsernames bool `json:"ignoreUnknownUsernames,omitempty"`
AllowDomainDiscovery bool `json:"allowDomainDiscovery,omitempty"` AllowDomainDiscovery bool `json:"allowDomainDiscovery,omitempty"`
DisableLoginWithEmail bool `json:"disableLoginWithEmail,omitempty"`
DisableLoginWithPhone bool `json:"disableLoginWithPhone,omitempty"`
PasswordlessType domain.PasswordlessType `json:"passwordlessType,omitempty"` PasswordlessType domain.PasswordlessType `json:"passwordlessType,omitempty"`
DefaultRedirectURI string `json:"defaultRedirectURI,omitempty"` DefaultRedirectURI string `json:"defaultRedirectURI,omitempty"`
PasswordCheckLifetime time.Duration `json:"passwordCheckLifetime,omitempty"` PasswordCheckLifetime time.Duration `json:"passwordCheckLifetime,omitempty"`
@ -52,7 +54,9 @@ func NewLoginPolicyAddedEvent(
forceMFA, forceMFA,
hidePasswordReset, hidePasswordReset,
ignoreUnknownUsernames, ignoreUnknownUsernames,
allowDomainDiscovery bool, allowDomainDiscovery,
disableLoginWithEmail,
disableLoginWithPhone bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
defaultRedirectURI string, defaultRedirectURI string,
passwordCheckLifetime, passwordCheckLifetime,
@ -77,6 +81,8 @@ func NewLoginPolicyAddedEvent(
MFAInitSkipLifetime: mfaInitSkipLifetime, MFAInitSkipLifetime: mfaInitSkipLifetime,
SecondFactorCheckLifetime: secondFactorCheckLifetime, SecondFactorCheckLifetime: secondFactorCheckLifetime,
MultiFactorCheckLifetime: multiFactorCheckLifetime, MultiFactorCheckLifetime: multiFactorCheckLifetime,
DisableLoginWithEmail: disableLoginWithEmail,
DisableLoginWithPhone: disableLoginWithPhone,
} }
} }
@ -103,6 +109,8 @@ type LoginPolicyChangedEvent struct {
HidePasswordReset *bool `json:"hidePasswordReset,omitempty"` HidePasswordReset *bool `json:"hidePasswordReset,omitempty"`
IgnoreUnknownUsernames *bool `json:"ignoreUnknownUsernames,omitempty"` IgnoreUnknownUsernames *bool `json:"ignoreUnknownUsernames,omitempty"`
AllowDomainDiscovery *bool `json:"allowDomainDiscovery,omitempty"` AllowDomainDiscovery *bool `json:"allowDomainDiscovery,omitempty"`
DisableLoginWithEmail *bool `json:"disableLoginWithEmail,omitempty"`
DisableLoginWithPhone *bool `json:"disableLoginWithPhone,omitempty"`
PasswordlessType *domain.PasswordlessType `json:"passwordlessType,omitempty"` PasswordlessType *domain.PasswordlessType `json:"passwordlessType,omitempty"`
DefaultRedirectURI *string `json:"defaultRedirectURI,omitempty"` DefaultRedirectURI *string `json:"defaultRedirectURI,omitempty"`
PasswordCheckLifetime *time.Duration `json:"passwordCheckLifetime,omitempty"` PasswordCheckLifetime *time.Duration `json:"passwordCheckLifetime,omitempty"`
@ -222,6 +230,18 @@ func ChangeDefaultRedirectURI(defaultRedirectURI string) func(*LoginPolicyChange
} }
} }
func ChangeDisableLoginWithEmail(disableLoginWithEmail bool) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.DisableLoginWithEmail = &disableLoginWithEmail
}
}
func ChangeDisableLoginWithPhone(DisableLoginWithPhone bool) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.DisableLoginWithPhone = &DisableLoginWithPhone
}
}
func LoginPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) { func LoginPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
e := &LoginPolicyChangedEvent{ e := &LoginPolicyChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event), BaseEvent: *eventstore.BaseEventFromRepo(event),

View File

@ -3916,6 +3916,16 @@ message UpdateLoginPolicyRequest {
description: "If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success." description: "If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success."
} }
]; ];
bool disable_login_with_email = 15 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if user can additionally (to the loginname) be identified by their verified email address"
}
];
bool disable_login_with_phone = 16 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if user can additionally (to the loginname) be identified by their verified phone number"
}
];
} }
message UpdateLoginPolicyResponse { message UpdateLoginPolicyResponse {

View File

@ -4611,6 +4611,16 @@ message AddCustomLoginPolicyRequest {
description: "If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success." description: "If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success."
} }
]; ];
bool disable_login_with_email = 18 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if user can additionally (to the loginname) be identified by their verified email address"
}
];
bool disable_login_with_phone = 19 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if user can additionally (to the loginname) be identified by their verified phone number"
}
];
} }
message AddCustomLoginPolicyResponse { message AddCustomLoginPolicyResponse {
@ -4645,6 +4655,16 @@ message UpdateCustomLoginPolicyRequest {
description: "If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success." description: "If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success."
} }
]; ];
bool disable_login_with_email = 15 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if user can additionally (to the loginname) be identified by their verified email address"
}
];
bool disable_login_with_phone = 16 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if user can additionally (to the loginname) be identified by their verified phone number"
}
];
} }
message UpdateCustomLoginPolicyResponse { message UpdateCustomLoginPolicyResponse {

View File

@ -179,6 +179,16 @@ message LoginPolicy {
description: "If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success." description: "If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organisation on success."
} }
]; ];
bool disable_login_with_email = 20 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if user can additionally (to the loginname) be identified by their verified email address"
}
];
bool disable_login_with_phone = 21 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if user can additionally (to the loginname) be identified by their verified phone number"
}
];
} }
enum SecondFactorType { enum SecondFactorType {