From bcdade7d8c30e77d2b5f3981b473f3be57dfe32a Mon Sep 17 00:00:00 2001 From: Callum Styan Date: Thu, 21 Aug 2025 07:56:41 -0700 Subject: [PATCH] fix: add database constraint to enforce minimum username length (#19453) Username length and format, via regex, are already enforced at the application layer, but we have some code paths with database queries where we could optimize away many of the DB query calls if we could be sure at the database level that the username is never an empty string. For example: https://github.com/coder/coder/pull/19395 --------- Signed-off-by: Callum Styan --- coderd/database/check_constraint.go | 1 + coderd/database/dump.sql | 3 ++- .../migrations/000361_username_length_constraint.down.sql | 2 ++ .../migrations/000361_username_length_constraint.up.sql | 3 +++ coderd/database/querier_test.go | 7 +++++-- 5 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 coderd/database/migrations/000361_username_length_constraint.down.sql create mode 100644 coderd/database/migrations/000361_username_length_constraint.up.sql diff --git a/coderd/database/check_constraint.go b/coderd/database/check_constraint.go index e827ef3f02..ac204f85f5 100644 --- a/coderd/database/check_constraint.go +++ b/coderd/database/check_constraint.go @@ -7,6 +7,7 @@ type CheckConstraint string // CheckConstraint enums. const ( CheckOneTimePasscodeSet CheckConstraint = "one_time_passcode_set" // users + CheckUsersUsernameMinLength CheckConstraint = "users_username_min_length" // users CheckMaxProvisionerLogsLength CheckConstraint = "max_provisioner_logs_length" // provisioner_jobs CheckValidationMonotonicOrder CheckConstraint = "validation_monotonic_order" // template_version_parameters CheckUsageEventTypeCheck CheckConstraint = "usage_event_type_check" // usage_events diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index aca22b6dbb..066fe0b1b8 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1015,7 +1015,8 @@ CREATE TABLE users ( hashed_one_time_passcode bytea, one_time_passcode_expires_at timestamp with time zone, is_system boolean DEFAULT false NOT NULL, - CONSTRAINT one_time_passcode_set CHECK ((((hashed_one_time_passcode IS NULL) AND (one_time_passcode_expires_at IS NULL)) OR ((hashed_one_time_passcode IS NOT NULL) AND (one_time_passcode_expires_at IS NOT NULL)))) + CONSTRAINT one_time_passcode_set CHECK ((((hashed_one_time_passcode IS NULL) AND (one_time_passcode_expires_at IS NULL)) OR ((hashed_one_time_passcode IS NOT NULL) AND (one_time_passcode_expires_at IS NOT NULL)))), + CONSTRAINT users_username_min_length CHECK ((length(username) >= 1)) ); COMMENT ON COLUMN users.quiet_hours_schedule IS 'Daily (!) cron schedule (with optional CRON_TZ) signifying the start of the user''s quiet hours. If empty, the default quiet hours on the instance is used instead.'; diff --git a/coderd/database/migrations/000361_username_length_constraint.down.sql b/coderd/database/migrations/000361_username_length_constraint.down.sql new file mode 100644 index 0000000000..cb3fccad73 --- /dev/null +++ b/coderd/database/migrations/000361_username_length_constraint.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE users +DROP CONSTRAINT IF EXISTS users_username_min_length; diff --git a/coderd/database/migrations/000361_username_length_constraint.up.sql b/coderd/database/migrations/000361_username_length_constraint.up.sql new file mode 100644 index 0000000000..526d31c0a7 --- /dev/null +++ b/coderd/database/migrations/000361_username_length_constraint.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE users +ADD CONSTRAINT users_username_min_length +CHECK (length(username) >= 1); diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 0e11886765..60e13ad5d9 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -1552,8 +1552,11 @@ func TestUpdateSystemUser(t *testing.T) { // When: attempting to update a system user's name. _, err = db.UpdateUserProfile(ctx, database.UpdateUserProfileParams{ - ID: systemUser.ID, - Name: "not prebuilds", + ID: systemUser.ID, + Email: systemUser.Email, + Username: systemUser.Username, + AvatarURL: systemUser.AvatarURL, + Name: "not prebuilds", }) // Then: the attempt is rejected by a postgres trigger. // require.ErrorContains(t, err, "Cannot modify or delete system users")