From a1d4a044de6ed5a833b9f765f088315ee0c866b7 Mon Sep 17 00:00:00 2001 From: jnfrati Date: Tue, 12 May 2026 11:02:14 +0200 Subject: [PATCH] fix(sql): handle SQLite text password groups --- go.mod | 2 +- storage/sql/crud.go | 9 +++++++-- storage/sql/crud_test.go | 41 ++++++++++++++++++++++++++++++++++++++++ storage/sql/migrate.go | 8 ++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f57a0fb2..e7150d13 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/dexidp/dex -go 1.24.0 +go 1.25.0 require ( cloud.google.com/go/compute/metadata v0.9.0 diff --git a/storage/sql/crud.go b/storage/sql/crud.go index 5ef6dddf..a5ae7ff0 100644 --- a/storage/sql/crud.go +++ b/storage/sql/crud.go @@ -64,8 +64,13 @@ func (j jsonDecoder) Scan(dest interface{}) error { if dest == nil { return errors.New("nil value") } - b, ok := dest.([]byte) - if !ok { + var b []byte + switch v := dest.(type) { + case []byte: + b = v + case string: + b = []byte(v) + default: return fmt.Errorf("expected []byte got %T", dest) } if err := json.Unmarshal(b, &j.i); err != nil { diff --git a/storage/sql/crud_test.go b/storage/sql/crud_test.go index 7cca1d6f..d8e44419 100644 --- a/storage/sql/crud_test.go +++ b/storage/sql/crud_test.go @@ -4,7 +4,9 @@ package sql import ( + "context" "database/sql" + "log/slog" "reflect" "testing" ) @@ -32,6 +34,45 @@ func TestDecoder(t *testing.T) { } } +func TestGetPasswordWithTextGroups(t *testing.T) { + logger := slog.New(slog.DiscardHandler) + conn, err := (&SQLite3{File: ":memory:"}).open(logger) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + email := "text-groups@example.com" + _, err = conn.Exec(` + insert into password ( + email, hash, username, preferred_username, user_id, groups, name, email_verified + ) + values ( + $1, $2, $3, $4, $5, CAST('[]' AS TEXT), $6, $7 + );`, + email, []byte("hash"), "username", "", "user-id", "", false, + ) + if err != nil { + t.Fatal(err) + } + + var storageClass string + if err := conn.QueryRow(`select typeof(groups) from password where email = $1;`, email).Scan(&storageClass); err != nil { + t.Fatal(err) + } + if storageClass != "text" { + t.Fatalf("expected groups storage class text, got %q", storageClass) + } + + p, err := conn.GetPassword(context.Background(), email) + if err != nil { + t.Fatal(err) + } + if len(p.Groups) != 0 { + t.Fatalf("expected empty groups, got %q", p.Groups) + } +} + func TestEncoder(t *testing.T) { db, err := sql.Open("sqlite3", ":memory:") if err != nil { diff --git a/storage/sql/migrate.go b/storage/sql/migrate.go index caa488ac..3b95f677 100644 --- a/storage/sql/migrate.go +++ b/storage/sql/migrate.go @@ -473,4 +473,12 @@ var migrations = []migration{ add column connector_data bytea;`, }, }, + // Convert password groups values backfilled from the original default '[]' + // from SQLite TEXT storage class to BLOB. + { + stmts: []string{ + `update password set groups = CAST(groups as BLOB);`, + }, + flavor: &flavorSQLite3, + }, }