Browse Source

feat: support groups and preferred_username for staticPasswords (#4456)

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
pull/4325/merge
Ivan Zviagintsev 2 months ago committed by GitHub
parent
commit
d1b2722e39
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 32
      cmd/dex/config.go
  2. 17
      cmd/dex/config_test.go
  3. 4
      config.dev.yaml
  4. 13
      config.yaml.dist
  5. 4
      examples/config-dev.yaml
  6. 4
      examples/k8s/dex.yaml
  7. 80
      server/handlers_test.go
  8. 14
      server/server.go
  9. 20
      server/server_test.go
  10. 20
      storage/conformance/conformance.go
  11. 4
      storage/ent/client/password.go
  12. 10
      storage/ent/client/types.go
  13. 2
      storage/ent/db/migrate/schema.go
  14. 173
      storage/ent/db/mutation.go
  15. 31
      storage/ent/db/password.go
  16. 13
      storage/ent/db/password/password.go
  17. 80
      storage/ent/db/password/where.go
  18. 41
      storage/ent/db/password_create.go
  19. 93
      storage/ent/db/password_update.go
  20. 6
      storage/ent/db/runtime.go
  21. 5
      storage/ent/schema/password.go
  22. 8
      storage/kubernetes/storage.go
  23. 28
      storage/kubernetes/types.go
  24. 18
      storage/sql/crud.go
  25. 40
      storage/sql/migrate.go
  26. 6
      storage/storage.go

32
cmd/dex/config.go

@ -95,19 +95,23 @@ type password storage.Password
func (p *password) UnmarshalJSON(b []byte) error {
var data struct {
Email string `json:"email"`
Username string `json:"username"`
UserID string `json:"userID"`
Hash string `json:"hash"`
HashFromEnv string `json:"hashFromEnv"`
Email string `json:"email"`
Username string `json:"username"`
PreferredUsername string `json:"preferredUsername"`
UserID string `json:"userID"`
Hash string `json:"hash"`
HashFromEnv string `json:"hashFromEnv"`
Groups []string `json:"groups"`
}
if err := json.Unmarshal(b, &data); err != nil {
return err
}
*p = password(storage.Password{
Email: data.Email,
Username: data.Username,
UserID: data.UserID,
Email: data.Email,
Username: data.Username,
PreferredUsername: data.PreferredUsername,
UserID: data.UserID,
Groups: data.Groups,
})
if len(data.Hash) == 0 && len(data.HashFromEnv) > 0 {
data.Hash = os.Getenv(data.HashFromEnv)
@ -275,12 +279,12 @@ var (
_ StorageConfig = (*ent.MySQL)(nil)
)
func getORMBasedSQLStorage(normal, entBased StorageConfig) func() StorageConfig {
func getORMBasedSQLStorage(normal, entBased func() StorageConfig) func() StorageConfig {
return func() StorageConfig {
if featureflags.EntEnabled.Enabled() {
return entBased
return entBased()
}
return normal
return normal()
}
}
@ -309,9 +313,9 @@ var storages = map[string]func() StorageConfig{
"etcd": func() StorageConfig { return new(etcd.Etcd) },
"kubernetes": func() StorageConfig { return new(kubernetes.Config) },
"memory": func() StorageConfig { return new(memory.Config) },
"sqlite3": getORMBasedSQLStorage(&sql.SQLite3{}, &ent.SQLite3{}),
"postgres": getORMBasedSQLStorage(&sql.Postgres{}, &ent.Postgres{}),
"mysql": getORMBasedSQLStorage(&sql.MySQL{}, &ent.MySQL{}),
"sqlite3": getORMBasedSQLStorage(func() StorageConfig { return new(sql.SQLite3) }, func() StorageConfig { return new(ent.SQLite3) }),
"postgres": getORMBasedSQLStorage(func() StorageConfig { return new(sql.Postgres) }, func() StorageConfig { return new(ent.Postgres) }),
"mysql": getORMBasedSQLStorage(func() StorageConfig { return new(sql.MySQL) }, func() StorageConfig { return new(ent.MySQL) }),
}
// UnmarshalJSON allows Storage to implement the unmarshaler interface to

17
cmd/dex/config_test.go

@ -116,6 +116,10 @@ staticPasswords:
# bcrypt hash of the string "password"
hash: "$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"
username: "admin"
preferredUsername: "admin-public"
groups:
- "team-a"
- "team-a/admins"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
- email: "foo@example.com"
# base64'd value of the same bcrypt hash above. We want to be able to parse both of these
@ -206,10 +210,15 @@ additionalFeatures: [
EnablePasswordDB: true,
StaticPasswords: []password{
{
Email: "admin@example.com",
Hash: []byte("$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"),
Username: "admin",
UserID: "08a8684b-db88-4b73-90a9-3cd1661f5466",
Email: "admin@example.com",
Hash: []byte("$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"),
Username: "admin",
PreferredUsername: "admin-public",
UserID: "08a8684b-db88-4b73-90a9-3cd1661f5466",
Groups: []string{
"team-a",
"team-a/admins",
},
},
{
Email: "foo@example.com",

4
config.dev.yaml

@ -32,4 +32,8 @@ staticPasswords:
- email: "admin@example.com"
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
username: "admin"
preferredUsername: "admin"
groups:
- "team-a"
- "team-a/admins"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"

13
config.yaml.dist

@ -135,4 +135,15 @@ enablePasswordDB: true
# A static list of passwords for the password connector.
#
# Alternatively, passwords my be added/updated through the gRPC API.
# staticPasswords: []
# staticPasswords:
# - email: "user@example.com"
# # bcrypt hash of the string "password"
# hash: "$2a$10$examplehash..."
# username: "user-login"
# # Optional. Maps to OIDC "preferred_username" claim.
# preferredUsername: "user-public"
# # Optional. Maps to OIDC "groups" claim (when 'groups' scope is requested).
# groups:
# - "team-a"
# - "team-a/admins"
# userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"

4
examples/config-dev.yaml

@ -164,4 +164,8 @@ staticPasswords:
# bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
username: "admin"
preferredUsername: "admin"
groups:
- "team-a"
- "team-a/admins"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"

4
examples/k8s/dex.yaml

@ -106,6 +106,10 @@ data:
# bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
username: "admin"
preferredUsername: "admin"
groups:
- "team-a"
- "team-a/admins"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
---
apiVersion: v1

80
server/handlers_test.go

@ -18,6 +18,7 @@ import (
"github.com/AppsFlyer/go-sundheit/checks"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
"golang.org/x/oauth2"
"github.com/dexidp/dex/storage"
@ -402,6 +403,85 @@ func TestHandlePassword(t *testing.T) {
}
}
func TestHandlePassword_LocalPasswordDBClaims(t *testing.T) {
ctx := t.Context()
// Setup a dex server.
httpServer, s := newTestServer(t, func(c *Config) {
c.PasswordConnector = "local"
})
defer httpServer.Close()
// Client credentials for password grant.
client := storage.Client{
ID: "test",
Secret: "barfoo",
RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"},
}
require.NoError(t, s.storage.CreateClient(ctx, client))
// Enable local connector.
localConn := storage.Connector{
ID: "local",
Type: LocalConnector,
Name: "Email",
ResourceVersion: "1",
}
require.NoError(t, s.storage.CreateConnector(ctx, localConn))
_, err := s.OpenConnector(localConn)
require.NoError(t, err)
// Create a user in the password DB with groups and preferred_username.
pw := "secret"
hash, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
require.NoError(t, err)
require.NoError(t, s.storage.CreatePassword(ctx, storage.Password{
Email: "user@example.com",
Username: "user-login",
PreferredUsername: "user-public",
UserID: "user-id",
Groups: []string{"team-a", "team-a/admins"},
Hash: hash,
}))
u, err := url.Parse(s.issuerURL.String())
require.NoError(t, err)
u.Path = path.Join(u.Path, "/token")
v := url.Values{}
v.Add("scope", "openid profile email groups")
v.Add("grant_type", "password")
v.Add("username", "user@example.com")
v.Add("password", pw)
req, _ := http.NewRequest("POST", u.String(), bytes.NewBufferString(v.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth("test", "barfoo")
rr := httptest.NewRecorder()
s.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
var tokenResponse struct {
IDToken string `json:"id_token"`
}
require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &tokenResponse))
require.NotEmpty(t, tokenResponse.IDToken)
p, err := oidc.NewProvider(ctx, httpServer.URL)
require.NoError(t, err)
idToken, err := p.Verifier(&oidc.Config{SkipClientIDCheck: true}).Verify(ctx, tokenResponse.IDToken)
require.NoError(t, err)
var claims struct {
PreferredUsername string `json:"preferred_username"`
Groups []string `json:"groups"`
}
require.NoError(t, idToken.Claims(&claims))
require.Equal(t, "user-public", claims.PreferredUsername)
require.Equal(t, []string{"team-a", "team-a/admins"}, claims.Groups)
}
func TestHandlePasswordLoginWithSkipApproval(t *testing.T) {
ctx := t.Context()

14
server/server.go

@ -565,10 +565,12 @@ func (db passwordDB) Login(ctx context.Context, s connector.Scopes, email, passw
return connector.Identity{}, false, nil
}
return connector.Identity{
UserID: p.UserID,
Username: p.Username,
Email: p.Email,
EmailVerified: true,
UserID: p.UserID,
Username: p.Username,
PreferredUsername: p.PreferredUsername,
Email: p.Email,
EmailVerified: true,
Groups: p.Groups,
}, true, nil
}
@ -591,8 +593,10 @@ func (db passwordDB) Refresh(ctx context.Context, s connector.Scopes, identity c
// refreshed token.
//
// No other fields are expected to be refreshable as email is effectively used
// as an ID and this implementation doesn't deal with groups.
// as an ID.
identity.Username = p.Username
identity.PreferredUsername = p.PreferredUsername
identity.Groups = p.Groups
return identity, nil
}

20
server/server_test.go

@ -1279,10 +1279,12 @@ func TestPasswordDB(t *testing.T) {
}
s.CreatePassword(ctx, storage.Password{
Email: "jane@example.com",
Username: "jane",
UserID: "foobar",
Hash: h,
Email: "jane@example.com",
Username: "jane",
PreferredUsername: "jane-public",
UserID: "foobar",
Groups: []string{"team-a", "team-a/admins"},
Hash: h,
})
tests := []struct {
@ -1298,10 +1300,12 @@ func TestPasswordDB(t *testing.T) {
username: "jane@example.com",
password: pw,
wantIdentity: connector.Identity{
Email: "jane@example.com",
Username: "jane",
UserID: "foobar",
EmailVerified: true,
Email: "jane@example.com",
Username: "jane",
PreferredUsername: "jane-public",
UserID: "foobar",
EmailVerified: true,
Groups: []string{"team-a", "team-a/admins"},
},
},
{

20
storage/conformance/conformance.go

@ -456,10 +456,12 @@ func testPasswordCRUD(t *testing.T, s storage.Storage) {
}
password1 := storage.Password{
Email: "jane@example.com",
Hash: passwordHash1,
Username: "jane",
UserID: "foobar",
Email: "jane@example.com",
Hash: passwordHash1,
Username: "jane",
PreferredUsername: "jane-public",
UserID: "foobar",
Groups: []string{"team-a", "team-a/admins"},
}
if err := s.CreatePassword(ctx, password1); err != nil {
t.Fatalf("create password token: %v", err)
@ -475,10 +477,12 @@ func testPasswordCRUD(t *testing.T, s storage.Storage) {
}
password2 := storage.Password{
Email: "john@example.com",
Hash: passwordHash2,
Username: "john",
UserID: "barfoo",
Email: "john@example.com",
Hash: passwordHash2,
Username: "john",
PreferredUsername: "john-public",
UserID: "barfoo",
Groups: []string{"team-b"},
}
if err := s.CreatePassword(ctx, password2); err != nil {
t.Fatalf("create password token: %v", err)

4
storage/ent/client/password.go

@ -14,7 +14,9 @@ func (d *Database) CreatePassword(ctx context.Context, password storage.Password
SetEmail(password.Email).
SetHash(password.Hash).
SetUsername(password.Username).
SetPreferredUsername(password.PreferredUsername).
SetUserID(password.UserID).
SetGroups(password.Groups).
Save(ctx)
if err != nil {
return convertDBError("create password: %w", err)
@ -86,7 +88,9 @@ func (d *Database) UpdatePassword(ctx context.Context, email string, updater fun
SetEmail(newPassword.Email).
SetHash(newPassword.Hash).
SetUsername(newPassword.Username).
SetPreferredUsername(newPassword.PreferredUsername).
SetUserID(newPassword.UserID).
SetGroups(newPassword.Groups).
Save(ctx)
if err != nil {
return rollback(tx, "update password uploading: %w", err)

10
storage/ent/client/types.go

@ -139,10 +139,12 @@ func toStorageRefreshToken(r *db.RefreshToken) storage.RefreshToken {
func toStoragePassword(p *db.Password) storage.Password {
return storage.Password{
Email: p.Email,
Hash: p.Hash,
Username: p.Username,
UserID: p.UserID,
Email: p.Email,
Hash: p.Hash,
Username: p.Username,
PreferredUsername: p.PreferredUsername,
UserID: p.UserID,
Groups: p.Groups,
}
}

2
storage/ent/db/migrate/schema.go

@ -161,7 +161,9 @@ var (
{Name: "email", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}},
{Name: "hash", Type: field.TypeBytes},
{Name: "username", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}},
{Name: "preferred_username", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}},
{Name: "user_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}},
{Name: "groups", Type: field.TypeJSON, Nullable: true},
}
// PasswordsTable holds the schema information for the "passwords" table.
PasswordsTable = &schema.Table{

173
storage/ent/db/mutation.go

@ -6314,17 +6314,20 @@ func (m *OfflineSessionMutation) ResetEdge(name string) error {
// PasswordMutation represents an operation that mutates the Password nodes in the graph.
type PasswordMutation struct {
config
op Op
typ string
id *int
email *string
hash *[]byte
username *string
user_id *string
clearedFields map[string]struct{}
done bool
oldValue func(context.Context) (*Password, error)
predicates []predicate.Password
op Op
typ string
id *int
email *string
hash *[]byte
username *string
preferred_username *string
user_id *string
groups *[]string
appendgroups []string
clearedFields map[string]struct{}
done bool
oldValue func(context.Context) (*Password, error)
predicates []predicate.Password
}
var _ ent.Mutation = (*PasswordMutation)(nil)
@ -6533,6 +6536,42 @@ func (m *PasswordMutation) ResetUsername() {
m.username = nil
}
// SetPreferredUsername sets the "preferred_username" field.
func (m *PasswordMutation) SetPreferredUsername(s string) {
m.preferred_username = &s
}
// PreferredUsername returns the value of the "preferred_username" field in the mutation.
func (m *PasswordMutation) PreferredUsername() (r string, exists bool) {
v := m.preferred_username
if v == nil {
return
}
return *v, true
}
// OldPreferredUsername returns the old "preferred_username" field's value of the Password entity.
// If the Password object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *PasswordMutation) OldPreferredUsername(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldPreferredUsername is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldPreferredUsername requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldPreferredUsername: %w", err)
}
return oldValue.PreferredUsername, nil
}
// ResetPreferredUsername resets all changes to the "preferred_username" field.
func (m *PasswordMutation) ResetPreferredUsername() {
m.preferred_username = nil
}
// SetUserID sets the "user_id" field.
func (m *PasswordMutation) SetUserID(s string) {
m.user_id = &s
@ -6569,6 +6608,71 @@ func (m *PasswordMutation) ResetUserID() {
m.user_id = nil
}
// SetGroups sets the "groups" field.
func (m *PasswordMutation) SetGroups(s []string) {
m.groups = &s
m.appendgroups = nil
}
// Groups returns the value of the "groups" field in the mutation.
func (m *PasswordMutation) Groups() (r []string, exists bool) {
v := m.groups
if v == nil {
return
}
return *v, true
}
// OldGroups returns the old "groups" field's value of the Password entity.
// If the Password object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *PasswordMutation) OldGroups(ctx context.Context) (v []string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldGroups is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldGroups requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldGroups: %w", err)
}
return oldValue.Groups, nil
}
// AppendGroups adds s to the "groups" field.
func (m *PasswordMutation) AppendGroups(s []string) {
m.appendgroups = append(m.appendgroups, s...)
}
// AppendedGroups returns the list of values that were appended to the "groups" field in this mutation.
func (m *PasswordMutation) AppendedGroups() ([]string, bool) {
if len(m.appendgroups) == 0 {
return nil, false
}
return m.appendgroups, true
}
// ClearGroups clears the value of the "groups" field.
func (m *PasswordMutation) ClearGroups() {
m.groups = nil
m.appendgroups = nil
m.clearedFields[password.FieldGroups] = struct{}{}
}
// GroupsCleared returns if the "groups" field was cleared in this mutation.
func (m *PasswordMutation) GroupsCleared() bool {
_, ok := m.clearedFields[password.FieldGroups]
return ok
}
// ResetGroups resets all changes to the "groups" field.
func (m *PasswordMutation) ResetGroups() {
m.groups = nil
m.appendgroups = nil
delete(m.clearedFields, password.FieldGroups)
}
// Where appends a list predicates to the PasswordMutation builder.
func (m *PasswordMutation) Where(ps ...predicate.Password) {
m.predicates = append(m.predicates, ps...)
@ -6603,7 +6707,7 @@ func (m *PasswordMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
func (m *PasswordMutation) Fields() []string {
fields := make([]string, 0, 4)
fields := make([]string, 0, 6)
if m.email != nil {
fields = append(fields, password.FieldEmail)
}
@ -6613,9 +6717,15 @@ func (m *PasswordMutation) Fields() []string {
if m.username != nil {
fields = append(fields, password.FieldUsername)
}
if m.preferred_username != nil {
fields = append(fields, password.FieldPreferredUsername)
}
if m.user_id != nil {
fields = append(fields, password.FieldUserID)
}
if m.groups != nil {
fields = append(fields, password.FieldGroups)
}
return fields
}
@ -6630,8 +6740,12 @@ func (m *PasswordMutation) Field(name string) (ent.Value, bool) {
return m.Hash()
case password.FieldUsername:
return m.Username()
case password.FieldPreferredUsername:
return m.PreferredUsername()
case password.FieldUserID:
return m.UserID()
case password.FieldGroups:
return m.Groups()
}
return nil, false
}
@ -6647,8 +6761,12 @@ func (m *PasswordMutation) OldField(ctx context.Context, name string) (ent.Value
return m.OldHash(ctx)
case password.FieldUsername:
return m.OldUsername(ctx)
case password.FieldPreferredUsername:
return m.OldPreferredUsername(ctx)
case password.FieldUserID:
return m.OldUserID(ctx)
case password.FieldGroups:
return m.OldGroups(ctx)
}
return nil, fmt.Errorf("unknown Password field %s", name)
}
@ -6679,6 +6797,13 @@ func (m *PasswordMutation) SetField(name string, value ent.Value) error {
}
m.SetUsername(v)
return nil
case password.FieldPreferredUsername:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetPreferredUsername(v)
return nil
case password.FieldUserID:
v, ok := value.(string)
if !ok {
@ -6686,6 +6811,13 @@ func (m *PasswordMutation) SetField(name string, value ent.Value) error {
}
m.SetUserID(v)
return nil
case password.FieldGroups:
v, ok := value.([]string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetGroups(v)
return nil
}
return fmt.Errorf("unknown Password field %s", name)
}
@ -6715,7 +6847,11 @@ func (m *PasswordMutation) AddField(name string, value ent.Value) error {
// ClearedFields returns all nullable fields that were cleared during this
// mutation.
func (m *PasswordMutation) ClearedFields() []string {
return nil
var fields []string
if m.FieldCleared(password.FieldGroups) {
fields = append(fields, password.FieldGroups)
}
return fields
}
// FieldCleared returns a boolean indicating if a field with the given name was
@ -6728,6 +6864,11 @@ func (m *PasswordMutation) FieldCleared(name string) bool {
// ClearField clears the value of the field with the given name. It returns an
// error if the field is not defined in the schema.
func (m *PasswordMutation) ClearField(name string) error {
switch name {
case password.FieldGroups:
m.ClearGroups()
return nil
}
return fmt.Errorf("unknown Password nullable field %s", name)
}
@ -6744,9 +6885,15 @@ func (m *PasswordMutation) ResetField(name string) error {
case password.FieldUsername:
m.ResetUsername()
return nil
case password.FieldPreferredUsername:
m.ResetPreferredUsername()
return nil
case password.FieldUserID:
m.ResetUserID()
return nil
case password.FieldGroups:
m.ResetGroups()
return nil
}
return fmt.Errorf("unknown Password field %s", name)
}

31
storage/ent/db/password.go

@ -3,6 +3,7 @@
package db
import (
"encoding/json"
"fmt"
"strings"
@ -22,8 +23,12 @@ type Password struct {
Hash []byte `json:"hash,omitempty"`
// Username holds the value of the "username" field.
Username string `json:"username,omitempty"`
// PreferredUsername holds the value of the "preferred_username" field.
PreferredUsername string `json:"preferred_username,omitempty"`
// UserID holds the value of the "user_id" field.
UserID string `json:"user_id,omitempty"`
UserID string `json:"user_id,omitempty"`
// Groups holds the value of the "groups" field.
Groups []string `json:"groups,omitempty"`
selectValues sql.SelectValues
}
@ -32,11 +37,11 @@ func (*Password) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case password.FieldHash:
case password.FieldHash, password.FieldGroups:
values[i] = new([]byte)
case password.FieldID:
values[i] = new(sql.NullInt64)
case password.FieldEmail, password.FieldUsername, password.FieldUserID:
case password.FieldEmail, password.FieldUsername, password.FieldPreferredUsername, password.FieldUserID:
values[i] = new(sql.NullString)
default:
values[i] = new(sql.UnknownType)
@ -77,12 +82,26 @@ func (_m *Password) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.Username = value.String
}
case password.FieldPreferredUsername:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field preferred_username", values[i])
} else if value.Valid {
_m.PreferredUsername = value.String
}
case password.FieldUserID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field user_id", values[i])
} else if value.Valid {
_m.UserID = value.String
}
case password.FieldGroups:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field groups", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.Groups); err != nil {
return fmt.Errorf("unmarshal field groups: %w", err)
}
}
default:
_m.selectValues.Set(columns[i], values[i])
}
@ -128,8 +147,14 @@ func (_m *Password) String() string {
builder.WriteString("username=")
builder.WriteString(_m.Username)
builder.WriteString(", ")
builder.WriteString("preferred_username=")
builder.WriteString(_m.PreferredUsername)
builder.WriteString(", ")
builder.WriteString("user_id=")
builder.WriteString(_m.UserID)
builder.WriteString(", ")
builder.WriteString("groups=")
builder.WriteString(fmt.Sprintf("%v", _m.Groups))
builder.WriteByte(')')
return builder.String()
}

13
storage/ent/db/password/password.go

@ -17,8 +17,12 @@ const (
FieldHash = "hash"
// FieldUsername holds the string denoting the username field in the database.
FieldUsername = "username"
// FieldPreferredUsername holds the string denoting the preferred_username field in the database.
FieldPreferredUsername = "preferred_username"
// FieldUserID holds the string denoting the user_id field in the database.
FieldUserID = "user_id"
// FieldGroups holds the string denoting the groups field in the database.
FieldGroups = "groups"
// Table holds the table name of the password in the database.
Table = "passwords"
)
@ -29,7 +33,9 @@ var Columns = []string{
FieldEmail,
FieldHash,
FieldUsername,
FieldPreferredUsername,
FieldUserID,
FieldGroups,
}
// ValidColumn reports if the column name is valid (part of the table columns).
@ -47,6 +53,8 @@ var (
EmailValidator func(string) error
// UsernameValidator is a validator for the "username" field. It is called by the builders before save.
UsernameValidator func(string) error
// DefaultPreferredUsername holds the default value on creation for the "preferred_username" field.
DefaultPreferredUsername string
// UserIDValidator is a validator for the "user_id" field. It is called by the builders before save.
UserIDValidator func(string) error
)
@ -69,6 +77,11 @@ func ByUsername(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUsername, opts...).ToFunc()
}
// ByPreferredUsername orders the results by the preferred_username field.
func ByPreferredUsername(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPreferredUsername, opts...).ToFunc()
}
// ByUserID orders the results by the user_id field.
func ByUserID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserID, opts...).ToFunc()

80
storage/ent/db/password/where.go

@ -67,6 +67,11 @@ func Username(v string) predicate.Password {
return predicate.Password(sql.FieldEQ(FieldUsername, v))
}
// PreferredUsername applies equality check predicate on the "preferred_username" field. It's identical to PreferredUsernameEQ.
func PreferredUsername(v string) predicate.Password {
return predicate.Password(sql.FieldEQ(FieldPreferredUsername, v))
}
// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ.
func UserID(v string) predicate.Password {
return predicate.Password(sql.FieldEQ(FieldUserID, v))
@ -242,6 +247,71 @@ func UsernameContainsFold(v string) predicate.Password {
return predicate.Password(sql.FieldContainsFold(FieldUsername, v))
}
// PreferredUsernameEQ applies the EQ predicate on the "preferred_username" field.
func PreferredUsernameEQ(v string) predicate.Password {
return predicate.Password(sql.FieldEQ(FieldPreferredUsername, v))
}
// PreferredUsernameNEQ applies the NEQ predicate on the "preferred_username" field.
func PreferredUsernameNEQ(v string) predicate.Password {
return predicate.Password(sql.FieldNEQ(FieldPreferredUsername, v))
}
// PreferredUsernameIn applies the In predicate on the "preferred_username" field.
func PreferredUsernameIn(vs ...string) predicate.Password {
return predicate.Password(sql.FieldIn(FieldPreferredUsername, vs...))
}
// PreferredUsernameNotIn applies the NotIn predicate on the "preferred_username" field.
func PreferredUsernameNotIn(vs ...string) predicate.Password {
return predicate.Password(sql.FieldNotIn(FieldPreferredUsername, vs...))
}
// PreferredUsernameGT applies the GT predicate on the "preferred_username" field.
func PreferredUsernameGT(v string) predicate.Password {
return predicate.Password(sql.FieldGT(FieldPreferredUsername, v))
}
// PreferredUsernameGTE applies the GTE predicate on the "preferred_username" field.
func PreferredUsernameGTE(v string) predicate.Password {
return predicate.Password(sql.FieldGTE(FieldPreferredUsername, v))
}
// PreferredUsernameLT applies the LT predicate on the "preferred_username" field.
func PreferredUsernameLT(v string) predicate.Password {
return predicate.Password(sql.FieldLT(FieldPreferredUsername, v))
}
// PreferredUsernameLTE applies the LTE predicate on the "preferred_username" field.
func PreferredUsernameLTE(v string) predicate.Password {
return predicate.Password(sql.FieldLTE(FieldPreferredUsername, v))
}
// PreferredUsernameContains applies the Contains predicate on the "preferred_username" field.
func PreferredUsernameContains(v string) predicate.Password {
return predicate.Password(sql.FieldContains(FieldPreferredUsername, v))
}
// PreferredUsernameHasPrefix applies the HasPrefix predicate on the "preferred_username" field.
func PreferredUsernameHasPrefix(v string) predicate.Password {
return predicate.Password(sql.FieldHasPrefix(FieldPreferredUsername, v))
}
// PreferredUsernameHasSuffix applies the HasSuffix predicate on the "preferred_username" field.
func PreferredUsernameHasSuffix(v string) predicate.Password {
return predicate.Password(sql.FieldHasSuffix(FieldPreferredUsername, v))
}
// PreferredUsernameEqualFold applies the EqualFold predicate on the "preferred_username" field.
func PreferredUsernameEqualFold(v string) predicate.Password {
return predicate.Password(sql.FieldEqualFold(FieldPreferredUsername, v))
}
// PreferredUsernameContainsFold applies the ContainsFold predicate on the "preferred_username" field.
func PreferredUsernameContainsFold(v string) predicate.Password {
return predicate.Password(sql.FieldContainsFold(FieldPreferredUsername, v))
}
// UserIDEQ applies the EQ predicate on the "user_id" field.
func UserIDEQ(v string) predicate.Password {
return predicate.Password(sql.FieldEQ(FieldUserID, v))
@ -307,6 +377,16 @@ func UserIDContainsFold(v string) predicate.Password {
return predicate.Password(sql.FieldContainsFold(FieldUserID, v))
}
// GroupsIsNil applies the IsNil predicate on the "groups" field.
func GroupsIsNil() predicate.Password {
return predicate.Password(sql.FieldIsNull(FieldGroups))
}
// GroupsNotNil applies the NotNil predicate on the "groups" field.
func GroupsNotNil() predicate.Password {
return predicate.Password(sql.FieldNotNull(FieldGroups))
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.Password) predicate.Password {
return predicate.Password(sql.AndPredicates(predicates...))

41
storage/ent/db/password_create.go

@ -37,12 +37,32 @@ func (_c *PasswordCreate) SetUsername(v string) *PasswordCreate {
return _c
}
// SetPreferredUsername sets the "preferred_username" field.
func (_c *PasswordCreate) SetPreferredUsername(v string) *PasswordCreate {
_c.mutation.SetPreferredUsername(v)
return _c
}
// SetNillablePreferredUsername sets the "preferred_username" field if the given value is not nil.
func (_c *PasswordCreate) SetNillablePreferredUsername(v *string) *PasswordCreate {
if v != nil {
_c.SetPreferredUsername(*v)
}
return _c
}
// SetUserID sets the "user_id" field.
func (_c *PasswordCreate) SetUserID(v string) *PasswordCreate {
_c.mutation.SetUserID(v)
return _c
}
// SetGroups sets the "groups" field.
func (_c *PasswordCreate) SetGroups(v []string) *PasswordCreate {
_c.mutation.SetGroups(v)
return _c
}
// Mutation returns the PasswordMutation object of the builder.
func (_c *PasswordCreate) Mutation() *PasswordMutation {
return _c.mutation
@ -50,6 +70,7 @@ func (_c *PasswordCreate) Mutation() *PasswordMutation {
// Save creates the Password in the database.
func (_c *PasswordCreate) Save(ctx context.Context) (*Password, error) {
_c.defaults()
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
}
@ -75,6 +96,14 @@ func (_c *PasswordCreate) ExecX(ctx context.Context) {
}
}
// defaults sets the default values of the builder before save.
func (_c *PasswordCreate) defaults() {
if _, ok := _c.mutation.PreferredUsername(); !ok {
v := password.DefaultPreferredUsername
_c.mutation.SetPreferredUsername(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_c *PasswordCreate) check() error {
if _, ok := _c.mutation.Email(); !ok {
@ -96,6 +125,9 @@ func (_c *PasswordCreate) check() error {
return &ValidationError{Name: "username", err: fmt.Errorf(`db: validator failed for field "Password.username": %w`, err)}
}
}
if _, ok := _c.mutation.PreferredUsername(); !ok {
return &ValidationError{Name: "preferred_username", err: errors.New(`db: missing required field "Password.preferred_username"`)}
}
if _, ok := _c.mutation.UserID(); !ok {
return &ValidationError{Name: "user_id", err: errors.New(`db: missing required field "Password.user_id"`)}
}
@ -142,10 +174,18 @@ func (_c *PasswordCreate) createSpec() (*Password, *sqlgraph.CreateSpec) {
_spec.SetField(password.FieldUsername, field.TypeString, value)
_node.Username = value
}
if value, ok := _c.mutation.PreferredUsername(); ok {
_spec.SetField(password.FieldPreferredUsername, field.TypeString, value)
_node.PreferredUsername = value
}
if value, ok := _c.mutation.UserID(); ok {
_spec.SetField(password.FieldUserID, field.TypeString, value)
_node.UserID = value
}
if value, ok := _c.mutation.Groups(); ok {
_spec.SetField(password.FieldGroups, field.TypeJSON, value)
_node.Groups = value
}
return _node, _spec
}
@ -167,6 +207,7 @@ func (_c *PasswordCreateBulk) Save(ctx context.Context) ([]*Password, error) {
for i := range _c.builders {
func(i int, root context.Context) {
builder := _c.builders[i]
builder.defaults()
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*PasswordMutation)
if !ok {

93
storage/ent/db/password_update.go

@ -9,6 +9,7 @@ import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/dialect/sql/sqljson"
"entgo.io/ent/schema/field"
"github.com/dexidp/dex/storage/ent/db/password"
"github.com/dexidp/dex/storage/ent/db/predicate"
@ -61,6 +62,20 @@ func (_u *PasswordUpdate) SetNillableUsername(v *string) *PasswordUpdate {
return _u
}
// SetPreferredUsername sets the "preferred_username" field.
func (_u *PasswordUpdate) SetPreferredUsername(v string) *PasswordUpdate {
_u.mutation.SetPreferredUsername(v)
return _u
}
// SetNillablePreferredUsername sets the "preferred_username" field if the given value is not nil.
func (_u *PasswordUpdate) SetNillablePreferredUsername(v *string) *PasswordUpdate {
if v != nil {
_u.SetPreferredUsername(*v)
}
return _u
}
// SetUserID sets the "user_id" field.
func (_u *PasswordUpdate) SetUserID(v string) *PasswordUpdate {
_u.mutation.SetUserID(v)
@ -75,6 +90,24 @@ func (_u *PasswordUpdate) SetNillableUserID(v *string) *PasswordUpdate {
return _u
}
// SetGroups sets the "groups" field.
func (_u *PasswordUpdate) SetGroups(v []string) *PasswordUpdate {
_u.mutation.SetGroups(v)
return _u
}
// AppendGroups appends value to the "groups" field.
func (_u *PasswordUpdate) AppendGroups(v []string) *PasswordUpdate {
_u.mutation.AppendGroups(v)
return _u
}
// ClearGroups clears the value of the "groups" field.
func (_u *PasswordUpdate) ClearGroups() *PasswordUpdate {
_u.mutation.ClearGroups()
return _u
}
// Mutation returns the PasswordMutation object of the builder.
func (_u *PasswordUpdate) Mutation() *PasswordMutation {
return _u.mutation
@ -148,9 +181,23 @@ func (_u *PasswordUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.Username(); ok {
_spec.SetField(password.FieldUsername, field.TypeString, value)
}
if value, ok := _u.mutation.PreferredUsername(); ok {
_spec.SetField(password.FieldPreferredUsername, field.TypeString, value)
}
if value, ok := _u.mutation.UserID(); ok {
_spec.SetField(password.FieldUserID, field.TypeString, value)
}
if value, ok := _u.mutation.Groups(); ok {
_spec.SetField(password.FieldGroups, field.TypeJSON, value)
}
if value, ok := _u.mutation.AppendedGroups(); ok {
_spec.AddModifier(func(u *sql.UpdateBuilder) {
sqljson.Append(u, password.FieldGroups, value)
})
}
if _u.mutation.GroupsCleared() {
_spec.ClearField(password.FieldGroups, field.TypeJSON)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{password.Label}
@ -205,6 +252,20 @@ func (_u *PasswordUpdateOne) SetNillableUsername(v *string) *PasswordUpdateOne {
return _u
}
// SetPreferredUsername sets the "preferred_username" field.
func (_u *PasswordUpdateOne) SetPreferredUsername(v string) *PasswordUpdateOne {
_u.mutation.SetPreferredUsername(v)
return _u
}
// SetNillablePreferredUsername sets the "preferred_username" field if the given value is not nil.
func (_u *PasswordUpdateOne) SetNillablePreferredUsername(v *string) *PasswordUpdateOne {
if v != nil {
_u.SetPreferredUsername(*v)
}
return _u
}
// SetUserID sets the "user_id" field.
func (_u *PasswordUpdateOne) SetUserID(v string) *PasswordUpdateOne {
_u.mutation.SetUserID(v)
@ -219,6 +280,24 @@ func (_u *PasswordUpdateOne) SetNillableUserID(v *string) *PasswordUpdateOne {
return _u
}
// SetGroups sets the "groups" field.
func (_u *PasswordUpdateOne) SetGroups(v []string) *PasswordUpdateOne {
_u.mutation.SetGroups(v)
return _u
}
// AppendGroups appends value to the "groups" field.
func (_u *PasswordUpdateOne) AppendGroups(v []string) *PasswordUpdateOne {
_u.mutation.AppendGroups(v)
return _u
}
// ClearGroups clears the value of the "groups" field.
func (_u *PasswordUpdateOne) ClearGroups() *PasswordUpdateOne {
_u.mutation.ClearGroups()
return _u
}
// Mutation returns the PasswordMutation object of the builder.
func (_u *PasswordUpdateOne) Mutation() *PasswordMutation {
return _u.mutation
@ -322,9 +401,23 @@ func (_u *PasswordUpdateOne) sqlSave(ctx context.Context) (_node *Password, err
if value, ok := _u.mutation.Username(); ok {
_spec.SetField(password.FieldUsername, field.TypeString, value)
}
if value, ok := _u.mutation.PreferredUsername(); ok {
_spec.SetField(password.FieldPreferredUsername, field.TypeString, value)
}
if value, ok := _u.mutation.UserID(); ok {
_spec.SetField(password.FieldUserID, field.TypeString, value)
}
if value, ok := _u.mutation.Groups(); ok {
_spec.SetField(password.FieldGroups, field.TypeJSON, value)
}
if value, ok := _u.mutation.AppendedGroups(); ok {
_spec.AddModifier(func(u *sql.UpdateBuilder) {
sqljson.Append(u, password.FieldGroups, value)
})
}
if _u.mutation.GroupsCleared() {
_spec.ClearField(password.FieldGroups, field.TypeJSON)
}
_node = &Password{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues

6
storage/ent/db/runtime.go

@ -212,8 +212,12 @@ func init() {
passwordDescUsername := passwordFields[2].Descriptor()
// password.UsernameValidator is a validator for the "username" field. It is called by the builders before save.
password.UsernameValidator = passwordDescUsername.Validators[0].(func(string) error)
// passwordDescPreferredUsername is the schema descriptor for preferred_username field.
passwordDescPreferredUsername := passwordFields[3].Descriptor()
// password.DefaultPreferredUsername holds the default value on creation for the preferred_username field.
password.DefaultPreferredUsername = passwordDescPreferredUsername.Default.(string)
// passwordDescUserID is the schema descriptor for user_id field.
passwordDescUserID := passwordFields[3].Descriptor()
passwordDescUserID := passwordFields[4].Descriptor()
// password.UserIDValidator is a validator for the "user_id" field. It is called by the builders before save.
password.UserIDValidator = passwordDescUserID.Validators[0].(func(string) error)
refreshtokenFields := schema.RefreshToken{}.Fields()

5
storage/ent/schema/password.go

@ -32,9 +32,14 @@ func (Password) Fields() []ent.Field {
field.Text("username").
SchemaType(textSchema).
NotEmpty(),
field.Text("preferred_username").
SchemaType(textSchema).
Default(""),
field.Text("user_id").
SchemaType(textSchema).
NotEmpty(),
field.JSON("groups", []string{}).
Optional(),
}
}

8
storage/kubernetes/storage.go

@ -383,13 +383,7 @@ func (cli *client) ListPasswords(ctx context.Context) (passwords []storage.Passw
}
for _, password := range passwordList.Passwords {
p := storage.Password{
Email: password.Email,
Hash: password.Hash,
Username: password.Username,
UserID: password.UserID,
}
passwords = append(passwords, p)
passwords = append(passwords, toStoragePassword(password))
}
return

28
storage/kubernetes/types.go

@ -431,9 +431,11 @@ type Password struct {
// This field is IMMUTABLE. Do not change.
Email string `json:"email,omitempty"`
Hash []byte `json:"hash,omitempty"`
Username string `json:"username,omitempty"`
UserID string `json:"userID,omitempty"`
Hash []byte `json:"hash,omitempty"`
Username string `json:"username,omitempty"`
PreferredUsername string `json:"preferredUsername,omitempty"`
UserID string `json:"userID,omitempty"`
Groups []string `json:"groups,omitempty"`
}
// PasswordList is a list of Passwords.
@ -454,19 +456,23 @@ func (cli *client) fromStoragePassword(p storage.Password) Password {
Name: cli.idToName(email),
Namespace: cli.namespace,
},
Email: email,
Hash: p.Hash,
Username: p.Username,
UserID: p.UserID,
Email: email,
Hash: p.Hash,
Username: p.Username,
PreferredUsername: p.PreferredUsername,
UserID: p.UserID,
Groups: p.Groups,
}
}
func toStoragePassword(p Password) storage.Password {
return storage.Password{
Email: p.Email,
Hash: p.Hash,
Username: p.Username,
UserID: p.UserID,
Email: p.Email,
Hash: p.Hash,
Username: p.Username,
PreferredUsername: p.PreferredUsername,
UserID: p.UserID,
Groups: p.Groups,
}
}

18
storage/sql/crud.go

@ -598,13 +598,13 @@ func (c *conn) CreatePassword(ctx context.Context, p storage.Password) error {
p.Email = strings.ToLower(p.Email)
_, err := c.Exec(`
insert into password (
email, hash, username, user_id
email, hash, username, preferred_username, user_id, groups
)
values (
$1, $2, $3, $4
$1, $2, $3, $4, $5, $6
);
`,
p.Email, p.Hash, p.Username, p.UserID,
p.Email, p.Hash, p.Username, p.PreferredUsername, p.UserID, encoder(p.Groups),
)
if err != nil {
if c.alreadyExistsCheck(err) {
@ -629,10 +629,10 @@ func (c *conn) UpdatePassword(ctx context.Context, email string, updater func(p
_, err = tx.Exec(`
update password
set
hash = $1, username = $2, user_id = $3
where email = $4;
hash = $1, username = $2, preferred_username = $3, user_id = $4, groups = $5
where email = $6;
`,
np.Hash, np.Username, np.UserID, p.Email,
np.Hash, np.Username, np.PreferredUsername, np.UserID, encoder(np.Groups), p.Email,
)
if err != nil {
return fmt.Errorf("update password: %v", err)
@ -648,7 +648,7 @@ func (c *conn) GetPassword(ctx context.Context, email string) (storage.Password,
func getPassword(ctx context.Context, q querier, email string) (p storage.Password, err error) {
return scanPassword(q.QueryRow(`
select
email, hash, username, user_id
email, hash, username, preferred_username, user_id, groups
from password where email = $1;
`, strings.ToLower(email)))
}
@ -656,7 +656,7 @@ func getPassword(ctx context.Context, q querier, email string) (p storage.Passwo
func (c *conn) ListPasswords(ctx context.Context) ([]storage.Password, error) {
rows, err := c.Query(`
select
email, hash, username, user_id
email, hash, username, preferred_username, user_id, groups
from password;
`)
if err != nil {
@ -680,7 +680,7 @@ func (c *conn) ListPasswords(ctx context.Context) ([]storage.Password, error) {
func scanPassword(s scanner) (p storage.Password, err error) {
err = s.Scan(
&p.Email, &p.Hash, &p.Username, &p.UserID,
&p.Email, &p.Hash, &p.Username, &p.PreferredUsername, &p.UserID, decoder(&p.Groups),
)
if err != nil {
if err == sql.ErrNoRows {

40
storage/sql/migrate.go

@ -298,4 +298,44 @@ var migrations = []migration{
add column hmac_key bytea;`,
},
},
{
stmts: []string{
`
alter table password
add column preferred_username text not null default '';`,
`
alter table password
add column groups bytea not null default convert_to('[]', 'UTF8');`,
},
flavor: &flavorPostgres,
},
{
stmts: []string{
`
alter table password
add column preferred_username text not null default '';`,
`
alter table password
add column groups bytea not null default '[]';`,
},
flavor: &flavorSQLite3,
},
{
stmts: []string{
`
alter table password
add column preferred_username text not null default '';`,
`
alter table password
add column groups bytea;`,
`
update password
set groups = '[]'
where groups is null;`,
`
alter table password
modify column groups bytea not null;`,
},
flavor: &flavorMySQL,
},
}

6
storage/storage.go

@ -352,8 +352,14 @@ type Password struct {
// Optional username to display. NOT used during login.
Username string `json:"username"`
// Optional preferred username for OIDC "preferred_username" claim.
PreferredUsername string `json:"preferredUsername"`
// Randomly generated user ID. This is NOT the primary ID of the Password object.
UserID string `json:"userID"`
// Groups assigned to the user
Groups []string `json:"groups"`
}
// Connector is an object that contains the metadata about connectors used to login to Dex.

Loading…
Cancel
Save