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
6 changes: 3 additions & 3 deletions doc/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<dt><a href="#invalidateCache">invalidateCache()</a> ⇒ <code>void</code></dt>
<dd><p>Invalidates the token cache</p>
</dd>
<dt><a href="#generateAccessToken">generateAccessToken(params)</a> ⇒ <code>Promise.&lt;object&gt;</code></dt>
<dt><a href="#generateAccessToken">generateAccessToken(params, [imsEnv])</a> ⇒ <code>Promise.&lt;object&gt;</code></dt>
<dd><p>Generates an access token for authentication (with caching)</p>
</dd>
</dl>
Expand Down Expand Up @@ -73,7 +73,7 @@ Invalidates the token cache
**Kind**: global function
<a name="generateAccessToken"></a>

## generateAccessToken(params) ⇒ <code>Promise.&lt;object&gt;</code>
## generateAccessToken(params, [imsEnv]) ⇒ <code>Promise.&lt;object&gt;</code>
Generates an access token for authentication (with caching)

**Kind**: global function
Expand All @@ -90,5 +90,5 @@ Generates an access token for authentication (with caching)
| params.clientSecret | <code>string</code> | | The client secret |
| params.orgId | <code>string</code> | | The organization ID |
| [params.scopes] | <code>Array.&lt;string&gt;</code> | <code>[]</code> | Array of scopes to request |
| [params.environment] | <code>string</code> | <code>&quot;&#x27;prod&#x27;&quot;</code> | The IMS environment ('prod' or 'stage') |
| [imsEnv] | <code>string</code> | | The IMS environment ('prod' or 'stage'); when omitted or falsy, uses stage if __OW_NAMESPACE starts with 'development-', else prod |

