Skip to content

Enforce normalised user emails with a check constraint - #1164

Merged
helpcodeai merged 4 commits into
mainfrom
fix/users-email-normalised-check
Oct 19, 2026
Merged

helpcodeai merged 4 commits into
mainfrom
fix/users-email-normalised-check

Conversation

@helpcodeai

@helpcodeai helpcodeai commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Why

#1159 made every write path normalise addresses (trimmed, lower case) and every lookup case-insensitive, and its migration lower-cased the existing rows. The rows that only differed in case were merged by hand afterwards, so production now has no address that is not normalised and no case-variant duplicates.

What

A migration that adds users_email_normalised_check: CHECK (email = lower(btrim(email))).

  • Combined with the existing unique index on email, an address can only exist once, in any case.
  • A write path that forgets to normalise now fails with a constraint error instead of quietly creating a second account.

A check constraint was chosen rather than a unique index on lower(email): Prisma cannot model expression indexes and would try to drop one on the next migrate dev, while it leaves check constraints alone.

Safety

  • The constraint is only added when no row violates it.
  • Self-hosted installs can still hold case-variant duplicates, which the Treat email addresses case-insensitively #1159 migration leaves untouched. On those the migration succeeds without adding the constraint and logs a notice: a failing migration would keep the app from starting.
  • Merge the rows listed by packages/backend/scripts/report-duplicate-emails.sql, then run the ALTER TABLE from the migration by hand.
  • Checked against production: 0 rows violate it, so on Cloud the constraint is added.

helpcodeai and others added 3 commits October 19, 2026 20:57
Every stored address is lower-cased and trimmed since the email case fix and
the manual merge of the case-variant duplicates. The check keeps it that way:
with the existing unique index on email, an address can only exist once in
any case, and a write path that forgets to normalise fails instead of
creating a second account.
A failing migration keeps the app from starting. Self-hosted installs can
still hold case-variant duplicates, so the constraint is only added when
every row satisfies it, and a notice explains how to add it later.
@helpcodeai
helpcodeai enabled auto-merge (squash) October 19, 2026 19:12
@helpcodeai
helpcodeai merged commit 023b4d1 into main Oct 19, 2026
14 checks passed
@helpcodeai
helpcodeai deleted the fix/users-email-normalised-check branch October 19, 2026 19:36
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant