fix: enforce unique usernames (#1628)

* fix: enforce unique usernames

Registration checked for a taken name, hashed the password, then
inserted, with nothing in the schema enforcing uniqueness. Concurrent
sign-ups could create two accounts with one name, after which login,
password reset, proxy login and admin lookups by username picked
between them arbitrarily.

Add a unique index on users.username for every dialect. Existing
duplicates are renamed (later rows get a -<id> suffix, the oldest keeps
the name) rather than deleted. Registration and admin user creation now
return the same 409 when the insert loses the race.

* fix: avoid username collisions during migration
This commit is contained in:
ZacharyZcR authored and GitHub committed 2026-10-14 08:17:47 +08:00
1 parent 3941dd4ecc
commit e610aba0e5
19 files changed
+14354 -26

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
-- Choose an unused prefix from N + 1 disjoint prefixes for N existing users.
WITH ranked AS MATERIALIZED (
SELECT id, username,
ROW_NUMBER() OVER (ORDER BY registered_at, id) AS sequence,
ROW_NUMBER() OVER (PARTITION BY username ORDER BY registered_at, id) AS occurrence
FROM users
), candidates AS (
SELECT 0 AS candidate UNION ALL SELECT sequence FROM ranked
), available AS (
SELECT MIN(candidate) AS candidate FROM candidates
WHERE NOT EXISTS (
SELECT 1 FROM ranked WHERE username LIKE 'termix-recovered-' || candidate || '-' || '%'
)
)
UPDATE users SET username = 'termix-recovered-' || (SELECT candidate FROM available) || '-' || ranked.sequence
FROM ranked WHERE ranked.id = users.id AND ranked.occurrence > 1;--> statement-breakpoint
ALTER TABLE "users" ALTER COLUMN "username" SET DATA TYPE varchar(255);--> statement-breakpoint
CREATE UNIQUE INDEX "idx_users_username" ON "users" USING btree ("username");