@sap/xssec
Version:
XS Advanced Container Security API for node.js
152 lines (131 loc) • 5.88 kB
JavaScript
const createSecurityContext = require("../context/createSecurityContext");
const ConfigurationError = require("../error/configuration/ConfigurationError");
const { getLogger } = require('../util/logging');
const XsuaaService = require("../service/XsuaaService");
const IdentityService = require("../service/IdentityService");
const UaaService = require("../service/IdentityService");
const createServiceFromCredentials = require("./createService");
const ValidationError = require("../error/validation/ValidationError");
const XsaService = require("../service/XsaService");
/**
* @typedef {import("../context/SecurityContext")} SecurityContext
* @typedef {import("../token/Token")} Token
* @typedef {import("../util/Types").SecurityContextConfig} SecurityContextConfig
* @typedef {import("../util/Types").ServiceConfig} ServiceConfig
*/
const LOG = getLogger("createSecurityContextV3.js");
/**
* @callback createSecurityContextV3Callback
* @param {Error} error
* @param {SecurityContext} xssec3SecurityContext
* @param {Token} xssec3TokenInfo
*/
/**
*
* @param {String} jwt
* @param {Object} configParameter
* @param {boolean|createSecurityContextV3Callback} forceType
* @param {createSecurityContextV3Callback} cb
*/
async function createSecurityContextV3(jwt, configParameter, forceType, cb) {
if (typeof forceType === 'function') {
cb = forceType;
forceType = null;
}
if (cb == null || typeof cb !== 'function') {
throw new ConfigurationError("The callback parameter must be a function.");
}
let securityContext;
try {
if (configParameter == null) {
throw new ConfigurationError("The configParameter parameter must not be null or undefined.");
} else if (jwt == null) {
throw new ConfigurationError("The jwt parameter must not be null or undefined.");
}
const contextConfig = buildContextConfig(configParameter, jwt);
const serviceConfig = buildServiceConfig(configParameter);
const services = buildServices(contextConfig.credentials, forceType, serviceConfig);
securityContext = await createSecurityContext(services, contextConfig);
} catch (error) {
if (error instanceof ValidationError) {
return cb(error, null, { isValid: () => false, getErrorObject: () => error });
}
return cb(error);
}
return cb(null, securityContext, securityContext.token);
}
/**
* Build new contextConfig structure based on old configParameter structure, which can have options potentially located inside credentials
* @param {Object} configParameter
* @param {String} jwt
* @returns {SecurityContextConfig}
*/
function buildContextConfig(configParameter, jwt) {
// if configParameter has no credentials property, assume it is a credentials object or array itself
const contextConfig = configParameter.credentials ? configParameter : { credentials: configParameter };
contextConfig.jwt = jwt;
contextConfig.correlationId ??= contextConfig.credentials.correlationId;
contextConfig.clientCertificatePem ??= contextConfig.x509Certificate ?? contextConfig.credentials.x509Certificate;
contextConfig.skipValidation ??= contextConfig.credentials.skipValidation;
return contextConfig;
}
/**
* Build new serviceConfig structure based on old configParameter structure
* @param {Object} configParameter
* @returns {ServiceConfig}
*/
function buildServiceConfig(configParameter) {
const serviceConfig = {};
serviceConfig.endpoints ??= configParameter.endpoints || {};
serviceConfig.validation ??= configParameter.validation || {};
serviceConfig.validation.x5t ??= {};
serviceConfig.validation.x5t.enabled ??= configParameter.x5tValidation;
serviceConfig.validation.jwks ??= configParameter.jwksCache || {};
serviceConfig.validation.jwks.shared = true; // use the same JWKS cache for subsequent calls to createSecurityContextV3, otherwise EACH request will fetch the JWKS again
serviceConfig.validation.signatureCache = configParameter.signatureCache;
serviceConfig.requests = configParameter.requests || {};
if (configParameter.disableCache) {
LOG.warn(`The 'disableCache' option to disable the JWKS cache is not supported by the v3 compatibility package. The cache is always enabled.`);
}
return serviceConfig;
}
/**
* Build service objects based on credentials and forceType
* @param {Array} credentials
* @param {boolean} forceType
* @param {ServiceConfig} serviceConfig
* @returns {Service[]}
*/
function buildServices(credentials, forceType, serviceConfig) {
const credentialsArray = Array.isArray(credentials) ? credentials : [credentials];
insertXsAppnameFromEnv(credentialsArray);
if (forceType) {
LOG.info(`forceType === ${forceType}. Creating ${forceType} service(s) from credentials.`);
}
switch (forceType) {
case "XSUAA":
return credentialsArray.map(c => new XsuaaService(c, serviceConfig));
case "XSA":
return credentialsArray.map(c => new XsaService(c, serviceConfig));
case "IAS":
return credentialsArray.map(c => new IdentityService(c, serviceConfig));
case "UAA":
return credentialsArray.map(c => new UaaService(c, serviceConfig));
case null:
case undefined:
default:
return credentialsArray.map(c => createServiceFromCredentials(c, serviceConfig));
}
}
/**
* Backward-compatible filling of xsappname in credentials with environment variable XSAPPNAME if present.
* @param {Array} credentials
*/
function insertXsAppnameFromEnv(credentials) {
if (process.env.XSAPPNAME) {
for (const c of credentials) {
c.xsappname = process.env.XSAPPNAME;
}
}
}
module.exports = createSecurityContextV3;