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
+18 -7
View File
@@ -15,14 +15,14 @@ import (
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
)
func (s *Server) createUserTypeHuman(ctx context.Context, humanPb *user.CreateUserRequest_Human, orgId string, userName, userId *string) (*connect.Response[user.CreateUserResponse], error) {
metadataEntries := make([]*user.SetMetadataEntry, len(humanPb.Metadata))
for i, metadataEntry := range humanPb.Metadata {
metadataEntries[i] = &user.SetMetadataEntry{
Key: metadataEntry.GetKey(),
Value: metadataEntry.GetValue(),
}
func (s *Server) createUserTypeHuman(ctx context.Context, humanPb *user.CreateUserRequest_Human, orgId string, userName, userId *string, metadata []*user.Metadata) (*connect.Response[user.CreateUserResponse], error) {
if len(metadata) > 0 && len(humanPb.Metadata) > 0 { //nolint:staticcheck
return nil, zerrors.ThrowInvalidArgument(nil, "USERv2-j0z7uy", "Errors.User.Metadata.Conflicting")
}
if len(metadata) == 0 && len(humanPb.Metadata) > 0 { //nolint:staticcheck
metadata = humanPb.Metadata //nolint:staticcheck
}
metadataEntries := setMetadataEntries(metadata)
addHumanPb := &user.AddHumanUserRequest{
Username: userName,
UserId: userId,
@@ -69,6 +69,17 @@ func (s *Server) createUserTypeHuman(ctx context.Context, humanPb *user.CreateUs
}), nil
}
func setMetadataEntries(metadata []*user.Metadata) []*user.SetMetadataEntry {
metadataEntries := make([]*user.SetMetadataEntry, len(metadata))
for i, metadataEntry := range metadata {
metadataEntries[i] = &user.SetMetadataEntry{
Key: metadataEntry.GetKey(),
Value: metadataEntry.GetValue(),
}
}
return metadataEntries
}
func (s *Server) updateUserTypeHuman(ctx context.Context, humanPb *user.UpdateUserRequest_Human, userId string, userName *string) (*connect.Response[user.UpdateUserResponse], error) {
cmd, err := updateHumanUserToCommand(userId, userName, humanPb)
if err != nil {
@@ -3111,7 +3111,7 @@ func TestServer_CreateUser(t *testing.T) {
},
},
{
name: "with metadata",
name: "error - metadata on both root and human",
testCase: func(runId string) testCase {
username := fmt.Sprintf("donald.duck+%s", runId)
email := username + "@example.com"
@@ -3135,16 +3135,16 @@ func TestServer_CreateUser(t *testing.T) {
},
Metadata: []*user.Metadata{
{Key: "key1", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value1")))},
{Key: "key2", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value2")))},
{Key: "key3", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value3")))},
},
},
},
Metadata: []*user.Metadata{
{Key: "key2", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value2")))},
{Key: "key3", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value3")))},
},
},
},
want: &user.CreateUserResponse{
Id: "is generated",
},
wantErr: true,
}
},
},
@@ -3514,154 +3514,271 @@ func TestServer_CreateUser_And_Compare(t *testing.T) {
type testCase struct {
name string
args args
assert func(t *testing.T, createResponse *user.CreateUserResponse, getResponse *user.GetUserByIDResponse)
assert func(
t *testing.T,
createResponse *user.CreateUserResponse,
getResponse *user.GetUserByIDResponse,
getMetadataResponse *user.ListUserMetadataResponse,
)
}
tests := []struct {
name string
testCase func(runId string) testCase
}{{
name: "human given username",
testCase: func(runId string) testCase {
username := fmt.Sprintf("donald.duck+%s", runId)
email := username + "@example.com"
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
Username: &username,
UserType: &user.CreateUserRequest_Human_{
Human: &user.CreateUserRequest_Human{
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
},
Email: &user.SetHumanEmail{
Email: email,
}{
{
name: "human given username",
testCase: func(runId string) testCase {
username := fmt.Sprintf("donald.duck+%s", runId)
email := username + "@example.com"
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
Username: &username,
UserType: &user.CreateUserRequest_Human_{
Human: &user.CreateUserRequest_Human{
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
},
Email: &user.SetHumanEmail{
Email: email,
},
},
},
},
},
},
assert: func(t *testing.T, _ *user.CreateUserResponse, getResponse *user.GetUserByIDResponse) {
assert.Equal(t, username, getResponse.GetUser().GetUsername())
},
}
assert: func(t *testing.T, _ *user.CreateUserResponse, getResponse *user.GetUserByIDResponse, _ *user.ListUserMetadataResponse) {
assert.Equal(t, username, getResponse.GetUser().GetUsername())
},
}
},
},
}, {
name: "human username default to email",
testCase: func(runId string) testCase {
username := fmt.Sprintf("donald.duck+%s", runId)
email := username + "@example.com"
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
UserType: &user.CreateUserRequest_Human_{
Human: &user.CreateUserRequest_Human{
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
},
Email: &user.SetHumanEmail{
Email: email,
{
name: "human with embedded metadata",
testCase: func(runId string) testCase {
username := fmt.Sprintf("donald.duck+%s", runId)
email := username + "@example.com"
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
Username: &username,
UserType: &user.CreateUserRequest_Human_{
Human: &user.CreateUserRequest_Human{
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
},
Email: &user.SetHumanEmail{
Email: email,
},
Metadata: []*user.Metadata{
{Key: "key1", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value1")))},
{Key: "key2", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value2")))},
},
},
},
},
},
},
assert: func(t *testing.T, _ *user.CreateUserResponse, getResponse *user.GetUserByIDResponse) {
assert.Equal(t, email, getResponse.GetUser().GetUsername())
},
}
assert: func(t *testing.T, _ *user.CreateUserResponse, getResponse *user.GetUserByIDResponse, getMetadataResponse *user.ListUserMetadataResponse) {
assert.Equal(t, username, getResponse.GetUser().GetUsername())
metadataByKey := getMetadataMap(getMetadataResponse)
assert.Len(t, getMetadataResponse.GetMetadata(), 2)
assert.Equal(t, []byte(base64.StdEncoding.EncodeToString([]byte("value1"))), metadataByKey["key1"])
assert.Equal(t, []byte(base64.StdEncoding.EncodeToString([]byte("value2"))), metadataByKey["key2"])
},
}
},
},
}, {
name: "service accountname given",
testCase: func(runId string) testCase {
username := fmt.Sprintf("donald.duck+%s", runId)
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
Username: &username,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: "donald",
{
name: "human with metadata",
testCase: func(runId string) testCase {
username := fmt.Sprintf("donald.duck+%s", runId)
email := username + "@example.com"
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
Username: &username,
UserType: &user.CreateUserRequest_Human_{
Human: &user.CreateUserRequest_Human{
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
},
Email: &user.SetHumanEmail{
Email: email,
},
},
},
Metadata: []*user.Metadata{
{Key: "key1", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value1")))},
},
},
},
},
assert: func(t *testing.T, _ *user.CreateUserResponse, getResponse *user.GetUserByIDResponse) {
assert.Equal(t, username, getResponse.GetUser().GetUsername())
},
}
assert: func(t *testing.T, _ *user.CreateUserResponse, getResponse *user.GetUserByIDResponse, getMetadataResponse *user.ListUserMetadataResponse) {
assert.Equal(t, username, getResponse.GetUser().GetUsername())
metadataByKey := getMetadataMap(getMetadataResponse)
assert.Len(t, getMetadataResponse.GetMetadata(), 1)
assert.Equal(t, []byte(base64.StdEncoding.EncodeToString([]byte("value1"))), metadataByKey["key1"])
},
}
},
},
}, {
name: "service accountname default to generated id",
testCase: func(runId string) testCase {
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: "donald",
{
name: "human username default to email",
testCase: func(runId string) testCase {
username := fmt.Sprintf("donald.duck+%s", runId)
email := username + "@example.com"
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
UserType: &user.CreateUserRequest_Human_{
Human: &user.CreateUserRequest_Human{
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
},
Email: &user.SetHumanEmail{
Email: email,
},
},
},
},
},
},
assert: func(t *testing.T, createResponse *user.CreateUserResponse, getResponse *user.GetUserByIDResponse) {
assert.Equal(t, createResponse.GetId(), getResponse.GetUser().GetUsername())
},
}
assert: func(t *testing.T, _ *user.CreateUserResponse, getResponse *user.GetUserByIDResponse, _ *user.ListUserMetadataResponse) {
assert.Equal(t, email, getResponse.GetUser().GetUsername())
},
}
},
},
}, {
name: "service accountname default to given id",
testCase: func(runId string) testCase {
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
UserId: &runId,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: "donald",
{
name: "service accountname given",
testCase: func(runId string) testCase {
username := fmt.Sprintf("donald.duck+%s", runId)
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
Username: &username,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: "donald",
},
},
},
},
},
assert: func(t *testing.T, createResponse *user.CreateUserResponse, getResponse *user.GetUserByIDResponse) {
assert.Equal(t, runId, getResponse.GetUser().GetUsername())
},
}
assert: func(t *testing.T, _ *user.CreateUserResponse, getResponse *user.GetUserByIDResponse, _ *user.ListUserMetadataResponse) {
assert.Equal(t, username, getResponse.GetUser().GetUsername())
},
}
},
},
}, {
name: "machine access token type jwt",
testCase: func(runId string) testCase {
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
UserId: &runId,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: "donald",
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT,
{
name: "service account with metadata",
testCase: func(runId string) testCase {
username := fmt.Sprintf("donald.duck+%s", runId)
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
Username: &username,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: "donald",
},
},
Metadata: []*user.Metadata{
{Key: "key1", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value1")))},
{Key: "key2", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value2")))},
},
},
},
},
assert: func(t *testing.T, createResponse *user.CreateUserResponse, getResponse *user.GetUserByIDResponse) {
assert.Equal(t, user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT, getResponse.GetUser().GetMachine().GetAccessTokenType())
},
}
assert: func(t *testing.T, _ *user.CreateUserResponse, getResponse *user.GetUserByIDResponse, getMetadataResponse *user.ListUserMetadataResponse) {
assert.Equal(t, username, getResponse.GetUser().GetUsername())
metadataByKey := getMetadataMap(getMetadataResponse)
assert.Len(t, getMetadataResponse.GetMetadata(), 2)
assert.Equal(t, []byte(base64.StdEncoding.EncodeToString([]byte("value1"))), metadataByKey["key1"])
assert.Equal(t, []byte(base64.StdEncoding.EncodeToString([]byte("value2"))), metadataByKey["key2"])
},
}
},
},
}}
{
name: "service accountname default to generated id",
testCase: func(runId string) testCase {
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: "donald",
},
},
},
},
assert: func(t *testing.T, createResponse *user.CreateUserResponse, getResponse *user.GetUserByIDResponse, _ *user.ListUserMetadataResponse) {
assert.Equal(t, createResponse.GetId(), getResponse.GetUser().GetUsername())
},
}
},
},
{
name: "service accountname default to given id",
testCase: func(runId string) testCase {
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
UserId: &runId,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: "donald",
},
},
},
},
assert: func(t *testing.T, createResponse *user.CreateUserResponse, getResponse *user.GetUserByIDResponse, _ *user.ListUserMetadataResponse) {
assert.Equal(t, runId, getResponse.GetUser().GetUsername())
},
}
},
},
{
name: "machine access token type jwt",
testCase: func(runId string) testCase {
return testCase{
args: args{
ctx: OrgCTX,
req: &user.CreateUserRequest{
OrganizationId: Instance.DefaultOrg.Id,
UserId: &runId,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: "donald",
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT,
},
},
},
},
assert: func(t *testing.T, createResponse *user.CreateUserResponse, getResponse *user.GetUserByIDResponse, _ *user.ListUserMetadataResponse) {
assert.Equal(t, user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT, getResponse.GetUser().GetMachine().GetAccessTokenType())
},
}
},
},
}
for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
now := time.Now()
@@ -3674,11 +3791,23 @@ func TestServer_CreateUser_And_Compare(t *testing.T) {
UserId: createResponse.GetId(),
})
require.NoError(t, err)
test.assert(t, createResponse, getResponse)
gotMetadataResponse, err := Client.ListUserMetadata(test.args.ctx, &user.ListUserMetadataRequest{
UserId: createResponse.GetId(),
})
require.NoError(t, err)
test.assert(t, createResponse, getResponse, gotMetadataResponse)
})
}
}
func getMetadataMap(getMetadataResponse *user.ListUserMetadataResponse) map[string][]byte {
metadataByKey := make(map[string][]byte, len(getMetadataResponse.GetMetadata()))
for _, md := range getMetadataResponse.GetMetadata() {
metadataByKey[md.Key] = md.Value
}
return metadataByKey
}
func TestServer_CreateUser_Permission(t *testing.T) {
newOrgOwnerEmail := integration.Email()
newOrg := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), newOrgOwnerEmail)
+9 -1
View File
@@ -12,7 +12,14 @@ import (
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
)
func (s *Server) createUserTypeMachine(ctx context.Context, machinePb *user.CreateUserRequest_Machine, orgId, userName, userId string) (*connect.Response[user.CreateUserResponse], error) {
func (s *Server) createUserTypeMachine(ctx context.Context, machinePb *user.CreateUserRequest_Machine, orgId, userName, userId string, reqMetadata []*user.Metadata) (*connect.Response[user.CreateUserResponse], error) {
metadata := make([]*command.AddMetadataEntry, len(reqMetadata))
for i, metadataEntry := range reqMetadata {
metadata[i] = &command.AddMetadataEntry{
Key: metadataEntry.GetKey(),
Value: metadataEntry.GetValue(),
}
}
cmd := &command.Machine{
Username: userName,
Name: machinePb.Name,
@@ -22,6 +29,7 @@ func (s *Server) createUserTypeMachine(ctx context.Context, machinePb *user.Crea
ResourceOwner: orgId,
AggregateID: userId,
},
Metadata: metadata,
}
details, err := s.command.AddMachine(
ctx,
+2 -2
View File
@@ -414,9 +414,9 @@ func (s *Server) HumanMFAInitSkipped(ctx context.Context, req *connect.Request[u
func (s *Server) CreateUser(ctx context.Context, req *connect.Request[user.CreateUserRequest]) (*connect.Response[user.CreateUserResponse], error) {
switch userType := req.Msg.GetUserType().(type) {
case *user.CreateUserRequest_Human_:
return s.createUserTypeHuman(ctx, userType.Human, req.Msg.GetOrganizationId(), req.Msg.Username, req.Msg.UserId)
return s.createUserTypeHuman(ctx, userType.Human, req.Msg.GetOrganizationId(), req.Msg.Username, req.Msg.UserId, req.Msg.GetMetadata())
case *user.CreateUserRequest_Machine_:
return s.createUserTypeMachine(ctx, userType.Machine, req.Msg.GetOrganizationId(), req.Msg.GetUsername(), req.Msg.GetUserId())
return s.createUserTypeMachine(ctx, userType.Machine, req.Msg.GetOrganizationId(), req.Msg.GetUsername(), req.Msg.GetUserId(), req.Msg.GetMetadata())
default:
return nil, zerrors.ThrowInternal(nil, "", "user type is not implemented")
}
+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{
+2
View File
@@ -132,6 +132,8 @@ Errors:
CouldNotGenerate: "تعذر توليد السر"
PAT:
NotFound: "رمز الوصول الشخصي غير موجود"
Metadata:
Conflicting: "يجب عدم تعيين البيانات الوصفية في كلٍ من حقول [user] و[human] في الوقت نفسه"
NotHuman: "يجب أن يكون المستخدم شخصياً"
NotMachine: "يجب أن يكون المستخدم تقنياً"
WrongType: "غير مسموح لنوع المستخدم هذا"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "Тайната не можа да бъде генерирана"
PAT:
NotFound: "Личен токен за достъп не е намерен"
Metadata:
Conflicting: "Метаданните не трябва да бъдат зададени едновременно в полетата [user] и [human]"
NotHuman: "Потребителят трябва да е личен"
NotMachine: "Потребителят трябва да е техничен"
WrongType: "Не е разрешено за този тип потребител"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "Tajemství nelze vygenerovat"
PAT:
NotFound: "Osobní přístupový token nenalezen"
Metadata:
Conflicting: "Metadata nesmí být nastavena současně v polích [user] a [human]"
NotHuman: "Uživatel musí být fyzická osoba"
NotMachine: "Uživatel musí být systémový uživatel / technická entita"
WrongType: "Nepovolen pro tento typ uživatele"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "Secret konnte nicht generiert werden"
PAT:
NotFound: "Persönliches Access Token nicht gefunden"
Metadata:
Conflicting: "Metadaten dürfen nicht gleichzeitig in den Feldern [user] und [human] gesetzt sein"
NotHuman: "Der Benutzer muss eine Person sein"
NotMachine: "Der Benutzer muss technisch sein"
WrongType: "Für diesen Benutzertyp nicht erlaubt"
+2
View File
@@ -132,6 +132,8 @@ Errors:
CouldNotGenerate: "Secret could not be generated"
PAT:
NotFound: "Personal Access Token not found"
Metadata:
Conflicting: "Metadata must not be set on both the user and human fields"
NotHuman: "The User must be personal"
NotMachine: "The User must be technical"
WrongType: "Not allowed for this user type"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "El secreto no pudo generarse"
PAT:
NotFound: "Token de acceso personal no encontrado"
Metadata:
Conflicting: "Los metadatos no deben definirse simultáneamente en los campos [user] y [human]"
NotHuman: "El usuario debe ser personal"
NotMachine: "El usuario debe ser técnico"
WrongType: "Tipo de usuario no permitido"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "Secret n'a pas pu être généré"
PAT:
NotFound: "Token d'accès personnel non trouvé"
Metadata:
Conflicting: "Les métadonnées ne doivent pas être définies simultanément dans les champs [user] et [human]"
NotHuman: "L'utilisateur doit être personnel"
NotMachine: "L'utilisateur doit être technique"
WrongType: "Non autorisé pour ce type d'utilisateur"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "A titkot nem sikerült generálni"
PAT:
NotFound: "A személyes hozzáférési token nem található"
Metadata:
Conflicting: "A metaadatok nem lehetnek egyszerre beállítva a [user] és a [human] mezőkben"
NotHuman: "A felhasználónak személyesnek kell lennie"
NotMachine: "A felhasználónak technikai jellegűnek kell lennie"
WrongType: "Ez a felhasználótípus számára nem engedélyezett"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "Rahasia tidak dapat dibuat"
PAT:
NotFound: "Token Akses Pribadi tidak ditemukan"
Metadata:
Conflicting: "Metadata tidak boleh diatur secara bersamaan pada field [user] dan [human]"
NotHuman: "Pengguna harus bersifat pribadi"
NotMachine: "Pengguna harus teknis"
WrongType: "Tidak diizinkan untuk jenis pengguna ini"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "Non è stato possibile generare il Secret"
PAT:
NotFound: "Personal Access Token non trovato"
Metadata:
Conflicting: "I metadati non devono essere impostati contemporaneamente nei campi [user] e [human]"
NotHuman: "L'utente deve essere personale"
NotMachine: "L'utente deve essere tecnico"
WrongType: "Non consentito per questo tipo di utente"
+2
View File
@@ -132,6 +132,8 @@ Errors:
CouldNotGenerate: "シークレットの生成に失敗しました"
PAT:
NotFound: "パーソナルアクセストークンが見つかりません"
Metadata:
Conflicting: "メタデータは [user] と [human] の両方のフィールドに同時に設定してはなりません"
NotHuman: "ユーザーはパーソナルである必要があります"
NotMachine: "ユーザーはテクニカルである必要があります"
WrongType: "このユーザータイプは許可されていません"
+2
View File
@@ -132,6 +132,8 @@ Errors:
CouldNotGenerate: "시크릿을 생성할 수 없습니다"
PAT:
NotFound: "개인 액세스 토큰을 찾을 수 없습니다"
Metadata:
Conflicting: "메타데이터는 [user] 및 [human] 필드에 동시에 설정되어서는 안 됩니다"
NotHuman: "사용자는 개인이어야 합니다"
NotMachine: "사용자는 기술적이어야 합니다"
WrongType: "이 사용자 유형에는 허용되지 않습니다"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "Тајната не може да биде генерирана"
PAT:
NotFound: "Личниот токен за пристап не е пронајден"
Metadata:
Conflicting: "Метаподатоците не смеат да бидат поставени истовремено во полињата [user] и [human]"
NotHuman: "Корисникот мора да биде личност"
NotMachine: "Корисникот мора да биде технички"
WrongType: "Не е дозволено за овој тип на корисник"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "Geheim kon niet worden gegenereerd"
PAT:
NotFound: "Persoonlijk toegangstoken niet gevonden"
Metadata:
Conflicting: "Metadata mogen niet tegelijkertijd in de velden [user] en [human] worden ingesteld"
NotHuman: "De gebruiker moet persoonlijk zijn"
NotMachine: "De gebruiker moet technisch zijn"
WrongType: "Niet toegestaan voor dit gebruikerstype"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "Sekret nie mógł zostać wygenerowany"
PAT:
NotFound: "Osobisty token dostępu nie znaleziony"
Metadata:
Conflicting: "Metadane nie mogą być ustawione jednocześnie w polach [user] i [human]"
NotHuman: "Użytkownik musi być osobą"
NotMachine: "Użytkownik musi być techniczny"
WrongType: "Niedozwolone dla tego typu użytkownika"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "Não foi possível gerar o segredo"
PAT:
NotFound: "Token de Acesso Pessoal não encontrado"
Metadata:
Conflicting: "Os metadados não devem ser definidos simultaneamente nos campos [user] e [human]"
NotHuman: "O usuário deve ser pessoal"
NotMachine: "O usuário deve ser técnico"
WrongType: "Não permitido para este tipo de usuário"
+2
View File
@@ -132,6 +132,8 @@ Errors:
CouldNotGenerate: "Secretul nu a putut fi generat"
PAT:
NotFound: "Token-ul de acces personal nu a fost găsit"
Metadata:
Conflicting: "Metadatele nu trebuie setate simultan în câmpurile [user] și [human]"
NotHuman: "Utilizatorul trebuie să fie personal"
NotMachine: "Utilizatorul trebuie să fie tehnic"
WrongType: "Nu este permis pentru acest tip de utilizator"
+2
View File
@@ -132,6 +132,8 @@ Errors:
CouldNotGenerate: "Ключ не может быть сгенерирован"
PAT:
NotFound: "Токен личного доступа не найден"
Metadata:
Conflicting: "Метаданные не должны быть заданы одновременно в полях [user] и [human]"
NotHuman: "Пользователь должен быть персональным"
NotMachine: "Пользователь должен быть техническим"
WrongType: "Запрещено для данного типа пользователя"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "Hemlig kod kunde inte genereras"
PAT:
NotFound: "Personlig åtkomst-token hittades inte"
Metadata:
Conflicting: "Metadata får inte anges samtidigt i fälten [user] och [human]"
NotHuman: "Användaren måste vara en person"
NotMachine: "Användaren måste vara en maskin"
WrongType: "Inte tillåtet för denna användartyp"
+2
View File
@@ -132,6 +132,8 @@ Errors:
CouldNotGenerate: "Gizli anahtar oluşturulamadı"
PAT:
NotFound: "Kişisel Erişim Token'ı bulunamadı"
Metadata:
Conflicting: "Meta veriler [user] ve [human] alanlarında aynı anda ayarlanmamalıdır"
NotHuman: "Kullanıcı kişisel olmalı"
NotMachine: "Kullanıcı teknik olmalı"
WrongType: "Bu kullanıcı türü için izin verilmiyor"
+2
View File
@@ -131,6 +131,8 @@ Errors:
CouldNotGenerate: "Не вдалося згенерувати секрет"
PAT:
NotFound: "Персональний токен доступу не знайдено"
Metadata:
Conflicting: "Метадані не повинні бути встановлені одночасно в полях [user] і [human]"
NotHuman: "Користувач повинен бути особистим"
NotMachine: "Користувач повинен бути технічним"
WrongType: "Не дозволено для цього типу користувача"
+2
View File
@@ -132,6 +132,8 @@ Errors:
CouldNotGenerate: "无法生成秘密"
PAT:
NotFound: "未找到个人访问令牌"
Metadata:
Conflicting: "元数据不得同时在 [user] 和 [human] 字段中设置"
NotHuman: "用户必须是个人"
NotMachine: "用户必须是技术人员"
WrongType: "此用户类型不允许"
+14 -2
View File
@@ -2078,7 +2078,7 @@ message CreateUserRequest{
// An Implementation of RFC 6238 is used, with HMAC-SHA-1 and time-step of 30 seconds.
// Currently no other options are supported, and if anything different is used the validation will fail.
optional string totp_secret = 8 [
(validate.rules).string = {min_len: 1 max_len: 200},
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1,
max_length: 200,
@@ -2086,8 +2086,12 @@ message CreateUserRequest{
}
];
// Metadata to bet set. The values have to be base64 encoded.
// Deprecated: Use the root-level `metadata` field in `CreateUserRequest` instead.
// If both metadata fields are set, the validation will fail.
//
// Metadata to be set. The values have to be base64 encoded.
repeated Metadata metadata = 9 [
deprecated = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[{\"key\": \"test1\", \"value\": \"VGhpcyBpcyBteSBmaXJzdCB2YWx1ZQ==\"}, {\"key\": \"test2\", \"value\": \"VGhpcyBpcyBteSBzZWNvbmQgdmFsdWU=\"}]"
}
@@ -2177,6 +2181,14 @@ message CreateUserRequest{
// You can change it to JWT by specifying the access token type in the machine field.
Machine machine = 5;
}
// Metadata to be set. The values have to be base64 encoded.
repeated Metadata metadata = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[{\"key\": \"test1\", \"value\": \"VGhpcyBpcyBteSBmaXJzdCB2YWx1ZQ==\"}, {\"key\": \"test2\", \"value\": \"VGhpcyBpcyBteSBzZWNvbmQgdmFsdWU=\"}]"
}
];
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
example: "{\"organizationId\":\"69629026806489455\",\"userId\":\"163840776835432345\",\"username\":\"minnie-mouse\",\"human\":{\"profile\":{\"givenName\":\"Minnie\",\"familyName\":\"Mouse\",\"nickName\":\"Mini\",\"displayName\":\"Minnie Mouse\",\"preferredLanguage\":\"en\",\"gender\":\"GENDER_FEMALE\"},\"email\":{\"email\":\"mini@mouse.com\",\"sendCode\":{\"urlTemplate\":\"https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}\"}},\"phone\":{\"phone\":\"+41791234567\",\"isVerified\":true},\"password\":{\"password\":\"Secr3tP4ssw0rd!\",\"changeRequired\":true},\"idpLinks\":[{\"idpId\":\"d654e6ba-70a3-48ef-a95d-37c8d8a7901a\",\"userId\":\"6516849804890468048461403518\",\"userName\":\"user@external.com\"}],\"totpSecret\":\"TJOPWSDYILLHXFV4MLKNNJOWFG7VSDCK\"}}";
};