Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BeanieBaseUserDocument unique email index not being applied, multiple users with the same email in the database #14

Open
mthanded opened this issue Jan 5, 2024 · 1 comment · May be fixed by #15
Labels
bug Something isn't working

Comments

@mthanded
Copy link

mthanded commented Jan 5, 2024

Describe the bug

BeanieBaseUserDocument defines 2 indexes on the email field , one being a unique index and the other being a collation. However only the collation index is applied to the email field in the database. This means that multiple users with the same emaill can end up in the database.

To Reproduce

To make reproducing this simpler i have lifted the BeanieBaseUserDocument model directly from this repo and put it into a minimal example.

Model from

class BeanieBaseUser(BaseModel):

import asyncio
from typing import Optional

from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseModel

from beanie import Document, Indexed, init_beanie
from pymongo import IndexModel
from pymongo.collation import Collation



class BeanieBaseUser(BaseModel):
    email: str
    hashed_password: str
    is_active: bool = True
    is_superuser: bool = False
    is_verified: bool = False

    class Settings:
        email_collation = Collation("en", strength=2)
        indexes = [
            IndexModel("email", unique=True),
            IndexModel(
                "email", name="case_insensitive_email_index", collation=email_collation
            ),
        ]


class BeanieBaseUserDocument(BeanieBaseUser, Document):  # type: ignore
    pass

async def example():
    client = AsyncIOMotorClient("mongodb://localhost:27017")
    await init_beanie(database=client.db_name, document_models=[BeanieBaseUserDocument])

    await BeanieBaseUserDocument(email="[email protected]",hashed_password="hashed_password").save()
    await BeanieBaseUserDocument(email="[email protected]",hashed_password="hashed_password1").save()

if __name__ == "__main__":
    asyncio.run(example())

The behavior is not as expected:

  • Instead of mongo complaining about the same email it allows multiple emails to be the same:
Screenshot 2024-01-04 at 9 34 52 PM
  • There is no unique index on the email field in the mongodb index list:
Screenshot 2024-01-04 at 9 23 09 PM

Expected behavior

The database should prevent duplicate emails from being added via the indexes.

Additional context

  • If you reverse the index definition order then the unique index works but the collation does not. Implying the second index takes precedence.
  • This can be fixed by putting the index definitions into a single definition like below:
class BeanieBaseUser(BaseModel):
    email: str
    hashed_password: str
    is_active: bool = True
    is_superuser: bool = False
    is_verified: bool = False

    class Settings:
        email_collation = Collation("en", strength=2)
        indexes = [
            IndexModel(
                "email", name="case_insensitive_email_index_unique", collation=email_collation, unique=True
            ),
        ]

Software versions

  • beanie 1.24.0
@mthanded mthanded added the bug Something isn't working label Jan 5, 2024
@frankie567
Copy link
Member

frankie567 commented Jan 9, 2024

Thank you @mthanded for the very detailed error report. I'm surprised both indices are not created properly 🤔

Anyway, as you suggest, it makes more sense to directly have the collation index unique since it's the behavior we adopt on Python's side.

PR welcome to solve this :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
2 participants