diff --git a/packages/api/src/@core/connections/connections.module.ts b/packages/api/src/@core/connections/connections.module.ts index 80c2a0d71..188d25c6c 100644 --- a/packages/api/src/@core/connections/connections.module.ts +++ b/packages/api/src/@core/connections/connections.module.ts @@ -12,6 +12,7 @@ import { ProductivityConnectionsModule } from './productivity/productivity.conne import { MarketingAutomationConnectionsModule } from './marketingautomation/marketingautomation.connection.module'; import { TicketingConnectionModule } from './ticketing/ticketing.connection.module'; import { EcommerceConnectionModule } from './ecommerce/ecommerce.connection.module'; +import { CybersecurityConnectionsModule } from './cybersecurity/cybersecurity.connection.module'; @Module({ controllers: [ConnectionsController], @@ -25,6 +26,7 @@ import { EcommerceConnectionModule } from './ecommerce/ecommerce.connection.modu FilestorageConnectionModule, HrisConnectionModule, EcommerceConnectionModule, + CybersecurityConnectionsModule, SyncModule, ], providers: [ValidateUserService, OAuthTokenRefreshService], @@ -40,6 +42,7 @@ import { EcommerceConnectionModule } from './ecommerce/ecommerce.connection.modu EcommerceConnectionModule, HrisConnectionModule, ProductivityConnectionsModule, + CybersecurityConnectionsModule, ], }) export class ConnectionsModule {} diff --git a/packages/api/src/@core/connections/cybersecurity/cybersecurity.connection.module.ts b/packages/api/src/@core/connections/cybersecurity/cybersecurity.connection.module.ts new file mode 100644 index 000000000..65508349f --- /dev/null +++ b/packages/api/src/@core/connections/cybersecurity/cybersecurity.connection.module.ts @@ -0,0 +1,38 @@ +import { EnvironmentService } from '@@core/@core-services/environment/environment.service'; +import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; +import { WebhookModule } from '@@core/@core-services/webhooks/panora-webhooks/webhook.module'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { Module } from '@nestjs/common'; +import { CybersecurityConnectionsService } from './services/cybersecurity.connection.service'; +import { ServiceRegistry } from './services/registry.service'; +import { TenableConnectionService } from './services/tenable/tenable.service'; +import { QualysConnectionService } from './services/qualys/qualys.service'; +import { SemgrepConnectionService } from './services/semgrep/semgrep.service'; +import { SentineloneConnectionService } from './services/sentinelone/sentinelone.service'; +import { Rapid7ConnectionService } from './services/rapid7insightvm/rapid7.service'; +import { SnykConnectionService } from './services/snyk/snyk.service'; +import { CrowdstrikeConnectionService } from './services/crowdstrike/crowdstrike.service'; +import { MicrosoftdefenderConnectionService } from './services/microsoftdefender/microsoftdefender.service'; + +@Module({ + imports: [WebhookModule, BullQueueModule], + providers: [ + CybersecurityConnectionsService, + WebhookService, + EnvironmentService, + ServiceRegistry, + ConnectionsStrategiesService, + //PROVIDERS SERVICES + SemgrepConnectionService, + TenableConnectionService, + QualysConnectionService, + SentineloneConnectionService, + Rapid7ConnectionService, + SnykConnectionService, + CrowdstrikeConnectionService, + MicrosoftdefenderConnectionService, + ], + exports: [CybersecurityConnectionsService], +}) +export class CybersecurityConnectionsModule {} diff --git a/packages/api/src/@core/connections/cybersecurity/services/crowdstrike/crowdstrike.service.ts b/packages/api/src/@core/connections/cybersecurity/services/crowdstrike/crowdstrike.service.ts new file mode 100644 index 000000000..4fcff71f9 --- /dev/null +++ b/packages/api/src/@core/connections/cybersecurity/services/crowdstrike/crowdstrike.service.ts @@ -0,0 +1,195 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { RetryHandler } from '@@core/@core-services/request-retry/retry.handler'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { ConnectionUtils } from '@@core/connections/@utils'; +import { + AbstractBaseConnectionService, + OAuthCallbackParams, + PassthroughInput, + RefreshParams, +} from '@@core/connections/@utils/types'; +import { PassthroughResponse } from '@@core/passthrough/types'; +import { Injectable } from '@nestjs/common'; +import { + AuthStrategy, + CONNECTORS_METADATA, + OAuth2AuthData, + providerToType, +} from '@panora/shared'; +import { v4 as uuidv4 } from 'uuid'; +import { ServiceRegistry } from '../registry.service'; +import { EnvironmentService } from '@@core/@core-services/environment/environment.service'; +import axios from 'axios'; + +export interface CrowdstrikeOAuthResponse { + access_token: string; + token_type: string; + scope: string; +} + +@Injectable() +export class CrowdstrikeConnectionService extends AbstractBaseConnectionService { + private readonly type: string; + + constructor( + protected prisma: PrismaService, + private logger: LoggerService, + protected cryptoService: EncryptionService, + private env: EnvironmentService, + private registry: ServiceRegistry, + private connectionUtils: ConnectionUtils, + private cService: ConnectionsStrategiesService, + private retryService: RetryHandler, + ) { + super(prisma, cryptoService); + this.logger.setContext(CrowdstrikeConnectionService.name); + this.registry.registerService('crowdstrike', this); + this.type = providerToType( + 'crowdstrike', + 'cybersecurity', + AuthStrategy.oauth2, + ); + } + + async passthrough( + input: PassthroughInput, + connectionId: string, + ): Promise { + try { + const { headers } = input; + const config = await this.constructPassthrough(input, connectionId); + + const connection = await this.prisma.connections.findUnique({ + where: { + id_connection: connectionId, + }, + }); + + const access_token = JSON.parse( + this.cryptoService.decrypt(connection.access_token), + ); + config.headers = { + ...config.headers, + ...headers, + Authorization: `Bearer ${access_token}`, + }; + + return await this.retryService.makeRequest( + { + method: config.method, + url: config.url, + data: config.data, + headers: config.headers, + }, + 'cybersecurity.crowdstrike.passthrough', + config.linkedUserId, + ); + } catch (error) { + throw error; + } + } + + async handleCallback(opts: OAuthCallbackParams) { + try { + const { linkedUserId, projectId, code } = opts; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'crowdstrike', + vertical: 'cybersecurity', + }, + }); + //reconstruct the redirect URI that was passed in the frontend it must be the same + const REDIRECT_URI = `${ + this.env.getDistributionMode() == 'selfhost' + ? this.env.getTunnelIngress() + : this.env.getPanoraBaseUrl() + }/connections/oauth/callback`; + + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const formData = new URLSearchParams({ + redirect_uri: REDIRECT_URI, + client_id: CREDENTIALS.CLIENT_ID, + client_secret: CREDENTIALS.CLIENT_SECRET, + code: code, + grant_type: 'authorization_code', + }); + const res = await axios.post( + 'https://api.crowdstrike.com/oauth/access_token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: CrowdstrikeOAuthResponse = res.data; + // save tokens for this customer inside our db + let db_res; + const connection_token = uuidv4(); + const BASE_API_URL = CONNECTORS_METADATA['cybersecurity']['crowdstrike'] + .urls.apiUrl as string; + // get the site id for the token + const site = await axios.get('https://api.crowdstrike.com/v2/sites', { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Bearer ${data.access_token}`, + }, + }); + const site_id = site.data.sites[0].id; + if (isNotUnique) { + // Update existing connection + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + status: 'valid', + created_at: new Date(), + }, + }); + } else { + // Create new connection + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'crowdstrike', + vertical: 'cybersecurity', + token_type: 'oauth2', + account_url: `${BASE_API_URL}/sites/${site_id}`, + access_token: this.cryptoService.encrypt(data.access_token), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { + id_linked_user: await this.connectionUtils.getLinkedUserId( + projectId, + linkedUserId, + ), + }, + }, + }, + }); + } + this.logger.log('Successfully added tokens inside DB ' + db_res); + return db_res; + } catch (error) { + throw error; + } + } + + async handleTokenRefresh(opts: RefreshParams) { + return; + } +} diff --git a/packages/api/src/@core/connections/cybersecurity/services/cybersecurity.connection.service.ts b/packages/api/src/@core/connections/cybersecurity/services/cybersecurity.connection.service.ts new file mode 100644 index 000000000..586f81b2e --- /dev/null +++ b/packages/api/src/@core/connections/cybersecurity/services/cybersecurity.connection.service.ts @@ -0,0 +1,129 @@ +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { + CallbackParams, + IConnectionCategory, + PassthroughInput, + RefreshParams, +} from '@@core/connections/@utils/types'; +import { Injectable } from '@nestjs/common'; +import { connections as Connection } from '@prisma/client'; +import { v4 as uuidv4 } from 'uuid'; +import { ServiceRegistry } from './registry.service'; +import { CategoryConnectionRegistry } from '@@core/@core-services/registries/connections-categories.registry'; +import { PassthroughResponse } from '@@core/passthrough/types'; + +@Injectable() +export class CybersecurityConnectionsService implements IConnectionCategory { + constructor( + private serviceRegistry: ServiceRegistry, + private connectionCategoryRegistry: CategoryConnectionRegistry, + private webhook: WebhookService, + private logger: LoggerService, + private prisma: PrismaService, + ) { + this.logger.setContext(CybersecurityConnectionsService.name); + this.connectionCategoryRegistry.registerService('cybersecurity', this); + } + //STEP 1:[FRONTEND STEP] + //create a frontend SDK snippet in which an authorization embedded link is set up so when users click + // on it to grant access => they grant US the access and then when confirmed + /*const authUrl = + 'https://app.hubspot.com/oauth/authorize' + + `?client_id=${encodeURIComponent(CLIENT_ID)}` + + `&scope=${encodeURIComponent(SCOPES)}` + + `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`;*/ //oauth/callback + + // oauth server calls this redirect callback + // WE WOULD HAVE CREATED A DEV ACCOUNT IN THE 5 CRMs (Panora dev account) + // we catch the tmp token and swap it against oauth2 server for access/refresh tokens + // to perform actions on his behalf + // this call pass 1. integrationID 2. CustomerId 3. Panora Api Key + async handleCallBack( + providerName: string, + callbackOpts: CallbackParams, + type_strategy: 'oauth2' | 'apikey' | 'basic', + ) { + try { + const serviceName = providerName.toLowerCase(); + + const service = this.serviceRegistry.getService(serviceName); + + if (!service) { + throw new ReferenceError(`Unknown provider, found ${providerName}`); + } + const data: Connection = await service.handleCallback(callbackOpts); + const event = await this.prisma.events.create({ + data: { + id_connection: data.id_connection, + id_project: data.id_project, + id_event: uuidv4(), + status: 'success', + type: 'connection.created', + method: 'GET', + url: `/${type_strategy}/callback`, + provider: providerName.toLowerCase(), + direction: '0', + timestamp: new Date(), + id_linked_user: callbackOpts.linkedUserId, + }, + }); + //directly send the webhook + await this.webhook.dispatchWebhook( + data, + 'connection.created', + callbackOpts.projectId, + event.id_event, + ); + } catch (error) { + throw error; + } + } + + async handleTokensRefresh( + connectionId: string, + providerName: string, + refresh_token: string, + id_project: string, + account_url?: string, + ) { + try { + const serviceName = providerName.toLowerCase(); + const service = this.serviceRegistry.getService(serviceName); + if (!service) { + throw new ReferenceError(`Unknown provider, found ${providerName}`); + } + const refreshOpts: RefreshParams = { + connectionId: connectionId, + refreshToken: refresh_token, + account_url: account_url, + projectId: id_project, + }; + await service.handleTokenRefresh(refreshOpts); + } catch (error) { + throw error; + } + } + + async passthrough( + input: PassthroughInput, + connectionId: string, + ): Promise { + try { + const connection = await this.prisma.connections.findUnique({ + where: { + id_connection: connectionId, + }, + }); + const serviceName = connection.provider_slug.toLowerCase(); + const service = this.serviceRegistry.getService(serviceName); + if (!service) { + throw new ReferenceError(`Unknown provider, found ${serviceName}`); + } + return await service.passthrough(input, connectionId); + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/@core/connections/cybersecurity/services/microsoftdefender/microsoftdefender.service.ts b/packages/api/src/@core/connections/cybersecurity/services/microsoftdefender/microsoftdefender.service.ts new file mode 100644 index 000000000..c874a92ba --- /dev/null +++ b/packages/api/src/@core/connections/cybersecurity/services/microsoftdefender/microsoftdefender.service.ts @@ -0,0 +1,199 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { RetryHandler } from '@@core/@core-services/request-retry/retry.handler'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { ConnectionUtils } from '@@core/connections/@utils'; +import { + AbstractBaseConnectionService, + OAuthCallbackParams, + PassthroughInput, + RefreshParams, +} from '@@core/connections/@utils/types'; +import { PassthroughResponse } from '@@core/passthrough/types'; +import { Injectable } from '@nestjs/common'; +import { + AuthStrategy, + CONNECTORS_METADATA, + OAuth2AuthData, + providerToType, +} from '@panora/shared'; +import { v4 as uuidv4 } from 'uuid'; +import { ServiceRegistry } from '../registry.service'; +import { EnvironmentService } from '@@core/@core-services/environment/environment.service'; +import axios from 'axios'; + +export interface MicrosoftdefenderOAuthResponse { + access_token: string; + token_type: string; + scope: string; +} + +@Injectable() +export class MicrosoftdefenderConnectionService extends AbstractBaseConnectionService { + private readonly type: string; + + constructor( + protected prisma: PrismaService, + private logger: LoggerService, + protected cryptoService: EncryptionService, + private env: EnvironmentService, + private registry: ServiceRegistry, + private connectionUtils: ConnectionUtils, + private cService: ConnectionsStrategiesService, + private retryService: RetryHandler, + ) { + super(prisma, cryptoService); + this.logger.setContext(MicrosoftdefenderConnectionService.name); + this.registry.registerService('microsoftdefender', this); + this.type = providerToType( + 'microsoftdefender', + 'cybersecurity', + AuthStrategy.oauth2, + ); + } + + async passthrough( + input: PassthroughInput, + connectionId: string, + ): Promise { + try { + const { headers } = input; + const config = await this.constructPassthrough(input, connectionId); + + const connection = await this.prisma.connections.findUnique({ + where: { + id_connection: connectionId, + }, + }); + + const access_token = JSON.parse( + this.cryptoService.decrypt(connection.access_token), + ); + config.headers = { + ...config.headers, + ...headers, + Authorization: `Bearer ${access_token}`, + }; + + return await this.retryService.makeRequest( + { + method: config.method, + url: config.url, + data: config.data, + headers: config.headers, + }, + 'cybersecurity.microsoftdefender.passthrough', + config.linkedUserId, + ); + } catch (error) { + throw error; + } + } + + async handleCallback(opts: OAuthCallbackParams) { + try { + const { linkedUserId, projectId, code } = opts; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'microsoftdefender', + vertical: 'cybersecurity', + }, + }); + //reconstruct the redirect URI that was passed in the frontend it must be the same + const REDIRECT_URI = `${ + this.env.getDistributionMode() == 'selfhost' + ? this.env.getTunnelIngress() + : this.env.getPanoraBaseUrl() + }/connections/oauth/callback`; + + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const formData = new URLSearchParams({ + redirect_uri: REDIRECT_URI, + client_id: CREDENTIALS.CLIENT_ID, + client_secret: CREDENTIALS.CLIENT_SECRET, + code: code, + grant_type: 'authorization_code', + }); + const res = await axios.post( + 'https://api.microsoftdefender.com/oauth/access_token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: MicrosoftdefenderOAuthResponse = res.data; + // save tokens for this customer inside our db + let db_res; + const connection_token = uuidv4(); + const BASE_API_URL = CONNECTORS_METADATA['cybersecurity'][ + 'microsoftdefender' + ].urls.apiUrl as string; + // get the site id for the token + const site = await axios.get( + 'https://api.microsoftdefender.com/v2/sites', + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Bearer ${data.access_token}`, + }, + }, + ); + const site_id = site.data.sites[0].id; + if (isNotUnique) { + // Update existing connection + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + status: 'valid', + created_at: new Date(), + }, + }); + } else { + // Create new connection + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'microsoftdefender', + vertical: 'cybersecurity', + token_type: 'oauth2', + account_url: `${BASE_API_URL}/sites/${site_id}`, + access_token: this.cryptoService.encrypt(data.access_token), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { + id_linked_user: await this.connectionUtils.getLinkedUserId( + projectId, + linkedUserId, + ), + }, + }, + }, + }); + } + this.logger.log('Successfully added tokens inside DB ' + db_res); + return db_res; + } catch (error) { + throw error; + } + } + + async handleTokenRefresh(opts: RefreshParams) { + return; + } +} diff --git a/packages/api/src/@core/connections/cybersecurity/services/qualys/qualys.service.ts b/packages/api/src/@core/connections/cybersecurity/services/qualys/qualys.service.ts new file mode 100644 index 000000000..7840147b9 --- /dev/null +++ b/packages/api/src/@core/connections/cybersecurity/services/qualys/qualys.service.ts @@ -0,0 +1,156 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { RetryHandler } from '@@core/@core-services/request-retry/retry.handler'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { ConnectionUtils } from '@@core/connections/@utils'; +import { + AbstractBaseConnectionService, + OAuthCallbackParams, + PassthroughInput, + RefreshParams, +} from '@@core/connections/@utils/types'; +import { PassthroughResponse } from '@@core/passthrough/types'; +import { Injectable } from '@nestjs/common'; +import { + AuthStrategy, + CONNECTORS_METADATA, + DynamicApiUrl, + providerToType, +} from '@panora/shared'; +import { v4 as uuidv4 } from 'uuid'; +import { ServiceRegistry } from '../registry.service'; + +@Injectable() +export class QualysConnectionService extends AbstractBaseConnectionService { + private readonly type: string; + + constructor( + protected prisma: PrismaService, + private logger: LoggerService, + protected cryptoService: EncryptionService, + private registry: ServiceRegistry, + private connectionUtils: ConnectionUtils, + private cService: ConnectionsStrategiesService, + private retryService: RetryHandler, + ) { + super(prisma, cryptoService); + this.logger.setContext(QualysConnectionService.name); + this.registry.registerService('qualys', this); + this.type = providerToType('qualys', 'cybersecurity', AuthStrategy.oauth2); + } + + async passthrough( + input: PassthroughInput, + connectionId: string, + ): Promise { + try { + const { headers } = input; + const config = await this.constructPassthrough(input, connectionId); + + const connection = await this.prisma.connections.findUnique({ + where: { + id_connection: connectionId, + }, + }); + const decryptedData = JSON.parse( + this.cryptoService.decrypt(connection.access_token), + ); + + const { username, password } = decryptedData; + + config.headers = { + ...config.headers, + ...headers, + 'X-Requested-With': 'Curl Sample', + Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString( + 'base64', + )}`, + }; + + return await this.retryService.makeRequest( + { + method: config.method, + url: config.url, + data: config.data, + headers: config.headers, + }, + 'cybersecurity.qualys.passthrough', + config.linkedUserId, + ); + } catch (error) { + throw error; + } + } + + async handleCallback(opts: OAuthCallbackParams) { + try { + const { linkedUserId, projectId, body } = opts; + const { username, password, api_url } = body; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'qualys', + vertical: 'cybersecurity', + }, + }); + + let db_res; + const connection_token = uuidv4(); + const BASE_API_URL = ( + CONNECTORS_METADATA['cybersecurity']['qualys'].urls + .apiUrl as DynamicApiUrl + )(api_url); + + if (isNotUnique) { + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt( + JSON.stringify({ username, password }), + ), + account_url: BASE_API_URL, + status: 'valid', + created_at: new Date(), + }, + }); + } else { + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'qualys', + vertical: 'cybersecurity', + token_type: 'basic', + account_url: BASE_API_URL, + access_token: this.cryptoService.encrypt( + JSON.stringify({ username, password }), + ), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { + id_linked_user: await this.connectionUtils.getLinkedUserId( + projectId, + linkedUserId, + ), + }, + }, + }, + }); + } + return db_res; + } catch (error) { + throw error; + } + } + + handleTokenRefresh?(opts: RefreshParams): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/api/src/@core/connections/cybersecurity/services/rapid7insightvm/rapid7.service.ts b/packages/api/src/@core/connections/cybersecurity/services/rapid7insightvm/rapid7.service.ts new file mode 100644 index 000000000..12e5cd767 --- /dev/null +++ b/packages/api/src/@core/connections/cybersecurity/services/rapid7insightvm/rapid7.service.ts @@ -0,0 +1,145 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { RetryHandler } from '@@core/@core-services/request-retry/retry.handler'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { ConnectionUtils } from '@@core/connections/@utils'; +import { + AbstractBaseConnectionService, + OAuthCallbackParams, + PassthroughInput, + RefreshParams, +} from '@@core/connections/@utils/types'; +import { PassthroughResponse } from '@@core/passthrough/types'; +import { Injectable } from '@nestjs/common'; +import { + AuthStrategy, + CONNECTORS_METADATA, + DynamicApiUrl, + providerToType, +} from '@panora/shared'; +import { v4 as uuidv4 } from 'uuid'; +import { ServiceRegistry } from '../registry.service'; + +@Injectable() +export class Rapid7ConnectionService extends AbstractBaseConnectionService { + private readonly type: string; + + constructor( + protected prisma: PrismaService, + private logger: LoggerService, + protected cryptoService: EncryptionService, + private registry: ServiceRegistry, + private connectionUtils: ConnectionUtils, + private cService: ConnectionsStrategiesService, + private retryService: RetryHandler, + ) { + super(prisma, cryptoService); + this.logger.setContext(Rapid7ConnectionService.name); + this.registry.registerService('rapid7', this); + this.type = providerToType('rapid7', 'cybersecurity', AuthStrategy.oauth2); + } + + async passthrough( + input: PassthroughInput, + connectionId: string, + ): Promise { + try { + const { headers } = input; + const config = await this.constructPassthrough(input, connectionId); + + const connection = await this.prisma.connections.findUnique({ + where: { + id_connection: connectionId, + }, + }); + config.headers = { + ...config.headers, + ...headers, + Authorization: `Basic ${Buffer.from( + `:${connection.access_token}`, + ).toString('base64')}`, + }; + + return await this.retryService.makeRequest( + { + method: config.method, + url: config.url, + data: config.data, + headers: config.headers, + }, + 'cybersecurity.rapid7.passthrough', + config.linkedUserId, + ); + } catch (error) { + throw error; + } + } + + async handleCallback(opts: OAuthCallbackParams) { + try { + const { linkedUserId, projectId, body } = opts; + const { api_key, region } = body; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'rapid7', + vertical: 'cybersecurity', + }, + }); + + let db_res; + const connection_token = uuidv4(); + const BASE_API_URL = ( + CONNECTORS_METADATA['cybersecurity']['rapid7'].urls + .apiUrl as DynamicApiUrl + )(region); + + if (isNotUnique) { + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(api_key), + account_url: BASE_API_URL, + status: 'valid', + created_at: new Date(), + }, + }); + } else { + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'rapid7', + vertical: 'cybersecurity', + token_type: 'basic', + account_url: BASE_API_URL, + access_token: this.cryptoService.encrypt(api_key), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { + id_linked_user: await this.connectionUtils.getLinkedUserId( + projectId, + linkedUserId, + ), + }, + }, + }, + }); + } + return db_res; + } catch (error) { + throw error; + } + } + + handleTokenRefresh?(opts: RefreshParams): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/api/src/@core/connections/cybersecurity/services/registry.service.ts b/packages/api/src/@core/connections/cybersecurity/services/registry.service.ts new file mode 100644 index 000000000..2e9e3b100 --- /dev/null +++ b/packages/api/src/@core/connections/cybersecurity/services/registry.service.ts @@ -0,0 +1,25 @@ +import { IConnectionService } from '@@core/connections/@utils/types'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class ServiceRegistry { + private serviceMap: Map; + + constructor() { + this.serviceMap = new Map(); + } + + registerService(serviceKey: string, service: IConnectionService) { + this.serviceMap.set(serviceKey, service); + } + + getService(integrationId: string): IConnectionService { + const service = this.serviceMap.get(integrationId); + if (!service) { + throw new ReferenceError( + `Service not found for integration ID: ${integrationId}`, + ); + } + return service; + } +} diff --git a/packages/api/src/@core/connections/cybersecurity/services/semgrep/semgrep.service.ts b/packages/api/src/@core/connections/cybersecurity/services/semgrep/semgrep.service.ts new file mode 100644 index 000000000..7bd36d5d0 --- /dev/null +++ b/packages/api/src/@core/connections/cybersecurity/services/semgrep/semgrep.service.ts @@ -0,0 +1,143 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { RetryHandler } from '@@core/@core-services/request-retry/retry.handler'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { ConnectionUtils } from '@@core/connections/@utils'; +import { + AbstractBaseConnectionService, + OAuthCallbackParams, + PassthroughInput, + RefreshParams, +} from '@@core/connections/@utils/types'; +import { PassthroughResponse } from '@@core/passthrough/types'; +import { Injectable } from '@nestjs/common'; +import { + AuthStrategy, + CONNECTORS_METADATA, + DynamicApiUrl, + providerToType, +} from '@panora/shared'; +import { v4 as uuidv4 } from 'uuid'; +import { ServiceRegistry } from '../registry.service'; + +@Injectable() +export class SemgrepConnectionService extends AbstractBaseConnectionService { + private readonly type: string; + + constructor( + protected prisma: PrismaService, + private logger: LoggerService, + protected cryptoService: EncryptionService, + private registry: ServiceRegistry, + private connectionUtils: ConnectionUtils, + private cService: ConnectionsStrategiesService, + private retryService: RetryHandler, + ) { + super(prisma, cryptoService); + this.logger.setContext(SemgrepConnectionService.name); + this.registry.registerService('semgrep', this); + this.type = providerToType('semgrep', 'cybersecurity', AuthStrategy.oauth2); + } + + async passthrough( + input: PassthroughInput, + connectionId: string, + ): Promise { + try { + const { headers } = input; + const config = await this.constructPassthrough(input, connectionId); + + const connection = await this.prisma.connections.findUnique({ + where: { + id_connection: connectionId, + }, + }); + config.headers = { + ...config.headers, + ...headers, + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }; + + return await this.retryService.makeRequest( + { + method: config.method, + url: config.url, + data: config.data, + headers: config.headers, + }, + 'cybersecurity.semgrep.passthrough', + config.linkedUserId, + ); + } catch (error) { + throw error; + } + } + + async handleCallback(opts: OAuthCallbackParams) { + try { + const { linkedUserId, projectId, body } = opts; + const { api_key } = body; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'semgrep', + vertical: 'cybersecurity', + }, + }); + + let db_res; + const connection_token = uuidv4(); + const BASE_API_URL = CONNECTORS_METADATA['cybersecurity']['semgrep'].urls + .apiUrl as string; + + if (isNotUnique) { + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(api_key), + account_url: BASE_API_URL, + status: 'valid', + created_at: new Date(), + }, + }); + } else { + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'semgrep', + vertical: 'cybersecurity', + token_type: 'basic', + account_url: BASE_API_URL, + access_token: this.cryptoService.encrypt(api_key), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { + id_linked_user: await this.connectionUtils.getLinkedUserId( + projectId, + linkedUserId, + ), + }, + }, + }, + }); + } + return db_res; + } catch (error) { + throw error; + } + } + + handleTokenRefresh?(opts: RefreshParams): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/api/src/@core/connections/cybersecurity/services/sentinelone/sentinelone.service.ts b/packages/api/src/@core/connections/cybersecurity/services/sentinelone/sentinelone.service.ts new file mode 100644 index 000000000..7d6f02fb5 --- /dev/null +++ b/packages/api/src/@core/connections/cybersecurity/services/sentinelone/sentinelone.service.ts @@ -0,0 +1,149 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { RetryHandler } from '@@core/@core-services/request-retry/retry.handler'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { ConnectionUtils } from '@@core/connections/@utils'; +import { + AbstractBaseConnectionService, + OAuthCallbackParams, + PassthroughInput, + RefreshParams, +} from '@@core/connections/@utils/types'; +import { PassthroughResponse } from '@@core/passthrough/types'; +import { Injectable } from '@nestjs/common'; +import { + AuthStrategy, + CONNECTORS_METADATA, + DynamicApiUrl, + providerToType, +} from '@panora/shared'; +import { v4 as uuidv4 } from 'uuid'; +import { ServiceRegistry } from '../registry.service'; + +@Injectable() +export class SentineloneConnectionService extends AbstractBaseConnectionService { + private readonly type: string; + + constructor( + protected prisma: PrismaService, + private logger: LoggerService, + protected cryptoService: EncryptionService, + private registry: ServiceRegistry, + private connectionUtils: ConnectionUtils, + private cService: ConnectionsStrategiesService, + private retryService: RetryHandler, + ) { + super(prisma, cryptoService); + this.logger.setContext(SentineloneConnectionService.name); + this.registry.registerService('sentinelone', this); + this.type = providerToType( + 'sentinelone', + 'cybersecurity', + AuthStrategy.oauth2, + ); + } + + async passthrough( + input: PassthroughInput, + connectionId: string, + ): Promise { + try { + const { headers } = input; + const config = await this.constructPassthrough(input, connectionId); + + const connection = await this.prisma.connections.findUnique({ + where: { + id_connection: connectionId, + }, + }); + config.headers = { + ...config.headers, + ...headers, + Authorization: `APIToken ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }; + + return await this.retryService.makeRequest( + { + method: config.method, + url: config.url, + data: config.data, + headers: config.headers, + }, + 'cybersecurity.sentinelone.passthrough', + config.linkedUserId, + ); + } catch (error) { + throw error; + } + } + + async handleCallback(opts: OAuthCallbackParams) { + try { + const { linkedUserId, projectId, body } = opts; + const { api_key, host } = body; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'sentinelone', + vertical: 'cybersecurity', + }, + }); + + let db_res; + const connection_token = uuidv4(); + const BASE_API_URL = ( + CONNECTORS_METADATA['cybersecurity']['sentinelone'].urls + .apiUrl as DynamicApiUrl + )(host); + + if (isNotUnique) { + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(api_key), + account_url: BASE_API_URL, + status: 'valid', + created_at: new Date(), + }, + }); + } else { + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'sentinelone', + vertical: 'cybersecurity', + token_type: 'basic', + account_url: BASE_API_URL, + access_token: this.cryptoService.encrypt(api_key), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { + id_linked_user: await this.connectionUtils.getLinkedUserId( + projectId, + linkedUserId, + ), + }, + }, + }, + }); + } + return db_res; + } catch (error) { + throw error; + } + } + + handleTokenRefresh?(opts: RefreshParams): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/api/src/@core/connections/cybersecurity/services/snyk/snyk.service.ts b/packages/api/src/@core/connections/cybersecurity/services/snyk/snyk.service.ts new file mode 100644 index 000000000..ff77211ce --- /dev/null +++ b/packages/api/src/@core/connections/cybersecurity/services/snyk/snyk.service.ts @@ -0,0 +1,191 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { RetryHandler } from '@@core/@core-services/request-retry/retry.handler'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { ConnectionUtils } from '@@core/connections/@utils'; +import { + AbstractBaseConnectionService, + OAuthCallbackParams, + PassthroughInput, + RefreshParams, +} from '@@core/connections/@utils/types'; +import { PassthroughResponse } from '@@core/passthrough/types'; +import { Injectable } from '@nestjs/common'; +import { + AuthStrategy, + CONNECTORS_METADATA, + OAuth2AuthData, + providerToType, +} from '@panora/shared'; +import { v4 as uuidv4 } from 'uuid'; +import { ServiceRegistry } from '../registry.service'; +import { EnvironmentService } from '@@core/@core-services/environment/environment.service'; +import axios from 'axios'; + +export interface SnykOAuthResponse { + access_token: string; + token_type: string; + scope: string; +} + +@Injectable() +export class SnykConnectionService extends AbstractBaseConnectionService { + private readonly type: string; + + constructor( + protected prisma: PrismaService, + private logger: LoggerService, + protected cryptoService: EncryptionService, + private env: EnvironmentService, + private registry: ServiceRegistry, + private connectionUtils: ConnectionUtils, + private cService: ConnectionsStrategiesService, + private retryService: RetryHandler, + ) { + super(prisma, cryptoService); + this.logger.setContext(SnykConnectionService.name); + this.registry.registerService('snyk', this); + this.type = providerToType('snyk', 'cybersecurity', AuthStrategy.oauth2); + } + + async passthrough( + input: PassthroughInput, + connectionId: string, + ): Promise { + try { + const { headers } = input; + const config = await this.constructPassthrough(input, connectionId); + + const connection = await this.prisma.connections.findUnique({ + where: { + id_connection: connectionId, + }, + }); + + const access_token = JSON.parse( + this.cryptoService.decrypt(connection.access_token), + ); + config.headers = { + ...config.headers, + ...headers, + Authorization: `Bearer ${access_token}`, + }; + + return await this.retryService.makeRequest( + { + method: config.method, + url: config.url, + data: config.data, + headers: config.headers, + }, + 'cybersecurity.snyk.passthrough', + config.linkedUserId, + ); + } catch (error) { + throw error; + } + } + + async handleCallback(opts: OAuthCallbackParams) { + try { + const { linkedUserId, projectId, code } = opts; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'snyk', + vertical: 'cybersecurity', + }, + }); + //reconstruct the redirect URI that was passed in the frontend it must be the same + const REDIRECT_URI = `${ + this.env.getDistributionMode() == 'selfhost' + ? this.env.getTunnelIngress() + : this.env.getPanoraBaseUrl() + }/connections/oauth/callback`; + + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const formData = new URLSearchParams({ + redirect_uri: REDIRECT_URI, + client_id: CREDENTIALS.CLIENT_ID, + client_secret: CREDENTIALS.CLIENT_SECRET, + code: code, + grant_type: 'authorization_code', + }); + const res = await axios.post( + 'https://api.snyk.com/oauth/access_token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: SnykOAuthResponse = res.data; + // save tokens for this customer inside our db + let db_res; + const connection_token = uuidv4(); + const BASE_API_URL = CONNECTORS_METADATA['cybersecurity']['snyk'].urls + .apiUrl as string; + // get the site id for the token + const site = await axios.get('https://api.snyk.com/v2/sites', { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Bearer ${data.access_token}`, + }, + }); + const site_id = site.data.sites[0].id; + if (isNotUnique) { + // Update existing connection + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + status: 'valid', + created_at: new Date(), + }, + }); + } else { + // Create new connection + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'snyk', + vertical: 'cybersecurity', + token_type: 'oauth2', + account_url: `${BASE_API_URL}/sites/${site_id}`, + access_token: this.cryptoService.encrypt(data.access_token), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { + id_linked_user: await this.connectionUtils.getLinkedUserId( + projectId, + linkedUserId, + ), + }, + }, + }, + }); + } + this.logger.log('Successfully added tokens inside DB ' + db_res); + return db_res; + } catch (error) { + throw error; + } + } + + async handleTokenRefresh(opts: RefreshParams) { + return; + } +} diff --git a/packages/api/src/@core/connections/cybersecurity/services/tenable/tenable.service.ts b/packages/api/src/@core/connections/cybersecurity/services/tenable/tenable.service.ts new file mode 100644 index 000000000..7c125d3d8 --- /dev/null +++ b/packages/api/src/@core/connections/cybersecurity/services/tenable/tenable.service.ts @@ -0,0 +1,147 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { RetryHandler } from '@@core/@core-services/request-retry/retry.handler'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { ConnectionUtils } from '@@core/connections/@utils'; +import { + AbstractBaseConnectionService, + OAuthCallbackParams, + PassthroughInput, + RefreshParams, +} from '@@core/connections/@utils/types'; +import { PassthroughResponse } from '@@core/passthrough/types'; +import { Injectable } from '@nestjs/common'; +import { + AuthStrategy, + CONNECTORS_METADATA, + DynamicApiUrl, + providerToType, +} from '@panora/shared'; +import { v4 as uuidv4 } from 'uuid'; +import { ServiceRegistry } from '../registry.service'; + +@Injectable() +export class TenableConnectionService extends AbstractBaseConnectionService { + private readonly type: string; + + constructor( + protected prisma: PrismaService, + private logger: LoggerService, + protected cryptoService: EncryptionService, + private registry: ServiceRegistry, + private connectionUtils: ConnectionUtils, + private cService: ConnectionsStrategiesService, + private retryService: RetryHandler, + ) { + super(prisma, cryptoService); + this.logger.setContext(TenableConnectionService.name); + this.registry.registerService('tenable', this); + this.type = providerToType('tenable', 'cybersecurity', AuthStrategy.oauth2); + } + + async passthrough( + input: PassthroughInput, + connectionId: string, + ): Promise { + try { + const { headers } = input; + const config = await this.constructPassthrough(input, connectionId); + + const connection = await this.prisma.connections.findUnique({ + where: { + id_connection: connectionId, + }, + }); + const decryptedData = JSON.parse( + this.cryptoService.decrypt(connection.access_token), + ); + + const { access_key, secret_key } = decryptedData; // todo + + config.headers = { + ...config.headers, + ...headers, + 'X-ApiKeys': `${secret_key}`, + }; + + return await this.retryService.makeRequest( + { + method: config.method, + url: config.url, + data: config.data, + headers: config.headers, + }, + 'cybersecurity.tenable.passthrough', + config.linkedUserId, + ); + } catch (error) { + throw error; + } + } + + async handleCallback(opts: OAuthCallbackParams) { + try { + const { linkedUserId, projectId, body } = opts; + const { access_key, secret_key } = body; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'tenable', + vertical: 'cybersecurity', + }, + }); + + let db_res; + const connection_token = uuidv4(); + const BASE_API_URL = CONNECTORS_METADATA['cybersecurity']['tenable'].urls + .apiUrl as string; + + if (isNotUnique) { + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(secret_key), + account_url: BASE_API_URL, + status: 'valid', + created_at: new Date(), + }, + }); + } else { + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'tenable', + vertical: 'cybersecurity', + token_type: 'basic', + account_url: BASE_API_URL, + access_token: this.cryptoService.encrypt(secret_key), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { + id_linked_user: await this.connectionUtils.getLinkedUserId( + projectId, + linkedUserId, + ), + }, + }, + }, + }); + } + return db_res; + } catch (error) { + throw error; + } + } + + handleTokenRefresh?(opts: RefreshParams): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/shared/src/categories.ts b/packages/shared/src/categories.ts index 5cb99075e..00369f15b 100644 --- a/packages/shared/src/categories.ts +++ b/packages/shared/src/categories.ts @@ -7,7 +7,8 @@ export enum ConnectorCategory { MarketingAutomation = 'marketingautomation', FileStorage = 'filestorage', Productivity = 'productivity', - Ecommerce = 'ecommerce' + Ecommerce = 'ecommerce', + Cybersecurity = 'cybersecurity' } export const categoriesVerticals: ConnectorCategory[] = Object.values(ConnectorCategory); diff --git a/packages/shared/src/connectors/metadata.ts b/packages/shared/src/connectors/metadata.ts index ef65b5d6c..687f99313 100644 --- a/packages/shared/src/connectors/metadata.ts +++ b/packages/shared/src/connectors/metadata.ts @@ -2967,5 +2967,124 @@ export const CONNECTORS_METADATA: ProvidersConfig = { properties: ['username', 'password', 'store_url'] } }, + }, + 'cybersecurity': { + 'semgrep': { + urls: { + docsUrl: 'https://semgrep.dev/api/v1/docs/#section/Introduction', + apiUrl: 'https://semgrep.dev/api', + }, + logoPath: 'https://yt3.googleusercontent.com/NWVXYvuzHDgJJsbda7eyyz21Ba2qnq5WmuGrt9ax1rs6PP-mlDl5LCJ4ZO0Z2ZbiCq4ZoxqiGg=s900-c-k-c0x00ffffff-no-rj', + description: 'Sync & Create orders, fulfillments, fulfillment orders, customers and products', + active: false, + primaryColor: '#10C096', + authStrategy: { + strategy: AuthStrategy.api_key, + properties: ['api_key'] + } + }, + 'snyk': { + scopes: '', + urls: { + docsUrl: 'https://docs.snyk.io/snyk-api/', + apiUrl: 'https://api.snyk.io', + authBaseUrl: 'https://app.snyk.io/oauth2/authorize' + }, + logoPath: 'https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1215%2Ffe4be452-1e68-444a-bf77-db21bf3a7bdc.png', + description: 'Sync & Create orders, fulfillments, fulfillment orders, customers and products', + active: false, + authStrategy: { + strategy: AuthStrategy.oauth2 + }, + options: { + local_redirect_uri_in_https: true + } + }, + 'tenable': { + urls: { + docsUrl: 'https://developer.tenable.com/reference/navigate', + apiUrl: 'https://cloud.tenable.com', + }, + logoPath: 'https://pbs.twimg.com/profile_images/1410604377757216768/ocEKYniC_400x400.jpg', + description: 'Sync & Create orders, fulfillments, fulfillment orders, customers and products', + active: false, + primaryColor: '#0D1E40', + authStrategy: { + strategy: AuthStrategy.basic, + properties: ['access_key', 'secret_key'] + } + }, + 'qualys': { + urls: { + docsUrl: 'https://docs.qualys.com/en/vm/api/scans/index.htm#t=get_started%2Fauthentication.htm', + apiUrl: (baseApi) => `https://${baseApi}/api` + }, + logoPath: 'https://companieslogo.com/img/orig/QLYS-68c2032c.png?t=1720244493', + description: 'Sync & Create orders, fulfillments, fulfillment orders, customers and products', + active: false, + primaryColor: '#ED2E28', + authStrategy: { + strategy: AuthStrategy.basic, + properties: ['username', 'password', 'api_url'] + } + }, + 'rapid7insightvm': { + urls: { + docsUrl: 'https://help.rapid7.com/insightvm/en-us/api/index.html', + apiUrl: (region) => `https://${region}.api.insight.rapid7.com`, + }, + logoPath: 'https://images.saasworthy.com/insightvm_9113_logo_1635748346_lc0gr.png', + description: 'Sync & Create orders, fulfillments, fulfillment orders, customers and products', + active: false, + primaryColor: '#E95722', + authStrategy: { + strategy: AuthStrategy.api_key, + properties: ['region', 'api_key'] + } + }, + 'crowdstrike': { + scopes: '', + urls: { + docsUrl: 'https://developer.crowdstrike.com/', + apiUrl: (dotHost) => `https://api${dotHost}.crowdstrike.com`, + authBaseUrl: '' + }, + logoPath: 'https://pbs.twimg.com/profile_images/1451022302578049024/6L-zG5oq_400x400.jpg', + description: 'Sync & Create orders, fulfillments, fulfillment orders, customers and products', + active: false, + primaryColor: '#FC0001', + authStrategy: { + strategy: AuthStrategy.oauth2, + } + }, + 'sentinelone': { + urls: { + docsUrl: 'https://www.postman.com/api-evangelist/sentinelone/overview', + apiUrl: (host) => `https://${host}.sentinelone.net`, + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTZHLye2za7fjLiggqC1upKhhM3T-laySJSLQ&s', + description: 'Sync & Create orders, fulfillments, fulfillment orders, customers and products', + active: false, + primaryColor: '#522E74', + authStrategy: { + strategy: AuthStrategy.api_key, + properties: ['host', 'api_key'] + } + }, + 'microsoftdefender': { + scopes: '', + urls: { + docsUrl: 'https://learn.microsoft.com/en-us/defender-endpoint/api/apis-intro', + apiUrl: '', + authBaseUrl: '' + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSuR4GZElDP7UNKhXS9jDGpElBTdchjg8hSsA&s', + description: 'Sync & Create orders, fulfillments, fulfillment orders, customers and products', + active: false, + primaryColor: '#0078D8', + authStrategy: { + strategy: AuthStrategy.oauth2, + } + }, } };