From 5f660a14ec54978a50ea3de30be8b2d9796ad705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daphn=C3=A9=20Popin?= Date: Wed, 29 Nov 2023 10:24:04 +0100 Subject: [PATCH] New Production check: UNknown Slack nango connection ids (#2695) * New Production check: UNknown Slack nango connection ids * Put Slack config in a set --- front/package-lock.json | 35 ++++++++ front/package.json | 1 + .../nango_connection_id_cleanup_slack.ts | 84 +++++++++++++++++++ .../production_checks/temporal/activities.ts | 5 ++ 4 files changed, 125 insertions(+) create mode 100644 front/production_checks/checks/nango_connection_id_cleanup_slack.ts diff --git a/front/package-lock.json b/front/package-lock.json index 772a153c711c..3d24f4f4e29b 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -11,6 +11,7 @@ "@headlessui/react": "^1.7.7", "@heroicons/react": "^2.0.11", "@nangohq/frontend": "^0.16.1", + "@nangohq/node": "^0.36.37", "@sendgrid/mail": "^7.7.0", "@slack/web-api": "^6.8.1", "@tailwindcss/forms": "^0.5.3", @@ -2042,6 +2043,40 @@ "resolved": "https://registry.npmjs.org/@nangohq/frontend/-/frontend-0.16.1.tgz", "integrity": "sha512-OUxl1PListmg5fJx9A54GZvsfM+BD8xpFH1zoMhDbnBrmXquXmKzVSFPlrh5lBuZue1BZuE6eERT4mWJ5ZC6Ww==" }, + "node_modules/@nangohq/node": { + "version": "0.36.37", + "resolved": "https://registry.npmjs.org/@nangohq/node/-/node-0.36.37.tgz", + "integrity": "sha512-ZlLy+if4UHnmIPWSFToJG1ccmh6OtUeZV63EBU02RTqX8xfzx6GoZaMBQEGkxnTZUqmBALjZ1fAp8zlTOtxNvg==", + "dependencies": { + "axios": "^1.2.0" + }, + "engines": { + "node": ">=16.7" + } + }, + "node_modules/@nangohq/node/node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/@nangohq/node/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@next/env": { "version": "13.5.4", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.4.tgz", diff --git a/front/package.json b/front/package.json index fa6704bff4d6..17d410d9e4ae 100644 --- a/front/package.json +++ b/front/package.json @@ -19,6 +19,7 @@ "@headlessui/react": "^1.7.7", "@heroicons/react": "^2.0.11", "@nangohq/frontend": "^0.16.1", + "@nangohq/node": "^0.36.37", "@sendgrid/mail": "^7.7.0", "@slack/web-api": "^6.8.1", "@tailwindcss/forms": "^0.5.3", diff --git a/front/production_checks/checks/nango_connection_id_cleanup_slack.ts b/front/production_checks/checks/nango_connection_id_cleanup_slack.ts new file mode 100644 index 000000000000..f0bcdbcfb893 --- /dev/null +++ b/front/production_checks/checks/nango_connection_id_cleanup_slack.ts @@ -0,0 +1,84 @@ +import { Nango } from "@nangohq/node"; +import { QueryTypes, Sequelize } from "sequelize"; + +import { CheckFunction } from "@app/production_checks/types/check"; + +const { + CONNECTORS_DATABASE_READ_REPLICA_URI, + NANGO_SECRET_KEY, + NANGO_SLACK_CONNECTOR_ID, +} = process.env; + +export const nangoConnectionIdCleanupSlack: CheckFunction = async ( + checkName, + reportSuccess, + reportFailure +) => { + if (!NANGO_SECRET_KEY) { + throw new Error("Env var NANGO_SECRET_KEY is not defined"); + } + if (!NANGO_SLACK_CONNECTOR_ID) { + throw new Error("Env var NANGO_SLACK_CONNECTOR_ID is not defined"); + } + if (!CONNECTORS_DATABASE_READ_REPLICA_URI) { + throw new Error( + "Env var CONNECTORS_DATABASE_READ_REPLICA_URI is not defined" + ); + } + + // Get all the Slack configurations in the database + const connectorsSequelize = new Sequelize( + CONNECTORS_DATABASE_READ_REPLICA_URI, + { + logging: false, + } + ); + const dbSlackConfigurationsData: { id: number; slackTeamId: string }[] = + await connectorsSequelize.query( + `SELECT id, "slackTeamId" FROM "slack_configurations"`, + { type: QueryTypes.SELECT } + ); + const dbSlackConfigurations = new Set( + dbSlackConfigurationsData.map((sc) => sc.slackTeamId) + ); + + // Get all the Slack connections in Nango (created more than 1 hour ago) + const oneHourAgo = new Date(); + oneHourAgo.setHours(oneHourAgo.getHours() - 1); + const nango = new Nango({ secretKey: NANGO_SECRET_KEY }); + const nangoConnections = await nango.listConnections(); + const nangoSlackConnections = nangoConnections.connections.filter( + (connection) => { + const createdAt = new Date(connection.created); + return ( + connection.provider === NANGO_SLACK_CONNECTOR_ID && + createdAt < oneHourAgo + ); + } + ); + + // Check that all the Slack connections in Nango have a corresponding Slack configuration in the database + const unknownNangoSlackConnections = []; + for (const conn of nangoSlackConnections) { + const connectionDetail = await nango.getConnection( + conn.provider, + conn.connection_id + ); + const slackTeamId = connectionDetail.credentials.raw.team.id; + if (!dbSlackConfigurations.has(slackTeamId)) { + unknownNangoSlackConnections.push({ + connectionId: conn.connection_id, + slackTeamId, + }); + } + } + + if (unknownNangoSlackConnections.length > 0) { + reportFailure( + { unknownConnections: unknownNangoSlackConnections }, + "Unknown Slack Teams in Nango" + ); + } else { + reportSuccess({}); + } +}; diff --git a/front/production_checks/temporal/activities.ts b/front/production_checks/temporal/activities.ts index 9cff7f52993c..09c5f6bfdec1 100644 --- a/front/production_checks/temporal/activities.ts +++ b/front/production_checks/temporal/activities.ts @@ -3,6 +3,7 @@ import { v4 as uuidv4 } from "uuid"; import mainLogger from "@app/logger/logger"; import { managedDataSourceGCGdriveCheck } from "@app/production_checks/checks/managed_data_source_gdrive_gc"; +import { nangoConnectionIdCleanupSlack } from "@app/production_checks/checks/nango_connection_id_cleanup_slack"; import { Check } from "@app/production_checks/types/check"; export async function runAllChecksActivity() { @@ -11,6 +12,10 @@ export async function runAllChecksActivity() { name: "managed_data_source_gdrive_gc", check: managedDataSourceGCGdriveCheck, }, + { + name: "nango_connection_id_cleanup_slack", + check: nangoConnectionIdCleanupSlack, + }, ]; await runAllChecks(checks); }