fix: correctly unmarshal login names and metadata in user repository (#11775)

## Problem description

When calling `repository.UserRepository().Get(...)` the following error
got returned caused by scanning.

`Scan error: scanning: doing scan: scanFn: scany: scan row into struct
fields: sql: Scan error on column index 7, name "metadata": unsupported
Scan, storing driver.Value type []uint8 into type *[]*domain.Metadata`

## How the problem got solved

The issue was that json arrays were not properly unmarshalled during
scanning the rows into structs.

## Additional changes

The issue could also happen on login names, therefore it got fixed for
this field too. Other fields are not affected because we use
`json.RawMessage` for the specific user types.

## Additional information

Introduced in https://github.com/zitadel/zitadel/pull/10967
This commit is contained in:
Silvan
2026-03-06 07:56:37 +01:00
committed by GitHub
parent 07dbb48cc7
commit f89aa05e92
7 changed files with 27 additions and 14 deletions
@@ -7,7 +7,7 @@ import (
"github.com/zitadel/zitadel/backend/v3/storage/database"
)
type JSONArray[T any] []*T
type JSONArray[T any] []T
var ErrScanSource = errors.New("unsupported scan source")
@@ -108,7 +108,7 @@ func TestJSONArray_Scan(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var a repository.JSONArray[testObject]
var a repository.JSONArray[*testObject]
gotErr := a.Scan(tt.src)
require.ErrorIs(t, gotErr, tt.want.err)
require.Len(t, a, len(tt.want.res))
@@ -270,7 +270,7 @@ func (i instance) UpdatedAtColumn() database.Column {
type rawInstance struct {
*domain.Instance
Domains JSONArray[domain.InstanceDomain] `json:"domains,omitempty" db:"domains"`
Domains JSONArray[*domain.InstanceDomain] `json:"domains,omitempty" db:"domains"`
}
func scanInstance(ctx context.Context, querier database.Querier, builder *database.StatementBuilder) (*domain.Instance, error) {
@@ -297,8 +297,8 @@ func (o org) UpdatedAtColumn() database.Column {
type rawOrg struct {
*domain.Organization
Domains JSONArray[domain.OrganizationDomain] `json:"domains,omitempty" db:"domains"`
Metadata JSONArray[domain.OrganizationMetadata] `json:"metadata,omitempty" db:"metadata"`
Domains JSONArray[*domain.OrganizationDomain] `json:"domains,omitempty" db:"domains"`
Metadata JSONArray[*domain.OrganizationMetadata] `json:"metadata,omitempty" db:"metadata"`
}
func scanOrganization(ctx context.Context, querier database.Querier, builder *database.StatementBuilder) (*domain.Organization, error) {
@@ -554,8 +554,8 @@ type rawSession struct {
Expiration *time.Time `json:"expiration" db:"expiration"`
UserID *string `json:"userID" db:"user_id"`
CreatorID *string `json:"creatorID" db:"creator_id"`
Factors JSONArray[rawFactor] `json:"factors,omitempty" db:"factors"`
Metadata JSONArray[domain.SessionMetadata] `json:"metadata,omitempty" db:"metadata"`
Factors JSONArray[*rawFactor] `json:"factors,omitempty" db:"factors"`
Metadata JSONArray[*domain.SessionMetadata] `json:"metadata,omitempty" db:"metadata"`
}
func scanSession(ctx context.Context, querier database.Querier, builder *database.StatementBuilder) (*domain.Session, error) {
@@ -353,6 +353,8 @@ type rawUser struct {
domain.User
Machine *json.RawMessage `json:"machine,omitempty" db:"machine"`
Human *json.RawMessage `json:"human,omitempty" db:"human"`
LoginNames JSONArray[domain.LoginName] `json:"loginNames,omitempty" db:"login_names"`
Metadata JSONArray[*domain.Metadata] `json:"metadata,omitempty" db:"metadata"`
}
func (u *rawUser) toDomain() (*domain.User, error) {
@@ -367,6 +369,8 @@ func (u *rawUser) toDomain() (*domain.User, error) {
return nil, err
}
}
u.User.LoginNames = u.LoginNames
u.User.Metadata = u.Metadata
return &u.User, nil
}
@@ -139,6 +139,15 @@ func Test_user_Get(t *testing.T) {
Name: "My Machine",
Description: "This is my machine user",
},
Metadata: []*domain.Metadata{
{
InstanceID: instanceID,
Key: "key",
Value: []byte(`"something"`),
CreatedAt: now,
UpdatedAt: now,
},
},
}
err = userRepo.Create(t.Context(), tx, human)