diff --git a/app/scenes/Settings/components/UserStatusFilter.tsx b/app/scenes/Settings/components/UserStatusFilter.tsx index 6a22350a1..f4dfce773 100644 --- a/app/scenes/Settings/components/UserStatusFilter.tsx +++ b/app/scenes/Settings/components/UserStatusFilter.tsx @@ -1,6 +1,9 @@ +import { compact } from "lodash"; +import { observer } from "mobx-react"; import * as React from "react"; import { useTranslation } from "react-i18next"; import FilterOptions from "~/components/FilterOptions"; +import useCurrentUser from "~/hooks/useCurrentUser"; type Props = { activeKey: string; @@ -9,34 +12,41 @@ type Props = { const UserStatusFilter = ({ activeKey, onSelect, ...rest }: Props) => { const { t } = useTranslation(); + const user = useCurrentUser(); + const options = React.useMemo( - () => [ - { - key: "", - label: t("Active"), - }, - { - key: "all", - label: t("Everyone"), - }, - { - key: "admins", - label: t("Admins"), - }, - { - key: "suspended", - label: t("Suspended"), - }, - { - key: "invited", - label: t("Invited"), - }, - { - key: "viewers", - label: t("Viewers"), - }, - ], - [t] + () => + compact([ + { + key: "", + label: t("Active"), + }, + { + key: "all", + label: t("Everyone"), + }, + { + key: "admins", + label: t("Admins"), + }, + ...(user.isAdmin + ? [ + { + key: "suspended", + label: t("Suspended"), + }, + ] + : []), + { + key: "invited", + label: t("Invited"), + }, + { + key: "viewers", + label: t("Viewers"), + }, + ]), + [t, user.isAdmin] ); return ( @@ -50,4 +60,4 @@ const UserStatusFilter = ({ activeKey, onSelect, ...rest }: Props) => { ); }; -export default UserStatusFilter; +export default observer(UserStatusFilter); diff --git a/server/routes/api/users.test.ts b/server/routes/api/users.test.ts index 28b954aca..1a2356a0f 100644 --- a/server/routes/api/users.test.ts +++ b/server/routes/api/users.test.ts @@ -39,9 +39,26 @@ describe("#users.list", () => { }); it("should allow filtering to suspended users", async () => { - const user = await buildUser({ + const admin = await buildAdmin(); + await buildUser({ name: "Tester", + teamId: admin.teamId, + suspendedAt: new Date(), }); + const res = await server.post("/api/users.list", { + body: { + query: "test", + filter: "suspended", + token: admin.getJwtToken(), + }, + }); + const body = await res.json(); + expect(res.status).toEqual(200); + expect(body.data.length).toEqual(1); + }); + + it("should not allow members to view suspended users", async () => { + const user = await buildUser(); await buildUser({ name: "Tester", teamId: user.teamId, @@ -50,13 +67,12 @@ describe("#users.list", () => { const res = await server.post("/api/users.list", { body: { query: "test", - filter: "suspended", token: user.getJwtToken(), }, }); const body = await res.json(); expect(res.status).toEqual(200); - expect(body.data.length).toEqual(1); + expect(body.data.length).toEqual(0); }); it("should allow filtering to invited", async () => { diff --git a/server/routes/api/users.ts b/server/routes/api/users.ts index e243081ab..3274cedb9 100644 --- a/server/routes/api/users.ts +++ b/server/routes/api/users.ts @@ -44,6 +44,16 @@ router.post("users.list", auth(), pagination(), async (ctx) => { teamId: actor.teamId, }; + // Filter out suspended users if we're not an admin + if (!actor.isAdmin) { + where = { + ...where, + suspendedAt: { + [Op.eq]: null, + }, + }; + } + switch (filter) { case "invited": { where = { ...where, lastActiveAt: null }; @@ -61,12 +71,14 @@ router.post("users.list", auth(), pagination(), async (ctx) => { } case "suspended": { - where = { - ...where, - suspendedAt: { - [Op.ne]: null, - }, - }; + if (actor.isAdmin) { + where = { + ...where, + suspendedAt: { + [Op.ne]: null, + }, + }; + } break; }