Skip to content

Commit 6f66e21

Browse files
authored
fix: update to s2s credentials (#178)
* fix: update to s2s credentials * fix env * events tests still rely on a variable named EVENTS_JWT_TOKEN
1 parent fd0db8b commit 6f66e21

File tree

8 files changed

+1523
-7705
lines changed

8 files changed

+1523
-7705
lines changed

.github/workflows/app-test.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ jobs:
2424
- run: npm ci
2525
- name: Run E2E Tests
2626
env:
27-
JWT_PRIVATE_KEY: ${{ secrets.JWT_PRIVATE_KEY }}
28-
JWT_CLIENTID: ${{ secrets.JWT_CLIENTID }}
29-
JWT_TECH_ACC_ID: ${{ secrets.JWT_TECH_ACC_ID }}
30-
JWT_ORG_ID: ${{ secrets.JWT_ORG_ID }}
31-
JWT_CLIENT_SECRET: ${{ secrets.JWT_CLIENT_SECRET }}
27+
IMS_CLIENT_ID: ${{ secrets.IMS_CLIENT_ID }}
28+
IMS_ORG_ID: ${{ secrets.IMS_ORG_ID }}
29+
IMS_CLIENT_SECRET: ${{ secrets.IMS_CLIENT_SECRET }}
30+
IMS_SCOPES: ${{ secrets.IMS_SCOPES }}
31+
IMS_ENV: prod
3232
TARGET_TENANT: ${{ secrets.TARGET_TENANT }}
3333
CAMPAIGN_STANDARD_TENANT_ID: ${{ secrets.CAMPAIGN_STANDARD_TENANT_ID }}
3434
ANALYTICS_COMPANY: ${{ secrets.ANALYTICS_COMPANY }}

package-lock.json

Lines changed: 1405 additions & 7185 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,22 @@
2525
"chalk": "^2.4.2",
2626
"dotenv": "^16.4.7",
2727
"execa": "^2.1.0",
28-
"form-data": "^4.0.0",
28+
"form-data": "^4.0.3",
2929
"fs-extra": "^8.1.0",
30-
"jsonwebtoken": "^9.0.2",
3130
"node-fetch": "^2.6.7"
3231
},
3332
"devDependencies": {
3433
"@adobe/eslint-config-aio-lib-config": "^4.0.0",
3534
"eslint": "^8.57.1",
3635
"eslint-config-standard": "^17.1.0",
37-
"eslint-plugin-import": "^2.31.0",
36+
"eslint-plugin-import": "^2.32.0",
3837
"eslint-plugin-jest": "^27.9.0",
3938
"eslint-plugin-jsdoc": "^48.11.0",
4039
"eslint-plugin-n": "^15.7.0",
4140
"eslint-plugin-node": "^11.1.0",
4241
"eslint-plugin-promise": "^6.6.0",
43-
"eslint-plugin-standard": "^4.0.1",
4442
"jest": "^29.7.0",
45-
"stdout-stderr": "^0.1.13",
46-
"typescript": "^5.7.3"
43+
"stdout-stderr": "^0.1.13"
4744
},
4845
"engines": {
4946
"node": ">=8.3.0"

repositories.json

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
"repository": "https://github.com/adobe/aio-lib-target",
44
"branch": "master",
55
"requiredEnv": [ "TARGET_TENANT", "TARGET_APIKEY", "TARGET_TOKEN" ],
6-
"requiredAuth": "jwt",
7-
"mapEnv": { "JWT_CLIENTID": "TARGET_APIKEY" , "JWT_TOKEN": "TARGET_TOKEN" },
6+
"requiredAuth": "oauth_s2s",
7+
"mapEnv": { "IMS_CLIENT_ID": "TARGET_APIKEY" , "IMS_TOKEN": "TARGET_TOKEN" },
88
"doNotLog": [ "TARGET_TOKEN" ]
99
},
1010

