SMQ-3108 - Add support for public and private metadata for users and clients (#3155)

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
This commit is contained in:
Felix Gateru
2026-01-22 10:55:25 +03:00
committed by GitHub
parent 9293de7636
commit 5b913dd46b
40 changed files with 1045 additions and 533 deletions
+45 -35
View File
@@ -161,17 +161,18 @@ type User struct {
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"`
PublicMetadata *structpb.Struct `protobuf:"bytes,6,opt,name=public_metadata,json=publicMetadata,proto3" json:"public_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
}
@@ -241,6 +242,13 @@ func (x *User) GetMetadata() *structpb.Struct {
return nil
}
func (x *User) GetPublicMetadata() *structpb.Struct {
if x != nil {
return x.PublicMetadata
}
return nil
}
func (x *User) GetStatus() uint32 {
if x != nil {
return x.Status
@@ -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\"\xfd\x04\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" +
"\bmetadata\x18\x05 \x01(\v2\x17.google.protobuf.StructR\bmetadata\x12@\n" +
"\x0fpublic_metadata\x18\x06 \x01(\v2\x17.google.protobuf.StructR\x0epublicMetadata\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"
@@ -381,16 +390,17 @@ 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, // 2: users.v1.User.public_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() }
+21 -4
View File
@@ -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.
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.
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.
metadata:
type: object
example: { "model": "example" }
description: Arbitrary, object-encoded client's data, private to the client.
status:
type: string
description: Client Status
@@ -1113,9 +1125,14 @@ components:
metadata:
type: object
example: { "role": "general" }
description: Arbitrary, object-encoded client's data.
description: Arbitrary, object-encoded client's data, private to the client.
public_metadata:
type: object
example: { "role": "general" }
description: Arbitrary, object-encoded client's data.
required:
- name
- public_metadata
- metadata
ClientTags:
+10 -2
View File
@@ -712,6 +712,10 @@ components:
- username
- secret
metadata:
type: object
example: { "domain": "example.com" }
description: Arbitrary, object-encoded user's data private to the user.
public_metadata:
type: object
example: { "domain": "example.com" }
description: Arbitrary, object-encoded user's data.
@@ -768,6 +772,10 @@ components:
example: john_doe
description: User's username for example john_doe for Mr John Doe.
metadata:
type: object
example: { "address": "example" }
description: Arbitrary, object-encoded user's data private to the user.
public_metadata:
type: object
example: { "address": "example" }
description: Arbitrary, object-encoded user's data.
@@ -1252,7 +1260,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
@@ -1334,7 +1342,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, metadata and public_metadata of user to be update
required: true
content:
application/json:
+93 -7
View File
@@ -28,6 +28,8 @@ var (
relation = "administrator"
all = "all"
conntype = `["publish","subscribe"]`
errEndJSONInput = errors.New("unexpected end of JSON input")
)
var client = smqsdk.Client{
@@ -295,6 +297,8 @@ func TestUpdateClientCmd(t *testing.T) {
newTagsJson := "[\"tag1\", \"tag2\"]"
newTagString := []string{"tag1", "tag2"}
newNameandMeta := "{\"name\": \"clientName\", \"metadata\": {\"role\": \"general\"}}"
newMetadata := "{\"metadata\": {\"role\": \"general\"}}"
newPublicMeta := "{\"public_metadata\": {\"role\": \"general\"}}"
newSecret := "secret"
cases := []struct {
@@ -305,6 +309,26 @@ func TestUpdateClientCmd(t *testing.T) {
client smqsdk.Client
logType outputLog
}{
{
desc: "update client name and public metadata successfully",
args: []string{
client.ID,
updateCmd,
newNameandMeta,
domainID,
token,
},
client: smqsdk.Client{
Name: "clientName",
PublicMetadata: map[string]any{
"role": "general",
},
ID: client.ID,
DomainID: client.DomainID,
Status: client.Status,
},
logType: entityLog,
},
{
desc: "update client name and metadata successfully",
args: []string{
@@ -317,9 +341,7 @@ func TestUpdateClientCmd(t *testing.T) {
client: smqsdk.Client{
Name: "clientName",
Metadata: map[string]any{
"metadata": map[string]any{
"role": "general",
},
"role": "general",
},
ID: client.ID,
DomainID: client.DomainID,
@@ -327,6 +349,70 @@ func TestUpdateClientCmd(t *testing.T) {
},
logType: entityLog,
},
{
desc: "update client public metadata successfully",
args: []string{
client.ID,
updateCmd,
newPublicMeta,
domainID,
token,
},
client: smqsdk.Client{
PublicMetadata: map[string]any{
"role": "general",
},
ID: client.ID,
DomainID: client.DomainID,
Status: client.Status,
},
logType: entityLog,
},
{
desc: "update client metadata successfully",
args: []string{
client.ID,
updateCmd,
newMetadata,
domainID,
token,
},
client: smqsdk.Client{
Metadata: map[string]any{
"role": "general",
},
ID: client.ID,
DomainID: client.DomainID,
Status: client.Status,
},
logType: entityLog,
},
{
desc: "update client public metadata with invalid json",
args: []string{
client.ID,
updateCmd,
"{\"public_metadata\": {\"role\": \"general\"}",
domainID,
token,
},
sdkErr: errors.NewSDKError(errEndJSONInput),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errEndJSONInput),
logType: errLog,
},
{
desc: "update client metadata with invalid json",
args: []string{
client.ID,
updateCmd,
"{\"metadata\": {\"role\": \"general\"}",
domainID,
token,
},
sdkErr: errors.NewSDKError(errEndJSONInput),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errEndJSONInput),
logType: errLog,
},
{
desc: "update client name and metadata with invalid json",
args: []string{
@@ -336,8 +422,8 @@ func TestUpdateClientCmd(t *testing.T) {
domainID,
token,
},
sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")),
sdkErr: errors.NewSDKError(errEndJSONInput),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errEndJSONInput),
logType: errLog,
},
{
@@ -383,8 +469,8 @@ func TestUpdateClientCmd(t *testing.T) {
token,
},
logType: errLog,
sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")),
sdkErr: errors.NewSDKError(errEndJSONInput),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errEndJSONInput),
},
{
desc: "update client tags with invalid client id",
+53 -5
View File
@@ -519,6 +519,8 @@ func TestUpdateUserCmd(t *testing.T) {
newRole := "administrator"
newTagsJSON := "[\"tag1\", \"tag2\"]"
newNameMetadataJSON := "{\"name\":\"new name\", \"metadata\":{\"key\": \"value\"}}"
newMetadataJSON := "{\"metadata\":{\"key\": \"value\"}}"
newPublicMetadataJSON := "{\"public_metadata\":{\"key\": \"value\"}}"
cases := []struct {
desc string
@@ -550,8 +552,8 @@ func TestUpdateUserCmd(t *testing.T) {
"[\"tag1\", \"tag2\"",
validToken,
},
sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")),
sdkErr: errors.NewSDKError(errEndJSONInput),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errEndJSONInput),
logType: errLog,
},
{
@@ -567,6 +569,52 @@ func TestUpdateUserCmd(t *testing.T) {
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
},
{
desc: "update user public metadata successfully",
args: []string{
userID,
updateCmd,
newPublicMetadataJSON,
validToken,
},
logType: entityLog,
user: user,
},
{
desc: "update user public metadata with invalid json",
args: []string{
userID,
updateCmd,
"{\"public_metadata\":{\"key\": \"value\"",
validToken,
},
sdkErr: errors.NewSDKError(errEndJSONInput),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errEndJSONInput),
logType: errLog,
},
{
desc: "update user metadata successfully",
args: []string{
userID,
updateCmd,
newMetadataJSON,
validToken,
},
logType: entityLog,
user: user,
},
{
desc: "update user metadata with invalid json",
args: []string{
userID,
updateCmd,
"{\"metadata\":{\"key\": \"value\"",
validToken,
},
sdkErr: errors.NewSDKError(errEndJSONInput),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errEndJSONInput),
logType: errLog,
},
{
desc: "update user email successfully",
args: []string{
@@ -623,8 +671,8 @@ func TestUpdateUserCmd(t *testing.T) {
"{\"name\":\"new name\", \"metadata\":{\"key\": \"value\"}",
validToken,
},
sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")),
sdkErr: errors.NewSDKError(errEndJSONInput),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errEndJSONInput),
logType: errLog,
},
{
@@ -708,7 +756,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)
+4 -3
View File
@@ -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,
Metadata: req.Metadata,
PublicMetadata: req.PublicMetadata,
}
client, err := svc.Update(ctx, session, cli)
if err != nil {
+26 -24
View File
@@ -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,
Metadata: 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{},
Metadata: 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,
+5 -4
View File
@@ -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"`
Metadata map[string]any `json:"metadata,omitempty"`
PublicMetadata map[string]any `json:"public_metadata,omitempty"`
Tags []string `json:"tags,omitempty"`
}
func (req updateClientReq) validate() error {
-1
View File
@@ -17,7 +17,6 @@ import (
const (
valid = "valid"
invalid = "invalid"
name = "client"
)
var validID = testsutil.GenerateUUID(&testing.T{})
+13 -12
View File
@@ -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"`
Metadata Metadata `json:"metadata,omitempty"`
PublicMetadata Metadata `json:"public_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"`
+9
View File
@@ -70,6 +70,9 @@ func (cce createClientEvent) Encode() (map[string]any, error) {
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
}
@@ -110,6 +113,9 @@ func (uce updateClientEvent) Encode() (map[string]any, error) {
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
}
@@ -174,6 +180,9 @@ func (vce viewClientEvent) Encode() (map[string]any, error) {
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
}
+1 -1
View File
@@ -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 {
+58 -48
View File
@@ -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, metadata, public_metadata, created_at, updated_at, updated_by, status)
VALUES (:id, :name, :tags, :domain_id, :parent_group_id, :identity, :secret, :metadata, :public_metadata, :created_at, :updated_at, :updated_by, :status)
RETURNING id, name, tags, identity, secret, metadata, public_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, metadata, public_metadata, created_at, updated_at, updated_by, status
FROM clients
WHERE secret = :secret AND status = %d`, clients.EnabledStatus)
switch prefix {
@@ -139,6 +139,9 @@ func (repo *clientRepo) Update(ctx context.Context, client clients.Client) (clie
if client.Name != "" {
query = append(query, "name = :name,")
}
if client.PublicMetadata != nil {
query = append(query, "public_metadata = :public_metadata,")
}
if client.Metadata != nil {
query = append(query, "metadata = :metadata,")
}
@@ -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, metadata, public_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, metadata, 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, metadata, 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, metadata, 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, metadata, 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, metadata, public_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,
@@ -940,7 +943,7 @@ func (repo *clientRepo) SearchClients(ctx context.Context, pm clients.Page) (cli
tq := query
query = applyOrdering(query, pm)
q := fmt.Sprintf(`SELECT c.id, c.name, c.created_at, c.updated_at FROM clients c %s LIMIT :limit OFFSET :offset;`, query)
q := fmt.Sprintf(`SELECT c.id, c.name, c.public_metadata, c.created_at, c.updated_at FROM clients c %s LIMIT :limit OFFSET :offset;`, query)
dbPage, err := ToDBClientsPage(pm)
if err != nil {
@@ -1036,6 +1039,7 @@ type DBClient struct {
ParentGroup sql.NullString `db:"parent_group_id,omitempty"`
Secret string `db:"secret"`
Metadata []byte `db:"metadata,omitempty"`
PublicMetadata []byte `db:"public_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("{}")
publicMetadata := []byte("{}")
if len(c.PublicMetadata) > 0 {
b, err := json.Marshal(c.PublicMetadata)
if err != nil {
return DBClient{}, errors.Wrap(repoerr.ErrMalformedEntity, err)
}
publicMetadata = b
}
metadata := []byte("{}")
if len(c.Metadata) > 0 {
b, err := json.Marshal(c.Metadata)
if err != nil {
return DBClient{}, errors.Wrap(repoerr.ErrMalformedEntity, err)
}
data = b
metadata = 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,
Metadata: metadata,
PublicMetadata: publicMetadata,
CreatedAt: c.CreatedAt,
UpdatedAt: updatedAt,
UpdatedBy: updatedBy,
Status: c.Status,
}, nil
}
func ToClient(t DBClient) (clients.Client, error) {
var metadata clients.Metadata
var publicMetadata, metadata 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.Metadata != nil {
if err := json.Unmarshal(t.Metadata, &metadata); err != nil {
return clients.Client{}, errors.Wrap(errors.ErrMalformedEntity, err)
if err := json.Unmarshal([]byte(t.Metadata), &metadata); err != nil {
return clients.Client{}, errors.Wrap(repoerr.ErrMalformedEntity, err)
}
}
@@ -1142,6 +1160,7 @@ func ToClient(t DBClient) (clients.Client, error) {
Secret: t.Secret,
},
Metadata: metadata,
PublicMetadata: publicMetadata,
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)})
+206 -98
View File
@@ -44,12 +44,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"},
Metadata: map[string]any{"key": "value"},
CreatedAt: time.Now().UTC().Truncate(time.Microsecond),
Status: clients.EnabledStatus,
}
invalidID = strings.Repeat("a", 37)
directAccess = "direct"
@@ -134,8 +135,9 @@ func TestClientsSave(t *testing.T) {
Identity: clientIdentity,
Secret: secret,
},
Metadata: clients.Metadata{},
Status: clients.EnabledStatus,
PublicMetadata: map[string]any{"key": "value"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
},
err: nil,
@@ -150,8 +152,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"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
{
ID: testsutil.GenerateUUID(t),
@@ -160,8 +163,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"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
{
ID: testsutil.GenerateUUID(t),
@@ -170,8 +174,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"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
},
err: nil,
@@ -187,8 +192,9 @@ func TestClientsSave(t *testing.T) {
Identity: clientIdentity,
Secret: secret,
},
Metadata: clients.Metadata{},
Status: clients.EnabledStatus,
PublicMetadata: map[string]any{"key": "value"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
},
err: errClientSecretNotAvailable,
@@ -203,8 +209,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"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
{
ID: testsutil.GenerateUUID(t),
@@ -214,8 +221,9 @@ func TestClientsSave(t *testing.T) {
Identity: clientIdentity,
Secret: secret,
},
Metadata: clients.Metadata{},
Status: clients.EnabledStatus,
PublicMetadata: map[string]any{"key": "value"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
},
err: errClientSecretNotAvailable,
@@ -230,8 +238,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"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
},
err: nil,
@@ -247,8 +256,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"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
},
err: repoerr.ErrCreateEntity,
@@ -263,8 +273,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"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
{
ID: invalidName,
@@ -273,8 +284,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"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
},
err: repoerr.ErrCreateEntity,
@@ -290,8 +302,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"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
},
err: repoerr.ErrCreateEntity,
@@ -306,8 +319,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"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
},
err: repoerr.ErrCreateEntity,
@@ -322,8 +336,9 @@ func TestClientsSave(t *testing.T) {
Identity: invalidName,
Secret: testsutil.GenerateUUID(t),
},
Metadata: clients.Metadata{},
Status: clients.EnabledStatus,
PublicMetadata: map[string]any{"key": "value"},
Metadata: map[string]any{"key": "value"},
Status: clients.EnabledStatus,
},
},
err: repoerr.ErrCreateEntity,
@@ -339,7 +354,8 @@ func TestClientsSave(t *testing.T) {
Identity: "",
Secret: testsutil.GenerateUUID(t),
},
Metadata: clients.Metadata{},
PublicMetadata: map[string]any{"key": "value"},
Metadata: map[string]any{"key": "value"},
},
},
err: nil,
@@ -354,11 +370,29 @@ func TestClientsSave(t *testing.T) {
Identity: "missing-client-secret@example.com",
Secret: "",
},
Metadata: clients.Metadata{},
PublicMetadata: map[string]any{"key": "value"},
Metadata: map[string]any{"key": "value"},
},
},
err: nil,
},
{
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 metadata",
clients: []clients.Client{
@@ -369,7 +403,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),
},
},
@@ -380,12 +414,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"},
Metadata: map[string]any{},
CreatedAt: validTimestamp,
Status: clients.EnabledStatus,
},
},
err: nil,
@@ -419,9 +454,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{},
Metadata: clients.Metadata{},
Status: clients.EnabledStatus,
}
_, err := repo.Save(context.Background(), client)
@@ -488,9 +524,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))
})
}
}
@@ -508,8 +546,13 @@ func TestRetrieveByID(t *testing.T) {
Identity: clientIdentity,
Secret: testsutil.GenerateUUID(t),
},
Metadata: clients.Metadata{},
Status: clients.EnabledStatus,
PublicMetadata: clients.Metadata{
"key": "value",
},
Metadata: clients.Metadata{
"key": "value",
},
Status: clients.EnabledStatus,
}
_, err := repo.Save(context.Background(), client)
@@ -547,6 +590,7 @@ 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.PublicMetadata, cli.PublicMetadata)
assert.Equal(t, client.Metadata, cli.Metadata)
assert.Equal(t, client.Credentials.Identity, cli.Credentials.Identity)
assert.Equal(t, client.Credentials.Secret, cli.Credentials.Secret)
@@ -577,11 +621,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"},
Metadata: map[string]any{"key": "value"},
UpdatedAt: validTimestamp,
UpdatedBy: testsutil.GenerateUUID(t),
},
err: nil,
},
@@ -596,6 +641,17 @@ func TestUpdate(t *testing.T) {
},
err: nil,
},
{
desc: "update client public metadata",
update: "public_metadata",
client: clients.Client{
ID: validClient.ID,
PublicMetadata: map[string]any{"key1": "value1"},
UpdatedAt: validTimestamp,
UpdatedBy: testsutil.GenerateUUID(t),
},
err: nil,
},
{
desc: "update client metadata",
update: "metadata",
@@ -611,11 +667,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"},
Metadata: map[string]any{"key": "value"},
UpdatedAt: validTimestamp,
UpdatedBy: testsutil.GenerateUUID(t),
},
err: repoerr.ErrNotFound,
},
@@ -623,10 +680,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"},
Metadata: map[string]any{"key": "value"},
UpdatedAt: validTimestamp,
UpdatedBy: testsutil.GenerateUUID(t),
},
err: repoerr.ErrNotFound,
},
@@ -643,9 +701,12 @@ 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.PublicMetadata, client.PublicMetadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client.PublicMetadata, client.PublicMetadata))
assert.Equal(t, tc.client.Metadata, client.Metadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client.Metadata, client.Metadata))
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 "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 "metadata":
assert.Equal(t, tc.client.Metadata, client.Metadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client.Metadata, client.Metadata))
}
@@ -936,7 +997,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,
@@ -1059,7 +1120,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,
@@ -1116,6 +1177,8 @@ func TestRetrieveAll(t *testing.T) {
pm: clients.Page{
Offset: 50,
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1129,16 +1192,18 @@ func TestRetrieveAll(t *testing.T) {
{
desc: "with limit only",
pm: clients.Page{
Limit: 50,
Limit: 10,
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
Total: nClients,
Offset: 0,
Limit: 50,
Limit: 10,
},
Clients: expectedClients[:50],
Clients: expectedClients[:10],
},
},
{
@@ -1147,6 +1212,8 @@ func TestRetrieveAll(t *testing.T) {
Offset: 0,
Limit: nClients,
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1163,6 +1230,8 @@ func TestRetrieveAll(t *testing.T) {
Offset: 50,
Limit: 50,
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1179,6 +1248,8 @@ func TestRetrieveAll(t *testing.T) {
Offset: 1000,
Limit: 50,
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1195,6 +1266,8 @@ func TestRetrieveAll(t *testing.T) {
Offset: 170,
Limit: 50,
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1206,12 +1279,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: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1231,6 +1306,8 @@ func TestRetrieveAll(t *testing.T) {
"faculty": namegen.Generate(),
},
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1250,6 +1327,8 @@ func TestRetrieveAll(t *testing.T) {
"faculty": make(chan int),
},
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1268,6 +1347,8 @@ func TestRetrieveAll(t *testing.T) {
Limit: nClients,
Name: expectedClients[0].Name,
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1302,6 +1383,8 @@ func TestRetrieveAll(t *testing.T) {
Limit: nClients,
Identity: expectedClients[0].Credentials.Identity,
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1336,6 +1419,8 @@ func TestRetrieveAll(t *testing.T) {
Limit: nClients,
Domain: expectedClients[0].Domain,
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1353,6 +1438,8 @@ func TestRetrieveAll(t *testing.T) {
Limit: nClients,
Domain: testsutil.GenerateUUID(t),
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1369,6 +1456,8 @@ func TestRetrieveAll(t *testing.T) {
Offset: 0,
Limit: 10,
Status: clients.EnabledStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1385,6 +1474,8 @@ func TestRetrieveAll(t *testing.T) {
Offset: 0,
Limit: nClients,
Status: clients.DisabledStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1401,6 +1492,8 @@ func TestRetrieveAll(t *testing.T) {
Offset: 0,
Limit: nClients,
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1434,6 +1527,8 @@ func TestRetrieveAll(t *testing.T) {
Limit: nClients,
Tag: expectedClients[0].Tags[0],
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1451,6 +1546,8 @@ func TestRetrieveAll(t *testing.T) {
Limit: nClients,
Tag: namegen.Generate(),
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1466,12 +1563,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: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1489,6 +1588,8 @@ func TestRetrieveAll(t *testing.T) {
Limit: nClients,
ID: expectedClients[0].ID,
Status: clients.AllStatus,
Order: defOrder,
Dir: ascDir,
},
response: clients.ClientsPage{
Page: clients.Page{
@@ -1728,7 +1829,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,
@@ -1935,7 +2036,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: ascDir,
@@ -1994,7 +2095,7 @@ func TestRetrieveUserClients(t *testing.T) {
},
Clients: []clients.Client(nil),
},
err: repoerr.ErrMalformedEntity,
err: repoerr.ErrViewEntity,
},
{
desc: "retrieve clients with name",
@@ -2129,7 +2230,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,
@@ -2592,6 +2693,9 @@ func TestSearchClients(t *testing.T) {
Identity: username,
Secret: testsutil.GenerateUUID(t),
},
PublicMetadata: clients.Metadata{
"department": namegen.Generate(),
},
Metadata: clients.Metadata{},
Status: clients.EnabledStatus,
CreatedAt: baseTime.Add(time.Duration(i) * time.Microsecond),
@@ -2600,9 +2704,10 @@ func TestSearchClients(t *testing.T) {
require.Nil(t, err, fmt.Sprintf("save client unexpected error: %s", err))
expectedClients = append(expectedClients, clients.Client{
ID: client.ID,
Name: client.Name,
CreatedAt: client.CreatedAt,
ID: client.ID,
Name: client.Name,
PublicMetadata: client.PublicMetadata,
CreatedAt: client.CreatedAt,
})
}
@@ -2973,10 +3078,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))
@@ -3601,13 +3706,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)
}
@@ -3669,13 +3774,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)
}
@@ -3722,6 +3827,9 @@ func generateClient(t *testing.T, status clients.Status, repo clients.Repository
Secret: testsutil.GenerateUUID(t),
},
Tags: namegen.GenerateMultiple(5),
PublicMetadata: clients.Metadata{
"name": namegen.Generate(),
},
Metadata: clients.Metadata{
"name": namegen.Generate(),
},
+9
View File
@@ -76,6 +76,15 @@ func Migration() (*migrate.MemoryMigrationSource, error) {
`ALTER TABLE clients ALTER COLUMN updated_at TYPE TIMESTAMP;`,
},
},
{
Id: "clients_04",
Up: []string{
`ALTER TABLE clients ADD COLUMN public_metadata JSONB;`,
},
Down: []string{
`ALTER TABLE clients DROP COLUMN public_metadata;`,
},
},
},
}
+6 -5
View File
@@ -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,
Metadata: cli.Metadata,
PublicMetadata: cli.PublicMetadata,
UpdatedAt: time.Now().UTC(),
UpdatedBy: session.UserID,
}
client, err := svc.repo.Update(ctx, client)
if err != nil {
+38 -19
View File
@@ -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,
Metadata: 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,
Metadata: validMetadata,
Status: clients.EnabledStatus,
Roles: []roles.MemberRoleActions{
{
RoleID: "test_role_id",
@@ -186,6 +188,19 @@ func TestCreateClients(t *testing.T) {
token: validToken,
err: nil,
},
{
desc: "create a new enabled client with public metadata",
client: clients.Client{
Credentials: clients.Credentials{
Identity: "newclientwithmetadata@example.com",
Secret: secret,
},
PublicMetadata: validMetadata,
Status: clients.EnabledStatus,
},
token: validToken,
err: nil,
},
{
desc: "create a new enabled client with metadata",
client: clients.Client{
@@ -193,20 +208,20 @@ func TestCreateClients(t *testing.T) {
Identity: "newclientwithmetadata@example.com",
Secret: secret,
},
Metadata: validTMetadata,
Metadata: validMetadata,
Status: clients.EnabledStatus,
},
token: validToken,
err: nil,
},
{
desc: "create a new disabled client with metadata",
desc: "create a new disabled client with public metadata",
client: clients.Client{
Credentials: clients.Credentials{
Identity: "newclientwithmetadata@example.com",
Secret: secret,
},
Metadata: validTMetadata,
PublicMetadata: validMetadata,
},
token: validToken,
err: nil,
@@ -243,6 +258,9 @@ func TestCreateClients(t *testing.T) {
Identity: "newclientwithallfields@example.com",
Secret: secret,
},
PublicMetadata: clients.Metadata{
"name": "newclientwithallfields",
},
Metadata: clients.Metadata{
"name": "newclientwithallfields",
},
@@ -592,6 +610,7 @@ func TestUpdateClient(t *testing.T) {
client1 := client
client2 := client
client1.Name = "Updated client"
client2.PublicMetadata = clients.Metadata{"role": "test"}
client2.Metadata = clients.Metadata{"role": "test"}
cases := []struct {
+1
View File
@@ -59,6 +59,7 @@ services:
AM_CERTS_SECRET_ID_PATH: ${AM_CERTS_SECRET_ID_PATH}
AM_CERTS_SECRET_RENEW_THRESHOLD: ${AM_CERTS_SECRET_RENEW_THRESHOLD}
AM_CERTS_SECRET_CHECK_INTERVAL: ${AM_CERTS_SECRET_CHECK_INTERVAL}
SMQ_ALLOW_UNVERIFIED_USER: ${SMQ_ALLOW_UNVERIFIED_USER}
ports:
- ${AM_CERTS_HTTP_PORT}:${AM_CERTS_HTTP_PORT}
- ${AM_CERTS_GRPC_PORT}:${AM_CERTS_GRPC_PORT}
+12 -11
View File
@@ -35,15 +35,16 @@ message User {
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 = 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;
}
+5
View File
@@ -95,6 +95,11 @@ func ToClient(data map[string]any) (clients.Client, error) {
c.Metadata = meta
}
pmeta, ok := data["public_metadata"].(map[string]any)
if ok {
c.PublicMetadata = pmeta
}
uby, ok := data["updated_by"].(string)
if ok {
c.UpdatedBy = uby
+14 -13
View File
@@ -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"`
Metadata map[string]any `json:"metadata,omitempty"`
PublicMetadata map[string]any `json:"public_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 {
+43 -39
View File
@@ -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,
Metadata: client.Metadata,
PublicMetadata: client.PublicMetadata,
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,
Metadata: client.Metadata,
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,
Metadata: client.Metadata,
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"},
Metadata: validMetadata,
PublicMetadata: validMetadata,
Status: clients.EnabledStatus.String(),
CreatedAt: createdAt,
UpdatedAt: updatedAt,
Roles: rl,
}
}
+21 -18
View File
@@ -172,6 +172,7 @@ func convertUser(c sdk.User) users.User {
Email: c.Email,
Credentials: users.Credentials(c.Credentials),
Metadata: users.Metadata(c.Metadata),
PublicMetadata: users.Metadata(c.PublicMetadata),
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
Status: status,
@@ -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),
Metadata: clients.Metadata(c.Metadata),
PublicMetadata: clients.Metadata(c.PublicMetadata),
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"},
Metadata: validMetadata,
PublicMetadata: validMetadata,
CreatedAt: createdAt,
UpdatedAt: createdAt,
Status: users.EnabledStatus.String(),
Role: users.UserRole.String(),
}
}
+1
View File
@@ -36,6 +36,7 @@ type User struct {
Credentials Credentials `json:"credentials"`
Tags []string `json:"tags,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
PublicMetadata Metadata `json:"public_metadata,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
Status string `json:"status,omitempty"`
+45 -40
View File
@@ -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,
Metadata: user.Metadata,
PublicMetadata: user.PublicMetadata,
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,
Metadata: createSdkUserReq.Metadata,
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,
Metadata: createSdkUserReq.Metadata,
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,
Metadata: createSdkUserReq.Metadata,
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,
Metadata: createSdkUserReq.Metadata,
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),
},
},
+23 -22
View File
@@ -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,
Metadata: 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,
+4 -3
View File
@@ -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,
Metadata: req.Metadata,
PublicMetadata: req.PublicMetadata,
}
user, err := svc.Update(ctx, session, req.id, usr)
+5
View File
@@ -110,6 +110,10 @@ func userFromProto(u *grpcUsersV1.User) (users.User, error) {
if u.GetMetadata() != nil {
metadata = users.Metadata(u.GetMetadata().AsMap())
}
publicMetadata := users.Metadata(nil)
if u.GetPublicMetadata() != nil {
publicMetadata = users.Metadata(u.GetPublicMetadata().AsMap())
}
user := users.User{
ID: u.GetId(),
@@ -117,6 +121,7 @@ func userFromProto(u *grpcUsersV1.User) (users.User, error) {
LastName: u.GetLastName(),
Tags: u.GetTags(),
Metadata: metadata,
PublicMetadata: publicMetadata,
Status: users.Status(u.GetStatus()),
Role: users.Role(u.GetRole()),
ProfilePicture: u.GetProfilePicture(),
+10 -3
View File
@@ -82,10 +82,16 @@ func toProtoUsers(us []users.User) ([]*grpcUsersV1.User, error) {
}
func toProtoUser(u users.User) (*grpcUsersV1.User, error) {
var md *structpb.Struct
var metadata, publicMetadata *structpb.Struct
var err error
if u.Metadata != nil {
md, err = structpb.NewStruct(u.Metadata)
metadata, err = structpb.NewStruct(u.Metadata)
if err != nil {
return nil, errors.Wrap(svcerr.ErrViewEntity, err)
}
}
if u.PublicMetadata != nil {
publicMetadata, err = structpb.NewStruct(u.PublicMetadata)
if err != nil {
return nil, errors.Wrap(svcerr.ErrViewEntity, err)
}
@@ -96,7 +102,8 @@ func toProtoUser(u users.User) (*grpcUsersV1.User, error) {
FirstName: u.FirstName,
LastName: u.LastName,
Tags: u.Tags,
Metadata: md,
Metadata: metadata,
PublicMetadata: publicMetadata,
Status: uint32(u.Status),
Role: uint32(u.Role),
ProfilePicture: u.ProfilePicture,
+5 -4
View File
@@ -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"`
Metadata *users.Metadata `json:"metadata,omitempty"`
PublicMetadata *users.Metadata `json:"public_metadata,omitempty"`
}
func (req updateUserReq) validate() error {
+13 -1
View File
@@ -92,6 +92,9 @@ func (uce createUserEvent) Encode() (map[string]any, error) {
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
}
@@ -171,6 +174,9 @@ func (uce updateUserEvent) Encode() (map[string]any, error) {
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,11 +296,14 @@ 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
}
@@ -338,6 +347,9 @@ func (vpe viewProfileEvent) Encode() (map[string]any, error) {
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
}
-2
View File
@@ -32,9 +32,7 @@ const (
viewProfileStream = supermqPrefix + profileView
listStream = supermqPrefix + userList
searchStream = supermqPrefix + userSearch
listByGroupStream = supermqPrefix + userListByGroup
identifyStream = supermqPrefix + userIdentify
resetTokenStream = supermqPrefix + generateResetToken
issueTokenStream = supermqPrefix + issueToken
refreshTokenStream = supermqPrefix + refreshToken
resetSecretStream = supermqPrefix + resetSecret
+1 -1
View File
@@ -1157,7 +1157,7 @@ func generateTestUser(t *testing.T) users.User {
Secret: "secret",
},
Tags: []string{"tag1", "tag2"},
Metadata: users.Metadata{
PublicMetadata: users.Metadata{
"key1": "value1",
"key2": "value2",
},
+1 -1
View File
@@ -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 {
+9
View File
@@ -136,6 +136,15 @@ func Migration() *migrate.MemoryMigrationSource {
`ALTER TABLE users DROP COLUMN auth_provider`,
},
},
{
Id: "clients_10",
Up: []string{
`ALTER TABLE users ADD COLUMN public_metadata JSONB;`,
},
Down: []string{
`ALTER TABLE users DROP COLUMN public_metadata;`,
},
},
},
}
}
+42 -28
View File
@@ -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, metadata, public_metadata, created_at, status, role, first_name, last_name, username, profile_picture, auth_provider)
VALUES (:id, :tags, :email, :secret, :metadata, :public_metadata, :created_at, :status, :role, :first_name, :last_name, :username, :profile_picture, :auth_provider)
RETURNING id, tags, email, metadata, public_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, metadata, public_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, metadata, 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,6 +195,10 @@ 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.PublicMetadata != nil {
query = append(query, "public_metadata = :public_metadata")
u.PublicMetadata = *ur.PublicMetadata
}
if ur.Metadata != nil {
query = append(query, "metadata = :metadata")
u.Metadata = *ur.Metadata
@@ -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, metadata, public_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, metadata, 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, metadata, 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, metadata, 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, metadata, 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, metadata, 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, metadata, public_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, metadata, public_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{
@@ -479,6 +483,7 @@ type DBUser struct {
Domain string `db:"domain_id"`
Secret string `db:"secret"`
Metadata []byte `db:"metadata,omitempty"`
PublicMetadata []byte `db:"public_metadata,omitempty"`
Tags pgtype.TextArray `db:"tags,omitempty"` // Tags
CreatedAt time.Time `db:"created_at,omitempty"`
UpdatedAt sql.NullTime `db:"updated_at,omitempty"`
@@ -496,13 +501,21 @@ type DBUser struct {
}
func toDBUser(u users.User) (DBUser, error) {
data := []byte("{}")
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
}
metadata := []byte("{}")
if len(u.Metadata) > 0 {
b, err := json.Marshal(u.Metadata)
if err != nil {
return DBUser{}, errors.Wrap(repoerr.ErrMalformedEntity, err)
}
data = b
metadata = b
}
var tags pgtype.TextArray
if err := tags.Set(u.Tags); err != nil {
@@ -530,7 +543,8 @@ func toDBUser(u users.User) (DBUser, error) {
ID: u.ID,
Tags: tags,
Secret: u.Credentials.Secret,
Metadata: data,
Metadata: metadata,
PublicMetadata: publicMetadata,
CreatedAt: u.CreatedAt,
UpdatedAt: updatedAt,
UpdatedBy: updatedBy,
@@ -547,7 +561,12 @@ func toDBUser(u users.User) (DBUser, error) {
}
func ToUser(dbu DBUser) (users.User, error) {
var metadata users.Metadata
var publicMetadata, metadata 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.Metadata != nil {
if err := json.Unmarshal([]byte(dbu.Metadata), &metadata); err != nil {
return users.User{}, errors.Wrap(repoerr.ErrMalformedEntity, err)
@@ -585,6 +604,7 @@ func ToUser(dbu DBUser) (users.User, error) {
},
Email: dbu.Email,
Metadata: metadata,
PublicMetadata: publicMetadata,
CreatedAt: dbu.CreatedAt.UTC(),
UpdatedAt: updatedAt,
UpdatedBy: updatedBy,
@@ -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 {
+126 -57
View File
@@ -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)
@@ -49,10 +53,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{},
Metadata: users.Metadata{},
Credentials: users.Credentials{
Username: namesgen.Generate(),
},
@@ -75,8 +80,13 @@ func TestUsersSave(t *testing.T) {
Username: username,
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
PublicMetadata: users.Metadata{
"organization": namesgen.Generate(),
},
Metadata: users.Metadata{
"address": namesgen.Generate(),
},
Status: users.EnabledStatus,
},
err: nil,
},
@@ -96,8 +106,13 @@ func TestUsersSave(t *testing.T) {
Username: namesgen.Generate(),
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
PublicMetadata: users.Metadata{
"organization": namesgen.Generate(),
},
Metadata: users.Metadata{
"address": namesgen.Generate(),
},
Status: users.EnabledStatus,
},
err: errors.ErrEmailAlreadyExists,
},
@@ -112,8 +127,13 @@ func TestUsersSave(t *testing.T) {
Username: username,
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
PublicMetadata: users.Metadata{
"organization": namesgen.Generate(),
},
Metadata: users.Metadata{
"address": namesgen.Generate(),
},
Status: users.EnabledStatus,
},
err: errors.ErrUsernameNotAvailable,
},
@@ -128,8 +148,13 @@ func TestUsersSave(t *testing.T) {
Username: username,
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
PublicMetadata: users.Metadata{
"organization": namesgen.Generate(),
},
Metadata: users.Metadata{
"address": namesgen.Generate(),
},
Status: users.EnabledStatus,
},
err: repoerr.ErrCreateEntity,
},
@@ -144,8 +169,13 @@ func TestUsersSave(t *testing.T) {
Username: invalidName,
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
PublicMetadata: users.Metadata{
"organization": namesgen.Generate(),
},
Metadata: users.Metadata{
"address": namesgen.Generate(),
},
Status: users.EnabledStatus,
},
err: repoerr.ErrCreateEntity,
},
@@ -159,7 +189,12 @@ func TestUsersSave(t *testing.T) {
Credentials: users.Credentials{
Secret: password,
},
Metadata: users.Metadata{},
PublicMetadata: users.Metadata{
"organization": namesgen.Generate(),
},
Metadata: users.Metadata{
"address": namesgen.Generate(),
},
},
err: nil,
},
@@ -173,7 +208,12 @@ func TestUsersSave(t *testing.T) {
Credentials: users.Credentials{
Username: namesgen.Generate(),
},
Metadata: users.Metadata{},
PublicMetadata: users.Metadata{
"organization": namesgen.Generate(),
},
Metadata: users.Metadata{
"address": namesgen.Generate(),
},
},
err: nil,
},
@@ -187,7 +227,7 @@ func TestUsersSave(t *testing.T) {
Username: username,
Secret: password,
},
Metadata: map[string]any{
PublicMetadata: map[string]any{
"key": make(chan int),
},
},
@@ -236,9 +276,10 @@ func TestIsPlatformAdmin(t *testing.T) {
Username: username,
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
Role: users.AdminRole,
PublicMetadata: users.Metadata{},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
Role: users.AdminRole,
},
err: nil,
},
@@ -253,9 +294,10 @@ func TestIsPlatformAdmin(t *testing.T) {
Username: namesgen.Generate(),
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
Role: users.UserRole,
PublicMetadata: users.Metadata{},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
Role: users.UserRole,
},
err: repoerr.ErrNotFound,
},
@@ -286,18 +328,24 @@ func TestRetrieveByID(t *testing.T) {
Username: namesgen.Generate(),
Secret: password,
},
Metadata: users.Metadata{},
Status: users.EnabledStatus,
PublicMetadata: users.Metadata{
"organization": namesgen.Generate(),
},
Metadata: 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{},
Metadata: users.Metadata{},
Credentials: users.Credentials{
Username: namesgen.Generate(),
},
@@ -369,14 +417,14 @@ 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),
UpdatedAt: baseTime.Add(time.Duration(i) * time.Millisecond),
PublicMetadata: users.Metadata{},
Status: users.EnabledStatus,
Tags: []string{"tag1"},
CreatedAt: baseTime.Add(time.Duration(i) * time.Millisecond),
UpdatedAt: 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
@@ -405,7 +453,7 @@ 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",
@@ -415,9 +463,9 @@ func TestRetrieveAll(t *testing.T) {
Page: users.Page{
Total: 200,
Offset: 0,
Limit: 50,
Limit: 1,
},
Users: items[0:50],
Users: items[0:1],
},
err: nil,
},
@@ -689,7 +737,7 @@ func TestRetrieveAll(t *testing.T) {
},
},
{
desc: "retrieve with metadata",
desc: "retrieve with public metadata",
pageMeta: users.Page{
Metadata: map[string]any{
"key": "value",
@@ -963,7 +1011,8 @@ func TestSearch(t *testing.T) {
Credentials: users.Credentials{
Username: user.Credentials.Username,
},
CreatedAt: user.CreatedAt,
PublicMetadata: user.PublicMetadata,
CreatedAt: user.CreatedAt,
})
}
@@ -1026,15 +1075,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],
},
},
{
@@ -1280,7 +1331,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))
}
@@ -1470,11 +1521,23 @@ func TestUpdate(t *testing.T) {
err: nil,
},
{
desc: "update malformed metadata for enabled user",
update: "metadata",
desc: "update public metadata for enabled user",
update: "public_metadata",
userID: user1.ID,
userReq: users.UserReq{
Metadata: &malformedMetadata,
PublicMetadata: &updatedMetadata,
},
userRes: users.User{
PublicMetadata: updatedMetadata,
},
err: nil,
},
{
desc: "update malformed public metadata for enabled user",
update: "public_metadata",
userID: user1.ID,
userReq: users.UserReq{
PublicMetadata: &malformedMetadata,
},
err: repoerr.ErrMalformedEntity,
},
@@ -1495,7 +1558,7 @@ func TestUpdate(t *testing.T) {
update: "metadata",
userID: user2.ID,
userReq: users.UserReq{
Metadata: &updatedMetadata,
PublicMetadata: &updatedMetadata,
},
err: repoerr.ErrNotFound,
},
@@ -1531,11 +1594,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,
},
@@ -1678,6 +1741,8 @@ 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 "public_metadata":
assert.Equal(t, c.userRes.PublicMetadata, expected.PublicMetadata)
case "metadata":
assert.Equal(t, c.userRes.Metadata, expected.Metadata)
case "first_name":
@@ -1955,6 +2020,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.Metadata = nil
items = append(items, user)
}
@@ -2117,7 +2183,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{
@@ -2148,7 +2214,7 @@ func TestRetrieveByIDs(t *testing.T) {
},
Users: []users.User(nil),
},
err: errors.ErrMalformedEntity,
err: repoerr.ErrViewEntity,
},
}
@@ -2210,7 +2276,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)
@@ -2261,7 +2327,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)
@@ -2304,8 +2370,11 @@ func generateUserWithTime(t *testing.T, status users.Status, repo users.Reposito
Secret: testsutil.GenerateUUID(t),
},
Tags: namesgen.GenerateMultiple(5),
PublicMetadata: users.Metadata{
"organization": namesgen.Generate(),
},
Metadata: users.Metadata{
"name": namesgen.Generate(),
"address": namesgen.Generate(),
},
Status: status,
CreatedAt: createdAt,
+5 -4
View File
@@ -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
}
}
+60 -18
View File
@@ -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",
Metadata: validCMetadata,
PublicMetadata: validCMetadata,
Status: users.EnabledStatus,
}
basicUser = users.User{
Credentials: users.Credentials{
@@ -127,6 +128,9 @@ func TestRegister(t *testing.T) {
Credentials: users.Credentials{
Secret: secret,
},
PublicMetadata: users.Metadata{
"name": "newuserwithallfields",
},
Metadata: users.Metadata{
"name": "newuserwithallfields",
},
@@ -518,6 +522,8 @@ func TestUpdateUser(t *testing.T) {
updateFirstName := "Updated user"
user1.FirstName = updateFirstName
updatedMetadata := users.Metadata{"role": "test"}
invalidMetadata := users.Metadata{"role": make(chan int)}
user2.PublicMetadata = updatedMetadata
user2.Metadata = updatedMetadata
adminID := testsutil.GenerateUUID(t)
@@ -535,7 +541,7 @@ func TestUpdateUser(t *testing.T) {
err error
}{
{
desc: "update user name successfully as normal user",
desc: "update user name successfully as normal user",
userID: user1.ID,
userReq: users.UserReq{
FirstName: &updateFirstName,
@@ -546,6 +552,29 @@ func TestUpdateUser(t *testing.T) {
token: validToken,
err: nil,
},
{
desc: "update public metadata successfully as normal user",
userID: user2.ID,
userReq: users.UserReq{
PublicMetadata: &updatedMetadata,
},
session: authn.Session{UserID: user2.ID},
updateResponse: user2,
token: validToken,
err: nil,
},
{
desc: "update public metadata with repo error",
userID: user2.ID,
userReq: users.UserReq{
PublicMetadata: &invalidMetadata,
},
session: authn.Session{UserID: user2.ID},
updateResponse: users.User{},
token: validToken,
updateErr: errors.ErrMalformedEntity,
err: svcerr.ErrUpdateEntity,
},
{
desc: "update metadata successfully as normal user",
userID: user2.ID,
@@ -558,6 +587,19 @@ func TestUpdateUser(t *testing.T) {
token: validToken,
err: nil,
},
{
desc: "update metadata with repo error",
userID: user2.ID,
userReq: users.UserReq{
Metadata: &invalidMetadata,
},
session: authn.Session{UserID: user2.ID},
updateResponse: users.User{},
retrieveByIDResp: user2,
token: validToken,
updateErr: errors.ErrMalformedEntity,
err: svcerr.ErrUpdateEntity,
},
{
desc: "update user name as normal user with repo error on update",
userID: user1.ID,
@@ -584,10 +626,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 +693,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,
+2
View File
@@ -20,6 +20,7 @@ type User struct {
LastName string `json:"last_name,omitempty"`
Tags []string `json:"tags,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
PublicMetadata Metadata `json:"public_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
@@ -50,6 +51,7 @@ type UserReq struct {
FirstName *string `json:"first_name,omitempty"`
LastName *string `json:"last_name,omitempty"`
Metadata *Metadata `json:"metadata,omitempty"`
PublicMetadata *Metadata `json:"public_metadata,omitempty"`
Tags *[]string `json:"tags,omitempty"`
ProfilePicture *string `json:"profile_picture,omitempty"`
UpdatedBy *string `json:"updated_by,omitempty"`