Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ const PIPEDRIVE_API_KEY = 'random string'
const PIPEDRIVE_DOMAIN = 'companydomain'
const PERSON_ID = 33333

describe('Pipedrive domain validation', () => {
it('should throw when domain contains URL injection characters', async () => {
await expect(
testDestination.testAuthentication({ apiToken: PIPEDRIVE_API_KEY, domain: 'attacker.com/path?x=' })
).rejects.toThrowError(/Invalid domain/)
})

it('should throw when domain contains @ injection', async () => {
await expect(
testDestination.testAuthentication({ apiToken: PIPEDRIVE_API_KEY, domain: 'attacker.com@legitimate' })
).rejects.toThrowError(/Invalid domain/)
})
})

describe('Pipedrive.createUpdatePerson', () => {
it('should create person if none exists', async () => {
const scope = nock(`https://${PIPEDRIVE_DOMAIN}.pipedrive.com/api/v1`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import createUpdateOrganization from './createUpdateOrganization'
import createUpdatePerson from './createUpdatePerson'
import { defaultValues, DestinationDefinition } from '@segment/actions-core'
import type { Settings } from './generated-types'
import { validateDomain } from './utils'

import createUpdateActivity from './createUpdateActivity'

Expand Down Expand Up @@ -59,6 +60,7 @@ const destination: DestinationDefinition<Settings> = {
}
},
testAuthentication: (request, { settings }) => {
validateDomain(settings.domain)
return request(`https://${settings.domain}.pipedrive.com/api/v1/users/me`)
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { InvalidAuthenticationError } from '@segment/actions-core'

type PayloadWithCustomFields = { custom_fields?: { [k: string]: unknown } }

export function validateDomain(domain: string): void {
if (!/^[a-zA-Z0-9-]+$/.test(domain)) {
throw new InvalidAuthenticationError(
'Invalid domain. Domain must contain only alphanumeric characters and hyphens.'
)
}
}
Comment thread
AnkitSegment marked this conversation as resolved.

export function addCustomFieldsFromPayloadToEntity<E extends object>(payload: PayloadWithCustomFields, entity: E) {
if (!payload.custom_fields) {
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,23 @@ describe('Qualtrics', () => {

await expect(testDestination.testAuthentication(authData)).rejects.toThrowError(/401/)
})

it('throw error when datacenter contains URL injection characters', async () => {
const authData = {
apiToken: 'VALID_API_TOKEN_VALUE',
datacenter: 'attacker.com/path?x='
}

await expect(testDestination.testAuthentication(authData)).rejects.toThrowError(/Invalid datacenter ID/)
})

it('throw error when datacenter contains @ injection', async () => {
const authData = {
apiToken: 'VALID_API_TOKEN_VALUE',
datacenter: 'attacker.com@legitimate'
}

await expect(testDestination.testAuthentication(authData)).rejects.toThrowError(/Invalid datacenter ID/)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import addContactToXmd from './addContactToXmd'
import upsertContactTransaction from './upsertContactTransaction'
import triggerXflowWorkflow from './triggerXflowWorkflow'
import QualtricsApiClient from './qualtricsApiClient'
import { validateDatacenter } from './utils'

const destination: DestinationDefinition<Settings> = {
name: 'Qualtrics',
Expand All @@ -31,6 +32,7 @@ const destination: DestinationDefinition<Settings> = {
}
},
testAuthentication: async (request: RequestClient, input) => {
validateDatacenter(input.settings.datacenter)
const apiClient = new QualtricsApiClient(input.settings.datacenter, input.settings.apiToken, request)
const response = await apiClient.whoaAmI()
return response.userName !== undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ export default class QualtricsApiClient {
private request: RequestClient

constructor(dc: string, apiToken: string, request: RequestClient) {
this.baseUrl = `https://${dc || 'iad1'}.qualtrics.com`
const datacenter = dc || 'iad1'
this.baseUrl = `https://${datacenter}.qualtrics.com`
this.apiToken = apiToken
this.request = request
}
Comment on lines 127 to 132
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
import { InvalidAuthenticationError } from '@segment/actions-core'

export function validateDatacenter(datacenter: string): void {
if (!/^[a-zA-Z0-9-]+$/.test(datacenter)) {
throw new InvalidAuthenticationError(
'Invalid datacenter ID. Datacenter must contain only alphanumeric characters and hyphens.'
)
}
}

export function parsedEmbeddedData(
data: { [key: string]: unknown } | undefined
): Record<string, string | number | boolean> {
Expand All @@ -7,11 +17,11 @@ export function parsedEmbeddedData(
return
}
if (typeof data[key] === 'string') {
parsedData[key] = data[key] as string
parsedData[key] = data[key]
} else if (typeof data[key] === 'number') {
parsedData[key] = data[key] as number
parsedData[key] = data[key]
} else if (typeof data[key] === 'boolean') {
parsedData[key] = data[key] as boolean
parsedData[key] = data[key]
} else {
Comment on lines 19 to 25

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why are we making this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not an intentional change — this repo's lint-staged pre-commit hook runs eslint --fix over the whole file whenever any part of it is staged, and the project's own @typescript-eslint/no-unnecessary-type-assertion rule flags these three casts as redundant (the preceding typeof checks already narrow the type). Since utils.ts got touched for validateDatacenter, the hook auto-stripped them. No behavior change — tried reverting it manually but the hook just re-strips it on every commit that touches this file, so leaving it as-is.

try {
parsedData[key] = JSON.stringify(data)
Expand Down
Loading