feat: set metadata at the user level in CreateUser request (#11719)

# Which Problems Are Solved

Currently, metadata can be set/updated only for human users, but it
should be available also for service accounts (machine users).

# How the Problems Are Solved

This is achieved by:
- adding a metadata field at the root level in `CreateUserRequest`,
which makes it available for both `human` and `machine` user types
- returning an error when both the root level and human-level `metadata`
fields are set in the `CreateUserRequest`
- setting `human` and `machine` metadata from the root `metadata` field
in the server layer
- pushing `user.metadata.set` event during machine user creation in the
command layer
- adding integration tests

# Additional Changes
N/A

# Additional Context
- Related to https://github.com/zitadel/zitadel/issues/11369

---------

Co-authored-by: Vitor Bari Buccianti <[email protected]>
Co-authored-by: Silvan <[email protected]>
This commit is contained in:
Gayathri Vijayan
2026-03-03 08:34:43 +00:00
committed by GitHub
co-authored by Vitor Bari Buccianti Silvan
parent 92a628d892
commit 4aacfa842f
29 changed files with 440 additions and 144 deletions
+30 -7
View File
@@ -26,6 +26,7 @@ type Machine struct {
Description string
AccessTokenType domain.OIDCTokenType
PermissionCheck PermissionCheck
Metadata []*AddMetadataEntry
}
func (m *Machine) IsZero() bool {
@@ -40,11 +41,9 @@ func AddMachineCommand(a *user.Aggregate, machine *Machine) preparation.Validati
if a.ID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-p0p2mi", "Errors.User.UserIDMissing")
}
if machine.Name == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-bs9Ds", "Errors.User.Invalid")
}
if machine.Username == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-bm9Ds", "Errors.User.Invalid")
err = machine.Validate()
if err != nil {
return nil, err
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
ctx, span := tracing.NewSpan(ctx)
@@ -65,9 +64,18 @@ func AddMachineCommand(a *user.Aggregate, machine *Machine) preparation.Validati
if err != nil {
return nil, err
}
return []eventstore.Command{
cmds := []eventstore.Command{
user.NewMachineAddedEvent(ctx, &a.Aggregate, machine.Username, machine.Name, machine.Description, domainPolicy.UserLoginMustBeDomain || orgScopedUsername, machine.AccessTokenType),
}, nil
}
for _, metadataEntry := range machine.Metadata {
cmds = append(cmds, user.NewMetadataSetEvent(
ctx,
&a.Aggregate,
metadataEntry.Key,
metadataEntry.Value,
))
}
return cmds, nil
}, nil
}
}
@@ -214,3 +222,18 @@ func getMachineWriteModel(ctx context.Context, userID, resourceOwner string, fil
}
return writeModel, err
}
func (m *Machine) Validate() (err error) {
if m.Name == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-bs9Ds", "Errors.User.Invalid")
}
if m.Username == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-bm9Ds", "Errors.User.Invalid")
}
for _, metadataEntry := range m.Metadata {
if err := metadataEntry.Valid(); err != nil {
return err
}
}
return nil
}
+69
View File
@@ -105,6 +105,64 @@ func TestCommandSide_AddMachine(t *testing.T) {
err: zerrors.IsPreconditionFailed,
},
},
{
name: "add machine with invalid metadata key, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
},
args: args{
ctx: context.Background(),
machine: &Machine{
ObjectRoot: models.ObjectRoot{
ResourceOwner: "org1",
},
Description: "description",
Name: "name",
Username: "username",
Metadata: []*AddMetadataEntry{
{
Key: "",
Value: []byte("value1"),
},
},
},
},
res: res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
name: "add machine with invalid metadata value, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
},
args: args{
ctx: context.Background(),
machine: &Machine{
ObjectRoot: models.ObjectRoot{
ResourceOwner: "org1",
},
Description: "description",
Name: "name",
Username: "username",
Metadata: []*AddMetadataEntry{
{
Key: "key1",
Value: []byte(""),
},
},
},
},
res: res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
name: "add machine, ok",
fields: fields{
@@ -131,6 +189,11 @@ func TestCommandSide_AddMachine(t *testing.T) {
true,
domain.OIDCTokenTypeBearer,
),
user.NewMetadataSetEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key1",
[]byte("value1"),
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
@@ -144,6 +207,12 @@ func TestCommandSide_AddMachine(t *testing.T) {
Description: "description",
Name: "name",
Username: "username",
Metadata: []*AddMetadataEntry{
{
Key: "key1",
Value: []byte("value1"),
},
},
},
},
res: res{