From f5fd162b371d8563982cdf233fd71126289d8a41 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 12 Mar 2018 15:39:50 -0600 Subject: [PATCH 1/9] moved user deletion into graph --- bin/cli-users | 95 +++++++------------------------- graph/mutators/user.js | 88 +++++++++++++++++++++++++++++ graph/resolvers/root_mutation.js | 3 + graph/typeDefs.graphql | 9 +++ perms/constants/mutation.js | 1 + 5 files changed, 121 insertions(+), 75 deletions(-) diff --git a/bin/cli-users b/bin/cli-users index ea308ae44..4255e9958 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -7,8 +7,6 @@ const util = require('./util'); const program = require('commander'); const inquirer = require('inquirer'); -const { graphql } = require('graphql'); -const helpers = require('../services/migration/helpers'); const { stripIndent } = require('common-tags'); const Table = require('cli-table'); @@ -21,31 +19,15 @@ inquirer.registerPrompt( require('inquirer-autocomplete-prompt') ); -const schema = require('../graph/schema'); const Context = require('../graph/context'); const UsersService = require('../services/users'); const UserModel = require('../models/user'); -const CommentModel = require('../models/comment'); -const ActionModel = require('../models/action'); const USER_ROLES = require('../models/enum/user_roles'); const mongoose = require('../services/mongoose'); // Register the shutdown criteria. util.onshutdown([() => mongoose.disconnect()]); -/** - * transforms a specific action to a removal action on the target model. - */ -const actionDecrTransformer = ({ item_id, action_type, group_id }) => ({ - query: { id: item_id }, - update: { - $inc: { - [`action_counts.${action_type.toLowerCase()}`]: -1, - [`action_counts.${action_type.toLowerCase()}_${group_id.toLowerCase()}`]: -1, - }, - }, -}); - /** * Deletes a user and cleans up their associated verifications. */ @@ -76,66 +58,29 @@ async function deleteUser(userID) { return util.shutdown(); } - const { transformSingleWithCursor } = helpers({ - queryBatchSize: 10000, - updateBatchSize: 10000, - }); + const ctx = Context.forSystem(); - console.warn("Removing user's actions"); - - // Remove all actions against comments. - await transformSingleWithCursor( - ActionModel.collection.find({ user_id: user.id, item_type: 'COMMENTS' }), - actionDecrTransformer, - CommentModel + const { data, errors } = await ctx.graphql( + ` + mutation DeleteUser($user_id: ID!) { + delUser(id: $user_id) { + errors { + translation_key + } + } + } + `, + { user_id: user.id } ); + if (errors) { + throw errors; + } - // Remove all actions against users. - await transformSingleWithCursor( - ActionModel.collection.find({ user_id: user.id, item_type: 'USERS' }), - actionDecrTransformer, - UserModel - ); + if (data.errors) { + throw data.errors; + } - // Remove all the user's actions. - await ActionModel.where({ user_id: user.id }) - .setOptions({ multi: true }) - .remove(); - - console.warn("Removing user's comments"); - - // Removes all the user's reply counts on each of the comments that they - // have commented on. - await transformSingleWithCursor( - CommentModel.collection.aggregate([ - { $match: { author_id: user.id } }, - { - $group: { - _id: '$parent_id', - count: { $sum: 1 }, - }, - }, - ]), - ({ _id: parent_id, count }) => ({ - query: { id: parent_id }, - update: { - $inc: { - reply_count: -1 * count, - }, - }, - }), - CommentModel - ); - - // Remove all the user's comments. - await CommentModel.where({ author_id: user.id }) - .setOptions({ multi: true }) - .remove(); - - console.warn('Removing the user'); - - // Remove the user. - await user.remove(); + console.log('User was deleted.'); util.shutdown(); } catch (err) { @@ -197,7 +142,7 @@ async function searchUsers() { value = ''; } - const { data, errors } = await graphql(schema, searchQuery, {}, ctx, { + const { data, errors } = await ctx.graphql(searchQuery, { value, }); if (errors && errors.length > 0) { diff --git a/graph/mutators/user.js b/graph/mutators/user.js index f8db7a19e..9d31e6dbd 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -1,5 +1,6 @@ const errors = require('../../errors'); const UsersService = require('../../services/users'); +const migrationHelpers = require('../../services/migration/helpers'); const { CHANGE_USERNAME, SET_USERNAME, @@ -7,6 +8,7 @@ const { SET_USER_BAN_STATUS, SET_USER_SUSPENSION_STATUS, UPDATE_USER_ROLES, + DELETE_USER, } = require('../../perms/constants'); const setUserUsernameStatus = async (ctx, id, status) => { @@ -70,6 +72,87 @@ const setRole = (ctx, id, role) => { return UsersService.setRole(id, role); }; +/** + * transforms a specific action to a removal action on the target model. + */ +const actionDecrTransformer = ({ item_id, action_type, group_id }) => ({ + query: { id: item_id }, + update: { + $inc: { + [`action_counts.${action_type.toLowerCase()}`]: -1, + [`action_counts.${action_type.toLowerCase()}_${group_id.toLowerCase()}`]: -1, + }, + }, +}); + +// delUser will delete a given user with the specified id. +const delUser = async (ctx, id) => { + const { connectors: { models: { User, Action, Comment } } } = ctx; + + // Find the user we're removing. + const user = await User.findOne({ id }); + if (!user) { + throw errors.ErrNotFound; + } + + // Get the query transformer we'll use to help batch process the user + // deletion. + const { transformSingleWithCursor } = migrationHelpers({ + queryBatchSize: 10000, + updateBatchSize: 10000, + }); + + // Remove all actions against comments. + await transformSingleWithCursor( + Action.collection.find({ user_id: user.id, item_type: 'COMMENTS' }), + actionDecrTransformer, + Comment + ); + + // Remove all actions against users. + await transformSingleWithCursor( + Action.collection.find({ user_id: user.id, item_type: 'USERS' }), + actionDecrTransformer, + User + ); + + // Remove all the user's actions. + await Action.where({ user_id: user.id }) + .setOptions({ multi: true }) + .remove(); + + // Removes all the user's reply counts on each of the comments that they + // have commented on. + await transformSingleWithCursor( + Comment.collection.aggregate([ + { $match: { author_id: user.id } }, + { + $group: { + _id: '$parent_id', + count: { $sum: 1 }, + }, + }, + ]), + ({ _id: parent_id, count }) => ({ + query: { id: parent_id }, + update: { + $inc: { + reply_count: -1 * count, + }, + }, + }), + Comment + ); + + // Remove all the user's comments. + await Comment.where({ author_id: user.id }) + .setOptions({ multi: true }) + .remove(); + + // Remove the user. + await user.remove(); +}; + module.exports = ctx => { let mutators = { User: { @@ -81,6 +164,7 @@ module.exports = ctx => { setUserUsernameStatus: () => Promise.reject(errors.ErrNotAuthorized), setUsername: () => Promise.reject(errors.ErrNotAuthorized), stopIgnoringUser: () => Promise.reject(errors.ErrNotAuthorized), + del: () => Promise.reject(errors.ErrNotAuthorized), }, }; @@ -116,6 +200,10 @@ module.exports = ctx => { mutators.User.setUserSuspensionStatus = (id, until, message) => setUserSuspensionStatus(ctx, id, until, message); } + + if (ctx.user.can(DELETE_USER)) { + mutators.User.del = id => delUser(ctx, id); + } } return mutators; diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index 3757bb3c4..2838f0f99 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -136,6 +136,9 @@ const RootMutation = { forceScrapeAsset: async (_, { id }, { mutators: { Asset } }) => { await Asset.scrape(id); }, + delUser: async (_, { id }, { mutators: { User } }) => { + await User.del(id); + }, }; module.exports = RootMutation; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index ea574ee6b..15b888ef1 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -1426,6 +1426,12 @@ type ForceScrapeAssetResponse implements Response { errors: [UserError!] } +type DelUserResponse implements Response { + + # An array of errors relating to the mutation that occurred. + errors: [UserError!] +} + # All mutations for the application are defined on this object. type RootMutation { @@ -1523,6 +1529,9 @@ type RootMutation { # forceScrapeAsset will force scrape the Asset with the given ID. forceScrapeAsset(id: ID!): ForceScrapeAssetResponse + + # delUser will delete the user with the specified id. + delUser(id: ID!): DelUserResponse } type UsernameChangedPayload { diff --git a/perms/constants/mutation.js b/perms/constants/mutation.js index 06e5b6f0f..d2ebe73f1 100644 --- a/perms/constants/mutation.js +++ b/perms/constants/mutation.js @@ -18,4 +18,5 @@ module.exports = { UPDATE_ASSET_SETTINGS: 'UPDATE_ASSET_SETTINGS', UPDATE_ASSET_STATUS: 'UPDATE_ASSET_STATUS', UPDATE_SETTINGS: 'UPDATE_SETTINGS', + DELETE_USER: 'DELETE_USER', }; From 93e850ba3fd42e0aa963d1b901e2a80ab01ee80a Mon Sep 17 00:00:00 2001 From: Olly Dutton Date: Tue, 13 Mar 2018 12:16:59 +1100 Subject: [PATCH 2/9] Update UserDetailComment.js Ternary to display asset url if there's no asset title in admin comments --- client/coral-admin/src/components/UserDetailComment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index ba185a9df..931b4bdeb 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -76,7 +76,7 @@ class UserDetailComment extends React.Component {
- Story: {comment.asset.title} + Story: {comment.asset.title ? comment.asset.title : comment.asset.url} { {t('modqueue.moderate')} From bfb9ea1eea8823ec310ee218db4c4136739309c5 Mon Sep 17 00:00:00 2001 From: Olly Dutton Date: Tue, 13 Mar 2018 12:18:21 +1100 Subject: [PATCH 3/9] Update Comment.js Ternary to display asset url if there's no asset title in admin comments --- client/coral-admin/src/routes/Moderation/components/Comment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index f865908b2..85e68e25a 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -122,7 +122,7 @@ class Comment extends React.Component {
- Story: {comment.asset.title} + Story: {comment.asset.title ? comment.asset.title : comment.asset.url} {!currentAsset && ( {t('modqueue.moderate')} From 8e0c53ef3b76bff2af2232a42722d9a461ef01b9 Mon Sep 17 00:00:00 2001 From: Olly Dutton Date: Tue, 13 Mar 2018 15:33:46 +1100 Subject: [PATCH 4/9] Update UserDetailComment.js fix linting error --- client/coral-admin/src/components/UserDetailComment.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index 931b4bdeb..2d39a7853 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -76,7 +76,8 @@ class UserDetailComment extends React.Component {
- Story: {comment.asset.title ? comment.asset.title : comment.asset.url} + Story:{' '} + {comment.asset.title ? comment.asset.title : comment.asset.url} { {t('modqueue.moderate')} From 6efda40e295fd0d0c8d3f40c99549057c4e64245 Mon Sep 17 00:00:00 2001 From: Olly Dutton Date: Tue, 13 Mar 2018 15:34:28 +1100 Subject: [PATCH 5/9] Update Comment.js fix linting error --- client/coral-admin/src/routes/Moderation/components/Comment.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index 85e68e25a..f3378a234 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -122,7 +122,8 @@ class Comment extends React.Component {
- Story: {comment.asset.title ? comment.asset.title : comment.asset.url} + Story:{' '} + {comment.asset.title ? comment.asset.title : comment.asset.url} {!currentAsset && ( {t('modqueue.moderate')} From bec3e94bbc5d1547f41119c6c243327f4ef71672 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 14 Mar 2018 11:02:37 -0600 Subject: [PATCH 6/9] Disallow logout from unauthenticated requests --- routes/api/v1/auth.js | 3 ++- services/passport.js | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/routes/api/v1/auth.js b/routes/api/v1/auth.js index 369f4278a..d8e0544a9 100644 --- a/routes/api/v1/auth.js +++ b/routes/api/v1/auth.js @@ -4,6 +4,7 @@ const { HandleGenerateCredentials, HandleLogout, } = require('../../../services/passport'); +const authz = require('../../../middleware/authorization'); const router = express.Router(); /** @@ -26,7 +27,7 @@ router.get('/', (req, res, next) => { /** * This blacklists the token used to authenticate. */ -router.delete('/', HandleLogout); +router.delete('/', authz.needed(), HandleLogout); //============================================================================== // PASSPORT ROUTES diff --git a/services/passport.js b/services/passport.js index 59215dd54..5748c911b 100644 --- a/services/passport.js +++ b/services/passport.js @@ -184,24 +184,24 @@ async function ValidateUserLogin(loginProfile, user, done) { * Revoke the token on the request. */ const HandleLogout = async (req, res, next) => { - const { jwt } = req; - - const now = new Date(); - const expiry = (jwt.exp - now.getTime() / 1000).toFixed(0); - try { + const { jwt } = req; + + const now = new Date(); + const expiry = (jwt.exp - now.getTime() / 1000).toFixed(0); + await client().set(`jtir[${jwt.jti}]`, now.toISOString(), 'EX', expiry); + + // Only clear the cookie on logout if enabled. + if (JWT_CLEAR_COOKIE_LOGOUT) { + debug('clearing the login cookie'); + res.clearCookie(JWT_SIGNING_COOKIE_NAME); + } + + res.status(204).end(); } catch (err) { return next(err); } - - // Only clear the cookie on logout if enabled. - if (JWT_CLEAR_COOKIE_LOGOUT) { - debug('clearing the login cookie'); - res.clearCookie(JWT_SIGNING_COOKIE_NAME); - } - - res.status(204).end(); }; const checkGeneralTokenBlacklist = jwt => From cbd3b2814ed57efee68ba4809f8425dd144bb20a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 14 Mar 2018 11:23:40 -0600 Subject: [PATCH 7/9] added .vscode to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 811e2ea2b..7c0007dc9 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ client/coral-framework/graphql/introspection.json *.swp *.DS_STORE .prettierrc.json +.vscode coverage/ test/e2e/reports/ From bb1b265cae1bd8383c8f989a80e285c8689b5779 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 14 Mar 2018 15:12:38 -0600 Subject: [PATCH 8/9] lint --- client/coral-admin/src/components/UserDetailComment.js | 2 +- client/coral-admin/src/routes/Moderation/components/Comment.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index 2d39a7853..a14aecf9f 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -76,7 +76,7 @@ class UserDetailComment extends React.Component {
- Story:{' '} + Story:{' '} {comment.asset.title ? comment.asset.title : comment.asset.url} { diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index f3378a234..637772c0f 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -122,7 +122,7 @@ class Comment extends React.Component {
- Story:{' '} + Story:{' '} {comment.asset.title ? comment.asset.title : comment.asset.url} {!currentAsset && ( From 8c7e8080c304a8a1679b21d95adb06ebc6e61601 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 14 Mar 2018 15:13:44 -0600 Subject: [PATCH 9/9] added translations --- client/coral-admin/src/components/UserDetailComment.js | 3 ++- client/coral-admin/src/routes/Moderation/components/Comment.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index a14aecf9f..206242058 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -76,7 +76,7 @@ class UserDetailComment extends React.Component {
- Story:{' '} + {t('common.story')}:{' '} {comment.asset.title ? comment.asset.title : comment.asset.url} { @@ -110,6 +110,7 @@ class UserDetailComment extends React.Component {
+ {/* TODO: translate string */} Contains Link diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index 637772c0f..efdcfbd79 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -122,7 +122,7 @@ class Comment extends React.Component {
- Story:{' '} + {t('common.story')}:{' '} {comment.asset.title ? comment.asset.title : comment.asset.url} {!currentAsset && ( @@ -157,6 +157,7 @@ class Comment extends React.Component {
+ {/* TODO: translate string */} Contains Link