New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@sap/approuter

Package Overview
Dependencies
Maintainers
0
Versions
195
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/approuter - npm Package Compare versions

Comparing version 17.0.0 to 17.1.0

lib/utils/app-cache-utils.js

13

CHANGELOG.md

@@ -8,2 +8,15 @@ # Change Log

## 17.1.0 - 2024-11-12
### Added
- Caching support in get applications API
- Max size check for cache
- Use of XS_BIND_ADDRESS env var for address binding.
- Support for backward compatible cookie validation
### Fixed
- Dynamic log level setting in managed approuter
- Get tenant name correctly for ias authentication
## 17.0.0 - 2024-10-28

@@ -10,0 +23,0 @@

4

lib/configuration/env-config.js

@@ -178,3 +178,5 @@ 'use strict';

enableCSPheaders: loadJsonVar('ENABLE_FRAME_ANCESTORS_CSP_HEADERS'),
ownSapCloudService: loadJsonVar('OWN_SAP_CLOUD_SERVICE')
ownSapCloudService: loadJsonVar('OWN_SAP_CLOUD_SERVICE'),
appsCacheExpiration: loadJsonVar('APPS_CACHE_EXPIRATION'),
cookieBackwardCompatibility: loadJsonVar('COOKIE_BACKWARD_COMPATIBILITY')
};

@@ -181,0 +183,0 @@

@@ -11,2 +11,3 @@ {

"minimumTokenValidity": { "type": "integer", "minimum": 0 },
"appsCacheExpiration": { "type": "integer", "minimum": 0 },
"sendXFrameOptions": { "type": "boolean" },

@@ -18,2 +19,3 @@ "tenantHostPattern": { "type": "string", "minLength": 1, "format": "regexWithCapture" },

"http2Support": { "type": "boolean"},
"cookieBackwardCompatibility": { "type": "boolean" },
"directRoutingUriPatterns": {"type": "array", "items": {"type": "string", "minLength": 1}, "minItems": 1},

@@ -20,0 +22,0 @@ "ownSapCloudService": {"type": "array", "items": {"type": "string", "minLength": 1}, "minItems": 1},

@@ -68,7 +68,7 @@ 'use strict';

};
let locationAfterLogin = cookie.serialize(redirectCookieName, '', cookieOptions);
let locationAfterLogin = cookieUtils.serialize(req, redirectCookieName, '', cookieOptions);
cookieUtils.setCookie(res, cookieUtils.addAttributes(locationAfterLogin, req));
// Fragment may be empty string, which is casted to false. Therefore check presence of key.
if (cookies.hasOwnProperty(fragmentCookieName)) {
let fragmentAfterLogin = cookie.serialize(fragmentCookieName, '', cookieOptions);
let fragmentAfterLogin = cookieUtils.serialize(req, fragmentCookieName, '', cookieOptions);
cookieUtils.setCookie(res, cookieUtils.addAttributes(fragmentAfterLogin, req));

@@ -78,3 +78,3 @@ }

if (cookies.hasOwnProperty('signature')) {
let signature = cookie.serialize('signature', '', cookieOptions);
let signature = cookieUtils.serialize(req, 'signature', '', cookieOptions);
cookieUtils.setCookie(res, cookieUtils.addAttributes(signature, req));

@@ -81,0 +81,0 @@ }

@@ -126,3 +126,3 @@ 'use strict';

if (!redirectCookie) {
let locationAfterLogin = cookie.serialize(redirectCookieName, req.url, {path: '/', httpOnly: true});
let locationAfterLogin = cookieUtils.serialize(req, redirectCookieName, req.url, {path: '/', httpOnly: true});
cookieUtils.setCookie(res, locationAfterLogin);

@@ -129,0 +129,0 @@ }

