UNPKG

@sap-cloud-sdk/connectivity

Version:

SAP Cloud SDK for JavaScript connectivity

185 lines • 8.65 kB
"use strict"; 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