-
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
Feat/microsoft dynamics connection #625
Changes from 2 commits
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 | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,243 @@ | ||||||||
import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; | ||||||||
import { EnvironmentService } from '@@core/@core-services/environment/environment.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 axios from 'axios'; | ||||||||
import { v4 as uuidv4 } from 'uuid'; | ||||||||
import { ServiceRegistry } from '../registry.service'; | ||||||||
import { URLSearchParams } from 'url'; | ||||||||
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 Using the - import { URLSearchParams } from 'url';
+ import { URLSearchParams } from 'node:url'; Committable suggestion
Suggested change
ToolsBiome
|
||||||||
|
||||||||
export type MicrosoftDynamicsSalesOAuthResponse = { | ||||||||
access_token: string; | ||||||||
refresh_token: string; | ||||||||
token_type: string; | ||||||||
expires_in: number; | ||||||||
scope: string; | ||||||||
}; | ||||||||
|
||||||||
@Injectable() | ||||||||
export class MicrosoftDynamicsSalesConnectionService extends AbstractBaseConnectionService { | ||||||||
private readonly type: string; | ||||||||
|
||||||||
constructor( | ||||||||
protected prisma: PrismaService, | ||||||||
private logger: LoggerService, | ||||||||
private env: EnvironmentService, | ||||||||
protected cryptoService: EncryptionService, | ||||||||
private registry: ServiceRegistry, | ||||||||
private cService: ConnectionsStrategiesService, | ||||||||
private connectionUtils: ConnectionUtils, | ||||||||
private retryService: RetryHandler, | ||||||||
) { | ||||||||
super(prisma, cryptoService); | ||||||||
this.logger.setContext(MicrosoftDynamicsSalesConnectionService.name); | ||||||||
this.registry.registerService('microsoftdynamicssales', this); | ||||||||
this.type = providerToType( | ||||||||
'microsoftdynamicssales', | ||||||||
'crm', | ||||||||
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, | ||||||||
}, | ||||||||
}); | ||||||||
|
||||||||
config.headers['Authorization'] = `Basic ${Buffer.from( | ||||||||
`${this.cryptoService.decrypt(connection.access_token)}:`, | ||||||||
).toString('base64')}`; | ||||||||
|
||||||||
config.headers = { | ||||||||
...config.headers, | ||||||||
...headers, | ||||||||
}; | ||||||||
|
||||||||
return await this.retryService.makeRequest( | ||||||||
{ | ||||||||
method: config.method, | ||||||||
url: config.url, | ||||||||
data: config.data, | ||||||||
headers: config.headers, | ||||||||
}, | ||||||||
'crm.microsoftdynamicssales.passthrough', | ||||||||
config.linkedUserId, | ||||||||
); | ||||||||
} catch (error) { | ||||||||
throw error; | ||||||||
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 redundant and can be removed. - } catch (error) {
- throw error;
- }
+ } Committable suggestion
Suggested change
ToolsBiome
|
||||||||
} | ||||||||
} | ||||||||
|
||||||||
async handleCallback(opts: OAuthCallbackParams) { | ||||||||
try { | ||||||||
const { linkedUserId, projectId, code, resource } = opts; | ||||||||
const isNotUnique = await this.prisma.connections.findFirst({ | ||||||||
where: { | ||||||||
id_linked_user: linkedUserId, | ||||||||
provider_slug: 'microsoftdynamicssales', | ||||||||
vertical: 'crm', | ||||||||
}, | ||||||||
}); | ||||||||
|
||||||||
const REDIRECT_URI = `${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, | ||||||||
scope: `https://${resource}/.default offline_access`, | ||||||||
grant_type: 'authorization_code', | ||||||||
}); | ||||||||
const res = await axios.post( | ||||||||
`https://login.microsoftonline.com/common/oauth2/v2.0/token`, | ||||||||
formData.toString(), | ||||||||
{ | ||||||||
headers: { | ||||||||
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', | ||||||||
}, | ||||||||
}, | ||||||||
); | ||||||||
const data: MicrosoftDynamicsSalesOAuthResponse = res.data; | ||||||||
this.logger.log( | ||||||||
'OAuth credentials : microsoftdynamicssales crm ' + | ||||||||
JSON.stringify(data), | ||||||||
Comment on lines
+134
to
+135
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 string concatenation. Template literals are preferred over string concatenation for better readability. - 'OAuth credentials : microsoftdynamicssales crm ' +
- JSON.stringify(data),
+ `OAuth credentials : microsoftdynamicssales crm ${JSON.stringify(data)}`, Committable suggestion
Suggested change
ToolsBiome
|
||||||||
); | ||||||||
|
||||||||
let db_res; | ||||||||
const connection_token = uuidv4(); | ||||||||
|
||||||||
if (isNotUnique) { | ||||||||
db_res = await this.prisma.connections.update({ | ||||||||
where: { | ||||||||
id_connection: isNotUnique.id_connection, | ||||||||
}, | ||||||||
data: { | ||||||||
access_token: this.cryptoService.encrypt(data.access_token), | ||||||||
refresh_token: this.cryptoService.encrypt(data.refresh_token), | ||||||||
account_url: `https://${resource}`, | ||||||||
expiration_timestamp: new Date( | ||||||||
new Date().getTime() + Number(data.expires_in) * 1000, | ||||||||
), | ||||||||
status: 'valid', | ||||||||
created_at: new Date(), | ||||||||
}, | ||||||||
}); | ||||||||
} else { | ||||||||
db_res = await this.prisma.connections.create({ | ||||||||
data: { | ||||||||
id_connection: uuidv4(), | ||||||||
connection_token: connection_token, | ||||||||
provider_slug: 'microsoftdynamicssales', | ||||||||
vertical: 'crm', | ||||||||
token_type: 'oauth2', | ||||||||
account_url: `https://${resource}`, | ||||||||
access_token: this.cryptoService.encrypt(data.access_token), | ||||||||
refresh_token: this.cryptoService.encrypt(data.refresh_token), | ||||||||
expiration_timestamp: new Date( | ||||||||
new Date().getTime() + Number(data.expires_in) * 1000, | ||||||||
), | ||||||||
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; | ||||||||
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 redundant and can be removed. - } catch (error) {
- throw error;
- }
+ } Committable suggestion
Suggested change
ToolsBiome
|
||||||||
} | ||||||||
} | ||||||||
async handleTokenRefresh(opts: RefreshParams) { | ||||||||
try { | ||||||||
const { connectionId, refreshToken, projectId } = opts; | ||||||||
const REDIRECT_URI = `${this.env.getPanoraBaseUrl()}/connections/oauth/callback`; | ||||||||
const CREDENTIALS = (await this.cService.getCredentials( | ||||||||
projectId, | ||||||||
this.type, | ||||||||
)) as OAuth2AuthData; | ||||||||
|
||||||||
const conn = await this.prisma.connections.findUnique({ | ||||||||
where: { | ||||||||
id_connection: connectionId, | ||||||||
}, | ||||||||
}); | ||||||||
|
||||||||
const formData = new URLSearchParams({ | ||||||||
grant_type: 'refresh_token', | ||||||||
scope: `${conn.account_url}/.default offline_access`, | ||||||||
client_id: CREDENTIALS.CLIENT_ID, | ||||||||
client_secret: CREDENTIALS.CLIENT_SECRET, | ||||||||
refresh_token: this.cryptoService.decrypt(refreshToken), | ||||||||
redirect_uri: REDIRECT_URI, | ||||||||
}); | ||||||||
|
||||||||
const res = await axios.post( | ||||||||
`https://login.microsoftonline.com/common/oauth2/v2.0/token`, | ||||||||
formData.toString(), | ||||||||
{ | ||||||||
headers: { | ||||||||
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', | ||||||||
}, | ||||||||
}, | ||||||||
); | ||||||||
const data: MicrosoftDynamicsSalesOAuthResponse = res.data; | ||||||||
await this.prisma.connections.update({ | ||||||||
where: { | ||||||||
id_connection: connectionId, | ||||||||
}, | ||||||||
data: { | ||||||||
access_token: this.cryptoService.encrypt(data.access_token), | ||||||||
refresh_token: this.cryptoService.encrypt(data.refresh_token), | ||||||||
expiration_timestamp: new Date( | ||||||||
new Date().getTime() + Number(data.expires_in) * 1000, | ||||||||
), | ||||||||
}, | ||||||||
}); | ||||||||
this.logger.log('OAuth credentials updated : microsoftdynamicssales '); | ||||||||
} catch (error) { | ||||||||
throw error; | ||||||||
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 redundant and can be removed. - } catch (error) {
- throw error;
- }
+ } Committable suggestion
Suggested change
ToolsBiome
|
||||||||
} | ||||||||
} | ||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -55,7 +55,10 @@ export const constructAuthUrl = async ({ projectId, linkedUserId, providerName, | |||||||||||||||
baseRedirectURL = redirectUriIngress.value!; | ||||||||||||||||
} | ||||||||||||||||
const encodedRedirectUrl = encodeURIComponent(`${baseRedirectURL}/connections/oauth/callback`); | ||||||||||||||||
const state = encodeURIComponent(JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl })); | ||||||||||||||||
let state = encodeURIComponent(JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl })); | ||||||||||||||||
if (providerName == 'microsoftdynamicssales') { | ||||||||||||||||
state = encodeURIComponent(JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl, resource: additionalParams!.end_user_domain })); | ||||||||||||||||
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. Avoid non-null assertion. Using non-null assertion ( - state = encodeURIComponent(JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl, resource: additionalParams!.end_user_domain }));
+ state = encodeURIComponent(JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl, resource: additionalParams?.end_user_domain })); Committable suggestion
Suggested change
ToolsBiome
|
||||||||||||||||
} | ||||||||||||||||
// console.log('State : ', JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl })); | ||||||||||||||||
// console.log('encodedRedirect URL : ', encodedRedirectUrl); | ||||||||||||||||
// const vertical = findConnectorCategory(providerName); | ||||||||||||||||
|
@@ -166,7 +169,19 @@ const handleOAuth2Url = async (input: HandleOAuth2Url) => { | |||||||||||||||
if (needsScope(providerName, vertical) && scopes) { | ||||||||||||||||
if(providerName === 'slack') { | ||||||||||||||||
params += `&scope=&user_scope=${encodeURIComponent(scopes)}`; | ||||||||||||||||
} else { | ||||||||||||||||
} else if (providerName == 'microsoftdynamicssales') { | ||||||||||||||||
const url = new URL(BASE_URL); | ||||||||||||||||
// Extract the base URL without parameters | ||||||||||||||||
const base = url.origin + url.pathname; | ||||||||||||||||
// Extract the resource parameter | ||||||||||||||||
const resource = url.searchParams.get('resource'); | ||||||||||||||||
BASE_URL = base; | ||||||||||||||||
let b = `https://${resource}/.default`; | ||||||||||||||||
b += (' offline_access'); | ||||||||||||||||
console.log("scopes is "+ b) | ||||||||||||||||
console.log("BASE URL is "+ BASE_URL) | ||||||||||||||||
Comment on lines
+179
to
+182
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 string concatenation. Template literals are preferred over string concatenation for better readability. - let b = `https://${resource}/.default`;
- b += (' offline_access');
- console.log("scopes is "+ b)
- console.log("BASE URL is "+ BASE_URL)
+ let b = `https://${resource}/.default offline_access`;
+ console.log(`scopes is ${b}`);
+ console.log(`BASE URL is ${BASE_URL}`); Committable suggestion
Suggested change
ToolsBiome
|
||||||||||||||||
params += `&scope=${encodeURIComponent(b)}`; | ||||||||||||||||
}else { | ||||||||||||||||
params += `&scope=${encodeURIComponent(scopes)}`; | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -203,18 +203,21 @@ export const CONNECTORS_METADATA: ProvidersConfig = { | |||||
strategy: AuthStrategy.oauth2 | ||||||
} | ||||||
}, | ||||||
'microsoft_dynamics_sales': { | ||||||
scopes: '', | ||||||
'microsoftdynamicssales': { | ||||||
scopes: 'offline_access', | ||||||
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. Add a more descriptive scope. The scope - scopes: 'offline_access',
+ scopes: 'offline_access Dynamics.CRM.read Dynamics.CRM.write', Committable suggestion
Suggested change
|
||||||
urls: { | ||||||
docsUrl: '', | ||||||
authBaseUrl: '', | ||||||
apiUrl: '', | ||||||
authBaseUrl: (orgName) => `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?resource=${orgName}`, | ||||||
apiUrl: `/api/data/v9.2`, | ||||||
}, | ||||||
logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', | ||||||
logoPath: 'https://play-lh.googleusercontent.com/MC_Aoa7rlMjGtcgAdiLJGeIm3-kpVw7APQmQUrUZtXuoZokiqVOJqR-bTu7idJBD8g', | ||||||
description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', | ||||||
active: false, | ||||||
options: { | ||||||
end_user_domain: true | ||||||
}, | ||||||
authStrategy: { | ||||||
strategy: AuthStrategy.api_key | ||||||
strategy: AuthStrategy.oauth2 | ||||||
} | ||||||
}, | ||||||
'nutshell': { | ||||||
|
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.
Fix grammatical issue.
Use "an" instead of "a" before "Ecommerce provider".
Committable suggestion
Tools
LanguageTool