Skip to content

Commit 328ec95

Browse files
authored
feat: support ims compatible api params + fixes (#7)
* feat: support ims compatible api params * fix: cache key, imsEnv input and validation as first step * fix: scopes must be an array * chore: tests
1 parent d0ddb51 commit 328ec95

5 files changed

Lines changed: 516 additions & 82 deletions

File tree

src/AuthErrors.js renamed to src/errors.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ const E = ErrorWrapper(
4040
// Error codes
4141
E('IMS_TOKEN_ERROR', 'Error calling IMS to get access token: %s')
4242
E('MISSING_PARAMETERS', 'Missing required parameters: %s')
43+
E('BAD_CREDENTIALS_FORMAT', 'Credentials must be either an object or a stringified object')
44+
E('BAD_SCOPES_FORMAT', 'Scopes must be an array')
4345
E('GENERIC_ERROR', 'An unexpected error occurred: %s')
4446

4547
export { codes, messages }

src/ims.js

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ OF ANY KIND, either express or implied. See the License for the specific languag
99
governing permissions and limitations under the License.
1010
*/
1111

12-
import { codes } from './AuthErrors.js'
12+
import { codes } from './errors.js'
1313

1414
/**
1515
* IMS Base URLs
@@ -21,36 +21,60 @@ const IMS_BASE_URL_STAGE = 'https://ims-na1-stg1.adobelogin.com'
2121
* Gets the IMS base URL based on the environment
2222
*
2323
* @private
24-
* @param {string} environment - The environment ('prod' or 'stage')
24+
* @param {string} env - The environment ('prod' or 'stage')
2525
* @returns {string} The IMS base URL
2626
*/
27-
function getImsUrl (environment) {
28-
return environment === 'stage' ? IMS_BASE_URL_STAGE : IMS_BASE_URL_PROD
27+
function getImsUrl (env) {
28+
return env === 'stage' ? IMS_BASE_URL_STAGE : IMS_BASE_URL_PROD
2929
}
3030

3131
/**
3232
* Validates required parameters for client credentials flow
3333
*
3434
* @private
3535
* @param {object} params - Parameters to validate
36-
* @param {string} params.clientId - The client ID
37-
* @param {string} params.clientSecret - The client secret
38-
* @param {string} params.orgId - The organization ID
39-
* @param {string[]} params.scopes - Array of scopes
36+
* @returns {object} Validated credentials object
4037
* @throws {Error} If any required parameters are missing
4138
*/
42-
function validateClientCredentialsParams ({ clientId, clientSecret, orgId, scopes }) {
39+
export function getAndValidateCredentials (params) {
40+
if (!(typeof params === 'object' && params !== null && !Array.isArray(params))) {
41+
throw new codes.BAD_CREDENTIALS_FORMAT({
42+
sdkDetails: { paramsType: typeof params }
43+
})
44+
}
45+
46+
if (params.scopes && !Array.isArray(params.scopes)) {
47+
throw new codes.BAD_SCOPES_FORMAT({
48+
sdkDetails: { scopesType: typeof params.scopes }
49+
})
50+
}
51+
52+
const credentials = {}
53+
credentials.clientId = params.clientId || params.client_id
54+
credentials.clientSecret = params.clientSecret || params.client_secret
55+
credentials.orgId = params.orgId || params.org_id
56+
credentials.scopes = params.scopes || []
57+
58+
const { clientId, clientSecret, orgId, scopes } = credentials
4359
const missingParams = []
44-
if (!clientId) missingParams.push('clientId')
45-
if (!clientSecret) missingParams.push('clientSecret')
46-
if (!orgId) missingParams.push('orgId')
60+
if (!clientId) {
61+
missingParams.push('clientId')
62+
}
63+
if (!clientSecret) {
64+
missingParams.push('clientSecret')
65+
}
66+
if (!orgId) {
67+
missingParams.push('orgId')
68+
}
4769

4870
if (missingParams.length > 0) {
4971
throw new codes.MISSING_PARAMETERS({
5072
messageValues: missingParams.join(', '),
5173
sdkDetails: { clientId, orgId, scopes }
5274
})
5375
}
76+
77+
return credentials
5478
}
5579

5680
/**
@@ -65,10 +89,8 @@ function validateClientCredentialsParams ({ clientId, clientSecret, orgId, scope
6589
* @returns {Promise<object>} Promise that resolves with the token response
6690
* @throws {Error} If there's an error getting the access token
6791
*/
68-
export async function getAccessTokenByClientCredentials ({ clientId, clientSecret, orgId, scopes = [], environment = 'prod' }) {
69-
validateClientCredentialsParams({ clientId, clientSecret, orgId, scopes })
70-
71-
const imsBaseUrl = getImsUrl(environment)
92+
export async function getAccessTokenByClientCredentials ({ clientId, clientSecret, orgId, scopes = [], env } ) {
93+
const imsBaseUrl = getImsUrl(env)
7294

7395
// Prepare form data using URLSearchParams (native Node.js)
7496
const formData = new URLSearchParams()

src/index.js

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,28 @@ OF ANY KIND, either express or implied. See the License for the specific languag
99
governing permissions and limitations under the License.
1010
*/
1111

12-
import { getAccessTokenByClientCredentials } from './ims.js'
12+
import { getAccessTokenByClientCredentials, getAndValidateCredentials } from './ims.js'
1313
import TTLCache from '@isaacs/ttlcache'
14+
import crypto from 'crypto'
1415

1516
// Token cache with TTL
16-
// Opinionated for now, we could make it confiurable in the future if needed -mg
17+
// Opinionated for now, we could make it configurable in the future if needed -mg
1718
const tokenCache = new TTLCache({ ttl: 5 * 60 * 1000 }) // 5 minutes in milliseconds
1819

1920
/**
2021
* Generates a cache key for token storage
2122
*
2223
* @private
23-
* @param {string} clientId - The client ID
24-
* @param {string} orgId - The organization ID
25-
* @param {string} environment - The environment
26-
* @param {string[]} scopes - Array of scopes
24+
* @param {object} credentials - The credentials object
25+
* @param {string} credentials.clientId - The client ID
26+
* @param {string} credentials.orgId - The organization ID
27+
* @param {string} credentials.env - The env
28+
* @param {string[]} credentials.scopes - Array of scopes
2729
* @returns {string} The cache key
2830
*/
29-
function getCacheKey (clientId, orgId, environment, scopes) {
31+
function getCacheKey ({clientId, orgId, env, scopes, clientSecret}) {
3032
const scopeKey = scopes.length > 0 ? scopes.sort().join(',') : 'none'
31-
return `${clientId}:${orgId}:${environment}:${scopeKey}`
33+
return crypto.createHash('sha1').update(`${clientId}:${orgId}:${scopeKey}:${clientSecret}:${env}`).digest('hex')
3234
}
3335

3436
/**
@@ -48,20 +50,24 @@ export function invalidateCache () {
4850
* @param {string} params.clientSecret - The client secret
4951
* @param {string} params.orgId - The organization ID
5052
* @param {string[]} [params.scopes=[]] - Array of scopes to request
51-
* @param {string} [params.environment='prod'] - The IMS environment ('prod' or 'stage')
53+
* @param {string} [imsEnv='prod'] - The IMS environment ('prod' or 'stage')
5254
* @returns {Promise<object>} Promise that resolves with the token response
5355
* @throws {Error} If there's an error getting the access token
5456
*/
55-
export async function generateAccessToken ({ clientId, clientSecret, orgId, scopes = [], environment = 'prod' }) {
57+
export async function generateAccessToken (params, imsEnv = 'prod' ) {
58+
const credentials = getAndValidateCredentials(params)
59+
60+
const credAndEnv = { ...credentials, env: imsEnv }
61+
5662
// Check cache first
57-
const cacheKey = getCacheKey(clientId, orgId, environment, scopes)
63+
const cacheKey = getCacheKey(credAndEnv)
5864
const cachedToken = tokenCache.get(cacheKey)
5965
if (cachedToken) {
6066
return cachedToken
6167
}
6268

6369
// Get token from IMS
64-
const token = await getAccessTokenByClientCredentials({ clientId, clientSecret, orgId, scopes, environment })
70+
const token = await getAccessTokenByClientCredentials(credAndEnv)
6571

6672
// Cache the token
6773
tokenCache.set(cacheKey, token)

test/AuthErrors.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ governing permissions and limitations under the License.
1010
*/
1111

1212
import { describe, test, expect } from 'vitest'
13-
import { codes, messages } from '../src/AuthErrors.js'
13+
import { codes, messages } from '../src/errors.js'
1414

1515
describe('AuthErrors', () => {
1616
test('codes object is defined', () => {

0 commit comments

Comments
 (0)