'use strict';
const bsUtils = require('../utils/business-service-utils');
const logRequestInfo = require('../utils/application-logs-utils').logRequestInfo;
const urlUtils = require('../utils/url-utils');
const urijs = require('urijs');
const svc2Approuter = require('./service-to-approuter-middleware');
const jwtDecode = require('jwt-decode');
const applicationLogUtils = require('../utils/application-logs-utils');
const logRequestInfo = require('../utils/application-logs-utils').logRequestInfo;
const urlUtils = require('../utils/url-utils');
const urijs = require('urijs');
const svc2Approuter = require('./service-to-approuter-middleware');
const jwtDecode = require('jwt-decode');
const applicationLogUtils = require('../utils/application-logs-utils');
const passportUtils = require('../passport/utils');
const html5RepoUtils = require('../utils/html5-repo-utils');
const drUtils = require('../utils/dynamic-routing-utils');
const cookie = require('cookie');
const { promisify } = require('util');
const svc2ApprouterProm = promisify(svc2Approuter);
const getDestHTML5Applications = promisify(bsUtils.getHTML5Applications);
const cacheBSDestinationsProm = promisify(bsUtils.cacheBSDestinations);
const html5RepoUtils = require('../utils/html5-repo-utils');
let getHTML5Applications = html5RepoUtils.getHTML5Applications; // let for testing
const drUtils = require('../utils/dynamic-routing-utils');
const cookie = require('cookie');
const { promisify } = require('util');
const appCacheUtils = require('../utils/app-cache-utils');
let svc2ApprouterProm = promisify(svc2Approuter); // let for testing
let getDestHTML5Applications = promisify(bsUtils.getHTML5Applications); // let for testing
const cacheBSDestinationsProm = promisify(bsUtils.cacheBSDestinations);
const GET_APPLICATIONS_API = '/applications';

@@ -58,2 +60,23 @@ const DOWNLOAD_APPLICATIONS_API = '/applications/content';

async function getApplications(req, res) {
const skipExternalCache = req.headers['x-approuter-authorization'] && req.headers['x-skip-external-cache'];
const excludeHTML5RepoCreds = isHTML5RepoCredsExcluded(req);
const compactResponse = req.headers['x-compact-response'] === 'true';
const queryParams = req.url.split('?')[1];
let cacheKey = queryParams ? `${req.tenant}_${queryParams}` : `${req.tenant}`;
if (excludeHTML5RepoCreds) {
cacheKey = cacheKey + '_excludeHTML5RepoCreds';
} else if (req.disabledDestCred) {
cacheKey = cacheKey + '_excludeDestCreds';
}
const cachedApps = !skipExternalCache && await appCacheUtils.getAppsCache(cacheKey, req);
if (cachedApps) {
res && res.setHeader('Content-Type', 'application/json');
res && res.setHeader('x-app-cache', 'hit');
let enrichedApps;
if (!compactResponse) {
enrichedApps = await html5RepoUtils.enrichWithHTML5RepoData(cachedApps, queryParams, req);
}
return res ? res.end(JSON.stringify(enrichedApps || cachedApps), null, 4) : null;
}
const html5Applications = !req.disabledDestCred && await getDestHTML5Applications(req);

@@ -63,4 +86,4 @@ let html5ApplicationsRepo = {

};
if (!isHTML5RepoCredsExcluded(req)) {
html5ApplicationsRepo = await html5RepoUtils.getHTML5Applications(req);
if (!excludeHTML5RepoCreds) {
html5ApplicationsRepo = await getHTML5Applications(req);
}

@@ -70,4 +93,31 @@ if (html5ApplicationsRepo.applications.length > 0 && html5Applications){

}
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify((html5Applications || html5ApplicationsRepo), null, 4));
const apps = html5Applications || html5ApplicationsRepo;
const compactApps = {};
if (apps && apps.applications) {
apps.applications = apps.applications.filter(app => (app.applicationType !== html5RepoUtils.technicalCacheBusterApplication));
if (excludeHTML5RepoCreds) {
apps.applications = apps.applications.filter(app => (app.destinationId || app.destinationName));
}
apps.applications.forEach(app => {
if (!compactApps[app.appHostId]) {
compactApps[app.appHostId] = {
destId: app.destinationId,
destName: app.destinationName,
sapCloudService: app.sapCloudService,
appIds: [],
};
app.subscribedAppName && (compactApps[app.appHostId].subscribedAppName = app.subscribedAppName);
app.subscribedCommercialAppName && (compactApps[app.appHostId].subscribedCommercialAppName = app.subscribedCommercialAppName);
}
compactApps[app.appHostId].appIds.push(app.applicationId);
});
}
await appCacheUtils.setAppsCache(cacheKey, compactApps, req);
res && res.setHeader('Content-Type', 'application/json');
res && res.setHeader('x-app-cache', 'miss');
if (compactResponse) {
res && res.end(JSON.stringify((compactApps), null, 4));
} else {
res && res.end(JSON.stringify((apps), null, 4));
}
}

