Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 2 additions & 1 deletion src/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ module.exports = {
STAGE_LAUNCH_PREFIX: 'https://experience-stage.adobe.com/?devMode=true#/custom-apps/?localDevUrl=',
PRIVATE_KEY_PATH: `${DEV_KEYS_DIR}/private.key`,
PUB_CERT_PATH: `${DEV_KEYS_DIR}/cert-pub.crt`,
BUNDLE_OPTIONS
BUNDLE_OPTIONS,
IMS_OAUTH_S2S_ENV_KEY: 'IMS_OAUTH_S2S'
}
17 changes: 16 additions & 1 deletion src/lib/run-dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ const coreLogger = require('@adobe/aio-lib-core-logging')
const { getReasonPhrase } = require('http-status-codes')

const utils = require('./app-helper')
const { SERVER_HOST, SERVER_DEFAULT_PORT, BUNDLER_DEFAULT_PORT, DEV_API_PREFIX, DEV_API_WEB_PREFIX, BUNDLE_OPTIONS, CHANGED_ASSETS_PRINT_LIMIT } = require('./constants')
const { SERVER_HOST, SERVER_DEFAULT_PORT, BUNDLER_DEFAULT_PORT, DEV_API_PREFIX, DEV_API_WEB_PREFIX, BUNDLE_OPTIONS, CHANGED_ASSETS_PRINT_LIMIT, IMS_OAUTH_S2S_ENV_KEY } = require('./constants')
const RAW_CONTENT_TYPES = ['application/octet-stream', 'multipart/form-data']

// for the include-ims-credentials annotation
let imsAuthObject = null

/* global Request, Response */

