Skip to content

Commit ee5d565

Browse files
[MAIN] [STRATCONN-6246] added OAUTH2.0 in taxonomy API in yahoo audiences (#3659)
* STRATCONN-6246 added OAUTH2.0 in taxonomy API in yahoo audiences * Code refactored --------- Co-authored-by: Prithviraj-rathore-segment <[email protected]>
1 parent 42598d2 commit ee5d565

4 files changed

Lines changed: 69 additions & 44 deletions

File tree

packages/destination-actions/src/destinations/yahoo-audiences/__tests__/index.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ describe('Yahoo Audiences', () => {
4444

4545
describe('Success cases', () => {
4646
it('It should create the audience successfully', async () => {
47+
// Mock the token endpoint called by get_taxonomy_access_token inside update_taxonomy
48+
nock('https://id.b2b.yahooincapis.com').post('/zts/v1/oauth2/token').reply(200, {
49+
access_token: 'fake-taxonomy-access-token'
50+
})
51+
4752
nock('https://datax.yahooapis.com').put(`/v1/taxonomy/append/${ENGAGE_SPACE_ID}`).reply(202, {
4853
anything: '123'
4954
})

packages/destination-actions/src/destinations/yahoo-audiences/index.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import type { AudienceDestinationDefinition, ModifiedResponse } from '@segment/actions-core'
1+
import type { AudienceDestinationDefinition } from '@segment/actions-core'
22
import { defaultValues, IntegrationError } from '@segment/actions-core'
33
import type { Settings, AudienceSettings } from './generated-types'
4-
import { generate_jwt } from './utils-rt'
54
import updateSegment from './updateSegment'
6-
import { gen_customer_taxonomy_payload, gen_segment_subtaxonomy_payload, update_taxonomy } from './utils-tax'
5+
import {
6+
gen_customer_taxonomy_payload,
7+
gen_segment_subtaxonomy_payload,
8+
update_taxonomy,
9+
get_taxonomy_access_token
10+
} from './utils-tax'
711
type PersonasSettings = {
812
computation_id: string
913
computation_key: string
1014
parent_id: string
1115
}
12-
interface RefreshTokenResponse {
13-
access_token: string
14-
}
1516

1617
const destination: AudienceDestinationDefinition<Settings, AudienceSettings> = {
1718
name: 'Yahoo Audiences',
@@ -80,22 +81,8 @@ const destination: AudienceDestinationDefinition<Settings, AudienceSettings> = {
8081
rt_client_secret = auth.clientSecret
8182
}
8283

83-
const prefixed_client_id = `idb2b.dsp.datax.${rt_client_key}`
84-
const jwt = generate_jwt(prefixed_client_id, rt_client_secret)
85-
const res: ModifiedResponse<RefreshTokenResponse> = await request<RefreshTokenResponse>(
86-
'https://id.b2b.yahooincapis.com/zts/v1/oauth2/token',
87-
{
88-
method: 'POST',
89-
body: new URLSearchParams({
90-
client_assertion: jwt,
91-
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
92-
grant_type: 'client_credentials',
93-
scope: 'idb2b.dsp.datax:role.online.writer',
94-
aud: 'https://id.b2b.yahooincapis.com/zts/v1'
95-
})
96-
}
97-
)
98-
const rt_access_token = res.data.access_token
84+
// Use shared token fetching utility (handles prefixing, JWT generation, and token request)
85+
const rt_access_token = await get_taxonomy_access_token(request, rt_client_key, rt_client_secret)
9986
return { accessToken: rt_access_token }
10087
}
10188
},

packages/destination-actions/src/destinations/yahoo-audiences/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,7 @@ export interface YahooSubTaxonomy {
3232
engage_space_id: string
3333
//identifier: string
3434
}
35+
36+
export interface TokenResponse {
37+
access_token: string
38+
}

packages/destination-actions/src/destinations/yahoo-audiences/utils-tax.ts

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import type { Settings } from './generated-types'
2-
import { createHmac } from 'crypto'
3-
import { CredsObj, YahooSubTaxonomy } from './types'
2+
import type { ModifiedResponse } from '@segment/actions-core'
3+
import { CredsObj, YahooSubTaxonomy, TokenResponse } from './types'
44
import { RequestClient, IntegrationError } from '@segment/actions-core'
55
import { StatsClient } from '@segment/actions-core/destination-kit'
6+
import { generate_jwt } from './utils-rt'
7+
8+
// Constants for Yahoo Taxonomy API
9+
const TAXONOMY_CLIENT_KEY_PREFIX = 'idb2b.dsp.datax'
10+
const TAXONOMY_TOKEN_ENDPOINT = 'https://id.b2b.yahooincapis.com/zts/v1/oauth2/token'
11+
const TAXONOMY_AUDIENCE_URL = 'https://id.b2b.yahooincapis.com/zts/v1'
12+
const TAXONOMY_SCOPE = 'idb2b.dsp.datax:role.online.writer'
613

714
export function gen_customer_taxonomy_payload(settings: Settings) {
815
const data = {
@@ -45,26 +52,45 @@ export function gen_random_id(length: number): string {
4552
return random_id.join('')
4653
}
4754

48-
export function gen_oauth1_signature(client_key: string, client_secret: string, method: string, url: string) {
49-
// Following logic in #9 https://oauth.net/core/1.0a/#sig_norm_param
50-
const timestamp = Math.floor(new Date().getTime() / 1000)
51-
const nonce = gen_random_id(15)
55+
/**
56+
* Obtains a short-lived OAuth 2.0 Bearer token for the Taxonomy API using the
57+
* same JWT client-credentials flow used by the Online (Realtime) API.
58+
* @param request RequestClient for making HTTP requests
59+
* @param tx_client_key Taxonomy API client key (will be prefixed with 'idb2b.dsp.datax.')
60+
* @param tx_client_secret Taxonomy API client secret
61+
* @returns OAuth 2.0 access token
62+
*/
63+
export async function get_taxonomy_access_token(
64+
request: RequestClient,
65+
tx_client_key: string,
66+
tx_client_secret: string
67+
): Promise<string> {
68+
// Prefix the client key as required by Yahoo's Taxonomy API
69+
const prefixed_client_key = `${TAXONOMY_CLIENT_KEY_PREFIX}.${tx_client_key}`
70+
71+
// Generate JWT using the shared utility
72+
const jwt = generate_jwt(prefixed_client_key, tx_client_secret)
73+
74+
const res: ModifiedResponse<TokenResponse> = await request<TokenResponse>(TAXONOMY_TOKEN_ENDPOINT, {
75+
method: 'POST',
76+
body: new URLSearchParams({
77+
client_assertion: jwt,
78+
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
79+
grant_type: 'client_credentials',
80+
scope: TAXONOMY_SCOPE,
81+
aud: TAXONOMY_AUDIENCE_URL
82+
})
83+
})
5284

53-
const param_string = `oauth_consumer_key=${encodeURIComponent(client_key)}&oauth_nonce=${encodeURIComponent(
54-
nonce
55-
)}&oauth_signature_method=${encodeURIComponent('HMAC-SHA1')}&oauth_timestamp=${encodeURIComponent(
56-
timestamp
57-
)}&oauth_version=${encodeURIComponent('1.0')}`
85+
if (!res?.data?.access_token) {
86+
throw new IntegrationError(
87+
'Failed to obtain taxonomy access token - missing access_token in response',
88+
'YAHOO_TAXONOMY_TOKEN_ERROR',
89+
500
90+
)
91+
}
5892

59-
const base_string = `${method.toUpperCase()}&${encodeURIComponent(url)}&${encodeURIComponent(param_string)}`
60-
const encoded_client_secret = encodeURIComponent(client_secret)
61-
const signature = encodeURIComponent(
62-
createHmac('sha1', encoded_client_secret + '&')
63-
.update(base_string)
64-
.digest('base64')
65-
)
66-
const oauth1_auth_string = `OAuth oauth_consumer_key="${client_key}", oauth_nonce="${nonce}", oauth_signature="${signature}", oauth_signature_method="HMAC-SHA1", oauth_timestamp="${timestamp}", oauth_version="1.0"`
67-
return oauth1_auth_string
93+
return res.data.access_token
6894
}
6995

7096
export async function update_taxonomy(
@@ -78,13 +104,16 @@ export async function update_taxonomy(
78104
const tx_client_secret = tx_creds.tx_client_secret
79105
const tx_client_key = tx_creds.tx_client_key
80106
const url = `https://datax.yahooapis.com/v1/taxonomy/append${engage_space_id.length > 0 ? '/' + engage_space_id : ''}`
81-
const oauth1_auth_string = gen_oauth1_signature(tx_client_key, tx_client_secret, 'PUT', url)
107+
108+
// Get a short-lived Bearer token using the same JWT client-credentials flow as the Online API
109+
const access_token = await get_taxonomy_access_token(request, tx_client_key, tx_client_secret)
110+
82111
try {
83112
const add_segment_node = await request(url, {
84113
method: 'PUT',
85114
body: body_form_data,
86115
headers: {
87-
Authorization: oauth1_auth_string,
116+
Authorization: `Bearer ${access_token}`,
88117
'Content-Type': 'multipart/form-data; boundary=SEGMENT-DATA'
89118
}
90119
})

0 commit comments

Comments
 (0)