Skip to content

feat(llmo-3954): add IMS service principal token for CDN routing API calls#2175

Draft
anagarwa wants to merge 1 commit intomainfrom
feat/llmo-3954-ims-service-principal-token
Draft

feat(llmo-3954): add IMS service principal token for CDN routing API calls#2175
anagarwa wants to merge 1 commit intomainfrom
feat/llmo-3954-ims-service-principal-token

Conversation

@anagarwa
Copy link
Copy Markdown
Contributor

Summary

  • Adds getServicePrincipalToken(context) to support/utils.js using ImsClient.getServiceAccessTokenV3() (client_credentials grant)
  • updateEdgeOptimizeCDNRouting now tries the SP token first; falls back to the user promise token if IMS_CLIENT_ID/IMS_CLIENT_SECRET env vars are not configured
  • This enables the LLMO backend to call the Internal CDN API on behalf of customers using its own service identity, without needing to pass a user token through the UI

Required env vars

IMS_HOST, IMS_CLIENT_ID, IMS_CLIENT_CODE, IMS_CLIENT_SECRET (already available via Vault)

Test plan

  • All 346 llmo controller tests pass
  • All 98 utils tests pass
  • When SP env vars are configured: CDN routing call uses SP token (no x-promise-token header needed)
  • When SP env vars are absent: falls back to promise token (existing behavior)

Fixes LLMO-3954

🤖 Generated with Claude Code

…calls

Adds getServicePrincipalToken() to support/utils.js using ImsClient
client_credentials grant (IMS v3 token endpoint). The CDN routing
function updateEdgeOptimizeCDNRouting now tries the SP token first
and falls back to the user promise token if SP credentials are not
configured in env.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 10, 2026 07:56
@github-actions
Copy link
Copy Markdown

This PR will trigger a minor release when merged.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an IMS service-principal (client_credentials) token acquisition helper and updates the LLMO Edge Optimize CDN routing flow to prefer a backend service token over a user-derived token (with fallback).

Changes:

  • Add getServicePrincipalToken(context) in src/support/utils.js using ImsClient.getServiceAccessTokenV3().
  • Update updateEdgeOptimizeCDNRouting to try a service-principal token first, then fall back to exchanging the provided promise token.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
src/support/utils.js Introduces a new helper to fetch an IMS service-principal access token via IMS v3.
src/controllers/llmo/llmo.js Updates CDN routing auth to prefer the service-principal token and fall back to the existing promise-token exchange.

imsUserToken = await getServicePrincipalToken(context);
log.info('IMS service principal token obtained successfully');
} 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.
Comment on lines +1617 to +1629
log.debug(`Getting IMS service principal token for site ${siteId}`);
imsUserToken = await getServicePrincipalToken(context);
log.info('IMS service principal token obtained successfully');
} catch (spTokenError) {
log.warn(`Service principal token unavailable for site ${siteId}: ${spTokenError.message}; falling back to promise token`);
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) {
log.warn(`Fetching IMS user token for site ${siteId} failed: ${tokenError.status} ${tokenError.message}`);
return createResponse({ message: 'Authentication failed with upstream IMS service' }, 401);
}
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.
Comment on lines 1615 to +1619
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');
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.
Comment on lines +1620 to +1626
} catch (spTokenError) {
log.warn(`Service principal token unavailable for site ${siteId}: ${spTokenError.message}; falling back to promise token`);
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) {
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.
Comment thread src/support/utils.js
Comment on lines +722 to +725
export async function getServicePrincipalToken(context) {
const imsClient = ImsClient.createFrom(context);
const { access_token: accessToken } = await imsClient.getServiceAccessTokenV3();
return accessToken;
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.
@anagarwa anagarwa marked this pull request as draft April 10, 2026 09:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants