diff --git a/db/migrations/0011_case_insensitive_emails.sql b/db/migrations/0011_case_insensitive_emails.sql new file mode 100644 index 00000000..b04772d0 --- /dev/null +++ b/db/migrations/0011_case_insensitive_emails.sql @@ -0,0 +1,18 @@ +-- +migrate Up + +CREATE OR REPLACE FUNCTION raise_exp() RETURNS VOID AS $$ +BEGIN + RAISE EXCEPTION 'Found duplicate emails when using case insensitive comparision, cannot perform migration.'; +END; +$$ LANGUAGE plpgsql; + +SELECT LOWER(email), + COUNT(email), + CASE + WHEN COUNT(email) > 1 THEN raise_exp() + ELSE NULL + END +FROM authd_user +GROUP BY LOWER(email); + +UPDATE authd_user SET email = LOWER(email); diff --git a/db/user.go b/db/user.go index 89e49b3c..7a93c480 100644 --- a/db/user.go +++ b/db/user.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "reflect" + "strings" "time" "github.com/go-gorp/gorp" @@ -400,7 +401,7 @@ func (r *userRepo) getByEmail(tx repo.Transaction, email string) (user.User, err qt := r.quote(userTableName) ex := r.executor(tx) var um userModel - err := ex.SelectOne(&um, fmt.Sprintf("select * from %s where email = $1", qt), email) + err := ex.SelectOne(&um, fmt.Sprintf("select * from %s where email = $1", qt), strings.ToLower(email)) if err != nil { if err == sql.ErrNoRows { @@ -424,7 +425,7 @@ func (r *userRepo) insertRemoteIdentity(tx repo.Transaction, userID string, ri u type userModel struct { ID string `db:"id"` - Email string `db:"email"` + Email string `db:"email"` // NOTE(ericchiang): When making comparisions emails are case insensitive. EmailVerified bool `db:"email_verified"` DisplayName string `db:"display_name"` Disabled bool `db:"disabled"` @@ -453,7 +454,7 @@ func newUserModel(u *user.User) (*userModel, error) { um := userModel{ ID: u.ID, DisplayName: u.DisplayName, - Email: u.Email, + Email: strings.ToLower(u.Email), EmailVerified: u.EmailVerified, Admin: u.Admin, Disabled: u.Disabled, diff --git a/user/user.go b/user/user.go index 979ee590..1737750b 100644 --- a/user/user.go +++ b/user/user.go @@ -105,6 +105,7 @@ func (u *User) AddToClaims(claims jose.Claims) { // UserRepo implementations maintain a persistent set of users. // The following invariants must be maintained: // * Users must have a unique Email and ID +// * Emails are case insensitive. // * No other Users may have the same RemoteIdentity as one of the // users. (This constraint may be relaxed in the future) type UserRepo interface {