@sap/xssec
Advanced tools
Comparing version 3.2.8 to 3.2.9
# Change Log | ||
All notable changes to this project will be documented in this file. | ||
## 3.2.9 - 2021-10-22 | ||
- custom domain support for IAS | ||
- support for "x-correlation-id" header to be set for createSecurityContext and tokenexchange-calls | ||
- support to turn off the internal cache for a createSecurityContext call | ||
## 3.2.8 - 2021-10-18 | ||
@@ -5,0 +10,0 @@ - add additional getter for user properties on XSUAA context |
@@ -57,4 +57,4 @@ 'use strict'; | ||
this.verifyToken = function (encodedToken, cb) { | ||
var verificationKey = new VerificationKey(config, "IAS"); | ||
this.verifyToken = function (encodedToken, attributes, cb) { | ||
var verificationKey = new VerificationKey(config, "IAS", attributes); | ||
var jwtValidator = createJwtTokenValidator(verificationKey, configArr, config); | ||
@@ -61,0 +61,0 @@ |
@@ -340,4 +340,4 @@ 'use strict'; | ||
this.verifyToken = function (encodedToken, cb) { | ||
var verificationKey = new VerificationKey(config, this.getConfigType()); | ||
this.verifyToken = function (encodedToken, attributes, cb) { | ||
var verificationKey = new VerificationKey(config, this.getConfigType(), attributes); | ||
var jwtValidator = createJwtTokenValidator(verificationKey, configArr, config); | ||
@@ -344,0 +344,0 @@ |
@@ -93,6 +93,7 @@ 'use strict'; | ||
KeyCache.prototype.getWellKnownFromOIDC = function(serviceUrl, cb) { | ||
var cacheKey = serviceUrl + ".well_known."; | ||
KeyCache.prototype.getWellKnownFromOIDC = function(serviceUrl, attrib, cb) { | ||
const attributes = attrib || { disableCache: false }; | ||
const cacheKey = serviceUrl + ".well_known."; | ||
var tmpResult = this.lruCache.get(cacheKey); | ||
var tmpResult = attributes.disableCache === true ? null : this.lruCache.get(cacheKey); | ||
if(tmpResult) { | ||
@@ -103,3 +104,3 @@ cb(null, tmpResult); | ||
requests.requestOpenIDConfiguration(serviceUrl, function(err, result) { | ||
requests.requestOpenIDConfiguration(serviceUrl, attributes, function(err, result) { | ||
if(err) { | ||
@@ -109,3 +110,5 @@ return cb(err); | ||
self.addKey(cacheKey, result); | ||
if(attributes.disableCache !== true) { | ||
self.addKey(cacheKey, result); | ||
} | ||
cb(null, result); | ||
@@ -116,6 +119,7 @@ }); | ||
KeyCache.prototype.getKeyOIDC = function(serviceUrl, keyId, zone_id, cb) { | ||
var self = this; | ||
KeyCache.prototype.getKeyOIDC = function(serviceUrl, keyId, zone_id, attrib, cb) { | ||
const attributes = attrib || { disableCache: false }; | ||
const self = this; | ||
this.getWellKnownFromOIDC(serviceUrl, function(err, result) { | ||
this.getWellKnownFromOIDC(serviceUrl, attributes, function(err, result) { | ||
if(err) { | ||
@@ -127,9 +131,11 @@ return cb(err); | ||
let tmpResult = self.lruCache.get(zone_id + tokenUrl + keyId); | ||
if(tmpResult) { | ||
//found in Cache | ||
return cb(null, tmpResult); | ||
if(attributes.disableCache !== true) { | ||
let tmpResult = self.lruCache.get(zone_id + tokenUrl + keyId); | ||
if(tmpResult) { | ||
//found in Cache | ||
return cb(null, tmpResult); | ||
} | ||
} | ||
requests.fetchOIDCKey(tokenUrl, zone_id, function(err, result) { | ||
requests.fetchOIDCKey(tokenUrl, zone_id, attributes, function(err, result) { | ||
if(err) { | ||
@@ -139,2 +145,3 @@ return cb(err); | ||
let found = null; | ||
//add all Keys to cache | ||
@@ -144,3 +151,9 @@ for(var i=0;i<result.keys.length;++i) { | ||
try { | ||
self.addKey(zone_id + tokenUrl + key.kid, createPublicKeyFromJWKS(key)); | ||
const pem = createPublicKeyFromJWKS(key); | ||
if(attributes.disableCache !== true) { | ||
self.addKey(zone_id + tokenUrl + key.kid, pem); | ||
} | ||
if(key.kid === keyId) { | ||
found = pem; | ||
} | ||
} catch(e) { | ||
@@ -151,4 +164,4 @@ debugError(e); | ||
var cacheKey = zone_id + tokenUrl + keyId; | ||
var tmpResult = self.lruCache.get(cacheKey); | ||
const cacheKey = zone_id + tokenUrl + keyId; | ||
const tmpResult = attributes.disableCache === true ? found : self.lruCache.get(cacheKey); | ||
if(tmpResult) { | ||
@@ -163,4 +176,5 @@ cb(null, tmpResult); | ||
KeyCache.prototype.getKey = function getKey(tokenKeyUrl, keyId, zid, cb) { | ||
KeyCache.prototype.getKey = function getKey(tokenKeyUrl, keyId, zid, attrib, cb) { | ||
var self = this; | ||
const attributes = attrib || { disableCache: false }; | ||
@@ -183,3 +197,3 @@ if ((tokenKeyUrl === null) || (tokenKeyUrl === undefined)) { | ||
// Check whether keyid is in cache | ||
var tmpResult = this.lruCache.get(cacheKey); | ||
var tmpResult = attributes.disableCache === true ? null : this.lruCache.get(cacheKey); | ||
if (tmpResult !== undefined) { | ||
@@ -195,3 +209,3 @@ debugTrace('Key with keyID: "' + keyId + '" found in cache. Returning key "' + tmpResult + '".'); | ||
return requests.fetchKeyFromXSUAA(tokenKeyUrl, zid, function(err, json) { | ||
return requests.fetchKeyFromXSUAA(tokenKeyUrl, zid, attributes, function(err, json) { | ||
if(err) { | ||
@@ -207,10 +221,21 @@ debugError(`An error occurred when reading the token keys from ${tokenKeyUrl}: ${err.message}${err.stack}`); | ||
debugTrace("Number of KeyIDs returned from XSUAA", json.keys.length); | ||
let found = null; | ||
for (var i = 0; i < json.keys.length; i++) { | ||
// Note: The following code removes line | ||
// breaks before adding the key to the cache | ||
self.addKey(tokenKeyUrl + json.keys[i].kid, json.keys[i].value.replace(/(\r\n|\n|\r)/gm, '')); | ||
const id = tokenKeyUrl + json.keys[i].kid; | ||
const pem = json.keys[i].value.replace(/(\r\n|\n|\r)/gm, ''); | ||
if(id === cacheKey) { | ||
found = pem; | ||
} | ||
if(attributes.disableCache !== true) { | ||
self.addKey(id, pem); | ||
} | ||
} | ||
var tmpResult = self.lruCache.get(cacheKey); | ||
if (tmpResult !== undefined) { | ||
var tmpResult = attributes.disableCache === true ? found : self.lruCache.get(cacheKey); | ||
if (tmpResult) { | ||
debugTrace('Key "' + cacheKey + '" found in cache. Returning key "' + tmpResult + '".'); | ||
@@ -217,0 +242,0 @@ return cb(null, tmpResult); |
@@ -101,3 +101,3 @@ 'use strict'; | ||
function appendAdditonalAttribites(options, additionalAttributes) { | ||
function appendAdditonalAttributes(options, additionalAttributes) { | ||
if (additionalAttributes !== null) { | ||
@@ -109,3 +109,3 @@ var authorities = {"az_attr": additionalAttributes}; | ||
function DefaultHeaders(zoneId) { | ||
function DefaultHeaders(zoneId, serviceCredentials) { | ||
var ret = { | ||
@@ -124,3 +124,3 @@ 'Accept': 'application/json', | ||
function buildOptions(serviceCredentials, additionalAttributes, url, grantType, zoneId, timeout) { | ||
function buildOptions(serviceCredentials, additionalAttributes, url, grantType, zoneId, timeout, attributes) { | ||
// jwt bearer flow | ||
@@ -130,3 +130,3 @@ const options = { | ||
url: url + '/oauth/token', | ||
headers: DefaultHeaders(zoneId), | ||
headers: DefaultHeaders(zoneId, serviceCredentials), | ||
form: { | ||
@@ -154,8 +154,14 @@ grant_type: grantType, | ||
appendAdditonalAttribites(options, additionalAttributes); | ||
appendAdditonalAttributes(options, additionalAttributes); | ||
if(attributes) { | ||
if(attributes.correlationId) { | ||
options.headers['x-correlation-id'] = attributes.correlationId; | ||
} | ||
} | ||
return options; | ||
} | ||
module.exports.requestOpenIDConfiguration = function (serviceCredentialsUrl, cb) { | ||
module.exports.requestOpenIDConfiguration = function (serviceCredentialsUrl, attributes, cb) { | ||
var options = { | ||
@@ -172,6 +178,12 @@ method: 'GET', | ||
if(attributes) { | ||
if(attributes.correlationId) { | ||
options.headers['x-correlation-id'] = attributes.correlationId; | ||
} | ||
} | ||
return _requestToNetwork(".well-known", options, cb); | ||
} | ||
module.exports.fetchOIDCKey = function (serviceCredentialsUrl, zone_id, cb) { | ||
module.exports.fetchOIDCKey = function (serviceCredentialsUrl, zone_id, attributes, cb) { | ||
var options = { | ||
@@ -192,6 +204,28 @@ method: 'GET', | ||
if(attributes) { | ||
if(attributes.correlationId) { | ||
options.headers['x-correlation-id'] = attributes.correlationId; | ||
} | ||
} | ||
return _requestToNetwork(".oidc-jkws", options, cb); | ||
} | ||
module.exports.requestUserToken = function (appToken, serviceCredentials, additionalAttributes, scopes, subdomain, zoneId, cb) { | ||
function getServiceCredentials(config) { | ||
if(config.credentials) { | ||
return config.credentials; | ||
} | ||
return config; | ||
} | ||
function getAttributes(config) { | ||
if(config.credentials) { | ||
return config; | ||
} | ||
return null; | ||
} | ||
module.exports.requestUserToken = function (appToken, config, additionalAttributes, scopes, subdomain, zoneId, cb) { | ||
//make it backward-compatible (where zoneId is not provided at all) | ||
@@ -203,2 +237,5 @@ if (typeof zoneId === 'function') { | ||
const serviceCredentials = getServiceCredentials(config); | ||
const attributes = getAttributes(config); | ||
var error = validateParameters(serviceCredentials, cb); | ||
@@ -219,3 +256,4 @@ if (error) { | ||
zoneId, | ||
DEFAULT_USER_TOKEN_TIMEOUT); | ||
DEFAULT_USER_TOKEN_TIMEOUT, | ||
attributes); | ||
@@ -237,3 +275,3 @@ //add Assertion | ||
module.exports.requestClientCredentialsToken = function (subdomain, serviceCredentials, additionalAttributes, zoneId, cb) { | ||
module.exports.requestClientCredentialsToken = function (subdomain, config, additionalAttributes, zoneId, cb) { | ||
//make it backward-compatible (where zoneId is not provided at all) | ||
@@ -245,2 +283,5 @@ if (typeof zoneId === 'function') { | ||
const serviceCredentials = getServiceCredentials(config); | ||
const attributes = getAttributes(config); | ||
// input validation | ||
@@ -262,5 +303,6 @@ const error = validateParameters(serviceCredentials, cb); | ||
zoneId, | ||
DEFAULT_TIMEOUT); | ||
DEFAULT_TIMEOUT, | ||
attributes); | ||
appendAdditonalAttribites(options, additionalAttributes); | ||
appendAdditonalAttributes(options, additionalAttributes); | ||
return _requestToNetwork("requestClientCredentialsToken", options, cb); | ||
@@ -274,3 +316,3 @@ } catch (e) { | ||
module.exports.fetchKeyFromXSUAA = async function (tokenKeyUrl, zid, cb) { | ||
module.exports.fetchKeyFromXSUAA = async function (tokenKeyUrl, zid, attributes, cb) { | ||
const options = { | ||
@@ -284,2 +326,8 @@ headers: { | ||
timeout: DEFAULT_TIMEOUT | ||
}; | ||
if(attributes) { | ||
if(attributes.correlationId) { | ||
options.headers['x-correlation-id'] = attributes.correlationId; | ||
} | ||
} | ||
@@ -286,0 +334,0 @@ |
@@ -48,2 +48,3 @@ 'use strict'; | ||
var authParams = options; | ||
if (!authorization) { | ||
@@ -71,2 +72,4 @@ debugTrace('Missing Authorization header'); | ||
const correlationId = req.headers["x-correlation-id"] || req.headers["vcap_request_id"]; | ||
try { | ||
@@ -111,3 +114,3 @@ | ||
xssec.createSecurityContext(token, this.options, paramA, paramB); | ||
xssec.createSecurityContext(token, {credentials: this.options, correlationId: correlationId}, paramA, paramB); | ||
} | ||
@@ -114,0 +117,0 @@ catch (err) { |
@@ -75,4 +75,8 @@ 'use strict'; | ||
this.getIssuer = function() { | ||
return payload.iss; | ||
return payload["ias_iss"] ? payload["ias_iss"] : payload.iss; | ||
} | ||
this.getCustomIssuer = function() { | ||
return payload["ias_iss"] ? payload.iss : null; | ||
} | ||
@@ -79,0 +83,0 @@ this.getSubject = function() { |
@@ -13,3 +13,3 @@ 'use strict'; | ||
function VerificationKey(config, type) { | ||
function VerificationKey(config, type, attributes) { | ||
var tokenInfo = null; | ||
@@ -75,6 +75,6 @@ var loadKeyError = null; | ||
var zone_id = tokenInfo.getPayload().zone_uuid || ""; | ||
var zone_id = tokenInfo.getPayload().zone_uuid || ""; | ||
//try to get a key from KeyCache | ||
keyCache.getKeyOIDC(tokenInfo.getIssuer(), accessToken.kid, zone_id, function(err, key) { | ||
keyCache.getKeyOIDC(tokenInfo.getIssuer(), accessToken.kid, zone_id, attributes, function(err, key) { | ||
if (err) { | ||
@@ -106,3 +106,3 @@ debugTrace('\n' + err); | ||
//try to get a key from KeyCache | ||
keyCache.getKey(accessToken.jku, accessToken.kid, zid, function(err, key) { | ||
keyCache.getKey(accessToken.jku, accessToken.kid, zid, attributes, function(err, key) { | ||
if (err) { | ||
@@ -109,0 +109,0 @@ //store the |
@@ -129,4 +129,23 @@ 'use strict'; | ||
function getConfigV2(config) { | ||
if(!config) { | ||
return { | ||
credentials: null | ||
}; | ||
} | ||
if(config.credentials) { | ||
return config; | ||
} | ||
return { | ||
credentials: config, | ||
correlationId: null, | ||
cache: config.cache, | ||
disableCache: false | ||
} | ||
} | ||
//For Backward compatibilty | ||
exports.createSecurityContext = function (token, configParam, forceType, cb) { | ||
exports.createSecurityContext = function (token, configParameter, forceType, cb) { | ||
if(typeof forceType === 'function') { | ||
@@ -142,2 +161,4 @@ cb = forceType; | ||
try { | ||
const configv2 = getConfigV2(configParameter); | ||
const configParam = configv2.credentials; | ||
//make sure the parameter is an array | ||
@@ -163,3 +184,3 @@ var configArr = Array.isArray(configParam) ? configParam : [configParam]; | ||
securityContext.verifyToken(token, cb); | ||
securityContext.verifyToken(token, configv2, cb); | ||
} catch (e) { | ||
@@ -166,0 +187,0 @@ cb(e); |
{ | ||
"name": "@sap/xssec", | ||
"version": "3.2.8", | ||
"version": "3.2.9", | ||
"description": "XS Advanced Container Security API for node.js", | ||
@@ -5,0 +5,0 @@ "main": "./lib", |
@@ -75,2 +75,53 @@ @sap/xssec: XS Advanced Container Security API for node.js | ||
### Support for X-Correlation-ID header | ||
The xssec library internally calls REST APIs of the XSUAA. | ||
Now it's possible to set a `correlationId` during context creation and for token exchange calls. | ||
For this you have to restructe the configuration object. | ||
```js | ||
const config = { | ||
credentials: xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa, | ||
correlationId: "1111-1111-11111111" | ||
}; | ||
//now you can call the createSecurityContext method as always | ||
xssec.createSecurityContext(access_token, config, function(error, securityContext, tokenInfo) { | ||
if (error) { | ||
console.log('Security Context creation failed'); | ||
return; | ||
} | ||
console.log('Security Context created successfully'); | ||
console.log(tokenInfo.getPublicClaims()); | ||
}); | ||
``` | ||
### Disable the cache for the current call (ONLY FOR TESTING!) | ||
The xssec library internally calls REST APIs to fetch the public verification keys from XSUAA/IAS. | ||
For performance reasons there is a cache, so not all calls have to fetch the key again. | ||
Now it's possible to turn off the cache using the `disableCache` option during context creation. | ||
For this you have to restructe the configuration object. | ||
```js | ||
const config = { | ||
credentials: xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa, | ||
correlationId: "1111-1111-11111111" | ||
disableCache: true | ||
}; | ||
//now you can call the createSecurityContext method as always | ||
//internally no Cache will be used! | ||
xssec.createSecurityContext(access_token, config, function(error, securityContext, tokenInfo) { | ||
if (error) { | ||
console.log('Security Context creation failed'); | ||
return; | ||
} | ||
console.log('Security Context created successfully'); | ||
console.log(tokenInfo.getPublicClaims()); | ||
}); | ||
``` | ||
This also works for the Tokenexchange methods! | ||
### Usage with Passport Strategy | ||
@@ -77,0 +128,0 @@ |
125226
1759
483