1111
"aio-lib-campaign-standard" : {
1212
"repository": "https://github.com/adobe/aio-lib-campaign-standard",
1313
"branch": "master",
1414
"requiredEnv": [ "CAMPAIGN_STANDARD_TENANT_ID", "CAMPAIGN_STANDARD_API_KEY", "CAMPAIGN_STANDARD_ACCESS_TOKEN" ],
15-
"requiredAuth": "jwt",
16-
"mapEnv": { "JWT_CLIENTID": "CAMPAIGN_STANDARD_API_KEY", "JWT_TOKEN": "CAMPAIGN_STANDARD_ACCESS_TOKEN" },
15+
"requiredAuth": "oauth_s2s",
16+
"mapEnv": { "IMS_CLIENT_ID": "CAMPAIGN_STANDARD_API_KEY", "IMS_TOKEN": "CAMPAIGN_STANDARD_ACCESS_TOKEN" },
1717
"doNotLog": [ "CAMPAIGN_STANDARD_ACCESS_TOKEN" ],
1818
"disabled": true
1919
},
@@ -22,26 +22,26 @@
2222
"repository": "https://github.com/adobe/aio-lib-analytics",
2323
"branch": "master",
2424
"requiredEnv": [ "ANALYTICS_COMPANY", "ANALYTICS_APIKEY", "ANALYTICS_TOKEN", "ANALYTICS_RSID"],
25-
"requiredAuth": "jwt",
26-
"mapEnv": { "JWT_CLIENTID": "ANALYTICS_APIKEY" , "JWT_TOKEN": "ANALYTICS_TOKEN" },
25+
"requiredAuth": "oauth_s2s",
26+
"mapEnv": { "IMS_CLIENT_ID": "ANALYTICS_APIKEY" , "IMS_TOKEN": "ANALYTICS_TOKEN" },
2727
"doNotLog": [ "ANALYTICS_TOKEN" ]
2828
},
2929

3030
"aio-lib-audience-manager-cd" : {
3131
"repository": "https://github.com/adobe/aio-lib-audience-manager-cd",
3232
"branch": "master",
3333
"requiredEnv": [ "AUDIENCE_MANAGER_ORG_ID", "AUDIENCE_MANAGER_API_KEY", "AUDIENCE_MANAGER_ACCESS_TOKEN", "AUDIENCE_MANAGER_ID"],
34-
"requiredAuth": "jwt",
35-
"mapEnv": { "JWT_CLIENTID": "AUDIENCE_MANAGER_API_KEY" , "JWT_TOKEN": "AUDIENCE_MANAGER_ACCESS_TOKEN", "JWT_ORG_ID": "AUDIENCE_MANAGER_ORG_ID" },
34+
"requiredAuth": "oauth_s2s",
35+
"mapEnv": { "IMS_CLIENT_ID": "AUDIENCE_MANAGER_API_KEY" , "IMS_TOKEN": "AUDIENCE_MANAGER_ACCESS_TOKEN", "IMS_ORG_ID": "AUDIENCE_MANAGER_ORG_ID" },
3636
"doNotLog": [ "AUDIENCE_MANAGER_ACCESS_TOKEN" ]
3737
},
3838

3939
"aio-lib-events" : {
4040
"repository": "https://github.com/adobe/aio-lib-events",
4141
"branch": "master",
4242
"requiredEnv": [ "EVENTS_ORG_ID", "EVENTS_API_KEY", "EVENTS_JWT_TOKEN", "EVENTS_CONSUMER_ORG_ID", "EVENTS_WORKSPACE_ID", "EVENTS_PROJECT_ID", "EVENTS_INTEGRATION_ID"],
43-
"requiredAuth": "jwt",
44-
"mapEnv": { "JWT_CLIENTID": "EVENTS_API_KEY" , "JWT_TOKEN": "EVENTS_JWT_TOKEN" },
43+
"requiredAuth": "oauth_s2s",
44+
"mapEnv": { "IMS_CLIENT_ID": "EVENTS_API_KEY" , "IMS_TOKEN": "EVENTS_JWT_TOKEN" },
4545
"doNotLog": [ "EVENTS_JWT_TOKEN" ]
4646
},
4747

@@ -55,10 +55,8 @@
5555
"aio-lib-ims" : {
5656
"repository": "https://github.com/adobe/aio-lib-ims",
5757
"branch": "master",
58-
"requiredEnv": [ "IMS_CLIENT_ID", "IMS_CLIENT_SECRET", "IMS_SIGNED_JWT" ],
59-
"requiredAuth": "jwt",
60-
"mapEnv": { "JWT_CLIENTID": "IMS_CLIENT_ID" , "JWT_CLIENT_SECRET": "IMS_CLIENT_SECRET", "JWT_SIGNED": "IMS_SIGNED_JWT" },
61-
"doNotLog": [ "JWT_CLIENT_SECRET", "JWT_SIGNED" ]
58+
"requiredEnv": [ "IMS_CLIENT_ID", "IMS_CLIENT_SECRET", "IMS_ORG_ID", "IMS_SCOPES" ],
59+
"doNotLog": [ "IMS_CLIENT_SECRET", "IMS_TOKEN" ]
6260
},
6361

