import bcrypt
import hashlib

from pymongo import ReturnDocument

PROJECTION = [
    "_id",
    "groups",
    "force_reset",
    "last_password_change",
    "permissions",
    "primary_group"
]


async def user_exists(db, user_id):
    return await db.users.find({"_id": user_id}).count() == 1


async def set_primary_group(db, user_id, group_id):
    """
    Set the primary group for a given user.

    """
    if not await user_exists(db, user_id):
        raise UserNotFoundError

    if group_id not in await db.groups.distinct("_id"):
        raise GroupNotFoundError

    document = await db.users.find_one_and_update({"_id": user_id}, {
        "$set": {
            "primary_group": group_id
        }
    }, return_document=ReturnDocument.AFTER)

    return document


async def validate_credentials(db, user_id, password):
    """
    Returns ``True`` if the username exists and the password is correct. Returns ``False`` if the username does not
    exist or the or the password is incorrect.

    :param db: a database client
    :type db: :class:`~motor.motor_asyncio.AsyncIOMotorClient`

    :param user_id: the username to check.
    :type user_id: str

    :param password: the password to check.
    :type password: str

    :return: validation success
    :rtype: bool

    """
    document = await db.users.find_one({"_id": user_id}, ["password", "salt"])

    # First, check if the user exists in the database. Return False if the user does not exist.
    if not document:
        return False

    # Return True if the attempted password matches the stored password.
    try:
        if check_password(password, document["password"]):
            return True
    except TypeError:
        pass

    if "salt" in document and check_legacy_password(password, document["salt"], document["password"]):
        return True

    return False


def hash_password(password):
    """
    Salt and hash a unicode password. Uses bcrypt.

    :param password: a password string to salt and hash
    :type password: str

    :return: a salt and hashed password
    :rtype: tuple

    """
    return bcrypt.hashpw(password.encode(), bcrypt.gensalt(12))


def check_password(password, hashed):
    """
    Check if a unicode ``password`` matches a ``hashed_password``.

    :param password: the password to check.
    :type password: str

    :param hashed: the salted and hashed password from the database
    :type hashed: str

    :return: success of test
    :rtype: bool

    """
    return bcrypt.checkpw(password.encode(), hashed)


def check_legacy_password(password, salt, hashed):
    """
    Check if a unicode ``password`` and ``salt`` match a ``hashed`` password from the database. This is for use only
    with legacy SHA512 hashed passwords. New password hash with :func:`.hash_password` will be hashed using bcrypt.
    
    :param password: the password to check
    :type password: str
    
    :param salt: a salt
    :type salt: str

    :param hashed: the hashed password from the database
    :type hashed: str

    :return: success of test
    :rtype: bool
     
    """
    return hashed == hashlib.sha512(salt.encode("utf-8") + password.encode("utf-8")).hexdigest()


class UserNotFoundError(Exception):
    pass


class GroupNotFoundError(Exception):
    pass

@@ -27,44 +27,8 @@
 ib%0A%0A
-from pymongo import ReturnDocument%0A%0A
 PROJ
@@ -197,548 +197,380 @@
     
-return await db.users.find(%7B%22_id%22: user_id%7D).count() == 1%0A%0A%0Aasync def set_primary_group(db, user_id, group_id):%0A    %22%22%22%0A    Set the primary group for a given user.%0A%0A    %22%22%22%0A    if not await user_exists(db,
+%22%22%22%0A    Check if the user with the passed %60%60user_id%60%60 exists in the database.%0A%0A    :param db: a application database client%0A    :type db: :class:%60~motor.motor_asyncio.AsyncIOMotorClient%60%0A%0A    :param user_id: the
  user
-_id):%0A        raise UserNotFoundError%0A%0A    if group_id not in await db.groups.distinct(%22_id%22):
+ id to check for%0A    :type user_id: str%0A%0A    :return: %60%60bool%60%60 indicating if the user exists%0A
 %0A    
+%22%22%22%0A
     r
-aise GroupNotFoundError%0A%0A    document = await db.users.find_one_and_update(%7B%22_id%22: user_id%7D, %7B%0A        %22$set%22: %7B%0A            %22primary_group%22: group_id%0A        %7D%0A    %7D, return_document=ReturnDocument.AFTER)%0A%0A    return document
+eturn await db.users.count(%7B%22_id%22: user_id%7D) == 1
 %0A%0A%0Aa
@@ -2991,99 +2991,4 @@
 t()%0A
-%0A%0Aclass UserNotFoundError(Exception):%0A    pass%0A%0A%0Aclass GroupNotFoundError(Exception):%0A    pass%0A

Update user.py