mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-13 11:40:16 +00:00
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:
@@ -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(),
|
||||
|
@@ -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(),
|
||||
|
@@ -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,
|
||||
},
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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) {
|
||||
|
@@ -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) {
|
||||
|
@@ -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)
|
||||
|
@@ -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 {
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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())
|
||||
|
@@ -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)
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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" .}}
|
||||
|
@@ -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" .}}
|
||||
|
@@ -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" .}}
|
||||
|
@@ -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" .}}
|
||||
|
@@ -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" .}}
|
||||
|
@@ -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" .}}
|
||||
|
@@ -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 }}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
}},
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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")
|
||||
}
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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),
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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))
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
73
internal/domain/policy_login_test.go
Normal file
73
internal/domain/policy_login_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
@@ -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]
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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,
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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,`+
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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",
|
||||
},
|
||||
},
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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),
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user