[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:
Nick Funk
2019-06-13 17:24:50 +00:00
committed by Wyatt Johnson
parent 3947b143cb
commit 662f5ce314
14 changed files with 78 additions and 6 deletions
+9
View File
@@ -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({
+1
View File
@@ -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.
"""
+7
View File
@@ -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,
];
+16
View File
@@ -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");
+7
View File
@@ -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