mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 03:57:32 +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:
@@ -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",
|
||||
},
|
||||
},
|
||||
|
Reference in New Issue
Block a user