Skip to content

fix: make signin email case-insensitive #4048

Merged
mikecao merged 2 commits intoumami-software:devfrom
AlejandroGispert:fix/3981-login-email-case
Apr 2, 2026
Merged

fix: make signin email case-insensitive #4048
mikecao merged 2 commits intoumami-software:devfrom
AlejandroGispert:fix/3981-login-email-case

Conversation

@AlejandroGispert
Copy link
Copy Markdown
Contributor

Case-Insensitive Username Login, Closes #3981

Author: Alejandro Gispert
First-time contributor to Umami


### What

Makes login case-insensitive: `Admin`, `admin`, and `ADMIN` all resolve to the same account.

### Why

- Reduces friction when users don't recall exact casing
- Aligns with common expectations from modern apps
- Avoids lockouts caused by caps lock

### How

- Usernames are normalized to lowercase on create, update, and lookup
- Run the provided SQL to lowercase existing usernames in the database

### Changes

- `src/queries/prisma/user.ts``getUserByUsername()` lowercases before lookup
- `src/app/api/users/route.ts` – Store and check usernames in lowercase on create
- `src/app/api/users/[userId]/route.ts` – Store lowercase on update; fix uniqueness check

### Post-merge: run this SQL

Existing users with mixed-case usernames must be updated. Run against your database:

```sql
UPDATE "user" SET username = LOWER(username) WHERE username != LOWER(username);

Full documentation

Summary

This PR makes user login case-insensitive so that usernames like Admin, admin, and ADMIN all resolve to the same account.


Problem

Login was case-sensitive: a user created with username admin could not log in by typing Admin or ADMIN. This led to:

  • Friction for users who don't recall the exact casing
  • Inconsistent behavior compared to many modern apps
  • More lockouts caused by accidental caps lock

Solution

Usernames are normalized to lowercase on storage and lookup:

  1. Storage – Usernames are stored in lowercase when creating or updating users.
  2. Lookup – User lookup always uses lowercase when resolving a username.
  3. Uniqueness – Username uniqueness is enforced in a case-insensitive way (e.g. Admin and admin cannot coexist).

Changes

1. src/queries/prisma/user.ts

  • getUserByUsername() – Normalizes the input with username.toLowerCase() before querying, so lookups are case-insensitive.

2. src/app/api/auth/login/route.ts

  • No code changes; it already calls getUserByUsername(), which now handles case-insensitivity.

3. src/app/api/users/route.ts

  • User creation – Uses username.toLowerCase() when checking for existing users and when creating new users.

4. src/app/api/users/[userId]/route.ts

  • User update – Uses username.toLowerCase() when updating usernames.
  • Uniqueness check – Fixed two issues when validating username changes:
    • Variable shadowing: The lookup reused the variable user (shadowing the current user), causing confusion. Renamed to existingUser.
  • Same-user case change: Previously, if the lookup returned any user (including the current user), the update was rejected. Now we only reject when a different user already has that username. That way, a user can safely “change” their username from Admin to admin (same logical username) without being blocked.

Post-merge: run this SQL

Existing users with mixed-case usernames must be updated. Run this against your database after merging:

UPDATE "user" SET username = LOWER(username) WHERE username != LOWER(username);

Note: This will fail if there are case-variant duplicates (e.g. both Admin and admin). Resolve those manually before running.


Testing

  • Login with uppercase, mixed case, and lowercase variants of a username.
  • Create a new user and confirm it is stored in lowercase.
  • Update an existing user's username and confirm it is stored in lowercase.
  • Ensure username uniqueness is enforced case-insensitively (e.g. cannot create admin if Admin exists).

Backward Compatibility

  • Existing users: The migration updates stored usernames to lowercase; login continues to work.
  • Display: Usernames are shown as stored (typically lowercase after running the SQL).
  • Breaking change: No; behavior is additive and improves UX.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 22, 2026

@AlejandroGispert is attempting to deploy a commit to the umami-software Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Feb 22, 2026

Greptile Summary

This PR implements case-insensitive username authentication by normalizing usernames to lowercase on storage and lookup. The implementation includes:

  • Username lookup normalized to lowercase in getUserByUsername() so login accepts any casing
  • Username storage converted to lowercase during user creation and updates
  • Uniqueness checking now properly case-insensitive across all endpoints
  • Fixed variable shadowing bug where existingUser was incorrectly named user
  • Fixed logic to allow users to update their own username casing without rejection

The approach is sound and aligns with common authentication patterns. The PR requires a post-merge SQL migration to lowercase existing usernames in the database.

Confidence Score: 4/5

  • Safe to merge with one minor consideration about SQL migration timing
  • The implementation is solid with proper case-insensitive handling across all user operations. The code fixes a real bug (variable shadowing) and implements the feature correctly. However, there's a deployment consideration: the SQL migration mentioned in the PR description needs careful coordination with the code deployment to avoid potential edge cases with existing mixed-case usernames.
  • No files require special attention - implementation is straightforward and correct

Important Files Changed

Filename Overview
src/queries/prisma/user.ts Normalized username lookup to lowercase in getUserByUsername() for case-insensitive authentication
src/app/api/users/route.ts Stores username in lowercase during user creation and checks for duplicates case-insensitively
src/app/api/users/[userId]/route.ts Stores username in lowercase on update; fixed variable shadowing and same-user update logic

Sequence Diagram

sequenceDiagram
    participant Client
    participant LoginAPI as /api/auth/login
    participant UserAPI as /api/users
    participant UpdateAPI as /api/users/[userId]
    participant getUserByUsername
    participant createUser
    participant updateUser
    participant Database

    Note over Client,Database: Login Flow (Case-Insensitive)
    Client->>LoginAPI: POST {username: "Admin", password}
    LoginAPI->>getUserByUsername: getUserByUsername("Admin")
    getUserByUsername->>Database: SELECT WHERE username = "admin"
    Database-->>getUserByUsername: user record
    getUserByUsername-->>LoginAPI: user found
    LoginAPI-->>Client: success + token

    Note over Client,Database: User Creation
    Client->>UserAPI: POST {username: "NewUser"}
    UserAPI->>getUserByUsername: check exists ("NewUser")
    getUserByUsername->>Database: SELECT WHERE username = "newuser"
    Database-->>getUserByUsername: null
    UserAPI->>createUser: createUser({username: "newuser"})
    createUser->>Database: INSERT username = "newuser"
    Database-->>UserAPI: user created
    UserAPI-->>Client: success

    Note over Client,Database: Username Update
    Client->>UpdateAPI: POST {username: "ADMIN"}
    UpdateAPI->>getUserByUsername: check exists ("ADMIN")
    getUserByUsername->>Database: SELECT WHERE username = "admin"
    Database-->>getUserByUsername: existing user found
    UpdateAPI->>UpdateAPI: if existingUser.id != userId, reject
    UpdateAPI->>updateUser: updateUser({username: "admin"})
    updateUser->>Database: UPDATE username = "admin"
    Database-->>UpdateAPI: user updated
    UpdateAPI-->>Client: success
Loading

Last reviewed commit: 2ab5870

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@mikecao mikecao merged commit b634159 into umami-software:dev Apr 2, 2026
0 of 5 checks passed
franciscao633 added a commit that referenced this pull request Apr 7, 2026
…-email-case"

This reverts commit b634159, reversing
changes made to 0735632.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants