feat: allow domain discovery for unknown usernames (#4484)

* fix: wait for projection initialization to be done

* feat: allow domain discovery for unknown usernames

* fix linting

* Update console/src/assets/i18n/de.json

Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>

* Update console/src/assets/i18n/en.json

Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>

* Update console/src/assets/i18n/it.json

Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>

* Update console/src/assets/i18n/fr.json

Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>

* fix zh i18n text

* fix projection table name

Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>
This commit is contained in:
Livio Spring 2022-10-06 13:30:14 +02:00 committed by GitHub
parent ce22961d8e
commit bffb10a4b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 519 additions and 370 deletions

View File

@ -399,6 +399,7 @@ DefaultInstance:
ForceMFA: false
HidePasswordReset: false
IgnoreUnknownUsernames: false
AllowDomainDiscovery: false
PasswordlessType: 1 #1: allowed 0: not allowed
DefaultRedirectURI: #empty because we use the Console UI
PasswordCheckLifetime: 240h #10d

View File

@ -271,6 +271,7 @@ export class IdpTableComponent implements OnInit {
.setNanos(this.loginPolicy.multiFactorCheckLifetime?.nanos ?? 0);
mgmtreq.setMultiFactorCheckLifetime(mficl);
mgmtreq.setAllowDomainDiscovery(this.loginPolicy.allowDomainDiscovery);
mgmtreq.setIgnoreUnknownUsernames(this.loginPolicy.ignoreUnknownUsernames);
mgmtreq.setDefaultRedirectUri(this.loginPolicy.defaultRedirectUri);

View File

@ -285,6 +285,28 @@
</ng-template> -->
</div>
<div class="login-policy-row">
<mat-checkbox
class="login-policy-toggle"
color="primary"
ngDefaultControl
[(ngModel)]="loginData.allowDomainDiscovery"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.ALLOWDOMAINDISCOVERY' | translate }}
</mat-checkbox>
</div>
<div class="login-policy-row">
<mat-checkbox
class="login-policy-toggle"

View File

@ -11,6 +11,7 @@ import {
import {
AddCustomLoginPolicyRequest,
GetLoginPolicyResponse as MgmtGetLoginPolicyResponse,
UpdateCustomLoginPolicyRequest,
} from 'src/app/proto/generated/zitadel/management_pb';
import { LoginPolicy, PasswordlessType } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
@ -144,37 +145,65 @@ export class LoginPolicyComponent implements OnInit {
if (this.loginData) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
const mgmtreq = new AddCustomLoginPolicyRequest();
mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
mgmtreq.setAllowRegister(this.loginData.allowRegister);
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
mgmtreq.setForceMfa(this.loginData.forceMfa);
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
mgmtreq.setMultiFactorsList(this.loginData.multiFactorsList);
mgmtreq.setSecondFactorsList(this.loginData.secondFactorsList);
const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60);
mgmtreq.setPasswordCheckLifetime(pcl);
const elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 12) * 60 * 60);
mgmtreq.setExternalLoginCheckLifetime(elcl);
const misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 720) * 60 * 60);
mgmtreq.setMfaInitSkipLifetime(misl);
const sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 12) * 60 * 60);
mgmtreq.setSecondFactorCheckLifetime(sfcl);
const mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 12) * 60 * 60);
mgmtreq.setMultiFactorCheckLifetime(mficl);
mgmtreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
mgmtreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
if (this.isDefault) {
const mgmtreq = new AddCustomLoginPolicyRequest();
mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
mgmtreq.setAllowRegister(this.loginData.allowRegister);
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
mgmtreq.setForceMfa(this.loginData.forceMfa);
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
mgmtreq.setMultiFactorsList(this.loginData.multiFactorsList);
mgmtreq.setSecondFactorsList(this.loginData.secondFactorsList);
const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60);
mgmtreq.setPasswordCheckLifetime(pcl);
const elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 12) * 60 * 60);
mgmtreq.setExternalLoginCheckLifetime(elcl);
const misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 720) * 60 * 60);
mgmtreq.setMfaInitSkipLifetime(misl);
const sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 12) * 60 * 60);
mgmtreq.setSecondFactorCheckLifetime(sfcl);
const mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 12) * 60 * 60);
mgmtreq.setMultiFactorCheckLifetime(mficl);
mgmtreq.setAllowDomainDiscovery(this.loginData.allowDomainDiscovery);
mgmtreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
mgmtreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
return (this.service as ManagementService).addCustomLoginPolicy(mgmtreq);
} else {
const mgmtreq = new UpdateCustomLoginPolicyRequest();
mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
mgmtreq.setAllowRegister(this.loginData.allowRegister);
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
mgmtreq.setForceMfa(this.loginData.forceMfa);
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60);
mgmtreq.setPasswordCheckLifetime(pcl);
const elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 12) * 60 * 60);
mgmtreq.setExternalLoginCheckLifetime(elcl);
const misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 720) * 60 * 60);
mgmtreq.setMfaInitSkipLifetime(misl);
const sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 12) * 60 * 60);
mgmtreq.setSecondFactorCheckLifetime(sfcl);
const mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 12) * 60 * 60);
mgmtreq.setMultiFactorCheckLifetime(mficl);
mgmtreq.setAllowDomainDiscovery(this.loginData.allowDomainDiscovery);
mgmtreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
mgmtreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
return (this.service as ManagementService).updateCustomLoginPolicy(mgmtreq);
}
case PolicyComponentServiceType.ADMIN:
@ -200,6 +229,7 @@ export class LoginPolicyComponent implements OnInit {
const admin_mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 12) * 60 * 60);
adminreq.setMultiFactorCheckLifetime(admin_mficl);
adminreq.setAllowDomainDiscovery(this.loginData.allowDomainDiscovery);
adminreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
adminreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);

View File

@ -1176,6 +1176,8 @@
"HIDELOGINNAMESUFFIX": "Loginname Suffix ausblenden",
"IGNOREUNKNOWNUSERNAMES": "Unbekannte Usernamen ignorieren",
"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_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.",
"DEFAULTREDIRECTURI": "Default Redirect URI",
"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",

View File

@ -1176,6 +1176,8 @@
"HIDELOGINNAMESUFFIX": "Hide Loginname suffix",
"IGNOREUNKNOWNUSERNAMES": "Ignore unknown usernames",
"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_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.",
"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)",
"ERRORMSGPOPUP": "Show Error in Dialog",

View File

@ -1176,6 +1176,8 @@
"HIDELOGINNAMESUFFIX": "Masquer le suffixe du nom de connexion",
"IGNOREUNKNOWNUSERNAMES": "Ignorer les noms d'utilisateur inconnus",
"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_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.",
"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).",
"ERRORMSGPOPUP": "Afficher l'erreur dans la boîte de dialogue",

View File

@ -1176,6 +1176,8 @@
"HIDELOGINNAMESUFFIX": "Nascondi il suffisso del nome utente",
"IGNOREUNKNOWNUSERNAMES": "Ignora un nome utente sconosciuto",
"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_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",
"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)",
"ERRORMSGPOPUP": "Mostra l'errore nella finestra di dialogo",

View File

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

View File

@ -4359,6 +4359,7 @@ this is en empty request
| mfa_init_skip_lifetime | google.protobuf.Duration | - | |
| second_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. | |

View File

@ -3091,7 +3091,7 @@ This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| primary_color | string | - | string.max_len: 50<br /> |
| hide_login_name_suffix | bool | hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set. Details about this scope in https://docs.zitadel.com/concepts#Reserved_Scopes | |
| hide_login_name_suffix | bool | hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set | |
| warn_color | string | - | string.max_len: 50<br /> |
| background_color | string | - | string.max_len: 50<br /> |
| font_color | string | - | string.max_len: 50<br /> |
@ -3159,6 +3159,7 @@ This is an empty request
| second_factors | repeated zitadel.policy.v1.SecondFactorType | - | |
| multi_factors | repeated zitadel.policy.v1.MultiFactorType | - | |
| 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. | |
@ -8106,6 +8107,7 @@ This is an empty request
| mfa_init_skip_lifetime | google.protobuf.Duration | - | |
| second_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. | |

View File

