Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
7 changes: 4 additions & 3 deletions src/commands/app/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const {
const rtLib = require('@adobe/aio-lib-runtime')
const LogForwarding = require('../../lib/log-forwarding')
const { sendAppAssetsDeployedAuditLog, sendAppDeployAuditLog } = require('../../lib/audit-logger')
const { setRuntimeApiHostAndAuthHandler, getAccessToken } = require('../../lib/auth-helper')
const { setRuntimeApiHostAndAuthHandler, getAccessToken, getTokenData } = require('../../lib/auth-helper')
const logActions = require('../../lib/log-actions')

const PRE_DEPLOY_EVENT_REG = 'pre-deploy-event-reg'
Expand Down Expand Up @@ -68,6 +68,8 @@ class Deploy extends BuildCommand {

if (cliDetails?.accessToken) {
try {
// store user id from token data for cdn deploy audit metadata
appInfo.auditUserId = getTokenData(cliDetails.accessToken)?.user_id
// send audit log at start (don't wait for deployment to finish)
await sendAppDeployAuditLog({
accessToken: cliDetails?.accessToken,
Expand Down Expand Up @@ -130,8 +132,7 @@ class Deploy extends BuildCommand {
// - break into smaller pieces deploy, allowing to first deploy all actions then all web assets
for (let i = 0; i < keys.length; ++i) {
const k = keys[i]
const v = setRuntimeApiHostAndAuthHandler(values[i])

const v = { auditUserId: appInfo.auditUserId, ...setRuntimeApiHostAndAuthHandler(values[i]) }
await this.deploySingleConfig({ name: k, config: v, originalConfig: values[i], flags, spinner })
if (cliDetails?.accessToken && v.app.hasFrontend && flags['web-assets']) {
const opItems = getFilesCountWithExtension(v.web.distProd)
Expand Down
30 changes: 30 additions & 0 deletions src/lib/auth-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,38 @@ const setRuntimeApiHostAndAuthHandler = (_config) => {
}
}

/**
* Decodes a JWT token and returns its payload as a JavaScript object.
*
* @function getTokenData
* @param {string} token - The JWT token to decode
* @returns {object|null} The decoded payload of the JWT token or null if the token is invalid or cannot be decoded
*/
const getTokenData = (token) => {
if (typeof token !== 'string') {
aioLogger.error('Invalid token provided to getTokenData :: not a string')
return null
}
const [, payload] = token.split('.', 3)
if (!payload) {
aioLogger.error('Invalid token provided to getTokenData :: not a jwt')
return null
}
try {
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/')
// add padding if necessary
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4)
const decoded = Buffer.from(padded, 'base64').toString('utf-8')
return JSON.parse(decoded)
} catch (e) {
aioLogger.error('Error decoding token payload in getTokenData ::', e)
return null
}
}

module.exports = {
getAccessToken,
getTokenData,
bearerAuthHandler,
setRuntimeApiHostAndAuthHandler
}
26 changes: 25 additions & 1 deletion test/commands/lib/auth-helper.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { getAccessToken, bearerAuthHandler, setRuntimeApiHostAndAuthHandler } = require('../../../src/lib/auth-helper')
const { getAccessToken, bearerAuthHandler, setRuntimeApiHostAndAuthHandler, getTokenData } = require('../../../src/lib/auth-helper')
const { getToken, context } = require('@adobe/aio-lib-ims')
const { CLI } = require('@adobe/aio-lib-ims/src/context')
const { getCliEnv } = require('@adobe/aio-lib-env')
Expand Down Expand Up @@ -57,6 +57,30 @@ describe('getAccessToken', () => {
})
})

describe('getTokenData', () => {
test('should decode JWT token and return payload', () => {
// Example JWT token with payload: {"user_id":"12345","name":"Test User"}
const exampleToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzNDUiLCJuYW1lIjoiVGVzdCBVc2VyIn0.sflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
const result = getTokenData(exampleToken)
expect(result).toEqual({ user_id: '12345', name: 'Test User' })
})
test('should return null for invalid token', () => {
const invalidToken = 'invalid.token.string'
const result = getTokenData(invalidToken)
expect(result).toBeNull()
})
test('should return null for malformed token', () => {
const malformedToken = 'malformedtoken'
const result = getTokenData(malformedToken)
expect(result).toBeNull()
})
test('should return null for non-string token', () => {
const nonStringToken = 12345
const result = getTokenData(nonStringToken)
expect(result).toBeNull()
})
})

describe('bearerAuthHandler', () => {
beforeEach(() => {
jest.clearAllMocks()
Expand Down