@sap-cloud-sdk/connectivity
Version:
SAP Cloud SDK for JavaScript connectivity
185 lines • 8.65 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.identityServicesCache = void 0;
exports.shouldExchangeToken = shouldExchangeToken;
exports.fetchIasToken = fetchIasToken;
exports.getIasAppTid = getIasAppTid;
const internal_1 = require("@sap-cloud-sdk/resilience/internal");
const resilience_1 = require("@sap-cloud-sdk/resilience");
const xssec_1 = require("@sap/xssec");
const util_1 = require("@sap-cloud-sdk/util");
const jwt_1 = require("./jwt");
const environment_accessor_1 = require("./environment-accessor");
var environment_accessor_2 = require("./environment-accessor");
Object.defineProperty(exports, "identityServicesCache", { enumerable: true, get: function () { return environment_accessor_2.identityServicesCache; } });
/**
* @internal
* Checks whether the IAS token to XSUAA token exchange should be applied.
* @param options - Configuration for how to retrieve destinations from the destination service.
* @returns A boolean value, that indicates whether the token exchange should be applied.
*/
function shouldExchangeToken(options) {
// iasToXsuaaTokenExchange is optional, token exchange is disabled by default
return (options.iasToXsuaaTokenExchange === true &&
!!options.jwt &&
!(0, jwt_1.isXsuaaToken)((0, jwt_1.decodeJwt)(options.jwt)));
}
/**
* Make a client credentials request against the IAS OAuth2 endpoint.
* Supports both certificate-based (mTLS) and client secret authentication.
* @param service - Service as it is defined in the environment variable.
* @param options - Options for token fetching, including authenticationType to specify authentication mode, optional resource parameter for app2app, appTid for multi-tenant scenarios, and extraParams for additional OAuth2 parameters.
* @returns Client credentials token response.
* @internal
*/
async function fetchIasToken(service, options = {}) {
const resolvedService = (0, environment_accessor_1.resolveServiceBinding)(service);
const fnArgument = {
serviceCredentials: resolvedService.credentials,
useCache: options.useCache,
...options
};
const token = await (0, internal_1.executeWithMiddleware)((0, resilience_1.resilience)(), {
fn: getIasTokenImpl,
fnArgument,
context: {
uri: fnArgument.serviceCredentials.url,
tenantId: fnArgument.serviceCredentials.app_tid
}
}).catch(err => {
const serviceName = typeof service === 'string' ? service : service.name || 'unknown';
let message = `Could not fetch IAS client for service "${serviceName}" of type ${resolvedService.label}`;
// Add contextual hints based on error status code (similar to Java SDK)
if (err.response?.status === 401) {
message +=
'. In case you are accessing a multi-tenant BTP service on behalf of a subscriber tenant, ensure that the service instance is declared as dependency to SaaS Provisioning Service or Subscription Manager (SMS) and subscribed for the current tenant';
}
throw new util_1.ErrorWithCause(message + (err.message ? `: ${err.message}` : '.'), err);
});
return token;
}
/**
* Converts an IAS resource to the URN format expected by @sap/xssec.
* @param resource - The IAS resource to convert.
* @returns The resource in URN format.
* @internal
*/
function convertResourceToUrn(resource) {
if (!resource) {
throw new Error('Resource parameter is required');
}
if ('name' in resource) {
return `urn:sap:identity:application:provider:name:${resource.name}`;
}
const segments = [
`urn:sap:identity:application:provider:clientid:${resource.providerClientId}`
];
if (resource.providerTenantId) {
segments.push(`apptid:${resource.providerTenantId}`);
}
return segments.join(':');
}
/**
* Transforms IAS options to the format expected by @sap/xssec.
* @param arg - The IAS parameters including options.
* @returns The transformed token fetch options.
* @internal
*/
function transformIasOptionsToXssecArgs(arg) {
const tokenOptions = {
token_format: 'jwt',
...(arg.resource && { resource: convertResourceToUrn(arg.resource) }),
...(arg.appTid && { app_tid: arg.appTid }),
...(arg.extraParams || {})
};
if (arg.authenticationType === 'OAuth2JWTBearer') {
// JWT bearer grant for business user propagation
if (!arg.assertion) {
throw new Error('JWT assertion required for authenticationType: "OAuth2JWTBearer". Provide iasOptions.assertion.');
}
// Disable refresh token for App-To-App JWT bearer token exchange (recommended for better performance)
if (arg.resource && tokenOptions.refresh_expiry === undefined) {
tokenOptions.refresh_expiry = 0;
}
// Extract appTid from assertion if not provided
const token = new xssec_1.IdentityServiceToken(arg.assertion);
if (!tokenOptions.app_tid) {
// Set to `null` if not set to prevent xssec from also trying to extract it internally
tokenOptions.app_tid = token?.appTid || null;
}
// Workaround for IAS bug
// JAVA SDK: https://github.com/SAP/cloud-sdk-java/blob/61903347b607a8397f7930709cd52526f05269b1/cloudplatform/connectivity-oauth/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/OAuth2Service.java#L225-L236
// Issue: https://jira.tools.sap/browse/SECREQ-5220
if (tokenOptions.app_tid) {
tokenOptions.refresh_expiry = 0;
}
}
return tokenOptions;
}
/**
* Implementation of the IAS client credentials token retrieval using @sap/xssec.
* @param arg - The parameters for IAS token retrieval.
* @returns A promise resolving to the client credentials response.
* @internal
*/
async function getIasTokenImpl(arg) {
const jwtForSubdomain =
// For OAuth2JWTBearer authentication, subdomain will be extracted from the assertion
arg.authenticationType === 'OAuth2JWTBearer'
? arg.assertion
: // For technical user flows, use JWT for subdomain extraction when requesting
// current-tenant context
arg.requestAs !== 'provider-tenant'
? arg.jwt
: undefined;
const identityService = (0, environment_accessor_1.getIdentityServiceInstanceFromCredentials)(arg.serviceCredentials, jwtForSubdomain);
const tokenOptions = transformIasOptionsToXssecArgs(arg);
const useCache = arg.useCache !== false;
const response = arg.authenticationType === 'OAuth2JWTBearer'
? // JWT bearer grant for business user access
useCache
? await identityService.getJwtBearerToken(arg.assertion, tokenOptions)
: await identityService.fetchJwtBearerToken(arg.assertion, tokenOptions)
: // Technical user client credentials grant
useCache
? await identityService.getClientCredentialsToken(tokenOptions)
: await identityService.fetchClientCredentialsToken(tokenOptions);
const decodedJwt = new xssec_1.IdentityServiceToken(response.access_token);
return {
access_token: response.access_token,
token_type: response.token_type,
expires_in: response.expires_in,
// IAS tokens don't have scope property
scope: '',
jti: decodedJwt.payload?.jti ?? '',
// `decodedJwt.audiences` always returns an array, preserve original type
aud: decodedJwt.payload?.aud ?? [],
app_tid: decodedJwt.appTid,
scim_id: decodedJwt.scimId,
// Added if resource parameter was specified
ias_apis: decodedJwt?.consumedApis,
custom_iss: decodedJwt.customIssuer ?? undefined,
// fetchJwtBearerToken may return a refresh token
refresh_token: response?.refresh_token
};
}
/**
* Resolves `app_tid` based on supplied IAS options and tenant context.
* @param iasOptions - IAS technical user options.
* @param service - Service binding for identity service.
* @param jwt - Optional JWT payload for current-tenant context.
* @returns The BTP app_tid based on `requestAs` configuration.
* @internal
*/
function getIasAppTid(iasOptions, service, jwt) {
const { requestAs } = iasOptions;
if (requestAs === 'provider-tenant') {
return service.credentials.app_tid;
}
if (requestAs === 'current-tenant' || !requestAs) {
return jwt?.app_tid;
}
requestAs;
throw new Error(`Invalid requestAs value: ${requestAs}`);
}
//# sourceMappingURL=identity-service.js.map