Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Deel + sage hris integrations #663

Merged
merged 5 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/api/scripts/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -541,12 +541,15 @@ CREATE TABLE connector_sets
crm_close boolean NULL,
fs_box boolean NULL,
tcg_github boolean NULL,
hris_gusto boolean NULL,
hris_deel boolean NULL,
hris_sage boolean NULL,
ecom_woocommerce boolean NULL,
ecom_shopify boolean NULL,
ecom_amazon boolean NULL,
ecom_squarespace boolean NULL,
ats_ashby boolean NULL,
hris_gusto boolean NULL,
ats_bamboohr boolean NULL,
CONSTRAINT PK_project_connector PRIMARY KEY ( id_connector_set )
);

Expand Down
8 changes: 4 additions & 4 deletions packages/api/scripts/seed.sql
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
INSERT INTO users (id_user, identification_strategy, email, password_hash, first_name, last_name) VALUES
('0ce39030-2901-4c56-8db0-5e326182ec6b', 'b2c','local@panora.dev', '$2b$10$Y7Q8TWGyGuc5ecdIASbBsuXMo3q/Rs3/cnY.mLZP4tUgfGUOCUBlG', 'local', 'Panora');

INSERT INTO connector_sets (id_connector_set, crm_hubspot, crm_zoho, crm_pipedrive, crm_attio, crm_zendesk, crm_close, tcg_zendesk, tcg_gorgias, tcg_front, tcg_jira, tcg_gitlab, fs_box, tcg_github) VALUES
('1709da40-17f7-4d3a-93a0-96dc5da6ddd7', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE),
('852dfff8-ab63-4530-ae49-e4b2924407f8', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE),
('aed0f856-f802-4a79-8640-66d441581a99', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
INSERT INTO connector_sets (id_connector_set, crm_hubspot, crm_zoho, crm_pipedrive, crm_attio, crm_zendesk, crm_close, tcg_zendesk, tcg_gorgias, tcg_front, tcg_jira, tcg_gitlab, fs_box, tcg_github, hris_deel, hris_sage, ats_ashby) VALUES
('1709da40-17f7-4d3a-93a0-96dc5da6ddd7', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE),
('852dfff8-ab63-4530-ae49-e4b2924407f8', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE),
('aed0f856-f802-4a79-8640-66d441581a99', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);

INSERT INTO projects (id_project, name, sync_mode, id_user, id_connector_set) VALUES
('1e468c15-aa57-4448-aa2b-7fed640d1e3d', 'Project 1', 'pull', '0ce39030-2901-4c56-8db0-5e326182ec6b', '1709da40-17f7-4d3a-93a0-96dc5da6ddd7'),
Expand Down
3 changes: 3 additions & 0 deletions packages/api/src/@core/connections/connections.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -25,6 +26,7 @@ import { EcommerceConnectionModule } from './ecommerce/ecommerce.connection.modu
FilestorageConnectionModule,
HrisConnectionModule,
EcommerceConnectionModule,
CybersecurityConnectionsModule,
SyncModule,
],
providers: [ValidateUserService, OAuthTokenRefreshService],
Expand All @@ -40,6 +42,7 @@ import { EcommerceConnectionModule } from './ecommerce/ecommerce.connection.modu
EcommerceConnectionModule,
HrisConnectionModule,
ProductivityConnectionsModule,
CybersecurityConnectionsModule,
],
})
export class ConnectionsModule {}
Original file line number Diff line number Diff line change
@@ -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 {}
Original file line number Diff line number Diff line change
@@ -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<PassthroughResponse> {
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;
}
Comment on lines +89 to +91
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove redundant catch clause.

The catch clause that only rethrows the original error is unnecessary and can be removed to simplify the code.

-    } catch (error) {
-      throw error;
-    }
+    }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
throw error;
}
}
Tools
Biome

[error] 90-90: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

}

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use template literals for logging.

Use template literals instead of string concatenation for consistency and readability.

-      this.logger.log('Successfully added tokens inside DB ' + db_res);
+      this.logger.log(`Successfully added tokens inside DB ${db_res}`);
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.logger.log('Successfully added tokens inside DB ' + db_res);
this.logger.log(`Successfully added tokens inside DB ${db_res}`);
Tools
Biome

[error] 185-185: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

return db_res;
} catch (error) {
throw error;
}
Comment on lines +187 to +189
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove redundant catch clause.

The catch clause that only rethrows the original error is unnecessary and can be removed to simplify the code.

-    } catch (error) {
-      throw error;
-    }
+    }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
throw error;
}
}
Tools
Biome

[error] 188-188: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

}

async handleTokenRefresh(opts: RefreshParams) {
return;
}
Comment on lines +192 to +194
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify or implement the handleTokenRefresh method.

The method currently returns without performing any action. Consider implementing it or documenting why it is not supported.

}
Loading
Loading