Skip to content

Commit

Permalink
Merge branch 'sabrecat/report-profile' into delta
Browse files Browse the repository at this point in the history
  • Loading branch information
SabreCat committed Jul 10, 2023
2 parents bdb26df + 55848c5 commit 91777d8
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 0 deletions.
36 changes: 36 additions & 0 deletions website/server/controllers/api-v3/members.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
} from '../../models/message';
import highlightMentions from '../../libs/highlightMentions';
import { handleGetMembersForChallenge } from '../../libs/challenges/handleGetMembersForChallenge';
import { chatReporterFactory } from '../../libs/chatReporting/chatReporterFactory';

const { achievements } = common;

Expand Down Expand Up @@ -777,4 +778,39 @@ api.transferGems = {
},
};

/**
* @api {post} /api/v3/members/:memberId/flag Flag (report) a user
* @apiDescription Sends an email to staff about another user or their profile
* @apiName FlagUser
* @apiGroup Members
*
* @apiParam (Path) {UUID} memberId The unique ID of the user being flagged
* @apiParam (Body) {String} [comment] explain why the user was flagged
*
* @apiSuccess {Object} data The flagged user
* @apiSuccess {UUID} data.id The id of the flagged user
* @apiSuccess {String} data.username The username of the flagged user
* @apiSuccess {Object} data.profile The flagged user's profile information
* @apiSuccess {String} data.profile.blurb Text of the flagged user's profile bio
* @apiSuccess {String} data.profile.imageUrl URL of the flagged user's profile image
* @apiSuccess {String} data.profile.name The flagged user's display name
*
* @apiError (400) {BadRequest} AlreadyFlagged A profile cannot be flagged
* more than once by the same user.
* @apiError (400) {BadRequest} MemberIdRequired The `memberId` param is required
* and must be a valid `UUID`.
* @apiError (404) {NotFound} UserWithIdNotFound The `memberId` param did not
* belong to an existing user.
*/
api.flagUser = {
method: 'POST',
url: '/members/:memberId/flag',
middlewares: [authWithHeaders()],
async handler (req, res) {
const chatReporter = chatReporterFactory('User', req, res);
const flaggedUser = await chatReporter.flag();
res.respond(200, flaggedUser);
},
};

export default api;
3 changes: 3 additions & 0 deletions website/server/libs/chatReporting/chatReporterFactory.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import GroupChatReporter from './groupChatReporter';
import InboxChatReporter from './inboxChatReporter';
import ProfileReporter from './profileReporter';