6462
"aio-lib-runtime" : {
@@ -73,8 +71,8 @@
7371
"repository": "https://github.com/adobe/aio-lib-customer-profile",
7472
"branch": "master",
7573
"requiredEnv": [ "CUSTOMER_PROFILE_API_TENANT_ID", "CUSTOMER_PROFILE_API_IMS_ORG_ID", "CUSTOMER_PROFILE_API_API_KEY", "CUSTOMER_PROFILE_API_ACCESS_TOKEN" ],
76-
"requiredAuth": "jwt",
77-
"mapEnv": { "JWT_CLIENTID": "CUSTOMER_PROFILE_API_API_KEY" , "JWT_TOKEN": "CUSTOMER_PROFILE_API_ACCESS_TOKEN", "JWT_ORG_ID": "CUSTOMER_PROFILE_API_IMS_ORG_ID" },
74+
"requiredAuth": "oauth_s2s",
75+
"mapEnv": { "IMS_CLIENT_ID": "CUSTOMER_PROFILE_API_API_KEY" , "IMS_TOKEN": "CUSTOMER_PROFILE_API_ACCESS_TOKEN", "IMS_ORG_ID": "CUSTOMER_PROFILE_API_IMS_ORG_ID" },
7876
"doNotLog": [ "CUSTOMER_PROFILE_API_ACCESS_TOKEN" ]
7977
},
8078

src/auth.js

Lines changed: 39 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -13,175 +13,59 @@ governing permissions and limitations under the License.
1313
/* eslint-disable camelcase */
1414

1515
const fetch = require('node-fetch')
16-
const jwt = require('jsonwebtoken')
1716
const FormData = require('form-data')
1817

