-
Notifications
You must be signed in to change notification settings - Fork 195
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
Changes from all commits
2e62b82
aef9526
0ffb1be
e3a079b
61fd0f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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','[email protected]', '$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'), | ||
|
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; | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
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); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
ToolsBiome
|
||||||||||
return db_res; | ||||||||||
} catch (error) { | ||||||||||
throw error; | ||||||||||
} | ||||||||||
Comment on lines
+187
to
+189
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
ToolsBiome
|
||||||||||
} | ||||||||||
|
||||||||||
async handleTokenRefresh(opts: RefreshParams) { | ||||||||||
return; | ||||||||||
} | ||||||||||
Comment on lines
+192
to
+194
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarify or implement the The method currently returns without performing any action. Consider implementing it or documenting why it is not supported. |
||||||||||
} |
There was a problem hiding this comment.
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.
Committable suggestion
Tools
Biome