diff --git a/console/src/app/modules/features/features.component.html b/console/src/app/modules/features/features.component.html
index 8aaa7a3c92..dfa764d507 100644
--- a/console/src/app/modules/features/features.component.html
+++ b/console/src/app/modules/features/features.component.html
@@ -66,6 +66,15 @@
+
+ {{'FEATURES.DATA.LOGINPOLICYPASSWORDRESET' | translate}}
+
+
+
+
+
{{'FEATURES.DATA.LOGINPOLICYREGISTRATION' | translate}}
@@ -132,4 +141,4 @@
type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}
-
\ No newline at end of file
+
diff --git a/console/src/app/modules/features/features.component.ts b/console/src/app/modules/features/features.component.ts
index 1df129fe20..5429adbbea 100644
--- a/console/src/app/modules/features/features.component.ts
+++ b/console/src/app/modules/features/features.component.ts
@@ -152,6 +152,7 @@ export class FeaturesComponent implements OnDestroy {
req.setOrgId(this.org.id);
req.setLoginPolicyUsernameLogin(this.features.loginPolicyUsernameLogin);
+ req.setLoginPolicyPasswordReset(this.features.loginPolicyPasswordReset);
req.setLoginPolicyRegistration(this.features.loginPolicyRegistration);
req.setLoginPolicyIdp(this.features.loginPolicyIdp);
req.setLoginPolicyFactors(this.features.loginPolicyFactors);
@@ -170,6 +171,7 @@ export class FeaturesComponent implements OnDestroy {
// update Default org iam policy?
const dreq = new SetDefaultFeaturesRequest();
dreq.setLoginPolicyUsernameLogin(this.features.loginPolicyUsernameLogin);
+ dreq.setLoginPolicyPasswordReset(this.features.loginPolicyPasswordReset);
dreq.setLoginPolicyRegistration(this.features.loginPolicyRegistration);
dreq.setLoginPolicyIdp(this.features.loginPolicyIdp);
dreq.setLoginPolicyFactors(this.features.loginPolicyFactors);
diff --git a/console/src/app/modules/policies/login-policy/login-policy.component.html b/console/src/app/modules/policies/login-policy/login-policy.component.html
index bf17798f60..39ef180416 100644
--- a/console/src/app/modules/policies/login-policy/login-policy.component.html
+++ b/console/src/app/modules/policies/login-policy/login-policy.component.html
@@ -95,6 +95,26 @@
+
+
+ {{'POLICY.DATA.HIDEPASSWORDRESET' | translate}}
+
+
+
+ {{'FEATURES.NOTAVAILABLE' | translate: ({value:
+ 'login_policy.hide_password_reset'})}}
+
+
+
+
+
+ {{'POLICY.DATA.HIDEPASSWORDRESET_DESC' | translate}}
+
+
+
+
@@ -201,4 +221,4 @@
-
\ No newline at end of file
+
diff --git a/console/src/app/modules/policies/login-policy/login-policy.component.ts b/console/src/app/modules/policies/login-policy/login-policy.component.ts
index 9ce4e8697d..cc95cc6979 100644
--- a/console/src/app/modules/policies/login-policy/login-policy.component.ts
+++ b/console/src/app/modules/policies/login-policy/login-policy.component.ts
@@ -143,6 +143,7 @@ export class LoginPolicyComponent implements OnDestroy {
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
mgmtreq.setForceMfa(this.loginData.forceMfa);
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
+ mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
if ((this.loginData as LoginPolicy.AsObject).isDefault) {
return (this.service as ManagementService).addCustomLoginPolicy(mgmtreq);
} else {
@@ -155,6 +156,7 @@ export class LoginPolicyComponent implements OnDestroy {
adminreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
adminreq.setForceMfa(this.loginData.forceMfa);
adminreq.setPasswordlessType(this.loginData.passwordlessType);
+ adminreq.setHidePasswordReset(this.loginData.hidePasswordReset);
return (this.service as AdminService).updateLoginPolicy(adminreq);
}
diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json
index b81156cd52..0f66f1b3e9 100644
--- a/console/src/assets/i18n/de.json
+++ b/console/src/assets/i18n/de.json
@@ -607,6 +607,7 @@
"DATA": {
"AUDITLOGRETENTION": "Audit Log Retention",
"LOGINPOLICYUSERNAMELOGIN": "Login Richtlinie: Login mit Username erlauben - benutzerdefiniert",
+ "LOGINPOLICYPASSWORDRESET": "Login Richtlinie: Passwort vergessen Link nicht anzeigen - benutzerdefiniert",
"LOGINPOLICYREGISTRATION": "Login Richtlinie: Registration erlauben - benutzerdefiniert",
"LOGINPOLICYIDP": "Login Richtlinie: Identity Providers - benutzerdefiniert",
"LOGINPOLICYFACTORS": "Login Richtlinie: Mltifaktoren - benutzerdefiniert",
@@ -683,7 +684,9 @@
"ALLOWEXTERNALIDP_DESC": "Der Login wird für die darunter liegenden Identity Provider erlaubt.",
"ALLOWREGISTER_DESC": "Ist die Option gewählt, erscheint im Login ein zusätzlicher Schritt zum Registrieren eines Benutzers.",
"FORCEMFA": "Mfa erzwingen",
- "FORCEMFA_DESC": "Ist die Option gewählt, müssen Benutzer einen zweiten Faktor für den Login verwenden."
+ "FORCEMFA_DESC": "Ist die Option gewählt, müssen Benutzer einen zweiten Faktor für den Login verwenden.",
+ "HIDEPASSWORDRESET": "Passwort vergessen, nicht anzeigen",
+ "FORCEMFA_DESC": "Ist die Option gewählt, ist es nicht möglich im Login das Passwort zurück zusetzen via Passwort vergessen Link."
},
"RESET": "Richtlinie zurücksetzen",
"CREATECUSTOM": "Benutzerdefinierte Richtlinie erstellen",
diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json
index cba5af891b..84bf81802d 100644
--- a/console/src/assets/i18n/en.json
+++ b/console/src/assets/i18n/en.json
@@ -607,6 +607,7 @@
"DATA": {
"AUDITLOGRETENTION": "Audit Log Retention",
"LOGINPOLICYUSERNAMELOGIN": "Login Policy: Allow login with Username - custom",
+ "LOGINPOLICYPASSWORDRESET": "Login Policy: Hide reset password link - custom",
"LOGINPOLICYREGISTRATION": "Login Policy: Allow self registration - custom",
"LOGINPOLICYIDP": "Login Policy: Identity Provider - custom",
"LOGINPOLICYFACTORS": "Login Policy: Multifactors - custom",
@@ -683,7 +684,9 @@
"ALLOWEXTERNALIDP_DESC": "The login is allowed for the underlying identity providers",
"ALLOWREGISTER_DESC": "If the option is selected, an additional step for registering a user appears in the login.",
"FORCEMFA": "Force MFA",
- "FORCEMFA_DESC": "If the option is selected, users have to configure a second factor for login."
+ "FORCEMFA_DESC": "If the option is selected, users have to configure a second factor for login.",
+ "HIDEPASSWORDRESET": "Hide Password reset",
+ "FORCEMFA_DESC": "If the option is selected, the user can't reset his password in the login process."
},
"RESET": "Reset Policy",
"CREATECUSTOM": "Create Custom Policy",
diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md
index 0d1c3cdaec..4258869e95 100644
--- a/docs/docs/apis/proto/admin.md
+++ b/docs/docs/apis/proto/admin.md
@@ -1421,6 +1421,7 @@ This is an empty response
| password_complexity_policy | bool | - | |
| label_policy | bool | - | |
| custom_domain | bool | - | |
+| login_policy_password_reset | bool | - | |
@@ -1456,6 +1457,7 @@ This is an empty response
| password_complexity_policy | bool | - | |
| label_policy | bool | - | |
| custom_domain | bool | - | |
+| login_policy_password_reset | bool | - | |
@@ -1696,6 +1698,7 @@ This is an empty response
| allow_external_idp | bool | - | |
| force_mfa | bool | - | |
| passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true
|
+| hide_password_reset | bool | - | |
diff --git a/docs/docs/apis/proto/management.md b/docs/docs/apis/proto/management.md
index 98e4bc6cfc..a8a0a374cc 100644
--- a/docs/docs/apis/proto/management.md
+++ b/docs/docs/apis/proto/management.md
@@ -1803,6 +1803,7 @@ Change OIDC identity provider configuration of the organisation
| allow_external_idp | bool | - | |
| force_mfa | bool | - | |
| passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true
|
+| hide_password_reset | bool | - | |
@@ -4988,6 +4989,7 @@ This is an empty request
| allow_external_idp | bool | - | |
| force_mfa | bool | - | |
| passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true
|
+| hide_password_reset | bool | - | |
diff --git a/docs/docs/apis/proto/policy.md b/docs/docs/apis/proto/policy.md
index d286e9df85..25ae3a7769 100644
--- a/docs/docs/apis/proto/policy.md
+++ b/docs/docs/apis/proto/policy.md
@@ -37,6 +37,7 @@ title: zitadel/policy.proto
| force_mfa | bool | - | |
| passwordless_type | PasswordlessType | - | |
| is_default | bool | - | |
+| hide_password_reset | bool | - | |
diff --git a/internal/api/grpc/admin/features.go b/internal/api/grpc/admin/features.go
index eb0613fec6..bd7950235f 100644
--- a/internal/api/grpc/admin/features.go
+++ b/internal/api/grpc/admin/features.go
@@ -69,6 +69,7 @@ func setDefaultFeaturesRequestToDomain(req *admin_pb.SetDefaultFeaturesRequest)
LoginPolicyPasswordless: req.LoginPolicyPasswordless,
LoginPolicyRegistration: req.LoginPolicyRegistration,
LoginPolicyUsernameLogin: req.LoginPolicyUsernameLogin,
+ LoginPolicyPasswordReset: req.LoginPolicyPasswordReset,
PasswordComplexityPolicy: req.PasswordComplexityPolicy,
LabelPolicy: req.LabelPolicy,
CustomDomain: req.CustomDomain,
@@ -87,6 +88,7 @@ func setOrgFeaturesRequestToDomain(req *admin_pb.SetOrgFeaturesRequest) *domain.
LoginPolicyPasswordless: req.LoginPolicyPasswordless,
LoginPolicyRegistration: req.LoginPolicyRegistration,
LoginPolicyUsernameLogin: req.LoginPolicyUsernameLogin,
+ LoginPolicyPasswordReset: req.LoginPolicyPasswordReset,
PasswordComplexityPolicy: req.PasswordComplexityPolicy,
LabelPolicy: req.LabelPolicy,
CustomDomain: req.CustomDomain,
diff --git a/internal/api/grpc/admin/login_policy_converter.go b/internal/api/grpc/admin/login_policy_converter.go
index fa7737fdf3..b2af7a00e1 100644
--- a/internal/api/grpc/admin/login_policy_converter.go
+++ b/internal/api/grpc/admin/login_policy_converter.go
@@ -15,6 +15,7 @@ func updateLoginPolicyToDomain(p *admin_pb.UpdateLoginPolicyRequest) *domain.Log
AllowExternalIDP: p.AllowExternalIdp,
ForceMFA: p.ForceMfa,
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
+ HidePasswordReset: p.HidePasswordReset,
}
}
diff --git a/internal/api/grpc/features/features.go b/internal/api/grpc/features/features.go
index e4dc2dceba..17055cc662 100644
--- a/internal/api/grpc/features/features.go
+++ b/internal/api/grpc/features/features.go
@@ -21,6 +21,7 @@ func FeaturesFromModel(features *features_model.FeaturesView) *features_pb.Featu
LoginPolicyPasswordless: features.LoginPolicyPasswordless,
LoginPolicyRegistration: features.LoginPolicyRegistration,
LoginPolicyUsernameLogin: features.LoginPolicyUsernameLogin,
+ LoginPolicyPasswordReset: features.LoginPolicyPasswordReset,
PasswordComplexityPolicy: features.PasswordComplexityPolicy,
LabelPolicy: features.LabelPolicy,
CustomDomain: features.CustomDomain,
diff --git a/internal/api/grpc/management/policy_login_converter.go b/internal/api/grpc/management/policy_login_converter.go
index ef74d57cb0..a85d15e8bb 100644
--- a/internal/api/grpc/management/policy_login_converter.go
+++ b/internal/api/grpc/management/policy_login_converter.go
@@ -15,6 +15,7 @@ func addLoginPolicyToDomain(p *mgmt_pb.AddCustomLoginPolicyRequest) *domain.Logi
AllowExternalIDP: p.AllowExternalIdp,
ForceMFA: p.ForceMfa,
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
+ HidePasswordReset: p.HidePasswordReset,
}
}
@@ -25,6 +26,7 @@ func updateLoginPolicyToDomain(p *mgmt_pb.UpdateCustomLoginPolicyRequest) *domai
AllowExternalIDP: p.AllowExternalIdp,
ForceMFA: p.ForceMfa,
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
+ HidePasswordReset: p.HidePasswordReset,
}
}
diff --git a/internal/api/grpc/policy/login_policy.go b/internal/api/grpc/policy/login_policy.go
index ca58a3df83..48c57ffe1c 100644
--- a/internal/api/grpc/policy/login_policy.go
+++ b/internal/api/grpc/policy/login_policy.go
@@ -14,6 +14,7 @@ func ModelLoginPolicyToPb(policy *model.LoginPolicyView) *policy_pb.LoginPolicy
AllowExternalIdp: policy.AllowExternalIDP,
ForceMfa: policy.ForceMFA,
PasswordlessType: ModelPasswordlessTypeToPb(policy.PasswordlessType),
+ HidePasswordReset: policy.HidePasswordReset,
}
}
diff --git a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go
index ba2b370c34..a937553a3f 100644
--- a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go
+++ b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go
@@ -163,6 +163,10 @@ func checkLoginPolicyFeatures(features *features_view_model.FeaturesView, requir
if !features.LoginPolicyUsernameLogin {
return MissingFeatureErr(requiredFeature)
}
+ case domain.FeatureLoginPolicyPasswordReset:
+ if !features.LoginPolicyPasswordReset {
+ return MissingFeatureErr(requiredFeature)
+ }
default:
if !features.LoginPolicyFactors && !features.LoginPolicyIDP && !features.LoginPolicyPasswordless && !features.LoginPolicyRegistration && !features.LoginPolicyUsernameLogin {
return MissingFeatureErr(requiredFeature)
diff --git a/internal/command/features_model.go b/internal/command/features_model.go
index 5483b67621..679c64db82 100644
--- a/internal/command/features_model.go
+++ b/internal/command/features_model.go
@@ -21,6 +21,7 @@ type FeaturesWriteModel struct {
LoginPolicyPasswordless bool
LoginPolicyRegistration bool
LoginPolicyUsernameLogin bool
+ LoginPolicyPasswordReset bool
PasswordComplexityPolicy bool
LabelPolicy bool
CustomDomain bool
@@ -61,6 +62,9 @@ func (wm *FeaturesWriteModel) Reduce() error {
if e.LoginPolicyUsernameLogin != nil {
wm.LoginPolicyUsernameLogin = *e.LoginPolicyUsernameLogin
}
+ if e.LoginPolicyPasswordReset != nil {
+ wm.LoginPolicyPasswordReset = *e.LoginPolicyPasswordReset
+ }
if e.PasswordComplexityPolicy != nil {
wm.PasswordComplexityPolicy = *e.PasswordComplexityPolicy
}
diff --git a/internal/command/iam_converter.go b/internal/command/iam_converter.go
index 020306069b..9fb5f82d1a 100644
--- a/internal/command/iam_converter.go
+++ b/internal/command/iam_converter.go
@@ -39,6 +39,7 @@ func writeModelToLoginPolicy(wm *LoginPolicyWriteModel) *domain.LoginPolicy {
AllowUsernamePassword: wm.AllowUserNamePassword,
AllowRegister: wm.AllowRegister,
AllowExternalIDP: wm.AllowExternalIDP,
+ HidePasswordReset: wm.HidePasswordReset,
ForceMFA: wm.ForceMFA,
PasswordlessType: wm.PasswordlessType,
}
diff --git a/internal/command/iam_policy_login.go b/internal/command/iam_policy_login.go
index 170793a00c..ccf1052823 100644
--- a/internal/command/iam_policy_login.go
+++ b/internal/command/iam_policy_login.go
@@ -48,7 +48,7 @@ func (c *Commands) addDefaultLoginPolicy(ctx context.Context, iamAgg *eventstore
return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-2B0ps", "Errors.IAM.LoginPolicy.AlreadyExists")
}
- return iam_repo.NewLoginPolicyAddedEvent(ctx, iamAgg, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.PasswordlessType), nil
+ return iam_repo.NewLoginPolicyAddedEvent(ctx, iamAgg, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.HidePasswordReset, policy.PasswordlessType), nil
}
func (c *Commands) ChangeDefaultLoginPolicy(ctx context.Context, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) {
@@ -77,7 +77,7 @@ func (c *Commands) changeDefaultLoginPolicy(ctx context.Context, iamAgg *eventst
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-M0sif", "Errors.IAM.LoginPolicy.NotFound")
}
- changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, iamAgg, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.PasswordlessType)
+ changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, iamAgg, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.HidePasswordReset, policy.PasswordlessType)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged")
}
diff --git a/internal/command/iam_policy_login_model.go b/internal/command/iam_policy_login_model.go
index 621f0e5e05..003566ad36 100644
--- a/internal/command/iam_policy_login_model.go
+++ b/internal/command/iam_policy_login_model.go
@@ -58,7 +58,8 @@ func (wm *IAMLoginPolicyWriteModel) NewChangedEvent(
allowUsernamePassword,
allowRegister,
allowExternalIDP,
- forceMFA bool,
+ forceMFA,
+ hidePasswordReset bool,
passwordlessType domain.PasswordlessType,
) (*iam.LoginPolicyChangedEvent, bool) {
@@ -78,6 +79,9 @@ func (wm *IAMLoginPolicyWriteModel) NewChangedEvent(
if passwordlessType.Valid() && wm.PasswordlessType != passwordlessType {
changes = append(changes, policy.ChangePasswordlessType(passwordlessType))
}
+ if wm.HidePasswordReset != hidePasswordReset {
+ changes = append(changes, policy.ChangeHidePasswordReset(hidePasswordReset))
+ }
if len(changes) == 0 {
return nil, false
}
diff --git a/internal/command/iam_policy_login_test.go b/internal/command/iam_policy_login_test.go
index 6bc560c764..77119d7b67 100644
--- a/internal/command/iam_policy_login_test.go
+++ b/internal/command/iam_policy_login_test.go
@@ -2,6 +2,8 @@ package command
import (
"context"
+ "testing"
+
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
@@ -12,7 +14,6 @@ import (
"github.com/caos/zitadel/internal/repository/user"
"github.com/stretchr/testify/assert"
- "testing"
)
func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
@@ -46,6 +47,7 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
true,
false,
false,
+ false,
domain.PasswordlessTypeAllowed,
),
),
@@ -79,6 +81,7 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -93,6 +96,7 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
+ HidePasswordReset: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
},
},
@@ -106,6 +110,7 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
+ HidePasswordReset: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
},
},
@@ -180,6 +185,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -193,6 +199,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
+ HidePasswordReset: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
},
},
@@ -213,6 +220,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -220,7 +228,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
- newDefaultLoginPolicyChangedEvent(context.Background(), false, false, false, false, domain.PasswordlessTypeNotAllowed),
+ newDefaultLoginPolicyChangedEvent(context.Background(), false, false, false, false, false, domain.PasswordlessTypeNotAllowed),
),
},
),
@@ -233,6 +241,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
AllowUsernamePassword: false,
AllowExternalIDP: false,
ForceMFA: false,
+ HidePasswordReset: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
},
},
@@ -246,6 +255,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
AllowUsernamePassword: false,
AllowExternalIDP: false,
ForceMFA: false,
+ HidePasswordReset: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
},
},
@@ -345,6 +355,7 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -496,6 +507,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -537,6 +549,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -583,6 +596,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -637,6 +651,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -1181,7 +1196,7 @@ func TestCommandSide_RemoveMultiFactorDefaultLoginPolicy(t *testing.T) {
}
}
-func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA bool, passwordlessType domain.PasswordlessType) *iam.LoginPolicyChangedEvent {
+func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA, hidePasswordReset bool, passwordlessType domain.PasswordlessType) *iam.LoginPolicyChangedEvent {
event, _ := iam.NewLoginPolicyChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
[]policy.LoginPolicyChanges{
@@ -1189,6 +1204,7 @@ func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allow
policy.ChangeAllowExternalIDP(allowExternalIDP),
policy.ChangeForceMFA(forceMFA),
policy.ChangeAllowUserNamePassword(allowUsernamePassword),
+ policy.ChangeHidePasswordReset(hidePasswordReset),
policy.ChangePasswordlessType(passwordlessType),
},
)
diff --git a/internal/command/org_features.go b/internal/command/org_features.go
index b1c6cdb8af..918ddf13e7 100644
--- a/internal/command/org_features.go
+++ b/internal/command/org_features.go
@@ -31,6 +31,7 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea
features.LoginPolicyPasswordless,
features.LoginPolicyRegistration,
features.LoginPolicyUsernameLogin,
+ features.LoginPolicyPasswordReset,
features.PasswordComplexityPolicy,
features.LabelPolicy,
features.CustomDomain,
@@ -165,7 +166,10 @@ func (c *Commands) setAllowedLoginPolicy(ctx context.Context, orgID string, feat
if !features.LoginPolicyUsernameLogin && defaultPolicy.AllowUsernamePassword != existingPolicy.AllowUserNamePassword {
policy.AllowUserNamePassword = defaultPolicy.AllowUsernamePassword
}
- changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, OrgAggregateFromWriteModel(&existingPolicy.WriteModel), policy.AllowUserNamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.PasswordlessType)
+ if !features.LoginPolicyPasswordReset && defaultPolicy.HidePasswordReset != existingPolicy.HidePasswordReset {
+ policy.HidePasswordReset = defaultPolicy.HidePasswordReset
+ }
+ changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, OrgAggregateFromWriteModel(&existingPolicy.WriteModel), policy.AllowUserNamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.HidePasswordReset, policy.PasswordlessType)
if hasChanged {
events = append(events, changedEvent)
}
diff --git a/internal/command/org_features_model.go b/internal/command/org_features_model.go
index 014d36234f..ed9dbb57ca 100644
--- a/internal/command/org_features_model.go
+++ b/internal/command/org_features_model.go
@@ -67,6 +67,7 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
loginPolicyPasswordless,
loginPolicyRegistration,
loginPolicyUsernameLogin,
+ loginPolicyPasswordReset,
passwordComplexityPolicy,
labelPolicy,
customDomain bool,
@@ -104,6 +105,9 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
if wm.LoginPolicyUsernameLogin != loginPolicyUsernameLogin {
changes = append(changes, features.ChangeLoginPolicyUsernameLogin(loginPolicyUsernameLogin))
}
+ if wm.LoginPolicyPasswordReset != loginPolicyPasswordReset {
+ changes = append(changes, features.ChangeLoginPolicyPasswordReset(loginPolicyPasswordReset))
+ }
if wm.PasswordComplexityPolicy != passwordComplexityPolicy {
changes = append(changes, features.ChangePasswordComplexityPolicy(passwordComplexityPolicy))
}
diff --git a/internal/command/org_features_test.go b/internal/command/org_features_test.go
index eab0cb72a8..028642448e 100644
--- a/internal/command/org_features_test.go
+++ b/internal/command/org_features_test.go
@@ -54,6 +54,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyPasswordless: false,
LoginPolicyRegistration: false,
LoginPolicyUsernameLogin: false,
+ LoginPolicyPasswordReset: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
CustomDomain: false,
@@ -87,6 +88,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyPasswordless: false,
LoginPolicyRegistration: false,
LoginPolicyUsernameLogin: false,
+ LoginPolicyPasswordReset: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
CustomDomain: false,
@@ -111,6 +113,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
false,
false,
false,
+ false,
domain.PasswordlessTypeAllowed,
),
),
@@ -191,6 +194,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyPasswordless: false,
LoginPolicyRegistration: false,
LoginPolicyUsernameLogin: false,
+ LoginPolicyPasswordReset: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
CustomDomain: false,
@@ -217,6 +221,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
false,
false,
false,
+ false,
domain.PasswordlessTypeAllowed,
),
),
@@ -325,6 +330,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyPasswordless: false,
LoginPolicyRegistration: false,
LoginPolicyUsernameLogin: false,
+ LoginPolicyPasswordReset: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
CustomDomain: false,
@@ -351,6 +357,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
false,
false,
false,
+ false,
domain.PasswordlessTypeAllowed,
),
),
@@ -469,6 +476,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyPasswordless: false,
LoginPolicyRegistration: false,
LoginPolicyUsernameLogin: false,
+ LoginPolicyPasswordReset: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
CustomDomain: false,
@@ -495,6 +503,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
false,
false,
false,
+ false,
domain.PasswordlessTypeAllowed,
),
),
@@ -623,6 +632,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyPasswordless: false,
LoginPolicyRegistration: false,
LoginPolicyUsernameLogin: false,
+ LoginPolicyPasswordReset: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
CustomDomain: false,
@@ -653,6 +663,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -664,6 +675,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
false,
false,
false,
+ false,
domain.PasswordlessTypeNotAllowed,
),
),
@@ -678,6 +690,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -790,7 +803,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
org.NewLoginPolicyMultiFactorAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, domain.MultiFactorTypeU2FWithPIN),
),
eventFromEventPusher(
- newLoginPolicyChangedEvent(context.Background(), "org1", true, true, true, true, domain.PasswordlessTypeAllowed),
+ newLoginPolicyChangedEvent(context.Background(), "org1", true, true, true, true, true, domain.PasswordlessTypeAllowed),
),
eventFromEventPusher(
org.NewPasswordComplexityPolicyRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate),
@@ -920,6 +933,7 @@ func TestCommandSide_RemoveOrgFeatures(t *testing.T) {
false,
false,
false,
+ false,
domain.PasswordlessTypeAllowed,
),
),
diff --git a/internal/command/org_policy_login.go b/internal/command/org_policy_login.go
index d65501b5a3..9460898592 100644
--- a/internal/command/org_policy_login.go
+++ b/internal/command/org_policy_login.go
@@ -42,6 +42,7 @@ func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, pol
policy.AllowRegister,
policy.AllowExternalIDP,
policy.ForceMFA,
+ policy.HidePasswordReset,
policy.PasswordlessType))
if err != nil {
return nil, err
@@ -81,7 +82,16 @@ func (c *Commands) ChangeLoginPolicy(ctx context.Context, resourceOwner string,
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LoginPolicyWriteModel.WriteModel)
- changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.PasswordlessType)
+ changedEvent, hasChanged := existingPolicy.NewChangedEvent(
+ ctx,
+ orgAgg,
+ policy.AllowUsernamePassword,
+ policy.AllowRegister,
+ policy.AllowExternalIDP,
+ policy.ForceMFA,
+ policy.HidePasswordReset,
+ policy.PasswordlessType)
+
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5M9vdd", "Errors.Org.LoginPolicy.NotChanged")
}
@@ -118,6 +128,9 @@ func (c *Commands) checkLoginPolicyAllowed(ctx context.Context, resourceOwner st
if defaultPolicy.AllowUsernamePassword != policy.AllowUsernamePassword {
requiredFeatures = append(requiredFeatures, domain.FeatureLoginPolicyUsernameLogin)
}
+ if defaultPolicy.HidePasswordReset != policy.HidePasswordReset {
+ requiredFeatures = append(requiredFeatures, domain.FeatureLoginPolicyPasswordReset)
+ }
return authz.CheckOrgFeatures(ctx, c.tokenVerifier, resourceOwner, requiredFeatures...)
}
diff --git a/internal/command/org_policy_login_model.go b/internal/command/org_policy_login_model.go
index e23c1e478b..cc87572004 100644
--- a/internal/command/org_policy_login_model.go
+++ b/internal/command/org_policy_login_model.go
@@ -61,7 +61,8 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
allowUsernamePassword,
allowRegister,
allowExternalIDP,
- forceMFA bool,
+ forceMFA,
+ hidePasswordReset bool,
passwordlessType domain.PasswordlessType,
) (*org.LoginPolicyChangedEvent, bool) {
@@ -78,6 +79,9 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
if wm.ForceMFA != forceMFA {
changes = append(changes, policy.ChangeForceMFA(forceMFA))
}
+ if wm.HidePasswordReset != hidePasswordReset {
+ changes = append(changes, policy.ChangeHidePasswordReset(hidePasswordReset))
+ }
if passwordlessType.Valid() && wm.PasswordlessType != passwordlessType {
changes = append(changes, policy.ChangePasswordlessType(passwordlessType))
}
diff --git a/internal/command/org_policy_login_test.go b/internal/command/org_policy_login_test.go
index e78501ee9a..ddd73b38af 100644
--- a/internal/command/org_policy_login_test.go
+++ b/internal/command/org_policy_login_test.go
@@ -70,6 +70,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -105,6 +106,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -141,6 +143,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -154,6 +157,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -170,6 +174,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
+ HidePasswordReset: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
},
},
@@ -183,6 +188,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
+ HidePasswordReset: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
},
},
@@ -285,6 +291,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -297,6 +304,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -332,6 +340,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -344,6 +353,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -359,6 +369,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
+ HidePasswordReset: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
},
},
@@ -379,6 +390,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -391,6 +403,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
false,
false,
false,
+ false,
domain.PasswordlessTypeNotAllowed,
),
),
@@ -398,7 +411,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
- newLoginPolicyChangedEvent(context.Background(), "org1", false, false, false, false, domain.PasswordlessTypeNotAllowed),
+ newLoginPolicyChangedEvent(context.Background(), "org1", false, false, false, false, false, domain.PasswordlessTypeNotAllowed),
),
},
),
@@ -426,6 +439,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
AllowUsernamePassword: false,
AllowExternalIDP: false,
ForceMFA: false,
+ HidePasswordReset: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
},
},
@@ -512,6 +526,7 @@ func TestCommandSide_RemoveLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -655,6 +670,7 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -839,6 +855,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -882,6 +899,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -932,6 +950,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -990,6 +1009,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true,
true,
true,
+ true,
domain.PasswordlessTypeAllowed,
),
),
@@ -1600,7 +1620,7 @@ func TestCommandSide_RemoveMultiFactorLoginPolicy(t *testing.T) {
}
}
-func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassword, register, externalIDP, mfa bool, passwordlessType domain.PasswordlessType) *org.LoginPolicyChangedEvent {
+func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassword, register, externalIDP, mfa, passwordReset bool, passwordlessType domain.PasswordlessType) *org.LoginPolicyChangedEvent {
event, _ := org.NewLoginPolicyChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
[]policy.LoginPolicyChanges{
@@ -1608,6 +1628,7 @@ func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassw
policy.ChangeAllowRegister(register),
policy.ChangeAllowExternalIDP(externalIDP),
policy.ChangeForceMFA(mfa),
+ policy.ChangeHidePasswordReset(passwordReset),
policy.ChangePasswordlessType(passwordlessType),
},
)
diff --git a/internal/command/policy_login_model.go b/internal/command/policy_login_model.go
index affb9fac6f..3b3a8bfcfe 100644
--- a/internal/command/policy_login_model.go
+++ b/internal/command/policy_login_model.go
@@ -13,6 +13,7 @@ type LoginPolicyWriteModel struct {
AllowRegister bool
AllowExternalIDP bool
ForceMFA bool
+ HidePasswordReset bool
PasswordlessType domain.PasswordlessType
State domain.PolicyState
}
@@ -26,6 +27,7 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
wm.AllowExternalIDP = e.AllowExternalIDP
wm.ForceMFA = e.ForceMFA
wm.PasswordlessType = e.PasswordlessType
+ wm.HidePasswordReset = e.HidePasswordReset
wm.State = domain.PolicyStateActive
case *policy.LoginPolicyChangedEvent:
if e.AllowRegister != nil {
@@ -40,6 +42,9 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
if e.ForceMFA != nil {
wm.ForceMFA = *e.ForceMFA
}
+ if e.HidePasswordReset != nil {
+ wm.HidePasswordReset = *e.HidePasswordReset
+ }
if e.PasswordlessType != nil {
wm.PasswordlessType = *e.PasswordlessType
}
diff --git a/internal/domain/features.go b/internal/domain/features.go
index 5357b12172..17a29369ef 100644
--- a/internal/domain/features.go
+++ b/internal/domain/features.go
@@ -13,6 +13,7 @@ const (
FeatureLoginPolicyPasswordless = FeatureLoginPolicy + ".passwordless"
FeatureLoginPolicyRegistration = FeatureLoginPolicy + ".registration"
FeatureLoginPolicyUsernameLogin = FeatureLoginPolicy + ".username_login"
+ FeatureLoginPolicyPasswordReset = FeatureLoginPolicy + ".password_reset"
FeaturePasswordComplexityPolicy = "password_complexity_policy"
FeatureLabelPolicy = "label_policy"
FeatureCustomDomain = "custom_domain"
@@ -33,6 +34,7 @@ type Features struct {
LoginPolicyPasswordless bool
LoginPolicyRegistration bool
LoginPolicyUsernameLogin bool
+ LoginPolicyPasswordReset bool
PasswordComplexityPolicy bool
LabelPolicy bool
CustomDomain bool
diff --git a/internal/domain/policy_login.go b/internal/domain/policy_login.go
index dd6c57a0e5..0ad43afb0d 100644
--- a/internal/domain/policy_login.go
+++ b/internal/domain/policy_login.go
@@ -14,6 +14,7 @@ type LoginPolicy struct {
SecondFactors []SecondFactorType
MultiFactors []MultiFactorType
PasswordlessType PasswordlessType
+ HidePasswordReset bool
}
type IDPProvider struct {
diff --git a/internal/features/model/features_view.go b/internal/features/model/features_view.go
index 704eb8f405..0fb0473dc6 100644
--- a/internal/features/model/features_view.go
+++ b/internal/features/model/features_view.go
@@ -23,6 +23,7 @@ type FeaturesView struct {
LoginPolicyPasswordless bool
LoginPolicyRegistration bool
LoginPolicyUsernameLogin bool
+ LoginPolicyPasswordReset bool
PasswordComplexityPolicy bool
LabelPolicy bool
CustomDomain bool
@@ -45,6 +46,9 @@ func (f *FeaturesView) FeatureList() []string {
if f.LoginPolicyUsernameLogin {
list = append(list, domain.FeatureLoginPolicyUsernameLogin)
}
+ if f.LoginPolicyPasswordReset {
+ list = append(list, domain.FeatureLoginPolicyPasswordReset)
+ }
if f.PasswordComplexityPolicy {
list = append(list, domain.FeaturePasswordComplexityPolicy)
}
diff --git a/internal/features/repository/view/model/features.go b/internal/features/repository/view/model/features.go
index 44e67ec329..bc71f7034f 100644
--- a/internal/features/repository/view/model/features.go
+++ b/internal/features/repository/view/model/features.go
@@ -36,6 +36,7 @@ type FeaturesView struct {
LoginPolicyPasswordless bool `json:"loginPolicyPasswordless" gorm:"column:login_policy_passwordless"`
LoginPolicyRegistration bool `json:"loginPolicyRegistration" gorm:"column:login_policy_registration"`
LoginPolicyUsernameLogin bool `json:"loginPolicyUsernameLogin" gorm:"column:login_policy_username_login"`
+ LoginPolicyPasswordReset bool `json:"loginPolicyPasswordReset" gorm:"column:login_policy_password_reset"`
PasswordComplexityPolicy bool `json:"passwordComplexityPolicy" gorm:"column:password_complexity_policy"`
LabelPolicy bool `json:"labelPolicy" gorm:"column:label_policy"`
CustomDomain bool `json:"customDomain" gorm:"column:custom_domain"`
@@ -58,6 +59,7 @@ func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
LoginPolicyPasswordless: features.LoginPolicyPasswordless,
LoginPolicyRegistration: features.LoginPolicyRegistration,
LoginPolicyUsernameLogin: features.LoginPolicyUsernameLogin,
+ LoginPolicyPasswordReset: features.LoginPolicyPasswordReset,
PasswordComplexityPolicy: features.PasswordComplexityPolicy,
LabelPolicy: features.LabelPolicy,
CustomDomain: features.CustomDomain,
diff --git a/internal/iam/model/login_policy_view.go b/internal/iam/model/login_policy_view.go
index b9e8f745e6..2c1a998ea0 100644
--- a/internal/iam/model/login_policy_view.go
+++ b/internal/iam/model/login_policy_view.go
@@ -1,9 +1,10 @@
package model
import (
+ "time"
+
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore/v1/models"
- "time"
)
type LoginPolicyView struct {
@@ -12,6 +13,7 @@ type LoginPolicyView struct {
AllowRegister bool
AllowExternalIDP bool
ForceMFA bool
+ HidePasswordReset bool
PasswordlessType PasswordlessType
SecondFactors []SecondFactorType
MultiFactors []MultiFactorType
@@ -80,6 +82,7 @@ func (p *LoginPolicyView) ToLoginPolicyDomain() *domain.LoginPolicy {
AllowRegister: p.AllowRegister,
AllowExternalIDP: p.AllowExternalIDP,
ForceMFA: p.ForceMFA,
+ HidePasswordReset: p.HidePasswordReset,
PasswordlessType: passwordLessTypeToDomain(p.PasswordlessType),
SecondFactors: secondFactorsToDomain(p.SecondFactors),
MultiFactors: multiFactorsToDomain(p.MultiFactors),
diff --git a/internal/iam/repository/view/model/login_policy.go b/internal/iam/repository/view/model/login_policy.go
index f180d14b11..3fdff7904d 100644
--- a/internal/iam/repository/view/model/login_policy.go
+++ b/internal/iam/repository/view/model/login_policy.go
@@ -2,13 +2,16 @@ package model
import (
"encoding/json"
- org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
- "github.com/lib/pq"
"time"
+ "github.com/lib/pq"
+
+ org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
+
es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/logging"
+
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/model"
@@ -29,6 +32,7 @@ type LoginPolicyView struct {
AllowUsernamePassword bool `json:"allowUsernamePassword" gorm:"column:allow_username_password"`
AllowExternalIDP bool `json:"allowExternalIdp" gorm:"column:allow_external_idp"`
ForceMFA bool `json:"forceMFA" gorm:"column:force_mfa"`
+ HidePasswordReset bool `json:"hidePasswordReset" gorm:"column:hide_password_reset"`
PasswordlessType int32 `json:"passwordlessType" gorm:"column:passwordless_type"`
SecondFactors pq.Int64Array `json:"-" gorm:"column:second_factors"`
MultiFactors pq.Int64Array `json:"-" gorm:"column:multi_factors"`
@@ -47,6 +51,7 @@ func LoginPolicyViewFromModel(policy *model.LoginPolicyView) *LoginPolicyView {
AllowExternalIDP: policy.AllowExternalIDP,
AllowUsernamePassword: policy.AllowUsernamePassword,
ForceMFA: policy.ForceMFA,
+ HidePasswordReset: policy.HidePasswordReset,
PasswordlessType: int32(policy.PasswordlessType),
SecondFactors: secondFactorsFromModel(policy.SecondFactors),
MultiFactors: multiFactorsFromModel(policy.MultiFactors),
@@ -80,6 +85,7 @@ func LoginPolicyViewToModel(policy *LoginPolicyView) *model.LoginPolicyView {
AllowExternalIDP: policy.AllowExternalIDP,
AllowUsernamePassword: policy.AllowUsernamePassword,
ForceMFA: policy.ForceMFA,
+ HidePasswordReset: policy.HidePasswordReset,
PasswordlessType: model.PasswordlessType(policy.PasswordlessType),
SecondFactors: secondFactorsToModel(policy.SecondFactors),
MultiFactors: multiFactorsToToModel(policy.MultiFactors),
diff --git a/internal/repository/features/features.go b/internal/repository/features/features.go
index d9d935b041..65a3ddfaa6 100644
--- a/internal/repository/features/features.go
+++ b/internal/repository/features/features.go
@@ -29,6 +29,7 @@ type FeaturesSetEvent struct {
LoginPolicyPasswordless *bool `json:"loginPolicyPasswordless,omitempty"`
LoginPolicyRegistration *bool `json:"loginPolicyRegistration,omitempty"`
LoginPolicyUsernameLogin *bool `json:"loginPolicyUsernameLogin,omitempty"`
+ LoginPolicyPasswordReset *bool `json:"loginPolicyPasswordReset,omitempty"`
PasswordComplexityPolicy *bool `json:"passwordComplexityPolicy,omitempty"`
LabelPolicy *bool `json:"labelPolicy,omitempty"`
CustomDomain *bool `json:"customDomain,omitempty"`
@@ -120,6 +121,12 @@ func ChangeLoginPolicyUsernameLogin(loginPolicyUsernameLogin bool) func(event *F
}
}
+func ChangeLoginPolicyPasswordReset(loginPolicyPasswordReset bool) func(event *FeaturesSetEvent) {
+ return func(e *FeaturesSetEvent) {
+ e.LoginPolicyPasswordReset = &loginPolicyPasswordReset
+ }
+}
+
func ChangePasswordComplexityPolicy(passwordComplexityPolicy bool) func(event *FeaturesSetEvent) {
return func(e *FeaturesSetEvent) {
e.PasswordComplexityPolicy = &passwordComplexityPolicy
diff --git a/internal/repository/iam/policy_login.go b/internal/repository/iam/policy_login.go
index 163098c49e..1662468b8a 100644
--- a/internal/repository/iam/policy_login.go
+++ b/internal/repository/iam/policy_login.go
@@ -24,7 +24,8 @@ func NewLoginPolicyAddedEvent(
allowUsernamePassword,
allowRegister,
allowExternalIDP,
- forceMFA bool,
+ forceMFA,
+ hidePasswordReset bool,
passwordlessType domain.PasswordlessType,
) *LoginPolicyAddedEvent {
return &LoginPolicyAddedEvent{
@@ -37,6 +38,7 @@ func NewLoginPolicyAddedEvent(
allowRegister,
allowExternalIDP,
forceMFA,
+ hidePasswordReset,
passwordlessType),
}
}
diff --git a/internal/repository/org/policy_login.go b/internal/repository/org/policy_login.go
index d2d64feaef..f278be6a1c 100644
--- a/internal/repository/org/policy_login.go
+++ b/internal/repository/org/policy_login.go
@@ -25,7 +25,8 @@ func NewLoginPolicyAddedEvent(
allowUsernamePassword,
allowRegister,
allowExternalIDP,
- forceMFA bool,
+ forceMFA,
+ hidePasswordReset bool,
passwordlessType domain.PasswordlessType,
) *LoginPolicyAddedEvent {
return &LoginPolicyAddedEvent{
@@ -38,6 +39,7 @@ func NewLoginPolicyAddedEvent(
allowRegister,
allowExternalIDP,
forceMFA,
+ hidePasswordReset,
passwordlessType),
}
}
diff --git a/internal/repository/policy/login.go b/internal/repository/policy/login.go
index dbcfbfe9a7..8c7037bb2c 100644
--- a/internal/repository/policy/login.go
+++ b/internal/repository/policy/login.go
@@ -2,6 +2,7 @@ package policy
import (
"encoding/json"
+
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
@@ -22,6 +23,7 @@ type LoginPolicyAddedEvent struct {
AllowRegister bool `json:"allowRegister,omitempty"`
AllowExternalIDP bool `json:"allowExternalIdp,omitempty"`
ForceMFA bool `json:"forceMFA,omitempty"`
+ HidePasswordReset bool `json:"hidePasswordReset,omitempty"`
PasswordlessType domain.PasswordlessType `json:"passwordlessType,omitempty"`
}
@@ -38,7 +40,8 @@ func NewLoginPolicyAddedEvent(
allowUserNamePassword,
allowRegister,
allowExternalIDP,
- forceMFA bool,
+ forceMFA,
+ hidePasswordReset bool,
passwordlessType domain.PasswordlessType,
) *LoginPolicyAddedEvent {
return &LoginPolicyAddedEvent{
@@ -48,6 +51,7 @@ func NewLoginPolicyAddedEvent(
AllowUserNamePassword: allowUserNamePassword,
ForceMFA: forceMFA,
PasswordlessType: passwordlessType,
+ HidePasswordReset: hidePasswordReset,
}
}
@@ -71,6 +75,7 @@ type LoginPolicyChangedEvent struct {
AllowRegister *bool `json:"allowRegister,omitempty"`
AllowExternalIDP *bool `json:"allowExternalIdp,omitempty"`
ForceMFA *bool `json:"forceMFA,omitempty"`
+ HidePasswordReset *bool `json:"hidePasswordReset,omitempty"`
PasswordlessType *domain.PasswordlessType `json:"passwordlessType,omitempty"`
}
@@ -133,6 +138,12 @@ func ChangePasswordlessType(passwordlessType domain.PasswordlessType) func(*Logi
}
}
+func ChangeHidePasswordReset(hidePasswordReset bool) func(*LoginPolicyChangedEvent) {
+ return func(e *LoginPolicyChangedEvent) {
+ e.HidePasswordReset = &hidePasswordReset
+ }
+}
+
func LoginPolicyChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &LoginPolicyChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
diff --git a/internal/ui/login/handler/password_handler.go b/internal/ui/login/handler/password_handler.go
index 91a07ffd52..af84565116 100644
--- a/internal/ui/login/handler/password_handler.go
+++ b/internal/ui/login/handler/password_handler.go
@@ -21,7 +21,15 @@ func (l *Login) renderPassword(w http.ResponseWriter, r *http.Request, authReq *
errMessage = l.getErrorMessage(r, err)
}
data := l.getUserData(r, authReq, "Password", errType, errMessage)
- l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplPassword], data, nil)
+ funcs := map[string]interface{}{
+ "showPasswordReset": func() bool {
+ if authReq.LoginPolicy != nil {
+ return !authReq.LoginPolicy.HidePasswordReset
+ }
+ return true
+ },
+ }
+ l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplPassword], data, funcs)
}
func (l *Login) handlePasswordCheck(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/ui/login/handler/renderer.go b/internal/ui/login/handler/renderer.go
index 7e6f2b6f61..322587d5d2 100644
--- a/internal/ui/login/handler/renderer.go
+++ b/internal/ui/login/handler/renderer.go
@@ -153,6 +153,9 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str
"hasUsernamePasswordLogin": func() bool {
return false
},
+ "showPasswordReset": func() bool {
+ return true
+ },
"hasExternalLogin": func() bool {
return false
},
diff --git a/internal/ui/login/static/templates/password.html b/internal/ui/login/static/templates/password.html
index 17b4159b4c..c2b815f613 100644
--- a/internal/ui/login/static/templates/password.html
+++ b/internal/ui/login/static/templates/password.html
@@ -21,9 +21,11 @@
{{template "error-message" .}}
+ {{ if showPasswordReset }}
{{t "Actions.ForgotPassword"}}
+ {{ end }}
diff --git a/migrations/cockroach/V1.46__password_reset.sql b/migrations/cockroach/V1.46__password_reset.sql
new file mode 100644
index 0000000000..48c9746466
--- /dev/null
+++ b/migrations/cockroach/V1.46__password_reset.sql
@@ -0,0 +1,9 @@
+ALTER TABLE adminapi.features ADD COLUMN login_policy_password_reset BOOLEAN;
+ALTER TABLE auth.features ADD COLUMN login_policy_password_reset BOOLEAN;
+ALTER TABLE authz.features ADD COLUMN login_policy_password_reset BOOLEAN;
+ALTER TABLE management.features ADD COLUMN login_policy_password_reset BOOLEAN;
+
+
+ALTER TABLE auth.login_policies ADD COLUMN hide_password_reset BOOLEAN;
+ALTER TABLE adminapi.login_policies ADD COLUMN hide_password_reset BOOLEAN;
+ALTER TABLE management.login_policies ADD COLUMN hide_password_reset BOOLEAN;
diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto
index b942d3af5c..cfc2801802 100644
--- a/proto/zitadel/admin.proto
+++ b/proto/zitadel/admin.proto
@@ -2194,6 +2194,7 @@ message SetDefaultFeaturesRequest {
bool password_complexity_policy = 11;
bool label_policy = 12;
bool custom_domain = 13;
+ bool login_policy_password_reset = 14;
}
message SetDefaultFeaturesResponse {
@@ -2224,6 +2225,7 @@ message SetOrgFeaturesRequest {
bool password_complexity_policy = 12;
bool label_policy = 13;
bool custom_domain = 14;
+ bool login_policy_password_reset = 15;
}
message SetOrgFeaturesResponse {
@@ -2421,6 +2423,11 @@ message UpdateLoginPolicyRequest {
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines if passwordless is allowed for users"
}];
+ bool hide_password_reset = 6 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "defines if password reset link should be shown in the login screen"
+ }
+ ];
}
message UpdateLoginPolicyResponse {
diff --git a/proto/zitadel/features.proto b/proto/zitadel/features.proto
index fa23d94f17..6ed939cc39 100644
--- a/proto/zitadel/features.proto
+++ b/proto/zitadel/features.proto
@@ -21,6 +21,7 @@ message Features {
bool password_complexity_policy = 10;
bool label_policy = 11;
bool custom_domain = 12;
+ bool login_policy_password_reset = 13;
}
message FeatureTier {
diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto
index f8d241298a..906bdeb292 100644
--- a/proto/zitadel/management.proto
+++ b/proto/zitadel/management.proto
@@ -3373,6 +3373,7 @@ message AddCustomLoginPolicyRequest {
bool allow_external_idp = 3;
bool force_mfa = 4;
zitadel.policy.v1.PasswordlessType passwordless_type = 5 [(validate.rules).enum = {defined_only: true}];
+ bool hide_password_reset = 6;
}
message AddCustomLoginPolicyResponse {
@@ -3385,6 +3386,7 @@ message UpdateCustomLoginPolicyRequest {
bool allow_external_idp = 3;
bool force_mfa = 4;
zitadel.policy.v1.PasswordlessType passwordless_type = 5 [(validate.rules).enum = {defined_only: true}];
+ bool hide_password_reset = 6;
}
message UpdateCustomLoginPolicyResponse {
diff --git a/proto/zitadel/policy.proto b/proto/zitadel/policy.proto
index ae98bb1b14..611f3acf86 100644
--- a/proto/zitadel/policy.proto
+++ b/proto/zitadel/policy.proto
@@ -77,6 +77,11 @@ message LoginPolicy {
description: "defines if the organisation's admin changed the policy"
}
];
+ bool hide_password_reset = 8 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "defines if password reset link should be shown in the login screen"
+ }
+ ];
}
enum SecondFactorType {