feat(username): normalize username into lowercase

- normalize username into lowercase (this is already ASCII only)
- permit users to login with their username with insensitive case
- if a disabled account is reactivated and got a collision, it will normalize it too
- server will stop on collision (ex: 2 entries with `Alice` and `alice`)
  in a such case admin can run:

```
oxicloud migrate lowercase-usernames --dry-run
```
then
```
oxicloud migrate lowercase-usernames
```
This commit is contained in:
Edouard Vanbelle
2026-09-13 18:46:42 +02:00
parent 0b9e8bfe23
commit a95a6b106c
15 changed files with 1454 additions and 81 deletions
@@ -125,10 +125,16 @@ impl StorageUsageService {
}
/// Same as [`Self::update_user_storage_usage`], keyed by username.
///
/// Lowercases input before bind — same rule as
/// `UserRepository::get_user_by_username`. Usernames are canonical
/// (lowercase) in the DB post-migration; callers may pass any case.
/// See `docs/plan/username-lowercase.md`.
pub async fn update_user_storage_usage_by_username(
&self,
username: &str,
) -> Result<i64, DomainError> {
let username = username.trim().to_ascii_lowercase();
let total_usage: Option<i64> = sqlx::query_scalar(
r#"
UPDATE auth.users u
@@ -146,7 +152,7 @@ impl StorageUsageService {
RETURNING u.storage_used_bytes
"#,
)
.bind(username)
.bind(&username)
.fetch_optional(self.pool.as_ref())
.await
.map_err(|e| {