mirror of
https://github.com/wassname/talk.git
synced 2026-07-01 01:27:39 +08:00
[CORL-409] Prevent users from ignoring staff members (#2355)
* Throw error if user tries to ignore a staff member Throws a UserCannotBeIgnoredError if a user tries to ignore a user who is a staff member. A staff member in this case is considered anyone who has a role of staff, moderator, or admin. CORL-409 * Prevent users from ignoring staff in the user info popover Creates the staff roles in a constant next to the user model. Uses this to add a computed property to the user resolver. CORL-409 * Remove unnecessary async declaration from userIsStaff helper function CORL-409 * Specify ignoreable on users in client test fixtures Allows the tests to pass for the required computed property of ignoreable that is computed by whether a user is a staff member or not. CORL-409 * Update more fixtures with ignoreable property on mocked users/commenters CORL-409 * Consolidate ignore-able calculation into re-usable helper methods Re-use the logic for whether a role is a staff member to clearly define when a user is ignore-able or not across the business logic. CORL-409 * Set the ignoreable optimisticResponse on comment mutations We have set the ignoreable value in the graphQL schema, so now the optimisticResponses are looking for a default value to use until the data result arrives. Put to false since the ignoreable value is set on our author, we likely don't want to ignore ourselves. CORL-409
This commit is contained in:
@@ -289,6 +289,7 @@ export const users = {
|
||||
username: "Markus",
|
||||
email: "markus@test.com",
|
||||
role: GQLUSER_ROLE.ADMIN,
|
||||
ignoreable: false,
|
||||
},
|
||||
],
|
||||
baseUser
|
||||
@@ -300,6 +301,7 @@ export const users = {
|
||||
username: "Lukas",
|
||||
email: "lukas@test.com",
|
||||
role: GQLUSER_ROLE.MODERATOR,
|
||||
ignoreable: false,
|
||||
},
|
||||
],
|
||||
baseUser
|
||||
@@ -311,6 +313,7 @@ export const users = {
|
||||
username: "Huy",
|
||||
email: "huy@test.com",
|
||||
role: GQLUSER_ROLE.STAFF,
|
||||
ignoreable: false,
|
||||
},
|
||||
],
|
||||
baseUser
|
||||
@@ -322,18 +325,21 @@ export const users = {
|
||||
username: "Isabelle",
|
||||
email: "isabelle@test.com",
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
ignoreable: true,
|
||||
},
|
||||
{
|
||||
id: "user-commenter-1",
|
||||
username: "Ngoc",
|
||||
email: "ngoc@test.com",
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
ignoreable: true,
|
||||
},
|
||||
{
|
||||
id: "user-commenter-2",
|
||||
username: "Max",
|
||||
email: "max@test.com",
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
ignoreable: true,
|
||||
},
|
||||
],
|
||||
baseUser
|
||||
@@ -344,6 +350,7 @@ export const users = {
|
||||
username: "Ingrid",
|
||||
email: "ingrid@test.com",
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
ignoreable: true,
|
||||
status: {
|
||||
current: [GQLUSER_STATUS.BANNED],
|
||||
ban: { active: true },
|
||||
|
||||
+1
@@ -178,6 +178,7 @@ function commit(
|
||||
id: viewer.id,
|
||||
username: viewer.username,
|
||||
createdAt: viewer.createdAt,
|
||||
ignoreable: false,
|
||||
},
|
||||
body: input.body,
|
||||
revision: {
|
||||
|
||||
+3
-1
@@ -30,7 +30,8 @@ export const UserPopoverOverviewContainer: FunctionComponent<Props> = ({
|
||||
const canIgnore =
|
||||
viewer &&
|
||||
viewer.id !== user.id &&
|
||||
viewer.ignoredUsers.every(u => u.id !== user.id);
|
||||
viewer.ignoredUsers.every(u => u.id !== user.id) &&
|
||||
user.ignoreable;
|
||||
return (
|
||||
<HorizontalGutter spacing={3} className={styles.root}>
|
||||
<HorizontalGutter spacing={2}>
|
||||
@@ -73,6 +74,7 @@ const enhanced = withFragmentContainer<Props>({
|
||||
id
|
||||
username
|
||||
createdAt
|
||||
ignoreable
|
||||
}
|
||||
`,
|
||||
})(UserPopoverOverviewContainer);
|
||||
|
||||
@@ -148,6 +148,7 @@ function commit(
|
||||
id: viewer.id,
|
||||
username: viewer.username,
|
||||
createdAt: viewer.createdAt,
|
||||
ignoreable: false,
|
||||
},
|
||||
revision: {
|
||||
id: uuidGenerator(),
|
||||
|
||||
@@ -104,21 +104,25 @@ export const commenters = createFixtures<GQLUser>(
|
||||
id: "user-0",
|
||||
username: "Markus",
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
ignoreable: true,
|
||||
},
|
||||
{
|
||||
id: "user-1",
|
||||
username: "Lukas",
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
ignoreable: true,
|
||||
},
|
||||
{
|
||||
id: "user-2",
|
||||
username: "Isabelle",
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
ignoreable: true,
|
||||
},
|
||||
{
|
||||
id: "user-3",
|
||||
username: "Markus",
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
ignoreable: true,
|
||||
},
|
||||
],
|
||||
baseUser
|
||||
@@ -354,6 +358,7 @@ export const moderators = createFixtures<GQLUser>(
|
||||
id: "me-as-moderator",
|
||||
username: "Moderator",
|
||||
role: GQLUSER_ROLE.MODERATOR,
|
||||
ignoreable: false,
|
||||
},
|
||||
],
|
||||
baseUser
|
||||
|
||||
@@ -231,6 +231,13 @@ export enum ERROR_CODES {
|
||||
*/
|
||||
USER_BANNED = "USER_BANNED",
|
||||
|
||||
/**
|
||||
* USER_CANNOT_BE_IGNORED is returned when the user attempts to ignore
|
||||
* a user that is not allowed to be ignored. This is usually because the
|
||||
* user is staff member.
|
||||
*/
|
||||
USER_CANNOT_BE_IGNORED = "USER_CANNOT_BE_IGNORED",
|
||||
|
||||
/**
|
||||
* INTEGRATION_DISABLED is returned when an operation is attempted against an
|
||||
* integration that has been disabled.
|
||||
|
||||
@@ -573,6 +573,15 @@ export class UserSuspended extends CoralError {
|
||||
}
|
||||
}
|
||||
|
||||
export class UserCannotBeIgnoredError extends CoralError {
|
||||
constructor(userID: string) {
|
||||
super({
|
||||
code: ERROR_CODES.USER_CANNOT_BE_IGNORED,
|
||||
context: { pub: { userID } },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class PasswordResetTokenExpired extends CoralError {
|
||||
constructor(reason: string, cause?: Error) {
|
||||
super({
|
||||
|
||||
@@ -27,6 +27,7 @@ export const ERROR_TRANSLATIONS: Record<ERROR_CODES, string> = {
|
||||
TOKEN_NOT_FOUND: "error-tokenNotFound",
|
||||
USER_NOT_ENTITLED: "error-userNotEntitled",
|
||||
USER_NOT_FOUND: "error-userNotFound",
|
||||
USER_CANNOT_BE_IGNORED: "error-userCannotBeIgnored",
|
||||
USERNAME_ALREADY_SET: "error-usernameAlreadySet",
|
||||
USERNAME_CONTAINS_INVALID_CHARACTERS:
|
||||
"error-usernameContainsInvalidCharacters",
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
GQLUserTypeResolver,
|
||||
} from "coral-server/graph/tenant/schema/__generated__/types";
|
||||
import * as user from "coral-server/models/user";
|
||||
import { roleIsStaff } from "coral-server/models/user/helpers";
|
||||
|
||||
import { UserStatusInput } from "./UserStatus";
|
||||
import { getRequestedFields } from "./util";
|
||||
@@ -41,4 +42,5 @@ export const User: GQLUserTypeResolver<user.User> = {
|
||||
}),
|
||||
ignoredUsers: ({ ignoredUsers }, input, ctx, info) =>
|
||||
maybeLoadOnlyIgnoredUserID(ctx, info, ignoredUsers),
|
||||
ignoreable: ({ role }) => !roleIsStaff(role),
|
||||
};
|
||||
|
||||
@@ -1456,6 +1456,13 @@ type User {
|
||||
permit: [SUSPENDED, BANNED]
|
||||
)
|
||||
|
||||
"""
|
||||
ignoreable is a computed property based on the
|
||||
user's role. Typically, users with elevated privileges
|
||||
aren't allowed to be ignored.
|
||||
"""
|
||||
ignoreable: Boolean!
|
||||
|
||||
"""
|
||||
comments are the comments written by the User.
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { GQLUSER_ROLE } from "coral-server/graph/tenant/schema/__generated__/types";
|
||||
|
||||
export const STAFF_ROLES = [
|
||||
GQLUSER_ROLE.ADMIN,
|
||||
GQLUSER_ROLE.MODERATOR,
|
||||
GQLUSER_ROLE.STAFF,
|
||||
];
|
||||
@@ -0,0 +1,16 @@
|
||||
import { GQLUSER_ROLE } from "coral-server/graph/tenant/schema/__generated__/types";
|
||||
import { STAFF_ROLES } from "coral-server/models/user/constants";
|
||||
|
||||
import { User } from ".";
|
||||
|
||||
export function roleIsStaff(role: GQLUSER_ROLE) {
|
||||
if (STAFF_ROLES.includes(role)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function userIsStaff(user: User) {
|
||||
return roleIsStaff(user.role);
|
||||
}
|
||||
@@ -25,17 +25,17 @@ import {
|
||||
} from "coral-server/graph/tenant/schema/__generated__/types";
|
||||
import { getLocalProfile, hasLocalProfile } from "coral-server/helpers/users";
|
||||
import logger from "coral-server/logger";
|
||||
import {
|
||||
Connection,
|
||||
ConnectionInput,
|
||||
resolveConnection,
|
||||
} from "coral-server/models/helpers/connection";
|
||||
import {
|
||||
createConnectionOrderVariants,
|
||||
createIndexFactory,
|
||||
} from "coral-server/models/helpers/indexing";
|
||||
import Query from "coral-server/models/helpers/query";
|
||||
import { TenantResource } from "coral-server/models/tenant";
|
||||
import {
|
||||
Connection,
|
||||
ConnectionInput,
|
||||
resolveConnection,
|
||||
} from "./helpers/connection";
|
||||
|
||||
function collection(mongo: Db) {
|
||||
return mongo.collection<Readonly<User>>("users");
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
TokenNotFoundError,
|
||||
UserAlreadyBannedError,
|
||||
UserAlreadySuspendedError,
|
||||
UserCannotBeIgnoredError,
|
||||
UsernameAlreadySetError,
|
||||
UserNotFoundError,
|
||||
} from "coral-server/errors";
|
||||
@@ -39,6 +40,7 @@ import {
|
||||
updateUserUsername,
|
||||
User,
|
||||
} from "coral-server/models/user";
|
||||
import { userIsStaff } from "coral-server/models/user/helpers";
|
||||
import { MailerQueue } from "coral-server/queue/tasks/mailer";
|
||||
import { JWTSigningConfig, signPATString } from "coral-server/services/jwt";
|
||||
|
||||
@@ -585,6 +587,11 @@ export async function ignore(
|
||||
throw new UserNotFoundError(userID);
|
||||
}
|
||||
|
||||
const userToBeIgnoredIsStaff = userIsStaff(targetUser);
|
||||
if (userToBeIgnoredIsStaff) {
|
||||
throw new UserCannotBeIgnoredError(userID);
|
||||
}
|
||||
|
||||
// TODO: extract function
|
||||
if (user.ignoredUsers && user.ignoredUsers.some(u => u.id === userID)) {
|
||||
// TODO: improve error
|
||||
|
||||
Reference in New Issue
Block a user