UNPKG

@sap-cloud-sdk/connectivity

Version:

SAP Cloud SDK for JavaScript connectivity

264 lines • 13.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.fetchDestinations = fetchDestinations; exports.fetchDestinationWithoutTokenRetrieval = fetchDestinationWithoutTokenRetrieval; exports.fetchCertificate = fetchCertificate; exports.fetchDestinationWithTokenRetrieval = fetchDestinationWithTokenRetrieval; const util_1 = require("@sap-cloud-sdk/util"); const axios_1 = __importStar(require("axios")); const internal_1 = require("@sap-cloud-sdk/resilience/internal"); const resilience_1 = require("@sap-cloud-sdk/resilience"); const async_retry_1 = __importDefault(require("async-retry")); const jwt_1 = require("../jwt"); const http_agent_1 = require("../../http-agent"); const authorization_header_1 = require("../authorization-header"); const destination_1 = require("./destination"); const destination_service_cache_1 = require("./destination-service-cache"); const logger = (0, util_1.createLogger)({ package: 'connectivity', messageContext: 'destination-service' }); /** * @internal * Fetch either subaccount or instance destinations (no token retrieval). * @param destinationServiceUri - The URI of the destination service * @param serviceToken - The service token for the destination service. * @param type - Either 'instance' or 'subaccount', depending on what destinations should be fetched. * @param options - Options to use for retrieving destinations. * @returns A promise resolving to a list of destinations of the requested type. */ async function fetchDestinations(destinationServiceUri, serviceToken, type, options) { const targetUri = `${(0, util_1.removeTrailingSlashes)(destinationServiceUri)}/destination-configuration/v1/${type}Destinations`; if (options?.useCache) { const destinationsFromCache = destination_service_cache_1.destinationServiceCache.retrieveDestinationsFromCache(targetUri, (0, jwt_1.decodeJwt)(serviceToken)); if (destinationsFromCache) { logger.debug(`Destinations retrieved from cache. There were ${destinationsFromCache.length} destinations returned from the cache.`); return destinationsFromCache; } } const headers = (0, jwt_1.wrapJwtInHeader)(serviceToken).headers; return callDestinationEndpoint({ uri: targetUri, tenantId: getTenantIdFromTokens(serviceToken) }, headers) .then(response => { const destinations = response.data.map((destination) => (0, destination_1.parseDestination)(destination)); if (options?.useCache) { destination_service_cache_1.destinationServiceCache.cacheRetrievedDestinations(targetUri, (0, jwt_1.decodeJwt)(serviceToken), destinations); } return destinations; }) .catch(error => { throw new util_1.ErrorWithCause(`Failed to fetch ${type} destinations.${errorMessageFromResponse(error)}`, error); }); } /** * @internal * Fetch a destination from the destination find API (`/destinations`) and skip the automatic token retrieval. * @param destinationName - Name of the destination. * @param destinationServiceUri - The URI of the destination service. * @param serviceToken - The service token for the destination service. * @returns A promise resolving to the requested destination. */ async function fetchDestinationWithoutTokenRetrieval(destinationName, destinationServiceUri, serviceToken) { const targetUri = `${(0, util_1.removeTrailingSlashes)(destinationServiceUri)}/destination-configuration/v1/destinations/${destinationName}?$skipTokenRetrieval=true`; try { const response = await callDestinationEndpoint({ uri: targetUri, tenantId: getTenantIdFromTokens(serviceToken) }, { Authorization: `Bearer ${serviceToken}` }); const destination = (0, destination_1.parseDestination)(response.data.destinationConfiguration); return { instance: response.data.owner?.InstanceId ? [destination] : [], subaccount: !response.data.owner?.InstanceId && response.data.owner?.SubaccountId ? [destination] : [] }; } catch (err) { if ((0, axios_1.isAxiosError)(err) && err.response?.status === 404 && err.response?.data?.ErrorMessage === 'Configuration with the specified name was not found') { return { instance: [], subaccount: [] }; } throw new util_1.ErrorWithCause(`Failed to fetch destination.${errorMessageFromResponse(err)}`, err); } } /** * Fetches a certificate from the subaccount and destination instance for a given a name. * Subaccount is tried first. * @param destinationServiceUri - The URI of the destination service * @param token - The access token for destination service. * @param certificateName - Name of the Certificate to be fetched * @returns A Promise resolving to the destination * @internal */ async function fetchCertificate(destinationServiceUri, token, certificateName) { const filetype = certificateName.split('.')[1]; if (filetype.toLowerCase() !== 'pem') { logger.warn(`The provided truststore ${certificateName} is not in 'pem' format which is currently the only supported format. Truststore is ignored.`); return; } const accountUri = `${(0, util_1.removeTrailingSlashes)(destinationServiceUri)}/destination-configuration/v1/subaccountCertificates/${certificateName}`; const instanceUri = `${(0, util_1.removeTrailingSlashes)(destinationServiceUri)}/destination-configuration/v1/instanceCertificates/${certificateName}`; const header = (0, jwt_1.wrapJwtInHeader)(token).headers; try { const response = await callCertificateEndpoint({ uri: accountUri, tenantId: getTenantIdFromTokens(token) }, header).catch(() => callCertificateEndpoint({ uri: instanceUri, tenantId: getTenantIdFromTokens(token) }, header)); return (0, destination_1.parseCertificate)(response.data); } catch (err) { logger.warn(`Failed to fetch truststore certificate ${certificateName} - Continuing without certificate. This may cause failing requests`, err); } } function getTenantIdFromTokens(token) { let tenant; if (typeof token === 'string') { tenant = (0, jwt_1.getTenantId)(token); } else { tenant = // represents the tenant as string already see https://api.sap.com/api/SAP_CP_CF_Connectivity_Destination/resource token.exchangeTenant || (0, jwt_1.getTenantId)(token.exchangeHeaderJwt) || (0, jwt_1.getTenantId)(token.authHeaderJwt); } if (!tenant) { throw new Error('Could not obtain tenant identifier from JWT.'); } return tenant; } /** * @internal * Fetches a specific destination including authorization tokens from the given URI. * For destinations with authenticationType `OAuth2SAMLBearerAssertion`, this call will trigger the `OAuth2SAMLBearer` flow against the target destination. * @param destinationServiceUri - The URI of the destination service * @param token - The access token or `AuthAndExchangeTokens` if you want to include other token headers for e.g. `OAuth2UserTokenExchange`. * @param options - Options to use for retrieving destinations. * @returns A promise resolving to the destination. */ async function fetchDestinationWithTokenRetrieval(destinationServiceUri, token, options) { const targetUri = `${(0, util_1.removeTrailingSlashes)(destinationServiceUri)}/destination-configuration/v1/destinations/${options.destinationName}`; token = typeof token === 'string' ? { authHeaderJwt: token } : token; let authHeader = (0, jwt_1.wrapJwtInHeader)(token.authHeaderJwt).headers; authHeader = token.exchangeHeaderJwt ? { ...authHeader, 'X-user-token': token.exchangeHeaderJwt } : authHeader; authHeader = token.exchangeTenant ? { ...authHeader, 'X-tenant': token.exchangeTenant } : authHeader; authHeader = token.refreshToken ? { ...authHeader, 'X-refresh-token': token.refreshToken } : authHeader; return callDestinationEndpoint({ uri: targetUri, tenantId: getTenantIdFromTokens(token) }, authHeader, options) .then(response => { const destination = (0, destination_1.parseDestination)(response.data); return destination; }) .catch(error => { { throw new util_1.ErrorWithCause(`Failed to fetch destination ${options.destinationName}.${errorMessageFromResponse(error)}`, error); } }); } function errorMessageFromResponse(error) { return (0, util_1.propertyExists)(error, 'response', 'data', 'ErrorMessage') ? ` ${error.response.data.ErrorMessage}` : ''; } function retryDestination(destinationName) { return options => arg => { let retryCount = 1; return (0, async_retry_1.default)(async (bail) => { try { const destination = await options.fn(arg); if (retryCount < 3) { retryCount++; // this will throw if the destination does not contain valid auth headers and a second try is done to get a destination with valid tokens. await (0, authorization_header_1.buildAuthorizationHeaders)((0, destination_1.parseDestination)(destination.data)); } return destination; } catch (error) { const status = (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined; if (status?.toString().startsWith('4')) { bail(new util_1.ErrorWithCause(`Request failed with status code ${status}`, error)); // We need to return something here but the actual value does not matter return undefined; } throw error; } }, { retries: 3, onRetry: (err) => logger.warn(`Failed to retrieve destination ${destinationName} - doing a retry. Original Error ${err.message}`) }); }; } async function callCertificateEndpoint(context, headers) { if (!context.uri.includes('Certificates')) { throw new Error(`callCertificateEndpoint was called with illegal argument: ${context.uri}. URL must be certificate endpoint of destination service.`); } return callDestinationService(context, headers); } async function callDestinationEndpoint(context, headers, options) { if (!context.uri.match(/[instance|subaccount]Destinations|v1\/destinations/)) { throw new Error(`callDestinationEndpoint was called with illegal argument: ${context.uri}. URL must be destination(s) endpoint of destination service.`); } return callDestinationService(context, headers, options); } async function callDestinationService(context, headers, options) { const { destinationName, retry } = options || {}; const requestConfig = { ...(await (0, http_agent_1.urlAndAgent)(context.uri)), method: 'get', headers }; const resilienceMiddleware = (0, resilience_1.resilience)(); if (destinationName && retry) { resilienceMiddleware.unshift(retryDestination(destinationName)); } return (0, internal_1.executeWithMiddleware)(resilienceMiddleware, { context, fnArgument: requestConfig, fn: config => axios_1.default.request(config) }); } //# sourceMappingURL=destination-service.js.map