diff --git a/docs/docs/apis/proto/management.md b/docs/docs/apis/proto/management.md
index a8a0a374cc..545d4ff02d 100644
--- a/docs/docs/apis/proto/management.md
+++ b/docs/docs/apis/proto/management.md
@@ -298,7 +298,18 @@ An sms will be sent to the given phone number to finish the phone verification p
> **rpc** SetHumanInitialPassword([SetHumanInitialPasswordRequest](#sethumaninitialpasswordrequest))
[SetHumanInitialPasswordResponse](#sethumaninitialpasswordresponse)
-A Manager is only allowed to set an initial password, on the next login the user has to change his password
+deprecated: use SetHumanPassword
+
+
+
+
+### SetHumanPassword
+
+> **rpc** SetHumanPassword([SetHumanPasswordRequest](#sethumanpasswordrequest))
+[SetHumanPasswordResponse](#sethumanpasswordresponse)
+
+Set a new password for a user, on default the user has to change the password on the next login
+Set no_change_required to true if the user does not have to change the password on the next login
@@ -4862,6 +4873,30 @@ This is an empty request
+### SetHumanPasswordRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| user_id | string | - | string.min_len: 1
|
+| password | string | - | string.min_len: 1
string.max_len: 72
|
+| no_change_required | bool | - | |
+
+
+
+
+### SetHumanPasswordResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
### SetPrimaryOrgDomainRequest
diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go
index 6db886f4e9..7189e32912 100644
--- a/internal/api/grpc/management/user.go
+++ b/internal/api/grpc/management/user.go
@@ -324,7 +324,7 @@ func (s *Server) ResendHumanPhoneVerification(ctx context.Context, req *mgmt_pb.
}
func (s *Server) SetHumanInitialPassword(ctx context.Context, req *mgmt_pb.SetHumanInitialPasswordRequest) (*mgmt_pb.SetHumanInitialPasswordResponse, error) {
- objectDetails, err := s.command.SetOneTimePassword(ctx, authz.GetCtxData(ctx).OrgID, req.UserId, req.Password)
+ objectDetails, err := s.command.SetPassword(ctx, authz.GetCtxData(ctx).OrgID, req.UserId, req.Password, true)
if err != nil {
return nil, err
}
@@ -333,6 +333,16 @@ func (s *Server) SetHumanInitialPassword(ctx context.Context, req *mgmt_pb.SetHu
}, nil
}
+func (s *Server) SetHumanPassword(ctx context.Context, req *mgmt_pb.SetHumanPasswordRequest) (*mgmt_pb.SetHumanPasswordResponse, error) {
+ objectDetails, err := s.command.SetPassword(ctx, authz.GetCtxData(ctx).OrgID, req.UserId, req.Password, !req.NoChangeRequired)
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.SetHumanPasswordResponse{
+ Details: obj_grpc.DomainToChangeDetailsPb(objectDetails),
+ }, nil
+}
+
func (s *Server) SendHumanResetPasswordNotification(ctx context.Context, req *mgmt_pb.SendHumanResetPasswordNotificationRequest) (*mgmt_pb.SendHumanResetPasswordNotificationResponse, error) {
objectDetails, err := s.command.RequestSetPassword(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, notifyTypeToDomain(req.Type))
if err != nil {
diff --git a/internal/command/user_human_password.go b/internal/command/user_human_password.go
index 3572650c35..98ee4fa6c3 100644
--- a/internal/command/user_human_password.go
+++ b/internal/command/user_human_password.go
@@ -11,7 +11,7 @@ import (
"github.com/caos/zitadel/internal/telemetry/tracing"
)
-func (c *Commands) SetOneTimePassword(ctx context.Context, orgID, userID, passwordString string) (objectDetails *domain.ObjectDetails, err error) {
+func (c *Commands) SetPassword(ctx context.Context, orgID, userID, passwordString string, oneTime bool) (objectDetails *domain.ObjectDetails, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if userID == "" {
@@ -26,7 +26,7 @@ func (c *Commands) SetOneTimePassword(ctx context.Context, orgID, userID, passwo
}
password := &domain.Password{
SecretString: passwordString,
- ChangeRequired: true,
+ ChangeRequired: oneTime,
}
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)
passwordEvent, err := c.changePassword(ctx, "", password, userAgg, existingPassword)
@@ -44,7 +44,7 @@ func (c *Commands) SetOneTimePassword(ctx context.Context, orgID, userID, passwo
return writeModelToObjectDetails(&existingPassword.WriteModel), nil
}
-func (c *Commands) SetPassword(ctx context.Context, orgID, userID, code, passwordString, userAgentID string) (err error) {
+func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, code, passwordString, userAgentID string) (err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
diff --git a/internal/command/user_human_password_test.go b/internal/command/user_human_password_test.go
index 1526b626bb..c7ee208f0c 100644
--- a/internal/command/user_human_password_test.go
+++ b/internal/command/user_human_password_test.go
@@ -28,6 +28,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
userID string
resourceOwner string
password string
+ oneTime bool
}
type res struct {
want *domain.ObjectDetails
@@ -72,7 +73,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
},
},
{
- name: "change password, ok",
+ name: "change password onetime, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
@@ -134,6 +135,78 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
userID: "user1",
resourceOwner: "org1",
password: "password",
+ oneTime: true,
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ {
+ name: "change password no one time, ok",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ expectFilter(
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "username",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.German,
+ domain.GenderUnspecified,
+ "email@test.ch",
+ true,
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanEmailVerifiedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ ),
+ ),
+ ),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ 1,
+ false,
+ false,
+ false,
+ false,
+ ),
+ ),
+ ),
+ expectPush(
+ []*repository.Event{
+ eventFromEventPusher(
+ user.NewHumanPasswordChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeHash,
+ Algorithm: "hash",
+ KeyID: "",
+ Crypted: []byte("password"),
+ },
+ false,
+ "",
+ ),
+ ),
+ },
+ ),
+ ),
+ userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
+ },
+ args: args{
+ ctx: context.Background(),
+ userID: "user1",
+ resourceOwner: "org1",
+ password: "password",
+ oneTime: false,
},
res: res{
want: &domain.ObjectDetails{
@@ -148,7 +221,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
eventstore: tt.fields.eventstore,
userPasswordAlg: tt.fields.userPasswordAlg,
}
- got, err := r.SetOneTimePassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.password)
+ got, err := r.SetPassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.password, tt.args.oneTime)
if tt.res.err == nil {
assert.NoError(t, err)
}
@@ -410,7 +483,7 @@ func TestCommandSide_SetPassword(t *testing.T) {
userPasswordAlg: tt.fields.userPasswordAlg,
passwordVerificationCode: tt.fields.secretGenerator,
}
- err := r.SetPassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.code, tt.args.password, tt.args.agentID)
+ err := r.SetPasswordWithVerifyCode(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.code, tt.args.password, tt.args.agentID)
if tt.res.err == nil {
assert.NoError(t, err)
}
diff --git a/internal/ui/login/handler/init_password_handler.go b/internal/ui/login/handler/init_password_handler.go
index ef57df9423..882f145334 100644
--- a/internal/ui/login/handler/init_password_handler.go
+++ b/internal/ui/login/handler/init_password_handler.go
@@ -69,7 +69,7 @@ func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *dom
userOrg = authReq.UserOrgID
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
- err = l.command.SetPassword(setContext(r.Context(), userOrg), userOrg, data.UserID, data.Code, data.Password, userAgentID)
+ err = l.command.SetPasswordWithVerifyCode(setContext(r.Context(), userOrg), userOrg, data.UserID, data.Code, data.Password, userAgentID)
if err != nil {
l.renderInitPassword(w, r, authReq, data.UserID, "", err)
return
diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto
index 906bdeb292..a8da4d17da 100644
--- a/proto/zitadel/management.proto
+++ b/proto/zitadel/management.proto
@@ -392,7 +392,7 @@ service ManagementService {
};
}
- // A Manager is only allowed to set an initial password, on the next login the user has to change his password
+ // deprecated: use SetHumanPassword
rpc SetHumanInitialPassword(SetHumanInitialPasswordRequest) returns (SetHumanInitialPasswordResponse) {
option (google.api.http) = {
post: "/users/{user_id}/password/_initialize"
@@ -404,6 +404,19 @@ service ManagementService {
};
}
+ // Set a new password for a user, on default the user has to change the password on the next login
+ // Set no_change_required to true if the user does not have to change the password on the next login
+ rpc SetHumanPassword(SetHumanPasswordRequest) returns (SetHumanPasswordResponse) {
+ option (google.api.http) = {
+ post: "/users/{user_id}/password"
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "user.write"
+ };
+ }
+
// An email will be sent to the given address to reset the password of the user
rpc SendHumanResetPasswordNotification(SendHumanResetPasswordNotificationRequest) returns (SendHumanResetPasswordNotificationResponse) {
option (google.api.http) = {
@@ -2380,6 +2393,16 @@ message SetHumanInitialPasswordResponse {
zitadel.v1.ObjectDetails details = 1;
}
+message SetHumanPasswordRequest {
+ string user_id = 1 [(validate.rules).string.min_len = 1];
+ string password = 2 [(validate.rules).string = {min_len: 1, max_len: 72}];
+ bool no_change_required = 3;
+}
+
+message SetHumanPasswordResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
message SendHumanResetPasswordNotificationRequest {
enum Type {
TYPE_EMAIL = 0;