export function chatReporterFactory (type, req, res) { // eslint-disable-line import/prefer-default-export, max-len
if (type === 'Group') {
return new GroupChatReporter(req, res);
} if (type === 'Inbox') {
return new InboxChatReporter(req, res);
} if (type === 'Profile' || type === 'User') {
return new ProfileReporter(req, res);
}

throw new Error('Invalid chat reporter type.');
Expand Down
103 changes: 103 additions & 0 deletions website/server/libs/chatReporting/profileReporter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import nconf from 'nconf';
import { model as User } from '../../models/user';
import { getUserInfo, sendTxn } from '../email';
import * as slack from '../slack';

import ChatReporter from './chatReporter';
import {
BadRequest,
NotFound,
} from '../errors';

const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL')
.split(',')
.map(email => ({ email, canSend: true }));

export default class ProfileReporter extends ChatReporter {
constructor (req, res) {
super(req, res);

this.user = res.locals.user;
}

async validate () {
this.req.checkParams('memberId', apiError('memberIdRequired')).notEmpty();

Check failure on line 24 in website/server/libs/chatReporting/profileReporter.js

View workflow job for this annotation

GitHub Actions / lint (14.x)

'apiError' is not defined

const validationErrors = this.req.validationErrors();
if (validationErrors) throw validationErrors;

const flaggedUser = await User.findOne(
{ _id: this.req.query.memberId },
{ auth: 1, profile: 1 },
).exec();
if (!flaggedUser) {
throw new NotFound(this.res.t('userWithIDNotFound'));
}

if (flaggedUser.profile.flags && flaggedUser.profile.flags.indexOf(this.user._id) !== -1
&& !this.user.hasPermission('moderator')) {
throw new BadRequest('A profile can not be flagged more than once by the same user.');
}

const userComment = this.req.body.comment;

return { flaggedUser, userComment };
}

getEmailVariables (flaggedUser) {
const reportingUserData = {
user: this.user.profile.name,
username: this.user.auth.local.username,
uuid: this.user._id,
email: getUserInfo(this.user, ['email']).email,
};

const flaggedUserData = {
user: flaggedUser.profile.name,
username: flaggedUser.auth.local.username,
uuid: flaggedUser._id,
email: getUserInfo(flaggedUser, ['email']).email,
};

return [
...this.createGenericAuthorVariables('REPORTER', reportingUserData),
...this.createGenericAuthorVariables('FLAGGED', flaggedUserData),
];
}

flagProfile (flaggedUser) {
// Log user ids that have flagged the account
if (!flaggedUser.profile.flags) {
flaggedUser.profile.flags = [];
}

flaggedUser.profile.flags.push(this.user._id);

return flaggedUser.save();
}

async notify (flaggedUser, userComment) {
let emailVariables = await this.getEmailVariables(flaggedUser);
emailVariables = emailVariables.concat([
{ name: 'REPORTER_COMMENT', content: userComment || '' },
]);

sendTxn(FLAG_REPORT_EMAILS, 'profile-report-to-mods-with-comments', emailVariables);

slack.sendProfileFlagNotification({
reporter: this.user,
flaggedUser,
userComment,
});
}

async flag () {
const { flaggedUser, userComment } = await this.validate();
await this.flagProfile(flaggedUser);
await this.notify(flaggedUser, userComment);
if (!this.user.hasPermission('moderator')) {
flaggedUser.profile.flags = [this.user._id];
}
return flaggedUser;
}
}

Check failure on line 103 in website/server/libs/chatReporting/profileReporter.js

View workflow job for this annotation

GitHub Actions / lint (14.x)

Newline required at end of file but not found
34 changes: 34 additions & 0 deletions website/server/libs/slack.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,39 @@ function sendChallengeFlagNotification ({
});
}

function sendProfileFlagNotification ({
reporter,
flaggedUser,
userComment,
}) {
const title = 'User Profile Report';
const titleLink = `${BASE_URL}/static/front/#?memberId=${flaggedUser._id}`;
const text = `@${reporter.auth.local.username} (${reporter._id}; language: ${reporter.preferences.language}) flagged @${flaggedUser.auth.local.username}'s profile`;
if (userComment) {
text += ` and commented: ${userComment}`;

Check failure on line 223 in website/server/libs/slack.js

View workflow job for this annotation

GitHub Actions / lint (14.x)

'text' is constant
}
const profileData = `Bio: ${flaggedUser.profile.blurb}`;
if (flaggedUser.profile.imageUrl) {
profileData += `\n\nImage URL: ${flaggedUser.profile.imageUrl}`;

Check failure on line 227 in website/server/libs/slack.js

View workflow job for this annotation

GitHub Actions / lint (14.x)

'profileData' is constant
}

flagSlack
.send({
text,
attachments: [{
fallback: 'Flag Profile',
color: 'danger',
title,
title_link: titleLink,
text: profileData,
mrkdwn_in: [
'text',
],
}],
})
.catch(err => logger.error(err, 'Error while sending flag data to Slack.'));
}

function sendSubscriptionNotification ({
buyer,
recipient,
Expand Down Expand Up @@ -338,6 +371,7 @@ export {
sendFlagNotification,
sendInboxFlagNotification,
sendChallengeFlagNotification,
sendProfileFlagNotification,
sendSubscriptionNotification,
sendShadowMutedPostNotification,
sendSlurNotification,
Expand Down
3 changes: 3 additions & 0 deletions website/server/models/user/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,9 @@ export const UserSchema = new Schema({
required: true,
trim: true,
},
flags: [
{ $type: String, validate: [v => validator.isUUID(v), 'Invalid user UUID.'], ref: 'User' },
],
},
stats: {
hp: { $type: Number, default: shared.maxHealth },
Expand Down

0 comments on commit 91777d8

Please sign in to comment.