feat: add default redirect uri and handling of unknown usernames (#3616)

* feat: add possibility to ignore username errors on first login screen

* console changes

* fix: handling of unknown usernames (#3445)

* fix: handling of unknown usernames

* fix: handle HideLoginNameSuffix on unknown users

* feat: add default redirect uri on login policy (#3607)

* feat: add default redirect uri on login policy

* fix tests

* feat: Console login policy default redirect (#3613)

* console default redirect

* placeholder

* validate default redirect uri

* allow empty default redirect uri

Co-authored-by: Max Peintner <max@caos.ch>

* remove wonrgly cherry picked migration

Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
Livio Amstutz 2022-05-16 15:39:09 +02:00 committed by GitHub
parent f1fa74a2c0
commit 411d7c6c5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 655 additions and 107 deletions

View File

@ -247,7 +247,9 @@ DefaultInstance:
AllowExternalIDP: true
ForceMFA: false
HidePasswordReset: false
IgnoreUnknownUsernames: false
PasswordlessType: 1 #1: allowed 0: not allowed
DefaultRedirectURI: #empty because we use the Console UI
PasswordCheckLifetime: 240h #10d
ExternalLoginCheckLifetime: 240h #10d
MfaInitSkipLifetime: 720h #30d

View File

@ -129,6 +129,19 @@
</cnsl-info-section>
</ng-template> -->
</div>
<div class="login-policy-row">
<mat-slide-toggle class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.ignoreUnknownUsernames">
{{ 'POLICY.DATA.IGNOREUNKNOWNUSERNAMES' | translate }}
</mat-slide-toggle>
</div>
<div class="login-policy-row">
<cnsl-form-field class="form-field" label="Access Code" required="true">
<cnsl-label>{{ 'POLICY.DATA.DEFAULTREDIRECTURI' | translate }}</cnsl-label>
<input cnslInput placeholder="https://" [(ngModel)]="loginData.defaultRedirectUri" />
</cnsl-form-field>
</div>
</cnsl-card>
<div class="login-policy-btn-container">

View File

@ -88,10 +88,12 @@ export class LoginPolicyComponent implements OnInit {
mgmtreq.setForceMfa(this.loginData.forceMfa);
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
mgmtreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
mgmtreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
// if(this.loginData.passwordCheckLifetime) {
// mgmtreq.setPasswordCheckLifetime(this.loginData.passwordCheckLifetime);
// }
if ((this.loginData as LoginPolicy.AsObject).isDefault) {
return (this.service as ManagementService).addCustomLoginPolicy(mgmtreq);
} else {
@ -105,6 +107,8 @@ export class LoginPolicyComponent implements OnInit {
adminreq.setForceMfa(this.loginData.forceMfa);
adminreq.setPasswordlessType(this.loginData.passwordlessType);
adminreq.setHidePasswordReset(this.loginData.hidePasswordReset);
adminreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
adminreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
// adminreq.setPasswordCheckLifetime(this.loginData.passwordCheckLifetime);
return (this.service as AdminService).updateLoginPolicy(adminreq);

View File

@ -1092,6 +1092,10 @@
"HIDEPASSWORDRESET": "Passwort vergessen ausblenden",
"HIDEPASSWORDRESET_DESC": "Ist die Option gewählt, ist es nicht möglich im Login das Passwort zurück zusetzen via Passwort vergessen Link.",
"HIDELOGINNAMESUFFIX": "Loginname Suffix ausblenden",
"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.",
"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",
"DISABLEWATERMARK": "Wasserzeichen ausblenden"
},

View File

@ -1092,6 +1092,10 @@
"HIDEPASSWORDRESET": "Hide Password reset",
"HIDEPASSWORDRESET_DESC": "If the option is selected, the user can't reset his password in the login process.",
"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.",
"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",
"DISABLEWATERMARK": "Hide Watermark"
},

View File

@ -1092,6 +1092,10 @@
"HIDEPASSWORDRESET": "Nascondi ripristino della password",
"HIDEPASSWORDRESET_DESC": "Se l'opzione \u00e8 selezionata, l'utente non pu\u00f2 resettare la sua password nel interfaccia login.",
"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.",
"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",
"DISABLEWATERMARK": "Nascondi la filigrana"
},

View File

@ -3700,6 +3700,8 @@ This is an empty request
| force_mfa | bool | - | |
| passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true<br /> |
| hide_password_reset | bool | - | |
| ignore_unknown_usernames | bool | - | |
| default_redirect_uri | string | - | |
| password_check_lifetime | google.protobuf.Duration | - | |
| external_login_check_lifetime | google.protobuf.Duration | - | |
| mfa_init_skip_lifetime | google.protobuf.Duration | - | |

View File

@ -3039,6 +3039,8 @@ This is an empty request
| force_mfa | bool | - | |
| passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true<br /> |
| hide_password_reset | bool | - | |
| ignore_unknown_usernames | bool | - | |
| default_redirect_uri | string | - | |
| password_check_lifetime | google.protobuf.Duration | - | |
| external_login_check_lifetime | google.protobuf.Duration | - | |
| mfa_init_skip_lifetime | google.protobuf.Duration | - | |
@ -7760,6 +7762,8 @@ This is an empty request
| force_mfa | bool | - | |
| passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true<br /> |
| hide_password_reset | bool | - | |
| ignore_unknown_usernames | bool | - | |
| default_redirect_uri | string | - | |
| password_check_lifetime | google.protobuf.Duration | - | |
| external_login_check_lifetime | google.protobuf.Duration | - | |
| mfa_init_skip_lifetime | google.protobuf.Duration | - | |

View File

@ -77,6 +77,8 @@ title: zitadel/policy.proto
| passwordless_type | PasswordlessType | - | |
| is_default | bool | - | |
| hide_password_reset | bool | - | |
| ignore_unknown_usernames | bool | - | |
| default_redirect_uri | string | - | |
| password_check_lifetime | google.protobuf.Duration | - | |
| external_login_check_lifetime | google.protobuf.Duration | - | |
| mfa_init_skip_lifetime | google.protobuf.Duration | - | |

View File

@ -16,6 +16,8 @@ func updateLoginPolicyToDomain(p *admin_pb.UpdateLoginPolicyRequest) *domain.Log
ForceMFA: p.ForceMfa,
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
HidePasswordReset: p.HidePasswordReset,
IgnoreUnknownUsernames: p.IgnoreUnknownUsernames,
DefaultRedirectURI: p.DefaultRedirectUri,
PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(),
ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(),
MFAInitSkipLifetime: p.MfaInitSkipLifetime.AsDuration(),

View File

@ -16,6 +16,8 @@ func addLoginPolicyToDomain(p *mgmt_pb.AddCustomLoginPolicyRequest) *domain.Logi
ForceMFA: p.ForceMfa,
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
HidePasswordReset: p.HidePasswordReset,
IgnoreUnknownUsernames: p.IgnoreUnknownUsernames,
DefaultRedirectURI: p.DefaultRedirectUri,
PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(),
ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(),
MFAInitSkipLifetime: p.MfaInitSkipLifetime.AsDuration(),
@ -32,6 +34,8 @@ func updateLoginPolicyToDomain(p *mgmt_pb.UpdateCustomLoginPolicyRequest) *domai
ForceMFA: p.ForceMfa,
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
HidePasswordReset: p.HidePasswordReset,
IgnoreUnknownUsernames: p.IgnoreUnknownUsernames,
DefaultRedirectURI: p.DefaultRedirectUri,
PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(),
ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(),
MFAInitSkipLifetime: p.MfaInitSkipLifetime.AsDuration(),

View File

@ -1,12 +1,13 @@
package policy
import (
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/pkg/grpc/object"
policy_pb "github.com/zitadel/zitadel/pkg/grpc/policy"
"google.golang.org/protobuf/types/known/durationpb"
timestamp_pb "google.golang.org/protobuf/types/known/timestamppb"
)
func ModelLoginPolicyToPb(policy *query.LoginPolicy) *policy_pb.LoginPolicy {
@ -18,6 +19,8 @@ func ModelLoginPolicyToPb(policy *query.LoginPolicy) *policy_pb.LoginPolicy {
ForceMfa: policy.ForceMFA,
PasswordlessType: ModelPasswordlessTypeToPb(policy.PasswordlessType),
HidePasswordReset: policy.HidePasswordReset,
IgnoreUnknownUsernames: policy.IgnoreUnknownUsernames,
DefaultRedirectUri: policy.DefaultRedirectURI,
PasswordCheckLifetime: durationpb.New(policy.PasswordCheckLifetime),
ExternalLoginCheckLifetime: durationpb.New(policy.ExternalLoginCheckLifetime),
MfaInitSkipLifetime: durationpb.New(policy.MFAInitSkipLifetime),
@ -25,8 +28,8 @@ func ModelLoginPolicyToPb(policy *query.LoginPolicy) *policy_pb.LoginPolicy {
MultiFactorCheckLifetime: durationpb.New(policy.MultiFactorCheckLifetime),
Details: &object.ObjectDetails{
Sequence: policy.Sequence,
CreationDate: timestamp_pb.New(policy.CreationDate),
ChangeDate: timestamp_pb.New(policy.ChangeDate),
CreationDate: timestamppb.New(policy.CreationDate),
ChangeDate: timestamppb.New(policy.ChangeDate),
ResourceOwner: policy.OrgID,
},
}

View File

@ -75,7 +75,7 @@ func (l *Login) handleExternalLogin(w http.ResponseWriter, r *http.Request) {
return
}
if authReq == nil {
http.Redirect(w, r, l.consolePath, http.StatusFound)
l.defaultRedirect(w, r)
return
}
l.handleIDP(w, r, authReq, data.IDPConfigID)

View File

@ -58,7 +58,7 @@ func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) {
return
}
if authReq == nil {
http.Redirect(w, r, l.consolePath, http.StatusFound)
l.defaultRedirect(w, r)
return
}
idpConfig, err := l.getIDPConfigByID(r, data.IDPConfigID)

View File

@ -39,8 +39,8 @@ type initPasswordData struct {
HasSymbol string
}
func InitPasswordLink(origin, userID, code string) string {
return fmt.Sprintf("%s%s?userID=%s&code=%s", externalLink(origin), EndpointInitPassword, userID, code)
func InitPasswordLink(origin, userID, code, orgID string) string {
return fmt.Sprintf("%s%s?userID=%s&code=%s&orgID=%s", externalLink(origin), EndpointInitPassword, userID, code, orgID)
}
func (l *Login) handleInitPassword(w http.ResponseWriter, r *http.Request) {

View File

@ -42,8 +42,8 @@ type initUserData struct {
HasSymbol string
}
func InitUserLink(origin, userID, code string, passwordSet bool) string {
return fmt.Sprintf("%s%s?userID=%s&code=%s&passwordset=%t", externalLink(origin), EndpointInitUser, userID, code, passwordSet)
func InitUserLink(origin, userID, code, orgID string, passwordSet bool) string {
return fmt.Sprintf("%s%s?userID=%s&code=%s&passwordset=%t&orgID=%s", externalLink(origin), EndpointInitUser, userID, code, passwordSet, orgID)
}
func (l *Login) handleInitUser(w http.ResponseWriter, r *http.Request) {

View File

@ -120,7 +120,7 @@ func (l *Login) jwtExtractionUserNotFound(w http.ResponseWriter, r *http.Request
l.renderError(w, r, authReq, err)
return
}
resourceOwner := l.getOrgID(authReq)
resourceOwner := l.getOrgID(r, authReq)
orgIamPolicy, err := l.getOrgDomainPolicy(r, resourceOwner)
if err != nil {
l.renderError(w, r, authReq, err)

View File

@ -3,13 +3,16 @@ package login
import (
"net/http"
"github.com/zitadel/logging"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
)
const (
tmplLogin = "login"
tmplLogin = "login"
queryOrgID = "orgID"
)
type loginData struct {
@ -17,8 +20,8 @@ type loginData struct {
Register bool `schema:"register"`
}
func LoginLink(origin string) string {
return externalLink(origin) + EndpointLogin
func LoginLink(origin, orgID string) string {
return externalLink(origin) + EndpointLogin + "?orgID=" + orgID
}
func externalLink(origin string) string {
@ -32,12 +35,23 @@ func (l *Login) handleLogin(w http.ResponseWriter, r *http.Request) {
return
}
if authReq == nil {
http.Redirect(w, r, l.consolePath, http.StatusFound)
l.defaultRedirect(w, r)
return
}
l.renderNextStep(w, r, authReq)
}
func (l *Login) defaultRedirect(w http.ResponseWriter, r *http.Request) {
orgID := r.FormValue(queryOrgID)
policy, err := l.getLoginPolicy(r, orgID)
logging.OnError(err).WithField("orgID", orgID).Error("error loading login policy")
redirect := l.consolePath
if policy != nil && policy.DefaultRedirectURI != "" {
redirect = policy.DefaultRedirectURI
}
http.Redirect(w, r, redirect, http.StatusFound)
}
func (l *Login) handleLoginName(w http.ResponseWriter, r *http.Request) {
authReq, err := l.getAuthRequest(r)
if err != nil {

View File

@ -27,8 +27,8 @@ type mailVerificationData struct {
UserID string
}
func MailVerificationLink(origin, userID, code string) string {
return fmt.Sprintf("%s%s?userID=%s&code=%s", externalLink(origin), EndpointMailVerification, userID, code)
func MailVerificationLink(origin, userID, code, orgID string) string {
return fmt.Sprintf("%s%s?userID=%s&code=%s&orgID=%s", externalLink(origin), EndpointMailVerification, userID, code, orgID)
}
func (l *Login) handleMailVerification(w http.ResponseWriter, r *http.Request) {

View File

@ -40,6 +40,10 @@ func (l *Login) handlePasswordCheck(w http.ResponseWriter, r *http.Request) {
}
err = l.authRepo.VerifyPassword(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, authReq.UserOrgID, data.Password, authReq.AgentID, domain.BrowserInfoFromRequest(r))
if err != nil {
if authReq.LoginPolicy.IgnoreUnknownUsernames {
l.renderLogin(w, r, authReq, err)
return
}
l.renderPassword(w, r, authReq, err)
return
}

View File

@ -1,10 +1,11 @@
package login
import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"net/http"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query"
)
const (
@ -29,6 +30,9 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) {
}
passwordCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordResetCode, l.userCodeAlg)
if err != nil {
if authReq.LoginPolicy.IgnoreUnknownUsernames && errors.IsNotFound(err) {
err = nil
}
l.renderPasswordResetDone(w, r, authReq, err)
return
}

View File

@ -21,3 +21,10 @@ func (l *Login) getOrgDomainPolicy(r *http.Request, orgID string) (*query.Domain
func (l *Login) getIDPConfigByID(r *http.Request, idpConfigID string) (*iam_model.IDPConfigView, error) {
return l.authRepo.GetIDPConfigByID(r.Context(), idpConfigID)
}
func (l *Login) getLoginPolicy(r *http.Request, orgID string) (*query.LoginPolicy, error) {
if orgID == "" {
return l.query.DefaultLoginPolicy(r.Context())
}
return l.query.LoginPolicyByID(r.Context(), orgID)
}

View File

@ -90,7 +90,7 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
return
}
if authRequest == nil {
http.Redirect(w, r, l.consolePath, http.StatusFound)
l.defaultRedirect(w, r)
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())

View File

@ -73,7 +73,7 @@ func (l *Login) handleRegisterOrgCheck(w http.ResponseWriter, r *http.Request) {
return
}
if authRequest == nil {
http.Redirect(w, r, l.consolePath, http.StatusFound)
l.defaultRedirect(w, r)
return
}
l.renderNextStep(w, r, authRequest)

View File

@ -342,8 +342,8 @@ func (l *Login) getBaseData(r *http.Request, authReq *domain.AuthRequest, title
Theme: l.getTheme(r),
ThemeMode: l.getThemeMode(r),
DarkMode: l.isDarkMode(r),
PrivateLabelingOrgID: l.getPrivateLabelingID(authz.GetInstance(r.Context()).InstanceID(), authReq),
OrgID: l.getOrgID(authReq),
PrivateLabelingOrgID: l.getPrivateLabelingID(r, authReq),
OrgID: l.getOrgID(r, authReq),
OrgName: l.getOrgName(authReq),
PrimaryDomain: l.getOrgPrimaryDomain(authReq),
DisplayLoginNameSuffix: l.isDisplayLoginNameSuffix(authReq),
@ -361,6 +361,10 @@ func (l *Login) getBaseData(r *http.Request, authReq *domain.AuthRequest, title
}
privacyPolicy = authReq.PrivacyPolicy
} else {
labelPolicy, _ := l.query.ActiveLabelPolicyByOrg(r.Context(), baseData.PrivateLabelingOrgID)
if labelPolicy != nil {
baseData.LabelPolicy = labelPolicy.ToDomain()
}
policy, err := l.query.DefaultPrivacyPolicy(r.Context())
if err != nil {
return baseData
@ -446,9 +450,9 @@ func (l *Login) isDarkMode(r *http.Request) bool {
return strings.HasSuffix(cookie.Value, "dark")
}
func (l *Login) getOrgID(authReq *domain.AuthRequest) string {
func (l *Login) getOrgID(r *http.Request, authReq *domain.AuthRequest) string {
if authReq == nil {
return ""
return r.FormValue(queryOrgID)
}
if authReq.RequestedOrgID != "" {
return authReq.RequestedOrgID
@ -456,9 +460,12 @@ func (l *Login) getOrgID(authReq *domain.AuthRequest) string {
return authReq.UserOrgID
}
func (l *Login) getPrivateLabelingID(instanceID string, authReq *domain.AuthRequest) string {
privateLabelingOrgID := instanceID
func (l *Login) getPrivateLabelingID(r *http.Request, authReq *domain.AuthRequest) string {
privateLabelingOrgID := authz.GetInstance(r.Context()).InstanceID()
if authReq == nil {
if id := r.FormValue(queryOrgID); id != "" {
return id
}
return privateLabelingOrgID
}
if authReq.PrivateLabelingSetting != domain.PrivateLabelingSettingUnspecified {

View File

@ -322,6 +322,8 @@ Errors:
Empty: Passwort ist leer
Invalid: Passwort ungültig
InvalidAndLocked: Password ist undgültig und Benutzer wurde gesperrt, melden Sie sich bei ihrem Administrator.
UsernameOrPassword:
Invalid: Username oder Passwort ist ungültig
PasswordComplexityPolicy:
NotFound: Passwort Policy konnte nicht gefunden werden
MinLength: Passwort ist zu kurz

View File

@ -323,6 +323,8 @@ Errors:
Empty: Password is empty
Invalid: Password is invalid
InvalidAndLocked: Password is invalid and user is locked, contact your administrator.
UsernameOrPassword:
Invalid: Username or Password is invalid
PasswordComplexityPolicy:
NotFound: Password policy not found
MinLength: Password is to short

View File

@ -323,6 +323,8 @@ Errors:
Empty: La password è vuota
Invalid: La password non è valida
InvalidAndLocked: La password non è valida e l'utente è bloccato, contatta il tuo amministratore.
UsernameOrPassword:
Invalid: Il nome utente o la password non sono validi
PasswordComplexityPolicy:
NotFound: Impostazioni della password non trovate
MinLength: La password è troppo corta

View File

@ -13,6 +13,7 @@
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="userID" value="{{ .UserID }}" />
<input type="hidden" name="orgID" value="{{ .OrgID }}" />
<div class="fields">
<div class="field">
@ -56,4 +57,4 @@
<script src="{{ resourceUrl "scripts/init_password_check.js" }}"></script>
{{template "main-bottom" .}}
{{template "main-bottom" .}}

View File

@ -12,15 +12,13 @@
{{ .CSRF }}
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="orgID" value="{{ .OrgID }}" />
<div class="lgnactions">
<a class="lgn-stroked-button lgn-primary" href="{{ loginUrl }}">
{{t "InitPasswordDone.CancelButtonText"}}
</a>
<div class="lgn-actions">
<span class="fill-space"></span>
<button class="lgn-raised-button lgn-primary" type="submit">{{t "InitPasswordDone.NextButtonText"}}</button>
</div>
</form>
{{template "main-bottom" .}}
{{template "main-bottom" .}}

View File

@ -15,6 +15,7 @@
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="userID" value="{{ .UserID }}" />
<input type="hidden" name="passwordSet" value="{{ .PasswordSet }}" />
<input type="hidden" name="orgID" value="{{ .OrgID }}" />
<div class="fields">
<div class="field">
@ -63,4 +64,4 @@
<script src="{{ resourceUrl "scripts/init_password_check.js" }}"></script>
{{ end }}
{{template "main-bottom" .}}
{{template "main-bottom" .}}

View File

@ -13,6 +13,7 @@
{{ .CSRF }}
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="orgID" value="{{ .OrgID }}" />
<div class="lgn-actions lgn-reverse-order">
<button class="lgn-raised-button lgn-primary" type="submit">{{t "InitUserDone.NextButtonText"}}</button>
@ -24,4 +25,4 @@
</form>
{{template "main-bottom" .}}
{{template "main-bottom" .}}

View File

@ -13,6 +13,7 @@
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="userID" value="{{ .UserID }}" />
<input type="hidden" name="orgID" value="{{ .OrgID }}" />
<div class="fields">
<label class="lgn-label" for="code">{{t "EmailVerification.CodeLabel"}}</label>
@ -41,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

@ -12,6 +12,7 @@
{{ .CSRF }}
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="orgID" value="{{ .OrgID }}" />
<div class="lgn-actions">
<a class="lgn-stroked-button lgn-primary" href="{{ loginUrl }}">
@ -29,4 +30,4 @@
</form>
{{template "main-bottom" .}}
{{template "main-bottom" .}}

View File

@ -16,6 +16,7 @@
{{ .CSRF }}
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="orgID" value="{{ .OrgID }}" />
<div class="lgn-actions">
{{if not .HideNextButton }}

View File

@ -27,6 +27,8 @@ import (
user_view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
)
const unknownUserID = "UNKNOWN"
type AuthRequestRepo struct {
Command *command.Commands
Query *query.Queries
@ -284,7 +286,7 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAge
if err != nil {
return err
}
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, userID)
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, userID, false)
if err != nil {
return err
}
@ -304,13 +306,28 @@ func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, authReqID, user
defer func() { span.EndWithError(err) }()
request, err := repo.getAuthRequestEnsureUser(ctx, authReqID, userAgentID, userID)
if err != nil {
if isIgnoreUserNotFoundError(err, request) {
return errors.ThrowInvalidArgument(nil, "EVENT-SDe2f", "Errors.User.UsernameOrPassword.Invalid")
}
return err
}
policy, err := repo.getLockoutPolicy(ctx, resourceOwner)
if err != nil {
return err
}
return repo.Command.HumanCheckPassword(ctx, resourceOwner, userID, password, request.WithCurrentInfo(info), lockoutPolicyToDomain(policy))
err = repo.Command.HumanCheckPassword(ctx, resourceOwner, userID, password, request.WithCurrentInfo(info), lockoutPolicyToDomain(policy))
if isIgnoreUserInvalidPasswordError(err, request) {
return errors.ThrowInvalidArgument(nil, "EVENT-Jsf32", "Errors.User.UsernameOrPassword.Invalid")
}
return err
}
func isIgnoreUserNotFoundError(err error, request *domain.AuthRequest) bool {
return request != nil && request.LoginPolicy != nil && request.LoginPolicy.IgnoreUnknownUsernames && errors.IsNotFound(err) && errors.Contains(err, "Errors.User.NotFound")
}
func isIgnoreUserInvalidPasswordError(err error, request *domain.AuthRequest) bool {
return request != nil && request.LoginPolicy != nil && request.LoginPolicy.IgnoreUnknownUsernames && errors.IsErrorInvalidArgument(err) && errors.Contains(err, "Errors.User.Password.Invalid")
}
func lockoutPolicyToDomain(policy *query.LockoutPolicy) *domain.LockoutPolicy {
@ -499,9 +516,9 @@ func (repo *AuthRequestRepo) getAuthRequestEnsureUser(ctx context.Context, authR
if request.UserID != userID {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-GBH32", "Errors.User.NotMatchingUserID")
}
_, err = activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, request.UserID)
_, err = activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, request.UserID, false)
if err != nil {
return nil, err
return request, err
}
return request, nil
}
@ -613,8 +630,8 @@ func (repo *AuthRequestRepo) tryUsingOnlyUserSession(request *domain.AuthRequest
func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain.AuthRequest, loginName string) (err error) {
user := new(user_view_model.UserView)
preferredLoginName := loginName
if request.RequestedOrgID != "" {
preferredLoginName := loginName
if request.RequestedOrgID != "" {
preferredLoginName += "@" + request.RequestedPrimaryDomain
}
@ -628,6 +645,15 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain
}
}
}
if request.LoginPolicy.IgnoreUnknownUsernames {
if errors.IsNotFound(err) || (user != nil && user.State == int32(domain.UserStateInactive)) {
if request.LabelPolicy.HideLoginNameSuffix {
preferredLoginName = loginName
}
request.SetUserInfo(unknownUserID, preferredLoginName, preferredLoginName, preferredLoginName, "", request.RequestedOrgID)
return nil
}
}
if err != nil {
return err
}
@ -675,6 +701,7 @@ func queryLoginPolicyToDomain(policy *query.LoginPolicy) *domain.LoginPolicy {
MultiFactors: policy.MultiFactors,
PasswordlessType: policy.PasswordlessType,
HidePasswordReset: policy.HidePasswordReset,
IgnoreUnknownUsernames: policy.IgnoreUnknownUsernames,
PasswordCheckLifetime: policy.PasswordCheckLifetime,
ExternalLoginCheckLifetime: policy.ExternalLoginCheckLifetime,
MFAInitSkipLifetime: policy.MFAInitSkipLifetime,
@ -703,7 +730,7 @@ func (repo *AuthRequestRepo) checkExternalUserLogin(ctx context.Context, request
if err != nil {
return err
}
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, externalIDP.UserID)
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, externalIDP.UserID, false)
if err != nil {
return err
}
@ -749,11 +776,13 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
}
return steps, nil
}
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, request.UserID)
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, request.UserID, request.LoginPolicy.IgnoreUnknownUsernames)
if err != nil {
return nil, err
}
request.LoginName = user.PreferredLoginName
if user.PreferredLoginName != "" {
request.LoginName = user.PreferredLoginName
}
userSession, err := userSessionByIDs(ctx, repo.UserSessionViewProvider, repo.UserEventProvider, request.AgentID, user)
if err != nil {
return nil, err
@ -1129,10 +1158,16 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
return user_view_model.UserSessionToModel(&sessionCopy, provider.PrefixAvatarURL()), nil
}
func activeUserByID(ctx context.Context, userViewProvider userViewProvider, userEventProvider userEventProvider, queries orgViewProvider, lockoutPolicyProvider lockoutPolicyViewProvider, userID string) (*user_model.UserView, error) {
func activeUserByID(ctx context.Context, userViewProvider userViewProvider, userEventProvider userEventProvider, queries orgViewProvider, lockoutPolicyProvider lockoutPolicyViewProvider, userID string, ignoreUnknownUsernames bool) (user *user_model.UserView, err error) {
// PLANNED: Check LockoutPolicy
user, err := userByID(ctx, userViewProvider, userEventProvider, userID)
user, err = userByID(ctx, userViewProvider, userEventProvider, userID)
if err != nil {
if ignoreUnknownUsernames && errors.IsNotFound(err) {
return &user_model.UserView{
ID: userID,
HumanView: &user_model.HumanView{},
}, nil
}
return nil, err
}

View File

@ -421,7 +421,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewNoUser{},
userEventProvider: &mockEventUser{},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
nil,
errors.IsNotFound,
},
@ -443,7 +443,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
nil,
errors.IsPreconditionFailed,
},
@ -464,7 +464,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
nil,
errors.IsPreconditionFailed,
},
@ -480,7 +480,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
nil,
errors.IsInternal,
},
@ -496,7 +496,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
nil,
errors.IsPreconditionFailed,
},
@ -532,7 +532,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
nil,
errors.IsInternal,
},
@ -552,7 +552,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
[]domain.NextStep{&domain.InitUserStep{
PasswordSet: true,
}},

View File

@ -68,7 +68,9 @@ type InstanceSetup struct {
AllowExternalIDP bool
ForceMFA bool
HidePasswordReset bool
IgnoreUnknownUsername bool
PasswordlessType domain.PasswordlessType
DefaultRedirectURI string
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration
MfaInitSkipLifetime time.Duration
@ -205,7 +207,9 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
setup.LoginPolicy.AllowExternalIDP,
setup.LoginPolicy.ForceMFA,
setup.LoginPolicy.HidePasswordReset,
setup.LoginPolicy.IgnoreUnknownUsername,
setup.LoginPolicy.PasswordlessType,
setup.LoginPolicy.DefaultRedirectURI,
setup.LoginPolicy.PasswordCheckLifetime,
setup.LoginPolicy.ExternalLoginCheckLifetime,
setup.LoginPolicy.MfaInitSkipLifetime,

View File

@ -31,8 +31,10 @@ func writeModelToLoginPolicy(wm *LoginPolicyWriteModel) *domain.LoginPolicy {
AllowRegister: wm.AllowRegister,
AllowExternalIDP: wm.AllowExternalIDP,
HidePasswordReset: wm.HidePasswordReset,
IgnoreUnknownUsernames: wm.IgnoreUnknownUsernames,
ForceMFA: wm.ForceMFA,
PasswordlessType: wm.PasswordlessType,
DefaultRedirectURI: wm.DefaultRedirectURI,
PasswordCheckLifetime: wm.PasswordCheckLifetime,
ExternalLoginCheckLifetime: wm.ExternalLoginCheckLifetime,
MFAInitSkipLifetime: wm.MFAInitSkipLifetime,

View File

@ -17,7 +17,9 @@ func AddDefaultLoginPolicy(
allowExternalIDP bool,
forceMFA bool,
hidePasswordReset bool,
ignoreUnknownUsernames bool,
passwordlessType domain.PasswordlessType,
defaultRedirectURI string,
passwordCheckLifetime time.Duration,
externalLoginCheckLifetime time.Duration,
mfaInitSkipLifetime time.Duration,
@ -34,7 +36,9 @@ func AddDefaultLoginPolicy(
allowExternalIDP,
forceMFA,
hidePasswordReset,
ignoreUnknownUsernames,
passwordlessType,
defaultRedirectURI,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
@ -56,7 +57,9 @@ func (c *Commands) addDefaultLoginPolicy(ctx context.Context, instanceAgg *event
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
@ -83,6 +86,9 @@ func (c *Commands) ChangeDefaultLoginPolicy(ctx context.Context, policy *domain.
}
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
@ -97,12 +103,15 @@ func (c *Commands) changeDefaultLoginPolicy(ctx context.Context, instanceAgg *ev
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
policy.SecondFactorCheckLifetime,
policy.MultiFactorCheckLifetime)
policy.MultiFactorCheckLifetime,
)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged")
}

View File

@ -65,8 +65,10 @@ func (wm *InstanceLoginPolicyWriteModel) NewChangedEvent(
allowRegister,
allowExternalIDP,
forceMFA,
hidePasswordReset bool,
hidePasswordReset,
ignoreUnknownUsernames bool,
passwordlessType domain.PasswordlessType,
defaultRedirectURI string,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
@ -93,6 +95,12 @@ func (wm *InstanceLoginPolicyWriteModel) NewChangedEvent(
if wm.HidePasswordReset != hidePasswordReset {
changes = append(changes, policy.ChangeHidePasswordReset(hidePasswordReset))
}
if wm.IgnoreUnknownUsernames != ignoreUnknownUsernames {
changes = append(changes, policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames))
}
if wm.DefaultRedirectURI != defaultRedirectURI {
changes = append(changes, policy.ChangeDefaultRedirectURI(defaultRedirectURI))
}
if wm.PasswordCheckLifetime != passwordCheckLifetime {
changes = append(changes, policy.ChangePasswordCheckLifetime(passwordCheckLifetime))
}

View File

@ -50,7 +50,9 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*1,
time.Hour*1,
@ -90,7 +92,9 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -110,7 +114,9 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
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,
@ -130,7 +136,9 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
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,
@ -210,7 +218,9 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -229,7 +239,9 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
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,
@ -256,7 +268,9 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -275,7 +289,9 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*10,
time.Hour*20,
time.Hour*30,
@ -294,7 +310,9 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
AllowExternalIDP: false,
ForceMFA: false,
HidePasswordReset: false,
IgnoreUnknownUsernames: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
DefaultRedirectURI: "",
PasswordCheckLifetime: time.Hour * 10,
ExternalLoginCheckLifetime: time.Hour * 20,
MFAInitSkipLifetime: time.Hour * 30,
@ -314,7 +332,9 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
AllowExternalIDP: false,
ForceMFA: false,
HidePasswordReset: false,
IgnoreUnknownUsernames: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
DefaultRedirectURI: "",
PasswordCheckLifetime: time.Hour * 10,
ExternalLoginCheckLifetime: time.Hour * 20,
MFAInitSkipLifetime: time.Hour * 30,
@ -408,7 +428,9 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -444,7 +466,9 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -500,7 +524,9 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -638,7 +664,9 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -674,7 +702,9 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -723,7 +753,9 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -777,7 +809,9 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -839,7 +873,9 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -1392,8 +1428,9 @@ func TestCommandSide_RemoveMultiFactorDefaultLoginPolicy(t *testing.T) {
}
}
func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA, hidePasswordReset bool,
func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA, hidePasswordReset, ignoreUnknownUsernames bool,
passwordlessType domain.PasswordlessType,
redirectURI string,
passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime time.Duration) *instance.LoginPolicyChangedEvent {
event, _ := instance.NewLoginPolicyChangedEvent(ctx,
&instance.NewAggregate("INSTANCE").Aggregate,
@ -1403,7 +1440,9 @@ func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allow
policy.ChangeForceMFA(forceMFA),
policy.ChangeAllowUserNamePassword(allowUsernamePassword),
policy.ChangeHidePasswordReset(hidePasswordReset),
policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames),
policy.ChangePasswordlessType(passwordlessType),
policy.ChangeDefaultRedirectURI(redirectURI),
policy.ChangePasswordCheckLifetime(passwordLifetime),
policy.ChangeExternalLoginCheckLifetime(externalLoginLifetime),
policy.ChangeMFAInitSkipLifetime(mfaInitSkipLifetime),

View File

@ -16,6 +16,9 @@ func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, pol
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Fn8ds", "Errors.ResourceOwnerMissing")
}
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-WSfdq", "Errors.Org.LoginPolicy.RedirectURIInvalid")
}
addedPolicy := NewOrgLoginPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
@ -36,7 +39,9 @@ func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, pol
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
@ -76,6 +81,9 @@ func (c *Commands) ChangeLoginPolicy(ctx context.Context, resourceOwner string,
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Mf9sf", "Errors.ResourceOwnerMissing")
}
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 {
@ -94,7 +102,9 @@ func (c *Commands) ChangeLoginPolicy(ctx context.Context, resourceOwner string,
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.IgnoreUnknownUsernames,
policy.PasswordlessType,
policy.DefaultRedirectURI,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,

View File

@ -67,8 +67,10 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
allowRegister,
allowExternalIDP,
forceMFA,
hidePasswordReset bool,
hidePasswordReset,
ignoreUnknownUsernames bool,
passwordlessType domain.PasswordlessType,
defaultRedirectURI string,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
@ -92,6 +94,9 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
if wm.HidePasswordReset != hidePasswordReset {
changes = append(changes, policy.ChangeHidePasswordReset(hidePasswordReset))
}
if wm.IgnoreUnknownUsernames != ignoreUnknownUsernames {
changes = append(changes, policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames))
}
if wm.PasswordCheckLifetime != passwordCheckLifetime {
changes = append(changes, policy.ChangePasswordCheckLifetime(passwordCheckLifetime))
}
@ -110,6 +115,9 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
if passwordlessType.Valid() && wm.PasswordlessType != passwordlessType {
changes = append(changes, policy.ChangePasswordlessType(passwordlessType))
}
if wm.DefaultRedirectURI != defaultRedirectURI {
changes = append(changes, policy.ChangeDefaultRedirectURI(defaultRedirectURI))
}
if len(changes) == 0 {
return nil, false
}

View File

@ -77,7 +77,9 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -96,7 +98,9 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
IgnoreUnknownUsernames: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
@ -124,7 +128,9 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -145,7 +151,9 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
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,
@ -164,7 +172,9 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
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,
@ -222,11 +232,13 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
args: args{
ctx: context.Background(),
policy: &domain.LoginPolicy{
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
IgnoreUnknownUsernames: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
},
},
res: res{
@ -245,11 +257,13 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
ctx: context.Background(),
orgID: "org1",
policy: &domain.LoginPolicy{
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
IgnoreUnknownUsernames: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
DefaultRedirectURI: "https://example.com/redirect",
},
},
res: res{
@ -270,7 +284,9 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -290,7 +306,9 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
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,
@ -316,7 +334,9 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"https://example.com/redirect",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -335,7 +355,9 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
&duration10,
&duration20,
&duration30,
@ -355,7 +377,9 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
AllowUsernamePassword: false,
AllowExternalIDP: false,
ForceMFA: false,
IgnoreUnknownUsernames: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
DefaultRedirectURI: "",
PasswordCheckLifetime: time.Hour * 10,
ExternalLoginCheckLifetime: time.Hour * 20,
MFAInitSkipLifetime: time.Hour * 30,
@ -374,7 +398,9 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
AllowExternalIDP: false,
ForceMFA: false,
HidePasswordReset: false,
IgnoreUnknownUsernames: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
DefaultRedirectURI: "",
PasswordCheckLifetime: time.Hour * 10,
ExternalLoginCheckLifetime: time.Hour * 20,
MFAInitSkipLifetime: time.Hour * 30,
@ -465,7 +491,9 @@ func TestCommandSide_RemoveLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -603,7 +631,9 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -642,7 +672,9 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -701,7 +733,9 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -863,7 +897,9 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -902,7 +938,9 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -953,7 +991,9 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -1011,7 +1051,9 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -1077,7 +1119,9 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -1694,8 +1738,9 @@ func TestCommandSide_RemoveMultiFactorLoginPolicy(t *testing.T) {
}
}
func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassword, register, externalIDP, mfa, passwordReset bool,
func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassword, register, externalIDP, mfa, passwordReset, ignoreUnknownUsernames bool,
passwordlessType domain.PasswordlessType,
redirectURI string,
passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime *time.Duration) *org.LoginPolicyChangedEvent {
changes := []policy.LoginPolicyChanges{
policy.ChangeAllowUserNamePassword(usernamePassword),
@ -1703,7 +1748,9 @@ func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassw
policy.ChangeAllowExternalIDP(externalIDP),
policy.ChangeForceMFA(mfa),
policy.ChangeHidePasswordReset(passwordReset),
policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames),
policy.ChangePasswordlessType(passwordlessType),
policy.ChangeDefaultRedirectURI(redirectURI),
}
if passwordLifetime != nil {
changes = append(changes, policy.ChangePasswordCheckLifetime(*passwordLifetime))

View File

@ -16,7 +16,9 @@ type LoginPolicyWriteModel struct {
AllowExternalIDP bool
ForceMFA bool
HidePasswordReset bool
IgnoreUnknownUsernames bool
PasswordlessType domain.PasswordlessType
DefaultRedirectURI string
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration
MFAInitSkipLifetime time.Duration
@ -35,6 +37,8 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
wm.ForceMFA = e.ForceMFA
wm.PasswordlessType = e.PasswordlessType
wm.HidePasswordReset = e.HidePasswordReset
wm.IgnoreUnknownUsernames = e.IgnoreUnknownUsernames
wm.DefaultRedirectURI = e.DefaultRedirectURI
wm.PasswordCheckLifetime = e.PasswordCheckLifetime
wm.ExternalLoginCheckLifetime = e.ExternalLoginCheckLifetime
wm.MFAInitSkipLifetime = e.MFAInitSkipLifetime
@ -57,9 +61,15 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
if e.HidePasswordReset != nil {
wm.HidePasswordReset = *e.HidePasswordReset
}
if e.IgnoreUnknownUsernames != nil {
wm.IgnoreUnknownUsernames = *e.IgnoreUnknownUsernames
}
if e.PasswordlessType != nil {
wm.PasswordlessType = *e.PasswordlessType
}
if e.DefaultRedirectURI != nil {
wm.DefaultRedirectURI = *e.DefaultRedirectURI
}
if e.PasswordCheckLifetime != nil {
wm.PasswordCheckLifetime = *e.PasswordCheckLifetime
}

View File

@ -1156,7 +1156,9 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -1191,7 +1193,9 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -1227,7 +1231,9 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -1279,7 +1285,9 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -1365,7 +1373,9 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -1458,7 +1468,9 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,

View File

@ -1687,7 +1687,9 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -1750,7 +1752,9 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -1813,7 +1817,9 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -1893,7 +1899,9 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -2031,7 +2039,9 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -2137,7 +2147,9 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -2237,7 +2249,9 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
@ -2359,7 +2373,9 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,

View File

@ -1,6 +1,7 @@
package domain
import (
"net/url"
"time"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
@ -19,6 +20,8 @@ type LoginPolicy struct {
MultiFactors []MultiFactorType
PasswordlessType PasswordlessType
HidePasswordReset bool
IgnoreUnknownUsernames bool
DefaultRedirectURI string
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration
MFAInitSkipLifetime time.Duration
@ -26,6 +29,24 @@ type LoginPolicy struct {
MultiFactorCheckLifetime time.Duration
}
func ValidateDefaultRedirectURI(rawURL string) bool {
if rawURL == "" {
return true
}
parsedURL, err := url.Parse(rawURL)
if err != nil {
return false
}
switch parsedURL.Scheme {
case "":
return false
case "http", "https":
return parsedURL.Host != ""
default:
return true
}
}
type IDPProvider struct {
models.ObjectRoot
Type IdentityProviderType

View File

@ -0,0 +1,73 @@
package domain
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidateDefaultRedirectURI(t *testing.T) {
type args struct {
rawURL string
}
tests := []struct {
name string
args args
want bool
}{
{
"invalid url, false",
args{
rawURL: string('\n'),
},
false,
},
{
"empty schema, false",
args{
rawURL: "url",
},
false,
},
{
"empty http host, false",
args{
rawURL: "http://",
},
false,
},
{
"empty https host, false",
args{
rawURL: "https://",
},
false,
},
{
"https, ok",
args{
rawURL: "https://test",
},
true,
},
{
"custom schema, ok",
args{
rawURL: "custom://",
},
true,
},
{
"empty url, ok",
args{
rawURL: "",
},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, ValidateDefaultRedirectURI(tt.args.rawURL), "ValidateDefaultRedirectURI(%v)", tt.args.rawURL)
})
}
}

View File

@ -21,7 +21,7 @@ type DomainClaimedData struct {
}
func SendDomainClaimed(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, username string, emailConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), colors *query.LabelPolicy, assetsPrefix string, origin string) error {
url := login.LoginLink(origin)
url := login.LoginLink(origin, user.ResourceOwner)
var args = mapNotifyUserToArgs(user)
args["TempUsername"] = username
args["Domain"] = strings.Split(user.LastEmail, "@")[1]

View File

@ -26,7 +26,7 @@ func SendEmailVerificationCode(ctx context.Context, mailhtml string, translator
if err != nil {
return err
}
url := login.MailVerificationLink(origin, user.ID, codeString)
url := login.MailVerificationLink(origin, user.ID, codeString, user.ResourceOwner)
var args = mapNotifyUserToArgs(user)
args["Code"] = codeString

View File

@ -25,6 +25,7 @@ type UrlData struct {
UserID string
Code string
PasswordSet bool
OrgID string
}
func SendUserInitCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.InitUserCode, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix, origin string) error {
@ -32,7 +33,7 @@ func SendUserInitCode(ctx context.Context, mailhtml string, translator *i18n.Tra
if err != nil {
return err
}
url := login.InitUserLink(origin, user.ID, codeString, user.PasswordSet)
url := login.InitUserLink(origin, user.ID, codeString, user.ResourceOwner, user.PasswordSet)
var args = mapNotifyUserToArgs(user)
args["Code"] = codeString

View File

@ -29,7 +29,7 @@ func SendPasswordCode(ctx context.Context, mailhtml string, translator *i18n.Tra
if err != nil {
return err
}
url := login.InitPasswordLink(origin, user.ID, codeString)
url := login.InitPasswordLink(origin, user.ID, codeString, user.ResourceOwner)
var args = mapNotifyUserToArgs(user)
args["Code"] = codeString

View File

@ -307,3 +307,25 @@ func prepareLabelPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LabelPolicy,
return policy, nil
}
}
func (p *LabelPolicy) ToDomain() *domain.LabelPolicy {
return &domain.LabelPolicy{
Default: p.IsDefault,
PrimaryColor: p.Light.PrimaryColor,
BackgroundColor: p.Light.BackgroundColor,
WarnColor: p.Light.WarnColor,
FontColor: p.Light.FontColor,
LogoURL: p.Light.LogoURL,
IconURL: p.Light.IconURL,
PrimaryColorDark: p.Dark.PrimaryColor,
BackgroundColorDark: p.Dark.BackgroundColor,
WarnColorDark: p.Dark.WarnColor,
FontColorDark: p.Dark.FontColor,
LogoDarkURL: p.Dark.LogoURL,
IconDarkURL: p.Dark.IconURL,
Font: p.FontURL,
HideLoginNameSuffix: p.HideLoginNameSuffix,
ErrorMsgPopup: p.ShouldErrorPopup,
DisableWatermark: p.WatermarkDisabled,
}
}

View File

@ -10,7 +10,6 @@ import (
"github.com/lib/pq"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query/projection"
@ -30,6 +29,8 @@ type LoginPolicy struct {
PasswordlessType domain.PasswordlessType
IsDefault bool
HidePasswordReset bool
IgnoreUnknownUsernames bool
DefaultRedirectURI string
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration
MFAInitSkipLifetime time.Duration
@ -107,6 +108,14 @@ var (
name: projection.LoginPolicyHidePWResetCol,
table: loginPolicyTable,
}
LoginPolicyColumnIgnoreUnknownUsernames = Column{
name: projection.IgnoreUnknownUsernames,
table: loginPolicyTable,
}
LoginPolicyColumnDefaultRedirectURI = Column{
name: projection.DefaultRedirectURI,
table: loginPolicyTable,
}
LoginPolicyColumnPasswordCheckLifetime = Column{
name: projection.PasswordCheckLifetimeCol,
table: loginPolicyTable,
@ -284,6 +293,8 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy,
LoginPolicyColumnPasswordlessType.identifier(),
LoginPolicyColumnIsDefault.identifier(),
LoginPolicyColumnHidePasswordReset.identifier(),
LoginPolicyColumnIgnoreUnknownUsernames.identifier(),
LoginPolicyColumnDefaultRedirectURI.identifier(),
LoginPolicyColumnPasswordCheckLifetime.identifier(),
LoginPolicyColumnExternalLoginCheckLifetime.identifier(),
LoginPolicyColumnMFAInitSkipLifetime.identifier(),
@ -294,6 +305,7 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy,
p := new(LoginPolicy)
secondFactors := pq.Int32Array{}
multiFactors := pq.Int32Array{}
defaultRedirectURI := sql.NullString{}
err := row.Scan(
&p.OrgID,
&p.CreationDate,
@ -308,6 +320,8 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy,
&p.PasswordlessType,
&p.IsDefault,
&p.HidePasswordReset,
&p.IgnoreUnknownUsernames,
&defaultRedirectURI,
&p.PasswordCheckLifetime,
&p.ExternalLoginCheckLifetime,
&p.MFAInitSkipLifetime,
@ -320,7 +334,7 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy,
}
return nil, errors.ThrowInternal(err, "QUERY-YcC53", "Errors.Internal")
}
p.DefaultRedirectURI = defaultRedirectURI.String
p.MultiFactors = make([]domain.MultiFactorType, len(multiFactors))
for i, mfa := range multiFactors {
p.MultiFactors[i] = domain.MultiFactorType(mfa)

View File

@ -44,6 +44,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
` 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,`+
@ -80,6 +82,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
` 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,`+
@ -100,6 +104,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
"passwordless_type",
"is_default",
"hide_password_reset",
"ignore_unknown_usernames",
"default_redirect_uri",
"password_check_lifetime",
"external_login_check_lifetime",
"mfa_init_skip_lifetime",
@ -120,6 +126,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
domain.PasswordlessTypeAllowed,
true,
true,
true,
"https://example.com/redirect",
time.Hour * 2,
time.Hour * 2,
time.Hour * 2,
@ -142,6 +150,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
PasswordlessType: domain.PasswordlessTypeAllowed,
IsDefault: true,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
DefaultRedirectURI: "https://example.com/redirect",
PasswordCheckLifetime: time.Hour * 2,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 2,
@ -167,6 +177,8 @@ func Test_LoginPolicyPrepares(t *testing.T) {
` 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,`+

View File

@ -29,6 +29,8 @@ const (
LoginPolicyMFAsCol = "multi_factors"
LoginPolicyPasswordlessTypeCol = "passwordless_type"
LoginPolicyHidePWResetCol = "hide_password_reset"
IgnoreUnknownUsernames = "ignore_unknown_usernames"
DefaultRedirectURI = "default_redirect_uri"
PasswordCheckLifetimeCol = "password_check_lifetime"
ExternalLoginCheckLifetimeCol = "external_login_check_lifetime"
MFAInitSkipLifetimeCol = "mfa_init_skip_lifetime"
@ -60,6 +62,8 @@ func NewLoginPolicyProjection(ctx context.Context, config crdb.StatementHandlerC
crdb.NewColumn(LoginPolicyMFAsCol, crdb.ColumnTypeEnumArray, crdb.Nullable()),
crdb.NewColumn(LoginPolicyPasswordlessTypeCol, crdb.ColumnTypeEnum),
crdb.NewColumn(LoginPolicyHidePWResetCol, crdb.ColumnTypeBool),
crdb.NewColumn(IgnoreUnknownUsernames, crdb.ColumnTypeBool),
crdb.NewColumn(DefaultRedirectURI, crdb.ColumnTypeText, crdb.Nullable()),
crdb.NewColumn(PasswordCheckLifetimeCol, crdb.ColumnTypeInt64),
crdb.NewColumn(ExternalLoginCheckLifetimeCol, crdb.ColumnTypeInt64),
crdb.NewColumn(MFAInitSkipLifetimeCol, crdb.ColumnTypeInt64),
@ -167,6 +171,8 @@ func (p *LoginPolicyProjection) reduceLoginPolicyAdded(event eventstore.Event) (
handler.NewCol(LoginPolicyPasswordlessTypeCol, policyEvent.PasswordlessType),
handler.NewCol(LoginPolicyIsDefaultCol, isDefault),
handler.NewCol(LoginPolicyHidePWResetCol, policyEvent.HidePasswordReset),
handler.NewCol(IgnoreUnknownUsernames, policyEvent.IgnoreUnknownUsernames),
handler.NewCol(DefaultRedirectURI, policyEvent.DefaultRedirectURI),
handler.NewCol(PasswordCheckLifetimeCol, policyEvent.PasswordCheckLifetime),
handler.NewCol(ExternalLoginCheckLifetimeCol, policyEvent.ExternalLoginCheckLifetime),
handler.NewCol(MFAInitSkipLifetimeCol, policyEvent.MFAInitSkipLifetime),
@ -208,6 +214,12 @@ func (p *LoginPolicyProjection) reduceLoginPolicyChanged(event eventstore.Event)
if policyEvent.HidePasswordReset != nil {
cols = append(cols, handler.NewCol(LoginPolicyHidePWResetCol, *policyEvent.HidePasswordReset))
}
if policyEvent.IgnoreUnknownUsernames != nil {
cols = append(cols, handler.NewCol(IgnoreUnknownUsernames, *policyEvent.IgnoreUnknownUsernames))
}
if policyEvent.DefaultRedirectURI != nil {
cols = append(cols, handler.NewCol(DefaultRedirectURI, *policyEvent.DefaultRedirectURI))
}
if policyEvent.PasswordCheckLifetime != nil {
cols = append(cols, handler.NewCol(PasswordCheckLifetimeCol, *policyEvent.PasswordCheckLifetime))
}

View File

@ -35,7 +35,9 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"allowExternalIdp": false,
"forceMFA": false,
"hidePasswordReset": true,
"ignoreUnknownUsernames": true,
"passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect",
"passwordCheckLifetime": 10000000,
"externalLoginCheckLifetime": 10000000,
"mfaInitSkipLifetime": 10000000,
@ -53,7 +55,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, 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)",
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)",
expectedArgs: []interface{}{
"agg-id",
"instance-id",
@ -67,6 +69,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
domain.PasswordlessTypeAllowed,
false,
true,
true,
"https://example.com/redirect",
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
@ -91,7 +95,9 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"allowExternalIdp": true,
"forceMFA": true,
"hidePasswordReset": true,
"ignoreUnknownUsernames": true,
"passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect",
"passwordCheckLifetime": 10000000,
"externalLoginCheckLifetime": 10000000,
"mfaInitSkipLifetime": 10000000,
@ -108,7 +114,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, 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) WHERE (aggregate_id = $14)",
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)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@ -118,6 +124,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
true,
domain.PasswordlessTypeAllowed,
true,
true,
"https://example.com/redirect",
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
@ -298,7 +306,9 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"allowExternalIdp": false,
"forceMFA": false,
"hidePasswordReset": true,
"ignoreUnknownUsernames": true,
"passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect",
"passwordCheckLifetime": 10000000,
"externalLoginCheckLifetime": 10000000,
"mfaInitSkipLifetime": 10000000,
@ -315,7 +325,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, 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)",
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)",
expectedArgs: []interface{}{
"agg-id",
"instance-id",
@ -329,6 +339,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
domain.PasswordlessTypeAllowed,
true,
true,
true,
"https://example.com/redirect",
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
@ -353,7 +365,9 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"allowExternalIdp": true,
"forceMFA": true,
"hidePasswordReset": true,
"passwordlessType": 1
"ignoreUnknownUsernames": true,
"passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect"
}`),
), instance.LoginPolicyChangedEventMapper),
},
@ -365,7 +379,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) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (aggregate_id = $9)",
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)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@ -375,6 +389,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
true,
domain.PasswordlessTypeAllowed,
true,
true,
"https://example.com/redirect",
"agg-id",
},
},

View File

@ -27,8 +27,10 @@ func NewLoginPolicyAddedEvent(
allowRegister,
allowExternalIDP,
forceMFA,
hidePasswordReset bool,
hidePasswordReset,
ignoreUnknownUsernames bool,
passwordlessType domain.PasswordlessType,
defaultRedirectURI string,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
@ -46,7 +48,9 @@ func NewLoginPolicyAddedEvent(
allowExternalIDP,
forceMFA,
hidePasswordReset,
ignoreUnknownUsernames,
passwordlessType,
defaultRedirectURI,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,

View File

@ -28,8 +28,10 @@ func NewLoginPolicyAddedEvent(
allowRegister,
allowExternalIDP,
forceMFA,
hidePasswordReset bool,
hidePasswordReset,
ignoreUnknownUsernames bool,
passwordlessType domain.PasswordlessType,
defaultRedirectURI string,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
@ -47,7 +49,9 @@ func NewLoginPolicyAddedEvent(
allowExternalIDP,
forceMFA,
hidePasswordReset,
ignoreUnknownUsernames,
passwordlessType,
defaultRedirectURI,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,

View File

@ -25,7 +25,9 @@ type LoginPolicyAddedEvent struct {
AllowExternalIDP bool `json:"allowExternalIdp,omitempty"`
ForceMFA bool `json:"forceMFA,omitempty"`
HidePasswordReset bool `json:"hidePasswordReset,omitempty"`
IgnoreUnknownUsernames bool `json:"ignoreUnknownUsernames,omitempty"`
PasswordlessType domain.PasswordlessType `json:"passwordlessType,omitempty"`
DefaultRedirectURI string `json:"defaultRedirectURI,omitempty"`
PasswordCheckLifetime time.Duration `json:"passwordCheckLifetime,omitempty"`
ExternalLoginCheckLifetime time.Duration `json:"externalLoginCheckLifetime,omitempty"`
MFAInitSkipLifetime time.Duration `json:"mfaInitSkipLifetime,omitempty"`
@ -47,8 +49,10 @@ func NewLoginPolicyAddedEvent(
allowRegister,
allowExternalIDP,
forceMFA,
hidePasswordReset bool,
hidePasswordReset,
ignoreUnknownUsernames bool,
passwordlessType domain.PasswordlessType,
defaultRedirectURI string,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
@ -63,6 +67,8 @@ func NewLoginPolicyAddedEvent(
ForceMFA: forceMFA,
PasswordlessType: passwordlessType,
HidePasswordReset: hidePasswordReset,
IgnoreUnknownUsernames: ignoreUnknownUsernames,
DefaultRedirectURI: defaultRedirectURI,
PasswordCheckLifetime: passwordCheckLifetime,
ExternalLoginCheckLifetime: externalLoginCheckLifetime,
MFAInitSkipLifetime: mfaInitSkipLifetime,
@ -92,7 +98,9 @@ type LoginPolicyChangedEvent struct {
AllowExternalIDP *bool `json:"allowExternalIdp,omitempty"`
ForceMFA *bool `json:"forceMFA,omitempty"`
HidePasswordReset *bool `json:"hidePasswordReset,omitempty"`
IgnoreUnknownUsernames *bool `json:"ignoreUnknownUsernames,omitempty"`
PasswordlessType *domain.PasswordlessType `json:"passwordlessType,omitempty"`
DefaultRedirectURI *string `json:"defaultRedirectURI,omitempty"`
PasswordCheckLifetime *time.Duration `json:"passwordCheckLifetime,omitempty"`
ExternalLoginCheckLifetime *time.Duration `json:"externalLoginCheckLifetime,omitempty"`
MFAInitSkipLifetime *time.Duration `json:"mfaInitSkipLifetime,omitempty"`
@ -167,26 +175,43 @@ func ChangePasswordCheckLifetime(passwordCheckLifetime time.Duration) func(*Logi
e.PasswordCheckLifetime = &passwordCheckLifetime
}
}
func ChangeExternalLoginCheckLifetime(externalLoginCheckLifetime time.Duration) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.ExternalLoginCheckLifetime = &externalLoginCheckLifetime
}
}
func ChangeMFAInitSkipLifetime(mfaInitSkipLifetime time.Duration) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.MFAInitSkipLifetime = &mfaInitSkipLifetime
}
}
func ChangeSecondFactorCheckLifetime(secondFactorCheckLifetime time.Duration) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.SecondFactorCheckLifetime = &secondFactorCheckLifetime
}
}
func ChangeMultiFactorCheckLifetime(multiFactorCheckLifetime time.Duration) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.MultiFactorCheckLifetime = &multiFactorCheckLifetime
}
}
func ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames bool) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.IgnoreUnknownUsernames = &ignoreUnknownUsernames
}
}
func ChangeDefaultRedirectURI(defaultRedirectURI string) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.DefaultRedirectURI = &defaultRedirectURI
}
}
func LoginPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
e := &LoginPolicyChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),

View File

@ -176,6 +176,7 @@ Errors:
LoginPolicy:
NotFound: Login Policy konnte nicht gefunden werden
Invalid: Login Policy ist ungültig
RedirectURIInvalid: Default Redirect URI ist ungültig
NotExisting: Login Policy existiert nicht auf dieser Organisation
AlreadyExists: Login Policy existiert bereits
IdpProviderAlreadyExisting: Idp Provider existiert bereits
@ -285,6 +286,7 @@ Errors:
NotChanged: Default Login Policy wurde nicht verändert
NotExisting: Default Login Policy existiert nicht
AlreadyExists: Default Login Policy existiert bereits
RedirectURIInvalid: Default Redirect URI ist ungültig
MFA:
AlreadyExists: Multifaktor existiert bereits
NotExisting: Multifaktor existiert nicht

View File

@ -176,7 +176,8 @@ Errors:
LoginPolicy:
NotFound: Login Policy not found
Invalid: Login Policy is invalid
NotExisting: Login Policy not existig
RedirectURIInvalid: Default Redirect URI is invalid
NotExisting: Login Policy not existing
AlreadyExists: Login Policy already exists
IdpProviderAlreadyExisting: Idp Provider already existing
IdpProviderNotExisting: Idp Provider not existing
@ -285,6 +286,7 @@ Errors:
NotChanged: Default Login Policy has not been changed
NotExisting: Default Login Policy not existig
AlreadyExists: Default Login Policy already exists
RedirectURIInvalid: Default Redirect URI is invalid
MFA:
AlreadyExists: Multifactor already exists
NotExisting: Multifactor not existing

View File

@ -176,6 +176,7 @@ Errors:
LoginPolicy:
NotFound: Impostazioni di accesso non trovati
Invalid: Impostazioni di accesso non sono validi
RedirectURIInvalid: Default Redirect URI non valido
NotExisting: Impostazioni di accesso non esistenti
AlreadyExists: Impostazioni di accesso già esistenti
IdpProviderAlreadyExisting: IDP già esistente
@ -283,6 +284,7 @@ Errors:
NotChanged: Le impostazioni di accesso predefinite non sono state cambiate
NotExisting: Impostazioni di accesso predefinite non esistenti
AlreadyExists: Impostazioni di accesso predefinite già esistenti
RedirectURIInvalid: Default Redirect URI non valido
MFA:
AlreadyExists: Multifattore già esistente
NotExisting: Multifattore non esistente

View File

@ -3478,7 +3478,7 @@ message AddCustomDomainPolicyRequest {
required: ["org_id"]
};
};
string org_id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
@ -3677,11 +3677,21 @@ message UpdateLoginPolicyRequest {
description: "defines if password reset link should be shown in the login screen"
}
];
google.protobuf.Duration password_check_lifetime = 7;
google.protobuf.Duration external_login_check_lifetime = 8;
google.protobuf.Duration mfa_init_skip_lifetime = 9;
google.protobuf.Duration second_factor_check_lifetime = 10;
google.protobuf.Duration multi_factor_check_lifetime = 11;
bool ignore_unknown_usernames = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if unknown username on login screen directly return an error or always display the password screen"
}
];
string default_redirect_uri = 8 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines where the user will be redirected to if the login is started without app context (e.g. from mail)"
}
];
google.protobuf.Duration password_check_lifetime = 9;
google.protobuf.Duration external_login_check_lifetime = 10;
google.protobuf.Duration mfa_init_skip_lifetime = 11;
google.protobuf.Duration second_factor_check_lifetime = 12;
google.protobuf.Duration multi_factor_check_lifetime = 13;
}
message UpdateLoginPolicyResponse {

View File

@ -4344,11 +4344,21 @@ message AddCustomLoginPolicyRequest {
bool force_mfa = 4;
zitadel.policy.v1.PasswordlessType passwordless_type = 5 [(validate.rules).enum = {defined_only: true}];
bool hide_password_reset = 6;
google.protobuf.Duration password_check_lifetime = 7;
google.protobuf.Duration external_login_check_lifetime = 8;
google.protobuf.Duration mfa_init_skip_lifetime = 9;
google.protobuf.Duration second_factor_check_lifetime = 10;
google.protobuf.Duration multi_factor_check_lifetime = 11;
bool ignore_unknown_usernames = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if unknown username on login screen directly return an error or always display the password screen"
}
];
string default_redirect_uri = 8 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines where the user will be redirected to if the login is started without app context (e.g. from mail)"
}
];
google.protobuf.Duration password_check_lifetime = 9;
google.protobuf.Duration external_login_check_lifetime = 10;
google.protobuf.Duration mfa_init_skip_lifetime = 11;
google.protobuf.Duration second_factor_check_lifetime = 12;
google.protobuf.Duration multi_factor_check_lifetime = 13;
}
message AddCustomLoginPolicyResponse {
@ -4362,11 +4372,21 @@ message UpdateCustomLoginPolicyRequest {
bool force_mfa = 4;
zitadel.policy.v1.PasswordlessType passwordless_type = 5 [(validate.rules).enum = {defined_only: true}];
bool hide_password_reset = 6;
google.protobuf.Duration password_check_lifetime = 7;
google.protobuf.Duration external_login_check_lifetime = 8;
google.protobuf.Duration mfa_init_skip_lifetime = 9;
google.protobuf.Duration second_factor_check_lifetime = 10;
google.protobuf.Duration multi_factor_check_lifetime = 11;
bool ignore_unknown_usernames = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if unknown username on login screen directly return an error or always display the password screen"
}
];
string default_redirect_uri = 8 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines where the user will be redirected to if the login is started without app context (e.g. from mail)"
}
];
google.protobuf.Duration password_check_lifetime = 9;
google.protobuf.Duration external_login_check_lifetime = 10;
google.protobuf.Duration mfa_init_skip_lifetime = 11;
google.protobuf.Duration second_factor_check_lifetime = 12;
google.protobuf.Duration multi_factor_check_lifetime = 13;
}
message UpdateCustomLoginPolicyResponse {

View File

@ -149,11 +149,21 @@ message LoginPolicy {
description: "defines if password reset link should be shown in the login screen"
}
];
google.protobuf.Duration password_check_lifetime = 9;
google.protobuf.Duration external_login_check_lifetime = 10;
google.protobuf.Duration mfa_init_skip_lifetime = 11;
google.protobuf.Duration second_factor_check_lifetime = 12;
google.protobuf.Duration multi_factor_check_lifetime = 13;
bool ignore_unknown_usernames = 9 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if unknown username on login screen directly return an error or always display the password screen"
}
];
string default_redirect_uri = 10 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines where the user will be redirected to if the login is started without app context (e.g. from mail)"
}
];
google.protobuf.Duration password_check_lifetime = 11;
google.protobuf.Duration external_login_check_lifetime = 12;
google.protobuf.Duration mfa_init_skip_lifetime = 13;
google.protobuf.Duration second_factor_check_lifetime = 14;
google.protobuf.Duration multi_factor_check_lifetime = 15;
}