@ -33,7 +33,7 @@ title: zitadel/policy.proto
| details | zitadel.v1.ObjectDetails | - | |
| primary_color | string | hex value for primary color | |
| is_default | bool | defines if the organisation's admin changed the policy | |
| hide_login_name_suffix | bool | hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set. Details about this scope in https://docs.zitadel.com/concepts#Reserved_Scopes | |
| hide_login_name_suffix | bool | hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set | |
| warn_color | string | hex value for secondary color | |
| background_color | string | hex value for background color | |
| font_color | string | hex value for font color | |
@ -88,6 +88,7 @@ title: zitadel/policy.proto
| second_factors | repeated SecondFactorType | - | |
| multi_factors | repeated MultiFactorType | - | |
| 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. | |

View File

@ -17,6 +17,7 @@ func updateLoginPolicyToDomain(p *admin_pb.UpdateLoginPolicyRequest) *domain.Log
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
HidePasswordReset: p.HidePasswordReset,
IgnoreUnknownUsernames: p.IgnoreUnknownUsernames,
AllowDomainDiscovery: p.AllowDomainDiscovery,
DefaultRedirectURI: p.DefaultRedirectUri,
PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(),
ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(),

View File

@ -26,7 +26,7 @@ func (s *Server) GetMyOrg(ctx context.Context, req *mgmt_pb.GetMyOrgRequest) (*m
}
func (s *Server) GetOrgByDomainGlobal(ctx context.Context, req *mgmt_pb.GetOrgByDomainGlobalRequest) (*mgmt_pb.GetOrgByDomainGlobalResponse, error) {
org, err := s.query.OrgByDomainGlobal(ctx, req.Domain)
org, err := s.query.OrgByPrimaryDomain(ctx, req.Domain)
if err != nil {
return nil, err
}

View File

@ -18,6 +18,7 @@ func AddLoginPolicyToDomain(p *mgmt_pb.AddCustomLoginPolicyRequest) *domain.Logi
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
HidePasswordReset: p.HidePasswordReset,
IgnoreUnknownUsernames: p.IgnoreUnknownUsernames,
AllowDomainDiscovery: p.AllowDomainDiscovery,
DefaultRedirectURI: p.DefaultRedirectUri,
PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(),
ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(),
@ -49,6 +50,7 @@ func updateLoginPolicyToDomain(p *mgmt_pb.UpdateCustomLoginPolicyRequest) *domai
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
HidePasswordReset: p.HidePasswordReset,
IgnoreUnknownUsernames: p.IgnoreUnknownUsernames,
AllowDomainDiscovery: p.AllowDomainDiscovery,
DefaultRedirectURI: p.DefaultRedirectUri,
PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(),
ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(),

View File

@ -21,6 +21,7 @@ func ModelLoginPolicyToPb(policy *query.LoginPolicy) *policy_pb.LoginPolicy {
PasswordlessType: ModelPasswordlessTypeToPb(policy.PasswordlessType),
HidePasswordReset: policy.HidePasswordReset,
IgnoreUnknownUsernames: policy.IgnoreUnknownUsernames,
AllowDomainDiscovery: policy.AllowDomainDiscovery,
DefaultRedirectUri: policy.DefaultRedirectURI,
PasswordCheckLifetime: durationpb.New(policy.PasswordCheckLifetime),
ExternalLoginCheckLifetime: durationpb.New(policy.ExternalLoginCheckLifetime),

View File

@ -91,7 +91,7 @@ func (o *OPStorage) ValidateJWTProfileScopes(ctx context.Context, subject string
scope := scopes[i]
if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
var orgID string
org, err := o.query.OrgByDomainGlobal(ctx, strings.TrimPrefix(scope, domain.OrgDomainPrimaryScope))
org, err := o.query.OrgByPrimaryDomain(ctx, strings.TrimPrefix(scope, domain.OrgDomainPrimaryScope))
if err == nil {
orgID = org.ID
}

View File

@ -59,11 +59,15 @@ func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) {
l.renderError(w, r, authReq, err)
return
}
l.handleExternalRegisterByConfigID(w, r, authReq, data.IDPConfigID)
}
func (l *Login) handleExternalRegisterByConfigID(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, configID string) {
if authReq == nil {
l.defaultRedirect(w, r)
return
}
idpConfig, err := l.getIDPConfigByID(r, data.IDPConfigID)
idpConfig, err := l.getIDPConfigByID(r, configID)
if err != nil {
l.renderError(w, r, authReq, err)
return

View File

@ -33,12 +33,29 @@ func (l *Login) renderRegisterOption(w http.ResponseWriter, r *http.Request, aut
if err != nil {
errID, errMessage = l.getErrorMessage(r, err)
}
allowed := registrationAllowed(authReq)
externalAllowed := externalRegistrationAllowed(authReq)
if err == nil {
// if only external allowed with a single idp then use that
if !allowed && externalAllowed && len(authReq.AllowedExternalIDPs) == 1 {
l.handleExternalRegisterByConfigID(w, r, authReq, authReq.AllowedExternalIDPs[0].IDPConfigID)
return
}
// if only direct registration is allowed, show the form
if allowed && !externalAllowed {
l.renderRegister(w, r, authReq, nil, nil)
return
}
}
data := registerOptionData{
baseData: l.getBaseData(r, authReq, "RegisterOption", errID, errMessage),
}
funcs := map[string]interface{}{
"hasRegistration": func() bool {
return allowed
},
"hasExternalLogin": func() bool {
return authReq.LoginPolicy.AllowExternalIDP && authReq.AllowedExternalIDPs != nil && len(authReq.AllowedExternalIDPs) > 0
return externalAllowed
},
}
translator := l.getTranslator(r.Context(), authReq)
@ -58,3 +75,11 @@ func (l *Login) handleRegisterOptionCheck(w http.ResponseWriter, r *http.Request
}
l.handleRegisterOption(w, r)
}
func registrationAllowed(authReq *domain.AuthRequest) bool {
return authReq != nil && authReq.LoginPolicy != nil && authReq.LoginPolicy.AllowRegister
}
func externalRegistrationAllowed(authReq *domain.AuthRequest) bool {
return authReq != nil && authReq.LoginPolicy != nil && authReq.LoginPolicy.AllowExternalIDP && authReq.AllowedExternalIDPs != nil && len(authReq.AllowedExternalIDPs) > 0
}

View File

@ -17,7 +17,7 @@
</a>
</div>
<div class="lgn-register-options">
{{if .LoginPolicy.AllowUsernamePassword }}
{{if hasRegistration }}
<button class="lgn-raised-button lgn-primary" name="usernamepassword" value="true"
formnovalidate>{{t "RegisterOption.RegisterUsernamePasswordButtonText"}}</button>
{{end}}
@ -42,4 +42,4 @@
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
{{template "main-bottom" .}}
{{template "main-bottom" .}}

View File

@ -93,7 +93,7 @@ type userCommandProvider interface {
type orgViewProvider interface {
OrgByID(context.Context, bool, string) (*query.Org, error)
OrgByDomainGlobal(context.Context, string) (*query.Org, error)
OrgByPrimaryDomain(context.Context, string) (*query.Org, error)
}
type userGrantProvider interface {
@ -651,23 +651,57 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain
}
}
}
if request.LoginPolicy != nil && request.LoginPolicy.IgnoreUnknownUsernames {
if errors.IsNotFound(err) || (user != nil && user.State == int32(domain.UserStateInactive)) {
if request.LabelPolicy != nil && request.LabelPolicy.HideLoginNameSuffix {
preferredLoginName = loginName
}
request.SetUserInfo(unknownUserID, preferredLoginName, preferredLoginName, preferredLoginName, "", request.RequestedOrgID)
return nil
}
}
if err != nil {
// return any error apart from not found ones directly
if err != nil && !errors.IsNotFound(err) {
return err
}
if user.State == int32(domain.UserStateInactive) {
// if there's an active user, let's use it
if user != nil && user.State == int32(domain.UserStateActive) {
request.SetUserInfo(user.ID, loginName, user.PreferredLoginName, "", "", user.ResourceOwner)
return nil
}
// the user was either not found or not active
// so check if the loginname suffix matches a verified org domain
if repo.checkDomainDiscovery(ctx, request, loginName) {
return nil
}
// let's just check for if unknown usernames are ignored
if request.LoginPolicy != nil && request.LoginPolicy.IgnoreUnknownUsernames {
if request.LabelPolicy != nil && request.LabelPolicy.HideLoginNameSuffix {
preferredLoginName = loginName
}
request.SetUserInfo(unknownUserID, preferredLoginName, preferredLoginName, preferredLoginName, "", request.RequestedOrgID)
return nil
}
// there was no policy that allowed unknown loginnames in any case
// let's once again check if the user was just inactive
if user != nil && user.State == int32(domain.UserStateInactive) {
return errors.ThrowPreconditionFailed(nil, "AUTH-2n8fs", "Errors.User.Inactive")
}
request.SetUserInfo(user.ID, loginName, user.PreferredLoginName, "", "", user.ResourceOwner)
return nil
// user was not found
return err
}
func (repo *AuthRequestRepo) checkDomainDiscovery(ctx context.Context, request *domain.AuthRequest, loginName string) bool {
// check if there's a suffix in the loginname
split := strings.Split(loginName, "@")
if len(split) < 2 {
return false
}
// check if the suffix matches a verified domain
org, err := repo.Query.OrgByVerifiedDomain(ctx, split[len(split)-1])
if err != nil {
return false
}
// and if the login policy allows domain discovery
policy, err := repo.Query.LoginPolicyByID(ctx, true, org.ID)
if err != nil || !policy.AllowDomainDiscovery {
return false
}
// discovery was allowed, so set the org as requested org
request.SetOrgInformation(org.ID, org.Name, org.Domain, false)
request.Prompt = append(request.Prompt, domain.PromptCreate)
return true
}
func (repo *AuthRequestRepo) checkLoginPolicyWithResourceOwner(ctx context.Context, request *domain.AuthRequest, user *user_view_model.UserView) error {
@ -1075,9 +1109,7 @@ func setOrgID(ctx context.Context, orgViewProvider orgViewProvider, request *dom
if err != nil {
return err
}
request.RequestedOrgID = org.ID
request.RequestedOrgName = org.Name
request.RequestedPrimaryDomain = org.Domain
request.SetOrgInformation(org.ID, org.Name, org.Domain, false)
return nil
}
@ -1086,14 +1118,11 @@ func setOrgID(ctx context.Context, orgViewProvider orgViewProvider, request *dom
return nil
}
org, err := orgViewProvider.OrgByDomainGlobal(ctx, primaryDomain)
org, err := orgViewProvider.OrgByPrimaryDomain(ctx, primaryDomain)
if err != nil {
return err
}
request.RequestedOrgID = org.ID
request.RequestedOrgName = org.Name
request.RequestedPrimaryDomain = primaryDomain
request.RequestedOrgDomain = true
request.SetOrgInformation(org.ID, org.Name, primaryDomain, true)
return nil
}

View File

@ -174,7 +174,7 @@ func (m *mockViewOrg) OrgByID(context.Context, bool, string) (*query.Org, error)
}, nil
}
func (m *mockViewOrg) OrgByDomainGlobal(context.Context, string) (*query.Org, error) {
func (m *mockViewOrg) OrgByPrimaryDomain(context.Context, string) (*query.Org, error) {
return &query.Org{
State: m.State,
}, nil
@ -186,7 +186,7 @@ func (m *mockViewErrOrg) OrgByID(context.Context, bool, string) (*query.Org, err
return nil, errors.ThrowInternal(nil, "id", "internal error")
}
func (m *mockViewErrOrg) OrgByDomainGlobal(context.Context, string) (*query.Org, error) {
func (m *mockViewErrOrg) OrgByPrimaryDomain(context.Context, string) (*query.Org, error) {
return nil, errors.ThrowInternal(nil, "id", "internal error")
}

View File

@ -71,6 +71,7 @@ type InstanceSetup struct {
ForceMFA bool
HidePasswordReset bool
IgnoreUnknownUsername bool
AllowDomainDiscovery bool
PasswordlessType domain.PasswordlessType
DefaultRedirectURI string
PasswordCheckLifetime time.Duration
@ -217,6 +218,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
setup.LoginPolicy.ForceMFA,
setup.LoginPolicy.HidePasswordReset,
setup.LoginPolicy.IgnoreUnknownUsername,
setup.LoginPolicy.AllowDomainDiscovery,
setup.LoginPolicy.PasswordlessType,
setup.LoginPolicy.DefaultRedirectURI,
setup.LoginPolicy.PasswordCheckLifetime,

View File

@ -32,6 +32,7 @@ func writeModelToLoginPolicy(wm *LoginPolicyWriteModel) *domain.LoginPolicy {
AllowExternalIDP: wm.AllowExternalIDP,
HidePasswordReset: wm.HidePasswordReset,
IgnoreUnknownUsernames: wm.IgnoreUnknownUsernames,
AllowDomainDiscovery: wm.AllowDomainDiscovery,
ForceMFA: wm.ForceMFA,
PasswordlessType: wm.PasswordlessType,
DefaultRedirectURI: wm.DefaultRedirectURI,

View File

@ -15,37 +15,6 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
func (c *Commands) AddDefaultLoginPolicy(
ctx context.Context,
allowUsernamePassword, allowRegister, allowExternalIDP, forceMFA, hidePasswordReset, ignoreUnknownUsernames bool,
passwordlessType domain.PasswordlessType,
defaultRedirectURI string,
passwordCheckLifetime, externalLoginCheckLifetime, mfaInitSkipLifetime, secondFactorCheckLifetime, multiFactorCheckLifetime time.Duration,
) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddDefaultLoginPolicy(instanceAgg, allowUsernamePassword,
allowRegister,
allowExternalIDP,
forceMFA,
hidePasswordReset,
ignoreUnknownUsernames,
passwordlessType,
defaultRedirectURI,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
secondFactorCheckLifetime,
multiFactorCheckLifetime))
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
return nil, err
}
return pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) ChangeDefaultLoginPolicy(ctx context.Context, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) {
existingPolicy := NewInstanceLoginPolicyWriteModel(ctx)
instanceAgg := InstanceAggregateFromWriteModel(&existingPolicy.LoginPolicyWriteModel.WriteModel)
@ -83,6 +52,7 @@ func (c *Commands) changeDefaultLoginPolicy(ctx context.Context, instanceAgg *ev
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.AllowDomainDiscovery,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,
@ -293,6 +263,7 @@ func prepareAddDefaultLoginPolicy(
forceMFA bool,
hidePasswordReset bool,
ignoreUnknownUsernames bool,
allowDomainDiscovery bool,
passwordlessType domain.PasswordlessType,
defaultRedirectURI string,
passwordCheckLifetime time.Duration,
@ -323,6 +294,7 @@ func prepareAddDefaultLoginPolicy(
forceMFA,
hidePasswordReset,
ignoreUnknownUsernames,
allowDomainDiscovery,
passwordlessType,
defaultRedirectURI,
passwordCheckLifetime,

View File

@ -66,7 +66,8 @@ func (wm *InstanceLoginPolicyWriteModel) NewChangedEvent(
allowExternalIDP,
forceMFA,
hidePasswordReset,
ignoreUnknownUsernames bool,
ignoreUnknownUsernames,
allowDomainDiscovery bool,
passwordlessType domain.PasswordlessType,
defaultRedirectURI string,
passwordCheckLifetime,
@ -98,6 +99,9 @@ func (wm *InstanceLoginPolicyWriteModel) NewChangedEvent(
if wm.IgnoreUnknownUsernames != ignoreUnknownUsernames {
changes = append(changes, policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames))
}
if wm.AllowDomainDiscovery != allowDomainDiscovery {
changes = append(changes, policy.ChangeAllowDomainDiscovery(allowDomainDiscovery))
}
if wm.DefaultRedirectURI != defaultRedirectURI {
changes = append(changes, policy.ChangeDefaultRedirectURI(defaultRedirectURI))
}

View File

@ -18,161 +18,6 @@ import (
"github.com/stretchr/testify/assert"
)
func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
allowUsernamePassword bool
allowRegister bool
allowExternalIDP bool
forceMFA bool
hidePasswordReset bool
ignoreUnknownUsernames bool
passwordlessType domain.PasswordlessType
defaultRedirectURI string
passwordCheckLifetime time.Duration
externalLoginCheckLifetime time.Duration
mfaInitSkipLifetime time.Duration
secondFactorCheckLifetime time.Duration
multiFactorCheckLifetime time.Duration
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "loginpolicy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
instance.NewLoginPolicyAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
true,
true,
false,
false,
false,
false,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*1,
time.Hour*1,
time.Hour*1,
time.Hour*1,
),
),
),
),
},
args: args{
ctx: context.Background(),
allowRegister: true,
allowUsernamePassword: true,
passwordlessType: domain.PasswordlessTypeAllowed,
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"INSTANCE",
instance.NewLoginPolicyAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
true,
true,
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
),
),
},
),
),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
allowRegister: true,
allowUsernamePassword: true,
allowExternalIDP: true,
forceMFA: true,
hidePasswordReset: true,
ignoreUnknownUsernames: 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,
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddDefaultLoginPolicy(
tt.args.ctx,
tt.args.allowUsernamePassword,
tt.args.allowRegister,
tt.args.allowExternalIDP,
tt.args.forceMFA,
tt.args.hidePasswordReset,
tt.args.ignoreUnknownUsernames,
tt.args.passwordlessType,
tt.args.defaultRedirectURI,
tt.args.passwordCheckLifetime,
tt.args.externalLoginCheckLifetime,
tt.args.mfaInitSkipLifetime,
tt.args.secondFactorCheckLifetime,
tt.args.multiFactorCheckLifetime,
)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
@ -225,6 +70,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
@ -246,6 +92,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
@ -275,6 +122,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
@ -296,6 +144,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*10,
@ -317,6 +166,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
ForceMFA: false,
HidePasswordReset: false,
IgnoreUnknownUsernames: false,
AllowDomainDiscovery: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
DefaultRedirectURI: "",
PasswordCheckLifetime: time.Hour * 10,
@ -339,6 +189,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
ForceMFA: false,
HidePasswordReset: false,
IgnoreUnknownUsernames: false,
AllowDomainDiscovery: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
DefaultRedirectURI: "",
PasswordCheckLifetime: time.Hour * 10,
@ -435,6 +286,7 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -473,6 +325,7 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -531,6 +384,7 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -671,6 +525,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -709,6 +564,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -760,6 +616,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -816,6 +673,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -880,6 +738,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -1434,7 +1293,7 @@ func TestCommandSide_RemoveMultiFactorDefaultLoginPolicy(t *testing.T) {
}
}
func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA, hidePasswordReset, ignoreUnknownUsernames bool,
func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA, hidePasswordReset, ignoreUnknownUsernames, allowDomainDiscovery bool,
passwordlessType domain.PasswordlessType,
redirectURI string,
passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime time.Duration) *instance.LoginPolicyChangedEvent {
@ -1447,6 +1306,7 @@ func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allow
policy.ChangeAllowUserNamePassword(allowUsernamePassword),
policy.ChangeHidePasswordReset(hidePasswordReset),
policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames),
policy.ChangeAllowDomainDiscovery(allowDomainDiscovery),
policy.ChangePasswordlessType(passwordlessType),
policy.ChangeDefaultRedirectURI(redirectURI),
policy.ChangePasswordCheckLifetime(passwordLifetime),

