Skip to content
Merged
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
10 changes: 5 additions & 5 deletions .github/workflows/app-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ jobs:
- run: npm ci
- name: Run E2E Tests
env:
JWT_PRIVATE_KEY: ${{ secrets.JWT_PRIVATE_KEY }}
JWT_CLIENTID: ${{ secrets.JWT_CLIENTID }}
JWT_TECH_ACC_ID: ${{ secrets.JWT_TECH_ACC_ID }}
JWT_ORG_ID: ${{ secrets.JWT_ORG_ID }}
JWT_CLIENT_SECRET: ${{ secrets.JWT_CLIENT_SECRET }}
IMS_CLIENT_ID: ${{ secrets.IMS_CLIENT_ID }}
IMS_ORG_ID: ${{ secrets.IMS_ORG_ID }}
IMS_CLIENT_SECRET: ${{ secrets.IMS_CLIENT_SECRET }}
IMS_SCOPES: ${{ secrets.IMS_SCOPES }}
IMS_ENV: prod
TARGET_TENANT: ${{ secrets.TARGET_TENANT }}
CAMPAIGN_STANDARD_TENANT_ID: ${{ secrets.CAMPAIGN_STANDARD_TENANT_ID }}
ANALYTICS_COMPANY: ${{ secrets.ANALYTICS_COMPANY }}
Expand Down
8,590 changes: 1,405 additions & 7,185 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 3 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,22 @@
"chalk": "^2.4.2",
"dotenv": "^16.4.7",
"execa": "^2.1.0",
"form-data": "^4.0.0",
"form-data": "^4.0.3",
"fs-extra": "^8.1.0",
"jsonwebtoken": "^9.0.2",
"node-fetch": "^2.6.7"
},
"devDependencies": {
"@adobe/eslint-config-aio-lib-config": "^4.0.0",
"eslint": "^8.57.1",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-jsdoc": "^48.11.0",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.6.0",
"eslint-plugin-standard": "^4.0.1",
"jest": "^29.7.0",
"stdout-stderr": "^0.1.13",
"typescript": "^5.7.3"
"stdout-stderr": "^0.1.13"
},
"engines": {
"node": ">=8.3.0"
Expand Down
30 changes: 14 additions & 16 deletions repositories.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
"repository": "https://github.com/adobe/aio-lib-target",
"branch": "master",
"requiredEnv": [ "TARGET_TENANT", "TARGET_APIKEY", "TARGET_TOKEN" ],
"requiredAuth": "jwt",
"mapEnv": { "JWT_CLIENTID": "TARGET_APIKEY" , "JWT_TOKEN": "TARGET_TOKEN" },
"requiredAuth": "oauth_s2s",
"mapEnv": { "IMS_CLIENT_ID": "TARGET_APIKEY" , "IMS_TOKEN": "TARGET_TOKEN" },
"doNotLog": [ "TARGET_TOKEN" ]
},

"aio-lib-campaign-standard" : {
"repository": "https://github.com/adobe/aio-lib-campaign-standard",
"branch": "master",
"requiredEnv": [ "CAMPAIGN_STANDARD_TENANT_ID", "CAMPAIGN_STANDARD_API_KEY", "CAMPAIGN_STANDARD_ACCESS_TOKEN" ],
"requiredAuth": "jwt",
"mapEnv": { "JWT_CLIENTID": "CAMPAIGN_STANDARD_API_KEY", "JWT_TOKEN": "CAMPAIGN_STANDARD_ACCESS_TOKEN" },
"requiredAuth": "oauth_s2s",
"mapEnv": { "IMS_CLIENT_ID": "CAMPAIGN_STANDARD_API_KEY", "IMS_TOKEN": "CAMPAIGN_STANDARD_ACCESS_TOKEN" },
"doNotLog": [ "CAMPAIGN_STANDARD_ACCESS_TOKEN" ],
"disabled": true
},
Expand All @@ -22,26 +22,26 @@
"repository": "https://github.com/adobe/aio-lib-analytics",
"branch": "master",
"requiredEnv": [ "ANALYTICS_COMPANY", "ANALYTICS_APIKEY", "ANALYTICS_TOKEN", "ANALYTICS_RSID"],
"requiredAuth": "jwt",
"mapEnv": { "JWT_CLIENTID": "ANALYTICS_APIKEY" , "JWT_TOKEN": "ANALYTICS_TOKEN" },
"requiredAuth": "oauth_s2s",
"mapEnv": { "IMS_CLIENT_ID": "ANALYTICS_APIKEY" , "IMS_TOKEN": "ANALYTICS_TOKEN" },
"doNotLog": [ "ANALYTICS_TOKEN" ]
},

