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

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

* console changes

* fix: handling of unknown usernames (#3445)

* fix: handling of unknown usernames

* fix: handle HideLoginNameSuffix on unknown users

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

* feat: add default redirect uri on login policy

* fix tests

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

* console default redirect

* placeholder

* validate default redirect uri

* allow empty default redirect uri

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

* remove wonrgly cherry picked migration

Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
Livio Amstutz
2022-05-16 15:39:09 +02:00
committed by GitHub
parent f1fa74a2c0
commit 411d7c6c5c
69 changed files with 655 additions and 107 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -35,7 +35,9 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"allowExternalIdp": false,
"forceMFA": false,
"hidePasswordReset": true,
"ignoreUnknownUsernames": true,
"passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect",
"passwordCheckLifetime": 10000000,
"externalLoginCheckLifetime": 10000000,
"mfaInitSkipLifetime": 10000000,
@@ -53,7 +55,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.login_policies (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)",
expectedStmt: "INSERT INTO projections.login_policies (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, ignore_unknown_usernames, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)",
expectedArgs: []interface{}{
"agg-id",
"instance-id",
@@ -67,6 +69,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
domain.PasswordlessTypeAllowed,
false,
true,
true,
"https://example.com/redirect",
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
@@ -91,7 +95,9 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"allowExternalIdp": true,
"forceMFA": true,
"hidePasswordReset": true,
"ignoreUnknownUsernames": true,
"passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect",
"passwordCheckLifetime": 10000000,
"externalLoginCheckLifetime": 10000000,
"mfaInitSkipLifetime": 10000000,
@@ -108,7 +114,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) WHERE (aggregate_id = $14)",
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, ignore_unknown_usernames, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE (aggregate_id = $16)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -118,6 +124,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
true,
domain.PasswordlessTypeAllowed,
true,
true,
"https://example.com/redirect",
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
@@ -298,7 +306,9 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"allowExternalIdp": false,
"forceMFA": false,
"hidePasswordReset": true,
"ignoreUnknownUsernames": true,
"passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect",
"passwordCheckLifetime": 10000000,
"externalLoginCheckLifetime": 10000000,
"mfaInitSkipLifetime": 10000000,
@@ -315,7 +325,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.login_policies (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)",
expectedStmt: "INSERT INTO projections.login_policies (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, ignore_unknown_usernames, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)",
expectedArgs: []interface{}{
"agg-id",
"instance-id",
@@ -329,6 +339,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
domain.PasswordlessTypeAllowed,
true,
true,
true,
"https://example.com/redirect",
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
@@ -353,7 +365,9 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"allowExternalIdp": true,
"forceMFA": true,
"hidePasswordReset": true,
"passwordlessType": 1
"ignoreUnknownUsernames": true,
"passwordlessType": 1,
"defaultRedirectURI": "https://example.com/redirect"
}`),
), instance.LoginPolicyChangedEventMapper),
},
@@ -365,7 +379,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (aggregate_id = $9)",
expectedStmt: "UPDATE projections.login_policies SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, ignore_unknown_usernames, default_redirect_uri) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) WHERE (aggregate_id = $11)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -375,6 +389,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
true,
domain.PasswordlessTypeAllowed,
true,
true,
"https://example.com/redirect",
"agg-id",
},
},