View File

@ -39,6 +39,7 @@ func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, pol
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.AllowDomainDiscovery,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,
@ -127,6 +128,7 @@ func (c *Commands) ChangeLoginPolicy(ctx context.Context, resourceOwner string,
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.AllowDomainDiscovery,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,

View File

@ -68,7 +68,8 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
allowExternalIDP,
forceMFA,
hidePasswordReset,
ignoreUnknownUsernames bool,
ignoreUnknownUsernames,
allowDomainDiscovery bool,
passwordlessType domain.PasswordlessType,
defaultRedirectURI string,
passwordCheckLifetime,
@ -97,6 +98,9 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
if wm.IgnoreUnknownUsernames != ignoreUnknownUsernames {
changes = append(changes, policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames))
}
if wm.AllowDomainDiscovery != allowDomainDiscovery {
changes = append(changes, policy.ChangeAllowDomainDiscovery(allowDomainDiscovery))
}
if wm.PasswordCheckLifetime != passwordCheckLifetime {
changes = append(changes, policy.ChangePasswordCheckLifetime(passwordCheckLifetime))
}

View File

@ -79,6 +79,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
@ -100,6 +101,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
AllowExternalIDP: true,
ForceMFA: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
@ -130,6 +132,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
@ -153,6 +156,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
@ -174,6 +178,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
@ -202,6 +207,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
@ -233,6 +239,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
@ -268,6 +275,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
@ -291,6 +299,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
@ -320,6 +329,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
@ -368,6 +378,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
@ -398,6 +409,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
@ -425,6 +437,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
@ -489,6 +502,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
AllowExternalIDP: true,
ForceMFA: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
},
@ -514,6 +528,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
AllowExternalIDP: true,
ForceMFA: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
},
@ -537,6 +552,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
@ -559,6 +575,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
ForceMFA: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
@ -587,6 +604,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
@ -608,6 +626,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
&duration10,
@ -630,6 +649,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
AllowExternalIDP: false,
ForceMFA: false,
IgnoreUnknownUsernames: false,
AllowDomainDiscovery: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
DefaultRedirectURI: "",
PasswordCheckLifetime: time.Hour * 10,
@ -651,6 +671,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
ForceMFA: false,
HidePasswordReset: false,
IgnoreUnknownUsernames: false,
AllowDomainDiscovery: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
DefaultRedirectURI: "",
PasswordCheckLifetime: time.Hour * 10,
@ -744,6 +765,7 @@ func TestCommandSide_RemoveLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -884,6 +906,7 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -925,6 +948,7 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -986,6 +1010,7 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -1150,6 +1175,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -1191,6 +1217,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -1244,6 +1271,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -1304,6 +1332,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -1372,6 +1401,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
@ -1990,7 +2020,7 @@ func TestCommandSide_RemoveMultiFactorLoginPolicy(t *testing.T) {
}
}
func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassword, register, externalIDP, mfa, passwordReset, ignoreUnknownUsernames bool,
func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassword, register, externalIDP, mfa, passwordReset, ignoreUnknownUsernames, allowDomainDiscovery bool,
passwordlessType domain.PasswordlessType,
redirectURI string,
passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime *time.Duration) *org.LoginPolicyChangedEvent {
@ -2001,6 +2031,7 @@ func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassw
policy.ChangeForceMFA(mfa),
policy.ChangeHidePasswordReset(passwordReset),
policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames),
policy.ChangeAllowDomainDiscovery(allowDomainDiscovery),
policy.ChangePasswordlessType(passwordlessType),
policy.ChangeDefaultRedirectURI(redirectURI),
}

