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

[Salesforce] Alternative authentication flow #2023

Merged
merged 16 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export {
export { createTestEvent } from './create-test-event'
export { createTestIntegration } from './create-test-integration'
export { default as createInstance } from './request-client'
export { default as createRequestClient } from './create-request-client'
export { defaultValues } from './defaults'
export {
IntegrationError,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import nock from 'nock'
import createRequestClient from '../../../../../core/src/create-request-client'
import Salesforce from '../sf-operations'
import Salesforce, { authenticateWithPassword } from '../sf-operations'
import { API_VERSION } from '../sf-operations'
import type { GenericPayload } from '../sf-types'
import { Settings } from '../generated-types'

const settings = {
instanceUrl: 'https://test.salesforce.com/'
Expand Down Expand Up @@ -771,4 +772,70 @@ describe('Salesforce', () => {
)
})
})

describe('Username & Password flow', () => {
const usernamePasswordOnly: Settings = {
username: 'spongebob@seamail.com',
auth_password: 'gary1997',
instanceUrl: 'https://spongebob.salesforce.com/',
isSandbox: false
}

const usernamePasswordAndToken: Settings = {
username: 'spongebob@seamail.com',
auth_password: 'gary1997',
instanceUrl: 'https://spongebob.salesforce.com/',
isSandbox: false,
security_token: 'abc123'
}

process.env['SALESFORCE_CLIENT_ID'] = 'id'
process.env['SALESFORCE_CLIENT_SECRET'] = 'secret'

it('should authenticate using the username and password flow when only the username and password are provided', async () => {
nock('https://login.salesforce.com/services/oauth2/token')
.post('', {
grant_type: 'password',
client_id: 'id',
client_secret: 'secret',
username: usernamePasswordOnly.username,
password: usernamePasswordOnly.auth_password
})
.reply(201, {
access_token: 'abc'
})

const res = await authenticateWithPassword(
usernamePasswordOnly.username as string, // tells typescript that these are defined
usernamePasswordOnly.auth_password as string,
usernamePasswordOnly.security_token,
usernamePasswordOnly.isSandbox
)

expect(res.accessToken).toEqual('abc')
})

it('should authenticate using the username and password flow when the username, password and security token are provided', async () => {
nock('https://login.salesforce.com/services/oauth2/token')
.post('', {
grant_type: 'password',
client_id: 'id',
client_secret: 'secret',
username: usernamePasswordAndToken.username,
password: `${usernamePasswordAndToken.auth_password}${usernamePasswordAndToken.security_token}`
})
.reply(201, {
access_token: 'abc'
})

const res = await authenticateWithPassword(
usernamePasswordAndToken.username as string, // tells typescript that these are defined
usernamePasswordAndToken.auth_password as string,
usernamePasswordAndToken.security_token,
usernamePasswordAndToken.isSandbox
)

expect(res.accessToken).toEqual('abc')
})
})
})
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ActionDefinition, IntegrationError } from '@segment/actions-core'
import type { Settings } from '../generated-types'
import Salesforce from '../sf-operations'
import Salesforce, { generateSalesforceRequest } from '../sf-operations'
import {
bulkUpsertExternalId,
bulkUpdateRecordId,
Expand Down Expand Up @@ -181,7 +181,7 @@ const action: ActionDefinition<Settings, Payload> = {
customFields: customFields
},
perform: async (request, { settings, payload }) => {
const sf: Salesforce = new Salesforce(settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request))

if (payload.operation === 'create') {
if (!payload.name) {
Expand All @@ -208,7 +208,7 @@ const action: ActionDefinition<Settings, Payload> = {
}
},
performBatch: async (request, { settings, payload }) => {
const sf: Salesforce = new Salesforce(settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request))

if (payload[0].operation === 'upsert') {
if (!payload[0].name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
recordMatcherOperator,
batch_size
} from '../sf-properties'
import Salesforce from '../sf-operations'
import Salesforce, { generateSalesforceRequest } from '../sf-operations'

const OBJECT_NAME = 'Case'

Expand All @@ -35,7 +35,7 @@ const action: ActionDefinition<Settings, Payload> = {
customFields: customFields
},
perform: async (request, { settings, payload }) => {
const sf: Salesforce = new Salesforce(settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request))

if (payload.operation === 'create') {
return await sf.createRecord(payload, OBJECT_NAME)
Expand All @@ -56,7 +56,7 @@ const action: ActionDefinition<Settings, Payload> = {
}
},
performBatch: async (request, { settings, payload }) => {
const sf: Salesforce = new Salesforce(settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request))

return sf.bulkHandler(payload, OBJECT_NAME)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
recordMatcherOperator,
batch_size
} from '../sf-properties'
import Salesforce from '../sf-operations'
import Salesforce, { generateSalesforceRequest } from '../sf-operations'

const OBJECT_NAME = 'Contact'

Expand Down Expand Up @@ -132,7 +132,7 @@ const action: ActionDefinition<Settings, Payload> = {
customFields: customFields
},
perform: async (request, { settings, payload }) => {
const sf: Salesforce = new Salesforce(settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request))

if (payload.operation === 'create') {
if (!payload.last_name) {
Expand All @@ -159,7 +159,7 @@ const action: ActionDefinition<Settings, Payload> = {
}
},
performBatch: async (request, { settings, payload }) => {
const sf: Salesforce = new Salesforce(settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request))

if (payload[0].operation === 'upsert') {
if (!payload[0].last_name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
recordMatcherOperator,
batch_size
} from '../sf-properties'
import Salesforce from '../sf-operations'
import Salesforce, { generateSalesforceRequest } from '../sf-operations'
import { PayloadValidationError } from '@segment/actions-core'
const OPERATIONS_WITH_CUSTOM_FIELDS = ['create', 'update', 'upsert']

Expand All @@ -39,7 +39,10 @@ const action: ActionDefinition<Settings, Payload> = {
},
dynamicFields: {
customObjectName: async (request, data) => {
const sf: Salesforce = new Salesforce(data.settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(
data.settings.instanceUrl,
await generateSalesforceRequest(data.settings, request)
)

return sf.customObjectName()
}
Expand All @@ -49,7 +52,7 @@ const action: ActionDefinition<Settings, Payload> = {
throw new PayloadValidationError('Custom fields are required for this operation.')
}

const sf: Salesforce = new Salesforce(settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request))

if (payload.operation === 'create') {
return await sf.createRecord(payload, payload.customObjectName)
Expand All @@ -74,7 +77,7 @@ const action: ActionDefinition<Settings, Payload> = {
throw new PayloadValidationError('Custom fields are required for this operation.')
}

const sf: Salesforce = new Salesforce(settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request))

return sf.bulkHandler(payload, payload[0].customObjectName)
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DestinationDefinition } from '@segment/actions-core'
import { DestinationDefinition } from '@segment/actions-core'
import type { Settings } from './generated-types'
// This has to be 'cases' because 'case' is a Javascript reserved word
import cases from './cases'
Expand All @@ -7,6 +7,7 @@ import opportunity from './opportunity'
import customObject from './customObject'
import contact from './contact'
import account from './account'
import { authenticateWithPassword } from './sf-operations'

interface RefreshTokenResponse {
access_token: string
Expand All @@ -33,9 +34,39 @@ const destination: DestinationDefinition<Settings> = {
'Enable to authenticate into a sandbox instance. You can log in to a sandbox by appending the sandbox name to your Salesforce username. For example, if a username for a production org is user@acme.com and the sandbox is named test, the username to log in to the sandbox is user@acme.com.test. If you are already authenticated, please disconnect and reconnect with your sandbox username.',
type: 'boolean',
default: false
},
username: {
label: 'Username',
description:
'The username of the Salesforce account you want to connect to. When all three of username, password, and security token are provided, a username-password flow is used to authenticate. This field is hidden to all users except those who have opted in to the username+password flow.',
type: 'string'
},
auth_password: {
// auth_ prefix is used because password is a reserved word
label: 'Password',
description:
'The password of the Salesforce account you want to connect to. When all three of username, password, and security token are provided, a username-password flow is used to authenticate. This field is hidden to all users except those who have opted in to the username+password flow.',
type: 'string'
},
security_token: {
label: 'Security Token',
description:
'The security token of the Salesforce account you want to connect to. When all three of username, password, and security token are provided, a username-password flow is used to authenticate. This value will be appended to the password field to construct the credential used for authentication. This field is hidden to all users except those who have opted in to the username+password flow.',
type: 'string'
}
},
refreshAccessToken: async (request, { auth, settings }) => {
if (settings.username && settings.auth_password) {
const { accessToken } = await authenticateWithPassword(
settings.username,
settings.auth_password,
settings.security_token,
settings.isSandbox
)

return { accessToken }
}

// Return a request that refreshes the access_token if the API supports it
const baseUrl = settings.isSandbox ? 'https://test.salesforce.com' : 'https://login.salesforce.com'
const res = await request<RefreshTokenResponse>(`${baseUrl}/services/oauth2/token`, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
recordMatcherOperator,
batch_size
} from '../sf-properties'
import Salesforce from '../sf-operations'
import Salesforce, { generateSalesforceRequest } from '../sf-operations'

const OBJECT_NAME = 'Lead'

Expand Down Expand Up @@ -139,7 +139,7 @@ const action: ActionDefinition<Settings, Payload> = {
customFields: customFields
},
perform: async (request, { settings, payload }) => {
const sf: Salesforce = new Salesforce(settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request))

if (payload.operation === 'create') {
if (!payload.last_name) {
Expand All @@ -166,7 +166,7 @@ const action: ActionDefinition<Settings, Payload> = {
}
},
performBatch: async (request, { settings, payload }) => {
const sf: Salesforce = new Salesforce(settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request))

if (payload[0].operation === 'upsert') {
if (!payload[0].last_name) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ActionDefinition, IntegrationError } from '@segment/actions-core'
import type { Settings } from '../generated-types'
import Salesforce from '../sf-operations'
import Salesforce, { generateSalesforceRequest } from '../sf-operations'
import {
bulkUpsertExternalId,
bulkUpdateRecordId,
Expand Down Expand Up @@ -56,7 +56,7 @@ const action: ActionDefinition<Settings, Payload> = {
customFields: customFields
},
perform: async (request, { settings, payload }) => {
const sf: Salesforce = new Salesforce(settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request))

if (payload.operation === 'create') {
if (!payload.close_date || !payload.name || !payload.stage_name) {
Expand All @@ -83,7 +83,7 @@ const action: ActionDefinition<Settings, Payload> = {
}
},
performBatch: async (request, { settings, payload }) => {
const sf: Salesforce = new Salesforce(settings.instanceUrl, request)
const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request))

if (payload[0].operation === 'upsert') {
if (!payload[0].close_date || !payload[0].name || !payload[0].stage_name) {
Expand Down
Loading
Loading