diff --git a/lib/config.go b/lib/config.go index 4e2773b..7e77630 100644 --- a/lib/config.go +++ b/lib/config.go @@ -251,11 +251,17 @@ func (c *Config) Validate() error { return fmt.Errorf("invalid config: %w", err) } + usernames := map[string]struct{}{} for i := range c.Users { err := c.Users[i].Validate(c.NoPassword) if err != nil { return fmt.Errorf("invalid config: %w", err) } + + if _, ok := usernames[c.Users[i].Username]; ok { + return fmt.Errorf("invalid config: duplicate username %q", c.Users[i].Username) + } + usernames[c.Users[i].Username] = struct{}{} } return nil diff --git a/lib/config_test.go b/lib/config_test.go index b0c1298..4e94186 100644 --- a/lib/config_test.go +++ b/lib/config_test.go @@ -579,3 +579,42 @@ users: require.True(t, cfg.Users[0].checkPassword("admin")) require.True(t, cfg.Users[1].checkPassword("basic")) } + +func TestConfigDuplicateUsernames(t *testing.T) { + t.Run("Literal", func(t *testing.T) { + t.Parallel() + + writeAndParseConfigWithError(t, ` +users: + - username: alice + password: one + - username: alice + password: two +`, ".yml", `duplicate username "alice"`) + }) + + t.Run("Environment", func(t *testing.T) { + t.Setenv("COLLIDE_USERNAME", "alice") + + writeAndParseConfigWithError(t, ` +users: + - username: alice + password: one + - username: '{env}COLLIDE_USERNAME' + password: two +`, ".yml", `duplicate username "alice"`) + }) + + t.Run("Case Sensitive", func(t *testing.T) { + t.Parallel() + + cfg := writeAndParseConfig(t, ` +users: + - username: alice + password: one + - username: Alice + password: two +`, ".yml") + require.Len(t, cfg.Users, 2) + }) +}