From 4fcf784274880895d1dbc8c8a08cd93393750526 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Tue, 29 Aug 2023 14:57:37 -0600 Subject: [PATCH 01/21] premod users based on custom email rules if EMAIL_PREMOD_FILTER is enabled --- src/core/server/graph/schema/schema.graphql | 6 +++ .../services/users/emailPremodFilter.ts | 47 +++++++++++++++++++ src/core/server/services/users/users.ts | 24 ++++++++-- 3 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 src/core/server/services/users/emailPremodFilter.ts diff --git a/src/core/server/graph/schema/schema.graphql b/src/core/server/graph/schema/schema.graphql index 23cd467c9a..2b2c40399d 100644 --- a/src/core/server/graph/schema/schema.graphql +++ b/src/core/server/graph/schema/schema.graphql @@ -567,6 +567,12 @@ enum FEATURE_FLAG { users, and commentActions more quickly. It is disabled by default. """ DATA_CACHE + + """ + EMAIL_PREMOD_FILTER enables the feature to premoderate accounts whose emails + match patterns that are common with spam accounts. + """ + EMAIL_PREMOD_FILTER } # The moderation mode of the site. diff --git a/src/core/server/services/users/emailPremodFilter.ts b/src/core/server/services/users/emailPremodFilter.ts new file mode 100644 index 0000000000..e93d41caff --- /dev/null +++ b/src/core/server/services/users/emailPremodFilter.ts @@ -0,0 +1,47 @@ +import { hasFeatureFlag, Tenant } from "coral-server/models/tenant"; + +import { GQLFEATURE_FLAG } from "coral-server/graph/schema/__generated__/types"; +import { User } from "coral-server/models/user"; + +const EMAIL_PREMOD_FILTER_PERIOD_LIMIT = 3; + +const emailHasTooManyPeriods = (email: string | undefined, limit: number) => { + if (!email) { + return false; + } + + const split = email.split("@"); + if (split.length !== 2) { + return false; + } + + const firstHalf = split[0]; + + let periodCount = 0; + for (const char of firstHalf) { + if (char === ".") { + periodCount++; + } + } + + return periodCount >= limit; +}; + +export const shouldPremodDueToLikelySpamEmail = ( + tenant: Readonly, + user: Readonly +) => { + // don't premod check unless the filter is enabled + if (!hasFeatureFlag(tenant, GQLFEATURE_FLAG.EMAIL_PREMOD_FILTER)) { + return false; + } + + // this is an array to allow for adding more rules in the + // future as we play whack-a-mole trying to block spammers + // and other trouble makers + const results = [ + emailHasTooManyPeriods(user.email, EMAIL_PREMOD_FILTER_PERIOD_LIMIT), + ]; + + return results.some((v) => v === true); +}; diff --git a/src/core/server/services/users/users.ts b/src/core/server/services/users/users.ts index f0bcb62b3e..a3d90f4a01 100644 --- a/src/core/server/services/users/users.ts +++ b/src/core/server/services/users/users.ts @@ -122,6 +122,7 @@ import { generateAdminDownloadLink, generateDownloadLink, } from "./download/token"; +import { shouldPremodDueToLikelySpamEmail } from "./emailPremodFilter"; import { checkForNewUserEmailDomainModeration, validateEmail, @@ -162,6 +163,7 @@ export interface FindOrCreateUserOptions { export async function findOrCreate( config: Config, mongo: MongoContext, + cache: DataCache, tenant: Tenant, input: FindOrCreateUser, options: FindOrCreateUserOptions, @@ -172,7 +174,11 @@ export async function findOrCreate( try { // Try to find or create the user. - const { user } = await findOrCreateUser(mongo, tenant.id, input, now); + let { user } = await findOrCreateUser(mongo, tenant.id, input, now); + + if (shouldPremodDueToLikelySpamEmail(tenant, user)) { + user = await premod(mongo, cache, tenant, null, user.id, now); + } return user; } catch (err) { @@ -182,7 +188,11 @@ export async function findOrCreate( if (err instanceof DuplicateUserError) { // Retry the operation once more, if this operation fails, the error will // exit this function. - const { user } = await findOrCreateUser(mongo, tenant.id, input, now); + let { user } = await findOrCreateUser(mongo, tenant.id, input, now); + + if (shouldPremodDueToLikelySpamEmail(tenant, user)) { + user = await premod(mongo, cache, tenant, null, user.id, now); + } return user; } @@ -201,13 +211,17 @@ export async function findOrCreate( // Create the user again this time, but associate the duplicate email to // the user account. - const { user } = await findOrCreateUser( + let { user } = await findOrCreateUser( mongo, tenant.id, { ...rest, duplicateEmail: email }, now ); + if (shouldPremodDueToLikelySpamEmail(tenant, user)) { + user = await premod(mongo, cache, tenant, null, user.id, now); + } + return user; } @@ -1757,7 +1771,7 @@ export async function premod( mongo: MongoContext, cache: DataCache, tenant: Tenant, - moderator: User, + moderator: User | null, userID: string, now = new Date() ) { @@ -1779,7 +1793,7 @@ export async function premod( mongo, tenant.id, userID, - moderator.id, + moderator ? moderator.id : undefined, now ); From dd3670de9934dd00c698826b7d7733b39d118e4a Mon Sep 17 00:00:00 2001 From: nick-funk Date: Tue, 29 Aug 2023 15:08:32 -0600 Subject: [PATCH 02/21] add tests around EMAIL_PREMOD_FILTER functionality --- .../services/users/emailPremodFilter.ts | 2 +- .../users/user.emailPremodFilter.spec.ts | 67 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/core/server/services/users/user.emailPremodFilter.spec.ts diff --git a/src/core/server/services/users/emailPremodFilter.ts b/src/core/server/services/users/emailPremodFilter.ts index e93d41caff..3de3bbbe1a 100644 --- a/src/core/server/services/users/emailPremodFilter.ts +++ b/src/core/server/services/users/emailPremodFilter.ts @@ -3,7 +3,7 @@ import { hasFeatureFlag, Tenant } from "coral-server/models/tenant"; import { GQLFEATURE_FLAG } from "coral-server/graph/schema/__generated__/types"; import { User } from "coral-server/models/user"; -const EMAIL_PREMOD_FILTER_PERIOD_LIMIT = 3; +export const EMAIL_PREMOD_FILTER_PERIOD_LIMIT = 3; const emailHasTooManyPeriods = (email: string | undefined, limit: number) => { if (!email) { diff --git a/src/core/server/services/users/user.emailPremodFilter.spec.ts b/src/core/server/services/users/user.emailPremodFilter.spec.ts new file mode 100644 index 0000000000..1af1fc42b9 --- /dev/null +++ b/src/core/server/services/users/user.emailPremodFilter.spec.ts @@ -0,0 +1,67 @@ +import { + createTenantFixture, + createUserFixture, +} from "coral-server/test/fixtures"; + +import { GQLFEATURE_FLAG } from "coral-server/graph/schema/__generated__/types"; + +import { + EMAIL_PREMOD_FILTER_PERIOD_LIMIT, + shouldPremodDueToLikelySpamEmail, +} from "./emailPremodFilter"; + +const tooManyPeriodsEmail = "this.has.too.many.periods@test.com"; +const justEnoughPeriodsEmail = "just.enough.periods@test.com"; +const noPeriodsEmail = "noperiodshere@test.com"; + +it("does not premod filter emails when feature flag is disabled", () => { + const tenant = createTenantFixture({ + featureFlags: [], + }); + + const user = createUserFixture({ + email: tooManyPeriodsEmail, + }); + + const result = shouldPremodDueToLikelySpamEmail(tenant, user); + expect(!result); +}); + +it(`does not premod filter emails when feature flag enabled and has less than ${EMAIL_PREMOD_FILTER_PERIOD_LIMIT} periods`, () => { + const tenant = createTenantFixture({ + featureFlags: [GQLFEATURE_FLAG.EMAIL_PREMOD_FILTER], + }); + + const user = createUserFixture({ + email: justEnoughPeriodsEmail, + }); + + const result = shouldPremodDueToLikelySpamEmail(tenant, user); + expect(result); +}); + +it(`does not premod filter emails when feature flag enabled and has no periods`, () => { + const tenant = createTenantFixture({ + featureFlags: [GQLFEATURE_FLAG.EMAIL_PREMOD_FILTER], + }); + + const user = createUserFixture({ + email: noPeriodsEmail, + }); + + const result = shouldPremodDueToLikelySpamEmail(tenant, user); + expect(result); +}); + +it(`does premod filter emails when feature flag is enabled and has too many (${EMAIL_PREMOD_FILTER_PERIOD_LIMIT} or more) periods`, () => { + const tenant = createTenantFixture({ + featureFlags: [GQLFEATURE_FLAG.EMAIL_PREMOD_FILTER], + }); + + const user = createUserFixture({ + email: tooManyPeriodsEmail, + }); + + const result = shouldPremodDueToLikelySpamEmail(tenant, user); + expect(result); +}); From 6bd9db55d5c7953ece3861659a1248071d6e169b Mon Sep 17 00:00:00 2001 From: nick-funk Date: Tue, 29 Aug 2023 15:15:03 -0600 Subject: [PATCH 03/21] don't require data cache in `findOrCreate` user --- src/core/server/services/users/users.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/server/services/users/users.ts b/src/core/server/services/users/users.ts index a3d90f4a01..8df187aa82 100644 --- a/src/core/server/services/users/users.ts +++ b/src/core/server/services/users/users.ts @@ -163,7 +163,6 @@ export interface FindOrCreateUserOptions { export async function findOrCreate( config: Config, mongo: MongoContext, - cache: DataCache, tenant: Tenant, input: FindOrCreateUser, options: FindOrCreateUserOptions, @@ -177,7 +176,7 @@ export async function findOrCreate( let { user } = await findOrCreateUser(mongo, tenant.id, input, now); if (shouldPremodDueToLikelySpamEmail(tenant, user)) { - user = await premod(mongo, cache, tenant, null, user.id, now); + user = await premodUser(mongo, tenant.id, user.id, undefined, now); } return user; @@ -191,7 +190,7 @@ export async function findOrCreate( let { user } = await findOrCreateUser(mongo, tenant.id, input, now); if (shouldPremodDueToLikelySpamEmail(tenant, user)) { - user = await premod(mongo, cache, tenant, null, user.id, now); + user = await premodUser(mongo, tenant.id, user.id, undefined, now); } return user; @@ -219,7 +218,7 @@ export async function findOrCreate( ); if (shouldPremodDueToLikelySpamEmail(tenant, user)) { - user = await premod(mongo, cache, tenant, null, user.id, now); + user = await premodUser(mongo, tenant.id, user.id, undefined, now); } return user; From 733b613f803ccc281ca6894851c794f1b5e52e97 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Tue, 29 Aug 2023 15:25:33 -0600 Subject: [PATCH 04/21] slightly tweak the email periods filter to handle abnormal input --- src/core/server/services/users/emailPremodFilter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/server/services/users/emailPremodFilter.ts b/src/core/server/services/users/emailPremodFilter.ts index 3de3bbbe1a..1fdf728be3 100644 --- a/src/core/server/services/users/emailPremodFilter.ts +++ b/src/core/server/services/users/emailPremodFilter.ts @@ -11,7 +11,7 @@ const emailHasTooManyPeriods = (email: string | undefined, limit: number) => { } const split = email.split("@"); - if (split.length !== 2) { + if (split.length < 2) { return false; } From da1bb5f533bd6ad5e8fd8033dbb1f68eb7b965bb Mon Sep 17 00:00:00 2001 From: nick-funk Date: Thu, 31 Aug 2023 16:19:31 -0600 Subject: [PATCH 05/21] make premoderate email address "too many periods" an Admin > Config --- .../Moderation/ModerationConfigContainer.tsx | 3 + .../PremoderateEmailAddressConfig.tsx | 69 +++++++++++++++++++ .../resolvers/PremoderateEmailAddress.ts | 12 ++++ src/core/server/graph/schema/schema.graphql | 26 +++++-- src/core/server/models/settings/settings.ts | 8 +++ .../services/users/emailPremodFilter.ts | 6 +- src/locales/en-US/admin.ftl | 11 +++ 7 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx create mode 100644 src/core/server/graph/resolvers/PremoderateEmailAddress.ts diff --git a/src/core/client/admin/routes/Configure/sections/Moderation/ModerationConfigContainer.tsx b/src/core/client/admin/routes/Configure/sections/Moderation/ModerationConfigContainer.tsx index 258a96436b..f58cff6c53 100644 --- a/src/core/client/admin/routes/Configure/sections/Moderation/ModerationConfigContainer.tsx +++ b/src/core/client/admin/routes/Configure/sections/Moderation/ModerationConfigContainer.tsx @@ -17,6 +17,7 @@ import EmailDomainConfigContainer from "./EmailDomainConfigContainer"; import ExternalLinksConfigContainer from "./ExternalLinksConfigContainer"; import NewCommentersConfigContainer from "./NewCommentersConfigContainer"; import PerspectiveConfig from "./PerspectiveConfig"; +import PremoderateEmailAddressConfig from "./PremoderateEmailAddressConfig"; import PreModerationConfigContainer from "./PreModerationConfigContainer"; import RecentCommentHistoryConfig from "./RecentCommentHistoryConfig"; @@ -50,6 +51,7 @@ export const ModerationConfigContainer: React.FunctionComponent = ({ + ); }; @@ -67,6 +69,7 @@ const enhanced = withFragmentContainer({ ...EmailDomainConfigContainer_settings ...ExternalLinksConfigContainer_formValues @relay(mask: false) ...ExternalLinksConfigContainer_settings + ...PremoderateEmailAddressConfig_formValues @relay(mask: false) } `, })(ModerationConfigContainer); diff --git a/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx b/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx new file mode 100644 index 0000000000..3c67d7e42b --- /dev/null +++ b/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx @@ -0,0 +1,69 @@ +import { Localized } from "@fluent/react/compat"; +import React, { FunctionComponent } from "react"; +import { graphql } from "react-relay"; + +import { + FieldSet, + FormField, + FormFieldHeader, + Label, +} from "coral-ui/components/v2"; + +import ConfigBox from "../../ConfigBox"; +import Header from "../../Header"; +import HelperText from "../../HelperText"; +import OnOffField from "../../OnOffField"; + +// eslint-disable-next-line no-unused-expressions +graphql` + fragment PremoderateEmailAddressConfig_formValues on Settings { + premoderateEmailAddress { + tooManyPeriods { + enabled + } + } + } +`; + +interface Props { + disabled: boolean; +} + +const PremoderateEmailAddressConfig: FunctionComponent = ({ + disabled, +}) => { + return ( + +
}>Email address
+ + } + container={
} + > + }> + + + + + + + If a users has three or more periods in the first part of their + email address (before the @), set their status to pre-moderate all + comments. This is often used because emails with more than 3 + periods can have a very high spam correlation. + + + + + + + ); +}; + +export default PremoderateEmailAddressConfig; diff --git a/src/core/server/graph/resolvers/PremoderateEmailAddress.ts b/src/core/server/graph/resolvers/PremoderateEmailAddress.ts new file mode 100644 index 0000000000..598f3a6d89 --- /dev/null +++ b/src/core/server/graph/resolvers/PremoderateEmailAddress.ts @@ -0,0 +1,12 @@ +import { + GQLPremoderateEmailAddressConfig, + GQLPremoderateEmailAddressConfigTypeResolver, +} from "coral-server/graph/schema/__generated__/types"; + +export const SlackConfiguration: GQLPremoderateEmailAddressConfigTypeResolver = + { + tooManyPeriods: (config) => + config && config.tooManyPeriods + ? config.tooManyPeriods + : { enabled: false }, + }; diff --git a/src/core/server/graph/schema/schema.graphql b/src/core/server/graph/schema/schema.graphql index 2b2c40399d..394990f7fc 100644 --- a/src/core/server/graph/schema/schema.graphql +++ b/src/core/server/graph/schema/schema.graphql @@ -567,12 +567,6 @@ enum FEATURE_FLAG { users, and commentActions more quickly. It is disabled by default. """ DATA_CACHE - - """ - EMAIL_PREMOD_FILTER enables the feature to premoderate accounts whose emails - match patterns that are common with spam accounts. - """ - EMAIL_PREMOD_FILTER } # The moderation mode of the site. @@ -1934,6 +1928,22 @@ type RTEConfiguration { sarcasm: Boolean! } +type TooManyPeriodsConfig { + enabled: Boolean! +} + +type PremoderateEmailAddressConfig { + tooManyPeriods: TooManyPeriodsConfig +} + +input TooManyPeriodsConfigInput { + enabled: Boolean +} + +input PremoderateEmailAddressConfigInput { + tooManyPeriods: TooManyPeriodsConfigInput +} + """ Settings stores the global settings for a given Tenant. """ @@ -2228,6 +2238,8 @@ type Settings @cacheControl(maxAge: 30) { they are enabled and any configured image urls """ flairBadges: FlairBadgeConfiguration + + premoderateEmailAddress: PremoderateEmailAddressConfig } ################################################################################ @@ -5808,6 +5820,8 @@ input SettingsInput { they are enabled and any configured image urls """ flairBadges: FlairBadgeConfigurationInput + + premoderateEmailAddress: PremoderateEmailAddressConfigInput } """ diff --git a/src/core/server/models/settings/settings.ts b/src/core/server/models/settings/settings.ts index 99ba0821a9..3b3e854965 100644 --- a/src/core/server/models/settings/settings.ts +++ b/src/core/server/models/settings/settings.ts @@ -303,6 +303,12 @@ export interface FlairBadgeConfig { badges?: FlairBadge[]; } +export interface PremoderateEmailAddressConfig { + tooManyPeriods?: { + enabled?: boolean; + }; +} + export type Settings = GlobalModerationSettings & Pick< GQLSettings, @@ -410,6 +416,8 @@ export type Settings = GlobalModerationSettings & forReviewQueue?: boolean; flairBadges?: FlairBadgeConfig; + + premoderateEmailAddress?: PremoderateEmailAddressConfig; }; export const defaultRTEConfiguration: RTEConfiguration = { diff --git a/src/core/server/services/users/emailPremodFilter.ts b/src/core/server/services/users/emailPremodFilter.ts index 1fdf728be3..eebe05f2a6 100644 --- a/src/core/server/services/users/emailPremodFilter.ts +++ b/src/core/server/services/users/emailPremodFilter.ts @@ -1,6 +1,4 @@ -import { hasFeatureFlag, Tenant } from "coral-server/models/tenant"; - -import { GQLFEATURE_FLAG } from "coral-server/graph/schema/__generated__/types"; +import { Tenant } from "coral-server/models/tenant"; import { User } from "coral-server/models/user"; export const EMAIL_PREMOD_FILTER_PERIOD_LIMIT = 3; @@ -32,7 +30,7 @@ export const shouldPremodDueToLikelySpamEmail = ( user: Readonly ) => { // don't premod check unless the filter is enabled - if (!hasFeatureFlag(tenant, GQLFEATURE_FLAG.EMAIL_PREMOD_FILTER)) { + if (!tenant?.premoderateEmailAddress?.tooManyPeriods?.enabled) { return false; } diff --git a/src/locales/en-US/admin.ftl b/src/locales/en-US/admin.ftl index 1ba67c3b5e..208d0ab434 100644 --- a/src/locales/en-US/admin.ftl +++ b/src/locales/en-US/admin.ftl @@ -873,6 +873,17 @@ configure-moderation-emailDomains-confirmDelete = Deleting this email domain wil configure-moderation-emailDomains-form-description-add = Add a domain and select the action that should be taken when on every new account created using the specified domain. configure-moderation-emailDomains-form-description-edit = Update the domain or action that should be taken when on every new account using the specified domain. +#### Premoderate Email Address Configuration + +configure-moderation-premoderateEmailAddress-title = Email address +configure-moderation-premoderateEmailAddress-enabled = + Premoderate users with too many periods +configure-moderation-premoderateEmailAddress-enabled = + If a users has three or more periods in the first part of their + email address (before the @), set their status to pre-moderate all + comments. This is often used because emails with more than 3 + periods can have a very high spam correlation. + #### Banned Words Configuration configure-wordList-banned-bannedWordsAndPhrases = Banned words and phrases configure-wordList-banned-explanation = From c8fd685530ca0184c2757f9b22d9bc2c61ebd910 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Thu, 31 Aug 2023 16:25:25 -0600 Subject: [PATCH 06/21] allow pre-mod filtering emails during local auth sign up --- .../app/handlers/api/auth/local/signup.ts | 7 +++- .../users/user.emailPremodFilter.spec.ts | 34 +++++++++++++------ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/core/server/app/handlers/api/auth/local/signup.ts b/src/core/server/app/handlers/api/auth/local/signup.ts index 8703f6e7c1..8fa2960727 100644 --- a/src/core/server/app/handlers/api/auth/local/signup.ts +++ b/src/core/server/app/handlers/api/auth/local/signup.ts @@ -10,9 +10,10 @@ import { UsernameAlreadyExists, } from "coral-server/errors"; import { hasEnabledAuthIntegration } from "coral-server/models/tenant"; -import { LocalProfile, User } from "coral-server/models/user"; +import { LocalProfile, premodUser, User } from "coral-server/models/user"; import { create, usernameAlreadyExists } from "coral-server/services/users"; import { sendConfirmationEmail } from "coral-server/services/users/auth"; +import { shouldPremodDueToLikelySpamEmail } from "coral-server/services/users/emailPremodFilter"; import { RequestHandler, TenantCoralRequest } from "coral-server/types/express"; import { GQLUSER_ROLE } from "coral-server/graph/schema/__generated__/types"; @@ -117,6 +118,10 @@ export const signupHandler = ({ now ); + if (shouldPremodDueToLikelySpamEmail(tenant, user)) { + await premodUser(mongo, tenant.id, user.id); + } + // Send off to the passport handler. return handleSuccessfulLogin(user, signingConfig, req, res, next); } catch (err) { diff --git a/src/core/server/services/users/user.emailPremodFilter.spec.ts b/src/core/server/services/users/user.emailPremodFilter.spec.ts index 1af1fc42b9..2f17a6fd5e 100644 --- a/src/core/server/services/users/user.emailPremodFilter.spec.ts +++ b/src/core/server/services/users/user.emailPremodFilter.spec.ts @@ -3,8 +3,6 @@ import { createUserFixture, } from "coral-server/test/fixtures"; -import { GQLFEATURE_FLAG } from "coral-server/graph/schema/__generated__/types"; - import { EMAIL_PREMOD_FILTER_PERIOD_LIMIT, shouldPremodDueToLikelySpamEmail, @@ -14,9 +12,13 @@ const tooManyPeriodsEmail = "this.has.too.many.periods@test.com"; const justEnoughPeriodsEmail = "just.enough.periods@test.com"; const noPeriodsEmail = "noperiodshere@test.com"; -it("does not premod filter emails when feature flag is disabled", () => { +it("does not premod filter emails when feature is disabled", () => { const tenant = createTenantFixture({ - featureFlags: [], + premoderateEmailAddress: { + tooManyPeriods: { + enabled: false, + }, + }, }); const user = createUserFixture({ @@ -27,9 +29,13 @@ it("does not premod filter emails when feature flag is disabled", () => { expect(!result); }); -it(`does not premod filter emails when feature flag enabled and has less than ${EMAIL_PREMOD_FILTER_PERIOD_LIMIT} periods`, () => { +it(`does not premod filter emails when feature enabled and has less than ${EMAIL_PREMOD_FILTER_PERIOD_LIMIT} periods`, () => { const tenant = createTenantFixture({ - featureFlags: [GQLFEATURE_FLAG.EMAIL_PREMOD_FILTER], + premoderateEmailAddress: { + tooManyPeriods: { + enabled: true, + }, + }, }); const user = createUserFixture({ @@ -40,9 +46,13 @@ it(`does not premod filter emails when feature flag enabled and has less than ${ expect(result); }); -it(`does not premod filter emails when feature flag enabled and has no periods`, () => { +it(`does not premod filter emails when feature enabled and has no periods`, () => { const tenant = createTenantFixture({ - featureFlags: [GQLFEATURE_FLAG.EMAIL_PREMOD_FILTER], + premoderateEmailAddress: { + tooManyPeriods: { + enabled: true, + }, + }, }); const user = createUserFixture({ @@ -53,9 +63,13 @@ it(`does not premod filter emails when feature flag enabled and has no periods`, expect(result); }); -it(`does premod filter emails when feature flag is enabled and has too many (${EMAIL_PREMOD_FILTER_PERIOD_LIMIT} or more) periods`, () => { +it(`does premod filter emails when feature is enabled and has too many (${EMAIL_PREMOD_FILTER_PERIOD_LIMIT} or more) periods`, () => { const tenant = createTenantFixture({ - featureFlags: [GQLFEATURE_FLAG.EMAIL_PREMOD_FILTER], + premoderateEmailAddress: { + tooManyPeriods: { + enabled: true, + }, + }, }); const user = createUserFixture({ From 9286a0b12693a1f562cf55ba82224ea2b64e9a64 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Thu, 31 Aug 2023 16:34:15 -0600 Subject: [PATCH 07/21] fix wording of email premod filter config description --- .../sections/Moderation/PremoderateEmailAddressConfig.tsx | 6 +++--- src/locales/en-US/admin.ftl | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx b/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx index 3c67d7e42b..fdace6fe4d 100644 --- a/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx +++ b/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx @@ -45,12 +45,12 @@ const PremoderateEmailAddressConfig: FunctionComponent = ({ - + - If a users has three or more periods in the first part of their + If a user has three or more periods in the first part of their email address (before the @), set their status to pre-moderate all comments. This is often used because emails with more than 3 periods can have a very high spam correlation. diff --git a/src/locales/en-US/admin.ftl b/src/locales/en-US/admin.ftl index 208d0ab434..2067cfb834 100644 --- a/src/locales/en-US/admin.ftl +++ b/src/locales/en-US/admin.ftl @@ -877,9 +877,9 @@ configure-moderation-emailDomains-form-description-edit = Update the domain or a configure-moderation-premoderateEmailAddress-title = Email address configure-moderation-premoderateEmailAddress-enabled = - Premoderate users with too many periods -configure-moderation-premoderateEmailAddress-enabled = - If a users has three or more periods in the first part of their + Premoderate emails with too many periods +configure-moderation-premoderateEmailAddress-enabled-description = + If a user has three or more periods in the first part of their email address (before the @), set their status to pre-moderate all comments. This is often used because emails with more than 3 periods can have a very high spam correlation. From 9e2219406fc0b69fa927f11371970241cee368e4 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Tue, 5 Sep 2023 10:19:33 -0600 Subject: [PATCH 08/21] improve pre-moderate email configuration copy --- .../sections/Moderation/PremoderateEmailAddressConfig.tsx | 6 +++--- src/locales/en-US/admin.ftl | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx b/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx index fdace6fe4d..0f5f19268f 100644 --- a/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx +++ b/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx @@ -51,9 +51,9 @@ const PremoderateEmailAddressConfig: FunctionComponent = ({ If a user has three or more periods in the first part of their - email address (before the @), set their status to pre-moderate all - comments. This is often used because emails with more than 3 - periods can have a very high spam correlation. + email address (before the @), set their status to pre-moderate + comments. Emails with 3 or more periods can have a very high spam + correlation. It can be useful to pro-actively pre-moderate them. diff --git a/src/locales/en-US/admin.ftl b/src/locales/en-US/admin.ftl index 2067cfb834..b6d7a293d1 100644 --- a/src/locales/en-US/admin.ftl +++ b/src/locales/en-US/admin.ftl @@ -880,9 +880,9 @@ configure-moderation-premoderateEmailAddress-enabled = Premoderate emails with too many periods configure-moderation-premoderateEmailAddress-enabled-description = If a user has three or more periods in the first part of their - email address (before the @), set their status to pre-moderate all - comments. This is often used because emails with more than 3 - periods can have a very high spam correlation. + email address (before the @), set their status to pre-moderate + comments. Emails with 3 or more periods can have a very high spam + correlation. It can be useful to pro-actively pre-moderate them. #### Banned Words Configuration configure-wordList-banned-bannedWordsAndPhrases = Banned words and phrases From 46be3fee116e82c714ccf24bb3b79c78e2d71fef Mon Sep 17 00:00:00 2001 From: nick-funk Date: Tue, 5 Sep 2023 10:20:19 -0600 Subject: [PATCH 09/21] fix pre-mod repeatedly re-pre-modding spam emails --- .../app/handlers/api/auth/local/signup.ts | 16 +++++++++-- src/core/server/models/user/user.ts | 22 ++++++++++++--- .../services/users/emailPremodFilter.ts | 12 ++++++++ src/core/server/services/users/users.ts | 28 +++++++++++++++++-- 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/core/server/app/handlers/api/auth/local/signup.ts b/src/core/server/app/handlers/api/auth/local/signup.ts index 8fa2960727..6503a081be 100644 --- a/src/core/server/app/handlers/api/auth/local/signup.ts +++ b/src/core/server/app/handlers/api/auth/local/signup.ts @@ -10,7 +10,12 @@ import { UsernameAlreadyExists, } from "coral-server/errors"; import { hasEnabledAuthIntegration } from "coral-server/models/tenant"; -import { LocalProfile, premodUser, User } from "coral-server/models/user"; +import { + LocalProfile, + premodUser, + PremodUserReason, + User, +} from "coral-server/models/user"; import { create, usernameAlreadyExists } from "coral-server/services/users"; import { sendConfirmationEmail } from "coral-server/services/users/auth"; import { shouldPremodDueToLikelySpamEmail } from "coral-server/services/users/emailPremodFilter"; @@ -119,7 +124,14 @@ export const signupHandler = ({ ); if (shouldPremodDueToLikelySpamEmail(tenant, user)) { - await premodUser(mongo, tenant.id, user.id); + await premodUser( + mongo, + tenant.id, + user.id, + undefined, + now, + PremodUserReason.EmailPremodFilter + ); } // Send off to the passport handler. diff --git a/src/core/server/models/user/user.ts b/src/core/server/models/user/user.ts index daaed68079..f7a5be1570 100644 --- a/src/core/server/models/user/user.ts +++ b/src/core/server/models/user/user.ts @@ -616,6 +616,8 @@ export interface User extends TenantResource { * bio is a user deifned biography */ bio?: string; + + premoderatedBecauseOfEmailAt?: Date; } function hashPassword(password: string): Promise { @@ -1818,6 +1820,11 @@ async function retrieveConnection( return resolveConnection(query, input, (user) => user.createdAt); } +export enum PremodUserReason { + None = 0, + EmailPremodFilter, +} + /** * premodUser will set a user to mandatory premod. * @@ -1832,7 +1839,8 @@ export async function premodUser( tenantID: string, id: string, createdBy?: string, - now = new Date() + now = new Date(), + reason = PremodUserReason.None ) { // Create the new ban. const premodStatusHistory: PremodStatusHistory = { @@ -1841,6 +1849,14 @@ export async function premodUser( createdAt: now, }; + const set: any = { + "status.premod.active": true, + }; + + if (reason === PremodUserReason.EmailPremodFilter) { + set.premoderatedBecauseOfEmailAt = now; + } + // Try to update the user if the user isn't already banned. const result = await mongo.users().findOneAndUpdate( { @@ -1851,9 +1867,7 @@ export async function premodUser( }, }, { - $set: { - "status.premod.active": true, - }, + $set: set, $push: { "status.premod.history": premodStatusHistory, }, diff --git a/src/core/server/services/users/emailPremodFilter.ts b/src/core/server/services/users/emailPremodFilter.ts index eebe05f2a6..514c84d642 100644 --- a/src/core/server/services/users/emailPremodFilter.ts +++ b/src/core/server/services/users/emailPremodFilter.ts @@ -34,6 +34,18 @@ export const shouldPremodDueToLikelySpamEmail = ( return false; } + // don't need to premod a user that is already premoderated + if (user.status.premod.active) { + return false; + } + + // if user is no longer pre-modded, but was because of this spam + // email check, don't return true again because staff probably + // removed the premod status. + if (!user.status.premod.active && user.premoderatedBecauseOfEmailAt) { + return false; + } + // this is an array to allow for adding more rules in the // future as we play whack-a-mole trying to block spammers // and other trouble makers diff --git a/src/core/server/services/users/users.ts b/src/core/server/services/users/users.ts index 8df187aa82..192a36827b 100644 --- a/src/core/server/services/users/users.ts +++ b/src/core/server/services/users/users.ts @@ -66,6 +66,7 @@ import { mergeUserSiteModerationScopes, NotificationSettingsInput, premodUser, + PremodUserReason, pullUserMembershipScopes, pullUserSiteModerationScopes, removeActiveUserSuspensions, @@ -176,7 +177,14 @@ export async function findOrCreate( let { user } = await findOrCreateUser(mongo, tenant.id, input, now); if (shouldPremodDueToLikelySpamEmail(tenant, user)) { - user = await premodUser(mongo, tenant.id, user.id, undefined, now); + user = await premodUser( + mongo, + tenant.id, + user.id, + undefined, + now, + PremodUserReason.EmailPremodFilter + ); } return user; @@ -190,7 +198,14 @@ export async function findOrCreate( let { user } = await findOrCreateUser(mongo, tenant.id, input, now); if (shouldPremodDueToLikelySpamEmail(tenant, user)) { - user = await premodUser(mongo, tenant.id, user.id, undefined, now); + user = await premodUser( + mongo, + tenant.id, + user.id, + undefined, + now, + PremodUserReason.EmailPremodFilter + ); } return user; @@ -218,7 +233,14 @@ export async function findOrCreate( ); if (shouldPremodDueToLikelySpamEmail(tenant, user)) { - user = await premodUser(mongo, tenant.id, user.id, undefined, now); + user = await premodUser( + mongo, + tenant.id, + user.id, + undefined, + now, + PremodUserReason.EmailPremodFilter + ); } return user; From 22b73bef6fc7ce251f1611205fb340812ba93b86 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Thu, 7 Sep 2023 10:15:53 -0600 Subject: [PATCH 10/21] fix typo in pre-moderate email address config title --- .../sections/Moderation/PremoderateEmailAddressConfig.tsx | 2 +- locales/en-US/admin.ftl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx b/client/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx index 0f5f19268f..9d9838a260 100644 --- a/client/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx +++ b/client/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx @@ -45,7 +45,7 @@ const PremoderateEmailAddressConfig: FunctionComponent = ({ diff --git a/locales/en-US/admin.ftl b/locales/en-US/admin.ftl index b6d7a293d1..a8105c322d 100644 --- a/locales/en-US/admin.ftl +++ b/locales/en-US/admin.ftl @@ -873,11 +873,11 @@ configure-moderation-emailDomains-confirmDelete = Deleting this email domain wil configure-moderation-emailDomains-form-description-add = Add a domain and select the action that should be taken when on every new account created using the specified domain. configure-moderation-emailDomains-form-description-edit = Update the domain or action that should be taken when on every new account using the specified domain. -#### Premoderate Email Address Configuration +#### Pre-moderate Email Address Configuration configure-moderation-premoderateEmailAddress-title = Email address configure-moderation-premoderateEmailAddress-enabled = - Premoderate emails with too many periods + Pre-moderate emails with too many periods configure-moderation-premoderateEmailAddress-enabled-description = If a user has three or more periods in the first part of their email address (before the @), set their status to pre-moderate From 886dbc670a4066348c3fd9bded79437cb1237576 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Thu, 7 Sep 2023 10:18:30 -0600 Subject: [PATCH 11/21] rename copy paste resolver and register in resolvers --- .../src/core/server/graph/resolvers/PremoderateEmailAddress.ts | 2 +- server/src/core/server/graph/resolvers/index.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/core/server/graph/resolvers/PremoderateEmailAddress.ts b/server/src/core/server/graph/resolvers/PremoderateEmailAddress.ts index 598f3a6d89..e1ef2541d5 100644 --- a/server/src/core/server/graph/resolvers/PremoderateEmailAddress.ts +++ b/server/src/core/server/graph/resolvers/PremoderateEmailAddress.ts @@ -3,7 +3,7 @@ import { GQLPremoderateEmailAddressConfigTypeResolver, } from "coral-server/graph/schema/__generated__/types"; -export const SlackConfiguration: GQLPremoderateEmailAddressConfigTypeResolver = +export const PremoderateEmailAddressConfig: GQLPremoderateEmailAddressConfigTypeResolver = { tooManyPeriods: (config) => config && config.tooManyPeriods diff --git a/server/src/core/server/graph/resolvers/index.ts b/server/src/core/server/graph/resolvers/index.ts index 3d25555cce..12f4134c2c 100644 --- a/server/src/core/server/graph/resolvers/index.ts +++ b/server/src/core/server/graph/resolvers/index.ts @@ -46,6 +46,7 @@ import { ModMessageStatusHistory } from "./ModMessageStatusHistory"; import { Mutation } from "./Mutation"; import { NewCommentersConfiguration } from "./NewCommentersConfiguration"; import { OIDCAuthIntegration } from "./OIDCAuthIntegration"; +import { PremoderateEmailAddressConfig } from "./PremoderateEmailAddress"; import { PremodStatus } from "./PremodStatus"; import { PremodStatusHistory } from "./PremodStatusHistory"; import { Profile } from "./Profile"; @@ -165,6 +166,7 @@ const Resolvers: GQLResolver = { YouTubeMediaConfiguration, LocalAuthIntegration, AuthenticationTargetFilter, + PremoderateEmailAddressConfig, }; export default Resolvers; From 2a94c317f7e5a2484dd8198aeadbbf6d8447332f Mon Sep 17 00:00:00 2001 From: nick-funk Date: Thu, 7 Sep 2023 10:20:38 -0600 Subject: [PATCH 12/21] rename Premod...Config to Premod...Configuration for consistency --- .../server/graph/resolvers/PremoderateEmailAddress.ts | 6 +++--- server/src/core/server/graph/resolvers/index.ts | 4 ++-- server/src/core/server/graph/schema/schema.graphql | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/server/src/core/server/graph/resolvers/PremoderateEmailAddress.ts b/server/src/core/server/graph/resolvers/PremoderateEmailAddress.ts index e1ef2541d5..b42920eb67 100644 --- a/server/src/core/server/graph/resolvers/PremoderateEmailAddress.ts +++ b/server/src/core/server/graph/resolvers/PremoderateEmailAddress.ts @@ -1,9 +1,9 @@ import { - GQLPremoderateEmailAddressConfig, - GQLPremoderateEmailAddressConfigTypeResolver, + GQLPremoderateEmailAddressConfiguration, + GQLPremoderateEmailAddressConfigurationTypeResolver, } from "coral-server/graph/schema/__generated__/types"; -export const PremoderateEmailAddressConfig: GQLPremoderateEmailAddressConfigTypeResolver = +export const PremoderateEmailAddressConfiguration: GQLPremoderateEmailAddressConfigurationTypeResolver = { tooManyPeriods: (config) => config && config.tooManyPeriods diff --git a/server/src/core/server/graph/resolvers/index.ts b/server/src/core/server/graph/resolvers/index.ts index 12f4134c2c..0fd2df51bc 100644 --- a/server/src/core/server/graph/resolvers/index.ts +++ b/server/src/core/server/graph/resolvers/index.ts @@ -46,7 +46,7 @@ import { ModMessageStatusHistory } from "./ModMessageStatusHistory"; import { Mutation } from "./Mutation"; import { NewCommentersConfiguration } from "./NewCommentersConfiguration"; import { OIDCAuthIntegration } from "./OIDCAuthIntegration"; -import { PremoderateEmailAddressConfig } from "./PremoderateEmailAddress"; +import { PremoderateEmailAddressConfiguration } from "./PremoderateEmailAddress"; import { PremodStatus } from "./PremodStatus"; import { PremodStatusHistory } from "./PremodStatusHistory"; import { Profile } from "./Profile"; @@ -166,7 +166,7 @@ const Resolvers: GQLResolver = { YouTubeMediaConfiguration, LocalAuthIntegration, AuthenticationTargetFilter, - PremoderateEmailAddressConfig, + PremoderateEmailAddressConfiguration, }; export default Resolvers; diff --git a/server/src/core/server/graph/schema/schema.graphql b/server/src/core/server/graph/schema/schema.graphql index 394990f7fc..0d5dd893b8 100644 --- a/server/src/core/server/graph/schema/schema.graphql +++ b/server/src/core/server/graph/schema/schema.graphql @@ -1932,7 +1932,7 @@ type TooManyPeriodsConfig { enabled: Boolean! } -type PremoderateEmailAddressConfig { +type PremoderateEmailAddressConfiguration { tooManyPeriods: TooManyPeriodsConfig } @@ -1940,7 +1940,7 @@ input TooManyPeriodsConfigInput { enabled: Boolean } -input PremoderateEmailAddressConfigInput { +input PremoderateEmailAddressConfigurationInput { tooManyPeriods: TooManyPeriodsConfigInput } @@ -2239,7 +2239,7 @@ type Settings @cacheControl(maxAge: 30) { """ flairBadges: FlairBadgeConfiguration - premoderateEmailAddress: PremoderateEmailAddressConfig + premoderateEmailAddress: PremoderateEmailAddressConfiguration } ################################################################################ @@ -5821,7 +5821,7 @@ input SettingsInput { """ flairBadges: FlairBadgeConfigurationInput - premoderateEmailAddress: PremoderateEmailAddressConfigInput + premoderateEmailAddress: PremoderateEmailAddressConfigurationInput } """ From fbd443c7489a2f8382a067e0740966e8b59274a3 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Thu, 7 Sep 2023 10:38:58 -0600 Subject: [PATCH 13/21] don't email premod if user email is on domain auto-ban list --- .../core/server/models/settings/settings.ts | 7 ++++- .../services/users/emailPremodFilter.ts | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/server/src/core/server/models/settings/settings.ts b/server/src/core/server/models/settings/settings.ts index 3b3e854965..70d51649c7 100644 --- a/server/src/core/server/models/settings/settings.ts +++ b/server/src/core/server/models/settings/settings.ts @@ -287,10 +287,15 @@ export interface StoryConfiguration { disableLazy: boolean; } +export enum NewUserModeration { + BAN = "BAN", + PREMOD = "PREMOD", +} + export interface EmailDomain { id: string; domain: string; - newUserModeration: "BAN" | "PREMOD"; + newUserModeration: NewUserModeration; } export interface FlairBadge { diff --git a/server/src/core/server/services/users/emailPremodFilter.ts b/server/src/core/server/services/users/emailPremodFilter.ts index 514c84d642..bd1c9ef3b1 100644 --- a/server/src/core/server/services/users/emailPremodFilter.ts +++ b/server/src/core/server/services/users/emailPremodFilter.ts @@ -1,3 +1,4 @@ +import { NewUserModeration } from "coral-server/models/settings"; import { Tenant } from "coral-server/models/tenant"; import { User } from "coral-server/models/user"; @@ -25,6 +26,30 @@ const emailHasTooManyPeriods = (email: string | undefined, limit: number) => { return periodCount >= limit; }; +const emailIsOnAutoBanList = ( + email: string | undefined, + tenant: Readonly +): boolean => { + if (!email) { + return false; + } + + const emailSplit = email.split("@"); + if (emailSplit.length < 2) { + return false; + } + + const domain = emailSplit[1].trim().toLowerCase(); + + const autoBanRecord = tenant.emailDomainModeration?.find( + (record) => + record.domain.toLowerCase() === domain && + record.newUserModeration === NewUserModeration.BAN + ); + + return !!autoBanRecord; +}; + export const shouldPremodDueToLikelySpamEmail = ( tenant: Readonly, user: Readonly @@ -46,6 +71,10 @@ export const shouldPremodDueToLikelySpamEmail = ( return false; } + if (emailIsOnAutoBanList(user.email, tenant)) { + return false; + } + // this is an array to allow for adding more rules in the // future as we play whack-a-mole trying to block spammers // and other trouble makers From fcf56114a2ebdf6e9d3459d8069f3ace3da69d05 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Thu, 7 Sep 2023 11:43:29 -0600 Subject: [PATCH 14/21] tag email premod filtered comments with `User email` --- .../components/ModerateCard/MarkersContainer.tsx | 7 +++++++ locales/en-US/admin.ftl | 1 + .../src/core/server/graph/schema/schema.graphql | 13 +++++++++++++ .../core/server/models/comment/counts/empty.ts | 2 ++ .../pipeline/phases/statusPreModerateUser.ts | 16 +++++++++++++++- .../stacks/helpers/updateAllCommentCounts.ts | 1 + 6 files changed, 39 insertions(+), 1 deletion(-) diff --git a/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx b/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx index a775ec64aa..f919a892e0 100644 --- a/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx +++ b/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx @@ -33,6 +33,13 @@ const markers: Array< )) || null, + (c) => + (c.status === "PREMOD" && c.tags.find((t) => t.code === "USER_EMAIL") && ( + + User email + + )) || + null, (c) => (c.revision && c.revision.actionCounts.flag.reasons.COMMENT_DETECTED_LINKS && ( diff --git a/locales/en-US/admin.ftl b/locales/en-US/admin.ftl index a8105c322d..7f3cc05f4e 100644 --- a/locales/en-US/admin.ftl +++ b/locales/en-US/admin.ftl @@ -1030,6 +1030,7 @@ moderate-marker-abusive = Abusive moderate-marker-newCommenter = New commenter moderate-marker-repeatPost = Repeat comment moderate-marker-other = Other +moderate-marker-preMod-userEmail = User email moderate-markers-details = Details moderate-flagDetails-latestReports = Latest reports diff --git a/server/src/core/server/graph/schema/schema.graphql b/server/src/core/server/graph/schema/schema.graphql index 0d5dd893b8..696c30c46f 100644 --- a/server/src/core/server/graph/schema/schema.graphql +++ b/server/src/core/server/graph/schema/schema.graphql @@ -3623,6 +3623,14 @@ enum TAG { that is set to Ratings and Reviews. """ QUESTION + + """ + USER_EMAIL is used when a comment is written by a user whose email has been + caught by the email pre-mod filter. This filter is used to catch likely spam + or bad actor emails. This tag allows moderators to see that a comment was + pre-moderated for this reason. + """ + USER_EMAIL } """ @@ -3694,6 +3702,11 @@ type CommentTagCounts { UNANSWERED is the count of Comment's with the UNANSWERED tag. """ UNANSWERED: Int! + + """ + USER_EMAIL is the count of Comment's with the USER_EMAIL tag. + """ + USER_EMAIL: Int! } """ diff --git a/server/src/core/server/models/comment/counts/empty.ts b/server/src/core/server/models/comment/counts/empty.ts index 2c43933445..44661b44b1 100644 --- a/server/src/core/server/models/comment/counts/empty.ts +++ b/server/src/core/server/models/comment/counts/empty.ts @@ -50,6 +50,7 @@ export function hasInvalidGQLCommentTagCounts( GQLTAG.REVIEW, GQLTAG.STAFF, GQLTAG.UNANSWERED, + GQLTAG.USER_EMAIL, ]; for (const key of keys) { @@ -85,6 +86,7 @@ export function createEmptyGQLCommentTagCounts(): GQLCommentTagCounts { [GQLTAG.REVIEW]: 0, [GQLTAG.STAFF]: 0, [GQLTAG.UNANSWERED]: 0, + [GQLTAG.USER_EMAIL]: 0, }; } diff --git a/server/src/core/server/services/comments/pipeline/phases/statusPreModerateUser.ts b/server/src/core/server/services/comments/pipeline/phases/statusPreModerateUser.ts index fbfc758f26..f961aefd66 100644 --- a/server/src/core/server/services/comments/pipeline/phases/statusPreModerateUser.ts +++ b/server/src/core/server/services/comments/pipeline/phases/statusPreModerateUser.ts @@ -3,7 +3,10 @@ import { IntermediatePhaseResult, } from "coral-server/services/comments/pipeline"; -import { GQLCOMMENT_STATUS } from "coral-server/graph/schema/__generated__/types"; +import { + GQLCOMMENT_STATUS, + GQLTAG, +} from "coral-server/graph/schema/__generated__/types"; // If a given user is set to always premod, set to premod. export const statusPreModerateUser: IntermediateModerationPhase = ({ @@ -13,6 +16,17 @@ export const statusPreModerateUser: IntermediateModerationPhase = ({ return; } + // if they have an active pre-mod and have the premoderated + // because of email date set, this comment is being pre-modded + // due to their email being caught by the email pre-mod filter. + // tag it so moderators can see this. + if (author.premoderatedBecauseOfEmailAt) { + return { + status: GQLCOMMENT_STATUS.PREMOD, + tags: [GQLTAG.USER_EMAIL], + }; + } + return { status: GQLCOMMENT_STATUS.PREMOD, }; diff --git a/server/src/core/server/stacks/helpers/updateAllCommentCounts.ts b/server/src/core/server/stacks/helpers/updateAllCommentCounts.ts index 98263e80d6..d358b9a9b8 100644 --- a/server/src/core/server/stacks/helpers/updateAllCommentCounts.ts +++ b/server/src/core/server/stacks/helpers/updateAllCommentCounts.ts @@ -115,6 +115,7 @@ export const calculateTags = ( [GQLTAG.REVIEW]: tagChanged(before, after, GQLTAG.REVIEW), [GQLTAG.STAFF]: tagChanged(before, after, GQLTAG.STAFF), [GQLTAG.UNANSWERED]: tagChanged(before, after, GQLTAG.UNANSWERED), + [GQLTAG.USER_EMAIL]: tagChanged(before, after, GQLTAG.USER_EMAIL), }; let total = 0; From dda42754958f76296a716ba19cb9b7def554263a Mon Sep 17 00:00:00 2001 From: nick-funk Date: Fri, 8 Sep 2023 09:57:14 -0600 Subject: [PATCH 15/21] Revert "tag email premod filtered comments with `User email`" This reverts commit fcf56114a2ebdf6e9d3459d8069f3ace3da69d05. --- .../components/ModerateCard/MarkersContainer.tsx | 7 ------- locales/en-US/admin.ftl | 1 - .../src/core/server/graph/schema/schema.graphql | 13 ------------- .../core/server/models/comment/counts/empty.ts | 2 -- .../pipeline/phases/statusPreModerateUser.ts | 16 +--------------- .../stacks/helpers/updateAllCommentCounts.ts | 1 - 6 files changed, 1 insertion(+), 39 deletions(-) diff --git a/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx b/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx index f919a892e0..a775ec64aa 100644 --- a/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx +++ b/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx @@ -33,13 +33,6 @@ const markers: Array< )) || null, - (c) => - (c.status === "PREMOD" && c.tags.find((t) => t.code === "USER_EMAIL") && ( - - User email - - )) || - null, (c) => (c.revision && c.revision.actionCounts.flag.reasons.COMMENT_DETECTED_LINKS && ( diff --git a/locales/en-US/admin.ftl b/locales/en-US/admin.ftl index 7f3cc05f4e..a8105c322d 100644 --- a/locales/en-US/admin.ftl +++ b/locales/en-US/admin.ftl @@ -1030,7 +1030,6 @@ moderate-marker-abusive = Abusive moderate-marker-newCommenter = New commenter moderate-marker-repeatPost = Repeat comment moderate-marker-other = Other -moderate-marker-preMod-userEmail = User email moderate-markers-details = Details moderate-flagDetails-latestReports = Latest reports diff --git a/server/src/core/server/graph/schema/schema.graphql b/server/src/core/server/graph/schema/schema.graphql index 696c30c46f..0d5dd893b8 100644 --- a/server/src/core/server/graph/schema/schema.graphql +++ b/server/src/core/server/graph/schema/schema.graphql @@ -3623,14 +3623,6 @@ enum TAG { that is set to Ratings and Reviews. """ QUESTION - - """ - USER_EMAIL is used when a comment is written by a user whose email has been - caught by the email pre-mod filter. This filter is used to catch likely spam - or bad actor emails. This tag allows moderators to see that a comment was - pre-moderated for this reason. - """ - USER_EMAIL } """ @@ -3702,11 +3694,6 @@ type CommentTagCounts { UNANSWERED is the count of Comment's with the UNANSWERED tag. """ UNANSWERED: Int! - - """ - USER_EMAIL is the count of Comment's with the USER_EMAIL tag. - """ - USER_EMAIL: Int! } """ diff --git a/server/src/core/server/models/comment/counts/empty.ts b/server/src/core/server/models/comment/counts/empty.ts index 44661b44b1..2c43933445 100644 --- a/server/src/core/server/models/comment/counts/empty.ts +++ b/server/src/core/server/models/comment/counts/empty.ts @@ -50,7 +50,6 @@ export function hasInvalidGQLCommentTagCounts( GQLTAG.REVIEW, GQLTAG.STAFF, GQLTAG.UNANSWERED, - GQLTAG.USER_EMAIL, ]; for (const key of keys) { @@ -86,7 +85,6 @@ export function createEmptyGQLCommentTagCounts(): GQLCommentTagCounts { [GQLTAG.REVIEW]: 0, [GQLTAG.STAFF]: 0, [GQLTAG.UNANSWERED]: 0, - [GQLTAG.USER_EMAIL]: 0, }; } diff --git a/server/src/core/server/services/comments/pipeline/phases/statusPreModerateUser.ts b/server/src/core/server/services/comments/pipeline/phases/statusPreModerateUser.ts index f961aefd66..fbfc758f26 100644 --- a/server/src/core/server/services/comments/pipeline/phases/statusPreModerateUser.ts +++ b/server/src/core/server/services/comments/pipeline/phases/statusPreModerateUser.ts @@ -3,10 +3,7 @@ import { IntermediatePhaseResult, } from "coral-server/services/comments/pipeline"; -import { - GQLCOMMENT_STATUS, - GQLTAG, -} from "coral-server/graph/schema/__generated__/types"; +import { GQLCOMMENT_STATUS } from "coral-server/graph/schema/__generated__/types"; // If a given user is set to always premod, set to premod. export const statusPreModerateUser: IntermediateModerationPhase = ({ @@ -16,17 +13,6 @@ export const statusPreModerateUser: IntermediateModerationPhase = ({ return; } - // if they have an active pre-mod and have the premoderated - // because of email date set, this comment is being pre-modded - // due to their email being caught by the email pre-mod filter. - // tag it so moderators can see this. - if (author.premoderatedBecauseOfEmailAt) { - return { - status: GQLCOMMENT_STATUS.PREMOD, - tags: [GQLTAG.USER_EMAIL], - }; - } - return { status: GQLCOMMENT_STATUS.PREMOD, }; diff --git a/server/src/core/server/stacks/helpers/updateAllCommentCounts.ts b/server/src/core/server/stacks/helpers/updateAllCommentCounts.ts index d358b9a9b8..98263e80d6 100644 --- a/server/src/core/server/stacks/helpers/updateAllCommentCounts.ts +++ b/server/src/core/server/stacks/helpers/updateAllCommentCounts.ts @@ -115,7 +115,6 @@ export const calculateTags = ( [GQLTAG.REVIEW]: tagChanged(before, after, GQLTAG.REVIEW), [GQLTAG.STAFF]: tagChanged(before, after, GQLTAG.STAFF), [GQLTAG.UNANSWERED]: tagChanged(before, after, GQLTAG.UNANSWERED), - [GQLTAG.USER_EMAIL]: tagChanged(before, after, GQLTAG.USER_EMAIL), }; let total = 0; From 3212ee9f911bee4bb3057c37d2ada5ecffd0ff15 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Fri, 8 Sep 2023 10:21:31 -0600 Subject: [PATCH 16/21] use premod email filter status to show `User email` marker/tag --- .../admin/components/ModerateCard/MarkersContainer.tsx | 10 ++++++++++ server/src/core/server/graph/schema/schema.graphql | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx b/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx index a775ec64aa..7bb48b20cd 100644 --- a/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx +++ b/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx @@ -33,6 +33,13 @@ const markers: Array< )) || null, + (c) => + (c.status === "PREMOD" && c.author.premoderatedBecauseOfEmailAt && ( + + User email + + )) || + null, (c) => (c.revision && c.revision.actionCounts.flag.reasons.COMMENT_DETECTED_LINKS && ( @@ -229,6 +236,9 @@ const enhanced = withFragmentContainer({ tags { code } + author { + premoderatedBecauseOfEmailAt + } revision { actionCounts { flag { diff --git a/server/src/core/server/graph/schema/schema.graphql b/server/src/core/server/graph/schema/schema.graphql index 0d5dd893b8..09696abb00 100644 --- a/server/src/core/server/graph/schema/schema.graphql +++ b/server/src/core/server/graph/schema/schema.graphql @@ -3158,6 +3158,13 @@ type User { bio is a user-defined biography. """ bio: String + + """ + premoderatedBecauseOfEmailAt is set when a user was set to pre-moderated + comments for having an email that was caught by the email pre-moderation + filter. + """ + premoderatedBecauseOfEmailAt: Time @auth(roles: [MODERATOR, ADMIN]) } """ From f40e466954c8070842d41590617db09d1226ed39 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Mon, 11 Sep 2023 12:03:14 -0600 Subject: [PATCH 17/21] add missing translation --- locales/en-US/admin.ftl | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/en-US/admin.ftl b/locales/en-US/admin.ftl index a8105c322d..7f3cc05f4e 100644 --- a/locales/en-US/admin.ftl +++ b/locales/en-US/admin.ftl @@ -1030,6 +1030,7 @@ moderate-marker-abusive = Abusive moderate-marker-newCommenter = New commenter moderate-marker-repeatPost = Repeat comment moderate-marker-other = Other +moderate-marker-preMod-userEmail = User email moderate-markers-details = Details moderate-flagDetails-latestReports = Latest reports From 876269f6df36cd7c70a2764acb2dfa04c6e7309d Mon Sep 17 00:00:00 2001 From: nick-funk Date: Mon, 11 Sep 2023 12:18:59 -0600 Subject: [PATCH 18/21] fix linting issue --- .../components/ModerateCard/MarkersContainer.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx b/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx index 7bb48b20cd..b26379ab2f 100644 --- a/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx +++ b/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx @@ -34,11 +34,13 @@ const markers: Array< )) || null, (c) => - (c.status === "PREMOD" && c.author.premoderatedBecauseOfEmailAt && ( - - User email - - )) || + (c.status === "PREMOD" && + c.author && + c.author.premoderatedBecauseOfEmailAt && ( + + User email + + )) || null, (c) => (c.revision && From 9f43c3f31e4204c1c8d6e791e8e982df95373c64 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Tue, 12 Dec 2023 09:15:53 -0700 Subject: [PATCH 19/21] add some documentation to schema for premod emails config's --- .../core/server/graph/schema/schema.graphql | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/server/src/core/server/graph/schema/schema.graphql b/server/src/core/server/graph/schema/schema.graphql index 50f2a7b695..3345039c46 100644 --- a/server/src/core/server/graph/schema/schema.graphql +++ b/server/src/core/server/graph/schema/schema.graphql @@ -1956,18 +1956,32 @@ type RTEConfiguration { } type TooManyPeriodsConfig { + """ + enabled decides whether the too many periods config is enabled or not. + """ enabled: Boolean! } type PremoderateEmailAddressConfiguration { + """ + tooManyPeriods is the configuration for premoderating emails + that have too many periods. + """ tooManyPeriods: TooManyPeriodsConfig } input TooManyPeriodsConfigInput { + """ + enabled decides whether the too many periods config is enabled or not. + """ enabled: Boolean } input PremoderateEmailAddressConfigurationInput { + """ + tooManyPeriods is the configuration for premoderating emails + that have too many periods. + """ tooManyPeriods: TooManyPeriodsConfigInput } @@ -2310,7 +2324,10 @@ type Settings @cacheControl(maxAge: 30) { """ dsa: DSAConfiguration! - + """ + premoderateEmailAddress is the configuration for finding specific email + addresses at sign up and premoderating them according to various rules. + """ premoderateEmailAddress: PremoderateEmailAddressConfiguration } @@ -6454,6 +6471,10 @@ input SettingsInput { """ dsa: DSAConfigurationInput + """ + premoderateEmailAddress is the configuration for finding specific email + addresses at sign up and premoderating them according to various rules. + """ premoderateEmailAddress: PremoderateEmailAddressConfigurationInput } From ba5e78765bc07fce2062867b4df9a9e330a0dfe5 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Tue, 12 Dec 2023 12:42:02 -0700 Subject: [PATCH 20/21] bump version to 8.6.4 --- client/package-lock.json | 4 ++-- client/package.json | 2 +- common/package-lock.json | 4 ++-- common/package.json | 2 +- config/package-lock.json | 4 ++-- config/package.json | 2 +- server/package-lock.json | 4 ++-- server/package.json | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 836cf0fda8..a10e0a3546 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "@coralproject/talk", - "version": "8.6.3", + "version": "8.6.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@coralproject/talk", - "version": "8.6.3", + "version": "8.6.4", "license": "Apache-2.0", "dependencies": { "@ampproject/toolbox-cache-url": "^2.9.0", diff --git a/client/package.json b/client/package.json index cdfcb9d206..a14be8691f 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@coralproject/talk", - "version": "8.6.3", + "version": "8.6.4", "author": "The Coral Project", "homepage": "https://coralproject.net/", "sideEffects": [ diff --git a/common/package-lock.json b/common/package-lock.json index f0a2c7e2d4..c145b0c349 100644 --- a/common/package-lock.json +++ b/common/package-lock.json @@ -1,12 +1,12 @@ { "name": "common", - "version": "8.6.3", + "version": "8.6.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "common", - "version": "8.6.3", + "version": "8.6.4", "license": "ISC", "dependencies": { "coral-config": "../config/dist", diff --git a/common/package.json b/common/package.json index e00d424ba6..946862ee4c 100644 --- a/common/package.json +++ b/common/package.json @@ -1,6 +1,6 @@ { "name": "common", - "version": "8.6.3", + "version": "8.6.4", "description": "", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/config/package-lock.json b/config/package-lock.json index cbcecb3cdd..fa6e8e4f8e 100644 --- a/config/package-lock.json +++ b/config/package-lock.json @@ -1,12 +1,12 @@ { "name": "common", - "version": "8.6.3", + "version": "8.6.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "common", - "version": "8.6.3", + "version": "8.6.4", "license": "ISC", "dependencies": { "typescript": "^3.9.5" diff --git a/config/package.json b/config/package.json index 1b005e96b4..06ec1340e4 100644 --- a/config/package.json +++ b/config/package.json @@ -1,6 +1,6 @@ { "name": "common", - "version": "8.6.3", + "version": "8.6.4", "description": "", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/server/package-lock.json b/server/package-lock.json index d833eadb75..be5c317d1f 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "@coralproject/talk", - "version": "8.6.3", + "version": "8.6.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@coralproject/talk", - "version": "8.6.3", + "version": "8.6.4", "license": "Apache-2.0", "dependencies": { "@ampproject/toolbox-cache-url": "^2.9.0", diff --git a/server/package.json b/server/package.json index 96bbeb13b3..8d00b2db9a 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@coralproject/talk", - "version": "8.6.3", + "version": "8.6.4", "author": "The Coral Project", "homepage": "https://coralproject.net/", "sideEffects": [ From d7ff58d105ac67d7d7290242ab60413c15a591a0 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Wed, 13 Dec 2023 10:40:46 -0700 Subject: [PATCH 21/21] move the email premod config above email domain config in admin --- .../Configure/sections/Moderation/ModerationConfigContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/core/client/admin/routes/Configure/sections/Moderation/ModerationConfigContainer.tsx b/client/src/core/client/admin/routes/Configure/sections/Moderation/ModerationConfigContainer.tsx index f58cff6c53..476f24a071 100644 --- a/client/src/core/client/admin/routes/Configure/sections/Moderation/ModerationConfigContainer.tsx +++ b/client/src/core/client/admin/routes/Configure/sections/Moderation/ModerationConfigContainer.tsx @@ -50,8 +50,8 @@ export const ModerationConfigContainer: React.FunctionComponent = ({ - + ); };