@@ -74,0 +124,0 @@

@@ -5,4 +5,5 @@ 'use strict';

const cookieModule = require('cookie');
const cookieUtils = require('../utils/cookie-utils');
function serializeCookies(cookies, sessionCookieName) {
function serializeCookies(req, cookies, sessionCookieName) {
let cookieNames = Object.keys(cookies);

@@ -16,3 +17,3 @@ let filteredCookies = cookieNames.filter(function (name) {

return filteredCookies.map(function (name) {
return cookieModule.serialize(name, cookies[name], { encode: same });
return cookieUtils.serialize(req, name, cookies[name], { encode: same });
}).join('; ');

@@ -36,3 +37,3 @@ }

let cookiesMap = cookieModule.parse(req.headers.cookie, { decode: same });
req.headers.cookie = serializeCookies(cookiesMap, sessionCookieName);
req.headers.cookie = serializeCookies(req, cookiesMap, sessionCookieName);
if (req.headers.cookie === null) {

@@ -39,0 +40,0 @@ delete req.headers.cookie;

@@ -62,3 +62,3 @@ 'use strict';

scopes: scopes,
tenant: token.ext_attr && token.ext_attr.zdn,
tenant: (token.ext_attr && token.ext_attr.zdn) || options.urlTenant,
urlTenant: options.urlTenant,

@@ -65,0 +65,0 @@ xsuaaToken: options.xsuaaToken

@@ -30,2 +30,3 @@ 'use strict';

let server;
let xsBindAddress = process.env.XS_BIND_ADDRESS;
if (routerConfig.http2Support){

@@ -50,9 +51,12 @@ server = http2.createServer(app);

wsServer.listen(server);
server.on('error', callback);
const listenArgs = [routerConfig.serverPort];
if (xsBindAddress) {
listenArgs.push(xsBindAddress);
}
server.on('error', callback);
server.listen(routerConfig.serverPort, function () {
app.logger.info('Application router is listening on port: ' +
server.address().port);
server.listen(...listenArgs, () => {
app.logger.info('Application router is listening on port: ' + server.address().port);
callback(undefined, new Server(server, wsServer));
});
};
};

@@ -96,3 +96,3 @@ /* eslint-disable camelcase */

if (dynamicLogLevel){
req.logger.setDynamicLoggingLevel(dynamicLogLevel);
req.logger.setLoggingLevel(dynamicLogLevel);
}

@@ -99,0 +99,0 @@ }

@@ -9,5 +9,8 @@ 'use strict';

const iasUtils = require('./ias-utils');
const cookie = require('cookie');
const SESSION_SECRET_LENGTH = 64;
let __toString = Object.prototype.toString;
exports.generateSessionSecret = function () {

@@ -191,2 +194,138 @@ return crypto.randomBytes(SESSION_SECRET_LENGTH).toString('hex');

exports.serialize = function (req, name, val, options) {
if (req && req.routerConfig && req.routerConfig.cookieBackwardCompatibility) {
return exports.internalSerialize(name, val, options);
}
else {
return cookie.serialize(name, val, options);
}
};
function encode (val) {
return encodeURIComponent(val);
}
function isDate (val) {
return __toString.call(val) === '[object Date]' ||
val instanceof Date;
}
exports.internalSerialize = function (name, val, options) {
// eslint-disable-next-line no-control-regex
const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
let opt = options || {};
let enc = opt.encode || encode;
if (typeof enc !== 'function') {
throw new TypeError('option encode is invalid');
}
if (!fieldContentRegExp.test(name)) {
throw new TypeError('argument name is invalid');
}
let value = enc(val);
if (value && !fieldContentRegExp.test(value)) {
throw new TypeError('argument val is invalid');
}
let str = name + '=' + value;
// eslint-disable-next-line eqeqeq
if (null != opt.maxAge) {
let maxAge = opt.maxAge - 0;
if (isNaN(maxAge) || !isFinite(maxAge)) {
throw new TypeError('option maxAge is invalid');
}
str += '; Max-Age=' + Math.floor(maxAge);
}
if (opt.domain) {
if (!fieldContentRegExp.test(opt.domain)) {
throw new TypeError('option domain is invalid');
}
str += '; Domain=' + opt.domain;
}
if (opt.path) {
if (!fieldContentRegExp.test(opt.path)) {
throw new TypeError('option path is invalid');
}
str += '; Path=' + opt.path;
}
if (opt.expires) {
let expires = opt.expires;
if (!isDate(expires) || isNaN(expires.valueOf())) {
throw new TypeError('option expires is invalid');
}
str += '; Expires=' + expires.toUTCString();
}
if (opt.httpOnly) {
str += '; HttpOnly';
}
if (opt.secure) {
str += '; Secure';
}
if (opt.partitioned) {
str += '; Partitioned';
}
if (opt.priority) {
let priority = typeof opt.priority === 'string'
? opt.priority.toLowerCase()
: opt.priority;
switch (priority) {
case 'low':
str += '; Priority=Low';
break;
case 'medium':
str += '; Priority=Medium';
break;
case 'high':
str += '; Priority=High';
break;
default:
throw new TypeError('option priority is invalid');
}
}
if (opt.sameSite) {
let sameSite = typeof opt.sameSite === 'string'
? opt.sameSite.toLowerCase() : opt.sameSite;
switch (sameSite) {
case true:
str += '; SameSite=Strict';
break;
case 'lax':
str += '; SameSite=Lax';
break;
case 'strict':
str += '; SameSite=Strict';
break;
case 'none':
str += '; SameSite=None';
break;
default:
throw new TypeError('option sameSite is invalid');
}
}
return str;
};
function encrypt(value, options, cb) {

@@ -193,0 +332,0 @@ if (!options || (!options.clientsecret && !options.clientid)) {

@@ -11,2 +11,3 @@ 'use strict';

const html5AppsCache = new NodeCache({stdTTL: stdTTL, checkperiod: checkPeriod});
const enrichedHTML5AppsCache = new NodeCache({stdTTL: stdTTL, checkperiod: checkPeriod});
const logUtil = require('./application-logs-utils');

@@ -26,2 +27,4 @@ const { promisify } = require('util');

module.exports.getHTML5RepoToken = getHTML5RepoToken;
module.exports.enrichWithHTML5RepoData = enrichWithHTML5RepoData;
module.exports.getApplicationsMetadata = getApplicationsMetadata;
module.exports.html5AppsCache = html5AppsCache;

@@ -45,18 +48,9 @@

// Get applications metadata
let requestOptions = await _getHTML5RepoOptions(req, `/applications/metadata/${queries}`);
logUtil.logRequestInfo(req,`Fetching HTML5 applications metadata for tenant ${req.tenant}`);
let result = await request.axiosRequest('get',requestOptions);
if (result.error){
throw new Error('Failed to fetch applications metadata ' + result.error
+ (result.response && result.response.statusCode ? `response status ${result.response.statusCode}` : ''));
}
const applicationsMetadata = result.body && JSON.parse(result.body);
let appsFound = applicationsMetadata && applicationsMetadata.length > 0 ? applicationsMetadata.length : 0;
logUtil.logRequestInfo(req,`${appsFound} HTML5 applications retrieved for tenant ${req.tenant}`);
appsFound > 0 && _addHTML5ApplicationsToCache(applicationsMetadata, req);
const applicationsMetadata = await exports.getApplicationsMetadata(req, queries);
applicationsMetadata && applicationsMetadata.length > 0 && _addHTML5ApplicationsToCache(applicationsMetadata, req);
// Get subscribed applications metadata
requestOptions = await _getHTML5RepoOptions(req, '/applications/subscriptions');
const requestOptions = await _getHTML5RepoOptions(req, '/applications/subscriptions');
logUtil.logRequestInfo(req,`Fetching subscribed HTML5 applications metadata for tenant ${req.tenant}`);
result = await request.axiosRequest('get',requestOptions);
const result = await request.axiosRequest('get',requestOptions);
if (result.error){

@@ -67,3 +61,3 @@ throw new Error('Failed to fetch applications metadata ' + result.error

const subscribedApplications = result.body && JSON.parse(result.body);
appsFound = subscribedApplications && subscribedApplications.length > 0 ? subscribedApplications.length : 0;
const appsFound = subscribedApplications && subscribedApplications.length > 0 ? subscribedApplications.length : 0;
logUtil.logRequestInfo(req,`${appsFound} subscribed HTML5 applications retrieved for tenant ${req.tenant}`);

@@ -90,2 +84,3 @@ appsFound > 0 && _addHTML5ApplicationsToCache(subscribedApplications, req, true);

if (sapCloudServiceKeys) {
const appKeys = {};
for (let sapCloudServiceKey in sapCloudServiceKeys) {

@@ -95,2 +90,3 @@ sapCloudServiceKeys[sapCloudServiceKey].applications.forEach((app) => {

const baseAppKey = serviceKeyComponents.serviceName + '.' + app.applicationName + '-' + app.applicationVersion;
const uniqueAppKey = app.appHostId + '.' + serviceKeyComponents.serviceName + '.' + app.applicationName + '-' + app.applicationVersion;
app['url'] = approuterHost + '/' + baseAppKey;

@@ -100,3 +96,6 @@ app['uniqueUrl'] = approuterHost + '/' + app.appHostId + '.' + baseAppKey;

app['businessServices'] = _getBoundBusinessServices(sapCloudServiceKeys[sapCloudServiceKey].credentials);
result.applications.push(app);
if (!appKeys[uniqueAppKey]) {
appKeys[uniqueAppKey] = uniqueAppKey;
result.applications.push(app);
}
});

@@ -108,2 +107,81 @@ }

async function enrichWithHTML5RepoData(cachedApps,queryParams,req) {
const cacheKey = queryParams ? `${req.tenant}_?${queryParams}` : `${req.tenant}`;
let cachedResult = enrichedHTML5AppsCache.get(cacheKey);
if (cachedResult) {
return cachedResult;
}
const runtimeHost = req.HTML5AppHost.indexOf('https://') !== 0 ? 'https://' + req.HTML5AppHost + '/' : req.HTML5AppHost + '/';
const result = {
applications: [],
errors: []
};
// Get local apps
const appsMetadata = await exports.getApplicationsMetadata(req, queryParams, null);
appsMetadata.forEach(app => {
_addAppToResult(app, result, cachedApps, runtimeHost);
});
// Try to get business services apps pointed by destinations, and subscribed apps
const fetchApplicationsPromises = [];
for (let appHostId in cachedApps) {
if (!cachedApps[appHostId].appFound) {
fetchApplicationsPromises.push(new Promise((resolve) => {
resolve(exports.getApplicationsMetadata(req, queryParams, appHostId));
}));
}
}
const html5RepoResult = await Promise.all(fetchApplicationsPromises);
const bsappsMetadata = html5RepoResult.flat();
bsappsMetadata.forEach(app => {
_addAppToResult(app, result, cachedApps, runtimeHost);
});
// Check for missing apps
for (let appHostId in cachedApps) {
const cachedAppElement = cachedApps[appHostId];
if (!cachedAppElement.appFound) {
const destinationIdentifier = cachedAppElement.destId || cachedAppElement.destName;
if (destinationIdentifier) {
result.errors.push(`Destination ${destinationIdentifier} with app-host ID ${appHostId} is not mapped to any application,
this app-host might not exist in html5 apps repo or no application was uploaded to it. Check in BTP cockpit if an html5-apps-repo instance with plan app-host and this ID exists,
if not, delete the destination. If it exists check your mta and try to re-deploy the applications`);
} else {
result.errors.push(`No applications found for appHostId ${appHostId}, this app-host might not exist in html5 apps repo or no application was uploaded to it.`);
}
}
}
enrichedHTML5AppsCache.set(cacheKey, result);
return result;
}
function _addAppToResult(app, result, cachedApps, runtimeHost) {
const cachedAppElement = cachedApps[app.appHostId];
// Managed Approuter needs a runtime url with sap.cloud.service, it may come from manifest.json or destination
if (cachedAppElement && (app.sapCloudService || cachedAppElement.destId || cachedAppElement.destName)) {
const credentials = app.configuration && app.configuration[CONFIG_CREDENTIALS];
const html5RuntimeEnabled = app.configuration && app.configuration['HTML5Runtime_enabled'];
const embeddedCredsApp = !!((credentials && (credentials.xsuaa || credentials.identity)) || html5RuntimeEnabled);
app.destinationName = cachedAppElement.destName;
app.destinationId = cachedAppElement.destId;
app.subscribedAppName = cachedAppElement.subscribedAppName;
app.subscribedCommercialAppName = cachedAppElement.subscribedCommercialAppName;
app.identityZone = app.subdomain;
app['app-host-id'] = app.appHostId;
const sapCloudService = cachedAppElement.sapCloudService || app.sapCloudService;
app['sap.cloud.service'] = sapCloudService;
const sapCloudServiceNoDots = sapCloudService && sapCloudService.replace(/\./g, '');
app.destinations = app.configuration && app.configuration.destinations;
app.businessServices = credentials && _getBoundBusinessServices(credentials);
const destIdSection = app.destinationId ? app.destinationId + '.' : '';
app.url = `${runtimeHost}${destIdSection}${sapCloudServiceNoDots}.${app.applicationName}-${app.applicationVersion}`;
app.uniqueUrl = embeddedCredsApp && `${runtimeHost}${app.appHostId}.${sapCloudServiceNoDots}.${app.applicationName}-${app.applicationVersion}`;
delete app.configuration;
embeddedCredsApp && (app.configuration = true);
cachedApps[app.appHostId].appFound = true;
result.applications.push(app);
}
}
async function downloadHTML5Application(req, res){

@@ -215,4 +293,2 @@ const dynamicRoutingUtils = require('./dynamic-routing-utils');

function _addHTML5ApplicationsToCache(applicationsMetadata, req, addApps) {

@@ -328,1 +404,16 @@ const tenant = req.tenant;

}
async function getApplicationsMetadata(req, queries, appHostId) {
const requestOptions = await _getHTML5RepoOptions(req, `/applications/metadata/${queries}`, appHostId);
logUtil.logRequestInfo(req,`Fetching HTML5 applications metadata for tenant ${req.tenant}`);
const result = await request.axiosRequest('get',requestOptions);
const responseStatus = (result.response && result.response.statusCode) || 'unknown';
if (result.error || (responseStatus !== 200 && responseStatus !== 404)){
const message = result.error || (result.response && result.response.message) || 'No applications metadata response body';
throw new Error(`Failed to fetch applications metadata from html5 apps repo ${message}, html5 apps repo response status: ${responseStatus}`);
}
const applicationsMetadata = result.body && JSON.parse(result.body);
const appsFound = applicationsMetadata && applicationsMetadata.length > 0 ? applicationsMetadata.length : 0;
logUtil.logRequestInfo(req,`${appsFound} HTML5 applications retrieved for tenant ${req.tenant}`);
return applicationsMetadata;
}

@@ -14,3 +14,3 @@ 'use strict';

function addSubscribedApplications(req, html5AppsResponse, cb) {
if (!process.env.RETURN_SUBSCRIBED_APPLICATIONS) {
if (!process.env.RETURN_SUBSCRIBED_APPLICATIONS || process.env.RETURN_SUBSCRIBED_APPLICATIONS === 'false') {
return cb(null, html5AppsResponse);

@@ -17,0 +17,0 @@ }

{
"name": "@sap/approuter",
"description": "Node.js based application router",
"version": "17.0.0",
"version": "17.1.0",
"repository": {},

@@ -6,0 +6,0 @@ "main": "approuter.js",

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc