diff --git a/templates/zerver/api/changelog.md b/templates/zerver/api/changelog.md
index c86b38446b..42e7183ef9 100644
--- a/templates/zerver/api/changelog.md
+++ b/templates/zerver/api/changelog.md
@@ -10,6 +10,11 @@ below features are supported.
## Changes in Zulip 4.0
+**Feature level 61**
+
+* Added support for inviting users as moderators to the invitation
+ endpoints.
+
**Feature level 60**
* [`POST /register`](/api/register-queue): Added a new boolean field
diff --git a/templates/zerver/app/invite_user.html b/templates/zerver/app/invite_user.html
index 73c8d70dc9..71f9156882 100644
--- a/templates/zerver/app/invite_user.html
+++ b/templates/zerver/app/invite_user.html
@@ -40,6 +40,7 @@
{% if is_admin %}
+
{% endif %}
{% if is_owner %}
diff --git a/templates/zerver/help/invite-new-users.md b/templates/zerver/help/invite-new-users.md
index 4c5cc48f7c..5680dc3458 100644
--- a/templates/zerver/help/invite-new-users.md
+++ b/templates/zerver/help/invite-new-users.md
@@ -12,7 +12,7 @@ the article below describes each in more detail.
* Share a **reusable invitation link**.
The last two, invite-based, techniques also allow you to control the
-[role (owner, admin, member, or guest)](/help/roles-and-permissions) that the
+[role (owner, admin, moderator, member, or guest)](/help/roles-and-permissions) that the
invited people will have.
You can also manage access by
@@ -78,7 +78,7 @@ and reusable invitation links expire 10 days after they are sent.
1. Enter a list of email addresses.
-1. Decide whether the users should join as [admins, members, or
+1. Decide whether the users should join as [admins, moderators, members, or
guests](/help/roles-and-permissions).
1. Select which streams they should join. If you send invitations often, you
@@ -100,7 +100,7 @@ and reusable invitation links expire 10 days after they are sent.
1. Click **Generate invite link**.
-1. Decide whether users using the link should join as [admins,
+1. Decide whether users using the link should join as [admins, moderators
members, or guests](/help/roles-and-permissions).
1. Select which streams they should join. If you send invitations often, you
diff --git a/version.py b/version.py
index ee9f9816f9..4c37560b7d 100644
--- a/version.py
+++ b/version.py
@@ -30,7 +30,7 @@ DESKTOP_WARNING_VERSION = "5.2.0"
#
# Changes should be accompanied by documentation explaining what the
# new level means in templates/zerver/api/changelog.md.
-API_FEATURE_LEVEL = 60
+API_FEATURE_LEVEL = 61
# Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump
diff --git a/zerver/models.py b/zerver/models.py
index bf260164ae..359fc0f7f2 100644
--- a/zerver/models.py
+++ b/zerver/models.py
@@ -1713,6 +1713,7 @@ class PreregistrationUser(models.Model):
INVITE_AS = dict(
REALM_OWNER=100,
REALM_ADMIN=200,
+ MODERATOR=300,
MEMBER=400,
GUEST_USER=600,
)
diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py
index f1c0e153de..115b4b46c1 100644
--- a/zerver/tests/test_signup.py
+++ b/zerver/tests/test_signup.py
@@ -1171,6 +1171,37 @@ class InviteUserTest(InviteUserBase):
)
self.assert_json_error(response, "Must be an organization administrator")
+ def test_successful_invite_user_as_moderator_from_admin_account(self) -> None:
+ self.login("iago")
+ invitee = self.nonreg_email("alice")
+ result = self.invite(
+ invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["MODERATOR"]
+ )
+ self.assert_json_success(result)
+ self.assertTrue(find_key_by_email(invitee))
+
+ self.submit_reg_form_for_user(invitee, "password")
+ invitee_profile = self.nonreg_user("alice")
+ self.assertFalse(invitee_profile.is_realm_admin)
+ self.assertTrue(invitee_profile.is_moderator)
+ self.assertFalse(invitee_profile.is_guest)
+
+ def test_invite_user_as_moderator_from_normal_account(self) -> None:
+ self.login("hamlet")
+ invitee = self.nonreg_email("alice")
+ response = self.invite(
+ invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["MODERATOR"]
+ )
+ self.assert_json_error(response, "Must be an organization administrator")
+
+ def test_invite_user_as_moderator_from_moderator_account(self) -> None:
+ self.login("shiva")
+ invitee = self.nonreg_email("alice")
+ response = self.invite(
+ invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["MODERATOR"]
+ )
+ self.assert_json_error(response, "Must be an organization administrator")
+
def test_invite_user_as_invalid_type(self) -> None:
"""
Test inviting a user as invalid type of user i.e. type of invite_as
diff --git a/zerver/views/invite.py b/zerver/views/invite.py
index f5b987c04b..e70a737e7c 100644
--- a/zerver/views/invite.py
+++ b/zerver/views/invite.py
@@ -47,7 +47,11 @@ def invite_users_backend(
return json_error(_("Must be invited as an valid type of user"))
check_if_owner_required(invite_as, user_profile)
if (
- invite_as == PreregistrationUser.INVITE_AS["REALM_ADMIN"]
+ invite_as
+ in [
+ PreregistrationUser.INVITE_AS["REALM_ADMIN"],
+ PreregistrationUser.INVITE_AS["MODERATOR"],
+ ]
and not user_profile.is_realm_admin
):
return json_error(_("Must be an organization administrator"))