19-
const JWT_EXPIRY_SECONDS = 1200 // 20 minutes
20-
2118
/**
22-
* Create a jwt payload.
23-
*
24-
* @param {object} options see getSignedJwt
25-
* @param {number} millisecondsSinceEpoch the date in ms since epoch
26-
* @returns {object} the payload
19+
* @param {string} env ims env
20+
* @param {string} clientId clientId
21+
* @param {string} clientSecret clientSecret
22+
* @param {string} orgId imsOrgId
23+
* @param {string} scopes coma separated string
24+
* @returns {{ access_token: string }} ims response
2725
*/
28-
function createJwtPayload (options, millisecondsSinceEpoch = Date.now()) {
29-
let m = options.metaScopes
30-
if (!Array.isArray(m)) {
31-
m = m.split(',')
32-
}
33-
34-
const metaScopes = {}
35-
m.forEach(m => {
36-
if (m.startsWith('https')) {
37-
metaScopes[m] = true
38-
} else {
39-
metaScopes[`${options.ims}/s/${m}`] = true
40-
}
41-
})
42-
43-
return {
44-
aud: `${options.ims}/c/${options.clientId}`,
45-
exp: Math.round(JWT_EXPIRY_SECONDS + millisecondsSinceEpoch / 1000),
46-
...metaScopes,
47-
iss: options.orgId,
48-
sub: options.technicalAccountId
26+
async function getAccessTokenByClientCredentials (env, clientId, clientSecret, orgId, scopes) {
27+
const IMS_ENDPOINTS = {
28+
stage: 'https://ims-na1-stg1.adobelogin.com',
29+
prod: 'https://ims-na1.adobelogin.com'
4930
}
50-
}
51-
52-
/**
53-
* Gets an OAuth token.
54-
*
55-
* @param {string} actionURL the url to fetch the token from
56-
* @returns {object} the token data
57-
*/
58-
async function getOauthToken (actionURL) {
59-
const postOptions = {
60-
method: 'POST'
61-
}
62-
63-
const res = await fetch(actionURL, postOptions)
64-
const json = await res.json()
65-
const { access_token, error, error_description } = json
66-
if (!access_token) {
67-
if (error && error_description) {
68-
throw new Error(`${error}: ${error_description}`)
69-
} else {
70-
throw new Error(`An unknown error occurred fetching oauth token. The response is as follows: ${JSON.stringify(json)}`)
71-
}
72-
}
73-
return json
74-
}
75-
76-
/**
77-
* Gets a signed JWT.
78-
*
79-
* @param {object} options all the options for generating the JWT
80-
* @param {string} options.clientId the jwt client id
81-
* @param {string} options.technicalAccountId the technical account id of the credential
82-
* @param {string} options.orgId the org id of the credential
83-
* @param {string} options.clientSecret the jwt client secret
84-
* @param {string} options.privateKey the jwt private key
85-
* @param {string} [options.passphrase=''] the passphrase for private key, if set
86-
* @param {Array<string>} options.metaScopes all the metascopes for the services tied to the credential
87-
* @param {string} [options.ims='https://ims-na1.adobelogin.com'] the IMS endpoint
88-
* @returns {string} the signed jwt
89-
*/
90-
async function getSignedJwt (options) {
91-
const {
92-
clientId,
93-
technicalAccountId,
94-
orgId,
95-
clientSecret,
96-
privateKey,
97-
passphrase = '',
98-
metaScopes = [
99-
'https://ims-na1.adobelogin.com/s/ent_analytics_bulk_ingest_sdk',
100-
'https://ims-na1.adobelogin.com/s/ent_marketing_sdk',
101-
'https://ims-na1.adobelogin.com/s/ent_campaign_sdk',
102-
'https://ims-na1.adobelogin.com/s/ent_adobeio_sdk',
103-
'https://ims-na1.adobelogin.com/s/ent_audiencemanagerplatform_sdk'
104-
],
105-
ims = 'https://ims-na1.adobelogin.com'
106-
} = options
107-
108-
const errors = []
109-
if (!clientId) { errors.push('clientId') }
110-
if (!technicalAccountId) { errors.push('technicalAccountId') }
111-
if (!orgId) { errors.push('orgId') }
112-
if (!clientSecret) { errors.push('clientSecret') }
113-
if (!privateKey) { errors.push('privateKey') }
114-
if (!metaScopes || metaScopes.length === 0) { errors.push('metaScopes') }
115-
if (!ims) { errors.push('ims') }
116-
if (errors.length > 0) {
117-
throw new Error(`Required parameter(s) ${errors.join(', ')} are missing`)
31+
const endpoint = IMS_ENDPOINTS[env]
32+
if (!endpoint) {
33+
throw new Error(`IMS_ENV must be one of "${Object.keys(IMS_ENDPOINTS)}"`)
11834
}
11935

120-
const jwtPayload = createJwtPayload({ // potentially add the defaults, to options
121-
...options,
122-
passphrase,
123-
metaScopes,
124-
ims
125-
})
126-
127-
const token = jwt.sign(
128-
jwtPayload,
129-
{ key: privateKey, passphrase },
130-
{ algorithm: 'RS256' }
131-
)
132-
133-
return token
134-
}
135-
136-
/**
137-
* Gets an OAuth token by exchanging a JWT.
138-
*
139-
* @param {object} options the parameters to send to the jwt exchange endpoint
140-
* @param {string} options.clientId the jwt client id
141-
* @param {string} options.clientSecret the jwt client secret
142-
* @param {string} [options.ims='https://ims-na1.adobelogin.com'] the IMS endpoint
143-
* @param {string} signedJwt the signed JWT
144-
* @returns {object} the access token
145-
*/
146-
async function getJWTToken (options, signedJwt) {
147-
const {
148-
clientId,
149-
clientSecret,
150-
ims = 'https://ims-na1.adobelogin.com'
151-
} = options
152-
153-
if (!signedJwt) {
154-
signedJwt = await getSignedJwt(options)
36+
// prepare the data with common data
37+
const postData = {
38+
grant_type: 'client_credentials',
39+
client_id: clientId,
40+
client_secret: clientSecret,
41+
org_id: orgId,
42+
scope: scopes
15543
}
156-
15744
const form = new FormData()
158-
form.append('client_id', clientId)
159-
form.append('client_secret', clientSecret)
160-
form.append('jwt_token', signedJwt)
45+
Object.entries(postData).forEach(([k, v]) =>
46+
form.append(k, v)
47+
)
16148

162-
const postOptions = {
163-
method: 'POST',
164-
body: form,
165-
headers: form.getHeaders()
49+
let res
50+
try {
51+
res = await fetch(
52+
IMS_ENDPOINTS[env] + '/ims/token/v2',
53+
{
54+
method: 'POST',
55+
headers: form.getHeaders(),
56+
body: form
57+
}
58+
)
59+
} catch (e) {
60+
throw new Error(`cannot send request to IMS: ${e.message}`)
16661
}
16762

168-
const res = await fetch(`${ims}/ims/exchange/jwt/`, postOptions)
169-
const json = await res.json()
170-
const { access_token, error, error_description } = json
171-
if (!access_token) {
172-
if (error && error_description) {
173-
throw new Error(`${error}: ${error_description}`)
174-
} else {
175-
throw new Error(`An unknown error occurred while swapping jwt. The response is as follows: ${JSON.stringify(json)}`)
176-
}
63+
if (res.ok) {
64+
return res.json()
17765
}
178-
return json
66+
throw new Error(`error response from IMS with status: ${res.status} and body: ${await res.text()}`)
17967
}
18068

18169
module.exports = {
182-
JWT_EXPIRY_SECONDS,
183-
createJwtPayload,
184-
getSignedJwt,
185-
getJWTToken,
186-
getOauthToken
70+
getAccessTokenByClientCredentials
18771
}

0 commit comments

Comments
 (0)