Skip to content

Commit

Permalink
fix constraints
Browse files Browse the repository at this point in the history
Signed-off-by: Kristoffer Dalby <[email protected]>
  • Loading branch information
kradalby committed Nov 22, 2024
1 parent 60b9595 commit 4f57410
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 11 deletions.
19 changes: 19 additions & 0 deletions hscontrol/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,25 @@ func NewHeadscaleDatabase(
return err
}

// Set up indexes and unique constraints outside of GORM, it does not support
// conditional unique constraints.
// This ensures the following:
// - A user name and provider_identifier is unique
// - A provider_identifier is unique
// - A user name is unique if there is no provider_identifier is not set
for _, idx := range []string{
"DROP INDEX IF EXISTS `idx_provider_identifier`",
"DROP INDEX IF EXISTS `idx_name_provider_identifier`",
"CREATE UNIQUE INDEX IF NOT EXISTS `idx_provider_identifier` ON `users` (`provider_identifier`) WHERE provider_identifier IS NOT NULL;",
"CREATE UNIQUE INDEX IF NOT EXISTS `idx_name_provider_identifier` ON `users` (`name`,`provider_identifier`);",
"CREATE UNIQUE INDEX IF NOT EXISTS `idx_name_no_provider_identifier` ON `users` (`name`) WHERE provider_identifier IS NULL;",
} {
err = tx.Exec(idx).Error
if err != nil {
return fmt.Errorf("creating username index: %w", err)
}
}

return nil
},
Rollback: func(db *gorm.DB) error { return nil },
Expand Down
18 changes: 9 additions & 9 deletions hscontrol/db/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,8 @@ func TestConstraints(t *testing.T) {
require.NoError(t, err)
_, err = CreateUser(db, "user1")
require.Error(t, err)
// assert.Contains(t, err.Error(), "UNIQUE constraint failed: users.username")
require.Contains(t, err.Error(), "user already exists")
assert.Contains(t, err.Error(), "UNIQUE constraint failed:")
// require.Contains(t, err.Error(), "user already exists")
},
},
{
Expand All @@ -295,7 +295,7 @@ func TestConstraints(t *testing.T) {

err = db.Save(&user).Error
require.Error(t, err)
require.Contains(t, err.Error(), "UNIQUE constraint failed: users.provider_identifier")
require.Contains(t, err.Error(), "UNIQUE constraint failed:")
},
},
{
Expand All @@ -318,7 +318,7 @@ func TestConstraints(t *testing.T) {

err = db.Save(&user).Error
require.Error(t, err)
require.Contains(t, err.Error(), "UNIQUE constraint failed: users.provider_identifier")
require.Contains(t, err.Error(), "UNIQUE constraint failed:")
},
},
{
Expand All @@ -328,9 +328,9 @@ func TestConstraints(t *testing.T) {
require.NoError(t, err)

user := types.User{
Name: "user1",
Name: "user1",
ProviderIdentifier: sql.NullString{String: "http://test.com/user1", Valid: true},
}
user.ProviderIdentifier.String = "http://test.com/user1"

err = db.Save(&user).Error
require.NoError(t, err)
Expand All @@ -340,9 +340,9 @@ func TestConstraints(t *testing.T) {
name: "allow-duplicate-username-oidc-then-cli",
run: func(t *testing.T, db *gorm.DB) {
user := types.User{
Name: "user1",
Name: "user1",
ProviderIdentifier: sql.NullString{String: "http://test.com/user1", Valid: true},
}
user.ProviderIdentifier.String = "http://test.com/user1"

err := db.Save(&user).Error
require.NoError(t, err)
Expand All @@ -360,7 +360,7 @@ func TestConstraints(t *testing.T) {
t.Fatalf("creating database: %s", err)
}

tt.run(t, db.DB)
tt.run(t, db.DB.Debug())
})

}
Expand Down
4 changes: 2 additions & 2 deletions hscontrol/types/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type User struct {

// Username for the user, is used if email is empty
// Should not be used, please use Username().
Name string `gorm:"uniqueIndex:idx_name_provider_identifier;index"`
Name string

// Typically the full name of the user
DisplayName string
Expand All @@ -39,7 +39,7 @@ type User struct {
// Unique identifier of the user from OIDC,
// comes from `sub` claim in the OIDC token
// and is used to lookup the user.
ProviderIdentifier sql.NullString `gorm:"uniqueIndex:idx_name_provider_identifier;uniqueIndex:idx_provider_identifier"`
ProviderIdentifier sql.NullString

// Provider is the origin of the user account,
// same as RegistrationMethod, without authkey.
Expand Down

0 comments on commit 4f57410

Please sign in to comment.