diff --git a/capsulflask/auth.py b/capsulflask/auth.py index 038b692..5815fce 100644 --- a/capsulflask/auth.py +++ b/capsulflask/auth.py @@ -43,17 +43,28 @@ def login(): errors.append("enter a valid email address") if len(errors) == 0: - token = get_model().login(email) + result = get_model().login(email) + token = result[0] + ignoreCaseMatches = result[1] if token is None: errors.append("too many logins. please use one of the existing login links that have been emailed to you") else: link = f"{current_app.config['BASE_URL']}/auth/magic/{token}" + + message = (f"Navigate to {link} to log into Capsul.\n" + "\nIf you didn't request this, ignore this message.") + + if len(ignoreCaseMatches) > 0: + joinedMatches = " or ".join(ignoreCaseMatches) + message = (f"You tried to log in as '{email}', but that account doesn't exist yet. " + "If you would like to create a new account for '{email}', click here {link} " + "If you meant to log in as {joinedMatches}, please return to capsul.org " + "and log in again with the correct (case-sensitive) email address.") current_app.config["FLASK_MAIL_INSTANCE"].send( Message( "Click This Link to Login to Capsul", - body=(f"Navigate to {link} to log into Capsul.\n" - "\nIf you didn't request this, ignore this message."), + body=message, recipients=[email] ) ) diff --git a/capsulflask/db.py b/capsulflask/db.py index 60cc103..9d5fde9 100644 --- a/capsulflask/db.py +++ b/capsulflask/db.py @@ -40,7 +40,7 @@ def init_app(app): hasSchemaVersionTable = False actionWasTaken = False schemaVersion = 0 - desiredSchemaVersion = 8 + desiredSchemaVersion = 9 cursor = connection.cursor() diff --git a/capsulflask/db_model.py b/capsulflask/db_model.py index 9017977..737db1f 100644 --- a/capsulflask/db_model.py +++ b/capsulflask/db_model.py @@ -10,18 +10,26 @@ class DBModel: def login(self, email): self.cursor.execute("SELECT * FROM accounts WHERE email = %s", (email, )) - if len(self.cursor.fetchall()) == 0: - self.cursor.execute("INSERT INTO accounts (email) VALUES (%s)", (email, )) + hasExactMatch = len(self.cursor.fetchall()) + self.cursor.execute("SELECT * FROM accounts WHERE email = %s AND ever_logged_in = TRUE", (email, )) + everLoggedIn = len(self.cursor.fetchall()) + ignoreCaseMatches = [] + if everLoggedIn == 0: + self.cursor.execute("SELECT email FROM accounts WHERE lower_case_email = %s", (email.lower(), )) + ignoreCaseMatches = self.cursor.fetchall() + + if hasExactMatch == 0: + self.cursor.execute("INSERT INTO accounts (email, lower_case_email) VALUES (%s, %s)", (email, email.lower())) self.cursor.execute("SELECT token FROM login_tokens WHERE email = %s", (email, )) if len(self.cursor.fetchall()) > 2: - return None + return (None, ignoreCaseMatches) token = generate() self.cursor.execute("INSERT INTO login_tokens (email, token) VALUES (%s, %s)", (email, token)) self.connection.commit() - return token + return (token, ignoreCaseMatches) def consume_token(self, token): self.cursor.execute("SELECT email FROM login_tokens WHERE token = %s and created > (NOW() - INTERVAL '20 min')", (token, )) @@ -29,6 +37,7 @@ class DBModel: if row: email = row[0] self.cursor.execute("DELETE FROM login_tokens WHERE email = %s", (email, )) + self.cursor.execute("UPDATE accounts SET ever_logged_in = TRUE WHERE email = %s", (email, )) self.connection.commit() return email return None @@ -228,7 +237,7 @@ class DBModel: if row: self.cursor.execute( "DELETE FROM unresolved_btcpay_invoices WHERE id = %s", (id,) ) if not completed: - self.cursor.execute("UPDATE payments SET invalidated = True WHERE email = %s id = %s", (row[0], row[1])) + self.cursor.execute("UPDATE payments SET invalidated = TRUE WHERE email = %s id = %s", (row[0], row[1])) self.connection.commit() @@ -249,7 +258,7 @@ class DBModel: self.connection.commit() def all_accounts(self): - self.cursor.execute("SELECT email, account_balance_warning FROM accounts") + self.cursor.execute("SELECT email, account_balance_warning FROM accounts WHERE ever_logged_in = TRUE ") return list(map(lambda row: dict(email=row[0], account_balance_warning=row[1]), self.cursor.fetchall())) diff --git a/capsulflask/schema_migrations/09_down_email_case.sql b/capsulflask/schema_migrations/09_down_email_case.sql new file mode 100644 index 0000000..050ecdf --- /dev/null +++ b/capsulflask/schema_migrations/09_down_email_case.sql @@ -0,0 +1,5 @@ + +ALTER TABLE accounts DROP COLUMN lower_case_email; +ALTER TABLE accounts DROP COLUMN ever_logged_in; + +UPDATE schemaversion SET version = 8; \ No newline at end of file diff --git a/capsulflask/schema_migrations/09_up_email_case.sql b/capsulflask/schema_migrations/09_up_email_case.sql new file mode 100644 index 0000000..01b3e71 --- /dev/null +++ b/capsulflask/schema_migrations/09_up_email_case.sql @@ -0,0 +1,10 @@ +ALTER TABLE accounts +ADD COLUMN lower_case_email TEXT NULL; + +ALTER TABLE accounts +ADD COLUMN ever_logged_in BOOLEAN NOT NULL DEFAULT FALSE; + +UPDATE accounts set lower_case_email = LOWER(accounts.email); +UPDATE accounts set ever_logged_in = TRUE; + +UPDATE schemaversion SET version = 9;