Skip to content

Add email normalization utility and integrate into login flow#5694

Open
razzeee wants to merge 4 commits intoflathub-infra:mainfrom
razzeee:merge-accounts
Open

Add email normalization utility and integrate into login flow#5694
razzeee wants to merge 4 commits intoflathub-infra:mainfrom
razzeee:merge-accounts

Conversation

@razzeee
Copy link
Copy Markdown
Member

@razzeee razzeee commented Sep 28, 2025

  • Implemented normalize_email function to standardize email format by removing aliases and converting to lowercase.
  • Updated login flow to utilize normalized email for account merging.
  • Added tests for email normalization functionality.
  • Created tests for login methods and user info retrieval.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds email-based account merging functionality to allow users with multiple OAuth provider accounts to have their accounts automatically merged when they use the same normalized email address. The normalization strips + aliases and lowercases the email.

  • Introduces normalize_email() utility function for consistent email normalization
  • Adds FlathubUser.by_normalized_email() method to find existing users by normalized email across all OAuth providers
  • Updates OAuth login flow to merge accounts when normalized emails match
  • Adds comprehensive test coverage for email normalization and basic login endpoints

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
backend/app/email_utils.py New utility module with email normalization function
backend/tests/test_email_utils.py Test coverage for email normalization edge cases
backend/app/models.py Adds database query method to find users by normalized email across account types
backend/app/logins.py Integrates email-based merging into OAuth flow logic
backend/tests/test_logins.py Basic smoke tests for login endpoints

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

user = login.user
if user is not None:
# Eventually we might do user-merge here?
if user is not None and (merged_user is None or user.id != merged_user.id):
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

This condition allows login when user is logged in and merged_user exists with the same ID, but doesn't handle the case where the account's existing user_entity differs from merged_user. If an account already exists and is linked to a different user, but the normalized email matches yet another user, this could create inconsistent state. Should check if account.user_entity.id != merged_user.id before allowing the merge.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

razzeee and others added 3 commits November 20, 2025 02:10
- Implemented `normalize_email` function to standardize email format by removing aliases and converting to lowercase.
- Updated login flow to utilize normalized email for account merging.
- Added tests for email normalization functionality.
- Created tests for login methods and user info retrieval.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +8 to +12
"""
if not email:
return email
if "@" not in email:
return email
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

The early return when '@' is not present returns the original email unchanged. This could silently allow malformed emails to pass through. Consider either raising an exception for invalid email formats or documenting this behavior explicitly in the docstring, as it may lead to unexpected account merging issues.

Suggested change
"""
if not email:
return email
if "@" not in email:
return email
Raises:
ValueError: If the email does not contain an '@' character.
"""
if not email:
return email
if "@" not in email:
raise ValueError(f"Invalid email format: '{email}' (missing '@')")

Copilot uses AI. Check for mistakes.
Comment on lines +829 to +834
normalized_email = (
normalize_email(provider_data.email) if provider_data.email else None
)
merged_user = None
if normalized_email:
merged_user = models.FlathubUser.by_normalized_email(db, normalized_email)
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

Email normalization is being used for automatic account merging, which could lead to a security issue. If a user controls user+alias@example.com and another user has user@example.com, this normalization could allow the first user to gain access to the second user's account. Email alias normalization should only be applied within the same email provider/domain and with user consent, not automatically for account merging.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback



def test_deleteuser_not_logged_in():
response = client.get("/auth/deleteuser")
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

The HTTP method used here should be DELETE instead of GET for a delete operation. Using GET for a destructive operation violates REST API best practices and could lead to security issues (e.g., accidental deletion via link prefetching).

Suggested change
response = client.get("/auth/deleteuser")
response = client.delete("/auth/deleteuser")

Copilot uses AI. Check for mistakes.
Comment on lines +444 to +460
norm_expr = func.lower(
func.concat(
func.substring(
acc_cls.email,
1,
func.nullif(func.position(literal("+"), acc_cls.email) - 1, -1),
)
if func.position(literal("+"), acc_cls.email) > 0
else func.substring(
acc_cls.email, 1, func.position(literal("@"), acc_cls.email) - 1
),
literal("@"),
func.substring(
acc_cls.email, func.position(literal("@"), acc_cls.email) + 1
),
)
)
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

This SQL-based email normalization duplicates the logic in normalize_email() from email_utils.py, making the code harder to maintain. If the normalization logic changes, both places need to be updated. Consider using a database-side generated column or creating a normalized_email field that's populated using the Python function to ensure consistency.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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