Skip to content

Commit 997b0d6

Browse files
committed
fix: reject mailing list connect if already owned by another project (CM-1318)
Signed-off-by: Uroš Marolt <uros@marolt.me>
1 parent 6461853 commit 997b0d6

3 files changed

Lines changed: 55 additions & 2 deletions

File tree

backend/src/services/integrationService.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import {
1616
} from '@crowd/common'
1717
import { CommonIntegrationService, getGithubInstallationToken } from '@crowd/common_services'
1818
import { ICreateInsightsProject } from '@crowd/data-access-layer/src/collections'
19-
import { upsertMailingLists } from '@crowd/data-access-layer/src/mailinglist'
19+
import {
20+
findMailingListsOwnedByOtherIntegration,
21+
upsertMailingLists,
22+
} from '@crowd/data-access-layer/src/mailinglist'
2023
import {
2124
ICreateRepository,
2225
IRepository,
@@ -1452,6 +1455,8 @@ export default class IntegrationService {
14521455
let integration
14531456

14541457
try {
1458+
const qx = SequelizeRepository.getQueryExecutor({ ...(options || this.options), transaction })
1459+
14551460
integration = await this.createOrUpdate(
14561461
{
14571462
platform: PlatformType.MAILINGLIST,
@@ -1462,8 +1467,16 @@ export default class IntegrationService {
14621467
options,
14631468
)
14641469

1470+
const conflicts = await findMailingListsOwnedByOtherIntegration(qx, integration.id, lists)
1471+
if (conflicts.length > 0) {
1472+
throw new Error400(
1473+
this.options.language,
1474+
'errors.mailingList.alreadyConnected',
1475+
conflicts.join(', '),
1476+
)
1477+
}
1478+
14651479
const currentSegmentId = (options || this.options).currentSegments[0].id
1466-
const qx = SequelizeRepository.getQueryExecutor({ ...(options || this.options), transaction })
14671480
await upsertMailingLists(qx, currentSegmentId, integration.id, lists)
14681481

14691482
if (!existingTransaction) {

services/libs/common/src/i18n/en.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ const en = {
182182
invalidCredentials: 'Invalid email or password',
183183
invalid2FA: 'Invalid Two-factor authentication code',
184184
},
185+
mailingList: {
186+
alreadyConnected:
187+
'The following mailing lists are already connected to another project: {0}',
188+
},
185189
alreadyExists: '{0}',
186190
organization: {
187191
unmerge: {

services/libs/data-access-layer/src/mailinglist/index.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,42 @@ export interface IMailingListToOnboard {
55
sourceUrl: string
66
}
77

8+
/**
9+
* Find sourceUrls already onboarded under a different, non-deleted
10+
* integration. Used to reject a connect/update call before it silently
11+
* re-points an already-owned list to the calling integration.
12+
* @param qx - Query executor
13+
* @param integrationId - Integration attempting to onboard `lists`
14+
* @param lists - Mailing lists the caller wants to onboard
15+
*/
16+
export async function findMailingListsOwnedByOtherIntegration(
17+
qx: QueryExecutor,
18+
integrationId: string,
19+
lists: IMailingListToOnboard[],
20+
): Promise<string[]> {
21+
if (lists.length === 0) {
22+
return []
23+
}
24+
25+
const rows = await qx.select(
26+
`
27+
SELECT "sourceUrl"
28+
FROM mailinglist.lists
29+
WHERE "integrationId" != $(integrationId)::uuid
30+
AND "deletedAt" IS NULL
31+
AND "sourceUrl" IN (
32+
SELECT v."sourceUrl" FROM json_to_recordset($(lists)::json) AS v(name text, "sourceUrl" text)
33+
)
34+
`,
35+
{
36+
integrationId,
37+
lists: JSON.stringify(lists),
38+
},
39+
)
40+
41+
return rows.map((row: { sourceUrl: string }) => row.sourceUrl)
42+
}
43+
844
/**
945
* Upsert mailing lists (public-inbox/lore) for a segment/integration and
1046
* seed their processing state so the mailing_list_integration worker picks

0 commit comments

Comments
 (0)