View File

@ -17,6 +17,7 @@ type LoginPolicyWriteModel struct {
ForceMFA bool
HidePasswordReset bool
IgnoreUnknownUsernames bool
AllowDomainDiscovery bool
PasswordlessType domain.PasswordlessType
DefaultRedirectURI string
PasswordCheckLifetime time.Duration
@ -38,6 +39,7 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
wm.PasswordlessType = e.PasswordlessType
wm.HidePasswordReset = e.HidePasswordReset
wm.IgnoreUnknownUsernames = e.IgnoreUnknownUsernames
wm.AllowDomainDiscovery = e.AllowDomainDiscovery
wm.DefaultRedirectURI = e.DefaultRedirectURI
wm.PasswordCheckLifetime = e.PasswordCheckLifetime
wm.ExternalLoginCheckLifetime = e.ExternalLoginCheckLifetime
@ -64,6 +66,9 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
if e.IgnoreUnknownUsernames != nil {
wm.IgnoreUnknownUsernames = *e.IgnoreUnknownUsernames
}
if e.AllowDomainDiscovery != nil {
wm.AllowDomainDiscovery = *e.AllowDomainDiscovery
}
if e.PasswordlessType != nil {
wm.PasswordlessType = *e.PasswordlessType
}

View File

@ -1157,6 +1157,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
@ -1194,6 +1195,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
@ -1232,6 +1234,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
@ -1286,6 +1289,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
@ -1374,6 +1378,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
@ -1469,6 +1474,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,

View File

@ -1679,6 +1679,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
@ -1745,6 +1746,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
@ -1811,6 +1813,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
@ -1894,6 +1897,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
@ -2035,6 +2039,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
@ -2144,6 +2149,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
@ -2247,6 +2253,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
@ -2372,6 +2379,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,

View File

@ -140,6 +140,13 @@ func (a *AuthRequest) SetUserInfo(userID, userName, loginName, displayName, avat
a.UserOrgID = userOrgID
}
func (a *AuthRequest) SetOrgInformation(id, name, primaryDomain string, requestedByDomain bool) {
a.RequestedOrgID = id
a.RequestedOrgName = name
a.RequestedPrimaryDomain = primaryDomain
a.RequestedOrgDomain = requestedByDomain
}
func (a *AuthRequest) MFALevel() MFALevel {
return -1
//PLANNED: check a.PossibleLOAs (and Prompt Login?)

View File

@ -21,6 +21,7 @@ type LoginPolicy struct {
PasswordlessType PasswordlessType
HidePasswordReset bool
IgnoreUnknownUsernames bool
AllowDomainDiscovery bool
DefaultRedirectURI string
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration

View File

@ -30,6 +30,7 @@ type LoginPolicy struct {
IsDefault bool
HidePasswordReset bool
IgnoreUnknownUsernames bool
AllowDomainDiscovery bool
DefaultRedirectURI string
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration
@ -113,6 +114,10 @@ var (
name: projection.IgnoreUnknownUsernames,
table: loginPolicyTable,
}
LoginPolicyColumnAllowDomainDiscovery = Column{
name: projection.AllowDomainDiscovery,
table: loginPolicyTable,
}
LoginPolicyColumnDefaultRedirectURI = Column{
name: projection.DefaultRedirectURI,
table: loginPolicyTable,
@ -305,6 +310,7 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Rows) (*LoginPolicy,
LoginPolicyColumnIsDefault.identifier(),
LoginPolicyColumnHidePasswordReset.identifier(),
LoginPolicyColumnIgnoreUnknownUsernames.identifier(),
LoginPolicyColumnAllowDomainDiscovery.identifier(),
LoginPolicyColumnDefaultRedirectURI.identifier(),
LoginPolicyColumnPasswordCheckLifetime.identifier(),
LoginPolicyColumnExternalLoginCheckLifetime.identifier(),
@ -343,6 +349,7 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Rows) (*LoginPolicy,
&p.IsDefault,
&p.HidePasswordReset,
&p.IgnoreUnknownUsernames,
&p.AllowDomainDiscovery,
&defaultRedirectURI,
&p.PasswordCheckLifetime,
&p.ExternalLoginCheckLifetime,

View File

@ -30,32 +30,33 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.login_policies.aggregate_id,`+
` projections.login_policies.creation_date,`+
` projections.login_policies.change_date,`+
` projections.login_policies.sequence,`+
` projections.login_policies.allow_register,`+
` projections.login_policies.allow_username_password,`+
` projections.login_policies.allow_external_idps,`+
` projections.login_policies.force_mfa,`+
` projections.login_policies.second_factors,`+
` projections.login_policies.multi_factors,`+
` projections.login_policies.passwordless_type,`+
` projections.login_policies.is_default,`+
` projections.login_policies.hide_password_reset,`+
` projections.login_policies.ignore_unknown_usernames,`+
` projections.login_policies.default_redirect_uri,`+
` projections.login_policies.password_check_lifetime,`+
` projections.login_policies.external_login_check_lifetime,`+
` projections.login_policies.mfa_init_skip_lifetime,`+
` projections.login_policies.second_factor_check_lifetime,`+
` projections.login_policies.multi_factor_check_lifetime,`+
regexp.QuoteMeta(`SELECT projections.login_policies2.aggregate_id,`+
` projections.login_policies2.creation_date,`+
` projections.login_policies2.change_date,`+
` projections.login_policies2.sequence,`+
` projections.login_policies2.allow_register,`+
` projections.login_policies2.allow_username_password,`+
` projections.login_policies2.allow_external_idps,`+
` projections.login_policies2.force_mfa,`+
` projections.login_policies2.second_factors,`+
` projections.login_policies2.multi_factors,`+
` projections.login_policies2.passwordless_type,`+
` projections.login_policies2.is_default,`+
` projections.login_policies2.hide_password_reset,`+
` projections.login_policies2.ignore_unknown_usernames,`+
` projections.login_policies2.allow_domain_discovery,`+
` projections.login_policies2.default_redirect_uri,`+
` projections.login_policies2.password_check_lifetime,`+
` projections.login_policies2.external_login_check_lifetime,`+
` projections.login_policies2.mfa_init_skip_lifetime,`+
` projections.login_policies2.second_factor_check_lifetime,`+
` projections.login_policies2.multi_factor_check_lifetime,`+
` projections.idp_login_policy_links3.idp_id,`+
` projections.idps2.name,`+
` projections.idps2.type`+
` FROM projections.login_policies`+
` FROM projections.login_policies2`+
` LEFT JOIN projections.idp_login_policy_links3 ON `+
` projections.login_policies.aggregate_id = projections.idp_login_policy_links3.aggregate_id`+
` projections.login_policies2.aggregate_id = projections.idp_login_policy_links3.aggregate_id`+
` LEFT JOIN projections.idps2 ON`+
` projections.idp_login_policy_links3.idp_id = projections.idps2.id`),
nil,
@ -75,32 +76,33 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyQuery,
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies.aggregate_id,`+
` projections.login_policies.creation_date,`+
` projections.login_policies.change_date,`+
` projections.login_policies.sequence,`+
` projections.login_policies.allow_register,`+
` projections.login_policies.allow_username_password,`+
` projections.login_policies.allow_external_idps,`+
` projections.login_policies.force_mfa,`+
` projections.login_policies.second_factors,`+
` projections.login_policies.multi_factors,`+
` projections.login_policies.passwordless_type,`+
` projections.login_policies.is_default,`+
` projections.login_policies.hide_password_reset,`+
` projections.login_policies.ignore_unknown_usernames,`+
` projections.login_policies.default_redirect_uri,`+
` projections.login_policies.password_check_lifetime,`+
` projections.login_policies.external_login_check_lifetime,`+
` projections.login_policies.mfa_init_skip_lifetime,`+
` projections.login_policies.second_factor_check_lifetime,`+
` projections.login_policies.multi_factor_check_lifetime,`+
regexp.QuoteMeta(`SELECT projections.login_policies2.aggregate_id,`+
` projections.login_policies2.creation_date,`+
` projections.login_policies2.change_date,`+
` projections.login_policies2.sequence,`+
` projections.login_policies2.allow_register,`+
` projections.login_policies2.allow_username_password,`+
` projections.login_policies2.allow_external_idps,`+
` projections.login_policies2.force_mfa,`+
` projections.login_policies2.second_factors,`+
` projections.login_policies2.multi_factors,`+
` projections.login_policies2.passwordless_type,`+
` projections.login_policies2.is_default,`+
` projections.login_policies2.hide_password_reset,`+
` projections.login_policies2.ignore_unknown_usernames,`+
` projections.login_policies2.allow_domain_discovery,`+
` projections.login_policies2.default_redirect_uri,`+
` projections.login_policies2.password_check_lifetime,`+
` projections.login_policies2.external_login_check_lifetime,`+
` projections.login_policies2.mfa_init_skip_lifetime,`+
` projections.login_policies2.second_factor_check_lifetime,`+
` projections.login_policies2.multi_factor_check_lifetime,`+
` projections.idp_login_policy_links3.idp_id,`+
` projections.idps2.name,`+
` projections.idps2.type`+
` FROM projections.login_policies`+
` FROM projections.login_policies2`+
` LEFT JOIN projections.idp_login_policy_links3 ON `+
` projections.login_policies.aggregate_id = projections.idp_login_policy_links3.aggregate_id`+
` projections.login_policies2.aggregate_id = projections.idp_login_policy_links3.aggregate_id`+
` LEFT JOIN projections.idps2 ON`+
` projections.idp_login_policy_links3.idp_id = projections.idps2.id`),
[]string{
@ -118,6 +120,7 @@ func Test_LoginPolicyPrepares(t *testing.T) {
"is_default",
"hide_password_reset",
"ignore_unknown_usernames",
"allow_domain_discovery",
"default_redirect_uri",
"password_check_lifetime",
"external_login_check_lifetime",
@ -143,6 +146,7 @@ func Test_LoginPolicyPrepares(t *testing.T) {
true,
true,
true,
true,
"https://example.com/redirect",
time.Hour * 2,
time.Hour * 2,
@ -170,6 +174,7 @@ func Test_LoginPolicyPrepares(t *testing.T) {
IsDefault: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 2,
ExternalLoginCheckLifetime: time.Hour * 2,
@ -190,32 +195,33 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.login_policies.aggregate_id,`+
` projections.login_policies.creation_date,`+
` projections.login_policies.change_date,`+
` projections.login_policies.sequence,`+
` projections.login_policies.allow_register,`+
` projections.login_policies.allow_username_password,`+
` projections.login_policies.allow_external_idps,`+
` projections.login_policies.force_mfa,`+
` projections.login_policies.second_factors,`+
` projections.login_policies.multi_factors,`+
` projections.login_policies.passwordless_type,`+
` projections.login_policies.is_default,`+
` projections.login_policies.hide_password_reset,`+
` projections.login_policies.ignore_unknown_usernames,`+
` projections.login_policies.default_redirect_uri,`+
` projections.login_policies.password_check_lifetime,`+
` projections.login_policies.external_login_check_lifetime,`+
` projections.login_policies.mfa_init_skip_lifetime,`+
` projections.login_policies.second_factor_check_lifetime,`+
` projections.login_policies.multi_factor_check_lifetime,`+
regexp.QuoteMeta(`SELECT projections.login_policies2.aggregate_id,`+
` projections.login_policies2.creation_date,`+
` projections.login_policies2.change_date,`+
` projections.login_policies2.sequence,`+
` projections.login_policies2.allow_register,`+
` projections.login_policies2.allow_username_password,`+
` projections.login_policies2.allow_external_idps,`+
` projections.login_policies2.force_mfa,`+
` projections.login_policies2.second_factors,`+
` projections.login_policies2.multi_factors,`+
` projections.login_policies2.passwordless_type,`+
` projections.login_policies2.is_default,`+
` projections.login_policies2.hide_password_reset,`+
` projections.login_policies2.ignore_unknown_usernames,`+
` projections.login_policies2.allow_domain_discovery,`+
` projections.login_policies2.default_redirect_uri,`+
` projections.login_policies2.password_check_lifetime,`+
` projections.login_policies2.external_login_check_lifetime,`+
` projections.login_policies2.mfa_init_skip_lifetime,`+
` projections.login_policies2.second_factor_check_lifetime,`+
` projections.login_policies2.multi_factor_check_lifetime,`+
` projections.idp_login_policy_links3.idp_id,`+
` projections.idps2.name,`+
` projections.idps2.type`+
` FROM projections.login_policies`+
` FROM projections.login_policies2`+
` LEFT JOIN projections.idp_login_policy_links3 ON `+
` projections.login_policies.aggregate_id = projections.idp_login_policy_links3.aggregate_id`+
` projections.login_policies2.aggregate_id = projections.idp_login_policy_links3.aggregate_id`+
` LEFT JOIN projections.idps2 ON`+
` projections.idp_login_policy_links3.idp_id = projections.idps2.id`),
sql.ErrConnDone,
@ -234,8 +240,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicy2FAsQuery,
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies.second_factors`+
` FROM projections.login_policies`),
regexp.QuoteMeta(`SELECT projections.login_policies2.second_factors`+
` FROM projections.login_policies2`),
[]string{
"second_factors",
},
@ -255,8 +261,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicy2FAsQuery,
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies.second_factors`+
` FROM projections.login_policies`),
regexp.QuoteMeta(`SELECT projections.login_policies2.second_factors`+
` FROM projections.login_policies2`),
[]string{
"second_factors",
},
@ -277,8 +283,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicy2FAsQuery,
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies.second_factors`+
` FROM projections.login_policies`),
regexp.QuoteMeta(`SELECT projections.login_policies2.second_factors`+
` FROM projections.login_policies2`),
[]string{
"second_factors",
},
@ -294,8 +300,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicy2FAsQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.login_policies.second_factors`+
` FROM projections.login_policies`),
regexp.QuoteMeta(`SELECT projections.login_policies2.second_factors`+
` FROM projections.login_policies2`),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {
@ -312,8 +318,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyMFAsQuery,
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies.multi_factors`+
` FROM projections.login_policies`),
regexp.QuoteMeta(`SELECT projections.login_policies2.multi_factors`+
` FROM projections.login_policies2`),
[]string{
"multi_factors",
},
@ -333,8 +339,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyMFAsQuery,
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies.multi_factors`+
` FROM projections.login_policies`),
regexp.QuoteMeta(`SELECT projections.login_policies2.multi_factors`+
` FROM projections.login_policies2`),
[]string{
"multi_factors",
},
@ -355,8 +361,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyMFAsQuery,
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.login_policies.multi_factors`+
` FROM projections.login_policies`),
regexp.QuoteMeta(`SELECT projections.login_policies2.multi_factors`+
` FROM projections.login_policies2`),
[]string{
"multi_factors",
},
@ -372,8 +378,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
prepare: prepareLoginPolicyMFAsQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.login_policies.multi_factors`+
` FROM projections.login_policies`),
regexp.QuoteMeta(`SELECT projections.login_policies2.multi_factors`+
` FROM projections.login_policies2`),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {

View File

@ -104,7 +104,7 @@ func (q *Queries) OrgByID(ctx context.Context, shouldTriggerBulk bool, id string
return scan(row)
}
func (q *Queries) OrgByDomainGlobal(ctx context.Context, domain string) (*Org, error) {
func (q *Queries) OrgByPrimaryDomain(ctx context.Context, domain string) (*Org, error) {
stmt, scan := prepareOrgQuery()
query, args, err := stmt.Where(sq.Eq{
OrgColumnDomain.identifier(): domain,
@ -118,6 +118,21 @@ func (q *Queries) OrgByDomainGlobal(ctx context.Context, domain string) (*Org, e
return scan(row)
}
func (q *Queries) OrgByVerifiedDomain(ctx context.Context, domain string) (*Org, error) {
stmt, scan := prepareOrgWithDomainsQuery()
query, args, err := stmt.Where(sq.Eq{
OrgDomainDomainCol.identifier(): domain,
OrgDomainIsVerifiedCol.identifier(): true,
OrgColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
}).ToSql()
if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-TYUCE", "Errors.Query.SQLStatement")
}
row := q.client.QueryRowContext(ctx, query, args...)
return scan(row)
}
func (q *Queries) IsOrgUnique(ctx context.Context, name, domain string) (isUnique bool, err error) {
if name == "" && domain == "" {
return false, errors.ThrowInvalidArgument(nil, "QUERY-DGqfd", "Errors.Query.InvalidRequest")
@ -268,6 +283,42 @@ func prepareOrgQuery() (sq.SelectBuilder, func(*sql.Row) (*Org, error)) {
}
}
func prepareOrgWithDomainsQuery() (sq.SelectBuilder, func(*sql.Row) (*Org, error)) {
return sq.Select(
OrgColumnID.identifier(),
OrgColumnCreationDate.identifier(),
OrgColumnChangeDate.identifier(),
OrgColumnResourceOwner.identifier(),
OrgColumnState.identifier(),
OrgColumnSequence.identifier(),
OrgColumnName.identifier(),
OrgColumnDomain.identifier(),
).
From(orgsTable.identifier()).
LeftJoin(join(OrgDomainOrgIDCol, OrgColumnID)).
PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*Org, error) {
o := new(Org)
err := row.Scan(
&o.ID,
&o.CreationDate,
&o.ChangeDate,
&o.ResourceOwner,
&o.State,
&o.Sequence,
&o.Name,
&o.Domain,
)
if err != nil {
if errs.Is(err, sql.ErrNoRows) {
return nil, errors.ThrowNotFound(err, "QUERY-iTTGJ", "Errors.Org.NotFound")
}
return nil, errors.ThrowInternal(err, "QUERY-pWS5H", "Errors.Internal")
}
return o, nil
}
}
func prepareOrgUniqueQuery() (sq.SelectBuilder, func(*sql.Row) (bool, error)) {
return sq.Select(uniqueColumn.identifier()).
From(orgsTable.identifier()).PlaceholderFormat(sq.Dollar),

View File

@ -13,7 +13,7 @@ import (
)
const (
LoginPolicyTable = "projections.login_policies"
LoginPolicyTable = "projections.login_policies2"
LoginPolicyIDCol = "aggregate_id"
LoginPolicyInstanceIDCol = "instance_id"
@ -30,6 +30,7 @@ const (
LoginPolicyPasswordlessTypeCol = "passwordless_type"
LoginPolicyHidePWResetCol = "hide_password_reset"
IgnoreUnknownUsernames = "ignore_unknown_usernames"
AllowDomainDiscovery = "allow_domain_discovery"
DefaultRedirectURI = "default_redirect_uri"
PasswordCheckLifetimeCol = "password_check_lifetime"
ExternalLoginCheckLifetimeCol = "external_login_check_lifetime"
@ -63,6 +64,7 @@ func newLoginPolicyProjection(ctx context.Context, config crdb.StatementHandlerC
crdb.NewColumn(LoginPolicyPasswordlessTypeCol, crdb.ColumnTypeEnum),
crdb.NewColumn(LoginPolicyHidePWResetCol, crdb.ColumnTypeBool),
crdb.NewColumn(IgnoreUnknownUsernames, crdb.ColumnTypeBool),
crdb.NewColumn(AllowDomainDiscovery, crdb.ColumnTypeBool),
crdb.NewColumn(DefaultRedirectURI, crdb.ColumnTypeText, crdb.Nullable()),
crdb.NewColumn(PasswordCheckLifetimeCol, crdb.ColumnTypeInt64),
crdb.NewColumn(ExternalLoginCheckLifetimeCol, crdb.ColumnTypeInt64),
@ -172,6 +174,7 @@ func (p *loginPolicyProjection) reduceLoginPolicyAdded(event eventstore.Event) (
handler.NewCol(LoginPolicyIsDefaultCol, isDefault),
handler.NewCol(LoginPolicyHidePWResetCol, policyEvent.HidePasswordReset),
handler.NewCol(IgnoreUnknownUsernames, policyEvent.IgnoreUnknownUsernames),
handler.NewCol(AllowDomainDiscovery, policyEvent.AllowDomainDiscovery),
handler.NewCol(DefaultRedirectURI, policyEvent.DefaultRedirectURI),
handler.NewCol(PasswordCheckLifetimeCol, policyEvent.PasswordCheckLifetime),
handler.NewCol(ExternalLoginCheckLifetimeCol, policyEvent.ExternalLoginCheckLifetime),
@ -217,6 +220,9 @@ func (p *loginPolicyProjection) reduceLoginPolicyChanged(event eventstore.Event)
if policyEvent.IgnoreUnknownUsernames != nil {
cols = append(cols, handler.NewCol(IgnoreUnknownUsernames, *policyEvent.IgnoreUnknownUsernames))
}
if policyEvent.AllowDomainDiscovery != nil {
cols = append(cols, handler.NewCol(AllowDomainDiscovery, *policyEvent.AllowDomainDiscovery))
}
if policyEvent.DefaultRedirectURI != nil {
cols = append(cols, handler.NewCol(DefaultRedirectURI, *policyEvent.DefaultRedirectURI))
}

View File

@ -24,7 +24,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
want wantReduce
}{
{
name: "org.reduceLoginPolicyAdded",
name: "org reduceLoginPolicyAdded",
args: args{
event: getEvent(testEvent(
repository.EventType(org.LoginPolicyAddedEventType),
@ -36,6 +36,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"forceMFA": false,
"hidePasswordReset": true,
"ignoreUnknownUsernames": true,
"allowDomainDiscovery": true,
"passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect",
"passwordCheckLifetime": 10000000,
@ -55,7 +56,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.login_policies (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, 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)",
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)",
expectedArgs: []interface{}{
"agg-id",
"instance-id",
@ -70,6 +71,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
false,
true,
true,
true,
"https://example.com/redirect",
time.Millisecond * 10,
time.Millisecond * 10,
@ -83,7 +85,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
},
},
{
name: "org.reduceLoginPolicyChanged",
name: "org reduceLoginPolicyChanged",
reduce: (&loginPolicyProjection{}).reduceLoginPolicyChanged,
args: args{
event: getEvent(testEvent(
@ -96,6 +98,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"forceMFA": true,
"hidePasswordReset": true,
"ignoreUnknownUsernames": true,
"allowDomainDiscovery": true,
"passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect",
"passwordCheckLifetime": 10000000,
@ -114,7 +117,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, ignore_unknown_usernames, 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) WHERE (aggregate_id = $16)",
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)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@ -125,6 +128,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
domain.PasswordlessTypeAllowed,
true,
true,
true,
"https://example.com/redirect",
time.Millisecond * 10,
time.Millisecond * 10,
@ -139,7 +143,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
},
},
{
name: "org.reduceMFAAdded",
name: "org reduceMFAAdded",
reduce: (&loginPolicyProjection{}).reduceMFAAdded,
args: args{
event: getEvent(testEvent(
@ -158,7 +162,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4)",
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@ -171,7 +175,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
},
},
{
name: "org.reduceMFARemoved",
name: "org reduceMFARemoved",
reduce: (&loginPolicyProjection{}).reduceMFARemoved,
args: args{
event: getEvent(testEvent(
@ -190,7 +194,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4)",
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@ -203,7 +207,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
},
},
{
name: "org.reduceLoginPolicyRemoved",
name: "org reduceLoginPolicyRemoved",
reduce: (&loginPolicyProjection{}).reduceLoginPolicyRemoved,
args: args{
event: getEvent(testEvent(
@ -220,7 +224,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.login_policies WHERE (aggregate_id = $1)",
expectedStmt: "DELETE FROM projections.login_policies2 WHERE (aggregate_id = $1)",
expectedArgs: []interface{}{
"agg-id",
},
@ -230,7 +234,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
},
},
{
name: "org.reduce2FAAdded",
name: "org reduce2FAAdded",
reduce: (&loginPolicyProjection{}).reduce2FAAdded,
args: args{
event: getEvent(testEvent(
@ -249,7 +253,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4)",
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@ -262,7 +266,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
},
},
{
name: "org.reduce2FARemoved",
name: "org reduce2FARemoved",
reduce: (&loginPolicyProjection{}).reduce2FARemoved,
args: args{
event: getEvent(testEvent(
@ -281,7 +285,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4)",
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@ -294,7 +298,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
},
},
{
name: "instance.reduceLoginPolicyAdded",
name: "instance reduceLoginPolicyAdded",
reduce: (&loginPolicyProjection{}).reduceLoginPolicyAdded,
args: args{
event: getEvent(testEvent(
@ -307,6 +311,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"forceMFA": false,
"hidePasswordReset": true,
"ignoreUnknownUsernames": true,
"allowDomainDiscovery": true,
"passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect",
"passwordCheckLifetime": 10000000,
@ -325,7 +330,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.login_policies (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, 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)",
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)",
expectedArgs: []interface{}{
"agg-id",
"instance-id",
@ -340,6 +345,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
true,
true,
true,
true,
"https://example.com/redirect",
time.Millisecond * 10,
time.Millisecond * 10,
@ -353,7 +359,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
},
},
{
name: "instance.reduceLoginPolicyChanged",
name: "instance reduceLoginPolicyChanged",
reduce: (&loginPolicyProjection{}).reduceLoginPolicyChanged,
args: args{
event: getEvent(testEvent(
@ -366,6 +372,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"forceMFA": true,
"hidePasswordReset": true,
"ignoreUnknownUsernames": true,
"allowDomainDiscovery": true,
"passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect"
}`),
@ -379,7 +386,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, ignore_unknown_usernames, default_redirect_uri) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) WHERE (aggregate_id = $11)",
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)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@ -390,6 +397,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
domain.PasswordlessTypeAllowed,
true,
true,
true,
"https://example.com/redirect",
"agg-id",
},
@ -399,7 +407,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
},
},
{
name: "instance.reduceMFAAdded",
name: "instance reduceMFAAdded",
reduce: (&loginPolicyProjection{}).reduceMFAAdded,
args: args{
event: getEvent(testEvent(
@ -418,7 +426,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4)",
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@ -431,7 +439,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
},
},
{
name: "instance.reduceMFARemoved",
name: "instance reduceMFARemoved",
reduce: (&loginPolicyProjection{}).reduceMFARemoved,
args: args{
event: getEvent(testEvent(
@ -450,7 +458,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4)",
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@ -463,7 +471,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
},
},
{
name: "instance.reduce2FAAdded",
name: "instance reduce2FAAdded",
reduce: (&loginPolicyProjection{}).reduce2FAAdded,
args: args{
event: getEvent(testEvent(
@ -482,7 +490,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4)",
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@ -495,7 +503,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
},
},
{
name: "instance.reduce2FARemoved",
name: "instance reduce2FARemoved",
reduce: (&loginPolicyProjection{}).reduce2FARemoved,
args: args{
event: getEvent(testEvent(
@ -514,7 +522,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4)",
expectedStmt: "UPDATE projections.login_policies2 SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),

View File

@ -28,7 +28,8 @@ func NewLoginPolicyAddedEvent(
allowExternalIDP,
forceMFA,
hidePasswordReset,
ignoreUnknownUsernames bool,
ignoreUnknownUsernames,
allowDomainDiscovery bool,
passwordlessType domain.PasswordlessType,
defaultRedirectURI string,
passwordCheckLifetime,
@ -49,6 +50,7 @@ func NewLoginPolicyAddedEvent(
forceMFA,
hidePasswordReset,
ignoreUnknownUsernames,
allowDomainDiscovery,
passwordlessType,
defaultRedirectURI,
passwordCheckLifetime,

View File

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

View File

@ -26,6 +26,7 @@ type LoginPolicyAddedEvent struct {
ForceMFA bool `json:"forceMFA,omitempty"`
HidePasswordReset bool `json:"hidePasswordReset,omitempty"`
IgnoreUnknownUsernames bool `json:"ignoreUnknownUsernames,omitempty"`
AllowDomainDiscovery bool `json:"allowDomainDiscovery,omitempty"`
PasswordlessType domain.PasswordlessType `json:"passwordlessType,omitempty"`
DefaultRedirectURI string `json:"defaultRedirectURI,omitempty"`
PasswordCheckLifetime time.Duration `json:"passwordCheckLifetime,omitempty"`
@ -50,7 +51,8 @@ func NewLoginPolicyAddedEvent(
allowExternalIDP,
forceMFA,
hidePasswordReset,
ignoreUnknownUsernames bool,
ignoreUnknownUsernames,
allowDomainDiscovery bool,
passwordlessType domain.PasswordlessType,
defaultRedirectURI string,
passwordCheckLifetime,
@ -68,6 +70,7 @@ func NewLoginPolicyAddedEvent(
PasswordlessType: passwordlessType,
HidePasswordReset: hidePasswordReset,
IgnoreUnknownUsernames: ignoreUnknownUsernames,
AllowDomainDiscovery: allowDomainDiscovery,
DefaultRedirectURI: defaultRedirectURI,
PasswordCheckLifetime: passwordCheckLifetime,
ExternalLoginCheckLifetime: externalLoginCheckLifetime,
@ -99,6 +102,7 @@ type LoginPolicyChangedEvent struct {
ForceMFA *bool `json:"forceMFA,omitempty"`
HidePasswordReset *bool `json:"hidePasswordReset,omitempty"`
IgnoreUnknownUsernames *bool `json:"ignoreUnknownUsernames,omitempty"`
AllowDomainDiscovery *bool `json:"allowDomainDiscovery,omitempty"`
PasswordlessType *domain.PasswordlessType `json:"passwordlessType,omitempty"`
DefaultRedirectURI *string `json:"defaultRedirectURI,omitempty"`
PasswordCheckLifetime *time.Duration `json:"passwordCheckLifetime,omitempty"`
@ -206,6 +210,12 @@ func ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames bool) func(*LoginPolicy
}
}
func ChangeAllowDomainDiscovery(allowDomainDiscovery bool) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.AllowDomainDiscovery = &allowDomainDiscovery
}
}
func ChangeDefaultRedirectURI(defaultRedirectURI string) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.DefaultRedirectURI = &defaultRedirectURI

View File

@ -3798,7 +3798,7 @@ message UpdateLabelPolicyRequest {
];
bool hide_login_name_suffix = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set. Details about this scope in https://docs.zitadel.com/concepts#Reserved_Scopes";
description: "hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set";
}
];
string warn_color = 4 [(validate.rules).string = {max_len: 50}];
@ -3910,6 +3910,12 @@ message UpdateLoginPolicyRequest {
google.protobuf.Duration mfa_init_skip_lifetime = 11;
google.protobuf.Duration second_factor_check_lifetime = 12;
google.protobuf.Duration multi_factor_check_lifetime = 13;
// 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 allow_domain_discovery = 14 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
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."
}
];
}
message UpdateLoginPolicyResponse {
@ -4852,4 +4858,4 @@ message ExportDataRequest {
message ExportDataResponse {
repeated DataOrg orgs = 1;
}
}

View File

@ -4585,6 +4585,12 @@ message AddCustomLoginPolicyRequest {
repeated zitadel.policy.v1.SecondFactorType second_factors = 14;
repeated zitadel.policy.v1.MultiFactorType multi_factors = 15;
repeated IDP idps = 16;
// 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 allow_domain_discovery = 17 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
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."
}
];
}
message AddCustomLoginPolicyResponse {
@ -4613,6 +4619,12 @@ message UpdateCustomLoginPolicyRequest {
google.protobuf.Duration mfa_init_skip_lifetime = 11;
google.protobuf.Duration second_factor_check_lifetime = 12;
google.protobuf.Duration multi_factor_check_lifetime = 13;
// 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 allow_domain_discovery = 14 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
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."
}
];
}
message UpdateCustomLoginPolicyResponse {
@ -4890,10 +4902,10 @@ message GetDefaultLabelPolicyResponse {
message AddCustomLabelPolicyRequest {
string primary_color = 1 [(validate.rules).string = {max_len: 50}];
// hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set. Details about this scope in https://docs.zitadel.com/concepts#Reserved_Scopes
// hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set
bool hide_login_name_suffix = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set. Details about this scope in https://docs.zitadel.com/concepts#Reserved_Scopes";
description: "hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set";
}
];
string warn_color = 4 [(validate.rules).string = {max_len: 50}];
@ -4914,7 +4926,7 @@ message UpdateCustomLabelPolicyRequest {
string primary_color = 1 [(validate.rules).string = {max_len: 50}];
bool hide_login_name_suffix = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set. Details about this scope in https://docs.zitadel.com/concepts#Reserved_Scopes";
description: "hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set";
}
];
string warn_color = 4 [(validate.rules).string = {max_len: 50}];

View File

@ -62,10 +62,10 @@ message LabelPolicy {
description: "defines if the organisation's admin changed the policy"
}
];
// hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set. Details about this scope in https://docs.zitadel.com/concepts#Reserved_Scopes
// hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set
bool hide_login_name_suffix = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set. Details about this scope in https://docs.zitadel.com/concepts#Reserved_Scopes";
description: "hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set";
}
];
// hex value for secondary color
@ -173,6 +173,12 @@ message LoginPolicy {
repeated SecondFactorType second_factors = 16;
repeated MultiFactorType multi_factors = 17;
repeated zitadel.idp.v1.IDPLoginPolicyLink idps = 18;
// 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 allow_domain_discovery = 19 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
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."
}
];
}
enum SecondFactorType {