diff --git a/api/grpc/users/v1/users.pb.go b/api/grpc/users/v1/users.pb.go index 0ad1777a94..96ed5eaff6 100644 --- a/api/grpc/users/v1/users.pb.go +++ b/api/grpc/users/v1/users.pb.go @@ -155,25 +155,26 @@ func (x *RetrieveUsersRes) GetUsers() []*User { } type User struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - FirstName string `protobuf:"bytes,2,opt,name=first_name,json=firstName,proto3" json:"first_name,omitempty"` - LastName string `protobuf:"bytes,3,opt,name=last_name,json=lastName,proto3" json:"last_name,omitempty"` - Tags []string `protobuf:"bytes,4,rep,name=tags,proto3" json:"tags,omitempty"` - Metadata *structpb.Struct `protobuf:"bytes,5,opt,name=metadata,proto3" json:"metadata,omitempty"` - Status uint32 `protobuf:"varint,6,opt,name=status,proto3" json:"status,omitempty"` - Role uint32 `protobuf:"varint,7,opt,name=role,proto3" json:"role,omitempty"` - ProfilePicture string `protobuf:"bytes,8,opt,name=profile_picture,json=profilePicture,proto3" json:"profile_picture,omitempty"` - Username string `protobuf:"bytes,9,opt,name=username,proto3" json:"username,omitempty"` - Email string `protobuf:"bytes,10,opt,name=email,proto3" json:"email,omitempty"` - CreatedAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` - UpdatedBy string `protobuf:"bytes,13,opt,name=updated_by,json=updatedBy,proto3" json:"updated_by,omitempty"` - VerifiedAt *timestamppb.Timestamp `protobuf:"bytes,14,opt,name=verified_at,json=verifiedAt,proto3" json:"verified_at,omitempty"` - AuthProvider string `protobuf:"bytes,15,opt,name=auth_provider,json=authProvider,proto3" json:"auth_provider,omitempty"` - Permissions []string `protobuf:"bytes,16,rep,name=permissions,proto3" json:"permissions,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + FirstName string `protobuf:"bytes,2,opt,name=first_name,json=firstName,proto3" json:"first_name,omitempty"` + LastName string `protobuf:"bytes,3,opt,name=last_name,json=lastName,proto3" json:"last_name,omitempty"` + Tags []string `protobuf:"bytes,4,rep,name=tags,proto3" json:"tags,omitempty"` + PublicMetadata *structpb.Struct `protobuf:"bytes,5,opt,name=public_metadata,json=publicMetadata,proto3" json:"public_metadata,omitempty"` + PrivateMetadata *structpb.Struct `protobuf:"bytes,6,opt,name=private_metadata,json=privateMetadata,proto3" json:"private_metadata,omitempty"` + Status uint32 `protobuf:"varint,7,opt,name=status,proto3" json:"status,omitempty"` + Role uint32 `protobuf:"varint,8,opt,name=role,proto3" json:"role,omitempty"` + ProfilePicture string `protobuf:"bytes,9,opt,name=profile_picture,json=profilePicture,proto3" json:"profile_picture,omitempty"` + Username string `protobuf:"bytes,10,opt,name=username,proto3" json:"username,omitempty"` + Email string `protobuf:"bytes,11,opt,name=email,proto3" json:"email,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,13,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + UpdatedBy string `protobuf:"bytes,14,opt,name=updated_by,json=updatedBy,proto3" json:"updated_by,omitempty"` + VerifiedAt *timestamppb.Timestamp `protobuf:"bytes,15,opt,name=verified_at,json=verifiedAt,proto3" json:"verified_at,omitempty"` + AuthProvider string `protobuf:"bytes,16,opt,name=auth_provider,json=authProvider,proto3" json:"auth_provider,omitempty"` + Permissions []string `protobuf:"bytes,17,rep,name=permissions,proto3" json:"permissions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *User) Reset() { @@ -234,9 +235,16 @@ func (x *User) GetTags() []string { return nil } -func (x *User) GetMetadata() *structpb.Struct { +func (x *User) GetPublicMetadata() *structpb.Struct { if x != nil { - return x.Metadata + return x.PublicMetadata + } + return nil +} + +func (x *User) GetPrivateMetadata() *structpb.Struct { + if x != nil { + return x.PrivateMetadata } return nil } @@ -331,30 +339,31 @@ const file_users_v1_users_proto_rawDesc = "" + "\x05total\x18\x01 \x01(\x04R\x05total\x12\x14\n" + "\x05limit\x18\x02 \x01(\x04R\x05limit\x12\x16\n" + "\x06offset\x18\x03 \x01(\x04R\x06offset\x12$\n" + - "\x05users\x18\x04 \x03(\v2\x0e.users.v1.UserR\x05users\"\xbb\x04\n" + + "\x05users\x18\x04 \x03(\v2\x0e.users.v1.UserR\x05users\"\x8c\x05\n" + "\x04User\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n" + "\n" + "first_name\x18\x02 \x01(\tR\tfirstName\x12\x1b\n" + "\tlast_name\x18\x03 \x01(\tR\blastName\x12\x12\n" + - "\x04tags\x18\x04 \x03(\tR\x04tags\x123\n" + - "\bmetadata\x18\x05 \x01(\v2\x17.google.protobuf.StructR\bmetadata\x12\x16\n" + - "\x06status\x18\x06 \x01(\rR\x06status\x12\x12\n" + - "\x04role\x18\a \x01(\rR\x04role\x12'\n" + - "\x0fprofile_picture\x18\b \x01(\tR\x0eprofilePicture\x12\x1a\n" + - "\busername\x18\t \x01(\tR\busername\x12\x14\n" + - "\x05email\x18\n" + - " \x01(\tR\x05email\x129\n" + + "\x04tags\x18\x04 \x03(\tR\x04tags\x12@\n" + + "\x0fpublic_metadata\x18\x05 \x01(\v2\x17.google.protobuf.StructR\x0epublicMetadata\x12B\n" + + "\x10private_metadata\x18\x06 \x01(\v2\x17.google.protobuf.StructR\x0fprivateMetadata\x12\x16\n" + + "\x06status\x18\a \x01(\rR\x06status\x12\x12\n" + + "\x04role\x18\b \x01(\rR\x04role\x12'\n" + + "\x0fprofile_picture\x18\t \x01(\tR\x0eprofilePicture\x12\x1a\n" + + "\busername\x18\n" + + " \x01(\tR\busername\x12\x14\n" + + "\x05email\x18\v \x01(\tR\x05email\x129\n" + "\n" + - "created_at\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "created_at\x18\f \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + "\n" + - "updated_at\x18\f \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x12\x1d\n" + + "updated_at\x18\r \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x12\x1d\n" + "\n" + - "updated_by\x18\r \x01(\tR\tupdatedBy\x12;\n" + - "\vverified_at\x18\x0e \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "updated_by\x18\x0e \x01(\tR\tupdatedBy\x12;\n" + + "\vverified_at\x18\x0f \x01(\v2\x1a.google.protobuf.TimestampR\n" + "verifiedAt\x12#\n" + - "\rauth_provider\x18\x0f \x01(\tR\fauthProvider\x12 \n" + - "\vpermissions\x18\x10 \x03(\tR\vpermissions2Y\n" + + "\rauth_provider\x18\x10 \x01(\tR\fauthProvider\x12 \n" + + "\vpermissions\x18\x11 \x03(\tR\vpermissions2Y\n" + "\fUsersService\x12I\n" + "\rRetrieveUsers\x12\x1a.users.v1.RetrieveUsersReq\x1a\x1a.users.v1.RetrieveUsersRes\"\x00B.Z,github.com/absmach/supermq/api/grpc/users/v1b\x06proto3" @@ -380,17 +389,18 @@ var file_users_v1_users_proto_goTypes = []any{ } var file_users_v1_users_proto_depIdxs = []int32{ 2, // 0: users.v1.RetrieveUsersRes.users:type_name -> users.v1.User - 3, // 1: users.v1.User.metadata:type_name -> google.protobuf.Struct - 4, // 2: users.v1.User.created_at:type_name -> google.protobuf.Timestamp - 4, // 3: users.v1.User.updated_at:type_name -> google.protobuf.Timestamp - 4, // 4: users.v1.User.verified_at:type_name -> google.protobuf.Timestamp - 0, // 5: users.v1.UsersService.RetrieveUsers:input_type -> users.v1.RetrieveUsersReq - 1, // 6: users.v1.UsersService.RetrieveUsers:output_type -> users.v1.RetrieveUsersRes - 6, // [6:7] is the sub-list for method output_type - 5, // [5:6] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 3, // 1: users.v1.User.public_metadata:type_name -> google.protobuf.Struct + 3, // 2: users.v1.User.private_metadata:type_name -> google.protobuf.Struct + 4, // 3: users.v1.User.created_at:type_name -> google.protobuf.Timestamp + 4, // 4: users.v1.User.updated_at:type_name -> google.protobuf.Timestamp + 4, // 5: users.v1.User.verified_at:type_name -> google.protobuf.Timestamp + 0, // 6: users.v1.UsersService.RetrieveUsers:input_type -> users.v1.RetrieveUsersReq + 1, // 7: users.v1.UsersService.RetrieveUsers:output_type -> users.v1.RetrieveUsersRes + 7, // [7:8] is the sub-list for method output_type + 6, // [6:7] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_users_v1_users_proto_init() } diff --git a/apidocs/openapi/clients.yaml b/apidocs/openapi/clients.yaml index 1ff100e460..2b0856bd96 100644 --- a/apidocs/openapi/clients.yaml +++ b/apidocs/openapi/clients.yaml @@ -943,10 +943,14 @@ components: example: bb7edb32-2eac-4aad-aebe-ed96fe073879 minimum: 8 description: Free-form account secret used for acquiring auth token(s). - metadata: + public_metadata: type: object example: { "model": "example" } description: Arbitrary, object-encoded client's data. + private_metadata: + type: object + example: { "model": "example" } + description: Arbitrary, object-encoded client's data, private to the client. status: type: string description: Client Status @@ -1001,10 +1005,14 @@ components: type: string example: bb7edb32-2eac-4aad-aebe-ed96fe073879 description: Client secret password. - metadata: + public_metadata: type: object example: { "model": "example" } description: Arbitrary, object-encoded client's data. + private_metadata: + type: object + example: { "model": "example" } + description: Arbitrary, object-encoded client's data, private to the client. status: type: string description: Client Status @@ -1058,10 +1066,14 @@ components: type: string example: "" description: Client secret password. - metadata: + public_metadata: type: object example: { "model": "example" } description: Arbitrary, object-encoded client's data. + private_metadata: + type: object + example: { "model": "example" } + description: Arbitrary, object-encoded client's data, private to the client. status: type: string description: Client Status @@ -1110,13 +1122,18 @@ components: type: string example: clientName description: Client name. - metadata: + public_metadata: type: object example: { "role": "general" } description: Arbitrary, object-encoded client's data. + private_metadata: + type: object + example: { "role": "general" } + description: Arbitrary, object-encoded client's data, private to the client. required: - name - - metadata + - public_metadata + - private_metadata ClientTags: type: object diff --git a/apidocs/openapi/users.yaml b/apidocs/openapi/users.yaml index b2226eb60a..8cad020972 100644 --- a/apidocs/openapi/users.yaml +++ b/apidocs/openapi/users.yaml @@ -711,10 +711,14 @@ components: required: - username - secret - metadata: + public_metadata: type: object example: { "domain": "example.com" } description: Arbitrary, object-encoded user's data. + private_metadata: + type: object + example: { "domain": "example.com" } + description: Arbitrary, object-encoded user's data private to the user. profile_picture: type: string example: "https://example.com/profile.jpg" @@ -767,10 +771,14 @@ components: type: string example: john_doe description: User's username for example john_doe for Mr John Doe. - metadata: + public_metadata: type: object example: { "address": "example" } description: Arbitrary, object-encoded user's data. + private_metadata: + type: object + example: { "address": "example" } + description: Arbitrary, object-encoded user's data private to the user. profile_picture: type: string example: "https://example.com/profile.jpg" @@ -891,14 +899,19 @@ components: type: string example: lastName description: User's last name. - metadata: + public_metadata: type: object example: { "role": "general" } description: Arbitrary, object-encoded user's data. + private_metadata: + type: object + example: { "role": "general" } + description: Arbitrary, object-encoded user's data, private to the user. required: - first_name - last_name - - metadata + - public_metadata + - private_metadata UserTags: type: object @@ -1257,7 +1270,7 @@ components: Metadata: name: metadata - description: Metadata filter. Filtering is performed matching the parameter with metadata on top level. Parameter is json. + description: Metadata filter. Filtering is performed matching the parameter with public metadata on top level. Parameter is json. in: query schema: type: object @@ -1339,7 +1352,7 @@ components: $ref: "#/components/schemas/UserReqObj" UserUpdateReq: - description: JSON-formated document describing the metadata and name of user to be update + description: JSON-formated document describing the name, public and private metadata of user to be update required: true content: application/json: diff --git a/cli/clients_test.go b/cli/clients_test.go index 9f8c799e34..f28ae4d187 100644 --- a/cli/clients_test.go +++ b/cli/clients_test.go @@ -306,7 +306,7 @@ func TestUpdateClientCmd(t *testing.T) { logType outputLog }{ { - desc: "update client name and metadata successfully", + desc: "update client name and public metadata successfully", args: []string{ client.ID, updateCmd, @@ -316,10 +316,28 @@ func TestUpdateClientCmd(t *testing.T) { }, client: smqsdk.Client{ Name: "clientName", - Metadata: map[string]any{ - "metadata": map[string]any{ - "role": "general", - }, + PublicMetadata: map[string]any{ + "role": "general", + }, + ID: client.ID, + DomainID: client.DomainID, + Status: client.Status, + }, + logType: entityLog, + }, + { + desc: "update client name and private metadata successfully", + args: []string{ + client.ID, + updateCmd, + newNameandMeta, + domainID, + token, + }, + client: smqsdk.Client{ + Name: "clientName", + PrivateMetadata: map[string]any{ + "role": "general", }, ID: client.ID, DomainID: client.DomainID, diff --git a/cli/users_test.go b/cli/users_test.go index 2e2dbc52fe..6ab8a9c566 100644 --- a/cli/users_test.go +++ b/cli/users_test.go @@ -708,7 +708,7 @@ Available update options: case len(tc.args) == 4: // Basic user update sdkCall = sdkMock.On("UpdateUser", mock.Anything, mgsdk.User{ FirstName: "new name", - Metadata: mgsdk.Metadata{ + PublicMetadata: mgsdk.Metadata{ "key": "value", }, }, tc.args[3]).Return(tc.user, tc.sdkErr) diff --git a/clients/api/http/endpoints.go b/clients/api/http/endpoints.go index 2bd6c7dc74..80ca580a0b 100644 --- a/clients/api/http/endpoints.go +++ b/clients/api/http/endpoints.go @@ -143,9 +143,10 @@ func updateClientEndpoint(svc clients.Service) endpoint.Endpoint { } cli := clients.Client{ - ID: req.id, - Name: req.Name, - Metadata: req.Metadata, + ID: req.id, + Name: req.Name, + PublicMetadata: req.PublicMetadata, + PrivateMetadata: req.PrivateMetadata, } client, err := svc.Update(ctx, session, cli) if err != nil { diff --git a/clients/api/http/endpoints_test.go b/clients/api/http/endpoints_test.go index fe3eb6ecb8..08b9ebe712 100644 --- a/clients/api/http/endpoints_test.go +++ b/clients/api/http/endpoints_test.go @@ -32,16 +32,17 @@ import ( ) var ( - secret = "strongsecret" - validCMetadata = clients.Metadata{"role": "client"} - ID = testsutil.GenerateUUID(&testing.T{}) - client = clients.Client{ - ID: ID, - Name: "clientname", - Tags: []string{"tag1", "tag2"}, - Credentials: clients.Credentials{Identity: "clientidentity", Secret: secret}, - Metadata: validCMetadata, - Status: clients.EnabledStatus, + secret = "strongsecret" + validMetadata = clients.Metadata{"role": "client"} + ID = testsutil.GenerateUUID(&testing.T{}) + client = clients.Client{ + ID: ID, + Name: "clientname", + Tags: []string{"tag1", "tag2"}, + Credentials: clients.Credentials{Identity: "clientidentity", Secret: secret}, + PublicMetadata: validMetadata, + PrivateMetadata: validMetadata, + Status: clients.EnabledStatus, } validToken = "token" inValidToken = "invalid" @@ -170,7 +171,7 @@ func TestCreateClient(t *testing.T) { Identity: "user@example.com", Secret: "12345678", }, - Metadata: map[string]any{ + PublicMetadata: map[string]any{ "test": make(chan int), }, }, @@ -260,8 +261,9 @@ func TestCreateClients(t *testing.T) { Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()), Secret: secret, }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: clients.Metadata{}, + PrivateMetadata: clients.Metadata{}, + Status: clients.EnabledStatus, } items = append(items, client) } @@ -362,7 +364,7 @@ func TestCreateClients(t *testing.T) { Identity: "user@example.com", Secret: "12345678", }, - Metadata: map[string]any{ + PublicMetadata: map[string]any{ "test": make(chan int), }, }, @@ -870,14 +872,14 @@ func TestUpdateClient(t *testing.T) { domainID: domainID, id: client.ID, authnRes: smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"public_metadata":%s}`, newName, newTag, toJSON(newMetadata)), token: validToken, contentType: contentType, clientResponse: clients.Client{ - ID: client.ID, - Name: newName, - Tags: []string{newTag}, - Metadata: newMetadata, + ID: client.ID, + Name: newName, + Tags: []string{newTag}, + PublicMetadata: newMetadata, }, status: http.StatusOK, @@ -886,7 +888,7 @@ func TestUpdateClient(t *testing.T) { { desc: "update client with invalid token", id: client.ID, - data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"public_metadata":%s}`, newName, newTag, toJSON(newMetadata)), domainID: domainID, token: inValidToken, contentType: contentType, @@ -897,7 +899,7 @@ func TestUpdateClient(t *testing.T) { { desc: "update client with empty token", id: client.ID, - data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"public_metadata":%s}`, newName, newTag, toJSON(newMetadata)), domainID: domainID, token: "", contentType: contentType, @@ -907,7 +909,7 @@ func TestUpdateClient(t *testing.T) { { desc: "update client with invalid contentype", id: client.ID, - data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"public_metadata":%s}`, newName, newTag, toJSON(newMetadata)), domainID: domainID, token: validToken, authnRes: smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -929,7 +931,7 @@ func TestUpdateClient(t *testing.T) { { desc: "update client with empty id", id: " ", - data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"public_metadata":%s}`, newName, newTag, toJSON(newMetadata)), domainID: domainID, token: validToken, authnRes: smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -941,7 +943,7 @@ func TestUpdateClient(t *testing.T) { desc: "update client with name that is too long", id: client.ID, authnRes: smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, strings.Repeat("a", api.MaxNameSize+1), newTag, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"public_metadata":%s}`, strings.Repeat("a", api.MaxNameSize+1), newTag, toJSON(newMetadata)), domainID: domainID, token: validToken, contentType: contentType, diff --git a/clients/api/http/requests.go b/clients/api/http/requests.go index d42e206164..29faf89857 100644 --- a/clients/api/http/requests.go +++ b/clients/api/http/requests.go @@ -112,10 +112,11 @@ func (req listMembersReq) validate() error { } type updateClientReq struct { - id string - Name string `json:"name,omitempty"` - Metadata map[string]any `json:"metadata,omitempty"` - Tags []string `json:"tags,omitempty"` + id string + Name string `json:"name,omitempty"` + PublicMetadata map[string]any `json:"public_metadata,omitempty"` + PrivateMetadata map[string]any `json:"private_metadata,omitempty"` + Tags []string `json:"tags,omitempty"` } func (req updateClientReq) validate() error { diff --git a/clients/clients.go b/clients/clients.go index be9c1031bd..a3bb96b9cb 100644 --- a/clients/clients.go +++ b/clients/clients.go @@ -153,18 +153,19 @@ type Cache interface { // Client Struct represents a client. type Client struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Tags []string `json:"tags,omitempty"` - Domain string `json:"domain_id,omitempty"` - ParentGroup string `json:"parent_group_id,omitempty"` - Credentials Credentials `json:"credentials,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - UpdatedBy string `json:"updated_by,omitempty"` - Status Status `json:"status,omitempty"` // 1 for enabled, 0 for disabled - Identity string `json:"identity,omitempty"` + ID string `json:"id"` + Name string `json:"name,omitempty"` + Tags []string `json:"tags,omitempty"` + Domain string `json:"domain_id,omitempty"` + ParentGroup string `json:"parent_group_id,omitempty"` + Credentials Credentials `json:"credentials,omitempty"` + PublicMetadata Metadata `json:"public_metadata,omitempty"` + PrivateMetadata Metadata `json:"private_metadata,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + Status Status `json:"status,omitempty"` // 1 for enabled, 0 for disabled + Identity string `json:"identity,omitempty"` // Extended ParentGroupPath string `json:"parent_group_path,omitempty"` RoleID string `json:"role_id,omitempty"` diff --git a/clients/events/events.go b/clients/events/events.go index 7510b5e832..6f712b1f5e 100644 --- a/clients/events/events.go +++ b/clients/events/events.go @@ -73,8 +73,8 @@ func (cce createClientEvent) Encode() (map[string]any, error) { if len(cce.Tags) > 0 { val["tags"] = cce.Tags } - if cce.Metadata != nil { - val["metadata"] = cce.Metadata + if cce.PublicMetadata != nil { + val["public_metadata"] = cce.PublicMetadata } if cce.Credentials.Identity != "" { val["identity"] = cce.Credentials.Identity @@ -113,8 +113,8 @@ func (uce updateClientEvent) Encode() (map[string]any, error) { if uce.Credentials.Identity != "" { val["identity"] = uce.Credentials.Identity } - if uce.Metadata != nil { - val["metadata"] = uce.Metadata + if uce.PublicMetadata != nil { + val["public_metadata"] = uce.PublicMetadata } if !uce.CreatedAt.IsZero() { val["created_at"] = uce.CreatedAt @@ -177,8 +177,8 @@ func (vce viewClientEvent) Encode() (map[string]any, error) { if vce.Credentials.Identity != "" { val["identity"] = vce.Credentials.Identity } - if vce.Metadata != nil { - val["metadata"] = vce.Metadata + if vce.PublicMetadata != nil { + val["public_metadata"] = vce.PublicMetadata } if !vce.CreatedAt.IsZero() { val["created_at"] = vce.CreatedAt diff --git a/clients/middleware/logging.go b/clients/middleware/logging.go index 346c89bf9d..bd20ef0ea5 100644 --- a/clients/middleware/logging.go +++ b/clients/middleware/logging.go @@ -126,7 +126,7 @@ func (lm *loggingMiddleware) Update(ctx context.Context, session authn.Session, slog.Group("client", slog.String("id", client.ID), slog.String("name", client.Name), - slog.Any("metadata", client.Metadata), + slog.Any("public_metadata", client.PublicMetadata), ), } if err != nil { diff --git a/clients/postgres/clients.go b/clients/postgres/clients.go index 318037e37e..e9aa30363f 100644 --- a/clients/postgres/clients.go +++ b/clients/postgres/clients.go @@ -64,9 +64,9 @@ func (repo *clientRepo) Save(ctx context.Context, cls ...clients.Client) ([]clie } dbClients = append(dbClients, dbcli) } - q := `INSERT INTO clients (id, name, tags, domain_id, parent_group_id, identity, secret, metadata, created_at, updated_at, updated_by, status) - VALUES (:id, :name, :tags, :domain_id, :parent_group_id, :identity, :secret, :metadata, :created_at, :updated_at, :updated_by, :status) - RETURNING id, name, tags, identity, secret, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` + q := `INSERT INTO clients (id, name, tags, domain_id, parent_group_id, identity, secret, public_metadata, private_metadata, created_at, updated_at, updated_by, status) + VALUES (:id, :name, :tags, :domain_id, :parent_group_id, :identity, :secret, :public_metadata, :private_metadata, :created_at, :updated_at, :updated_by, :status) + RETURNING id, name, tags, identity, secret, public_metadata, private_metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` row, err := repo.DB.NamedQueryContext(ctx, q, dbClients) if err != nil { @@ -92,7 +92,7 @@ func (repo *clientRepo) Save(ctx context.Context, cls ...clients.Client) ([]clie } func (repo *clientRepo) RetrieveBySecret(ctx context.Context, key, id string, prefix authn.AuthPrefix) (clients.Client, error) { - q := fmt.Sprintf(`SELECT id, name, tags, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, identity, secret, metadata, created_at, updated_at, updated_by, status + q := fmt.Sprintf(`SELECT id, name, tags, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, identity, secret, public_metadata, private_metadata, created_at, updated_at, updated_by, status FROM clients WHERE secret = :secret AND status = %d`, clients.EnabledStatus) switch prefix { @@ -139,8 +139,11 @@ func (repo *clientRepo) Update(ctx context.Context, client clients.Client) (clie if client.Name != "" { query = append(query, "name = :name,") } - if client.Metadata != nil { - query = append(query, "metadata = :metadata,") + if client.PublicMetadata != nil { + query = append(query, "public_metadata = :public_metadata,") + } + if client.PrivateMetadata != nil { + query = append(query, "private_metadata = :private_metadata,") } if len(query) > 0 { upq = strings.Join(query, " ") @@ -148,7 +151,7 @@ func (repo *clientRepo) Update(ctx context.Context, client clients.Client) (clie q := fmt.Sprintf(`UPDATE clients SET %s updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, secret, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by`, + RETURNING id, name, tags, identity, secret, public_metadata, private_metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by`, upq) client.Status = clients.EnabledStatus return repo.update(ctx, client, q) @@ -157,7 +160,7 @@ func (repo *clientRepo) Update(ctx context.Context, client clients.Client) (clie func (repo *clientRepo) UpdateTags(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET tags = :tags, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` + RETURNING id, name, tags, identity, public_metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } @@ -165,7 +168,7 @@ func (repo *clientRepo) UpdateTags(ctx context.Context, client clients.Client) ( func (repo *clientRepo) UpdateIdentity(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET identity = :identity, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, COALESCE(parent_group_id, '') AS parent_group_id, created_at, updated_at, updated_by` + RETURNING id, name, tags, identity, public_metadata, COALESCE(domain_id, '') AS domain_id, status, COALESCE(parent_group_id, '') AS parent_group_id, created_at, updated_at, updated_by` client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } @@ -173,7 +176,7 @@ func (repo *clientRepo) UpdateIdentity(ctx context.Context, client clients.Clien func (repo *clientRepo) UpdateSecret(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET secret = :secret, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` + RETURNING id, name, tags, identity, public_metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` client.Status = clients.EnabledStatus return repo.update(ctx, client, q) } @@ -181,7 +184,7 @@ func (repo *clientRepo) UpdateSecret(ctx context.Context, client clients.Client) func (repo *clientRepo) ChangeStatus(ctx context.Context, client clients.Client) (clients.Client, error) { q := `UPDATE clients SET status = :status, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id - RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` + RETURNING id, name, tags, identity, public_metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` return repo.update(ctx, client, q) } @@ -353,7 +356,7 @@ func (repo *clientRepo) RetrieveByIDWithRoles(ctx context.Context, id, memberID COALESCE(c2.parent_group_id, '') AS parent_group_id, c2."identity", c2.secret, - c2.metadata, + c2.public_metadata, c2.created_at, c2.updated_at, c2.updated_by, @@ -386,7 +389,7 @@ func (repo *clientRepo) RetrieveByIDWithRoles(ctx context.Context, id, memberID } func (repo *clientRepo) RetrieveByID(ctx context.Context, id string) (clients.Client, error) { - q := `SELECT id, name, tags, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, identity, secret, metadata, created_at, updated_at, updated_by, status + q := `SELECT id, name, tags, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, identity, secret, public_metadata, private_metadata, created_at, updated_at, updated_by, status FROM clients WHERE id = :id` dbc := DBClient{ @@ -446,7 +449,7 @@ func (repo *clientRepo) RetrieveAll(ctx context.Context, pm clients.Page) (clien c.name, c.tags, c.identity, - c.metadata, + c.public_metadata, COALESCE(c.domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, COALESCE((SELECT path FROM groups WHERE id = c.parent_group_id), ''::::ltree) AS parent_group_path, @@ -561,7 +564,7 @@ func (repo *clientRepo) retrieveClients(ctx context.Context, domainID, userID st c.identity, c.secret, c.tags, - c.metadata, + c.public_metadata, c.created_at, c.updated_at, c.updated_by, @@ -622,7 +625,7 @@ func (repo *clientRepo) retrieveClients(ctx context.Context, domainID, userID st c.identity, c.secret, c.tags, - c.metadata, + c.public_metadata, c.created_at, c.updated_at, c.updated_by, @@ -667,7 +670,7 @@ func (repo *clientRepo) userClientBaseQuery(domainID, userID string) string { c.domain_id, c.parent_group_id, c.tags, - c.metadata, + c.public_metadata, c.identity, c.secret, c.created_at, @@ -832,7 +835,7 @@ func (repo *clientRepo) userClientBaseQuery(domainID, userID string) string { c.domain_id, c.parent_group_id, c.tags, - c.metadata, + c.public_metadata, c.identity, c.secret, c.created_at, @@ -864,7 +867,7 @@ func (repo *clientRepo) userClientBaseQuery(domainID, userID string) string { gc.domain_id, gc.parent_group_id, gc.tags, - gc.metadata, + gc.public_metadata, gc.identity, gc.secret, gc.created_at, @@ -889,7 +892,7 @@ func (repo *clientRepo) userClientBaseQuery(domainID, userID string) string { dc.domain_id, dc.parent_group_id, dc.tags, - dc.metadata, + dc.public_metadata, dc.identity, dc.secret, dc.created_at, @@ -1035,7 +1038,8 @@ type DBClient struct { Domain string `db:"domain_id"` ParentGroup sql.NullString `db:"parent_group_id,omitempty"` Secret string `db:"secret"` - Metadata []byte `db:"metadata,omitempty"` + PublicMetadata []byte `db:"public_metadata,omitempty"` + PrivateMetadata []byte `db:"private_metadata,omitempty"` CreatedAt time.Time `db:"created_at,omitempty"` UpdatedAt sql.NullTime `db:"updated_at,omitempty"` UpdatedBy *string `db:"updated_by,omitempty"` @@ -1055,13 +1059,21 @@ type DBClient struct { } func ToDBClient(c clients.Client) (DBClient, error) { - data := []byte("{}") - if len(c.Metadata) > 0 { - b, err := json.Marshal(c.Metadata) + publicMetadata := []byte("{}") + if len(c.PublicMetadata) > 0 { + b, err := json.Marshal(c.PublicMetadata) if err != nil { return DBClient{}, errors.Wrap(repoerr.ErrMalformedEntity, err) } - data = b + publicMetadata = b + } + privateMetadata := []byte("{}") + if len(c.PrivateMetadata) > 0 { + b, err := json.Marshal(c.PrivateMetadata) + if err != nil { + return DBClient{}, errors.Wrap(repoerr.ErrMalformedEntity, err) + } + privateMetadata = b } var tags pgtype.TextArray if err := tags.Set(c.Tags); err != nil { @@ -1077,26 +1089,32 @@ func ToDBClient(c clients.Client) (DBClient, error) { } return DBClient{ - ID: c.ID, - Name: c.Name, - Tags: tags, - Domain: c.Domain, - ParentGroup: toNullString(c.ParentGroup), - Identity: c.Credentials.Identity, - Secret: c.Credentials.Secret, - Metadata: data, - CreatedAt: c.CreatedAt, - UpdatedAt: updatedAt, - UpdatedBy: updatedBy, - Status: c.Status, + ID: c.ID, + Name: c.Name, + Tags: tags, + Domain: c.Domain, + ParentGroup: toNullString(c.ParentGroup), + Identity: c.Credentials.Identity, + Secret: c.Credentials.Secret, + PublicMetadata: publicMetadata, + PrivateMetadata: privateMetadata, + CreatedAt: c.CreatedAt, + UpdatedAt: updatedAt, + UpdatedBy: updatedBy, + Status: c.Status, }, nil } func ToClient(t DBClient) (clients.Client, error) { - var metadata clients.Metadata - if t.Metadata != nil { - if err := json.Unmarshal(t.Metadata, &metadata); err != nil { - return clients.Client{}, errors.Wrap(errors.ErrMalformedEntity, err) + var publicMetadata, privateMetadata clients.Metadata + if t.PublicMetadata != nil { + if err := json.Unmarshal([]byte(t.PublicMetadata), &publicMetadata); err != nil { + return clients.Client{}, errors.Wrap(repoerr.ErrMalformedEntity, err) + } + } + if t.PrivateMetadata != nil { + if err := json.Unmarshal([]byte(t.PrivateMetadata), &privateMetadata); err != nil { + return clients.Client{}, errors.Wrap(repoerr.ErrMalformedEntity, err) } } @@ -1141,7 +1159,8 @@ func ToClient(t DBClient) (clients.Client, error) { Identity: t.Identity, Secret: t.Secret, }, - Metadata: metadata, + PublicMetadata: publicMetadata, + PrivateMetadata: privateMetadata, CreatedAt: t.CreatedAt.UTC(), UpdatedAt: updatedAt, UpdatedBy: updatedBy, @@ -1206,11 +1225,6 @@ type dbClientsPage struct { } func PageQuery(pm clients.Page) (string, error) { - mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata) - if err != nil { - return "", errors.Wrap(errors.ErrMalformedEntity, err) - } - var query []string if pm.Name != "" { query = append(query, "c.name ILIKE '%' || :name || '%'") @@ -1225,10 +1239,6 @@ func PageQuery(pm clients.Page) (string, error) { query = append(query, "EXISTS (SELECT 1 FROM unnest(tags) AS tag WHERE tag ILIKE '%' || :tag || '%')") } - if mq != "" { - query = append(query, mq) - } - if len(pm.IDs) != 0 { query = append(query, fmt.Sprintf("c.id IN ('%s')", strings.Join(pm.IDs, "','"))) } @@ -1268,7 +1278,7 @@ func PageQuery(pm clients.Page) (string, error) { query = append(query, "c.actions @> :actions") } if len(pm.Metadata) > 0 { - query = append(query, "c.metadata @> :metadata") + query = append(query, "c.public_metadata @> :metadata") } var emq string @@ -1333,7 +1343,7 @@ func (repo *clientRepo) RetrieveByIds(ctx context.Context, ids []string) (client return clients.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) } - q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, c.status, + q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.public_metadata, COALESCE(c.domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, c.status, c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM clients c %s ORDER BY c.created_at`, query) dbPage, err := ToDBClientsPage(pm) @@ -1498,7 +1508,7 @@ func (repo *clientRepo) RemoveClientConnections(ctx context.Context, clientID st } func (repo *clientRepo) RetrieveParentGroupClients(ctx context.Context, parentGroupID string) ([]clients.Client, error) { - query := `SELECT c.id, c.name, c.tags, c.metadata, COALESCE(c.domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, c.status, + query := `SELECT c.id, c.name, c.tags, c.public_metadata, COALESCE(c.domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, c.status, c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM clients c WHERE c.parent_group_id = :parent_group_id ;` rows, err := repo.DB.NamedQueryContext(ctx, query, DBClient{ParentGroup: toNullString(parentGroupID)}) diff --git a/clients/postgres/clients_test.go b/clients/postgres/clients_test.go index 724250e74e..5c6cdd2438 100644 --- a/clients/postgres/clients_test.go +++ b/clients/postgres/clients_test.go @@ -43,12 +43,13 @@ var ( namegen = namegenerator.NewGenerator() validTimestamp = time.Now().UTC().Truncate(time.Millisecond) validClient = clients.Client{ - ID: testsutil.GenerateUUID(&testing.T{}), - Domain: testsutil.GenerateUUID(&testing.T{}), - Name: namegen.Generate(), - Metadata: map[string]any{"key": "value"}, - CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: clients.EnabledStatus, + ID: testsutil.GenerateUUID(&testing.T{}), + Domain: testsutil.GenerateUUID(&testing.T{}), + Name: namegen.Generate(), + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + CreatedAt: time.Now().UTC().Truncate(time.Microsecond), + Status: clients.EnabledStatus, } invalidID = strings.Repeat("a", 37) directAccess = "direct" @@ -133,8 +134,9 @@ func TestClientsSave(t *testing.T) { Identity: clientIdentity, Secret: secret, }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, }, err: nil, @@ -149,8 +151,9 @@ func TestClientsSave(t *testing.T) { Credentials: clients.Credentials{ Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, { ID: testsutil.GenerateUUID(t), @@ -159,8 +162,9 @@ func TestClientsSave(t *testing.T) { Credentials: clients.Credentials{ Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, { ID: testsutil.GenerateUUID(t), @@ -169,8 +173,9 @@ func TestClientsSave(t *testing.T) { Credentials: clients.Credentials{ Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, }, err: nil, @@ -186,8 +191,9 @@ func TestClientsSave(t *testing.T) { Identity: clientIdentity, Secret: secret, }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, }, err: errClientSecretNotAvailable, @@ -202,8 +208,9 @@ func TestClientsSave(t *testing.T) { Credentials: clients.Credentials{ Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, { ID: testsutil.GenerateUUID(t), @@ -213,8 +220,9 @@ func TestClientsSave(t *testing.T) { Identity: clientIdentity, Secret: secret, }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, }, err: errClientSecretNotAvailable, @@ -229,8 +237,9 @@ func TestClientsSave(t *testing.T) { Identity: "withoutdomain-client@example.com", Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, }, err: nil, @@ -246,8 +255,9 @@ func TestClientsSave(t *testing.T) { Identity: "invalidid-client@example.com", Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, }, err: repoerr.ErrCreateEntity, @@ -262,8 +272,9 @@ func TestClientsSave(t *testing.T) { Credentials: clients.Credentials{ Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, { ID: invalidName, @@ -272,8 +283,9 @@ func TestClientsSave(t *testing.T) { Credentials: clients.Credentials{ Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, }, err: repoerr.ErrCreateEntity, @@ -289,8 +301,9 @@ func TestClientsSave(t *testing.T) { Identity: "invalidname-client@example.com", Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, }, err: repoerr.ErrCreateEntity, @@ -305,8 +318,9 @@ func TestClientsSave(t *testing.T) { Identity: "invaliddomainid-client@example.com", Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, }, err: repoerr.ErrCreateEntity, @@ -321,8 +335,9 @@ func TestClientsSave(t *testing.T) { Identity: invalidName, Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + Status: clients.EnabledStatus, }, }, err: repoerr.ErrCreateEntity, @@ -338,7 +353,8 @@ func TestClientsSave(t *testing.T) { Identity: "", Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, }, }, err: nil, @@ -353,13 +369,31 @@ func TestClientsSave(t *testing.T) { Identity: "missing-client-secret@example.com", Secret: "", }, - Metadata: clients.Metadata{}, + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, }, }, err: nil, }, { - desc: "add a client with invalid metadata", + desc: "add a client with invalid public metadata", + clients: []clients.Client{ + { + ID: testsutil.GenerateUUID(t), + Name: namegen.Generate(), + Credentials: clients.Credentials{ + Identity: fmt.Sprintf("%s@example.com", namegen.Generate()), + Secret: testsutil.GenerateUUID(t), + }, + PublicMetadata: map[string]any{ + "key": make(chan int), + }, + }, + }, + err: errors.ErrMalformedEntity, + }, + { + desc: "add a client with invalid private metadata", clients: []clients.Client{ { ID: testsutil.GenerateUUID(t), @@ -368,7 +402,7 @@ func TestClientsSave(t *testing.T) { Identity: fmt.Sprintf("%s@example.com", namegen.Generate()), Secret: testsutil.GenerateUUID(t), }, - Metadata: map[string]any{ + PublicMetadata: map[string]any{ "key": make(chan int), }, }, @@ -379,12 +413,13 @@ func TestClientsSave(t *testing.T) { desc: "add client with duplicate name", clients: []clients.Client{ { - ID: duplicateClientID, - Domain: validClient.Domain, - Name: validClient.Name, - Metadata: map[string]any{"key": "different_value"}, - CreatedAt: validTimestamp, - Status: clients.EnabledStatus, + ID: duplicateClientID, + Domain: validClient.Domain, + Name: validClient.Name, + PublicMetadata: map[string]any{"key": "different_value"}, + PrivateMetadata: map[string]any{}, + CreatedAt: validTimestamp, + Status: clients.EnabledStatus, }, }, err: nil, @@ -418,9 +453,10 @@ func TestClientsRetrieveBySecret(t *testing.T) { Identity: clientIdentity, Secret: testsutil.GenerateUUID(t), }, - Domain: testsutil.GenerateUUID(t), - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + Domain: testsutil.GenerateUUID(t), + PublicMetadata: clients.Metadata{}, + PrivateMetadata: clients.Metadata{}, + Status: clients.EnabledStatus, } _, err := repo.Save(context.Background(), client) @@ -487,9 +523,11 @@ func TestClientsRetrieveBySecret(t *testing.T) { } for _, tc := range cases { - res, err := repo.RetrieveBySecret(context.Background(), tc.secret, tc.id, tc.prefix) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, res, tc.response, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, res)) + t.Run(tc.desc, func(t *testing.T) { + res, err := repo.RetrieveBySecret(context.Background(), tc.secret, tc.id, tc.prefix) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, res, tc.response, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, res)) + }) } } @@ -507,8 +545,13 @@ func TestRetrieveByID(t *testing.T) { Identity: clientIdentity, Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, + PublicMetadata: clients.Metadata{ + "key": "value", + }, + PrivateMetadata: clients.Metadata{ + "key": "value", + }, + Status: clients.EnabledStatus, } _, err := repo.Save(context.Background(), client) @@ -546,7 +589,8 @@ func TestRetrieveByID(t *testing.T) { if err == nil { assert.Equal(t, client.ID, cli.ID) assert.Equal(t, client.Name, cli.Name) - assert.Equal(t, client.Metadata, cli.Metadata) + assert.Equal(t, client.PublicMetadata, cli.PublicMetadata) + assert.Equal(t, client.PrivateMetadata, cli.PrivateMetadata) assert.Equal(t, client.Credentials.Identity, cli.Credentials.Identity) assert.Equal(t, client.Credentials.Secret, cli.Credentials.Secret) assert.Equal(t, client.Status, cli.Status) @@ -576,11 +620,12 @@ func TestUpdate(t *testing.T) { desc: "update client successfully", update: "all", client: clients.Client{ - ID: validClient.ID, - Name: namegen.Generate(), - Metadata: map[string]any{"key": "value"}, - UpdatedAt: validTimestamp, - UpdatedBy: testsutil.GenerateUUID(t), + ID: validClient.ID, + Name: namegen.Generate(), + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + UpdatedAt: validTimestamp, + UpdatedBy: testsutil.GenerateUUID(t), }, err: nil, }, @@ -596,13 +641,24 @@ func TestUpdate(t *testing.T) { err: nil, }, { - desc: "update client metadata", - update: "metadata", + desc: "update client public metadata", + update: "public_metadata", client: clients.Client{ - ID: validClient.ID, - Metadata: map[string]any{"key1": "value1"}, - UpdatedAt: validTimestamp, - UpdatedBy: testsutil.GenerateUUID(t), + ID: validClient.ID, + PublicMetadata: map[string]any{"key1": "value1"}, + UpdatedAt: validTimestamp, + UpdatedBy: testsutil.GenerateUUID(t), + }, + err: nil, + }, + { + desc: "update client private metadata", + update: "private_metadata", + client: clients.Client{ + ID: validClient.ID, + PrivateMetadata: map[string]any{"key1": "value1"}, + UpdatedAt: validTimestamp, + UpdatedBy: testsutil.GenerateUUID(t), }, err: nil, }, @@ -610,11 +666,12 @@ func TestUpdate(t *testing.T) { desc: "update client with invalid ID", update: "all", client: clients.Client{ - ID: testsutil.GenerateUUID(t), - Name: namegen.Generate(), - Metadata: map[string]any{"key": "value"}, - UpdatedAt: validTimestamp, - UpdatedBy: testsutil.GenerateUUID(t), + ID: testsutil.GenerateUUID(t), + Name: namegen.Generate(), + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + UpdatedAt: validTimestamp, + UpdatedBy: testsutil.GenerateUUID(t), }, err: repoerr.ErrNotFound, }, @@ -622,10 +679,11 @@ func TestUpdate(t *testing.T) { desc: "update client with empty ID", update: "all", client: clients.Client{ - Name: namegen.Generate(), - Metadata: map[string]any{"key": "value"}, - UpdatedAt: validTimestamp, - UpdatedBy: testsutil.GenerateUUID(t), + Name: namegen.Generate(), + PublicMetadata: map[string]any{"key": "value"}, + PrivateMetadata: map[string]any{"key": "value"}, + UpdatedAt: validTimestamp, + UpdatedBy: testsutil.GenerateUUID(t), }, err: repoerr.ErrNotFound, }, @@ -642,11 +700,14 @@ func TestUpdate(t *testing.T) { switch tc.update { case "all": assert.Equal(t, tc.client.Name, client.Name, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client.Name, client.Name)) - assert.Equal(t, tc.client.Metadata, client.Metadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client.Metadata, client.Metadata)) + assert.Equal(t, tc.client.PublicMetadata, client.PublicMetadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client.PublicMetadata, client.PublicMetadata)) + assert.Equal(t, tc.client.PrivateMetadata, client.PrivateMetadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client.PrivateMetadata, client.PrivateMetadata)) case "name": assert.Equal(t, tc.client.Name, client.Name, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client.Name, client.Name)) - case "metadata": - assert.Equal(t, tc.client.Metadata, client.Metadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client.Metadata, client.Metadata)) + case "public_metadata": + assert.Equal(t, tc.client.PublicMetadata, client.PublicMetadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client.PublicMetadata, client.PublicMetadata)) + case "private_metadata": + assert.Equal(t, tc.client.PrivateMetadata, client.PrivateMetadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client.PrivateMetadata, client.PrivateMetadata)) } } }) @@ -935,7 +996,7 @@ func TestRetrieveByIDsWithRoles(t *testing.T) { Secret: testsutil.GenerateUUID(t), }, Tags: namegen.GenerateMultiple(5), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ "department": namegen.Generate(), }, Status: clients.EnabledStatus, @@ -1057,7 +1118,7 @@ func TestRetrieveAll(t *testing.T) { Secret: testsutil.GenerateUUID(t), }, Tags: namegen.GenerateMultiple(5), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ "department": namegen.Generate(), }, Status: clients.EnabledStatus, @@ -1109,6 +1170,8 @@ func TestRetrieveAll(t *testing.T) { pm: clients.Page{ Offset: 50, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1122,16 +1185,18 @@ func TestRetrieveAll(t *testing.T) { { desc: "with limit only", pm: clients.Page{ - Limit: 50, + Limit: 10, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ Total: nClients, Offset: 0, - Limit: 50, + Limit: 10, }, - Clients: expectedClients[:50], + Clients: expectedClients[:10], }, }, { @@ -1140,6 +1205,8 @@ func TestRetrieveAll(t *testing.T) { Offset: 0, Limit: nClients, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1156,6 +1223,8 @@ func TestRetrieveAll(t *testing.T) { Offset: 50, Limit: 50, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1172,6 +1241,8 @@ func TestRetrieveAll(t *testing.T) { Offset: 1000, Limit: 50, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1188,6 +1259,8 @@ func TestRetrieveAll(t *testing.T) { Offset: 170, Limit: 50, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1199,12 +1272,14 @@ func TestRetrieveAll(t *testing.T) { }, }, { - desc: "with metadata", + desc: "with public metadata", pm: clients.Page{ Offset: 0, Limit: nClients, - Metadata: expectedClients[0].Metadata, + Metadata: expectedClients[0].PublicMetadata, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1224,6 +1299,8 @@ func TestRetrieveAll(t *testing.T) { "faculty": namegen.Generate(), }, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1243,6 +1320,8 @@ func TestRetrieveAll(t *testing.T) { "faculty": make(chan int), }, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1261,6 +1340,8 @@ func TestRetrieveAll(t *testing.T) { Limit: nClients, Name: expectedClients[0].Name, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1295,6 +1376,8 @@ func TestRetrieveAll(t *testing.T) { Limit: nClients, Identity: expectedClients[0].Credentials.Identity, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1329,6 +1412,8 @@ func TestRetrieveAll(t *testing.T) { Limit: nClients, Domain: expectedClients[0].Domain, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1346,6 +1431,8 @@ func TestRetrieveAll(t *testing.T) { Limit: nClients, Domain: testsutil.GenerateUUID(t), Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1362,6 +1449,8 @@ func TestRetrieveAll(t *testing.T) { Offset: 0, Limit: 10, Status: clients.EnabledStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1378,6 +1467,8 @@ func TestRetrieveAll(t *testing.T) { Offset: 0, Limit: nClients, Status: clients.DisabledStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1394,6 +1485,8 @@ func TestRetrieveAll(t *testing.T) { Offset: 0, Limit: nClients, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1427,6 +1520,8 @@ func TestRetrieveAll(t *testing.T) { Limit: nClients, Tag: expectedClients[0].Tags[0], Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1444,6 +1539,8 @@ func TestRetrieveAll(t *testing.T) { Limit: nClients, Tag: namegen.Generate(), Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1459,12 +1556,14 @@ func TestRetrieveAll(t *testing.T) { pm: clients.Page{ Offset: 0, Limit: nClients, - Metadata: expectedClients[0].Metadata, + Metadata: expectedClients[0].PublicMetadata, Name: expectedClients[0].Name, Tag: expectedClients[0].Tags[0], Identity: expectedClients[0].Credentials.Identity, Domain: expectedClients[0].Domain, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1482,6 +1581,8 @@ func TestRetrieveAll(t *testing.T) { Limit: nClients, ID: expectedClients[0].ID, Status: clients.AllStatus, + Order: defOrder, + Dir: defDir, }, response: clients.ClientsPage{ Page: clients.Page{ @@ -1579,7 +1680,7 @@ func TestRetrieveUserClients(t *testing.T) { Secret: testsutil.GenerateUUID(t), }, Tags: namegen.GenerateMultiple(5), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ "department": namegen.Generate(), }, Status: clients.EnabledStatus, @@ -1777,7 +1878,7 @@ func TestRetrieveUserClients(t *testing.T) { pm: clients.Page{ Offset: 0, Limit: nClients, - Metadata: directClients[0].Metadata, + Metadata: directClients[0].PublicMetadata, Status: clients.AllStatus, Order: defOrder, Dir: defDir, @@ -1836,7 +1937,7 @@ func TestRetrieveUserClients(t *testing.T) { }, Clients: []clients.Client(nil), }, - err: repoerr.ErrMalformedEntity, + err: repoerr.ErrViewEntity, }, { desc: "retrieve clients with name", @@ -1971,7 +2072,7 @@ func TestRetrieveUserClients(t *testing.T) { pm: clients.Page{ Offset: 0, Limit: nClients, - Metadata: directClients[0].Metadata, + Metadata: directClients[0].PublicMetadata, Name: directClients[0].Name, Tag: directClients[0].Tags[0], Identity: directClients[0].Credentials.Identity, @@ -2277,9 +2378,12 @@ func TestSearchClients(t *testing.T) { Identity: username, Secret: testsutil.GenerateUUID(t), }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, - CreatedAt: baseTime.Add(time.Duration(i) * time.Microsecond), + PublicMetadata: clients.Metadata{ + "department": namegen.Generate(), + }, + PrivateMetadata: clients.Metadata{}, + Status: clients.EnabledStatus, + CreatedAt: baseTime.Add(time.Duration(i) * time.Microsecond), } _, err := repo.Save(context.Background(), client) require.Nil(t, err, fmt.Sprintf("save client unexpected error: %s", err)) @@ -2658,10 +2762,10 @@ func TestRetrieveByIDs(t *testing.T) { Identity: name + emailSuffix, Secret: testsutil.GenerateUUID(t), }, - Tags: namegen.GenerateMultiple(5), - Metadata: map[string]any{"name": name}, - CreatedAt: baseTime.Add(time.Duration(i) * time.Microsecond), - Status: clients.EnabledStatus, + Tags: namegen.GenerateMultiple(5), + PublicMetadata: map[string]any{"name": name}, + CreatedAt: baseTime.Add(time.Duration(i) * time.Microsecond), + Status: clients.EnabledStatus, } _, err := repo.Save(context.Background(), client) require.Nil(t, err, fmt.Sprintf("add new client: expected nil got %s\n", err)) @@ -3286,13 +3390,13 @@ func TestRetrieveParentGroupClients(t *testing.T) { for i := 0; i < 10; i++ { name := namegen.Generate() client := clients.Client{ - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - ParentGroup: parentID, - Name: name, - Metadata: map[string]any{"name": name}, - CreatedAt: baseTime.Add(time.Duration(i) * time.Microsecond), - Status: clients.EnabledStatus, + ID: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), + ParentGroup: parentID, + Name: name, + PublicMetadata: map[string]any{"name": name}, + CreatedAt: baseTime.Add(time.Duration(i) * time.Microsecond), + Status: clients.EnabledStatus, } items = append(items, client) } @@ -3354,13 +3458,13 @@ func TestUnsetParentGroupFromClients(t *testing.T) { for i := 0; i < 10; i++ { name := namegen.Generate() client := clients.Client{ - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - ParentGroup: parentID, - Name: name, - Metadata: map[string]any{"name": name}, - CreatedAt: baseTime.Add(time.Duration(i) * time.Microsecond), - Status: clients.EnabledStatus, + ID: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), + ParentGroup: parentID, + Name: name, + PublicMetadata: map[string]any{"name": name}, + CreatedAt: baseTime.Add(time.Duration(i) * time.Microsecond), + Status: clients.EnabledStatus, } items = append(items, client) } @@ -3407,7 +3511,10 @@ func generateClient(t *testing.T, status clients.Status, repo clients.Repository Secret: testsutil.GenerateUUID(t), }, Tags: namegen.GenerateMultiple(5), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ + "name": namegen.Generate(), + }, + PrivateMetadata: clients.Metadata{ "name": namegen.Generate(), }, Status: status, diff --git a/clients/postgres/init.go b/clients/postgres/init.go index 610f1fd499..681e668739 100644 --- a/clients/postgres/init.go +++ b/clients/postgres/init.go @@ -76,6 +76,17 @@ func Migration() (*migrate.MemoryMigrationSource, error) { `ALTER TABLE clients ALTER COLUMN updated_at TYPE TIMESTAMP;`, }, }, + { + Id: "clients_04", + Up: []string{ + `ALTER TABLE clients RENAME COLUMN metadata TO private_metadata;`, + `ALTER TABLE clients ADD COLUMN public_metadata JSONB;`, + }, + Down: []string{ + `ALTER TABLE clients DROP COLUMN public_metadata;`, + `ALTER TABLE clients RENAME COLUMN private_metadata TO metadata;`, + }, + }, }, } diff --git a/clients/service.go b/clients/service.go index f2655063e2..5f4fe27d24 100644 --- a/clients/service.go +++ b/clients/service.go @@ -166,11 +166,12 @@ func (svc service) ListUserClients(ctx context.Context, session authn.Session, u func (svc service) Update(ctx context.Context, session authn.Session, cli Client) (Client, error) { client := Client{ - ID: cli.ID, - Name: cli.Name, - Metadata: cli.Metadata, - UpdatedAt: time.Now().UTC(), - UpdatedBy: session.UserID, + ID: cli.ID, + Name: cli.Name, + PublicMetadata: cli.PublicMetadata, + PrivateMetadata: cli.PrivateMetadata, + UpdatedAt: time.Now().UTC(), + UpdatedBy: session.UserID, } client, err := svc.repo.Update(ctx, client) if err != nil { diff --git a/clients/service_test.go b/clients/service_test.go index acbda32d5a..05e6a34f0f 100644 --- a/clients/service_test.go +++ b/clients/service_test.go @@ -29,24 +29,26 @@ import ( ) var ( - secret = "strongsecret" - validTMetadata = clients.Metadata{"role": "client"} - ID = "6e5e10b3-d4df-4758-b426-4929d55ad740" - client = clients.Client{ - ID: ID, - Name: "clientname", - Tags: []string{"tag1", "tag2"}, - Credentials: clients.Credentials{Identity: "clientidentity", Secret: secret}, - Metadata: validTMetadata, - Status: clients.EnabledStatus, + secret = "strongsecret" + validMetadata = clients.Metadata{"role": "client"} + ID = "6e5e10b3-d4df-4758-b426-4929d55ad740" + client = clients.Client{ + ID: ID, + Name: "clientname", + Tags: []string{"tag1", "tag2"}, + Credentials: clients.Credentials{Identity: "clientidentity", Secret: secret}, + PublicMetadata: validMetadata, + PrivateMetadata: validMetadata, + Status: clients.EnabledStatus, } clientWithRoles = clients.Client{ - ID: ID, - Name: "clientname", - Tags: []string{"tag1", "tag2"}, - Credentials: clients.Credentials{Identity: "clientidentity", Secret: secret}, - Metadata: validTMetadata, - Status: clients.EnabledStatus, + ID: ID, + Name: "clientname", + Tags: []string{"tag1", "tag2"}, + Credentials: clients.Credentials{Identity: "clientidentity", Secret: secret}, + PublicMetadata: validMetadata, + PrivateMetadata: validMetadata, + Status: clients.EnabledStatus, Roles: []roles.MemberRoleActions{ { RoleID: "test_role_id", @@ -187,26 +189,39 @@ func TestCreateClients(t *testing.T) { err: nil, }, { - desc: "create a new enabled client with metadata", + desc: "create a new enabled client with public metadata", client: clients.Client{ Credentials: clients.Credentials{ Identity: "newclientwithmetadata@example.com", Secret: secret, }, - Metadata: validTMetadata, - Status: clients.EnabledStatus, + PublicMetadata: validMetadata, + Status: clients.EnabledStatus, }, token: validToken, err: nil, }, { - desc: "create a new disabled client with metadata", + desc: "create a new enabled client with private metadata", client: clients.Client{ Credentials: clients.Credentials{ Identity: "newclientwithmetadata@example.com", Secret: secret, }, - Metadata: validTMetadata, + PrivateMetadata: validMetadata, + Status: clients.EnabledStatus, + }, + token: validToken, + err: nil, + }, + { + desc: "create a new disabled client with public metadata", + client: clients.Client{ + Credentials: clients.Credentials{ + Identity: "newclientwithmetadata@example.com", + Secret: secret, + }, + PublicMetadata: validMetadata, }, token: validToken, err: nil, @@ -243,7 +258,10 @@ func TestCreateClients(t *testing.T) { Identity: "newclientwithallfields@example.com", Secret: secret, }, - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ + "name": "newclientwithallfields", + }, + PrivateMetadata: clients.Metadata{ "name": "newclientwithallfields", }, Status: clients.EnabledStatus, @@ -592,7 +610,8 @@ func TestUpdateClient(t *testing.T) { client1 := client client2 := client client1.Name = "Updated client" - client2.Metadata = clients.Metadata{"role": "test"} + client2.PublicMetadata = clients.Metadata{"role": "test"} + client2.PrivateMetadata = clients.Metadata{"role": "test"} cases := []struct { desc string diff --git a/cmd/users/main.go b/cmd/users/main.go index 78eceda6ba..9661c591fd 100644 --- a/cmd/users/main.go +++ b/cmd/users/main.go @@ -360,7 +360,7 @@ func createAdmin(ctx context.Context, c config, repo users.Repository, hsr users Username: "admin", Secret: hash, }, - Metadata: users.Metadata{ + PrivateMetadata: users.Metadata{ "role": "admin", }, CreatedAt: time.Now().UTC(), diff --git a/internal/proto/users/v1/users.proto b/internal/proto/users/v1/users.proto index aa5703389a..ad125770cc 100644 --- a/internal/proto/users/v1/users.proto +++ b/internal/proto/users/v1/users.proto @@ -34,16 +34,17 @@ message User { string first_name = 2; string last_name = 3; repeated string tags = 4; - google.protobuf.Struct metadata = 5; - uint32 status = 6; - uint32 role = 7; - string profile_picture = 8; - string username = 9; - string email = 10; - google.protobuf.Timestamp created_at = 11; - google.protobuf.Timestamp updated_at = 12; - string updated_by = 13; - google.protobuf.Timestamp verified_at = 14; - string auth_provider = 15; - repeated string permissions = 16; + google.protobuf.Struct public_metadata = 5; + google.protobuf.Struct private_metadata = 6; + uint32 status = 7; + uint32 role = 8; + string profile_picture = 9; + string username = 10; + string email = 11; + google.protobuf.Timestamp created_at = 12; + google.protobuf.Timestamp updated_at = 13; + string updated_by = 14; + google.protobuf.Timestamp verified_at = 15; + string auth_provider = 16; + repeated string permissions = 17; } diff --git a/pkg/clients/events/consumer/decode.go b/pkg/clients/events/consumer/decode.go index 3e0f6d629f..78d263c591 100644 --- a/pkg/clients/events/consumer/decode.go +++ b/pkg/clients/events/consumer/decode.go @@ -90,9 +90,9 @@ func ToClient(data map[string]any) (clients.Client, error) { c.Tags = tags } - meta, ok := data["metadata"].(map[string]any) + meta, ok := data["public_metadata"].(map[string]any) if ok { - c.Metadata = meta + c.PublicMetadata = meta } uby, ok := data["updated_by"].(string) diff --git a/pkg/oauth2/normalize.go b/pkg/oauth2/normalize.go index b79e9d57ed..3efb38fbc4 100644 --- a/pkg/oauth2/normalize.go +++ b/pkg/oauth2/normalize.go @@ -43,12 +43,12 @@ func NormalizeUser(data []byte, provider string) (users.User, error) { } return users.User{ - ID: user.ID, - FirstName: user.FirstName, - LastName: user.LastName, - Email: user.Email, - ProfilePicture: user.Picture, - Metadata: users.Metadata{"oauth_provider": provider}, + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email, + ProfilePicture: user.Picture, + PrivateMetadata: users.Metadata{"oauth_provider": provider}, }, nil } diff --git a/pkg/oauth2/normalize_test.go b/pkg/oauth2/normalize_test.go index 19423258f5..4ebe368fa5 100644 --- a/pkg/oauth2/normalize_test.go +++ b/pkg/oauth2/normalize_test.go @@ -29,12 +29,12 @@ func TestNormalizeUser(t *testing.T) { }`, provider: "google", wantUser: users.User{ - ID: "123", - FirstName: "Jane", - LastName: "Doe", - Email: "jane@example.com", - ProfilePicture: "pic.jpg", - Metadata: users.Metadata{"oauth_provider": "google"}, + ID: "123", + FirstName: "Jane", + LastName: "Doe", + Email: "jane@example.com", + ProfilePicture: "pic.jpg", + PrivateMetadata: users.Metadata{"oauth_provider": "google"}, }, wantErrStr: "", }, diff --git a/pkg/sdk/clients.go b/pkg/sdk/clients.go index ab1698d3e2..59d4dcfbe3 100644 --- a/pkg/sdk/clients.go +++ b/pkg/sdk/clients.go @@ -27,19 +27,20 @@ const ( // Client represents supermq client. type Client struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Tags []string `json:"tags,omitempty"` - DomainID string `json:"domain_id,omitempty"` - ParentGroup string `json:"parent_group_id,omitempty"` - Credentials ClientCredentials `json:"credentials"` - Metadata map[string]any `json:"metadata,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - UpdatedBy string `json:"updated_by,omitempty"` - Status string `json:"status,omitempty"` - Permissions []string `json:"permissions,omitempty"` - Roles []roles.MemberRoleActions `json:"roles,omitempty"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Tags []string `json:"tags,omitempty"` + DomainID string `json:"domain_id,omitempty"` + ParentGroup string `json:"parent_group_id,omitempty"` + Credentials ClientCredentials `json:"credentials"` + PublicMetadata map[string]any `json:"public_metadata,omitempty"` + PrivateMetadata map[string]any `json:"private_metadata,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + Status string `json:"status,omitempty"` + Permissions []string `json:"permissions,omitempty"` + Roles []roles.MemberRoleActions `json:"roles,omitempty"` } type ClientCredentials struct { diff --git a/pkg/sdk/clients_test.go b/pkg/sdk/clients_test.go index 9e313c6931..57e58ebaf0 100644 --- a/pkg/sdk/clients_test.go +++ b/pkg/sdk/clients_test.go @@ -51,11 +51,12 @@ func TestCreateClient(t *testing.T) { client := generateTestClient(t, false) createClientReq := sdk.Client{ - Name: client.Name, - Tags: client.Tags, - Credentials: client.Credentials, - Metadata: client.Metadata, - Status: client.Status, + Name: client.Name, + Tags: client.Tags, + Credentials: client.Credentials, + PublicMetadata: client.PublicMetadata, + PrivateMetadata: client.PrivateMetadata, + Status: client.Status, } conf := sdk.Config{ @@ -126,11 +127,12 @@ func TestCreateClient(t *testing.T) { domainID: domainID, token: validToken, createClientReq: sdk.Client{ - Name: strings.Repeat("a", 1025), - Tags: client.Tags, - Credentials: client.Credentials, - Metadata: client.Metadata, - Status: client.Status, + Name: strings.Repeat("a", 1025), + Tags: client.Tags, + Credentials: client.Credentials, + PublicMetadata: client.PublicMetadata, + PrivateMetadata: client.PrivateMetadata, + Status: client.Status, }, svcReq: clients.Client{}, svcRes: []clients.Client{}, @@ -143,12 +145,13 @@ func TestCreateClient(t *testing.T) { domainID: domainID, token: validToken, createClientReq: sdk.Client{ - ID: "123456789", - Name: client.Name, - Tags: client.Tags, - Credentials: client.Credentials, - Metadata: client.Metadata, - Status: client.Status, + ID: "123456789", + Name: client.Name, + Tags: client.Tags, + Credentials: client.Credentials, + PublicMetadata: client.PublicMetadata, + PrivateMetadata: client.PrivateMetadata, + Status: client.Status, }, svcReq: clients.Client{}, svcRes: []clients.Client{}, @@ -162,7 +165,7 @@ func TestCreateClient(t *testing.T) { token: validToken, createClientReq: sdk.Client{ Name: valid, - Metadata: map[string]any{ + PublicMetadata: map[string]any{ valid: make(chan int), }, }, @@ -182,7 +185,7 @@ func TestCreateClient(t *testing.T) { Name: client.Name, Tags: client.Tags, Credentials: clients.Credentials(client.Credentials), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ "test": make(chan int), }, }}, @@ -276,7 +279,7 @@ func TestCreateClients(t *testing.T) { desc: "create new clients with a request that can't be marshalled", domainID: domainID, token: validToken, - createClientsRequest: []sdk.Client{{Name: "test", Metadata: map[string]any{"test": make(chan int)}}}, + createClientsRequest: []sdk.Client{{Name: "test", PublicMetadata: map[string]any{"test": make(chan int)}}}, svcReq: convertClients(sdkClients...), svcRes: []clients.Client{}, svcErr: nil, @@ -293,7 +296,7 @@ func TestCreateClients(t *testing.T) { Name: sdkClients[0].Name, Tags: sdkClients[0].Tags, Credentials: clients.Credentials(sdkClients[0].Credentials), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ "test": make(chan int), }, }}, @@ -559,7 +562,7 @@ func TestListClients(t *testing.T) { Name: sdkClients[0].Name, Tags: sdkClients[0].Tags, Credentials: clients.Credentials(sdkClients[0].Credentials), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ "test": make(chan int), }, }}, @@ -695,7 +698,7 @@ func TestViewClient(t *testing.T) { Name: sdkClient.Name, Tags: sdkClient.Tags, Credentials: clients.Credentials(sdkClient.Credentials), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ "test": make(chan int), }, }, @@ -742,13 +745,13 @@ func TestUpdateClient(t *testing.T) { sdkClient := generateTestClient(t, false) updatedClient := sdkClient updatedClient.Name = "newName" - updatedClient.Metadata = map[string]any{ + updatedClient.PublicMetadata = map[string]any{ "newKey": "newValue", } updateClientReq := sdk.Client{ - ID: sdkClient.ID, - Name: updatedClient.Name, - Metadata: updatedClient.Metadata, + ID: sdkClient.ID, + Name: updatedClient.Name, + PublicMetadata: updatedClient.PublicMetadata, } conf := sdk.Config{ @@ -844,7 +847,7 @@ func TestUpdateClient(t *testing.T) { updateClientReq: sdk.Client{ ID: valid, - Metadata: map[string]any{ + PublicMetadata: map[string]any{ "test": make(chan int), }, }, @@ -864,7 +867,7 @@ func TestUpdateClient(t *testing.T) { Name: updatedClient.Name, Tags: updatedClient.Tags, Credentials: clients.Credentials(updatedClient.Credentials), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ "test": make(chan int), }, }, @@ -996,7 +999,7 @@ func TestUpdateClientTags(t *testing.T) { token: validToken, updateClientReq: sdk.Client{ ID: valid, - Metadata: map[string]any{ + PublicMetadata: map[string]any{ "test": make(chan int), }, }, @@ -1016,7 +1019,7 @@ func TestUpdateClientTags(t *testing.T) { Name: updatedClient.Name, Tags: updatedClient.Tags, Credentials: clients.Credentials(updatedClient.Credentials), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ "test": make(chan int), }, }, @@ -1148,7 +1151,7 @@ func TestUpdateClientSecret(t *testing.T) { Name: updatedClient.Name, Tags: updatedClient.Tags, Credentials: clients.Credentials(updatedClient.Credentials), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ "test": make(chan int), }, }, @@ -1251,7 +1254,7 @@ func TestEnableClient(t *testing.T) { Name: enabledClient.Name, Tags: enabledClient.Tags, Credentials: clients.Credentials(enabledClient.Credentials), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ "test": make(chan int), }, }, @@ -1354,7 +1357,7 @@ func TestDisableClient(t *testing.T) { Name: disabledClient.Name, Tags: disabledClient.Tags, Credentials: clients.Credentials(disabledClient.Credentials), - Metadata: clients.Metadata{ + PublicMetadata: clients.Metadata{ "test": make(chan int), }, }, @@ -3268,11 +3271,12 @@ func generateTestClient(t *testing.T, withRoles bool) sdk.Client { Identity: "client@example.com", Secret: generateUUID(t), }, - Tags: []string{"tag1", "tag2"}, - Metadata: validMetadata, - Status: clients.EnabledStatus.String(), - CreatedAt: createdAt, - UpdatedAt: updatedAt, - Roles: rl, + Tags: []string{"tag1", "tag2"}, + PublicMetadata: validMetadata, + PrivateMetadata: validMetadata, + Status: clients.EnabledStatus.String(), + CreatedAt: createdAt, + UpdatedAt: updatedAt, + Roles: rl, } } diff --git a/pkg/sdk/setup_test.go b/pkg/sdk/setup_test.go index 8b9fb67b2e..6f69f0ea8d 100644 --- a/pkg/sdk/setup_test.go +++ b/pkg/sdk/setup_test.go @@ -165,18 +165,19 @@ func convertUser(c sdk.User) users.User { return users.User{} } return users.User{ - ID: c.ID, - FirstName: c.FirstName, - LastName: c.LastName, - Tags: c.Tags, - Email: c.Email, - Credentials: users.Credentials(c.Credentials), - Metadata: users.Metadata(c.Metadata), - CreatedAt: c.CreatedAt, - UpdatedAt: c.UpdatedAt, - Status: status, - Role: role, - ProfilePicture: c.ProfilePicture, + ID: c.ID, + FirstName: c.FirstName, + LastName: c.LastName, + Tags: c.Tags, + Email: c.Email, + Credentials: users.Credentials(c.Credentials), + PublicMetadata: users.Metadata(c.PublicMetadata), + PrivateMetadata: users.Metadata(c.PrivateMetadata), + CreatedAt: c.CreatedAt, + UpdatedAt: c.UpdatedAt, + Status: status, + Role: role, + ProfilePicture: c.ProfilePicture, } } @@ -189,18 +190,19 @@ func convertClient(c sdk.Client) clients.Client { return clients.Client{} } return clients.Client{ - ID: c.ID, - Name: c.Name, - Tags: c.Tags, - Domain: c.DomainID, - ParentGroup: c.ParentGroup, - Credentials: clients.Credentials(c.Credentials), - Metadata: clients.Metadata(c.Metadata), - CreatedAt: c.CreatedAt, - UpdatedAt: c.UpdatedAt, - UpdatedBy: c.UpdatedBy, - Status: status, - Roles: c.Roles, + ID: c.ID, + Name: c.Name, + Tags: c.Tags, + Domain: c.DomainID, + ParentGroup: c.ParentGroup, + Credentials: clients.Credentials(c.Credentials), + PublicMetadata: clients.Metadata(c.PublicMetadata), + PrivateMetadata: clients.Metadata(c.PrivateMetadata), + CreatedAt: c.CreatedAt, + UpdatedAt: c.UpdatedAt, + UpdatedBy: c.UpdatedBy, + Status: status, + Roles: c.Roles, } } @@ -265,12 +267,13 @@ func generateTestUser(t *testing.T) sdk.User { Username: "username", Secret: secret, }, - Tags: []string{"tag1", "tag2"}, - Metadata: validMetadata, - CreatedAt: createdAt, - UpdatedAt: createdAt, - Status: users.EnabledStatus.String(), - Role: users.UserRole.String(), + Tags: []string{"tag1", "tag2"}, + PublicMetadata: validMetadata, + PrivateMetadata: validMetadata, + CreatedAt: createdAt, + UpdatedAt: createdAt, + Status: users.EnabledStatus.String(), + Role: users.UserRole.String(), } } diff --git a/pkg/sdk/users.go b/pkg/sdk/users.go index a113749fad..3cfe97599e 100644 --- a/pkg/sdk/users.go +++ b/pkg/sdk/users.go @@ -29,19 +29,20 @@ const ( // User represents supermq user its credentials. type User struct { - ID string `json:"id"` - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` - Email string `json:"email,omitempty"` - Credentials Credentials `json:"credentials"` - Tags []string `json:"tags,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - Status string `json:"status,omitempty"` - Role string `json:"role,omitempty"` - ProfilePicture string `json:"profile_picture,omitempty"` - AuthProvider string `json:"auth_provider,omitempty"` + ID string `json:"id"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + Email string `json:"email,omitempty"` + Credentials Credentials `json:"credentials"` + Tags []string `json:"tags,omitempty"` + PublicMetadata Metadata `json:"public_metadata,omitempty"` + PrivateMetadata Metadata `json:"private_metadata,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + Status string `json:"status,omitempty"` + Role string `json:"role,omitempty"` + ProfilePicture string `json:"profile_picture,omitempty"` + AuthProvider string `json:"auth_provider,omitempty"` } func (sdk mgSDK) CreateUser(ctx context.Context, user User, token string) (User, errors.SDKError) { diff --git a/pkg/sdk/users_test.go b/pkg/sdk/users_test.go index b24a1cfbe7..3e6987e6fe 100644 --- a/pkg/sdk/users_test.go +++ b/pkg/sdk/users_test.go @@ -56,13 +56,14 @@ func TestCreateUser(t *testing.T) { defer ts.Close() createSdkUserReq := sdk.User{ - FirstName: user.FirstName, - LastName: user.LastName, - Email: user.Email, - Tags: user.Tags, - Credentials: user.Credentials, - Metadata: user.Metadata, - Status: user.Status, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email, + Tags: user.Tags, + Credentials: user.Credentials, + PublicMetadata: user.PublicMetadata, + PrivateMetadata: user.PrivateMetadata, + Status: user.Status, } conf := sdk.Config{ @@ -142,10 +143,11 @@ func TestCreateUser(t *testing.T) { desc: "register user with first name too long", token: validToken, createSdkUserReq: sdk.User{ - FirstName: strings.Repeat("a", 1025), - Credentials: createSdkUserReq.Credentials, - Metadata: createSdkUserReq.Metadata, - Tags: createSdkUserReq.Tags, + FirstName: strings.Repeat("a", 1025), + Credentials: createSdkUserReq.Credentials, + PublicMetadata: createSdkUserReq.PublicMetadata, + PrivateMetadata: createSdkUserReq.PrivateMetadata, + Tags: createSdkUserReq.Tags, }, svcReq: users.User{}, svcRes: users.User{}, @@ -164,8 +166,9 @@ func TestCreateUser(t *testing.T) { Username: "", Secret: createSdkUserReq.Credentials.Secret, }, - Metadata: createSdkUserReq.Metadata, - Tags: createSdkUserReq.Tags, + PublicMetadata: createSdkUserReq.PublicMetadata, + PrivateMetadata: createSdkUserReq.PrivateMetadata, + Tags: createSdkUserReq.Tags, }, svcReq: users.User{}, svcRes: users.User{}, @@ -184,8 +187,9 @@ func TestCreateUser(t *testing.T) { Username: createSdkUserReq.Credentials.Username, Secret: "", }, - Metadata: createSdkUserReq.Metadata, - Tags: createSdkUserReq.Tags, + PublicMetadata: createSdkUserReq.PublicMetadata, + PrivateMetadata: createSdkUserReq.PrivateMetadata, + Tags: createSdkUserReq.Tags, }, svcReq: users.User{}, svcRes: users.User{}, @@ -204,8 +208,9 @@ func TestCreateUser(t *testing.T) { Username: createSdkUserReq.Credentials.Username, Secret: "weak", }, - Metadata: createSdkUserReq.Metadata, - Tags: createSdkUserReq.Tags, + PublicMetadata: createSdkUserReq.PublicMetadata, + PrivateMetadata: createSdkUserReq.PrivateMetadata, + Tags: createSdkUserReq.Tags, }, svcReq: users.User{}, svcRes: users.User{}, @@ -224,7 +229,7 @@ func TestCreateUser(t *testing.T) { FirstName: createSdkUserReq.FirstName, LastName: createSdkUserReq.LastName, Email: createSdkUserReq.Email, - Metadata: map[string]any{ + PublicMetadata: map[string]any{ "test": make(chan int), }, }, @@ -248,7 +253,7 @@ func TestCreateUser(t *testing.T) { Username: createSdkUserReq.Credentials.Username, Secret: createSdkUserReq.Credentials.Secret, }, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key": make(chan int), }, }, @@ -290,9 +295,9 @@ func TestListUsers(t *testing.T) { Username: fmt.Sprintf("Username_%d", i), Secret: fmt.Sprintf("password_%d", i), }, - Metadata: sdk.Metadata{"name": fmt.Sprintf("user_%d", i)}, - Status: users.EnabledStatus.String(), - Role: users.UserRole.String(), + PublicMetadata: sdk.Metadata{"name": fmt.Sprintf("user_%d", i)}, + Status: users.EnabledStatus.String(), + Role: users.UserRole.String(), } if i == 50 { cl.Status = users.DisabledStatus.String() @@ -546,7 +551,7 @@ func TestListUsers(t *testing.T) { { ID: id, FirstName: "user_99", - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key": make(chan int), }, }, @@ -600,9 +605,9 @@ func TestSearchUsers(t *testing.T) { Username: fmt.Sprintf("Username_%d", i), Secret: fmt.Sprintf("password_%d", i), }, - Metadata: sdk.Metadata{"name": fmt.Sprintf("user_%d", i)}, - Status: users.EnabledStatus.String(), - Role: users.UserRole.String(), + PublicMetadata: sdk.Metadata{"name": fmt.Sprintf("user_%d", i)}, + Status: users.EnabledStatus.String(), + Role: users.UserRole.String(), } if i == 50 { cl.Status = users.DisabledStatus.String() @@ -782,7 +787,7 @@ func TestViewUser(t *testing.T) { ID: id, FirstName: user.FirstName, LastName: user.LastName, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key": make(chan int), }, }, @@ -861,7 +866,7 @@ func TestUserProfile(t *testing.T) { svcRes: users.User{ ID: id, FirstName: user.FirstName, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key": make(chan int), }, }, @@ -1001,7 +1006,7 @@ func TestUpdateUser(t *testing.T) { token: validToken, updateUserReq: sdk.User{ ID: generateUUID(t), - Metadata: map[string]any{ + PublicMetadata: map[string]any{ "test": make(chan int), }, }, @@ -1025,7 +1030,7 @@ func TestUpdateUser(t *testing.T) { svcRes: users.User{ ID: id, FirstName: updatedName, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key": make(chan int), }, }, @@ -1162,7 +1167,7 @@ func TestUpdateUserTags(t *testing.T) { token: validToken, updateUserReq: sdk.User{ ID: generateUUID(t), - Metadata: map[string]any{ + PublicMetadata: map[string]any{ "test": make(chan int), }, }, @@ -1186,7 +1191,7 @@ func TestUpdateUserTags(t *testing.T) { svcRes: users.User{ ID: id, Tags: updatedTags, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key": make(chan int), }, }, @@ -1334,7 +1339,7 @@ func TestUpdateUserEmail(t *testing.T) { svcRes: users.User{ ID: id, FirstName: updatedEmail, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key": make(chan int), }, }, @@ -1630,7 +1635,7 @@ func TestUpdatePassword(t *testing.T) { svcRes: users.User{ ID: id, FirstName: user.FirstName, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key": make(chan int), }, }, @@ -1764,7 +1769,7 @@ func TestUpdateUserRole(t *testing.T) { token: validToken, updateUserReq: sdk.User{ ID: generateUUID(t), - Metadata: map[string]any{ + PublicMetadata: map[string]any{ "test": make(chan int), }, }, @@ -1788,7 +1793,7 @@ func TestUpdateUserRole(t *testing.T) { svcRes: users.User{ ID: id, Role: users.AdminRole, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key": make(chan int), }, }, @@ -1952,7 +1957,7 @@ func TestUpdateUsername(t *testing.T) { Credentials: users.Credentials{ Username: updatedUsername, }, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key": make(chan int), }, }, @@ -2092,7 +2097,7 @@ func TestUpdateProfilePicture(t *testing.T) { token: validToken, updateUserReq: sdk.User{ ID: generateUUID(t), - Metadata: map[string]any{ + PublicMetadata: map[string]any{ "test": make(chan int), }, }, @@ -2115,7 +2120,7 @@ func TestUpdateProfilePicture(t *testing.T) { }, svcRes: users.User{ ID: id, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key": make(chan int), }, }, @@ -2293,7 +2298,7 @@ func TestDisableUser(t *testing.T) { svcRes: users.User{ ID: id, Status: users.DisabledStatus, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key": make(chan int), }, }, diff --git a/users/api/endpoint_test.go b/users/api/endpoint_test.go index dbf6e57108..fa8e1731a2 100644 --- a/users/api/endpoint_test.go +++ b/users/api/endpoint_test.go @@ -37,14 +37,15 @@ var ( secret = "strongsecret" validCMetadata = users.Metadata{"role": "user"} user = users.User{ - ID: testsutil.GenerateUUID(&testing.T{}), - LastName: "doe", - FirstName: "jane", - Tags: []string{"foo", "bar"}, - Email: "useremail@example.com", - Credentials: users.Credentials{Username: "username", Secret: secret}, - Metadata: validCMetadata, - Status: users.EnabledStatus, + ID: testsutil.GenerateUUID(&testing.T{}), + LastName: "doe", + FirstName: "jane", + Tags: []string{"foo", "bar"}, + Email: "useremail@example.com", + Credentials: users.Credentials{Username: "username", Secret: secret}, + PublicMetadata: validCMetadata, + PrivateMetadata: validCMetadata, + Status: users.EnabledStatus, } validToken = "valid" inValidToken = "invalid" @@ -145,7 +146,7 @@ func TestRegister(t *testing.T) { Credentials: users.Credentials{ Secret: "12345678", }, - Metadata: map[string]any{ + PublicMetadata: map[string]any{ "test": make(chan int), }, }, @@ -917,14 +918,14 @@ func TestUpdate(t *testing.T) { { desc: "update as admin user with valid token", id: user.ID, - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","public_metadata":%s}`, newName, toJSON(newMetadata)), token: validToken, authnRes: verifiedSession, contentType: contentType, userResponse: users.User{ - ID: user.ID, - FirstName: newName, - Metadata: newMetadata, + ID: user.ID, + FirstName: newName, + PublicMetadata: newMetadata, }, status: http.StatusOK, err: nil, @@ -932,14 +933,14 @@ func TestUpdate(t *testing.T) { { desc: "update as normal user with valid token", id: user.ID, - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","public_metadata":%s}`, newName, toJSON(newMetadata)), token: validToken, authnRes: verifiedSession, contentType: contentType, userResponse: users.User{ - ID: user.ID, - FirstName: newName, - Metadata: newMetadata, + ID: user.ID, + FirstName: newName, + PublicMetadata: newMetadata, }, status: http.StatusOK, err: nil, @@ -947,7 +948,7 @@ func TestUpdate(t *testing.T) { { desc: "update user with invalid token", id: user.ID, - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","public_metadata":%s}`, newName, toJSON(newMetadata)), token: inValidToken, authnRes: smqauthn.Session{UserID: validID, DomainID: validID, Verified: true}, contentType: contentType, @@ -958,7 +959,7 @@ func TestUpdate(t *testing.T) { { desc: "update user with empty token", id: user.ID, - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","public_metadata":%s}`, newName, toJSON(newMetadata)), token: "", authnRes: smqauthn.Session{UserID: validID, DomainID: validID, Verified: true}, contentType: contentType, @@ -969,7 +970,7 @@ func TestUpdate(t *testing.T) { { desc: "update user with invalid id", id: inValid, - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","public_metadata":%s}`, newName, toJSON(newMetadata)), token: validToken, authnRes: verifiedSession, contentType: contentType, @@ -979,7 +980,7 @@ func TestUpdate(t *testing.T) { { desc: "update user with invalid contentype", id: user.ID, - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","public_metadata":%s}`, newName, toJSON(newMetadata)), token: validToken, authnRes: verifiedSession, contentType: "application/xml", @@ -999,7 +1000,7 @@ func TestUpdate(t *testing.T) { { desc: "update user with empty id", id: " ", - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), + data: fmt.Sprintf(`{"name":"%s","public_metadata":%s}`, newName, toJSON(newMetadata)), token: validToken, authnRes: verifiedSession, contentType: contentType, diff --git a/users/api/endpoints.go b/users/api/endpoints.go index 60b5910db4..d0bb00be45 100644 --- a/users/api/endpoints.go +++ b/users/api/endpoints.go @@ -209,9 +209,10 @@ func updateEndpoint(svc users.Service) endpoint.Endpoint { } usr := users.UserReq{ - FirstName: req.FirstName, - LastName: req.LastName, - Metadata: req.Metadata, + FirstName: req.FirstName, + LastName: req.LastName, + PublicMetadata: req.PublicMetadata, + PrivateMetadata: req.PrivateMetadata, } user, err := svc.Update(ctx, session, req.id, usr) diff --git a/users/api/grpc/client.go b/users/api/grpc/client.go index 0e5122f4bf..ec537a9069 100644 --- a/users/api/grpc/client.go +++ b/users/api/grpc/client.go @@ -106,20 +106,26 @@ func usersFromProto(us []*grpcUsersV1.User) ([]users.User, error) { } func userFromProto(u *grpcUsersV1.User) (users.User, error) { - metadata := users.Metadata(nil) - if u.GetMetadata() != nil { - metadata = users.Metadata(u.GetMetadata().AsMap()) + publicMetadata := users.Metadata(nil) + if u.GetPublicMetadata() != nil { + publicMetadata = users.Metadata(u.GetPublicMetadata().AsMap()) + } + + privateMetadata := users.Metadata(nil) + if u.GetPrivateMetadata() != nil { + privateMetadata = users.Metadata(u.GetPrivateMetadata().AsMap()) } user := users.User{ - ID: u.GetId(), - FirstName: u.GetFirstName(), - LastName: u.GetLastName(), - Tags: u.GetTags(), - Metadata: metadata, - Status: users.Status(u.GetStatus()), - Role: users.Role(u.GetRole()), - ProfilePicture: u.GetProfilePicture(), + ID: u.GetId(), + FirstName: u.GetFirstName(), + LastName: u.GetLastName(), + Tags: u.GetTags(), + PublicMetadata: publicMetadata, + PrivateMetadata: privateMetadata, + Status: users.Status(u.GetStatus()), + Role: users.Role(u.GetRole()), + ProfilePicture: u.GetProfilePicture(), Credentials: users.Credentials{ Username: u.GetUsername(), }, diff --git a/users/api/grpc/server.go b/users/api/grpc/server.go index ab427a6a71..230d187deb 100644 --- a/users/api/grpc/server.go +++ b/users/api/grpc/server.go @@ -82,29 +82,36 @@ func toProtoUsers(us []users.User) ([]*grpcUsersV1.User, error) { } func toProtoUser(u users.User) (*grpcUsersV1.User, error) { - var md *structpb.Struct + var publicMetadata, privateMetadata *structpb.Struct var err error - if u.Metadata != nil { - md, err = structpb.NewStruct(u.Metadata) + if u.PublicMetadata != nil { + publicMetadata, err = structpb.NewStruct(u.PublicMetadata) + if err != nil { + return nil, errors.Wrap(svcerr.ErrViewEntity, err) + } + } + if u.PrivateMetadata != nil { + privateMetadata, err = structpb.NewStruct(u.PrivateMetadata) if err != nil { return nil, errors.Wrap(svcerr.ErrViewEntity, err) } } pu := &grpcUsersV1.User{ - Id: u.ID, - FirstName: u.FirstName, - LastName: u.LastName, - Tags: u.Tags, - Metadata: md, - Status: uint32(u.Status), - Role: uint32(u.Role), - ProfilePicture: u.ProfilePicture, - Username: u.Credentials.Username, - Email: u.Email, - UpdatedBy: u.UpdatedBy, - AuthProvider: u.AuthProvider, - Permissions: u.Permissions, + Id: u.ID, + FirstName: u.FirstName, + LastName: u.LastName, + Tags: u.Tags, + PublicMetadata: publicMetadata, + PrivateMetadata: privateMetadata, + Status: uint32(u.Status), + Role: uint32(u.Role), + ProfilePicture: u.ProfilePicture, + Username: u.Credentials.Username, + Email: u.Email, + UpdatedBy: u.UpdatedBy, + AuthProvider: u.AuthProvider, + Permissions: u.Permissions, } if !u.CreatedAt.IsZero() { diff --git a/users/api/requests.go b/users/api/requests.go index 6b620e54fe..352caa0a0c 100644 --- a/users/api/requests.go +++ b/users/api/requests.go @@ -147,10 +147,11 @@ func (req searchUsersReq) validate() error { } type updateUserReq struct { - id string - FirstName *string `json:"first_name,omitempty"` - LastName *string `json:"last_name,omitempty"` - Metadata *users.Metadata `json:"metadata,omitempty"` + id string + FirstName *string `json:"first_name,omitempty"` + LastName *string `json:"last_name,omitempty"` + PublicMetadata *users.Metadata `json:"public_metadata,omitempty"` + PrivateMetadata *users.Metadata `json:"private_metadata,omitempty"` } func (req updateUserReq) validate() error { diff --git a/users/events/events.go b/users/events/events.go index f298845350..cb9ec81b6a 100644 --- a/users/events/events.go +++ b/users/events/events.go @@ -89,8 +89,8 @@ func (uce createUserEvent) Encode() (map[string]any, error) { if len(uce.Tags) > 0 { val["tags"] = uce.Tags } - if uce.Metadata != nil { - val["metadata"] = uce.Metadata + if uce.PublicMetadata != nil { + val["public_metadata"] = uce.PublicMetadata } if uce.Credentials.Username != "" { val["username"] = uce.Credentials.Username @@ -168,8 +168,8 @@ func (uce updateUserEvent) Encode() (map[string]any, error) { if uce.Email != "" { val["email"] = uce.Email } - if uce.Metadata != nil { - val["metadata"] = uce.Metadata + if uce.PublicMetadata != nil { + val["public_metadata"] = uce.PublicMetadata } if !uce.CreatedAt.IsZero() { val["created_at"] = uce.CreatedAt @@ -290,10 +290,10 @@ func (vue viewUserEvent) Encode() (map[string]any, error) { val["email"] = vue.Email } if vue.Credentials.Username != "" { - val["email"] = vue.Credentials.Username + val["username"] = vue.Credentials.Username } - if vue.Metadata != nil { - val["metadata"] = vue.Metadata + if vue.PublicMetadata != nil { + val["public_metadata"] = vue.PublicMetadata } if !vue.CreatedAt.IsZero() { val["created_at"] = vue.CreatedAt @@ -335,8 +335,8 @@ func (vpe viewProfileEvent) Encode() (map[string]any, error) { if vpe.Credentials.Username != "" { val["username"] = vpe.Credentials.Username } - if vpe.Metadata != nil { - val["metadata"] = vpe.Metadata + if vpe.PublicMetadata != nil { + val["public_metadata"] = vpe.PublicMetadata } if !vpe.CreatedAt.IsZero() { val["created_at"] = vpe.CreatedAt diff --git a/users/events/streams_test.go b/users/events/streams_test.go index 716646c9fb..4e9cad2c36 100644 --- a/users/events/streams_test.go +++ b/users/events/streams_test.go @@ -1148,7 +1148,7 @@ func generateTestUser(t *testing.T) users.User { Secret: "secret", }, Tags: []string{"tag1", "tag2"}, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ "key1": "value1", "key2": "value2", }, diff --git a/users/middleware/logging.go b/users/middleware/logging.go index 8339cfe9ca..b0e3d67b9b 100644 --- a/users/middleware/logging.go +++ b/users/middleware/logging.go @@ -229,7 +229,7 @@ func (lm *loggingMiddleware) Update(ctx context.Context, session authn.Session, slog.String("username", u.Credentials.Username), slog.String("first_name", u.FirstName), slog.String("last_name", u.LastName), - slog.Any("metadata", u.Metadata), + slog.Any("public_metadata", u.PublicMetadata), ), } if err != nil { diff --git a/users/postgres/init.go b/users/postgres/init.go index 7d46a1cd1b..41d546e8e5 100644 --- a/users/postgres/init.go +++ b/users/postgres/init.go @@ -136,6 +136,17 @@ func Migration() *migrate.MemoryMigrationSource { `ALTER TABLE users DROP COLUMN auth_provider`, }, }, + { + Id: "clients_10", + Up: []string{ + `ALTER TABLE users RENAME COLUMN metadata TO private_metadata;`, + `ALTER TABLE users ADD COLUMN public_metadata JSONB;`, + }, + Down: []string{ + `ALTER TABLE users DROP COLUMN public_metadata;`, + `ALTER TABLE users RENAME COLUMN private_metadata TO metadata;`, + }, + }, }, } } diff --git a/users/postgres/users.go b/users/postgres/users.go index 6e13cb8043..611a11b219 100644 --- a/users/postgres/users.go +++ b/users/postgres/users.go @@ -36,9 +36,9 @@ func NewRepository(db postgres.Database) users.Repository { } func (repo *userRepo) Save(ctx context.Context, c users.User) (users.User, error) { - q := `INSERT INTO users (id, tags, email, secret, metadata, created_at, status, role, first_name, last_name, username, profile_picture, auth_provider) - VALUES (:id, :tags, :email, :secret, :metadata, :created_at, :status, :role, :first_name, :last_name, :username, :profile_picture, :auth_provider) - RETURNING id, tags, email, metadata, created_at, status, role, first_name, last_name, username, profile_picture, verified_at, auth_provider` + q := `INSERT INTO users (id, tags, email, secret, public_metadata, private_metadata, created_at, status, role, first_name, last_name, username, profile_picture, auth_provider) + VALUES (:id, :tags, :email, :secret, :public_metadata, :private_metadata, :created_at, :status, :role, :first_name, :last_name, :username, :profile_picture, :auth_provider) + RETURNING id, tags, email, public_metadata, private_metadata, created_at, status, role, first_name, last_name, username, profile_picture, verified_at, auth_provider` dbu, err := toDBUser(c) if err != nil { @@ -86,7 +86,7 @@ func (repo *userRepo) CheckSuperAdmin(ctx context.Context, adminID string) error } func (repo *userRepo) RetrieveByID(ctx context.Context, id string) (users.User, error) { - q := `SELECT id, tags, email, secret, metadata, created_at, updated_at, updated_by, status, role, first_name, last_name, username, profile_picture, verified_at, auth_provider + q := `SELECT id, tags, email, secret, public_metadata, private_metadata, created_at, updated_at, updated_by, status, role, first_name, last_name, username, profile_picture, verified_at, auth_provider FROM users WHERE id = :id` dbu := DBUser{ @@ -124,7 +124,7 @@ func (repo *userRepo) RetrieveAll(ctx context.Context, pm users.Page) (users.Use squery := applyOrdering(query, pm) - q := fmt.Sprintf(`SELECT u.id, u.tags, u.email, u.metadata, u.status, u.role, u.first_name, u.last_name, u.username, + q := fmt.Sprintf(`SELECT u.id, u.tags, u.email, u.public_metadata, u.status, u.role, u.first_name, u.last_name, u.username, u.created_at, u.updated_at, u.profile_picture, COALESCE(u.updated_by, '') AS updated_by, u.verified_at FROM users u %s LIMIT :limit OFFSET :offset;`, squery) @@ -178,7 +178,7 @@ func (repo *userRepo) RetrieveAll(ctx context.Context, pm users.Page) (users.Use func (repo *userRepo) UpdateUsername(ctx context.Context, user users.User) (users.User, error) { q := `UPDATE users SET username = :username, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, tags, metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, email, role, verified_at` + RETURNING id, tags, public_metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, email, role, verified_at` return repo.update(ctx, user, q) } @@ -195,9 +195,13 @@ func (repo *userRepo) Update(ctx context.Context, id string, ur users.UserReq) ( query = append(query, "last_name = :last_name") u.LastName = *ur.LastName } - if ur.Metadata != nil { - query = append(query, "metadata = :metadata") - u.Metadata = *ur.Metadata + if ur.PublicMetadata != nil { + query = append(query, "public_metadata = :public_metadata") + u.PublicMetadata = *ur.PublicMetadata + } + if ur.PrivateMetadata != nil { + query = append(query, "private_metadata = :private_metadata") + u.PrivateMetadata = *ur.PrivateMetadata } if ur.Tags != nil { query = append(query, "tags = :tags") @@ -223,7 +227,7 @@ func (repo *userRepo) Update(ctx context.Context, id string, ur users.UserReq) ( q := fmt.Sprintf(`UPDATE users SET %s WHERE id = :id AND status = :status - RETURNING id, tags, metadata, status, created_at, updated_at, updated_by, last_name, first_name, username, profile_picture, email, role, verified_at`, upq) + RETURNING id, tags, public_metadata, private_metadata, status, created_at, updated_at, updated_by, last_name, first_name, username, profile_picture, email, role, verified_at`, upq) u.Status = users.EnabledStatus return repo.update(ctx, u, q) @@ -256,7 +260,7 @@ func (repo *userRepo) update(ctx context.Context, user users.User, query string) func (repo *userRepo) UpdateEmail(ctx context.Context, user users.User) (users.User, error) { q := `UPDATE users SET email = :email, verified_at = NULL, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, tags, email, metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, role, verified_at` + RETURNING id, tags, email, public_metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, role, verified_at` user.Status = users.EnabledStatus return repo.update(ctx, user, q) } @@ -264,7 +268,7 @@ func (repo *userRepo) UpdateEmail(ctx context.Context, user users.User) (users.U func (repo *userRepo) UpdateRole(ctx context.Context, user users.User) (users.User, error) { q := `UPDATE users SET role = :role, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, tags, email, metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, role, verified_at` + RETURNING id, tags, email, public_metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, role, verified_at` user.Status = users.EnabledStatus return repo.update(ctx, user, q) } @@ -272,7 +276,7 @@ func (repo *userRepo) UpdateRole(ctx context.Context, user users.User) (users.Us func (repo *userRepo) UpdateSecret(ctx context.Context, user users.User) (users.User, error) { q := `UPDATE users SET secret = :secret, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id AND status = :status - RETURNING id, tags, email, metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, role, verified_at` + RETURNING id, tags, email, public_metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, role, verified_at` user.Status = users.EnabledStatus return repo.update(ctx, user, q) } @@ -280,7 +284,7 @@ func (repo *userRepo) UpdateSecret(ctx context.Context, user users.User) (users. func (repo *userRepo) ChangeStatus(ctx context.Context, user users.User) (users.User, error) { q := `UPDATE users SET status = :status, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id - RETURNING id, tags, email, metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, role, verified_at` + RETURNING id, tags, email, public_metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, role, verified_at` return repo.update(ctx, user, q) } @@ -288,7 +292,7 @@ func (repo *userRepo) ChangeStatus(ctx context.Context, user users.User) (users. func (repo *userRepo) UpdateVerifiedAt(ctx context.Context, user users.User) (users.User, error) { q := `UPDATE users SET verified_at = :verified_at WHERE id = :id and email = :email - RETURNING id, tags, email, metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, role, verified_at` + RETURNING id, tags, email, public_metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, role, verified_at` return repo.update(ctx, user, q) } @@ -316,7 +320,7 @@ func (repo *userRepo) SearchUsers(ctx context.Context, pm users.Page) (users.Use tq := query query = applyOrdering(query, pm) - q := fmt.Sprintf(`SELECT u.id, u.username, u.first_name, u.last_name, u.created_at, u.updated_at FROM users u %s LIMIT :limit OFFSET :offset;`, query) + q := fmt.Sprintf(`SELECT u.id, u.username, u.public_metadata, u.first_name, u.last_name, u.created_at, u.updated_at FROM users u %s LIMIT :limit OFFSET :offset;`, query) dbPage, err := ToDBUsersPage(pm) if err != nil { @@ -375,7 +379,7 @@ func (repo *userRepo) RetrieveAllByIDs(ctx context.Context, pm users.Page) (user } squery := applyOrdering(query, pm) - q := fmt.Sprintf(`SELECT u.id, u.username, u.tags, u.email, u.metadata, u.status, u.role, u.first_name, u.last_name, + q := fmt.Sprintf(`SELECT u.id, u.username, u.tags, u.email, u.public_metadata, u.status, u.role, u.first_name, u.last_name, u.created_at, u.updated_at, COALESCE(u.updated_by, '') AS updated_by FROM users u %s LIMIT :limit OFFSET :offset;`, squery) dbPage, err := ToDBUsersPage(pm) if err != nil { @@ -421,7 +425,7 @@ func (repo *userRepo) RetrieveAllByIDs(ctx context.Context, pm users.Page) (user } func (repo *userRepo) RetrieveByEmail(ctx context.Context, email string) (users.User, error) { - q := `SELECT id, tags, email, secret, metadata, created_at, updated_at, updated_by, status, role, first_name, last_name, username, verified_at, auth_provider + q := `SELECT id, tags, email, secret, public_metadata, private_metadata, created_at, updated_at, updated_by, status, role, first_name, last_name, username, verified_at, auth_provider FROM users WHERE email = :email AND status = :status` dbu := DBUser{ @@ -448,7 +452,7 @@ func (repo *userRepo) RetrieveByEmail(ctx context.Context, email string) (users. } func (repo *userRepo) RetrieveByUsername(ctx context.Context, username string) (users.User, error) { - q := `SELECT id, tags, email, secret, metadata, created_at, updated_at, updated_by, status, role, first_name, last_name, username, verified_at, auth_provider + q := `SELECT id, tags, email, secret, public_metadata, private_metadata, created_at, updated_at, updated_by, status, role, first_name, last_name, username, verified_at, auth_provider FROM users WHERE username = :username AND status = :status` dbu := DBUser{ @@ -475,34 +479,43 @@ func (repo *userRepo) RetrieveByUsername(ctx context.Context, username string) ( } type DBUser struct { - ID string `db:"id"` - Domain string `db:"domain_id"` - Secret string `db:"secret"` - Metadata []byte `db:"metadata,omitempty"` - Tags pgtype.TextArray `db:"tags,omitempty"` // Tags - CreatedAt time.Time `db:"created_at,omitempty"` - UpdatedAt sql.NullTime `db:"updated_at,omitempty"` - UpdatedBy *string `db:"updated_by,omitempty"` - Groups []groups.Group `db:"groups,omitempty"` - Status users.Status `db:"status,omitempty"` - Role *users.Role `db:"role,omitempty"` - Username sql.NullString `db:"username, omitempty"` - FirstName sql.NullString `db:"first_name, omitempty"` - LastName sql.NullString `db:"last_name, omitempty"` - ProfilePicture sql.NullString `db:"profile_picture, omitempty"` - Email string `db:"email,omitempty"` - VerifiedAt sql.NullTime `db:"verified_at,omitempty"` - AuthProvider sql.NullString `db:"auth_provider,omitempty"` + ID string `db:"id"` + Domain string `db:"domain_id"` + Secret string `db:"secret"` + PublicMetadata []byte `db:"public_metadata,omitempty"` + PrivateMetadata []byte `db:"private_metadata,omitempty"` + Tags pgtype.TextArray `db:"tags,omitempty"` // Tags + CreatedAt time.Time `db:"created_at,omitempty"` + UpdatedAt sql.NullTime `db:"updated_at,omitempty"` + UpdatedBy *string `db:"updated_by,omitempty"` + Groups []groups.Group `db:"groups,omitempty"` + Status users.Status `db:"status,omitempty"` + Role *users.Role `db:"role,omitempty"` + Username sql.NullString `db:"username, omitempty"` + FirstName sql.NullString `db:"first_name, omitempty"` + LastName sql.NullString `db:"last_name, omitempty"` + ProfilePicture sql.NullString `db:"profile_picture, omitempty"` + Email string `db:"email,omitempty"` + VerifiedAt sql.NullTime `db:"verified_at,omitempty"` + AuthProvider sql.NullString `db:"auth_provider,omitempty"` } func toDBUser(u users.User) (DBUser, error) { - data := []byte("{}") - if len(u.Metadata) > 0 { - b, err := json.Marshal(u.Metadata) + publicMetadata := []byte("{}") + if len(u.PublicMetadata) > 0 { + b, err := json.Marshal(u.PublicMetadata) + if err != nil { + return DBUser{}, errors.Wrap(repoerr.ErrMalformedEntity, err) + } + publicMetadata = b + } + privateMetadata := []byte("{}") + if len(u.PrivateMetadata) > 0 { + b, err := json.Marshal(u.PrivateMetadata) if err != nil { return DBUser{}, errors.Wrap(repoerr.ErrMalformedEntity, err) } - data = b + privateMetadata = b } var tags pgtype.TextArray if err := tags.Set(u.Tags); err != nil { @@ -527,29 +540,35 @@ func toDBUser(u users.User) (DBUser, error) { } return DBUser{ - ID: u.ID, - Tags: tags, - Secret: u.Credentials.Secret, - Metadata: data, - CreatedAt: u.CreatedAt, - UpdatedAt: updatedAt, - UpdatedBy: updatedBy, - Status: u.Status, - Role: &u.Role, - LastName: stringToNullString(u.LastName), - FirstName: stringToNullString(u.FirstName), - Username: stringToNullString(u.Credentials.Username), - ProfilePicture: stringToNullString(u.ProfilePicture), - Email: u.Email, - VerifiedAt: verifiedAt, - AuthProvider: authProvider, + ID: u.ID, + Tags: tags, + Secret: u.Credentials.Secret, + PublicMetadata: publicMetadata, + PrivateMetadata: privateMetadata, + CreatedAt: u.CreatedAt, + UpdatedAt: updatedAt, + UpdatedBy: updatedBy, + Status: u.Status, + Role: &u.Role, + LastName: stringToNullString(u.LastName), + FirstName: stringToNullString(u.FirstName), + Username: stringToNullString(u.Credentials.Username), + ProfilePicture: stringToNullString(u.ProfilePicture), + Email: u.Email, + VerifiedAt: verifiedAt, + AuthProvider: authProvider, }, nil } func ToUser(dbu DBUser) (users.User, error) { - var metadata users.Metadata - if dbu.Metadata != nil { - if err := json.Unmarshal([]byte(dbu.Metadata), &metadata); err != nil { + var publicMetadata, privateMetadata users.Metadata + if dbu.PublicMetadata != nil { + if err := json.Unmarshal([]byte(dbu.PublicMetadata), &publicMetadata); err != nil { + return users.User{}, errors.Wrap(repoerr.ErrMalformedEntity, err) + } + } + if dbu.PrivateMetadata != nil { + if err := json.Unmarshal([]byte(dbu.PrivateMetadata), &privateMetadata); err != nil { return users.User{}, errors.Wrap(repoerr.ErrMalformedEntity, err) } } @@ -583,16 +602,17 @@ func ToUser(dbu DBUser) (users.User, error) { Username: nullStringString(dbu.Username), Secret: dbu.Secret, }, - Email: dbu.Email, - Metadata: metadata, - CreatedAt: dbu.CreatedAt.UTC(), - UpdatedAt: updatedAt, - UpdatedBy: updatedBy, - Status: dbu.Status, - Tags: tags, - ProfilePicture: nullStringString(dbu.ProfilePicture), - VerifiedAt: verifiedAt, - AuthProvider: authProvider, + Email: dbu.Email, + PublicMetadata: publicMetadata, + PrivateMetadata: privateMetadata, + CreatedAt: dbu.CreatedAt.UTC(), + UpdatedAt: updatedAt, + UpdatedBy: updatedBy, + Status: dbu.Status, + Tags: tags, + ProfilePicture: nullStringString(dbu.ProfilePicture), + VerifiedAt: verifiedAt, + AuthProvider: authProvider, } if dbu.Role != nil { user.Role = *dbu.Role @@ -639,11 +659,6 @@ func ToDBUsersPage(pm users.Page) (DBUsersPage, error) { } func PageQuery(pm users.Page) (string, error) { - mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata) - if err != nil { - return "", errors.Wrap(errors.ErrMalformedEntity, err) - } - var query []string if pm.FirstName != "" { query = append(query, "first_name ILIKE '%' || :first_name || '%'") @@ -666,9 +681,8 @@ func PageQuery(pm users.Page) (string, error) { if pm.Role != users.AllRole { query = append(query, "u.role = :role") } - - if mq != "" { - query = append(query, mq) + if len(pm.Metadata) > 0 { + query = append(query, "public_metadata @> :metadata") } if len(pm.IDs) != 0 { diff --git a/users/postgres/users_test.go b/users/postgres/users_test.go index 8ce2f8cca5..2b33fb7779 100644 --- a/users/postgres/users_test.go +++ b/users/postgres/users_test.go @@ -20,7 +20,11 @@ import ( "github.com/stretchr/testify/require" ) -const maxNameSize = 254 +const ( + maxNameSize = 254 + defOrder = "created_at" + defDir = "asc" +) var ( invalidName = strings.Repeat("m", maxNameSize+10) @@ -46,10 +50,11 @@ func TestUsersSave(t *testing.T) { email := first_name + "@example.com" externalUser := users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: namesgen.Generate(), - LastName: namesgen.Generate(), - Metadata: users.Metadata{}, + ID: testsutil.GenerateUUID(t), + FirstName: namesgen.Generate(), + LastName: namesgen.Generate(), + PublicMetadata: users.Metadata{}, + PrivateMetadata: users.Metadata{}, Credentials: users.Credentials{ Username: namesgen.Generate(), }, @@ -72,8 +77,13 @@ func TestUsersSave(t *testing.T) { Username: username, Secret: password, }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, + PublicMetadata: users.Metadata{ + "organization": namesgen.Generate(), + }, + PrivateMetadata: users.Metadata{ + "address": namesgen.Generate(), + }, + Status: users.EnabledStatus, }, err: nil, }, @@ -93,8 +103,13 @@ func TestUsersSave(t *testing.T) { Username: namesgen.Generate(), Secret: password, }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, + PublicMetadata: users.Metadata{ + "organization": namesgen.Generate(), + }, + PrivateMetadata: users.Metadata{ + "address": namesgen.Generate(), + }, + Status: users.EnabledStatus, }, err: errors.ErrEmailAlreadyExists, }, @@ -109,8 +124,13 @@ func TestUsersSave(t *testing.T) { Username: username, Secret: password, }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, + PublicMetadata: users.Metadata{ + "organization": namesgen.Generate(), + }, + PrivateMetadata: users.Metadata{ + "address": namesgen.Generate(), + }, + Status: users.EnabledStatus, }, err: errors.ErrUsernameNotAvailable, }, @@ -125,8 +145,13 @@ func TestUsersSave(t *testing.T) { Username: username, Secret: password, }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, + PublicMetadata: users.Metadata{ + "organization": namesgen.Generate(), + }, + PrivateMetadata: users.Metadata{ + "address": namesgen.Generate(), + }, + Status: users.EnabledStatus, }, err: repoerr.ErrCreateEntity, }, @@ -141,8 +166,13 @@ func TestUsersSave(t *testing.T) { Username: invalidName, Secret: password, }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, + PublicMetadata: users.Metadata{ + "organization": namesgen.Generate(), + }, + PrivateMetadata: users.Metadata{ + "address": namesgen.Generate(), + }, + Status: users.EnabledStatus, }, err: repoerr.ErrCreateEntity, }, @@ -156,7 +186,12 @@ func TestUsersSave(t *testing.T) { Credentials: users.Credentials{ Secret: password, }, - Metadata: users.Metadata{}, + PublicMetadata: users.Metadata{ + "organization": namesgen.Generate(), + }, + PrivateMetadata: users.Metadata{ + "address": namesgen.Generate(), + }, }, err: nil, }, @@ -170,7 +205,12 @@ func TestUsersSave(t *testing.T) { Credentials: users.Credentials{ Username: namesgen.Generate(), }, - Metadata: users.Metadata{}, + PublicMetadata: users.Metadata{ + "organization": namesgen.Generate(), + }, + PrivateMetadata: users.Metadata{ + "address": namesgen.Generate(), + }, }, err: nil, }, @@ -184,7 +224,7 @@ func TestUsersSave(t *testing.T) { Username: username, Secret: password, }, - Metadata: map[string]any{ + PublicMetadata: map[string]any{ "key": make(chan int), }, }, @@ -233,9 +273,10 @@ func TestIsPlatformAdmin(t *testing.T) { Username: username, Secret: password, }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, - Role: users.AdminRole, + PublicMetadata: users.Metadata{}, + PrivateMetadata: users.Metadata{}, + Status: users.EnabledStatus, + Role: users.AdminRole, }, err: nil, }, @@ -250,9 +291,10 @@ func TestIsPlatformAdmin(t *testing.T) { Username: namesgen.Generate(), Secret: password, }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, - Role: users.UserRole, + PublicMetadata: users.Metadata{}, + PrivateMetadata: users.Metadata{}, + Status: users.EnabledStatus, + Role: users.UserRole, }, err: repoerr.ErrNotFound, }, @@ -283,18 +325,24 @@ func TestRetrieveByID(t *testing.T) { Username: namesgen.Generate(), Secret: password, }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, + PublicMetadata: users.Metadata{ + "organization": namesgen.Generate(), + }, + PrivateMetadata: users.Metadata{ + "address": namesgen.Generate(), + }, + Status: users.EnabledStatus, } _, err := repo.Save(context.Background(), user) require.Nil(t, err, fmt.Sprintf("failed to save users %s", user.ID)) externalUser := users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: namesgen.Generate(), - LastName: namesgen.Generate(), - Metadata: users.Metadata{}, + ID: testsutil.GenerateUUID(t), + FirstName: namesgen.Generate(), + LastName: namesgen.Generate(), + PublicMetadata: users.Metadata{}, + PrivateMetadata: users.Metadata{}, Credentials: users.Credentials{ Username: namesgen.Generate(), }, @@ -366,13 +414,13 @@ func TestRetrieveAll(t *testing.T) { Username: namesgen.Generate(), Secret: "", }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, - Tags: []string{"tag1"}, - CreatedAt: baseTime.Add(time.Duration(i) * time.Millisecond), + PublicMetadata: users.Metadata{}, + Status: users.EnabledStatus, + Tags: []string{"tag1"}, + CreatedAt: baseTime.Add(time.Duration(i) * time.Millisecond), } if i%50 == 0 { - user.Metadata = map[string]any{ + user.PublicMetadata = map[string]any{ "key": "value", } user.Role = users.AdminRole @@ -396,19 +444,19 @@ func TestRetrieveAll(t *testing.T) { desc: "retrieve first page of users", pageMeta: users.Page{ Offset: 0, - Limit: 50, + Limit: 1, Role: users.AllRole, Status: users.AllStatus, - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, page: users.UsersPage{ Page: users.Page{ Total: 200, Offset: 0, - Limit: 50, + Limit: 1, }, - Users: items[0:50], + Users: items[0:1], }, err: nil, }, @@ -419,8 +467,8 @@ func TestRetrieveAll(t *testing.T) { Limit: 200, Role: users.AllRole, Status: users.AllStatus, - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, page: users.UsersPage{ Page: users.Page{ @@ -439,8 +487,8 @@ func TestRetrieveAll(t *testing.T) { Limit: 50, Role: users.AllRole, Status: users.AllStatus, - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, page: users.UsersPage{ Page: users.Page{ @@ -476,8 +524,8 @@ func TestRetrieveAll(t *testing.T) { Limit: 1000, Role: users.AllRole, Status: users.AllStatus, - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, page: users.UsersPage{ Page: users.Page{ @@ -510,8 +558,8 @@ func TestRetrieveAll(t *testing.T) { Limit: 3, Role: users.AllRole, Status: users.AllStatus, - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, page: users.UsersPage{ Page: users.Page{ @@ -550,8 +598,8 @@ func TestRetrieveAll(t *testing.T) { Limit: 3, Role: users.AllRole, Status: users.AllStatus, - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, page: users.UsersPage{ Page: users.Page{ @@ -571,8 +619,8 @@ func TestRetrieveAll(t *testing.T) { Limit: 3, Role: users.AllRole, Status: users.AllStatus, - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, page: users.UsersPage{ Page: users.Page{ @@ -591,8 +639,8 @@ func TestRetrieveAll(t *testing.T) { Offset: 0, Limit: 200, Role: users.AllRole, - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, page: users.UsersPage{ Page: users.Page{ @@ -611,8 +659,8 @@ func TestRetrieveAll(t *testing.T) { Offset: 0, Limit: 200, Role: users.AllRole, - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, page: users.UsersPage{ Page: users.Page{ @@ -630,8 +678,8 @@ func TestRetrieveAll(t *testing.T) { Offset: 0, Limit: 200, Role: users.AllRole, - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, page: users.UsersPage{ Page: users.Page{ @@ -680,7 +728,7 @@ func TestRetrieveAll(t *testing.T) { }, }, { - desc: "retrieve with metadata", + desc: "retrieve with public metadata", pageMeta: users.Page{ Metadata: map[string]any{ "key": "value", @@ -689,8 +737,8 @@ func TestRetrieveAll(t *testing.T) { Limit: 200, Role: users.AllRole, Status: users.AllStatus, - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, page: users.UsersPage{ Page: users.Page{ @@ -730,8 +778,8 @@ func TestRetrieveAll(t *testing.T) { Offset: 0, Limit: 200, Status: users.AllStatus, - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, page: users.UsersPage{ Page: users.Page{ @@ -795,7 +843,8 @@ func TestSearch(t *testing.T) { Credentials: users.Credentials{ Username: user.Credentials.Username, }, - CreatedAt: user.CreatedAt, + PublicMetadata: user.PublicMetadata, + CreatedAt: user.CreatedAt, }) } @@ -841,8 +890,8 @@ func TestSearch(t *testing.T) { desc: "with limit only", page: users.Page{ Limit: 10, - Order: "name", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, response: users.UsersPage{ Users: expectedUsers[0:10], @@ -858,15 +907,17 @@ func TestSearch(t *testing.T) { desc: "retrieve all users", page: users.Page{ Offset: 0, - Limit: nUsers, + Limit: 10, + Order: defOrder, + Dir: defDir, }, response: users.UsersPage{ Page: users.Page{ Total: nUsers, Offset: 0, - Limit: nUsers, + Limit: 10, }, - Users: expectedUsers, + Users: expectedUsers[:10], }, }, { @@ -874,8 +925,8 @@ func TestSearch(t *testing.T) { page: users.Page{ Offset: 10, Limit: 10, - Order: "name", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, response: users.UsersPage{ Users: expectedUsers[10:20], @@ -907,8 +958,8 @@ func TestSearch(t *testing.T) { page: users.Page{ Offset: 190, Limit: 50, - Order: "name", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, response: users.UsersPage{ Page: users.Page{ @@ -926,7 +977,7 @@ func TestSearch(t *testing.T) { Offset: 0, Limit: 10, Order: "first_name", - Dir: "asc", + Dir: defDir, }, response: users.UsersPage{ Users: findUsers(expectedUsers, expectedUsers[0].FirstName[:4], 0, 10), @@ -979,7 +1030,7 @@ func TestSearch(t *testing.T) { Offset: 0, Limit: 10, Order: "first_name", - Dir: "asc", + Dir: defDir, }, response: users.UsersPage{ Users: findUsers(expectedUsers, expectedUsers[0].FirstName[:4], 0, 10), @@ -1046,7 +1097,7 @@ func TestSearch(t *testing.T) { desc: "with name in asc order", page: users.Page{ Order: "first_name", - Dir: "asc", + Dir: defDir, FirstName: expectedUsers[0].FirstName[:1], Offset: 0, Limit: 10, @@ -1071,7 +1122,7 @@ func TestSearch(t *testing.T) { page: users.Page{ LastName: expectedUsers[0].LastName[:1], Order: "last_name", - Dir: "asc", + Dir: defDir, }, response: users.UsersPage{ Users: []users.User{expectedUsers[0]}, @@ -1088,7 +1139,7 @@ func TestSearch(t *testing.T) { page: users.Page{ Username: expectedUsers[0].Credentials.Username[:1], Order: "username", - Dir: "asc", + Dir: defDir, }, response: users.UsersPage{ Users: []users.User{expectedUsers[0]}, @@ -1112,7 +1163,7 @@ func TestSearch(t *testing.T) { assert.Equal(t, c.response.Total, response.Total) assert.Equal(t, c.response.Limit, response.Limit) assert.Equal(t, c.response.Offset, response.Offset) - assert.ElementsMatch(t, response.Users, c.response.Users) + assert.ElementsMatch(t, response.Users, c.response.Users, fmt.Sprintf("expected %v got %v\n", c.response.Users, response.Users)) default: assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) } @@ -1290,23 +1341,23 @@ func TestUpdate(t *testing.T) { err error }{ { - desc: "update metadata for enabled user", - update: "metadata", + desc: "update public metadata for enabled user", + update: "public_metadata", userID: user1.ID, userReq: users.UserReq{ - Metadata: &updatedMetadata, + PublicMetadata: &updatedMetadata, }, userRes: users.User{ - Metadata: updatedMetadata, + PublicMetadata: updatedMetadata, }, err: nil, }, { - desc: "update malformed metadata for enabled user", - update: "metadata", + desc: "update malformed public metadata for enabled user", + update: "public_metadata", userID: user1.ID, userReq: users.UserReq{ - Metadata: &malformedMetadata, + PublicMetadata: &malformedMetadata, }, err: repoerr.ErrMalformedEntity, }, @@ -1315,10 +1366,10 @@ func TestUpdate(t *testing.T) { update: "metadata", userID: user3.ID, userReq: users.UserReq{ - Metadata: &users.Metadata{}, + PublicMetadata: &users.Metadata{}, }, userRes: users.User{ - Metadata: users.Metadata{}, + PublicMetadata: users.Metadata{}, }, err: nil, }, @@ -1327,7 +1378,7 @@ func TestUpdate(t *testing.T) { update: "metadata", userID: user2.ID, userReq: users.UserReq{ - Metadata: &updatedMetadata, + PublicMetadata: &updatedMetadata, }, err: repoerr.ErrNotFound, }, @@ -1363,11 +1414,11 @@ func TestUpdate(t *testing.T) { err: repoerr.ErrNotFound, }, { - desc: "update metadata for invalid user", - update: "metadata", + desc: "update public metadata for invalid user", + update: "public_metadata", userID: testsutil.GenerateUUID(t), userReq: users.UserReq{ - Metadata: &updatedMetadata, + PublicMetadata: &updatedMetadata, }, err: repoerr.ErrNotFound, }, @@ -1510,8 +1561,10 @@ func TestUpdate(t *testing.T) { assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) if err == nil { switch c.update { - case "metadata": - assert.Equal(t, c.userRes.Metadata, expected.Metadata) + case "public_metadata": + assert.Equal(t, c.userRes.PublicMetadata, expected.PublicMetadata) + case "private_metadata": + assert.Equal(t, c.userRes.PrivateMetadata, expected.PrivateMetadata) case "first_name": assert.Equal(t, c.userRes.FirstName, expected.FirstName) case "last_name": @@ -1787,6 +1840,7 @@ func TestRetrieveByIDs(t *testing.T) { baseTime := time.Now().UTC().Truncate(time.Millisecond) for i := 0; i < num; i++ { user := generateUserWithTime(t, users.EnabledStatus, repo, baseTime.Add(time.Duration(i)*time.Millisecond)) + user.PrivateMetadata = nil items = append(items, user) } @@ -1806,8 +1860,8 @@ func TestRetrieveByIDs(t *testing.T) { Offset: 0, Limit: 10, IDs: getIDs(items[0:3]), - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, response: users.UsersPage{ Page: users.Page{ @@ -1840,8 +1894,8 @@ func TestRetrieveByIDs(t *testing.T) { page: users.Page{ Offset: 10, IDs: getIDs(items[0:20]), - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, response: users.UsersPage{ Page: users.Page{ @@ -1858,8 +1912,8 @@ func TestRetrieveByIDs(t *testing.T) { page: users.Page{ Limit: 10, IDs: getIDs(items[0:20]), - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, response: users.UsersPage{ Page: users.Page{ @@ -1894,8 +1948,8 @@ func TestRetrieveByIDs(t *testing.T) { Offset: 15, Limit: 10, IDs: getIDs(items[0:20]), - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, response: users.UsersPage{ Page: users.Page{ @@ -1931,8 +1985,8 @@ func TestRetrieveByIDs(t *testing.T) { Limit: 10, FirstName: items[0].FirstName, IDs: getIDs(items[0:20]), - Order: "created_at", - Dir: "asc", + Order: defOrder, + Dir: defDir, }, response: users.UsersPage{ Page: users.Page{ @@ -1949,7 +2003,7 @@ func TestRetrieveByIDs(t *testing.T) { page: users.Page{ Offset: 0, Limit: 10, - Metadata: items[0].Metadata, + Metadata: items[0].PublicMetadata, IDs: getIDs(items[0:20]), }, response: users.UsersPage{ @@ -1980,7 +2034,7 @@ func TestRetrieveByIDs(t *testing.T) { }, Users: []users.User(nil), }, - err: errors.ErrMalformedEntity, + err: repoerr.ErrViewEntity, }, } @@ -2042,7 +2096,7 @@ func TestRetrieveByEmail(t *testing.T) { assert.Equal(t, user.ID, usr.ID) assert.Equal(t, user.FirstName, usr.FirstName) assert.Equal(t, user.LastName, usr.LastName) - assert.Equal(t, user.Metadata, usr.Metadata) + assert.Equal(t, user.PublicMetadata, usr.PublicMetadata) assert.Equal(t, user.Email, usr.Email) assert.Equal(t, user.Credentials.Username, usr.Credentials.Username) assert.Equal(t, user.Status, usr.Status) @@ -2093,7 +2147,7 @@ func TestRetrieveByUsername(t *testing.T) { assert.Equal(t, user.ID, usr.ID) assert.Equal(t, user.FirstName, usr.FirstName) assert.Equal(t, user.LastName, usr.LastName) - assert.Equal(t, user.Metadata, usr.Metadata) + assert.Equal(t, user.PublicMetadata, usr.PublicMetadata) assert.Equal(t, user.Email, usr.Email) assert.Equal(t, user.Credentials.Username, usr.Credentials.Username) assert.Equal(t, user.Status, usr.Status) @@ -2136,8 +2190,11 @@ func generateUserWithTime(t *testing.T, status users.Status, repo users.Reposito Secret: testsutil.GenerateUUID(t), }, Tags: namesgen.GenerateMultiple(5), - Metadata: users.Metadata{ - "name": namesgen.Generate(), + PublicMetadata: users.Metadata{ + "organization": namesgen.Generate(), + }, + PrivateMetadata: users.Metadata{ + "address": namesgen.Generate(), }, Status: status, CreatedAt: createdAt, diff --git a/users/service.go b/users/service.go index 2dbcb57ea3..a7b850ba2e 100644 --- a/users/service.go +++ b/users/service.go @@ -238,10 +238,11 @@ func (svc service) View(ctx context.Context, session authn.Session, id string) ( if session.UserID != id { if err := svc.checkSuperAdmin(ctx, session); err != nil { return User{ - FirstName: user.FirstName, - LastName: user.LastName, - ID: user.ID, - Credentials: Credentials{Username: user.Credentials.Username}, + FirstName: user.FirstName, + LastName: user.LastName, + ID: user.ID, + PublicMetadata: user.PublicMetadata, + Credentials: Credentials{Username: user.Credentials.Username}, }, nil } } diff --git a/users/service_test.go b/users/service_test.go index 8c6ef33322..0747c9f835 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -34,14 +34,15 @@ var ( validCMetadata = users.Metadata{"role": "user"} userID = "d8dd12ef-aa2a-43fe-8ef2-2e4fe514360f" user = users.User{ - ID: userID, - FirstName: "firstname", - LastName: "lastname", - Tags: []string{"tag1", "tag2"}, - Credentials: users.Credentials{Username: "username", Secret: secret}, - Email: "useremail@email.com", - Metadata: validCMetadata, - Status: users.EnabledStatus, + ID: userID, + FirstName: "firstname", + LastName: "lastname", + Tags: []string{"tag1", "tag2"}, + Credentials: users.Credentials{Username: "username", Secret: secret}, + Email: "useremail@email.com", + PublicMetadata: validCMetadata, + PrivateMetadata: validCMetadata, + Status: users.EnabledStatus, } basicUser = users.User{ Credentials: users.Credentials{ @@ -127,7 +128,10 @@ func TestRegister(t *testing.T) { Credentials: users.Credentials{ Secret: secret, }, - Metadata: users.Metadata{ + PublicMetadata: users.Metadata{ + "name": "newuserwithallfields", + }, + PrivateMetadata: users.Metadata{ "name": "newuserwithallfields", }, Status: users.EnabledStatus, @@ -518,7 +522,8 @@ func TestUpdateUser(t *testing.T) { updateFirstName := "Updated user" user1.FirstName = updateFirstName updatedMetadata := users.Metadata{"role": "test"} - user2.Metadata = updatedMetadata + user2.PublicMetadata = updatedMetadata + user2.PrivateMetadata = updatedMetadata adminID := testsutil.GenerateUUID(t) cases := []struct { @@ -547,10 +552,21 @@ func TestUpdateUser(t *testing.T) { err: nil, }, { - desc: "update metadata successfully as normal user", + desc: "update public metadata successfully as normal user", userID: user2.ID, userReq: users.UserReq{ - Metadata: &updatedMetadata, + PublicMetadata: &updatedMetadata, + }, + session: authn.Session{UserID: user2.ID}, + updateResponse: user2, + token: validToken, + err: nil, + }, + { + desc: "update private metadata successfully as normal user", + userID: user2.ID, + userReq: users.UserReq{ + PrivateMetadata: &updatedMetadata, }, session: authn.Session{UserID: user2.ID}, updateResponse: user2, @@ -584,10 +600,10 @@ func TestUpdateUser(t *testing.T) { err: nil, }, { - desc: "update user metadata as admin successfully", + desc: "update user public metadata as admin successfully", userID: user2.ID, userReq: users.UserReq{ - Metadata: &updatedMetadata, + PublicMetadata: &updatedMetadata, }, session: authn.Session{UserID: adminID, SuperAdmin: true}, updateResponse: user2, @@ -651,18 +667,18 @@ func TestUpdateUser(t *testing.T) { desc: "update user metadata with external auth provider should succeed", userID: user2.ID, userReq: users.UserReq{ - Metadata: &updatedMetadata, + PublicMetadata: &updatedMetadata, }, session: authn.Session{UserID: user2.ID}, retrieveByIDResp: users.User{ - ID: user2.ID, - AuthProvider: "google", - Metadata: updatedMetadata, + ID: user2.ID, + AuthProvider: "google", + PublicMetadata: updatedMetadata, }, updateResponse: users.User{ - ID: user2.ID, - AuthProvider: "google", - Metadata: updatedMetadata, + ID: user2.ID, + AuthProvider: "google", + PublicMetadata: updatedMetadata, }, token: validToken, err: nil, diff --git a/users/users.go b/users/users.go index 4a269f4215..a15b898b77 100644 --- a/users/users.go +++ b/users/users.go @@ -15,22 +15,23 @@ import ( ) type User struct { - ID string `json:"id"` - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` - Tags []string `json:"tags,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` - Status Status `json:"status"` // 0 for enabled, 1 for disabled - Role Role `json:"role"` // 0 for normal user, 1 for admin - ProfilePicture string `json:"profile_picture,omitempty"` // profile picture URL - Credentials Credentials `json:"credentials,omitempty"` - Permissions []string `json:"permissions,omitempty"` - Email string `json:"email,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - UpdatedBy string `json:"updated_by,omitempty"` - VerifiedAt time.Time `json:"verified_at,omitempty"` - AuthProvider string `json:"auth_provider,omitempty"` + ID string `json:"id"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + Tags []string `json:"tags,omitempty"` + PublicMetadata Metadata `json:"public_metadata,omitempty"` + PrivateMetadata Metadata `json:"private_metadata,omitempty"` + Status Status `json:"status"` // 0 for enabled, 1 for disabled + Role Role `json:"role"` // 0 for normal user, 1 for admin + ProfilePicture string `json:"profile_picture,omitempty"` // profile picture URL + Credentials Credentials `json:"credentials,omitempty"` + Permissions []string `json:"permissions,omitempty"` + Email string `json:"email,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + VerifiedAt time.Time `json:"verified_at,omitempty"` + AuthProvider string `json:"auth_provider,omitempty"` } type Credentials struct { @@ -47,13 +48,14 @@ type UsersPage struct { type Metadata map[string]any type UserReq struct { - FirstName *string `json:"first_name,omitempty"` - LastName *string `json:"last_name,omitempty"` - Metadata *Metadata `json:"metadata,omitempty"` - Tags *[]string `json:"tags,omitempty"` - ProfilePicture *string `json:"profile_picture,omitempty"` - UpdatedBy *string `json:"updated_by,omitempty"` - UpdatedAt *time.Time `json:"updated_at,omitempty"` + FirstName *string `json:"first_name,omitempty"` + LastName *string `json:"last_name,omitempty"` + PublicMetadata *Metadata `json:"public_metadata,omitempty"` + PrivateMetadata *Metadata `json:"private_metadata,omitempty"` + Tags *[]string `json:"tags,omitempty"` + ProfilePicture *string `json:"profile_picture,omitempty"` + UpdatedBy *string `json:"updated_by,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` } // MembersPage contains page related metadata as well as list of members that