diff --git a/src/auth/auth.cpp b/src/auth/auth.cpp
index 01a763fb8..15f50da29 100644
--- a/src/auth/auth.cpp
+++ b/src/auth/auth.cpp
@@ -1,6 +1,6 @@
 #include "auth/auth.hpp"
 
-#include "utils/string.hpp"
+#include "auth/exceptions.hpp"
 
 namespace auth {
 
@@ -44,7 +44,7 @@ std::experimental::optional<User> Auth::GetUser(const std::string &username) {
   try {
     data = nlohmann::json::parse(*existing_user);
   } catch (const nlohmann::json::parse_error &e) {
-    throw utils::BasicException("Couldn't load user data!");
+    throw AuthException("Couldn't load user data!");
   }
 
   auto user = User::Deserialize(data);
@@ -59,38 +59,59 @@ std::experimental::optional<User> Auth::GetUser(const std::string &username) {
   return user;
 }
 
-bool Auth::SaveUser(const User &user) {
-  if (!storage_.Put(kUserPrefix + user.username(), user.Serialize().dump())) {
-    return false;
-  }
+void Auth::SaveUser(const User &user) {
+  bool success = false;
   if (user.role()) {
-    return storage_.Put(kLinkPrefix + user.username(), user.role()->rolename());
+    success = storage_.PutMultiple(
+        {{kUserPrefix + user.username(), user.Serialize().dump()},
+         {kLinkPrefix + user.username(), user.role()->rolename()}});
   } else {
-    return storage_.Delete(kLinkPrefix + user.username());
+    success = storage_.PutAndDeleteMultiple(
+        {{kUserPrefix + user.username(), user.Serialize().dump()}},
+        {kLinkPrefix + user.username()});
+  }
+  if (!success) {
+    throw AuthException("Couldn't save user '{}'!", user.username());
   }
 }
 
-std::experimental::optional<User> Auth::AddUser(const std::string &username) {
+std::experimental::optional<User> Auth::AddUser(
+    const std::string &username,
+    const std::experimental::optional<std::string> &password) {
   auto existing_user = GetUser(username);
   if (existing_user) return std::experimental::nullopt;
+  auto existing_role = GetRole(username);
+  if (existing_role) return std::experimental::nullopt;
   auto new_user = User(username);
-  if (!SaveUser(new_user)) return std::experimental::nullopt;
+  new_user.UpdatePassword(password);
+  SaveUser(new_user);
   return new_user;
 }
 
 bool Auth::RemoveUser(const std::string &username) {
   if (!storage_.Get(kUserPrefix + username)) return false;
-  if (!storage_.Delete(kLinkPrefix + username)) return false;
-  return storage_.Delete(kUserPrefix + username);
+  std::vector<std::string> keys(
+      {kLinkPrefix + username, kUserPrefix + username});
+  if (!storage_.DeleteMultiple(keys)) {
+    throw AuthException("Couldn't remove user '{}'!", username);
+  }
+  return true;
+}
+
+std::vector<auth::User> Auth::AllUsers() {
+  std::vector<auth::User> ret;
+  for (auto it = storage_.begin(kUserPrefix); it != storage_.end(kUserPrefix);
+       ++it) {
+    auto user = GetUser(it->first.substr(kUserPrefix.size()));
+    if (user) {
+      ret.push_back(*user);
+    }
+  }
+  return ret;
 }
 
 bool Auth::HasUsers() {
-  for (auto it = storage_.begin(); it != storage_.end(); ++it) {
-    if (utils::StartsWith(it->first, kUserPrefix)) {
-      return true;
-    }
-  }
-  return false;
+  return storage_.begin(kUserPrefix) != storage_.end(kUserPrefix);
 }
 
 std::experimental::optional<Role> Auth::GetRole(const std::string &rolename) {
@@ -101,36 +122,74 @@ std::experimental::optional<Role> Auth::GetRole(const std::string &rolename) {
   try {
     data = nlohmann::json::parse(*existing_role);
   } catch (const nlohmann::json::parse_error &e) {
-    throw utils::BasicException("Couldn't load role data!");
+    throw AuthException("Couldn't load role data!");
   }
 
   return Role::Deserialize(data);
 }
 
-bool Auth::SaveRole(const Role &role) {
-  return storage_.Put(kRolePrefix + role.rolename(), role.Serialize().dump());
+void Auth::SaveRole(const Role &role) {
+  if (!storage_.Put(kRolePrefix + role.rolename(), role.Serialize().dump())) {
+    throw AuthException("Couldn't save role '{}'!", role.rolename());
+  }
 }
 
 std::experimental::optional<Role> Auth::AddRole(const std::string &rolename) {
   auto existing_role = GetRole(rolename);
   if (existing_role) return std::experimental::nullopt;
+  auto existing_user = GetUser(rolename);
+  if (existing_user) return std::experimental::nullopt;
   auto new_role = Role(rolename);
-  if (!SaveRole(new_role)) return std::experimental::nullopt;
+  SaveRole(new_role);
   return new_role;
 }
 
 bool Auth::RemoveRole(const std::string &rolename) {
   if (!storage_.Get(kRolePrefix + rolename)) return false;
-  std::vector<std::string> links;
-  for (auto it = storage_.begin(); it != storage_.end(); ++it) {
-    if (utils::StartsWith(it->first, kLinkPrefix) && it->second == rolename) {
-      links.push_back(it->first);
+  std::vector<std::string> keys;
+  for (auto it = storage_.begin(kLinkPrefix); it != storage_.end(kLinkPrefix);
+       ++it) {
+    if (it->second == rolename) {
+      keys.push_back(it->first);
     }
   }
-  for (const auto &link : links) {
-    storage_.Delete(link);
+  keys.push_back(kRolePrefix + rolename);
+  if (!storage_.DeleteMultiple(keys)) {
+    throw AuthException("Couldn't remove role '{}'!", rolename);
   }
-  return storage_.Delete(kRolePrefix + rolename);
+  return true;
+}
+
+std::vector<auth::Role> Auth::AllRoles() {
+  std::vector<auth::Role> ret;
+  for (auto it = storage_.begin(kRolePrefix); it != storage_.end(kRolePrefix);
+       ++it) {
+    auto rolename = it->first.substr(kRolePrefix.size());
+    auto role = GetRole(rolename);
+    if (role) {
+      ret.push_back(*role);
+    } else {
+      throw AuthException("Couldn't load role '{}'!", rolename);
+    }
+  }
+  return ret;
+}
+
+std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename) {
+  std::vector<auth::User> ret;
+  for (auto it = storage_.begin(kLinkPrefix); it != storage_.end(kLinkPrefix);
+       ++it) {
+    auto username = it->first.substr(kLinkPrefix.size());
+    if (it->second == rolename) {
+      auto user = GetUser(username);
+      if (user) {
+        ret.push_back(*user);
+      } else {
+        throw AuthException("Couldn't load user '{}'!", username);
+      }
+    }
+  }
+  return ret;
 }
 
 std::mutex &Auth::WithLock() { return lock_; }
diff --git a/src/auth/auth.hpp b/src/auth/auth.hpp
index 9590cfdd4..55060f66a 100644
--- a/src/auth/auth.hpp
+++ b/src/auth/auth.hpp
@@ -2,7 +2,9 @@
 
 #include <experimental/optional>
 #include <mutex>
+#include <vector>
 
+#include "auth/exceptions.hpp"
 #include "auth/models.hpp"
 #include "storage/kvstore.hpp"
 
@@ -18,27 +20,125 @@ class Auth final {
  public:
   Auth(const std::string &storage_directory);
 
+  /**
+   * Authenticates a user using his username and password.
+   *
+   * @param username
+   * @param password
+   *
+   * @return a user when the username and password match, nullopt otherwise
+   */
   std::experimental::optional<User> Authenticate(const std::string &username,
                                                  const std::string &password);
 
+  /**
+   * Gets a user from the storage.
+   *
+   * @param username
+   *
+   * @return a user when the user exists, nullopt otherwise
+   */
   std::experimental::optional<User> GetUser(const std::string &username);
 
-  bool SaveUser(const User &user);
+  /**
+   * Saves a user object to the storage.
+   *
+   * @param user
+   */
+  void SaveUser(const User &user);
 
-  std::experimental::optional<User> AddUser(const std::string &username);
+  /**
+   * Creates a user if the user doesn't exist.
+   *
+   * @param username
+   * @param password
+   *
+   * @return a user when the user is created, nullopt if the user exists
+   */
+  std::experimental::optional<User> AddUser(
+      const std::string &username,
+      const std::experimental::optional<std::string> &password =
+          std::experimental::nullopt);
 
+  /**
+   * Removes a user from the storage.
+   *
+   * @param username
+   *
+   * @return `true` if the user existed and was removed, `false` if the user
+   *         doesn't exist
+   */
   bool RemoveUser(const std::string &username);
 
+  /**
+   * Gets all users from the storage.
+   *
+   * @return a list of users
+   */
+  std::vector<User> AllUsers();
+
+  /**
+   * Returns whether there are users in the storage.
+   *
+   * @return `true` if the storage contains any users, `false` otherwise
+   */
   bool HasUsers();
 
+  /**
+   * Gets a role from the storage.
+   *
+   * @param rolename
+   *
+   * @return a role when the role exists, nullopt otherwise
+   */
   std::experimental::optional<Role> GetRole(const std::string &rolename);
 
-  bool SaveRole(const Role &role);
+  /**
+   * Saves a role object to the storage.
+   *
+   * @param role
+   */
+  void SaveRole(const Role &role);
 
+  /**
+   * Creates a role if the role doesn't exist.
+   *
+   * @param rolename
+   *
+   * @return a role when the role is created, nullopt if the role exists
+   */
   std::experimental::optional<Role> AddRole(const std::string &rolename);
 
+  /**
+   * Removes a role from the storage.
+   *
+   * @param rolename
+   *
+   * @return `true` if the role existed and was removed, `false` if the role
+   *         doesn't exist
+   */
   bool RemoveRole(const std::string &rolename);
 
+  /**
+   * Gets all roles from the storage.
+   *
+   * @return a list of roles
+   */
+  std::vector<Role> AllRoles();
+
+  /**
+   * Gets all users for a role from the storage.
+   *
+   * @param rolename
+   *
+   * @return a list of roles
+   */
+  std::vector<User> AllUsersForRole(const std::string &rolename);
+
+  /**
+   * Returns a reference to the lock that should be used for all operations that
+   * require more than one interaction with this class.
+   */
   std::mutex &WithLock();
 
  private:
diff --git a/src/auth/crypto.cpp b/src/auth/crypto.cpp
index 7dabe09b7..1362ec2f4 100644
--- a/src/auth/crypto.cpp
+++ b/src/auth/crypto.cpp
@@ -2,7 +2,7 @@
 
 #include <libbcrypt/bcrypt.h>
 
-#include "utils/exceptions.hpp"
+#include "auth/exceptions.hpp"
 
 namespace auth {
 
@@ -14,11 +14,11 @@ const std::string EncryptPassword(const std::string &password) {
   // its default value of `12`. Increasing the workfactor increases the time
   // needed to generate the salt.
   if (bcrypt_gensalt(-1, salt) != 0) {
-    throw utils::BasicException("Couldn't generate hashing salt!");
+    throw AuthException("Couldn't generate hashing salt!");
   }
 
   if (bcrypt_hashpw(password.c_str(), salt, hash) != 0) {
-    throw utils::BasicException("Couldn't hash password!");
+    throw AuthException("Couldn't hash password!");
   }
 
   return std::string(hash);
@@ -27,7 +27,7 @@ const std::string EncryptPassword(const std::string &password) {
 bool VerifyPassword(const std::string &password, const std::string &hash) {
   int ret = bcrypt_checkpw(password.c_str(), hash.c_str());
   if (ret == -1) {
-    throw utils::BasicException("Couldn't check password!");
+    throw AuthException("Couldn't check password!");
   }
   return ret == 0;
 }
diff --git a/src/auth/exceptions.hpp b/src/auth/exceptions.hpp
new file mode 100644
index 000000000..964764233
--- /dev/null
+++ b/src/auth/exceptions.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "utils/exceptions.hpp"
+
+namespace auth {
+
+/**
+ * This exception class is thrown for all exceptions that can occur when dealing
+ * with the Auth library.
+ */
+class AuthException : public utils::BasicException {
+ public:
+  using utils::BasicException::BasicException;
+};
+}  // namespace auth
diff --git a/src/auth/models.cpp b/src/auth/models.cpp
index 6a4555918..20c3c1140 100644
--- a/src/auth/models.cpp
+++ b/src/auth/models.cpp
@@ -1,11 +1,45 @@
 #include "auth/models.hpp"
 
+#include <regex>
+
+#include <gflags/gflags.h>
+
 #include "auth/crypto.hpp"
+#include "auth/exceptions.hpp"
 #include "utils/cast.hpp"
-#include "utils/exceptions.hpp"
+
+DEFINE_bool(auth_password_permit_null, true,
+            "Set to false to disable null passwords.");
+
+DEFINE_string(auth_password_strength_regex, ".+",
+              "The regular expression that should be used to match the entire "
+              "entered password to ensure its strength.");
 
 namespace auth {
 
+std::string PermissionToString(Permission permission) {
+  switch (permission) {
+    case Permission::MATCH:
+      return "MATCH";
+    case Permission::CREATE:
+      return "CREATE";
+    case Permission::MERGE:
+      return "MERGE";
+    case Permission::DELETE:
+      return "DELETE";
+    case Permission::SET:
+      return "SET";
+    case Permission::REMOVE:
+      return "REMOVE";
+    case Permission::INDEX:
+      return "INDEX";
+    case Permission::AUTH:
+      return "AUTH";
+    case Permission::STREAM:
+      return "STREAM";
+  }
+}
+
 Permissions::Permissions(uint64_t grants, uint64_t denies) {
   // The deny bitmask has higher priority than the grant bitmask.
   denies_ = denies;
@@ -16,11 +50,11 @@ Permissions::Permissions(uint64_t grants, uint64_t denies) {
 PermissionLevel Permissions::Has(Permission permission) const {
   // Check for the deny first because it has greater priority than a grant.
   if (denies_ & utils::UnderlyingCast(permission)) {
-    return PermissionLevel::Deny;
+    return PermissionLevel::DENY;
   } else if (grants_ & utils::UnderlyingCast(permission)) {
-    return PermissionLevel::Grant;
+    return PermissionLevel::GRANT;
   }
-  return PermissionLevel::Neutral;
+  return PermissionLevel::NEUTRAL;
 }
 
 void Permissions::Grant(Permission permission) {
@@ -44,6 +78,26 @@ void Permissions::Deny(Permission permission) {
   grants_ &= ~utils::UnderlyingCast(permission);
 }
 
+std::vector<Permission> Permissions::GetGrants() const {
+  std::vector<Permission> ret;
+  for (const auto &permission : kPermissionsAll) {
+    if (Has(permission) == PermissionLevel::GRANT) {
+      ret.push_back(permission);
+    }
+  }
+  return ret;
+}
+
+std::vector<Permission> Permissions::GetDenies() const {
+  std::vector<Permission> ret;
+  for (const auto &permission : kPermissionsAll) {
+    if (Has(permission) == PermissionLevel::DENY) {
+      ret.push_back(permission);
+    }
+  }
+  return ret;
+}
+
 nlohmann::json Permissions::Serialize() const {
   nlohmann::json data = nlohmann::json::object();
   data["grants"] = grants_;
@@ -53,11 +107,11 @@ nlohmann::json Permissions::Serialize() const {
 
 Permissions Permissions::Deserialize(const nlohmann::json &data) {
   if (!data.is_object()) {
-    throw utils::BasicException("Couldn't load permissions data!");
+    throw AuthException("Couldn't load permissions data!");
   }
   if (!data["grants"].is_number_unsigned() ||
       !data["denies"].is_number_unsigned()) {
-    throw utils::BasicException("Couldn't load permissions data!");
+    throw AuthException("Couldn't load permissions data!");
   }
   return {data["grants"], data["denies"]};
 }
@@ -91,10 +145,10 @@ nlohmann::json Role::Serialize() const {
 
 Role Role::Deserialize(const nlohmann::json &data) {
   if (!data.is_object()) {
-    throw utils::BasicException("Couldn't load role data!");
+    throw AuthException("Couldn't load role data!");
   }
   if (!data["rolename"].is_string() || !data["permissions"].is_object()) {
-    throw utils::BasicException("Couldn't load role data!");
+    throw AuthException("Couldn't load role data!");
   }
   auto permissions = Permissions::Deserialize(data["permissions"]);
   return {data["rolename"], permissions};
@@ -114,15 +168,33 @@ User::User(const std::string &username, const std::string &password_hash,
       permissions_(permissions) {}
 
 bool User::CheckPassword(const std::string &password) {
+  if (password_hash_ == "") return true;
   return VerifyPassword(password, password_hash_);
 }
 
-void User::UpdatePassword(const std::string &password) {
-  password_hash_ = EncryptPassword(password);
+void User::UpdatePassword(
+    const std::experimental::optional<std::string> &password) {
+  if (password) {
+    std::regex re(FLAGS_auth_password_strength_regex);
+    if (!std::regex_match(*password, re)) {
+      throw AuthException(
+          "The user password doesn't conform to the required strength! Regex: "
+          "{}",
+          FLAGS_auth_password_strength_regex);
+    }
+    password_hash_ = EncryptPassword(*password);
+  } else {
+    if (!FLAGS_auth_password_permit_null) {
+      throw AuthException("Null passwords aren't permitted!");
+    }
+    password_hash_ = "";
+  }
 }
 
 void User::SetRole(const Role &role) { role_.emplace(role); }
 
+void User::ClearRole() { role_ = std::experimental::nullopt; }
+
 const Permissions User::GetPermissions() const {
   if (role_) {
     return Permissions(permissions_.grants() | role_->permissions().grants(),
@@ -148,11 +220,11 @@ nlohmann::json User::Serialize() const {
 
 User User::Deserialize(const nlohmann::json &data) {
   if (!data.is_object()) {
-    throw utils::BasicException("Couldn't load user data!");
+    throw AuthException("Couldn't load user data!");
   }
   if (!data["username"].is_string() || !data["password_hash"].is_string() ||
       !data["permissions"].is_object()) {
-    throw utils::BasicException("Couldn't load user data!");
+    throw AuthException("Couldn't load user data!");
   }
   auto permissions = Permissions::Deserialize(data["permissions"]);
   return {data["username"], data["password_hash"], permissions};
diff --git a/src/auth/models.hpp b/src/auth/models.hpp
index fc80021dd..8c9cd25c1 100644
--- a/src/auth/models.hpp
+++ b/src/auth/models.hpp
@@ -7,25 +7,36 @@
 
 namespace auth {
 
-// TODO (mferencevic): Add permissions for admin actions.
+// These permissions must have values that are applicable for usage in a
+// bitmask.
 enum class Permission : uint64_t {
-  Read = 0x00000001,
-
-  Create = 0x00000002,
-
-  Update = 0x00000004,
-
-  Delete = 0x00000008,
+  MATCH = 0x00000001,
+  CREATE = 0x00000002,
+  MERGE = 0x00000004,
+  DELETE = 0x00000008,
+  SET = 0x00000010,
+  REMOVE = 0x00000020,
+  INDEX = 0x00000040,
+  AUTH = 0x00010000,
+  STREAM = 0x00020000,
 };
 
+// Constant list of all available permissions.
+const std::vector<Permission> kPermissionsAll = {
+    Permission::MATCH,  Permission::CREATE, Permission::MERGE,
+    Permission::DELETE, Permission::SET,    Permission::REMOVE,
+    Permission::INDEX,  Permission::AUTH,   Permission::STREAM};
+
+// Function that converts a permission to its string representation.
+std::string PermissionToString(Permission permission);
+
+// Class that indicates what permission level the user/role has.
 enum class PermissionLevel {
-  Grant,
-  Neutral,
-  Deny,
+  GRANT,
+  NEUTRAL,
+  DENY,
 };
 
-// TODO (mferencevic): Add string conversions to/from permissions.
-
 class Permissions final {
  public:
   Permissions(uint64_t grants = 0, uint64_t denies = 0);
@@ -38,6 +49,10 @@ class Permissions final {
 
   void Deny(Permission permission);
 
+  std::vector<Permission> GetGrants() const;
+
+  std::vector<Permission> GetDenies() const;
+
   nlohmann::json Serialize() const;
 
   static Permissions Deserialize(const nlohmann::json &data);
@@ -77,7 +92,6 @@ class Role final {
 
 bool operator==(const Role &first, const Role &second);
 
-// TODO (mferencevic): Implement password strength enforcement.
 // TODO (mferencevic): Implement password expiry.
 class User final {
  public:
@@ -88,10 +102,13 @@ class User final {
 
   bool CheckPassword(const std::string &password);
 
-  void UpdatePassword(const std::string &password);
+  void UpdatePassword(const std::experimental::optional<std::string> &password =
+                          std::experimental::nullopt);
 
   void SetRole(const Role &role);
 
+  void ClearRole();
+
   const Permissions GetPermissions() const;
 
   const std::string &username() const;
diff --git a/src/query/frontend/ast/ast.capnp b/src/query/frontend/ast/ast.capnp
index 1c48259f1..2368fc9fb 100644
--- a/src/query/frontend/ast/ast.capnp
+++ b/src/query/frontend/ast/ast.capnp
@@ -421,8 +421,10 @@ struct AuthQuery {
     match @2;
     merge @3;
     set @4;
-    auth @5;
-    stream @6;
+    remove @5;
+    index @6;
+    auth @7;
+    stream @8;
   }
   action @0 :Action;
   user @1 :Text;
diff --git a/src/query/frontend/ast/ast.cpp b/src/query/frontend/ast/ast.cpp
index ce604be2d..f417bb9b4 100644
--- a/src/query/frontend/ast/ast.cpp
+++ b/src/query/frontend/ast/ast.cpp
@@ -2146,6 +2146,12 @@ void AuthQuery::Save(capnp::AuthQuery::Builder *builder,
       case Privilege::SET:
         privileges_builder.set(i, capnp::AuthQuery::Privilege::SET);
         break;
+      case Privilege::REMOVE:
+        privileges_builder.set(i, capnp::AuthQuery::Privilege::REMOVE);
+        break;
+      case Privilege::INDEX:
+        privileges_builder.set(i, capnp::AuthQuery::Privilege::INDEX);
+        break;
       case Privilege::AUTH:
         privileges_builder.set(i, capnp::AuthQuery::Privilege::AUTH);
         break;
@@ -2232,6 +2238,12 @@ void AuthQuery::Load(const capnp::Tree::Reader &base_reader,
       case capnp::AuthQuery::Privilege::SET:
         privileges_.push_back(Privilege::SET);
         break;
+      case capnp::AuthQuery::Privilege::REMOVE:
+        privileges_.push_back(Privilege::REMOVE);
+        break;
+      case capnp::AuthQuery::Privilege::INDEX:
+        privileges_.push_back(Privilege::INDEX);
+        break;
       case capnp::AuthQuery::Privilege::AUTH:
         privileges_.push_back(Privilege::AUTH);
         break;
diff --git a/src/query/frontend/ast/ast.hpp b/src/query/frontend/ast/ast.hpp
index e829def1e..96da6b840 100644
--- a/src/query/frontend/ast/ast.hpp
+++ b/src/query/frontend/ast/ast.hpp
@@ -2361,7 +2361,19 @@ class AuthQuery : public Clause {
     SHOW_USERS_FOR_ROLE
   };
 
-  enum class Privilege { CREATE, DELETE, MATCH, MERGE, SET, AUTH, STREAM };
+  // When adding new privileges, please add them to the `kPrivilegesAll`
+  // constant.
+  enum class Privilege {
+    CREATE,
+    DELETE,
+    MATCH,
+    MERGE,
+    SET,
+    REMOVE,
+    INDEX,
+    AUTH,
+    STREAM
+  };
 
   Action action_;
   std::string user_;
@@ -2405,6 +2417,14 @@ class AuthQuery : public Clause {
             std::vector<int> *loaded_uids) override;
 };
 
+// Constant that holds all available privileges.
+const std::vector<AuthQuery::Privilege> kPrivilegesAll = {
+    AuthQuery::Privilege::CREATE, AuthQuery::Privilege::DELETE,
+    AuthQuery::Privilege::MATCH,  AuthQuery::Privilege::MERGE,
+    AuthQuery::Privilege::SET,    AuthQuery::Privilege::REMOVE,
+    AuthQuery::Privilege::INDEX,  AuthQuery::Privilege::AUTH,
+    AuthQuery::Privilege::STREAM};
+
 class CreateStream : public Clause {
   friend class AstStorage;
 
diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp
index 86f3a1e57..62f6dd957 100644
--- a/src/query/frontend/ast/cypher_main_visitor.cpp
+++ b/src/query/frontend/ast/cypher_main_visitor.cpp
@@ -386,8 +386,13 @@ antlrcpp::Any CypherMainVisitor::visitGrantPrivilege(
   AuthQuery *auth = storage_.Create<AuthQuery>();
   auth->action_ = AuthQuery::Action::GRANT_PRIVILEGE;
   auth->user_or_role_ = ctx->userOrRole->accept(this).as<std::string>();
-  for (auto *privilege : ctx->privilegeList()->privilege()) {
-    auth->privileges_.push_back(privilege->accept(this));
+  if (ctx->privilegeList()) {
+    for (auto *privilege : ctx->privilegeList()->privilege()) {
+      auth->privileges_.push_back(privilege->accept(this));
+    }
+  } else {
+    /* grant all privileges */
+    auth->privileges_ = kPrivilegesAll;
   }
   return auth;
 }
@@ -400,8 +405,13 @@ antlrcpp::Any CypherMainVisitor::visitDenyPrivilege(
   AuthQuery *auth = storage_.Create<AuthQuery>();
   auth->action_ = AuthQuery::Action::DENY_PRIVILEGE;
   auth->user_or_role_ = ctx->userOrRole->accept(this).as<std::string>();
-  for (auto *privilege : ctx->privilegeList()->privilege()) {
-    auth->privileges_.push_back(privilege->accept(this));
+  if (ctx->privilegeList()) {
+    for (auto *privilege : ctx->privilegeList()->privilege()) {
+      auth->privileges_.push_back(privilege->accept(this));
+    }
+  } else {
+    /* deny all privileges */
+    auth->privileges_ = kPrivilegesAll;
   }
   return auth;
 }
@@ -420,11 +430,7 @@ antlrcpp::Any CypherMainVisitor::visitRevokePrivilege(
     }
   } else {
     /* revoke all privileges */
-    auth->privileges_ = {
-        AuthQuery::Privilege::CREATE, AuthQuery::Privilege::DELETE,
-        AuthQuery::Privilege::MATCH,  AuthQuery::Privilege::MERGE,
-        AuthQuery::Privilege::SET,    AuthQuery::Privilege::AUTH,
-        AuthQuery::Privilege::STREAM};
+    auth->privileges_ = kPrivilegesAll;
   }
   return auth;
 }
@@ -439,6 +445,8 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(
   if (ctx->MATCH()) return AuthQuery::Privilege::MATCH;
   if (ctx->MERGE()) return AuthQuery::Privilege::MERGE;
   if (ctx->SET()) return AuthQuery::Privilege::SET;
+  if (ctx->REMOVE()) return AuthQuery::Privilege::REMOVE;
+  if (ctx->INDEX()) return AuthQuery::Privilege::INDEX;
   if (ctx->AUTH()) return AuthQuery::Privilege::AUTH;
   if (ctx->STREAM()) return AuthQuery::Privilege::STREAM;
   LOG(FATAL) << "Should not get here - unknown privilege!";
diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4
index 56c533074..9c8f2dc7c 100644
--- a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4
+++ b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4
@@ -88,14 +88,14 @@ grantRole : GRANT ROLE role=userOrRoleName TO user=userOrRoleName ;
 
 revokeRole : REVOKE ROLE role=userOrRoleName FROM user=userOrRoleName ;
 
-grantPrivilege : GRANT privilegeList TO userOrRole=userOrRoleName ;
+grantPrivilege : GRANT ( ALL PRIVILEGES | privileges=privilegeList ) TO userOrRole=userOrRoleName ;
 
-denyPrivilege : DENY privilegeList TO userOrRole=userOrRoleName ;
+denyPrivilege : DENY ( ALL PRIVILEGES | privileges=privilegeList ) TO userOrRole=userOrRoleName ;
 
 revokePrivilege : REVOKE ( ALL PRIVILEGES | privileges=privilegeList ) FROM userOrRole=userOrRoleName ;
 
 privilege : CREATE | DELETE | MATCH | MERGE | SET
-          | AUTH | STREAM ;
+          | REMOVE | INDEX | AUTH | STREAM ;
 
 privilegeList : privilege ( ',' privilege )* ;
 
diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp
index ba26ca500..6f0a00a2d 100644
--- a/src/query/plan/operator.cpp
+++ b/src/query/plan/operator.cpp
@@ -3893,22 +3893,108 @@ std::unique_ptr<Cursor> PullRemoteOrderBy::MakeCursor(
 AuthHandler::AuthHandler(AuthQuery::Action action, std::string user,
                          std::string role, std::string user_or_role,
                          Expression *password,
-                         std::vector<AuthQuery::Privilege> privileges)
+                         std::vector<AuthQuery::Privilege> privileges,
+                         Symbol user_symbol, Symbol role_symbol,
+                         Symbol grants_symbol)
     : action_(action),
       user_(user),
       role_(role),
       user_or_role_(user_or_role),
       password_(password),
-      privileges_(privileges) {}
+      privileges_(privileges),
+      user_symbol_(user_symbol),
+      role_symbol_(role_symbol),
+      grants_symbol_(grants_symbol) {}
 
 bool AuthHandler::Accept(HierarchicalLogicalOperatorVisitor &visitor) {
   return visitor.Visit(*this);
 }
 
+std::vector<Symbol> AuthHandler::OutputSymbols(const SymbolTable &) const {
+  switch (action_) {
+    case AuthQuery::Action::SHOW_USERS:
+    case AuthQuery::Action::SHOW_USERS_FOR_ROLE:
+      return {user_symbol_};
+
+    case AuthQuery::Action::SHOW_ROLES:
+    case AuthQuery::Action::SHOW_ROLE_FOR_USER:
+      return {role_symbol_};
+
+    case AuthQuery::Action::SHOW_GRANTS:
+      return {grants_symbol_};
+
+    case AuthQuery::Action::CREATE_USER:
+    case AuthQuery::Action::DROP_USER:
+    case AuthQuery::Action::SET_PASSWORD:
+    case AuthQuery::Action::CREATE_ROLE:
+    case AuthQuery::Action::DROP_ROLE:
+    case AuthQuery::Action::GRANT_ROLE:
+    case AuthQuery::Action::REVOKE_ROLE:
+    case AuthQuery::Action::GRANT_PRIVILEGE:
+    case AuthQuery::Action::DENY_PRIVILEGE:
+    case AuthQuery::Action::REVOKE_PRIVILEGE:
+      return {};
+  }
+}
+
 class AuthHandlerCursor : public Cursor {
  public:
   AuthHandlerCursor(const AuthHandler &self) : self_(self) {}
 
+  std::vector<auth::Permission> GetAuthPermissions() {
+    std::vector<auth::Permission> ret;
+    for (const auto &privilege : self_.privileges()) {
+      switch (privilege) {
+        case AuthQuery::Privilege::MATCH:
+          ret.push_back(auth::Permission::MATCH);
+          break;
+        case AuthQuery::Privilege::CREATE:
+          ret.push_back(auth::Permission::CREATE);
+          break;
+        case AuthQuery::Privilege::MERGE:
+          ret.push_back(auth::Permission::MERGE);
+          break;
+        case AuthQuery::Privilege::DELETE:
+          ret.push_back(auth::Permission::DELETE);
+          break;
+        case AuthQuery::Privilege::SET:
+          ret.push_back(auth::Permission::SET);
+          break;
+        case AuthQuery::Privilege::REMOVE:
+          ret.push_back(auth::Permission::REMOVE);
+          break;
+        case AuthQuery::Privilege::INDEX:
+          ret.push_back(auth::Permission::INDEX);
+          break;
+        case AuthQuery::Privilege::AUTH:
+          ret.push_back(auth::Permission::AUTH);
+          break;
+        case AuthQuery::Privilege::STREAM:
+          ret.push_back(auth::Permission::STREAM);
+          break;
+      }
+    }
+    return ret;
+  }
+
+  std::vector<std::string> GetGrantsFromAuthPermissions(
+      auth::Permissions &permissions) {
+    std::vector<std::string> grants, denies, ret;
+    for (const auto &permission : permissions.GetGrants()) {
+      grants.push_back(auth::PermissionToString(permission));
+    }
+    for (const auto &permission : permissions.GetDenies()) {
+      denies.push_back(auth::PermissionToString(permission));
+    }
+    if (grants.size() > 0) {
+      ret.push_back(fmt::format("GRANT {}", utils::Join(grants, ", ")));
+    }
+    if (denies.size() > 0) {
+      ret.push_back(fmt::format("DENY {}", utils::Join(denies, ", ")));
+    }
+    return ret;
+  }
+
   bool Pull(Frame &frame, Context &ctx) override {
     if (ctx.in_explicit_transaction_) {
       throw UserModificationInMulticommandTxException();
@@ -3916,37 +4002,32 @@ class AuthHandlerCursor : public Cursor {
 
     ExpressionEvaluator evaluator(frame, &ctx, GraphView::OLD);
     std::experimental::optional<std::string> password;
-    /* TODO(mferencevic): handle null passwords properly */
     if (self_.password()) {
       auto password_tv = self_.password()->Accept(evaluator);
-      if (!password_tv.IsString()) {
-        throw QueryRuntimeException("Password must be a string, not '{}'!",
-                                    password_tv.type());
+      if (!password_tv.IsString() && !password_tv.IsNull()) {
+        throw QueryRuntimeException(
+            "Password must be a string or null, not '{}'!", password_tv.type());
+      }
+      if (password_tv.IsString()) {
+        password = password_tv.ValueString();
       }
-      password = password_tv.ValueString();
     }
 
     auto &auth = *ctx.auth_;
-    std::lock_guard<std::mutex> lock(auth.WithLock());
 
     switch (self_.action()) {
       case AuthQuery::Action::CREATE_USER: {
-        if (!password) {
-          throw QueryRuntimeException(
-              "Password must be provided when creating a user!");
-        }
-        auto user = auth.AddUser(self_.user());
+        std::lock_guard<std::mutex> lock(auth.WithLock());
+        auto user = auth.AddUser(self_.user(), password);
         if (!user) {
-          throw QueryRuntimeException("User '{}' already exists!",
+          throw QueryRuntimeException("User or role '{}' already exists!",
                                       self_.user());
         }
-        user->UpdatePassword(*password);
-        if (!auth.SaveUser(*user)) {
-          throw QueryRuntimeException("Couldn't save user '{}'!", self_.user());
-        }
-        break;
+        return false;
       }
+
       case AuthQuery::Action::DROP_USER: {
+        std::lock_guard<std::mutex> lock(auth.WithLock());
         auto user = auth.GetUser(self_.user());
         if (!user) {
           throw QueryRuntimeException("User '{}' doesn't exist!", self_.user());
@@ -3955,38 +4036,216 @@ class AuthHandlerCursor : public Cursor {
           throw QueryRuntimeException("Couldn't remove user '{}'!",
                                       self_.user());
         }
-        break;
+        return false;
       }
+
       case AuthQuery::Action::SET_PASSWORD: {
-        if (!password) {
-          throw QueryRuntimeException("Password must be provided!");
-        }
+        std::lock_guard<std::mutex> lock(auth.WithLock());
         auto user = auth.GetUser(self_.user());
         if (!user) {
           throw QueryRuntimeException("User '{}' doesn't exist!", self_.user());
         }
-        user->UpdatePassword(*password);
-        if (!auth.SaveUser(*user)) {
-          throw QueryRuntimeException("Couldn't set password for user '{}'!",
-                                      self_.user());
-        }
-        break;
+        user->UpdatePassword(password);
+        auth.SaveUser(*user);
+        return false;
       }
-      case AuthQuery::Action::CREATE_ROLE:
-      case AuthQuery::Action::DROP_ROLE:
-      case AuthQuery::Action::SHOW_ROLES:
-      case AuthQuery::Action::SHOW_USERS:
-      case AuthQuery::Action::GRANT_ROLE:
-      case AuthQuery::Action::REVOKE_ROLE:
+
+      case AuthQuery::Action::CREATE_ROLE: {
+        std::lock_guard<std::mutex> lock(auth.WithLock());
+        auto role = auth.AddRole(self_.role());
+        if (!role) {
+          throw QueryRuntimeException("User or role '{}' already exists!",
+                                      self_.role());
+        }
+        return false;
+      }
+
+      case AuthQuery::Action::DROP_ROLE: {
+        std::lock_guard<std::mutex> lock(auth.WithLock());
+        auto role = auth.GetRole(self_.role());
+        if (!role) {
+          throw QueryRuntimeException("Role '{}' doesn't exist!", self_.role());
+        }
+        if (!auth.RemoveRole(self_.role())) {
+          throw QueryRuntimeException("Couldn't remove role '{}'!",
+                                      self_.role());
+        }
+        return false;
+      }
+
+      case AuthQuery::Action::SHOW_USERS: {
+        if (!users_) {
+          std::lock_guard<std::mutex> lock(auth.WithLock());
+          users_.emplace(auth.AllUsers());
+          users_it_ = users_->begin();
+        }
+
+        if (users_it_ == users_->end()) return false;
+
+        frame[self_.user_symbol()] = users_it_->username();
+        users_it_++;
+
+        return true;
+      }
+
+      case AuthQuery::Action::SHOW_ROLES: {
+        if (!roles_) {
+          std::lock_guard<std::mutex> lock(auth.WithLock());
+          roles_.emplace(auth.AllRoles());
+          roles_it_ = roles_->begin();
+        }
+
+        if (roles_it_ == roles_->end()) return false;
+
+        frame[self_.role_symbol()] = roles_it_->rolename();
+        roles_it_++;
+
+        return true;
+      }
+
+      case AuthQuery::Action::GRANT_ROLE: {
+        std::lock_guard<std::mutex> lock(auth.WithLock());
+        auto user = auth.GetUser(self_.user());
+        if (!user) {
+          throw QueryRuntimeException("User '{}' doesn't exist!", self_.user());
+        }
+        auto role = auth.GetRole(self_.role());
+        if (!role) {
+          throw QueryRuntimeException("Role '{}' doesn't exist!", self_.role());
+        }
+        if (user->role()) {
+          throw QueryRuntimeException(
+              "User '{}' is already a member of role '{}'!", self_.user(),
+              user->role()->rolename());
+        }
+        user->SetRole(*role);
+        auth.SaveUser(*user);
+        return false;
+      }
+
+      case AuthQuery::Action::REVOKE_ROLE: {
+        std::lock_guard<std::mutex> lock(auth.WithLock());
+        auto user = auth.GetUser(self_.user());
+        if (!user) {
+          throw QueryRuntimeException("User '{}' doesn't exist!", self_.user());
+        }
+        auto role = auth.GetRole(self_.role());
+        if (!role) {
+          throw QueryRuntimeException("Role '{}' doesn't exist!", self_.role());
+        }
+        if (user->role() != role) {
+          throw QueryRuntimeException("User '{}' isn't a member of role '{}'!",
+                                      self_.user(), self_.role());
+        }
+        user->ClearRole();
+        auth.SaveUser(*user);
+        return false;
+      }
+
       case AuthQuery::Action::GRANT_PRIVILEGE:
       case AuthQuery::Action::DENY_PRIVILEGE:
-      case AuthQuery::Action::REVOKE_PRIVILEGE:
-      case AuthQuery::Action::SHOW_GRANTS:
-      case AuthQuery::Action::SHOW_ROLE_FOR_USER:
-      case AuthQuery::Action::SHOW_USERS_FOR_ROLE:
-        throw utils::NotYetImplemented("user auth");
+      case AuthQuery::Action::REVOKE_PRIVILEGE: {
+        std::lock_guard<std::mutex> lock(auth.WithLock());
+        auto user = auth.GetUser(self_.user_or_role());
+        auto role = auth.GetRole(self_.user_or_role());
+        if (!user && !role) {
+          throw QueryRuntimeException("User or role '{}' doesn't exist!",
+                                      self_.user_or_role());
+        }
+        auto permissions = GetAuthPermissions();
+        if (user) {
+          for (const auto &permission : permissions) {
+            // TODO (mferencevic): should we first check that the privilege
+            // is granted/denied/revoked before unconditionally
+            // granting/denying/revoking it?
+            if (self_.action() == AuthQuery::Action::GRANT_PRIVILEGE) {
+              user->permissions().Grant(permission);
+            } else if (self_.action() == AuthQuery::Action::DENY_PRIVILEGE) {
+              user->permissions().Deny(permission);
+            } else {
+              user->permissions().Revoke(permission);
+            }
+          }
+          auth.SaveUser(*user);
+        } else {
+          for (const auto &permission : permissions) {
+            // TODO (mferencevic): should we first check that the privilege
+            // is granted/denied/revoked before unconditionally
+            // granting/denying/revoking it?
+            if (self_.action() == AuthQuery::Action::GRANT_PRIVILEGE) {
+              role->permissions().Grant(permission);
+            } else if (self_.action() == AuthQuery::Action::DENY_PRIVILEGE) {
+              role->permissions().Deny(permission);
+            } else {
+              role->permissions().Revoke(permission);
+            }
+          }
+          auth.SaveRole(*role);
+        }
+        return false;
+      }
+
+      case AuthQuery::Action::SHOW_GRANTS: {
+        if (!grants_) {
+          std::lock_guard<std::mutex> lock(auth.WithLock());
+          auto user = auth.GetUser(self_.user_or_role());
+          auto role = auth.GetRole(self_.user_or_role());
+          if (!user && !role) {
+            throw QueryRuntimeException("User or role '{}' doesn't exist!",
+                                        self_.user_or_role());
+          }
+          if (user) {
+            grants_.emplace(GetGrantsFromAuthPermissions(user->permissions()));
+          } else {
+            grants_.emplace(GetGrantsFromAuthPermissions(role->permissions()));
+          }
+          grants_it_ = grants_->begin();
+        }
+
+        if (grants_it_ == grants_->end()) return false;
+
+        frame[self_.grants_symbol()] = *grants_it_;
+        grants_it_++;
+
+        return true;
+      }
+
+      case AuthQuery::Action::SHOW_ROLE_FOR_USER: {
+        if (returned_role_for_user_) return false;
+        std::lock_guard<std::mutex> lock(auth.WithLock());
+        auto user = auth.GetUser(self_.user());
+        if (!user) {
+          throw QueryRuntimeException("User '{}' doesn't exist!", self_.user());
+        }
+        if (user->role()) {
+          frame[self_.role_symbol()] = user->role()->rolename();
+        } else {
+          frame[self_.role_symbol()] = TypedValue::Null;
+        }
+        returned_role_for_user_ = true;
+        return true;
+      }
+
+      case AuthQuery::Action::SHOW_USERS_FOR_ROLE: {
+        if (!users_) {
+          std::lock_guard<std::mutex> lock(auth.WithLock());
+          auto role = auth.GetRole(self_.role());
+          if (!role) {
+            throw QueryRuntimeException("Role '{}' doesn't exist!",
+                                        self_.role());
+          }
+          users_.emplace(auth.AllUsersForRole(self_.role()));
+          users_it_ = users_->begin();
+        }
+
+        if (users_it_ == users_->end()) return false;
+
+        frame[self_.user_symbol()] = users_it_->username();
+        users_it_++;
+
+        return true;
+      }
     }
-    return false;
   }
 
   void Reset() override {
@@ -3995,6 +4254,13 @@ class AuthHandlerCursor : public Cursor {
 
  private:
   const AuthHandler &self_;
+  std::experimental::optional<std::vector<auth::User>> users_;
+  std::vector<auth::User>::iterator users_it_;
+  std::experimental::optional<std::vector<auth::Role>> roles_;
+  std::vector<auth::Role>::iterator roles_it_;
+  std::experimental::optional<std::vector<std::string>> grants_;
+  std::vector<std::string>::iterator grants_it_;
+  bool returned_role_for_user_{false};
 };
 
 std::unique_ptr<Cursor> AuthHandler::MakeCursor(
diff --git a/src/query/plan/operator.lcp b/src/query/plan/operator.lcp
index 05e5a9f42..4f3e39cc9 100644
--- a/src/query/plan/operator.lcp
+++ b/src/query/plan/operator.lcp
@@ -2019,6 +2019,12 @@ and returns true, once.")
                       case AuthQuery::Privilege::SET:
                         ${builder}.set(i, query::capnp::AuthQuery::Privilege::SET);
                         break;
+                      case AuthQuery::Privilege::REMOVE:
+                        ${builder}.set(i, query::capnp::AuthQuery::Privilege::REMOVE);
+                        break;
+                      case AuthQuery::Privilege::INDEX:
+                        ${builder}.set(i, query::capnp::AuthQuery::Privilege::INDEX);
+                        break;
                       case AuthQuery::Privilege::AUTH:
                         ${builder}.set(i, query::capnp::AuthQuery::Privilege::AUTH);
                         break;
@@ -2048,6 +2054,12 @@ and returns true, once.")
                        case query::capnp::AuthQuery::Privilege::SET:
                          ${member-name}.push_back(AuthQuery::Privilege::SET);
                          break;
+                       case query::capnp::AuthQuery::Privilege::REMOVE:
+                         ${member-name}.push_back(AuthQuery::Privilege::REMOVE);
+                         break;
+                       case query::capnp::AuthQuery::Privilege::INDEX:
+                         ${member-name}.push_back(AuthQuery::Privilege::INDEX);
+                         break;
                        case query::capnp::AuthQuery::Privilege::AUTH:
                          ${member-name}.push_back(AuthQuery::Privilege::AUTH);
                          break;
@@ -2056,16 +2068,21 @@ and returns true, once.")
                          break;
                      }
                    }
-                   cpp<#)))
+                   cpp<#))
+   (user-symbol "Symbol" :reader t)
+   (role-symbol "Symbol" :reader t)
+   (grants-symbol "Symbol" :reader t))
   (:public
     #>cpp
     AuthHandler(AuthQuery::Action action, std::string user, std::string role,
                 std::string user_or_role, Expression * password,
-                std::vector<AuthQuery::Privilege> privileges);
+                std::vector<AuthQuery::Privilege> privileges,
+                Symbol user_symbol, Symbol role_symbol, Symbol grants_symbol);
 
     bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
     std::unique_ptr<Cursor> MakeCursor(database::GraphDbAccessor & db)
         const override;
+    std::vector<Symbol> OutputSymbols(const SymbolTable &) const override;
     virtual std::vector<Symbol> ModifiedSymbols(const SymbolTable &)
         const override { return {}; }
   
diff --git a/src/query/plan/rule_based_planner.hpp b/src/query/plan/rule_based_planner.hpp
index 50bf99883..821d04f65 100644
--- a/src/query/plan/rule_based_planner.hpp
+++ b/src/query/plan/rule_based_planner.hpp
@@ -185,10 +185,14 @@ class RuleBasedPlanner {
         } else if (auto *auth_query =
                        dynamic_cast<query::AuthQuery *>(clause)) {
           DCHECK(!input_op) << "Unexpected operator before AuthQuery";
+          auto &symbol_table = context.symbol_table;
           input_op = std::make_unique<plan::AuthHandler>(
               auth_query->action_, auth_query->user_, auth_query->role_,
               auth_query->user_or_role_, auth_query->password_,
-              auth_query->privileges_);
+              auth_query->privileges_,
+              symbol_table.CreateSymbol("user", false),
+              symbol_table.CreateSymbol("role", false),
+              symbol_table.CreateSymbol("grants", false));
         } else if (auto *create_stream =
                        dynamic_cast<query::CreateStream *>(clause)) {
           DCHECK(!input_op) << "Unexpected operator before CreateStream";
diff --git a/src/storage/kvstore.cpp b/src/storage/kvstore.cpp
index 1260c08fc..10a2a4246 100644
--- a/src/storage/kvstore.cpp
+++ b/src/storage/kvstore.cpp
@@ -40,6 +40,15 @@ bool KVStore::Put(const std::string &key, const std::string &value) {
   return s.ok();
 }
 
+bool KVStore::PutMultiple(const std::map<std::string, std::string> &items) {
+  rocksdb::WriteBatch batch;
+  for (const auto &item : items) {
+    batch.Put(item.first, item.second);
+  }
+  auto s = pimpl_->db->Write(rocksdb::WriteOptions(), &batch);
+  return s.ok();
+}
+
 std::experimental::optional<std::string> KVStore::Get(
     const std::string &key) const noexcept {
   std::string value;
@@ -53,6 +62,15 @@ bool KVStore::Delete(const std::string &key) {
   return s.ok();
 }
 
+bool KVStore::DeleteMultiple(const std::vector<std::string> &keys) {
+  rocksdb::WriteBatch batch;
+  for (const auto &key : keys) {
+    batch.Delete(key);
+  }
+  auto s = pimpl_->db->Write(rocksdb::WriteOptions(), &batch);
+  return s.ok();
+}
+
 bool KVStore::DeletePrefix(const std::string &prefix) {
   std::unique_ptr<rocksdb::Iterator> iter = std::unique_ptr<rocksdb::Iterator>(
       pimpl_->db->NewIterator(rocksdb::ReadOptions()));
@@ -64,6 +82,20 @@ bool KVStore::DeletePrefix(const std::string &prefix) {
   return true;
 }
 
+bool KVStore::PutAndDeleteMultiple(
+    const std::map<std::string, std::string> &items,
+    const std::vector<std::string> &keys) {
+  rocksdb::WriteBatch batch;
+  for (const auto &item : items) {
+    batch.Put(item.first, item.second);
+  }
+  for (const auto &key : keys) {
+    batch.Delete(key);
+  }
+  auto s = pimpl_->db->Write(rocksdb::WriteOptions(), &batch);
+  return s.ok();
+}
+
 // iterator
 
 struct KVStore::iterator::impl {
diff --git a/src/storage/kvstore.hpp b/src/storage/kvstore.hpp
index 049a432e2..5be82b72b 100644
--- a/src/storage/kvstore.hpp
+++ b/src/storage/kvstore.hpp
@@ -2,8 +2,10 @@
 
 #include <experimental/filesystem>
 #include <experimental/optional>
+#include <map>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "utils/exceptions.hpp"
 
@@ -49,6 +51,16 @@ class KVStore final {
    */
   bool Put(const std::string &key, const std::string &value);
 
+  /**
+   * Store values under the given keys.
+   *
+   * @param items
+   *
+   * @return true if the items have been successfully stored.
+   *         In case of any error false is going to be returned.
+   */
+  bool PutMultiple(const std::map<std::string, std::string> &items);
+
   /**
    * Retrieve value for the given key.
    *
@@ -71,6 +83,17 @@ class KVStore final {
    */
   bool Delete(const std::string &key);
 
+  /**
+   * Deletes the keys and corresponding values from storage.
+   *
+   * @param keys
+   *
+   * @return True on success, false on error. The return value is
+   *         true if the keys don't exist and underlying storage
+   *         didn't encounter any error.
+   */
+  bool DeleteMultiple(const std::vector<std::string> &keys);
+
   /**
    * Delete all (key, value) pairs where key begins with a given prefix.
    *
@@ -83,6 +106,18 @@ class KVStore final {
    */
   bool DeletePrefix(const std::string &prefix = "");
 
+  /**
+   * Store values under the given keys and delete the keys.
+   *
+   * @param items
+   * @param keys
+   *
+   * @return true if the items have been successfully stored and deleted.
+   *         In case of any error false is going to be returned.
+   */
+  bool PutAndDeleteMultiple(const std::map<std::string, std::string> &items,
+                            const std::vector<std::string> &keys);
+
   /**
    * Returns total number of stored (key, value) pairs. The function takes an
    * optional prefix parameter used for filtering keys that start with that
diff --git a/src/storage/kvstore_dummy.cpp b/src/storage/kvstore_dummy.cpp
index e3fd5d6fa..c78c75582 100644
--- a/src/storage/kvstore_dummy.cpp
+++ b/src/storage/kvstore_dummy.cpp
@@ -16,6 +16,11 @@ bool KVStore::Put(const std::string &key, const std::string &value) {
       << "Unsupported operation (KVStore::Put) -- this is a dummy kvstore";
 }
 
+bool KVStore::PutMultiple(const std::map<std::string, std::string> &items) {
+  CHECK(false) << "Unsupported operation (KVStore::PutMultiple) -- this is a "
+                  "dummy kvstore";
+}
+
 std::experimental::optional<std::string> KVStore::Get(
     const std::string &key) const noexcept {
   CHECK(false)
@@ -27,11 +32,24 @@ bool KVStore::Delete(const std::string &key) {
       << "Unsupported operation (KVStore::Delete) -- this is a dummy kvstore";
 }
 
+bool KVStore::DeleteMultiple(const std::vector<std::string> &keys) {
+  CHECK(false) << "Unsupported operation (KVStore::DeleteMultiple) -- this is "
+                  "a dummy kvstore";
+}
+
 bool KVStore::DeletePrefix(const std::string &prefix) {
   CHECK(false) << "Unsupported operation (KVStore::DeletePrefix) -- this is a "
                   "dummy kvstore";
 }
 
+bool KVStore::PutAndDeleteMultiple(
+    const std::map<std::string, std::string> &items,
+    const std::vector<std::string> &keys) {
+  CHECK(false)
+      << "Unsupported operation (KVStore::PutAndDeleteMultiple) -- this is a "
+         "dummy kvstore";
+}
+
 // iterator
 
 struct KVStore::iterator::impl {};
diff --git a/tests/unit/auth.cpp b/tests/unit/auth.cpp
index 542c7512b..dd06c6363 100644
--- a/tests/unit/auth.cpp
+++ b/tests/unit/auth.cpp
@@ -12,9 +12,16 @@
 using namespace auth;
 namespace fs = std::experimental::filesystem;
 
+DECLARE_bool(auth_password_permit_null);
+DECLARE_string(auth_password_strength_regex);
+
 class AuthWithStorage : public ::testing::Test {
  protected:
-  virtual void SetUp() { utils::EnsureDir(test_folder_); }
+  virtual void SetUp() {
+    utils::EnsureDir(test_folder_);
+    FLAGS_auth_password_permit_null = true;
+    FLAGS_auth_password_strength_regex = ".+";
+  }
 
   virtual void TearDown() { fs::remove_all(test_folder_); }
 
@@ -64,15 +71,24 @@ TEST_F(AuthWithStorage, Authenticate) {
   ASSERT_NE(user, std::experimental::nullopt);
   ASSERT_TRUE(auth.HasUsers());
 
-  ASSERT_THROW(auth.Authenticate("test", "123"), utils::BasicException);
+  ASSERT_TRUE(auth.Authenticate("test", "123"));
 
   user->UpdatePassword("123");
-  ASSERT_TRUE(auth.SaveUser(*user));
+  auth.SaveUser(*user);
 
   ASSERT_NE(auth.Authenticate("test", "123"), std::experimental::nullopt);
 
   ASSERT_EQ(auth.Authenticate("test", "456"), std::experimental::nullopt);
   ASSERT_NE(auth.Authenticate("test", "123"), std::experimental::nullopt);
+
+  user->UpdatePassword();
+  auth.SaveUser(*user);
+
+  ASSERT_NE(auth.Authenticate("test", "123"), std::experimental::nullopt);
+  ASSERT_NE(auth.Authenticate("test", "456"), std::experimental::nullopt);
+
+  ASSERT_EQ(auth.Authenticate("nonexistant", "123"),
+            std::experimental::nullopt);
 }
 
 TEST_F(AuthWithStorage, UserRolePermissions) {
@@ -84,27 +100,27 @@ TEST_F(AuthWithStorage, UserRolePermissions) {
   ASSERT_NE(user, std::experimental::nullopt);
 
   // Test initial user permissions.
-  ASSERT_EQ(user->permissions().Has(Permission::Read),
-            PermissionLevel::Neutral);
-  ASSERT_EQ(user->permissions().Has(Permission::Create),
-            PermissionLevel::Neutral);
-  ASSERT_EQ(user->permissions().Has(Permission::Update),
-            PermissionLevel::Neutral);
-  ASSERT_EQ(user->permissions().Has(Permission::Delete),
-            PermissionLevel::Neutral);
+  ASSERT_EQ(user->permissions().Has(Permission::MATCH),
+            PermissionLevel::NEUTRAL);
+  ASSERT_EQ(user->permissions().Has(Permission::CREATE),
+            PermissionLevel::NEUTRAL);
+  ASSERT_EQ(user->permissions().Has(Permission::MERGE),
+            PermissionLevel::NEUTRAL);
+  ASSERT_EQ(user->permissions().Has(Permission::DELETE),
+            PermissionLevel::NEUTRAL);
   ASSERT_EQ(user->permissions(), user->GetPermissions());
 
   // Change one user permission.
-  user->permissions().Grant(Permission::Read);
+  user->permissions().Grant(Permission::MATCH);
 
   // Check permissions.
-  ASSERT_EQ(user->permissions().Has(Permission::Read), PermissionLevel::Grant);
-  ASSERT_EQ(user->permissions().Has(Permission::Create),
-            PermissionLevel::Neutral);
-  ASSERT_EQ(user->permissions().Has(Permission::Update),
-            PermissionLevel::Neutral);
-  ASSERT_EQ(user->permissions().Has(Permission::Delete),
-            PermissionLevel::Neutral);
+  ASSERT_EQ(user->permissions().Has(Permission::MATCH), PermissionLevel::GRANT);
+  ASSERT_EQ(user->permissions().Has(Permission::CREATE),
+            PermissionLevel::NEUTRAL);
+  ASSERT_EQ(user->permissions().Has(Permission::MERGE),
+            PermissionLevel::NEUTRAL);
+  ASSERT_EQ(user->permissions().Has(Permission::DELETE),
+            PermissionLevel::NEUTRAL);
   ASSERT_EQ(user->permissions(), user->GetPermissions());
 
   // Create role.
@@ -113,29 +129,29 @@ TEST_F(AuthWithStorage, UserRolePermissions) {
   ASSERT_NE(role, std::experimental::nullopt);
 
   // Assign permissions to role and role to user.
-  role->permissions().Grant(Permission::Delete);
+  role->permissions().Grant(Permission::DELETE);
   user->SetRole(*role);
 
   // Check permissions.
   {
     auto permissions = user->GetPermissions();
-    ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Grant);
-    ASSERT_EQ(permissions.Has(Permission::Delete), PermissionLevel::Grant);
-    ASSERT_EQ(permissions.Has(Permission::Create), PermissionLevel::Neutral);
-    ASSERT_EQ(permissions.Has(Permission::Update), PermissionLevel::Neutral);
+    ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::GRANT);
+    ASSERT_EQ(permissions.Has(Permission::DELETE), PermissionLevel::GRANT);
+    ASSERT_EQ(permissions.Has(Permission::CREATE), PermissionLevel::NEUTRAL);
+    ASSERT_EQ(permissions.Has(Permission::MERGE), PermissionLevel::NEUTRAL);
   }
 
   // Add explicit deny to role.
-  role->permissions().Deny(Permission::Read);
+  role->permissions().Deny(Permission::MATCH);
   user->SetRole(*role);
 
   // Check permissions.
   {
     auto permissions = user->GetPermissions();
-    ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Deny);
-    ASSERT_EQ(permissions.Has(Permission::Delete), PermissionLevel::Grant);
-    ASSERT_EQ(permissions.Has(Permission::Create), PermissionLevel::Neutral);
-    ASSERT_EQ(permissions.Has(Permission::Update), PermissionLevel::Neutral);
+    ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::DENY);
+    ASSERT_EQ(permissions.Has(Permission::DELETE), PermissionLevel::GRANT);
+    ASSERT_EQ(permissions.Has(Permission::CREATE), PermissionLevel::NEUTRAL);
+    ASSERT_EQ(permissions.Has(Permission::MERGE), PermissionLevel::NEUTRAL);
   }
 }
 
@@ -202,6 +218,126 @@ TEST_F(AuthWithStorage, RoleManipulations) {
     ASSERT_TRUE(role2);
     ASSERT_EQ(role2->rolename(), "role2");
   }
+
+  {
+    auto users = auth.AllUsers();
+    std::sort(users.begin(), users.end(), [](const User &a, const User &b) {
+      return a.username() < b.username();
+    });
+    ASSERT_EQ(users.size(), 2);
+    ASSERT_EQ(users[0].username(), "user1");
+    ASSERT_EQ(users[1].username(), "user2");
+  }
+
+  {
+    auto roles = auth.AllRoles();
+    std::sort(roles.begin(), roles.end(), [](const Role &a, const Role &b) {
+      return a.rolename() < b.rolename();
+    });
+    ASSERT_EQ(roles.size(), 2);
+    ASSERT_EQ(roles[0].rolename(), "role1");
+    ASSERT_EQ(roles[1].rolename(), "role2");
+  }
+
+  {
+    auto users = auth.AllUsersForRole("role2");
+    ASSERT_EQ(users.size(), 1);
+    ASSERT_EQ(users[0].username(), "user2");
+  }
+}
+
+TEST_F(AuthWithStorage, UserRoleLinkUnlink) {
+  {
+    auto user = auth.AddUser("user");
+    ASSERT_TRUE(user);
+    auto role = auth.AddRole("role");
+    ASSERT_TRUE(role);
+    user->SetRole(*role);
+    auth.SaveUser(*user);
+  }
+
+  {
+    auto user = auth.GetUser("user");
+    ASSERT_TRUE(user);
+    auto role = user->role();
+    ASSERT_TRUE(role);
+    ASSERT_EQ(role->rolename(), "role");
+  }
+
+  {
+    auto user = auth.GetUser("user");
+    ASSERT_TRUE(user);
+    user->ClearRole();
+    auth.SaveUser(*user);
+  }
+
+  {
+    auto user = auth.GetUser("user");
+    ASSERT_TRUE(user);
+    ASSERT_FALSE(user->role());
+  }
+}
+
+TEST_F(AuthWithStorage, UserPasswordCreation) {
+  {
+    auto user = auth.AddUser("test");
+    ASSERT_TRUE(user);
+    ASSERT_TRUE(auth.Authenticate("test", "123"));
+    ASSERT_TRUE(auth.Authenticate("test", "456"));
+    ASSERT_TRUE(auth.RemoveUser(user->username()));
+  }
+
+  {
+    auto user = auth.AddUser("test", "123");
+    ASSERT_TRUE(user);
+    ASSERT_TRUE(auth.Authenticate("test", "123"));
+    ASSERT_FALSE(auth.Authenticate("test", "456"));
+    ASSERT_TRUE(auth.RemoveUser(user->username()));
+  }
+}
+
+TEST_F(AuthWithStorage, PasswordStrength) {
+  const std::string kWeakRegex = ".+";
+  // https://stackoverflow.com/questions/5142103/regex-to-validate-password-strength
+  const std::string kStrongRegex =
+      "^(?=.*[A-Z].*[A-Z])(?=.*[!@#$&*])(?=.*[0-9].*[0-9])(?=.*[a-z].*[a-z].*["
+      "a-z]).{8,}$";
+
+  const std::string kWeakPassword = "weak";
+  const std::string kAlmostStrongPassword =
+      "ThisPasswordMeetsAllButOneCriterion1234";
+  const std::string kStrongPassword = "ThisIsAVeryStrongPassword123$";
+
+  auto user = auth.AddUser("user");
+  ASSERT_TRUE(user);
+
+  FLAGS_auth_password_permit_null = true;
+  FLAGS_auth_password_strength_regex = kWeakRegex;
+  ASSERT_NO_THROW(user->UpdatePassword());
+  ASSERT_NO_THROW(user->UpdatePassword(kWeakPassword));
+  ASSERT_NO_THROW(user->UpdatePassword(kAlmostStrongPassword));
+  ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword));
+
+  FLAGS_auth_password_permit_null = false;
+  FLAGS_auth_password_strength_regex = kWeakRegex;
+  ASSERT_THROW(user->UpdatePassword(), AuthException);
+  ASSERT_NO_THROW(user->UpdatePassword(kWeakPassword));
+  ASSERT_NO_THROW(user->UpdatePassword(kAlmostStrongPassword));
+  ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword));
+
+  FLAGS_auth_password_permit_null = true;
+  FLAGS_auth_password_strength_regex = kStrongRegex;
+  ASSERT_NO_THROW(user->UpdatePassword());
+  ASSERT_THROW(user->UpdatePassword(kWeakPassword), AuthException);
+  ASSERT_THROW(user->UpdatePassword(kAlmostStrongPassword), AuthException);
+  ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword));
+
+  FLAGS_auth_password_permit_null = false;
+  FLAGS_auth_password_strength_regex = kStrongRegex;
+  ASSERT_THROW(user->UpdatePassword(), AuthException);
+  ASSERT_THROW(user->UpdatePassword(kWeakPassword), AuthException);
+  ASSERT_THROW(user->UpdatePassword(kAlmostStrongPassword), AuthException);
+  ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword));
 }
 
 TEST(AuthWithoutStorage, Permissions) {
@@ -209,50 +345,50 @@ TEST(AuthWithoutStorage, Permissions) {
   ASSERT_EQ(permissions.grants(), 0);
   ASSERT_EQ(permissions.denies(), 0);
 
-  permissions.Grant(Permission::Read);
-  ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Grant);
-  ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::Read));
+  permissions.Grant(Permission::MATCH);
+  ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::GRANT);
+  ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::MATCH));
   ASSERT_EQ(permissions.denies(), 0);
 
-  permissions.Revoke(Permission::Read);
-  ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Neutral);
+  permissions.Revoke(Permission::MATCH);
+  ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::NEUTRAL);
   ASSERT_EQ(permissions.grants(), 0);
   ASSERT_EQ(permissions.denies(), 0);
 
-  permissions.Deny(Permission::Read);
-  ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Deny);
-  ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::Read));
+  permissions.Deny(Permission::MATCH);
+  ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::DENY);
+  ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::MATCH));
   ASSERT_EQ(permissions.grants(), 0);
 
-  permissions.Grant(Permission::Read);
-  ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Grant);
-  ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::Read));
+  permissions.Grant(Permission::MATCH);
+  ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::GRANT);
+  ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::MATCH));
   ASSERT_EQ(permissions.denies(), 0);
 
-  permissions.Deny(Permission::Create);
-  ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Grant);
-  ASSERT_EQ(permissions.Has(Permission::Create), PermissionLevel::Deny);
-  ASSERT_EQ(permissions.Has(Permission::Update), PermissionLevel::Neutral);
-  ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::Read));
-  ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::Create));
+  permissions.Deny(Permission::CREATE);
+  ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::GRANT);
+  ASSERT_EQ(permissions.Has(Permission::CREATE), PermissionLevel::DENY);
+  ASSERT_EQ(permissions.Has(Permission::MERGE), PermissionLevel::NEUTRAL);
+  ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::MATCH));
+  ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::CREATE));
 
-  permissions.Grant(Permission::Delete);
-  ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Grant);
-  ASSERT_EQ(permissions.Has(Permission::Create), PermissionLevel::Deny);
-  ASSERT_EQ(permissions.Has(Permission::Update), PermissionLevel::Neutral);
-  ASSERT_EQ(permissions.Has(Permission::Delete), PermissionLevel::Grant);
+  permissions.Grant(Permission::DELETE);
+  ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::GRANT);
+  ASSERT_EQ(permissions.Has(Permission::CREATE), PermissionLevel::DENY);
+  ASSERT_EQ(permissions.Has(Permission::MERGE), PermissionLevel::NEUTRAL);
+  ASSERT_EQ(permissions.Has(Permission::DELETE), PermissionLevel::GRANT);
   ASSERT_EQ(permissions.grants(),
-            utils::UnderlyingCast(Permission::Read) |
-                utils::UnderlyingCast(Permission::Delete));
-  ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::Create));
+            utils::UnderlyingCast(Permission::MATCH) |
+                utils::UnderlyingCast(Permission::DELETE));
+  ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::CREATE));
 
-  permissions.Revoke(Permission::Delete);
-  ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Grant);
-  ASSERT_EQ(permissions.Has(Permission::Create), PermissionLevel::Deny);
-  ASSERT_EQ(permissions.Has(Permission::Update), PermissionLevel::Neutral);
-  ASSERT_EQ(permissions.Has(Permission::Delete), PermissionLevel::Neutral);
-  ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::Read));
-  ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::Create));
+  permissions.Revoke(Permission::DELETE);
+  ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::GRANT);
+  ASSERT_EQ(permissions.Has(Permission::CREATE), PermissionLevel::DENY);
+  ASSERT_EQ(permissions.Has(Permission::MERGE), PermissionLevel::NEUTRAL);
+  ASSERT_EQ(permissions.Has(Permission::DELETE), PermissionLevel::NEUTRAL);
+  ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::MATCH));
+  ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::CREATE));
 }
 
 TEST(AuthWithoutStorage, PermissionsMaskTest) {
@@ -275,8 +411,8 @@ TEST(AuthWithoutStorage, PermissionsMaskTest) {
 
 TEST(AuthWithoutStorage, UserSerializeDeserialize) {
   auto user = User("test");
-  user.permissions().Grant(Permission::Read);
-  user.permissions().Deny(Permission::Update);
+  user.permissions().Grant(Permission::MATCH);
+  user.permissions().Deny(Permission::MERGE);
   user.UpdatePassword("world");
 
   auto data = user.Serialize();
@@ -287,8 +423,8 @@ TEST(AuthWithoutStorage, UserSerializeDeserialize) {
 
 TEST(AuthWithoutStorage, RoleSerializeDeserialize) {
   auto role = Role("test");
-  role.permissions().Grant(Permission::Read);
-  role.permissions().Deny(Permission::Update);
+  role.permissions().Grant(Permission::MATCH);
+  role.permissions().Deny(Permission::MERGE);
 
   auto data = role.Serialize();
 
@@ -297,25 +433,32 @@ TEST(AuthWithoutStorage, RoleSerializeDeserialize) {
 }
 
 TEST_F(AuthWithStorage, UserWithRoleSerializeDeserialize) {
-  auto role = auth.AddRole("test");
+  auto role = auth.AddRole("role");
   ASSERT_TRUE(role);
-  role->permissions().Grant(Permission::Read);
-  role->permissions().Deny(Permission::Update);
+  role->permissions().Grant(Permission::MATCH);
+  role->permissions().Deny(Permission::MERGE);
   auth.SaveRole(*role);
 
-  auto user = auth.AddUser("test");
+  auto user = auth.AddUser("user");
   ASSERT_TRUE(user);
-  user->permissions().Grant(Permission::Read);
-  user->permissions().Deny(Permission::Update);
+  user->permissions().Grant(Permission::MATCH);
+  user->permissions().Deny(Permission::MERGE);
   user->UpdatePassword("world");
   user->SetRole(*role);
   auth.SaveUser(*user);
 
-  auto new_user = auth.GetUser("test");
+  auto new_user = auth.GetUser("user");
   ASSERT_TRUE(new_user);
   ASSERT_EQ(*user, *new_user);
 }
 
+TEST_F(AuthWithStorage, UserRoleUniqueName) {
+  ASSERT_TRUE(auth.AddUser("user"));
+  ASSERT_TRUE(auth.AddRole("role"));
+  ASSERT_FALSE(auth.AddRole("user"));
+  ASSERT_FALSE(auth.AddUser("role"));
+}
+
 TEST(AuthWithoutStorage, Crypto) {
   auto hash = EncryptPassword("hello");
   ASSERT_TRUE(VerifyPassword("hello", hash));
diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp
index 01061d084..e3ca7d99e 100644
--- a/tests/unit/cypher_main_visitor.cpp
+++ b/tests/unit/cypher_main_visitor.cpp
@@ -2037,7 +2037,8 @@ TYPED_TEST(CypherMainVisitorTest, RevokePrivilege) {
       "", "", "user", {},
       {AuthQuery::Privilege::CREATE, AuthQuery::Privilege::DELETE,
        AuthQuery::Privilege::MATCH, AuthQuery::Privilege::MERGE,
-       AuthQuery::Privilege::SET, AuthQuery::Privilege::AUTH,
+       AuthQuery::Privilege::SET, AuthQuery::Privilege::REMOVE,
+       AuthQuery::Privilege::INDEX, AuthQuery::Privilege::AUTH,
        AuthQuery::Privilege::STREAM});
 }
 
diff --git a/tests/unit/kvstore.cpp b/tests/unit/kvstore.cpp
index e8afef4bd..4b232187f 100644
--- a/tests/unit/kvstore.cpp
+++ b/tests/unit/kvstore.cpp
@@ -26,6 +26,13 @@ TEST_F(KVStore, PutGet) {
   ASSERT_EQ(kvstore.Get("key").value(), "value");
 }
 
+TEST_F(KVStore, PutMultipleGet) {
+  storage::KVStore kvstore(test_folder_ / "PutMultipleGet");
+  ASSERT_TRUE(kvstore.PutMultiple({{"key1", "value1"}, {"key2", "value2"}}));
+  ASSERT_EQ(kvstore.Get("key1").value(), "value1");
+  ASSERT_EQ(kvstore.Get("key2").value(), "value2");
+}
+
 TEST_F(KVStore, PutGetDeleteGet) {
   storage::KVStore kvstore(test_folder_ / "PutGetDeleteGet");
   ASSERT_TRUE(kvstore.Put("key", "value"));
@@ -34,6 +41,28 @@ TEST_F(KVStore, PutGetDeleteGet) {
   ASSERT_FALSE(static_cast<bool>(kvstore.Get("key")));
 }
 
+TEST_F(KVStore, PutMultipleGetDeleteMultipleGet) {
+  storage::KVStore kvstore(test_folder_ / "PutMultipleGetDeleteMultipleGet");
+  ASSERT_TRUE(kvstore.PutMultiple({{"key1", "value1"}, {"key2", "value2"}}));
+  ASSERT_EQ(kvstore.Get("key1").value(), "value1");
+  ASSERT_EQ(kvstore.Get("key2").value(), "value2");
+  ASSERT_TRUE(kvstore.DeleteMultiple({"key1", "key2", "key3"}));
+  ASSERT_FALSE(static_cast<bool>(kvstore.Get("key1")));
+  ASSERT_FALSE(static_cast<bool>(kvstore.Get("key2")));
+  ASSERT_FALSE(static_cast<bool>(kvstore.Get("key3")));
+}
+
+TEST_F(KVStore, PutMultipleGetPutAndDeleteMultipleGet) {
+  storage::KVStore kvstore(test_folder_ / "PutMultipleGetPutAndDeleteMultipleGet");
+  ASSERT_TRUE(kvstore.PutMultiple({{"key1", "value1"}, {"key2", "value2"}}));
+  ASSERT_EQ(kvstore.Get("key1").value(), "value1");
+  ASSERT_EQ(kvstore.Get("key2").value(), "value2");
+  ASSERT_TRUE(kvstore.PutAndDeleteMultiple({{"key3", "value3"}}, {"key1", "key2"}));
+  ASSERT_FALSE(static_cast<bool>(kvstore.Get("key1")));
+  ASSERT_FALSE(static_cast<bool>(kvstore.Get("key2")));
+  ASSERT_EQ(kvstore.Get("key3").value(), "value3");
+}
+
 TEST_F(KVStore, Durability) {
   {
     storage::KVStore kvstore(test_folder_ / "Durability");