9 changes: 0 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,8 @@
"name": "@adobe/aio-lib-core-auth",
"version": "1.0.0",
"description": "Adobe I/O Core Authentication Library",
"type": "module",
"main": "src/index.js",
"types": "types.d.ts",
"exports": {
".": {
"import": "./src/index.js",
"types": "./types.d.ts",
"require": "./src/index.js",
"default": "./src/index.js"
}
},
"scripts": {
"test": "vitest run --coverage",
"lint": "eslint src test",
Expand Down
4 changes: 2 additions & 2 deletions src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ OF ANY KIND, either express or implied. See the License for the specific languag
governing permissions and limitations under the License.
*/

import { AioCoreSDKErrorWrapper } from '@adobe/aio-lib-core-errors'
const { AioCoreSDKErrorWrapper } = require('@adobe/aio-lib-core-errors')
const { ErrorWrapper, createUpdater } = AioCoreSDKErrorWrapper

const codes = {}
Expand Down Expand Up @@ -44,4 +44,4 @@ E('BAD_CREDENTIALS_FORMAT', 'Credentials must be either an object or a stringifi
E('BAD_SCOPES_FORMAT', 'Scopes must be an array')
E('GENERIC_ERROR', 'An unexpected error occurred: %s')

export { codes, messages }
module.exports = { codes, messages }
15 changes: 10 additions & 5 deletions src/ims.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ OF ANY KIND, either express or implied. See the License for the specific languag
governing permissions and limitations under the License.
*/

import { codes } from './errors.js'
const { codes } = require('./errors.js')

/**
* IMS Base URLs
Expand All @@ -36,7 +36,7 @@ function getImsUrl (env) {
* @returns {object} Validated credentials object
* @throws {Error} If any required parameters are missing
*/
export function getAndValidateCredentials (params) {
function getAndValidateCredentials (params) {
if (!(typeof params === 'object' && params !== null && !Array.isArray(params))) {
throw new codes.BAD_CREDENTIALS_FORMAT({
sdkDetails: { paramsType: typeof params }
Expand Down Expand Up @@ -89,7 +89,7 @@ export function getAndValidateCredentials (params) {
* @returns {Promise<object>} Promise that resolves with the token response
* @throws {Error} If there's an error getting the access token
*/
export async function getAccessTokenByClientCredentials ({ clientId, clientSecret, orgId, scopes = [], env } ) {
async function getAccessTokenByClientCredentials ({ clientId, clientSecret, orgId, scopes = [], env } ) {
const imsBaseUrl = getImsUrl(env)

// Prepare form data using URLSearchParams (native Node.js)
Expand All @@ -109,7 +109,7 @@ export async function getAccessTokenByClientCredentials ({ clientId, clientSecre
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formData.toString()
})
/* v8 ignore next */})

const data = await response.json()

Expand All @@ -131,7 +131,7 @@ export async function getAccessTokenByClientCredentials ({ clientId, clientSecre
scopes,
imsEnv: env
}
})
/* v8 ignore next */})
}

return data
Expand All @@ -154,3 +154,8 @@ export async function getAccessTokenByClientCredentials ({ clientId, clientSecre
})
}
}

module.exports = {
getAndValidateCredentials,
getAccessTokenByClientCredentials
}
16 changes: 11 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ OF ANY KIND, either express or implied. See the License for the specific languag
governing permissions and limitations under the License.
*/

import { getAccessTokenByClientCredentials, getAndValidateCredentials } from './ims.js'
import { TTLCache } from '@isaacs/ttlcache'
import crypto from 'crypto'
const { getAccessTokenByClientCredentials, getAndValidateCredentials } = require('./ims.js')
const { codes, messages } = require('./errors.js')
const { TTLCache } = require('@isaacs/ttlcache')
const crypto = require('crypto')

// Token cache with TTL
// Opinionated for now, we could make it configurable in the future if needed -mg
Expand All @@ -38,7 +39,7 @@ function getCacheKey ({clientId, orgId, env, scopes, clientSecret}) {
*
* @returns {void}
*/
export function invalidateCache () {
function invalidateCache () {
tokenCache.clear()
}

Expand All @@ -54,7 +55,7 @@ export function invalidateCache () {
* @returns {Promise<object>} Promise that resolves with the token response
* @throws {Error} If there's an error getting the access token
*/
export async function generateAccessToken (params, imsEnv) {
async function generateAccessToken (params, imsEnv) {
imsEnv = imsEnv || (ioRuntimeStageNamespace() ? 'stage' : 'prod')

const credentials = getAndValidateCredentials(params)
Expand All @@ -80,3 +81,8 @@ export async function generateAccessToken (params, imsEnv) {
function ioRuntimeStageNamespace () {
return process.env.__OW_NAMESPACE && process.env.__OW_NAMESPACE.startsWith('development-')
}

module.exports = {
invalidateCache,
generateAccessToken
}
39 changes: 20 additions & 19 deletions test/ims.test.js → test/ims.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ governing permissions and limitations under the License.
*/

import { describe, test, expect, beforeEach, vi } from 'vitest'
import { getAccessTokenByClientCredentials, getAndValidateCredentials } from '../src/ims.js'
import { codes } from '../src/errors.js'
import {
getAccessTokenByClientCredentials,
getAndValidateCredentials
} from '../src/ims.js'

// Mock fetch globally
global.fetch = vi.fn()
Expand Down Expand Up @@ -205,7 +207,7 @@ describe('getAccessTokenByClientCredentials', () => {

await expect(getAccessTokenByClientCredentials(validParams))
.rejects
.toThrow(codes.IMS_TOKEN_ERROR)
.toThrow('IMS_TOKEN_ERROR')

// Additional validation
let error
Expand Down Expand Up @@ -234,7 +236,7 @@ describe('getAccessTokenByClientCredentials', () => {

await expect(getAccessTokenByClientCredentials(validParams))
.rejects
.toThrow(codes.IMS_TOKEN_ERROR)
.toThrow('IMS_TOKEN_ERROR')
})

test('throws IMS_TOKEN_ERROR with HTTP status when no error fields present', async () => {
Expand All @@ -257,15 +259,14 @@ describe('getAccessTokenByClientCredentials', () => {
expect(error.name).toBe('AuthSDKError')
expect(error.code).toBe('IMS_TOKEN_ERROR')
expect(error.message).toContain('HTTP 503')
expect(error.sdkDetails.statusCode).toBe(503)
})

test('throws GENERIC_ERROR on network failure', async () => {
fetch.mockRejectedValue(new Error('Network connection failed'))

await expect(getAccessTokenByClientCredentials(validParams))
.rejects
.toThrow(codes.GENERIC_ERROR)
.toThrow('GENERIC_ERROR')

// Additional validation
let error
Expand All @@ -284,7 +285,7 @@ describe('getAccessTokenByClientCredentials', () => {

await expect(getAccessTokenByClientCredentials(validParams))
.rejects
.toThrow(codes.GENERIC_ERROR)
.toThrow('GENERIC_ERROR')
})

test('includes sdkDetails in error for debugging', async () => {
Expand All @@ -302,7 +303,7 @@ describe('getAccessTokenByClientCredentials', () => {

await expect(getAccessTokenByClientCredentials(validParams))
.rejects
.toThrow(codes.IMS_TOKEN_ERROR)
.toThrow('IMS_TOKEN_ERROR')

// Additional validation
let error
Expand Down Expand Up @@ -431,7 +432,7 @@ describe('getAndValidateCredentials', () => {

test('throws BAD_CREDENTIALS_FORMAT when params is null', () => {
expect(() => getAndValidateCredentials(null))
.toThrow(codes.BAD_CREDENTIALS_FORMAT)
.toThrow('BAD_CREDENTIALS_FORMAT')

let error
try {
Expand All @@ -445,22 +446,22 @@ describe('getAndValidateCredentials', () => {

test('throws BAD_CREDENTIALS_FORMAT when params is undefined', () => {
expect(() => getAndValidateCredentials(undefined))
.toThrow(codes.BAD_CREDENTIALS_FORMAT)
.toThrow('BAD_CREDENTIALS_FORMAT')
})

test('throws BAD_CREDENTIALS_FORMAT when params is an array', () => {
expect(() => getAndValidateCredentials(['test']))
.toThrow(codes.BAD_CREDENTIALS_FORMAT)
.toThrow('BAD_CREDENTIALS_FORMAT')
})

test('throws BAD_CREDENTIALS_FORMAT when params is a string', () => {
expect(() => getAndValidateCredentials('test'))
.toThrow(codes.BAD_CREDENTIALS_FORMAT)
.toThrow('BAD_CREDENTIALS_FORMAT')
})

test('throws BAD_CREDENTIALS_FORMAT when params is a number', () => {
expect(() => getAndValidateCredentials(123))
.toThrow(codes.BAD_CREDENTIALS_FORMAT)
.toThrow('BAD_CREDENTIALS_FORMAT')
})

test('throws MISSING_PARAMETERS when clientId is missing', () => {
Expand All @@ -470,7 +471,7 @@ describe('getAndValidateCredentials', () => {
}

expect(() => getAndValidateCredentials(params))
.toThrow(codes.MISSING_PARAMETERS)
.toThrow('MISSING_PARAMETERS')
})

test('throws MISSING_PARAMETERS when clientSecret is missing', () => {
Expand All @@ -480,7 +481,7 @@ describe('getAndValidateCredentials', () => {
}

expect(() => getAndValidateCredentials(params))
.toThrow(codes.MISSING_PARAMETERS)
.toThrow('MISSING_PARAMETERS')
})

test('throws MISSING_PARAMETERS when orgId is missing', () => {
Expand All @@ -490,7 +491,7 @@ describe('getAndValidateCredentials', () => {
}

expect(() => getAndValidateCredentials(params))
.toThrow(codes.MISSING_PARAMETERS)
.toThrow('MISSING_PARAMETERS')
})

test('throws MISSING_PARAMETERS with all missing params listed', () => {
Expand All @@ -517,7 +518,7 @@ describe('getAndValidateCredentials', () => {
}

expect(() => getAndValidateCredentials(params))
.toThrow(codes.BAD_SCOPES_FORMAT)
.toThrow('BAD_SCOPES_FORMAT')

let error
try {
Expand All @@ -539,7 +540,7 @@ describe('getAndValidateCredentials', () => {
}

expect(() => getAndValidateCredentials(params))
.toThrow(codes.BAD_SCOPES_FORMAT)
.toThrow('BAD_SCOPES_FORMAT')
})

test('throws BAD_SCOPES_FORMAT when scopes is a number', () => {
Expand All @@ -551,7 +552,7 @@ describe('getAndValidateCredentials', () => {
}

expect(() => getAndValidateCredentials(params))
.toThrow(codes.BAD_SCOPES_FORMAT)
.toThrow('BAD_SCOPES_FORMAT')
})

test('accepts scopes as an array', () => {
Expand Down
18 changes: 9 additions & 9 deletions test/index.test.js → test/index.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('generateAccessToken', () => {
test('throws same errors as getAccessTokenByClientCredentials', async () => {
await expect(generateAccessToken({}))
.rejects
.toThrow(codes.MISSING_PARAMETERS)
.toThrow('MISSING_PARAMETERS')
})
})

Expand Down Expand Up @@ -215,13 +215,13 @@ describe('generateAccessToken - with caching', () => {
// First call - should fail
await expect(generateAccessToken(validParams))
.rejects
.toThrow(codes.IMS_TOKEN_ERROR)
.toThrow('IMS_TOKEN_ERROR')
expect(fetch).toHaveBeenCalledTimes(1)

// Second call - should try again (not cached)
await expect(generateAccessToken(validParams))
.rejects
.toThrow(codes.IMS_TOKEN_ERROR)
.toThrow('IMS_TOKEN_ERROR')
expect(fetch).toHaveBeenCalledTimes(2)
})
})
Expand All @@ -247,7 +247,7 @@ describe('generateAccessToken - BAD_SCOPES_FORMAT error', () => {

await expect(generateAccessToken(params))
.rejects
.toThrow(codes.BAD_SCOPES_FORMAT)
.toThrow('BAD_SCOPES_FORMAT')
})
})

Expand Down Expand Up @@ -533,25 +533,25 @@ describe('generateAccessToken - BAD_CREDENTIALS_FORMAT error', () => {
test('throws BAD_CREDENTIALS_FORMAT when params is null', async () => {
await expect(generateAccessToken(null))
.rejects
.toThrow(codes.BAD_CREDENTIALS_FORMAT)
.toThrow('BAD_CREDENTIALS_FORMAT')
})

test('throws BAD_CREDENTIALS_FORMAT when params is undefined', async () => {
await expect(generateAccessToken(undefined))
.rejects
.toThrow(codes.BAD_CREDENTIALS_FORMAT)
.toThrow('BAD_CREDENTIALS_FORMAT')
})

test('throws BAD_CREDENTIALS_FORMAT when params is an array', async () => {
await expect(generateAccessToken(['test']))
.rejects
.toThrow(codes.BAD_CREDENTIALS_FORMAT)
.toThrow('BAD_CREDENTIALS_FORMAT')
})

test('throws BAD_CREDENTIALS_FORMAT when params is a string', async () => {
await expect(generateAccessToken('test'))
.rejects
.toThrow(codes.BAD_CREDENTIALS_FORMAT)
.toThrow('BAD_CREDENTIALS_FORMAT')
})

test('BAD_CREDENTIALS_FORMAT error includes sdk details', async () => {
Expand All @@ -565,6 +565,6 @@ describe('generateAccessToken - BAD_CREDENTIALS_FORMAT error', () => {
expect(error.name).toBe('AuthSDKError')
expect(error.code).toBe('BAD_CREDENTIALS_FORMAT')
expect(error.sdkDetails).toBeDefined()
expect(error.sdkDetails.paramsType).toBe('object') // typeof null === 'object'
expect(error.sdkDetails.paramsType).toBe('object')
})
})
File renamed without changes.
6 changes: 5 additions & 1 deletion types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ export interface TokenResponse {
/**
* Generates an access token for authentication (with caching)
* @param params - Parameters for token generation
* @param params.clientId - The client ID
* @param params.clientSecret - The client secret
* @param params.orgId - The organization ID
* @param [params.scopes = []] - Array of scopes to request
* @param [imsEnv] - The IMS environment ('prod' or 'stage'); when omitted or falsy, uses stage if __OW_NAMESPACE starts with 'development-', else prod
* @returns Promise that resolves with the token response
* @throws {Error} If there's an error getting the access token
*/
export function generateAccessToken(params: TokenParams): Promise<TokenResponse>

Expand Down
Loading