diff --git a/backend/clubs/management/commands/osa_perms_updates.py b/backend/clubs/management/commands/osa_perms_updates.py
new file mode 100644
index 000000000..d54d3e802
--- /dev/null
+++ b/backend/clubs/management/commands/osa_perms_updates.py
@@ -0,0 +1,36 @@
+from django.conf import settings
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group, Permission
+from django.contrib.contenttypes.models import ContentType
+from django.core.management.base import BaseCommand
+
+from clubs.models import Club
+
+
+class Command(BaseCommand):
+ help = "Give superuser to hard-coded user accounts affiliated with OSA."
+ web_execute = True
+
+ def handle(self, *args, **kwargs):
+ User = get_user_model()
+ content_type = ContentType.objects.get_for_model(Club)
+ approve_perm = Permission.objects.get(
+ codename="approve_club", content_type=content_type
+ )
+ pending_perm = Permission.objects.get(
+ codename="see_pending_clubs", content_type=content_type
+ )
+ if not settings.OSA_KEYS:
+ raise ValueError("OSA_KEYS not set in settings")
+ if not (approvers := Group.objects.filter(name="Approvers").first()):
+ raise ValueError("Approvers group not found")
+ for key in settings.OSA_KEYS:
+ if not key or not (user := User.objects.get(username=key)):
+ continue
+ user.is_superuser = True
+ user.is_staff = True
+ user.user_permissions.add(approve_perm)
+ user.user_permissions.add(pending_perm)
+ approvers.user_set.add(user)
+ user.save()
+ approvers.save()
diff --git a/backend/pennclubs/settings/base.py b/backend/pennclubs/settings/base.py
index 4c645ea94..d74fa029a 100644
--- a/backend/pennclubs/settings/base.py
+++ b/backend/pennclubs/settings/base.py
@@ -265,3 +265,5 @@
# Cybersource settings
CYBERSOURCE_CLIENT_VERSION = "0.15"
+
+OSA_KEYS = None
diff --git a/backend/pennclubs/settings/development.py b/backend/pennclubs/settings/development.py
index 90c2d1504..720e07a23 100644
--- a/backend/pennclubs/settings/development.py
+++ b/backend/pennclubs/settings/development.py
@@ -42,3 +42,5 @@
"run_environment": "apitest.cybersource.com",
}
CYBERSOURCE_TARGET_ORIGIN = "https://localhost:3001"
+
+OSA_KEYS = ["gwashington"]
diff --git a/backend/pennclubs/settings/production.py b/backend/pennclubs/settings/production.py
index bea022416..ed445fe2b 100644
--- a/backend/pennclubs/settings/production.py
+++ b/backend/pennclubs/settings/production.py
@@ -89,3 +89,5 @@
"run_environment": "api.cybersource.com",
}
CYBERSOURCE_TARGET_ORIGIN = "https://pennclubs.com"
+
+OSA_KEYS = os.getenv("OSA_KEYS", "").split(",")
diff --git a/backend/tests/clubs/test_commands.py b/backend/tests/clubs/test_commands.py
index 48e3799b3..90ceefda1 100644
--- a/backend/tests/clubs/test_commands.py
+++ b/backend/tests/clubs/test_commands.py
@@ -777,3 +777,31 @@ def test_graduate_users_output(self):
"Updated the membership status of 1 student club relationships!",
out.getvalue(),
)
+
+
+class OsaPermsUpdatesTestCase(TestCase):
+ def setUp(self):
+ self.user1 = get_user_model().objects.create_user("gwashington")
+
+ def test_osa_perms_updates(self):
+ # Test error when OSA_KEYS is not set
+ with mock.patch("django.conf.settings.OSA_KEYS", None):
+ with self.assertRaises(ValueError):
+ call_command("osa_perms_updates")
+ self.assertFalse(self.user1.is_superuser)
+
+ with mock.patch("django.conf.settings.OSA_KEYS", ["gwashington"]):
+ # Test error when Approvers group is not found
+ with self.assertRaises(ValueError):
+ call_command("osa_perms_updates")
+ self.assertFalse(self.user1.is_superuser)
+
+ # Create Approvers group
+ Group.objects.create(name="Approvers")
+ call_command("osa_perms_updates")
+ self.user1.refresh_from_db()
+ self.assertTrue(self.user1.groups.filter(name="Approvers").exists())
+ self.assertTrue(self.user1.is_staff)
+ self.assertTrue(self.user1.is_superuser)
+ self.assertTrue(self.user1.has_perm("approve_club"))
+ self.assertTrue(self.user1.has_perm("see_pending_clubs"))
diff --git a/frontend/components/ClubEditPage/ClubEditCard.tsx b/frontend/components/ClubEditPage/ClubEditCard.tsx
index 679a310ba..58e380d88 100644
--- a/frontend/components/ClubEditPage/ClubEditCard.tsx
+++ b/frontend/components/ClubEditPage/ClubEditCard.tsx
@@ -151,6 +151,49 @@ const Card = ({
)
}
+interface EmailModalProps {
+ closeModal: () => void
+ email: string
+ setEmail: (inp: string) => void
+ confirmSubmission: () => void
+}
+
+const EmailModal = ({
+ closeModal,
+ email,
+ setEmail,
+ confirmSubmission,
+}: EmailModalProps): ReactElement => {
+ return (
+
+
+
+ This email will be visible to the public.
+
+ We recommend that you don’t use a personal email, and instead use a
+ club email.
+
+ setEmail(e.target.value)}
+ className="input mb-5"
+ style={{ maxWidth: '350px' }}
+ >
+