def test_generate_secure_hash(): salted_hash = generate_secure_hash(password='******') assert len(salted_hash) == len(( '{SSHA256}hddcAPBgxzuWWgs5UtzAjXdAjytpgeP129yCIQWWbel8WLWpj9fN4v/nFmecZd72MPtL4ckI+eYJ9qXfwW+q0ANMJi3qheHBtXkjx' 'jAkK6KxIo+ZhbkwAS3opq+xVltM' )) assert salted_hash.startswith('{SSHA256}') assert password_hash_is_valid(password='******', password_hash=salted_hash) is True assert password_hash_is_valid(password='******', password_hash=salted_hash) is False # Make sure every salted hash is unique assert generate_secure_hash(password='******') != salted_hash
def create_password_reset_token(db: DatabaseHandler, email: str) -> Optional[str]: """Generate password reset token used for both activating newly registered users and resetting passwords. Returns non-hashed password reset token or None if user was not found. """ email = decode_object_from_bytes_if_needed(email) if not email: raise McAuthProfileException('Email address is empty.') # Check if the email address exists in the user table; if not, pretend that we sent the activation link with a # "success" message. That way the adversary would not be able to find out which email addresses are active users. # # (Possible improvement: make the script work for the exact same amount of time in both cases to avoid timing # attacks) user_exists = db.query( """ SELECT auth_users_id, email FROM auth_users WHERE email = %(email)s LIMIT 1 """, { 'email': email }).hash() if user_exists is None or len(user_exists) == 0: # User was not found, so set the email address to an empty string, but don't return just now and continue with a # rather slowish process of generating a activation token (in order to reduce the risk of timing attacks) email = '' # Generate the activation token password_reset_token = random_string(length=64) if len(password_reset_token) == 0: raise McAuthProfileException('Unable to generate an activation token.') # Hash + validate the activation token password_reset_token_hash = generate_secure_hash( password=password_reset_token) if not password_reset_token_hash: raise McAuthProfileException("Unable to hash an activation token.") # Set the activation token hash in the database (if the email address doesn't exist, this query will do nothing) db.query( """ UPDATE auth_users SET password_reset_token_hash = %(password_reset_token_hash)s WHERE email = %(email)s AND email != '' """, { 'email': email, 'password_reset_token_hash': password_reset_token_hash, }) return password_reset_token
def add_user(db: DatabaseHandler, new_user: NewUser) -> None: """Add new user.""" if not new_user: raise McAuthRegisterException("New user is undefined.") # Check if user already exists user_exists = db.query( """ SELECT auth_users_id FROM auth_users WHERE email = %(email)s LIMIT 1 """, { 'email': new_user.email() }).hash() if user_exists is not None and 'auth_users_id' in user_exists: raise McAuthRegisterException("User with email '%s' already exists." % new_user.email()) # Hash + validate the password try: password_hash = generate_secure_hash(password=new_user.password()) if not password_hash: raise McAuthRegisterException("Password hash is empty.") except Exception as ex: log.error("Unable to hash a new password: {}".format(ex)) raise McAuthRegisterException('Unable to hash a new password.') db.begin() # Create the user db.create(table='auth_users', insert_hash={ 'email': new_user.email(), 'password_hash': password_hash, 'full_name': new_user.full_name(), 'notes': new_user.notes(), 'active': bool(int(new_user.active())), }) # Fetch the user's ID try: user = user_info(db=db, email=new_user.email()) except Exception as ex: db.rollback() raise McAuthRegisterException( "I've attempted to create the user but it doesn't exist: %s" % str(ex)) # Create roles try: for auth_roles_id in new_user.role_ids(): db.create(table='auth_users_roles_map', insert_hash={ 'auth_users_id': user.user_id(), 'auth_roles_id': auth_roles_id, }) except Exception as ex: raise McAuthRegisterException("Unable to create roles: %s" % str(ex)) # Update limits (if they're defined) if new_user.weekly_requests_limit() is not None: db.query( """ UPDATE auth_user_limits SET weekly_requests_limit = %(weekly_requests_limit)s WHERE auth_users_id = %(auth_users_id)s """, { 'auth_users_id': user.user_id(), 'weekly_requests_limit': new_user.weekly_requests_limit(), }) if new_user.weekly_requested_items_limit() is not None: db.query( """ UPDATE auth_user_limits SET weekly_requested_items_limit = %(weekly_requested_items_limit)s WHERE auth_users_id = %(auth_users_id)s """, { 'auth_users_id': user.user_id(), 'weekly_requested_items_limit': new_user.weekly_requested_items_limit(), }) # Subscribe to newsletter if new_user.subscribe_to_newsletter(): db.create(table='auth_users_subscribe_to_newsletter', insert_hash={'auth_users_id': user.user_id()}) if not new_user.active(): send_user_activation_token( db=db, email=new_user.email(), activation_link=new_user.activation_url(), subscribe_to_newsletter=new_user.subscribe_to_newsletter(), ) db.commit()
def change_password(db: DatabaseHandler, email: str, new_password: str, new_password_repeat: str, do_not_inform_via_email: bool = False) -> None: """Change user's password.""" email = decode_object_from_bytes_if_needed(email) new_password = decode_object_from_bytes_if_needed(new_password) new_password_repeat = decode_object_from_bytes_if_needed( new_password_repeat) if isinstance(do_not_inform_via_email, bytes): do_not_inform_via_email = decode_object_from_bytes_if_needed( do_not_inform_via_email) do_not_inform_via_email = bool(int(do_not_inform_via_email)) # Check if user exists try: user = user_info(db=db, email=email) except Exception: raise McAuthChangePasswordException( 'User with email address "%s" does not exist.' % email) password_validation_message = validate_new_password( email=email, password=new_password, password_repeat=new_password_repeat) if password_validation_message: raise McAuthChangePasswordException("Unable to change password: %s" % password_validation_message) # Hash + validate the password try: password_new_hash = generate_secure_hash(password=new_password) except Exception as ex: raise McAuthChangePasswordException( "Unable to hash a new password: %s" % str(ex)) if not password_new_hash: raise McAuthChangePasswordException( "Generated password hash is empty.") # Set the password hash db.query( """ UPDATE auth_users SET password_hash = %(password_hash)s, active = TRUE WHERE email = %(email)s """, { 'email': email, 'password_hash': password_new_hash, }) if not do_not_inform_via_email: message = AuthPasswordChangedMessage(to=email, full_name=user.full_name()) if not send_email(message): raise McAuthChangePasswordException( 'The password has been changed, but I was unable to send an email notifying you about the change.' )