/**
Expand Down Expand Up @@ -86,6 +89,11 @@ async function runDev (runOptions, config, _inprocHookRunner) {
// ex. console.log('AIO_DEV ', process.env.AIO_DEV ? 'dev' : 'prod')
process.env.AIO_DEV = 'true'

// for the include-ims-credentials annotation
try {
imsAuthObject = JSON.parse(process.env[IMS_OAUTH_S2S_ENV_KEY])
} catch (e) {}

const serverPortToUse = parseInt(process.env.PORT) || SERVER_DEFAULT_PORT
const serverPort = await getPort({ port: serverPortToUse })

Expand Down Expand Up @@ -346,6 +354,13 @@ async function invokeAction ({ actionRequestContext, logger }) {
}
}

// process the include-ims-credentials annotation
const newInputs = rtLib.utils.getIncludeIMSCredentialsAnnotationInputs(action, imsAuthObject)
if (newInputs) {
Object.entries(newInputs).forEach(([k, v]) => { params[k] = v })
logger.debug(`Added IMS credentials to action params for action '${actionName}'.`)
}

// if we run an action, we will restore the process.env after the call
// we must do this before we load the action because code can execute on require/import
const preCallEnv = { ...process.env }
Expand Down
3 changes: 2 additions & 1 deletion test/__mocks__/@adobe/aio-lib-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ const mockRtLibInstance = {

const mockRtUtils = {
getActionUrls: jest.fn(),
checkOpenWhiskCredentials: jest.fn()
checkOpenWhiskCredentials: jest.fn(),
getIncludeIMSCredentialsAnnotationInputs: jest.fn()
}

const init = jest.fn().mockReturnValue(mockRtLibInstance)
Expand Down
4 changes: 3 additions & 1 deletion test/lib/constants.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const {
STAGE_LAUNCH_PREFIX,
PRIVATE_KEY_PATH,
PUB_CERT_PATH,
BUNDLE_OPTIONS
BUNDLE_OPTIONS,
IMS_OAUTH_S2S_ENV_KEY
} = require(CONSTANTS_PATH)

test('exports', () => {
Expand All @@ -39,6 +40,7 @@ test('exports', () => {
expect(PRIVATE_KEY_PATH).toBeDefined()
expect(PUB_CERT_PATH).toBeDefined()
expect(BUNDLE_OPTIONS).toBeDefined()
expect(IMS_OAUTH_S2S_ENV_KEY).toBeDefined()
})

describe('override via env vars', () => {
Expand Down
148 changes: 148 additions & 0 deletions test/lib/run-dev.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,12 @@ describe('invokeSequence', () => {
})

describe('runDev', () => {
const originalEnv = process.env

afterEach(() => {
process.env = { ...originalEnv }
})

test('no front end, no back end', async () => {
const actionPath = fixturePath('actions/successNoReturnAction.js')
const config = createConfig({
Expand All @@ -1253,6 +1259,53 @@ describe('runDev', () => {
expect(Object.keys(actionUrls).length).toEqual(0)
})

test('parses IMS_OAUTH_S2S environment variable', async () => {
const imsAuthData = { access_token: 'test-token', org_id: 'test-org' }
process.env.IMS_OAUTH_S2S = JSON.stringify(imsAuthData)

const actionPath = fixturePath('actions/successNoReturnAction.js')
const config = createConfig({
hasFrontend: false,
hasBackend: true,
packageName: 'mypackage',
actions: {
myaction: {
function: actionPath
}
}
})
const runOptions = createRunOptions({ cert: 'my-cert', key: 'my-key' })
const hookRunner = () => {}
const { actionUrls, serverCleanup } = await runDev(runOptions, config, hookRunner)

await serverCleanup()
// Verify runDev completes successfully when IMS_OAUTH_S2S env var is set with valid JSON
expect(Object.keys(actionUrls).length).toBeGreaterThan(0)
})

test('handles invalid IMS_OAUTH_S2S JSON gracefully', async () => {
process.env.IMS_OAUTH_S2S = 'not-valid-json'

const actionPath = fixturePath('actions/successNoReturnAction.js')
const config = createConfig({
hasFrontend: false,
hasBackend: true,
packageName: 'mypackage',
actions: {
myaction: {
function: actionPath
}
}
})
const runOptions = createRunOptions({ cert: 'my-cert', key: 'my-key' })
const hookRunner = () => {}
const { actionUrls, serverCleanup } = await runDev(runOptions, config, hookRunner)

await serverCleanup()
// Verify runDev completes successfully even when IMS_OAUTH_S2S contains invalid JSON
expect(Object.keys(actionUrls).length).toBeGreaterThan(0)
})

test('no front end, has back end', async () => {
const actionPath = fixturePath('actions/successNoReturnAction.js')
const config = createConfig({
Expand Down Expand Up @@ -1706,6 +1759,101 @@ describe('invokeAction', () => {
statusCode: 400
})
})

describe('include-ims-credentials annotation', () => {
const rtLib = jest.requireActual('@adobe/aio-lib-runtime')
let getIncludeIMSCredentialsAnnotationInputsSpy

beforeEach(() => {
getIncludeIMSCredentialsAnnotationInputsSpy = jest.spyOn(rtLib.utils, 'getIncludeIMSCredentialsAnnotationInputs')
})

afterEach(() => {
getIncludeIMSCredentialsAnnotationInputsSpy.mockRestore()
})

test('adds IMS credentials to params when getIncludeIMSCredentialsAnnotationInputs returns inputs', async () => {
const packageName = 'foo'
const actionPath = fixturePath('actions/successReturnAction.js')
const actionLoader = createActionLoader(actionPath)

const action = {
function: actionPath,
annotations: {
'include-ims-credentials': true
}
}
const actionParams = { existingParam: 'value' }
const actionName = 'a'
const actionConfig = {
[packageName]: {
actions: {
[actionName]: action
}
}
}

// Mock the function to return IMS credentials
const mockImsInputs = {
__ims_oauth_s2s: { client_id: 'mock-access-token', org_id: 'mock-org-id' },
__ims_env: 'stage'
}
getIncludeIMSCredentialsAnnotationInputsSpy.mockReturnValue(mockImsInputs)

const actionRequestContext = {
contextActionLoader: actionLoader,
contextItem: action,
contextItemParams: actionParams,
contextItemName: actionName,
packageName,
actionConfig
}
const response = await invokeAction({ actionRequestContext, logger: mockLogger })

expect(getIncludeIMSCredentialsAnnotationInputsSpy).toHaveBeenCalledWith(action, expect.anything())
expect(actionParams.__ims_oauth_s2s).toEqual({ client_id: 'mock-access-token', org_id: 'mock-org-id' })
expect(actionParams.__ims_env).toEqual('stage')
expect(actionParams.existingParam).toEqual('value')
expect(mockLogger.debug).toHaveBeenCalledWith(`Added IMS credentials to action params for action '${actionName}'.`)
expect(response.statusCode).toEqual(200)
})

test('does not add IMS credentials when getIncludeIMSCredentialsAnnotationInputs returns null', async () => {
const packageName = 'foo'
const actionPath = fixturePath('actions/successReturnAction.js')
const actionLoader = createActionLoader(actionPath)

const action = { function: actionPath }
const actionParams = { existingParam: 'value' }
const actionName = 'a'
const actionConfig = {
[packageName]: {
actions: {
[actionName]: action
}
}
}

// Mock the function to return null (no annotation or no IMS auth object)
getIncludeIMSCredentialsAnnotationInputsSpy.mockReturnValue(null)

const actionRequestContext = {
contextActionLoader: actionLoader,
contextItem: action,
contextItemParams: actionParams,
contextItemName: actionName,
packageName,
actionConfig
}
const response = await invokeAction({ actionRequestContext, logger: mockLogger })

expect(getIncludeIMSCredentialsAnnotationInputsSpy).toHaveBeenCalledWith(action, expect.anything())
expect(actionParams.__ow_ims_access_token).toBeUndefined()
expect(actionParams.__ow_ims_org_id).toBeUndefined()
expect(actionParams.existingParam).toEqual('value')
expect(response.statusCode).toEqual(200)
})
})
})

describe('defaultActionLoader', () => {
Expand Down
Loading