"aio-lib-audience-manager-cd" : {
"repository": "https://github.com/adobe/aio-lib-audience-manager-cd",
"branch": "master",
"requiredEnv": [ "AUDIENCE_MANAGER_ORG_ID", "AUDIENCE_MANAGER_API_KEY", "AUDIENCE_MANAGER_ACCESS_TOKEN", "AUDIENCE_MANAGER_ID"],
"requiredAuth": "jwt",
"mapEnv": { "JWT_CLIENTID": "AUDIENCE_MANAGER_API_KEY" , "JWT_TOKEN": "AUDIENCE_MANAGER_ACCESS_TOKEN", "JWT_ORG_ID": "AUDIENCE_MANAGER_ORG_ID" },
"requiredAuth": "oauth_s2s",
"mapEnv": { "IMS_CLIENT_ID": "AUDIENCE_MANAGER_API_KEY" , "IMS_TOKEN": "AUDIENCE_MANAGER_ACCESS_TOKEN", "IMS_ORG_ID": "AUDIENCE_MANAGER_ORG_ID" },
"doNotLog": [ "AUDIENCE_MANAGER_ACCESS_TOKEN" ]
},

"aio-lib-events" : {
"repository": "https://github.com/adobe/aio-lib-events",
"branch": "master",
"requiredEnv": [ "EVENTS_ORG_ID", "EVENTS_API_KEY", "EVENTS_JWT_TOKEN", "EVENTS_CONSUMER_ORG_ID", "EVENTS_WORKSPACE_ID", "EVENTS_PROJECT_ID", "EVENTS_INTEGRATION_ID"],
"requiredAuth": "jwt",
"mapEnv": { "JWT_CLIENTID": "EVENTS_API_KEY" , "JWT_TOKEN": "EVENTS_JWT_TOKEN" },
"requiredAuth": "oauth_s2s",
"mapEnv": { "IMS_CLIENT_ID": "EVENTS_API_KEY" , "IMS_TOKEN": "EVENTS_JWT_TOKEN" },
"doNotLog": [ "EVENTS_JWT_TOKEN" ]
},

Expand All @@ -55,10 +55,8 @@
"aio-lib-ims" : {
"repository": "https://github.com/adobe/aio-lib-ims",
"branch": "master",
"requiredEnv": [ "IMS_CLIENT_ID", "IMS_CLIENT_SECRET", "IMS_SIGNED_JWT" ],
"requiredAuth": "jwt",
"mapEnv": { "JWT_CLIENTID": "IMS_CLIENT_ID" , "JWT_CLIENT_SECRET": "IMS_CLIENT_SECRET", "JWT_SIGNED": "IMS_SIGNED_JWT" },
"doNotLog": [ "JWT_CLIENT_SECRET", "JWT_SIGNED" ]
"requiredEnv": [ "IMS_CLIENT_ID", "IMS_CLIENT_SECRET", "IMS_ORG_ID", "IMS_SCOPES" ],
"doNotLog": [ "IMS_CLIENT_SECRET", "IMS_TOKEN" ]
},

"aio-lib-runtime" : {
Expand All @@ -73,8 +71,8 @@
"repository": "https://github.com/adobe/aio-lib-customer-profile",
"branch": "master",
"requiredEnv": [ "CUSTOMER_PROFILE_API_TENANT_ID", "CUSTOMER_PROFILE_API_IMS_ORG_ID", "CUSTOMER_PROFILE_API_API_KEY", "CUSTOMER_PROFILE_API_ACCESS_TOKEN" ],
"requiredAuth": "jwt",
"mapEnv": { "JWT_CLIENTID": "CUSTOMER_PROFILE_API_API_KEY" , "JWT_TOKEN": "CUSTOMER_PROFILE_API_ACCESS_TOKEN", "JWT_ORG_ID": "CUSTOMER_PROFILE_API_IMS_ORG_ID" },
"requiredAuth": "oauth_s2s",
"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" },
"doNotLog": [ "CUSTOMER_PROFILE_API_ACCESS_TOKEN" ]
},

Expand Down
194 changes: 39 additions & 155 deletions src/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,175 +13,59 @@ governing permissions and limitations under the License.
/* eslint-disable camelcase */

const fetch = require('node-fetch')
const jwt = require('jsonwebtoken')
const FormData = require('form-data')

const JWT_EXPIRY_SECONDS = 1200 // 20 minutes

