Skip to content
Closed
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
21 changes: 14 additions & 7 deletions src/controllers/llmo/llmo.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { Entitlement as EntitlementModel } from '@adobe/spacecat-shared-data-acc
import TokowakaClient, { calculateForwardedHost } from '@adobe/spacecat-shared-tokowaka-client';
import AccessControlUtil from '../../support/access-control-util.js';
import { UnauthorizedProductError } from '../../support/errors.js';
import { exchangePromiseToken } from '../../support/utils.js';
import { exchangePromiseToken, getServicePrincipalToken } from '../../support/utils.js';
import { triggerBrandProfileAgent } from '../../support/brand-profile-trigger.js';
import {
applyFilters,
Expand Down Expand Up @@ -1614,12 +1614,19 @@ function LlmoController(ctx) {

let imsUserToken;
try {
log.debug(`Getting IMS user token for site ${siteId}`);
imsUserToken = await exchangePromiseToken(context, promiseToken);
log.info('IMS user token obtained successfully');
} catch (tokenError) {
log.warn(`Fetching IMS user token for site ${siteId} failed: ${tokenError.status} ${tokenError.message}`);
return createResponse({ message: 'Authentication failed with upstream IMS service' }, 401);
log.debug(`Getting IMS service principal token for site ${siteId}`);
imsUserToken = await getServicePrincipalToken(context);
log.info('IMS service principal token obtained successfully');
Comment on lines 1615 to +1619
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imsUserToken now holds either a service-principal token or a user-exchanged token. Renaming this variable to something neutral (e.g., imsAccessToken) would avoid confusion in logs/debugging and reduce the chance of future misuse.

Copilot uses AI. Check for mistakes.
} catch (spTokenError) {
log.warn(`Service principal token unavailable for site ${siteId}: ${spTokenError.message}; falling back to promise token`);
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateEdgeOptimizeCDNRouting still rejects requests when x-promise-token is missing (earlier in the function), so the new service-principal path can never be used without that header. If the intent is “SP token first; only require promise token for fallback”, move/conditionalize the x-promise-token validation so SP-token flows can succeed without it.

Suggested change
log.warn(`Service principal token unavailable for site ${siteId}: ${spTokenError.message}; falling back to promise token`);
log.warn(`Service principal token unavailable for site ${siteId}: ${spTokenError.message}; falling back to promise token`);
if (!hasText(promiseToken)) {
log.warn(`No promise token provided for site ${siteId}; unable to use fallback authentication`);
return createResponse({ message: 'Authentication failed with upstream IMS service' }, 401);
}

Copilot uses AI. Check for mistakes.
try {
log.debug(`Getting IMS user token for site ${siteId}`);
imsUserToken = await exchangePromiseToken(context, promiseToken);
log.info('IMS user token obtained successfully (fallback)');
} catch (tokenError) {
Comment on lines +1620 to +1626
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback currently triggers on any service-principal token error, even when IMS credentials are configured. If fallback is only intended when env vars are missing (per PR description), consider checking required env vars before calling IMS or narrowing the catch to “not configured” errors so misconfigurations/outages don’t get silently masked.

Copilot uses AI. Check for mistakes.
log.warn(`Fetching IMS user token for site ${siteId} failed: ${tokenError.status} ${tokenError.message}`);
return createResponse({ message: 'Authentication failed with upstream IMS service' }, 401);
}
Comment on lines +1617 to +1629
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change introduces a new token acquisition path (getServicePrincipalToken) and changes the controller’s behavior, but no tests are updated/added in this PR. Please update test/controllers/llmo/llmo.test.js to stub/assert the SP-token path (including the “no x-promise-token needed” case) and the fallback-to-promise-token case.

Copilot generated this review using guidance from repository custom instructions.
}

try {
Expand Down
18 changes: 17 additions & 1 deletion src/support/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import { Site as SiteModel } from '@adobe/spacecat-shared-data-access';
import { Entitlement as EntitlementModel } from '@adobe/spacecat-shared-data-access/src/models/entitlement/index.js';
import { Config } from '@adobe/spacecat-shared-data-access/src/models/site/config.js';
import { ImsPromiseClient } from '@adobe/spacecat-shared-ims-client';
import { ImsPromiseClient, ImsClient } from '@adobe/spacecat-shared-ims-client';
import URI from 'urijs';
import {
hasText,
Expand Down Expand Up @@ -709,6 +709,22 @@ export async function exchangePromiseToken(context, promiseToken) {
return accessToken;
}

/**
* Obtains an IMS Service Principal (client_credentials) access token for the
* LLMO backend service. Uses the IMS v3 token endpoint.
*
* Required env vars: IMS_HOST, IMS_CLIENT_ID, IMS_CLIENT_CODE, IMS_CLIENT_SECRET
*
* @param {Object} context - The request context (provides env + log).
* @returns {Promise<string>} The IMS access token string.
* @throws {Error} If env vars are missing or the IMS call fails.
*/
export async function getServicePrincipalToken(context) {
const imsClient = ImsClient.createFrom(context);
const { access_token: accessToken } = await imsClient.getServiceAccessTokenV3();
return accessToken;
Comment on lines +722 to +725
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getServicePrincipalToken relies on ImsClient.createFrom(context) throwing when required env vars are missing. To make fallback behavior deterministic (and reduce noisy warn logs), consider explicitly validating IMS_HOST, IMS_CLIENT_ID, IMS_CLIENT_CODE, and IMS_CLIENT_SECRET and throwing a clear “not configured” error when any are absent.

Copilot uses AI. Check for mistakes.
}

/**
* Parses and retrieves a specific cookie value by name from the request context.
* Uses indexOf-based splitting to correctly handle values containing '='
Expand Down
Loading