Merge branch 'master' into gdpr-password

This commit is contained in:
Wyatt Johnson
2018-04-18 10:28:17 -06:00
43 changed files with 1827 additions and 1498 deletions
+4 -125
View File
@@ -1,126 +1,5 @@
const debug = require('debug')('talk:plugin:akismet');
const { ErrSpam } = require('./errors');
const akismet = require('akismet-api');
const { get, merge } = require('lodash');
const { KEY, SITE } = require('./config');
const client = akismet.client({
key: KEY,
blog: SITE,
});
const typeDefs = require('./server/typeDefs');
const hooks = require('./server/hooks');
const resolvers = require('./server/resolvers');
let enabled = true;
// TODO: when using a developer key, this is possible, the plus plan does not
// allow us to check the key.
// let enabled = false;
// client.verifyKey((err, valid) => {
// if (err) {
// throw err;
// }
// if (valid) {
// enabled = true;
// } else {
// throw new Error('Akismet key is invalid');
// }
// });
module.exports = {
typeDefs: `
input CreateCommentInput {
# If true, the mutation will fail when the
# body contains detected spam.
checkSpam: Boolean
}
type Comment {
spam: Boolean
}
`,
hooks: {
RootMutation: {
createComment: {
async pre(_, { input }, { loaders, parent: req }) {
// If the key validation failed, then we can't run with the client.
if (!enabled) {
debug('not enabled, passing');
return;
}
let spam = false;
try {
const user_ip = get(req, 'ip', false);
if (!user_ip) {
debug('no ip on request');
return;
}
// Get some headers from the request.
const user_agent = req.get('User-Agent');
if (!user_agent || user_agent.length === 0) {
debug('no user agent on request');
return;
}
const referrer = req.get('Referrer');
if (!referrer || referrer.length === 0) {
debug('no referrer on request');
return;
}
// Get the Asset that the comment is being made against.
const asset = await loaders.Assets.getByID.load(input.asset_id);
if (!asset) {
debug('asset not found for new comment');
return;
}
// Send off the comment to Akismet to check to see what they say.
spam = await client.checkSpam({
user_ip,
user_agent,
referrer,
permalink: asset.url,
comment_type: 'comment',
comment_content: input.body,
is_test: true,
});
debug(`comment analyzed as ${spam ? 'being' : 'not being'} spam`);
} catch (err) {
console.trace(err);
return;
}
// Attach scores to metadata.
input.metadata = merge({}, input.metadata || {}, {
akismet: spam,
});
if (spam) {
if (input.checkSpam) {
throw new ErrSpam();
}
// Attach reason information for the flag being added.
input.status = 'SYSTEM_WITHHELD';
input.actions =
input.actions && input.actions.length >= 0 ? input.actions : [];
input.actions.push({
action_type: 'FLAG',
user_id: null,
group_id: 'SPAM_COMMENT',
metadata: {},
});
}
},
},
},
},
resolvers: {
Comment: {
spam: comment => get(comment, 'metadata.akismet', null),
},
},
};
module.exports = { typeDefs, hooks, resolvers };
+107
View File
@@ -0,0 +1,107 @@
const debug = require('debug')('talk:plugin:akismet');
const { ErrSpam } = require('./errors');
const akismet = require('akismet-api');
const { get, merge } = require('lodash');
const { KEY, SITE } = require('./config');
const client = akismet.client({
key: KEY,
blog: SITE,
});
let enabled = true;
// TODO: when using a developer key, this is possible, the plus plan does not
// allow us to check the key.
// let enabled = false;
// client.verifyKey((err, valid) => {
// if (err) {
// throw err;
// }
// if (valid) {
// enabled = true;
// } else {
// throw new Error('Akismet key is invalid');
// }
// });
module.exports = {
RootMutation: {
createComment: {
async pre(_, { input }, { loaders, parent: req }) {
// If the key validation failed, then we can't run with the client.
if (!enabled) {
debug('not enabled, passing');
return;
}
let spam = false;
try {
const user_ip = get(req, 'ip', false);
if (!user_ip) {
debug('no ip on request');
return;
}
// Get some headers from the request.
const user_agent = req.get('User-Agent');
if (!user_agent || user_agent.length === 0) {
debug('no user agent on request');
return;
}
const referrer = req.get('Referrer');
if (!referrer || referrer.length === 0) {
debug('no referrer on request');
return;
}
// Get the Asset that the comment is being made against.
const asset = await loaders.Assets.getByID.load(input.asset_id);
if (!asset) {
debug('asset not found for new comment');
return;
}
// Send off the comment to Akismet to check to see what they say.
spam = await client.checkSpam({
user_ip,
user_agent,
referrer,
permalink: asset.url,
comment_type: 'comment',
comment_content: input.body,
is_test: true,
});
debug(`comment analyzed as ${spam ? 'being' : 'not being'} spam`);
} catch (err) {
console.trace(err);
return;
}
// Attach scores to metadata.
input.metadata = merge({}, input.metadata || {}, {
akismet: spam,
});
if (spam) {
if (input.checkSpam) {
throw new ErrSpam();
}
// Attach reason information for the flag being added.
input.status = 'SYSTEM_WITHHELD';
input.actions =
input.actions && input.actions.length >= 0 ? input.actions : [];
input.actions.push({
action_type: 'FLAG',
user_id: null,
group_id: 'SPAM_COMMENT',
metadata: {},
});
}
},
},
},
};
@@ -0,0 +1,7 @@
const { get } = require('lodash');
module.exports = {
Comment: {
spam: comment => get(comment, 'metadata.akismet', null),
},
};
@@ -0,0 +1,14 @@
const resolvers = require('./resolvers');
describe('talk-plugin-akismet', () => {
describe('resolvers', () => {
it('resolves when there is a akismet value', () => {
const spam = resolvers.Comment.spam({ metadata: { akismet: true } });
expect(spam).toEqual(true);
});
it('resolves when there not is a akismet value', () => {
const spam = resolvers.Comment.spam({});
expect(spam).toEqual(null);
});
});
});
@@ -0,0 +1,10 @@
input CreateCommentInput {
# If true, the mutation will fail when the
# body contains detected spam.
checkSpam: Boolean
}
type Comment {
spam: Boolean
}
@@ -0,0 +1,7 @@
const fs = require('fs');
const path = require('path');
module.exports = fs.readFileSync(
path.join(__dirname, 'typeDefs.graphql'),
'utf8'
);
@@ -0,0 +1,11 @@
let values = {};
const getScores = () => values.getScores;
const isToxic = () => values.isToxic;
const setValues = newValues => {
values = newValues;
};
module.exports = { getScores, isToxic, setValues };
@@ -1,11 +1,6 @@
const { getScores, isToxic } = require('./perspective');
const { ErrToxic } = require('./errors');
// We don't add the hooks during _test_ as the perspective API is not available.
if (process.env.NODE_ENV === 'test') {
return null;
}
module.exports = {
RootMutation: {
createComment: {
@@ -16,7 +11,7 @@ module.exports = {
scores = await getScores(input.body);
} catch (err) {
// Warn and let mutation pass.
console.trace(err);
console.trace(err); // TODO: log/handle this differently?
return;
}
@@ -0,0 +1,31 @@
const hooks = require('./hooks');
const { ErrToxic } = require('./errors');
// Mock out the perspective api call.
jest.mock('./perspective');
describe('talk-plugin-toxic-comments', () => {
describe('hooks', () => {
beforeEach(() => {
require('./perspective').setValues({ isToxic: true });
});
it('sets the correct values for a toxic comment', async () => {
let input = { body: 'This is a body.', checkToxicity: false };
await hooks.RootMutation.createComment.pre(null, { input }, null, null);
expect(input).toHaveProperty('status', 'SYSTEM_WITHHELD');
});
it('throws an error when a toxic comment is sent', async () => {
expect.assertions(1);
await expect(
hooks.RootMutation.createComment.pre(
null,
{ input: { checkToxicity: true } },
null,
null
)
).rejects.toBeInstanceOf(ErrToxic);
});
});
});