Skip to content

fix(identity): normalize blank email/phone to NULL before persisting - #850

Open
libingtong wants to merge 1 commit into
dataelement:mainfrom
libingtong:fix/blank-identity-contact-fields
Open

fix(identity): normalize blank email/phone to NULL before persisting#850
libingtong wants to merge 1 commit into
dataelement:mainfrom
libingtong:fix/blank-identity-contact-fields

Conversation

@libingtong

Copy link
Copy Markdown

identities.email and identities.phone are both unique=True and nullable. Postgres allows many NULLs in a unique index but only a single empty string, so writing "" makes the second identity that lacks the field fail with:

duplicate key value violates unique constraint "ix_identities_email"

How it happens

ExternalUserInfo defaults email and mobile to "" (not None), and BaseAuthProvider._create_new_user passes them straight to find_or_create_identity, which forwards them to identity_dao.create_identity.

Any SSO provider whose upstream directory does not expose an email address therefore works for exactly one user and then breaks for everyone after.

Observed

With an enterprise directory that returns neither email nor mobile for some members: the first such user signs in fine, the second gets a 500 from the OAuth callback (OAuth 登录失败 / Internal server error on the callback page).

This is easy to miss during integration testing because it only appears once a second contact-less user tries to sign in.

This PR

Normalizes at the entry point of find_or_create_identity, covering both the lookup and the insert path.

It also prevents "" from being used as a lookup key — which would otherwise merge every contact-less user into a single identity. The function's own comment notes that only email and phone are authoritative identity claims, and an empty string is not a claim.

Existing rows already containing "" should be normalized to NULL as a one-off (UPDATE identities SET email = NULL WHERE email = ''), otherwise they keep occupying the single allowed empty-string slot.

identities.email and identities.phone are both `unique=True` and nullable.
Postgres allows many NULLs in a unique index but only a single empty string, so
writing "" makes the *second* identity that lacks the field fail with:

    duplicate key value violates unique constraint "ix_identities_email"

ExternalUserInfo defaults email and mobile to "" (not None), and
BaseAuthProvider._create_new_user passes them straight to
find_or_create_identity, which forwards them to identity_dao.create_identity.
Any SSO provider whose upstream directory does not expose an email address
therefore works for exactly one user and then breaks for everyone after.

Observed in production with an enterprise directory that returns neither email
nor mobile for some members: the first such user signs in fine, the second gets
a 500 from the OAuth callback.

Normalizing at the entry point of find_or_create_identity covers both the lookup
and the insert path. It also prevents "" from being used as a lookup key, which
would otherwise merge every contact-less user into one identity — the function's
own comment notes that only email and phone are authoritative ownership claims,
and an empty string is not a claim.
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.

1 participant