mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 03:47:33 +00:00
feat: Login verification lifetimes (#3190)
* feat: add login check lifetimes to login policy * feat: org features test * feat: read lifetimes from loginpolicy
This commit is contained in:
@@ -14,19 +14,24 @@ import (
|
||||
)
|
||||
|
||||
type LoginPolicy struct {
|
||||
OrgID string
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
Sequence uint64
|
||||
AllowRegister bool
|
||||
AllowUsernamePassword bool
|
||||
AllowExternalIDPs bool
|
||||
ForceMFA bool
|
||||
SecondFactors []domain.SecondFactorType
|
||||
MultiFactors []domain.MultiFactorType
|
||||
PasswordlessType domain.PasswordlessType
|
||||
IsDefault bool
|
||||
HidePasswordReset bool
|
||||
OrgID string
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
Sequence uint64
|
||||
AllowRegister bool
|
||||
AllowUsernamePassword bool
|
||||
AllowExternalIDPs bool
|
||||
ForceMFA bool
|
||||
SecondFactors []domain.SecondFactorType
|
||||
MultiFactors []domain.MultiFactorType
|
||||
PasswordlessType domain.PasswordlessType
|
||||
IsDefault bool
|
||||
HidePasswordReset bool
|
||||
PasswordCheckLifetime time.Duration
|
||||
ExternalLoginCheckLifetime time.Duration
|
||||
MFAInitSkipLifetime time.Duration
|
||||
SecondFactorCheckLifetime time.Duration
|
||||
MultiFactorCheckLifetime time.Duration
|
||||
}
|
||||
|
||||
type SecondFactors struct {
|
||||
@@ -95,6 +100,26 @@ var (
|
||||
name: projection.LoginPolicyHidePWResetCol,
|
||||
table: loginPolicyTable,
|
||||
}
|
||||
LoginPolicyColumnPasswordCheckLifetime = Column{
|
||||
name: projection.PasswordCheckLifetimeCol,
|
||||
table: loginPolicyTable,
|
||||
}
|
||||
LoginPolicyColumnExternalLoginCheckLifetime = Column{
|
||||
name: projection.ExternalLoginCheckLifetimeCol,
|
||||
table: loginPolicyTable,
|
||||
}
|
||||
LoginPolicyColumnMFAInitSkipLifetime = Column{
|
||||
name: projection.MFAInitSkipLifetimeCol,
|
||||
table: loginPolicyTable,
|
||||
}
|
||||
LoginPolicyColumnSecondFactorCheckLifetime = Column{
|
||||
name: projection.SecondFactorCheckLifetimeCol,
|
||||
table: loginPolicyTable,
|
||||
}
|
||||
LoginPolicyColumnMultiFacotrCheckLifetime = Column{
|
||||
name: projection.MultiFactorCheckLifetimeCol,
|
||||
table: loginPolicyTable,
|
||||
}
|
||||
)
|
||||
|
||||
func (q *Queries) LoginPolicyByID(ctx context.Context, orgID string) (*LoginPolicy, error) {
|
||||
@@ -234,6 +259,11 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy,
|
||||
LoginPolicyColumnPasswordlessType.identifier(),
|
||||
LoginPolicyColumnIsDefault.identifier(),
|
||||
LoginPolicyColumnHidePasswordReset.identifier(),
|
||||
LoginPolicyColumnPasswordCheckLifetime.identifier(),
|
||||
LoginPolicyColumnExternalLoginCheckLifetime.identifier(),
|
||||
LoginPolicyColumnMFAInitSkipLifetime.identifier(),
|
||||
LoginPolicyColumnSecondFactorCheckLifetime.identifier(),
|
||||
LoginPolicyColumnMultiFacotrCheckLifetime.identifier(),
|
||||
).From(loginPolicyTable.identifier()).PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*LoginPolicy, error) {
|
||||
p := new(LoginPolicy)
|
||||
@@ -253,6 +283,11 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy,
|
||||
&p.PasswordlessType,
|
||||
&p.IsDefault,
|
||||
&p.HidePasswordReset,
|
||||
&p.PasswordCheckLifetime,
|
||||
&p.ExternalLoginCheckLifetime,
|
||||
&p.MFAInitSkipLifetime,
|
||||
&p.SecondFactorCheckLifetime,
|
||||
&p.MultiFactorCheckLifetime,
|
||||
)
|
||||
if err != nil {
|
||||
if errs.Is(err, sql.ErrNoRows) {
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
errs "github.com/caos/zitadel/internal/errors"
|
||||
@@ -41,7 +42,12 @@ func Test_LoginPolicyPrepares(t *testing.T) {
|
||||
` zitadel.projections.login_policies.multi_factors,`+
|
||||
` zitadel.projections.login_policies.passwordless_type,`+
|
||||
` zitadel.projections.login_policies.is_default,`+
|
||||
` zitadel.projections.login_policies.hide_password_reset`+
|
||||
` zitadel.projections.login_policies.hide_password_reset,`+
|
||||
` zitadel.projections.login_policies.password_check_lifetime,`+
|
||||
` zitadel.projections.login_policies.external_login_check_lifetime,`+
|
||||
` zitadel.projections.login_policies.mfa_init_skip_lifetime,`+
|
||||
` zitadel.projections.login_policies.second_factor_check_lifetime,`+
|
||||
` zitadel.projections.login_policies.multi_factor_check_lifetime`+
|
||||
` FROM zitadel.projections.login_policies`),
|
||||
nil,
|
||||
nil,
|
||||
@@ -72,7 +78,12 @@ func Test_LoginPolicyPrepares(t *testing.T) {
|
||||
` zitadel.projections.login_policies.multi_factors,`+
|
||||
` zitadel.projections.login_policies.passwordless_type,`+
|
||||
` zitadel.projections.login_policies.is_default,`+
|
||||
` zitadel.projections.login_policies.hide_password_reset`+
|
||||
` zitadel.projections.login_policies.hide_password_reset,`+
|
||||
` zitadel.projections.login_policies.password_check_lifetime,`+
|
||||
` zitadel.projections.login_policies.external_login_check_lifetime,`+
|
||||
` zitadel.projections.login_policies.mfa_init_skip_lifetime,`+
|
||||
` zitadel.projections.login_policies.second_factor_check_lifetime,`+
|
||||
` zitadel.projections.login_policies.multi_factor_check_lifetime`+
|
||||
` FROM zitadel.projections.login_policies`),
|
||||
[]string{
|
||||
"aggregate_id",
|
||||
@@ -88,6 +99,11 @@ func Test_LoginPolicyPrepares(t *testing.T) {
|
||||
"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",
|
||||
},
|
||||
[]driver.Value{
|
||||
"ro",
|
||||
@@ -103,23 +119,33 @@ func Test_LoginPolicyPrepares(t *testing.T) {
|
||||
domain.PasswordlessTypeAllowed,
|
||||
true,
|
||||
true,
|
||||
time.Hour * 2,
|
||||
time.Hour * 2,
|
||||
time.Hour * 2,
|
||||
time.Hour * 2,
|
||||
time.Hour * 2,
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &LoginPolicy{
|
||||
OrgID: "ro",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211109,
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDPs: true,
|
||||
ForceMFA: true,
|
||||
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
|
||||
MultiFactors: []domain.MultiFactorType{domain.MultiFactorTypeU2FWithPIN},
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
IsDefault: true,
|
||||
HidePasswordReset: true,
|
||||
OrgID: "ro",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211109,
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDPs: true,
|
||||
ForceMFA: true,
|
||||
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
|
||||
MultiFactors: []domain.MultiFactorType{domain.MultiFactorTypeU2FWithPIN},
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
IsDefault: true,
|
||||
HidePasswordReset: true,
|
||||
PasswordCheckLifetime: time.Hour * 2,
|
||||
ExternalLoginCheckLifetime: time.Hour * 2,
|
||||
MFAInitSkipLifetime: time.Hour * 2,
|
||||
SecondFactorCheckLifetime: time.Hour * 2,
|
||||
MultiFactorCheckLifetime: time.Hour * 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -139,7 +165,12 @@ func Test_LoginPolicyPrepares(t *testing.T) {
|
||||
` zitadel.projections.login_policies.multi_factors,`+
|
||||
` zitadel.projections.login_policies.passwordless_type,`+
|
||||
` zitadel.projections.login_policies.is_default,`+
|
||||
` zitadel.projections.login_policies.hide_password_reset`+
|
||||
` zitadel.projections.login_policies.hide_password_reset,`+
|
||||
` zitadel.projections.login_policies.password_check_lifetime,`+
|
||||
` zitadel.projections.login_policies.external_login_check_lifetime,`+
|
||||
` zitadel.projections.login_policies.mfa_init_skip_lifetime,`+
|
||||
` zitadel.projections.login_policies.second_factor_check_lifetime,`+
|
||||
` zitadel.projections.login_policies.multi_factor_check_lifetime`+
|
||||
` FROM zitadel.projections.login_policies`),
|
||||
sql.ErrConnDone,
|
||||
),
|
||||
|
@@ -111,6 +111,11 @@ const (
|
||||
LoginPolicyPasswordlessTypeCol = "passwordless_type"
|
||||
LoginPolicyIsDefaultCol = "is_default"
|
||||
LoginPolicyHidePWResetCol = "hide_password_reset"
|
||||
PasswordCheckLifetimeCol = "password_check_lifetime"
|
||||
ExternalLoginCheckLifetimeCol = "external_login_check_lifetime"
|
||||
MFAInitSkipLifetimeCol = "mfa_init_skip_lifetime"
|
||||
SecondFactorCheckLifetimeCol = "second_factor_check_lifetime"
|
||||
MultiFactorCheckLifetimeCol = "multi_factor_check_lifetime"
|
||||
)
|
||||
|
||||
func (p *LoginPolicyProjection) reduceLoginPolicyAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
@@ -140,6 +145,11 @@ func (p *LoginPolicyProjection) reduceLoginPolicyAdded(event eventstore.Event) (
|
||||
handler.NewCol(LoginPolicyPasswordlessTypeCol, policyEvent.PasswordlessType),
|
||||
handler.NewCol(LoginPolicyIsDefaultCol, isDefault),
|
||||
handler.NewCol(LoginPolicyHidePWResetCol, policyEvent.HidePasswordReset),
|
||||
handler.NewCol(PasswordCheckLifetimeCol, policyEvent.PasswordCheckLifetime),
|
||||
handler.NewCol(ExternalLoginCheckLifetimeCol, policyEvent.ExternalLoginCheckLifetime),
|
||||
handler.NewCol(MFAInitSkipLifetimeCol, policyEvent.MFAInitSkipLifetime),
|
||||
handler.NewCol(SecondFactorCheckLifetimeCol, policyEvent.SecondFactorCheckLifetime),
|
||||
handler.NewCol(MultiFactorCheckLifetimeCol, policyEvent.MultiFactorCheckLifetime),
|
||||
}), nil
|
||||
}
|
||||
|
||||
@@ -177,6 +187,22 @@ func (p *LoginPolicyProjection) reduceLoginPolicyChanged(event eventstore.Event)
|
||||
if policyEvent.HidePasswordReset != nil {
|
||||
cols = append(cols, handler.NewCol(LoginPolicyHidePWResetCol, *policyEvent.HidePasswordReset))
|
||||
}
|
||||
if policyEvent.PasswordCheckLifetime != nil {
|
||||
cols = append(cols, handler.NewCol(PasswordCheckLifetimeCol, *policyEvent.PasswordCheckLifetime))
|
||||
}
|
||||
if policyEvent.ExternalLoginCheckLifetime != nil {
|
||||
cols = append(cols, handler.NewCol(ExternalLoginCheckLifetimeCol, *policyEvent.ExternalLoginCheckLifetime))
|
||||
}
|
||||
if policyEvent.MFAInitSkipLifetime != nil {
|
||||
cols = append(cols, handler.NewCol(MFAInitSkipLifetimeCol, *policyEvent.MFAInitSkipLifetime))
|
||||
}
|
||||
if policyEvent.SecondFactorCheckLifetime != nil {
|
||||
cols = append(cols, handler.NewCol(SecondFactorCheckLifetimeCol, *policyEvent.SecondFactorCheckLifetime))
|
||||
}
|
||||
if policyEvent.MultiFactorCheckLifetime != nil {
|
||||
cols = append(cols, handler.NewCol(MultiFactorCheckLifetimeCol, *policyEvent.MultiFactorCheckLifetime))
|
||||
}
|
||||
|
||||
return crdb.NewUpdateStatement(
|
||||
&policyEvent,
|
||||
cols,
|
||||
|
@@ -2,6 +2,7 @@ package projection
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
@@ -29,13 +30,18 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
repository.EventType(org.LoginPolicyAddedEventType),
|
||||
org.AggregateType,
|
||||
[]byte(`{
|
||||
"allowUsernamePassword": true,
|
||||
"allowRegister": true,
|
||||
"allowExternalIdp": false,
|
||||
"forceMFA": false,
|
||||
"hidePasswordReset": true,
|
||||
"passwordlessType": 1
|
||||
}`),
|
||||
"allowUsernamePassword": true,
|
||||
"allowRegister": true,
|
||||
"allowExternalIdp": false,
|
||||
"forceMFA": false,
|
||||
"hidePasswordReset": true,
|
||||
"passwordlessType": 1,
|
||||
"passwordCheckLifetime": 10000000,
|
||||
"externalLoginCheckLifetime": 10000000,
|
||||
"mfaInitSkipLifetime": 10000000,
|
||||
"secondFactorCheckLifetime": 10000000,
|
||||
"multiFactorCheckLifetime": 10000000
|
||||
}`),
|
||||
), org.LoginPolicyAddedEventMapper),
|
||||
},
|
||||
reduce: (&LoginPolicyProjection{}).reduceLoginPolicyAdded,
|
||||
@@ -47,7 +53,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
|
||||
expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_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)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@@ -60,6 +66,11 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
domain.PasswordlessTypeAllowed,
|
||||
false,
|
||||
true,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -74,13 +85,18 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
repository.EventType(org.LoginPolicyChangedEventType),
|
||||
org.AggregateType,
|
||||
[]byte(`{
|
||||
"allowUsernamePassword": true,
|
||||
"allowRegister": true,
|
||||
"allowExternalIdp": true,
|
||||
"forceMFA": true,
|
||||
"hidePasswordReset": true,
|
||||
"passwordlessType": 1
|
||||
}`),
|
||||
"allowUsernamePassword": true,
|
||||
"allowRegister": true,
|
||||
"allowExternalIdp": true,
|
||||
"forceMFA": true,
|
||||
"hidePasswordReset": true,
|
||||
"passwordlessType": 1,
|
||||
"passwordCheckLifetime": 10000000,
|
||||
"externalLoginCheckLifetime": 10000000,
|
||||
"mfaInitSkipLifetime": 10000000,
|
||||
"secondFactorCheckLifetime": 10000000,
|
||||
"multiFactorCheckLifetime": 10000000
|
||||
}`),
|
||||
), org.LoginPolicyChangedEventMapper),
|
||||
},
|
||||
want: wantReduce{
|
||||
@@ -91,7 +107,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE zitadel.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 zitadel.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)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@@ -101,6 +117,11 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
true,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
"agg-id",
|
||||
},
|
||||
},
|
||||
@@ -271,12 +292,17 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
repository.EventType(iam.LoginPolicyAddedEventType),
|
||||
iam.AggregateType,
|
||||
[]byte(`{
|
||||
"allowUsernamePassword": true,
|
||||
"allowRegister": true,
|
||||
"allowExternalIdp": false,
|
||||
"forceMFA": false,
|
||||
"hidePasswordReset": true,
|
||||
"passwordlessType": 1
|
||||
"allowUsernamePassword": true,
|
||||
"allowRegister": true,
|
||||
"allowExternalIdp": false,
|
||||
"forceMFA": false,
|
||||
"hidePasswordReset": true,
|
||||
"passwordlessType": 1,
|
||||
"passwordCheckLifetime": 10000000,
|
||||
"externalLoginCheckLifetime": 10000000,
|
||||
"mfaInitSkipLifetime": 10000000,
|
||||
"secondFactorCheckLifetime": 10000000,
|
||||
"multiFactorCheckLifetime": 10000000
|
||||
}`),
|
||||
), iam.LoginPolicyAddedEventMapper),
|
||||
},
|
||||
@@ -288,7 +314,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
|
||||
expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_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)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@@ -301,6 +327,11 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
domain.PasswordlessTypeAllowed,
|
||||
true,
|
||||
true,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Reference in New Issue
Block a user