/**
* Create a jwt payload.
*
* @param {object} options see getSignedJwt
* @param {number} millisecondsSinceEpoch the date in ms since epoch
* @returns {object} the payload
* @param {string} env ims env
* @param {string} clientId clientId
* @param {string} clientSecret clientSecret
* @param {string} orgId imsOrgId
* @param {string} scopes coma separated string
* @returns {{ access_token: string }} ims response
*/
function createJwtPayload (options, millisecondsSinceEpoch = Date.now()) {
let m = options.metaScopes
if (!Array.isArray(m)) {
m = m.split(',')
}

const metaScopes = {}
m.forEach(m => {
if (m.startsWith('https')) {
metaScopes[m] = true
} else {
metaScopes[`${options.ims}/s/${m}`] = true
}
})

return {
aud: `${options.ims}/c/${options.clientId}`,
exp: Math.round(JWT_EXPIRY_SECONDS + millisecondsSinceEpoch / 1000),
...metaScopes,
iss: options.orgId,
sub: options.technicalAccountId
async function getAccessTokenByClientCredentials (env, clientId, clientSecret, orgId, scopes) {
const IMS_ENDPOINTS = {
stage: 'https://ims-na1-stg1.adobelogin.com',
prod: 'https://ims-na1.adobelogin.com'
}
}

/**
* Gets an OAuth token.
*
* @param {string} actionURL the url to fetch the token from
* @returns {object} the token data
*/
async function getOauthToken (actionURL) {
const postOptions = {
method: 'POST'
}

const res = await fetch(actionURL, postOptions)
const json = await res.json()
const { access_token, error, error_description } = json
if (!access_token) {
if (error && error_description) {
throw new Error(`${error}: ${error_description}`)
} else {
throw new Error(`An unknown error occurred fetching oauth token. The response is as follows: ${JSON.stringify(json)}`)
}
}
return json
}

/**
* Gets a signed JWT.
*
* @param {object} options all the options for generating the JWT
* @param {string} options.clientId the jwt client id
* @param {string} options.technicalAccountId the technical account id of the credential
* @param {string} options.orgId the org id of the credential
* @param {string} options.clientSecret the jwt client secret
* @param {string} options.privateKey the jwt private key
* @param {string} [options.passphrase=''] the passphrase for private key, if set
* @param {Array<string>} options.metaScopes all the metascopes for the services tied to the credential
* @param {string} [options.ims='https://ims-na1.adobelogin.com'] the IMS endpoint
* @returns {string} the signed jwt
*/
async function getSignedJwt (options) {
const {
clientId,
technicalAccountId,
orgId,
clientSecret,
privateKey,
passphrase = '',
metaScopes = [
'https://ims-na1.adobelogin.com/s/ent_analytics_bulk_ingest_sdk',
'https://ims-na1.adobelogin.com/s/ent_marketing_sdk',
'https://ims-na1.adobelogin.com/s/ent_campaign_sdk',
'https://ims-na1.adobelogin.com/s/ent_adobeio_sdk',
'https://ims-na1.adobelogin.com/s/ent_audiencemanagerplatform_sdk'
],
ims = 'https://ims-na1.adobelogin.com'
} = options

const errors = []
if (!clientId) { errors.push('clientId') }
if (!technicalAccountId) { errors.push('technicalAccountId') }
if (!orgId) { errors.push('orgId') }
if (!clientSecret) { errors.push('clientSecret') }
if (!privateKey) { errors.push('privateKey') }
if (!metaScopes || metaScopes.length === 0) { errors.push('metaScopes') }
if (!ims) { errors.push('ims') }
if (errors.length > 0) {
throw new Error(`Required parameter(s) ${errors.join(', ')} are missing`)
const endpoint = IMS_ENDPOINTS[env]
if (!endpoint) {
throw new Error(`IMS_ENV must be one of "${Object.keys(IMS_ENDPOINTS)}"`)
}

const jwtPayload = createJwtPayload({ // potentially add the defaults, to options
...options,
passphrase,
metaScopes,
ims
})

const token = jwt.sign(
jwtPayload,
{ key: privateKey, passphrase },
{ algorithm: 'RS256' }
)

return token
}

/**
* Gets an OAuth token by exchanging a JWT.
*
* @param {object} options the parameters to send to the jwt exchange endpoint
* @param {string} options.clientId the jwt client id
* @param {string} options.clientSecret the jwt client secret
* @param {string} [options.ims='https://ims-na1.adobelogin.com'] the IMS endpoint
* @param {string} signedJwt the signed JWT
* @returns {object} the access token
*/
async function getJWTToken (options, signedJwt) {
const {
clientId,
clientSecret,
ims = 'https://ims-na1.adobelogin.com'
} = options

if (!signedJwt) {
signedJwt = await getSignedJwt(options)
// prepare the data with common data
const postData = {
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret,
org_id: orgId,
scope: scopes
}

const form = new FormData()
form.append('client_id', clientId)
form.append('client_secret', clientSecret)
form.append('jwt_token', signedJwt)
Object.entries(postData).forEach(([k, v]) =>
form.append(k, v)
)

const postOptions = {
method: 'POST',
body: form,
headers: form.getHeaders()
let res
try {
res = await fetch(
IMS_ENDPOINTS[env] + '/ims/token/v2',
{
method: 'POST',
headers: form.getHeaders(),
body: form
}
)
} catch (e) {
throw new Error(`cannot send request to IMS: ${e.message}`)
}

const res = await fetch(`${ims}/ims/exchange/jwt/`, postOptions)
const json = await res.json()
const { access_token, error, error_description } = json
if (!access_token) {
if (error && error_description) {
throw new Error(`${error}: ${error_description}`)
} else {
throw new Error(`An unknown error occurred while swapping jwt. The response is as follows: ${JSON.stringify(json)}`)
}
if (res.ok) {
return res.json()
}
return json
throw new Error(`error response from IMS with status: ${res.status} and body: ${await res.text()}`)
}

module.exports = {
JWT_EXPIRY_SECONDS,
createJwtPayload,
getSignedJwt,
getJWTToken,
getOauthToken
getAccessTokenByClientCredentials
}
Loading
Loading