Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@sap/xssec

Package Overview
Dependencies
Maintainers
3
Versions
85
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/xssec - npm Package Compare versions

Comparing version 3.0.3 to 3.0.5

lib/tokeninfo.js

11

lib/constants.js

@@ -89,3 +89,3 @@ // Scope Prefix:

Object.defineProperty(exports, "KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES", {
value: 15,
value: 10,
enumerable: true,

@@ -97,3 +97,3 @@ writable: false,

Object.defineProperty(exports, "KEYCACHE_DEFAULT_CACHE_SIZE", {
value: 100,
value: 1000,
enumerable: true,

@@ -103,1 +103,8 @@ writable: false,

});
Object.defineProperty(exports, "USER_AGENT", {
value: "nodejs-xssec-3",
enumerable: true,
writable: false,
configurable: false
});

12

lib/keycache.js

@@ -18,4 +18,3 @@ 'use strict';

function KeyCache(cacheSize, cacheEntryExpirationTimeInMinutes,
callUaaToReadTokenKeys, tokenKeyPath) {
function KeyCache(cacheSize, cacheEntryExpirationTimeInMinutes, callUaaToReadTokenKeys, tokenKeyPath) {
debugTrace('Initializing KeyCache with parameters cacheSize (' + cacheSize

@@ -83,3 +82,3 @@ + '), cacheEntryExpirationTimeInMinutes ('

KeyCache.prototype.getKey = function getKey(tokenKeyUrl, keyId, cb) {
KeyCache.prototype.getKey = function getKey(tokenKeyUrl, keyId, zid, cb) {
var self = this;

@@ -130,7 +129,12 @@

timeout: 2000,
headers: {
"User-Agent": constants.USER_AGENT,
},
maxAttempts: 3,
retryDelay: 500,
followRedirect: false,
retryStrategy: request.RetryStrategies.HTTPOrNetworkError
};
debugTrace('Key "' + cacheKey + '" not found in cache. Querying keys from UAA via URL "' + options.url + '".');
debugTrace('Key "' + cacheKey + '" not found in cache. Querying keys from UAA via URL "' + options.url + '" and zid: "' + zid + '".');
request

@@ -137,0 +141,0 @@ .get(

@@ -48,3 +48,6 @@ 'use strict';

'&response_type=token&client_id=' + serviceCredentials.clientid + '&assertion=' + securityContext.getAppToken(),
headers: { Accept: 'application/json' },
headers: {
'Accept': 'application/json',
'User-Agent': constants.USER_AGENT
},
auth: {

@@ -56,2 +59,3 @@ user: serviceCredentials.clientid,

};
if (scopes !== null) {

@@ -64,2 +68,4 @@ options.url = options.url + "&scope=" + scopes;

}
debugTrace('requestUserToken::HTTP Call with %O', options);
request.post(

@@ -78,2 +84,3 @@ options,

debugTrace('requestToken: Call to /oauth/token was not successful (grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer). Bearer token invalid, requesting client does not have the grant_type or no scopes were granted.');
debugTrace(body);
return cb(new Error('Call to /oauth/token was not successful (grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer). Bearer token invalid, requesting client does not have the grant_type or no scopes were granted.'), null);

@@ -83,2 +90,3 @@ }

debugTrace('requestToken: Call to /oauth/token was not successful (grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer). HTTP status code: ' + response.statusCode);
debugTrace(body);
return cb(new Error('Call to /oauth/token was not successful (grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer). HTTP status code: ' + response.statusCode), null);

@@ -128,3 +136,7 @@ }

url : urlWithCorrectSubdomain + '/oauth/token?grant_type=client_credentials&response_type=token',
headers: { 'Accept' : 'application/json', 'Content-Type' : 'application/x-www-form-urlencoded' },
headers: {
'Accept' : 'application/json',
'Content-Type' : 'application/x-www-form-urlencoded',
'User-Agent': constants.USER_AGENT
},
auth: {

@@ -140,2 +152,3 @@ user: serviceCredentials.clientid,

}
debugTrace('requestClientCredentialsToken::HTTP Call with %O', options);
request.post(

@@ -142,0 +155,0 @@ options,

@@ -60,4 +60,6 @@ 'use strict';

var self = this;
xssec.createSecurityContext(token, this.options, function (err, ctx) {
if (err) {
xssec.createSecurityContext(token, this.options, function (err, ctx, tokenInfo) {
req.tokenInfo = tokenInfo;
if (err) {
return err.statuscode ? self.fail(err.statuscode) : self.error(err);

@@ -64,0 +66,0 @@ }

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

const jwt = require('jsonwebtoken');
const tokenInfo = require('./tokeninfo')

@@ -54,6 +55,6 @@ const DOT = ".";

this.validateToken = function (audiencesFromToken) {
this.validateToken = function (audiencesFromToken, scopesFromToken, cid) {
foreignMode = false;
var allowedAudiences = getAllowedAudiencesFromToken(audiencesFromToken);
if (validateAudienceOfXsuaaBrokerClone(allowedAudiences) === true || validateDefault(allowedAudiences) === true) {
var allowedAudiences = getAllowedAudiencesFromToken(audiencesFromToken, scopesFromToken || []);
if (validateSameClientId(cid) === true || validateAudienceOfXsuaaBrokerClone(allowedAudiences) === true || validateDefault(allowedAudiences) === true) {
return ValidationResults.createValid();

@@ -69,2 +70,9 @@ }

function validateSameClientId(cid) {
if(!cid || !clientId) {
return false;
}
return clientId.trim() === cid.trim();
}
//iterate over all configured clientIds and return true of the cb returns true

@@ -107,3 +115,7 @@ function forEachClientId(cb) {

function getAllowedAudiencesFromToken(aud) {
this.getListOfAudiencesFromToken = function(aud, scopes) {
return getAllowedAudiencesFromToken(aud || [], scopes || []);
}
function getAllowedAudiencesFromToken(aud, scopes) {
var audiences = [];

@@ -118,3 +130,3 @@ var tokenAudiences = aud || [];

var aud = audience.substring(0, audience.indexOf(DOT)).trim();
if (aud && aud.length > 0) {
if (aud && !audiences.includes(aud)) {
audiences.push(aud);

@@ -126,2 +138,14 @@ }

}
if (audiences.length == 0) {
for(var i=0;i < scopes.length;++i) {
var scope = scopes[i];
if (scope.indexOf(DOT) >-1) {
var aud = scope.substring(0, scope.indexOf(DOT)).trim();
if(aud && !audiences.includes(aud)) {
audiences.push(aud);
}
}
}
}
return audiences;

@@ -152,13 +176,13 @@ }

jwt.verify(accessToken,
verificationKey.getCallback(),
{
algorithms: ['RS256'] //we currently only allow RS256
},
function (err, decodedToken) {
var token = new tokenInfo(accessToken);
token.verify(verificationKey.getCallback(token),
function (err, token) {
var decodedToken = token.getPayload();
if (err) {
debugError(err.statuscode);
debugError(err.message);
debugError(err.stack);
err.statuscode = accessToken ? 403 : 401;
return cb(err);
debugError(err.stack);
return cb(err, token);
}

@@ -179,5 +203,5 @@

var valid_result = audienceValidator.validateToken(decodedToken.aud);
var valid_result = audienceValidator.validateToken(decodedToken.aud, decodedToken.scope, decodedToken.cid);
if (!valid_result.isValid()) {
return returnError(403, valid_result.getErrorDescription());
return returnError(401, valid_result.getErrorDescription());
}

@@ -189,4 +213,5 @@

cb(null, decodedToken);
});
cb(null, token);
}
);
};

@@ -193,0 +218,0 @@ };

@@ -11,11 +11,9 @@ 'use strict';

// Note: the keycache is initialized currently with the default size defined in constants
// Consider making this configurable for the application, e.g. via xssecurity.json
// or (probably worse) via environment variables.
// Similarly, the keycache uses the default expiration time for cache entries as
// defined in constants. Also here, consider making this configurable.
const keyCache = new keycache.KeyCache(constants.KEYCACHE_DEFAULT_CACHE_SIZE, constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES);
//keyCache is now configureable using the config object
var keyCache = null;
function VerificationKey(config) {
this.getCallback = function() {
var tokenInfo = null;
this.getCallback = function(token) {
tokenInfo = token;
return this.loadKey.bind(this);

@@ -48,2 +46,3 @@ }

this.loadKey = function(accessToken, cb) {
var zid = tokenInfo.getPayload().zid;
if (!accessToken.kid || accessToken.kid == 'legacy-token-key' || !accessToken.jku) {

@@ -60,4 +59,8 @@ return cb(null, cleanUp(config.verificationkey));

if(!keyCache) {
keyCache = new keycache.KeyCache(config.keyCache.cacheSize, config.keyCache.expirationTime);
}
//try to get a key from KeyCache
keyCache.getKey(accessToken.jku, accessToken.kid, function(err, key) {
keyCache.getKey(accessToken.jku, accessToken.kid, zid, function(err, key) {
if (err) {

@@ -64,0 +67,0 @@ debugTrace('\n', err);

@@ -49,3 +49,4 @@ 'use strict';

var clientId;
var identityZone;
var subaccountid;
var zid;
var subdomain = null;

@@ -59,2 +60,4 @@ var origin = null;

var tokenInfo = null;
var isForeignMode = false;

@@ -114,2 +117,32 @@

}
if(!config.keyCache) {
config.keyCache = {
expirationTime: constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES,
cacheSize: constants.KEYCACHE_DEFAULT_CACHE_SIZE
};
debugTrace("Using KeyCache with default values %o", config.keyCache);
} else {
if(config.keyCache.expirationTime) {
//if it's provided it has to be a number bigger than zero
if(typeof config.keyCache.expirationTime !== 'number' || config.keyCache.expirationTime < constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES) {
debugError("keyCache.expirationTime has to be a Number with the value of at least " + constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES + ". taking default value.");
config.keyCache.expirationTime = constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES;
}
} else {
config.keyCache.expirationTime = constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES;
}
if(config.keyCache.cacheSize) {
//if it's provided it has to be a number bigger than zero
if(typeof config.keyCache.cacheSize !== 'number' || config.keyCache.cacheSize < constants.KEYCACHE_DEFAULT_CACHE_SIZE) {
debugError("keyCache.cacheSize has to be a Number of at least " + constants.KEYCACHE_DEFAULT_CACHE_SIZE + ". taking default value");
config.keyCache.cacheSize = constants.KEYCACHE_DEFAULT_CACHE_SIZE;
}
} else {
config.keyCache.cacheSize = constants.KEYCACHE_DEFAULT_CACHE_SIZE;
}
debugTrace("Using KeyCache with custom values %o", config.keyCache);
}
}

@@ -127,5 +160,9 @@

this.getSubaccountId = function () {
return identityZone;
return subaccountid;
};
this.getZoneId = function () {
return zid;
};
this.getSubdomain = function () {

@@ -200,3 +237,3 @@ return subdomain;

return samlToken ? samlToken : token;
return samlToken ? samlToken : this.getAppToken();
};

@@ -208,2 +245,6 @@

this.getTokenInfo = function() {
return tokenInfo;
}
this.getAttribute = function (name) {

@@ -265,3 +306,3 @@ if(!ifNotClientCredentialsToken('SecurityContext.getAttribute', true)) {

this.checkLocalScope = function (scope) {
if (!scope) {
if (!scope || !scopes) {
return false;

@@ -278,3 +319,3 @@ }

this.checkScope = function (scope) {
if (!scope) {
if (!scope || !scopes) {
return false;

@@ -299,8 +340,28 @@ }

function fillContext(encodedToken, decodedToken) {
function cleanUpUserAttributes(attr) {
if(!attr) {
return null;
}
var len = 0;
for(var n in attr) {
++len;
}
return len == 0 ? null : attr;
}
function fillContext(encodedToken, info) {
tokenInfo = info;
var decodedToken = tokenInfo.getPayload();
debugTrace('\nApplication received a token of grant type "' + decodedToken.grant_type + '".');
token = encodedToken;
scopes = decodedToken.scope;
identityZone = decodedToken.zid;
scopes = decodedToken.scope || [];
zid = decodedToken.zid;
subaccountid = decodedToken["ext_attr"] ? decodedToken["ext_attr"].subaccountid : zid;
if(!subaccountid) {
subaccountid = zid;
}
clientId = decodedToken.cid;

@@ -337,2 +398,4 @@ expirationDate = new Date(decodedToken.exp * 1000);

userAttributes = cleanUpUserAttributes(userAttributes);
if(userAttributes) {

@@ -368,5 +431,5 @@ debugTrace('\nObtained attributes: ' + JSON.stringify(userAttributes, null, 4));

//Now validate the tokens
jwtValidator.validateToken(encodedToken, function(err, decodedToken) {
jwtValidator.validateToken(encodedToken, function(err, tokenInfo) {
if(err) {
return cb(err);
return cb(err, null, tokenInfo);
}

@@ -377,5 +440,5 @@

//Token is now validated. So just fill local variables
fillContext.call(this, encodedToken, decodedToken);
fillContext.call(this, encodedToken, tokenInfo);
cb(null, this);
cb(null, this, tokenInfo);
}.bind(this));

@@ -382,0 +445,0 @@ };

@@ -1,1 +0,37 @@

{"bundleDependencies":false,"dependencies":{"debug":"4.1.1","jsonwebtoken":"^8.5.1","lru-cache":"5.1.1","request":"2.88.0","requestretry":"4.0.0","valid-url":"1.0.9"},"deprecated":false,"description":"XS Advanced Container Security API for node.js","devDependencies":{"@sap/xsenv":"^2.2.0","istanbul":"^0.4.5","jwt-decode":"^2.2.0","mocha":"^5.1.0","should":"^13.2.1"},"keywords":["xs"],"main":"./lib","name":"@sap/xssec","repository":{"type":"git","url":"ssh://git@github.wdf.sap.corp/xs2/node-xs2sec.git"},"scripts":{"prepareRelease":"npm prune --production","test":"make test"},"version":"3.0.3","license":"SEE LICENSE IN developer-license-3.1.txt"}
{
"name": "@sap/xssec",
"version": "3.0.5",
"description": "XS Advanced Container Security API for node.js",
"main": "./lib",
"scripts": {
"test": "make test",
"prepareRelease": "npm prune --production"
},
"repository": {
"type": "git",
"url": "ssh://git@github.wdf.sap.corp/xs2/node-xs2sec.git"
},
"files": [
"lib",
"package.json",
"README.md"
],
"keywords": [
"xs"
],
"devDependencies": {
"mocha": "^5.1.0",
"istanbul": "^0.4.5",
"should": "^13.2.1",
"jwt-decode": "^2.2.0",
"@sap/xsenv": "^2.2.0"
},
"dependencies": {
"debug": "4.1.1",
"jsonwebtoken": "^8.5.1",
"lru-cache": "5.1.1",
"request": "2.88.0",
"requestretry": "4.0.0",
"valid-url": "1.0.9"
}
}

@@ -57,3 +57,3 @@ @sap/xssec: XS Advanced Container Security API for node.js

```js
xssec.createSecurityContext(access_token, xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa, function(error, securityContext) {
xssec.createSecurityContext(access_token, xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa, function(error, securityContext, tokenInfo) {
if (error) {

@@ -64,2 +64,3 @@ console.log('Security Context creation failed');

console.log('Security Context created successfully');
console.log(tokenInfo.getPublicClaims());
});

@@ -74,3 +75,2 @@ ```

### Usage with Passport Strategy

@@ -106,2 +106,3 @@

* request.authInfo - the [Security Context](#api-description)
* request.tokenInfo - the [TokenInfo](doc/TokenInfo.md) object

@@ -111,3 +112,5 @@ If the `client_credentials` JWT token is present in the request and it is successfully verified, following objects are created:

* request.authInfo - the [Security Context](#api-description)
* request.tokenInfo - the [TokenInfo](doc/TokenInfo.md) object
#### Session

@@ -119,2 +122,54 @@

### Configure the cache of Verificationkeys
For token verification the library needs a so called `public key`. This key can be requested from the XSUAA server.
The library caches these keys to reduce the load to the XSUAA. (And for better performance!)
There are two values that are used to control the cache. The number of cache entries and an expiration time of each item. The latter is important to easily support key rotation scenarios and should not be too high.
:exclamation: **Normally you don't need to overwrite the default values!**
But in rare situations there is a need to change them.
**Conditions:**
The cacheSize value has to be >=1000
The expirationTime is measured in minutes and has to be a number >= 10
**Currently the default values are:**
```json
{
"cacheSize": 1000,
"expirationTime": 10
}
```
**Here are some codesnippets how to do this:**
```js
//just add the keyCache object into the config object and pass it to the constructor functions
var config = xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa;
config.keyCache = {
cacheSize: 5000,
expirationTime: 10
};
//if you only want to overwrite one value you can also:
var config = xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa;
config.keyCache = {
cacheSize: 10000
};
//then pass the config object to createSecurityConfig
xssec.createSecurityContext(access_token, config, function(error, securityContext, tokenInfo) {
...
});
//if you use passport:
var config = xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa;
config.keyCache = {
cacheSize: 10000
};
passport.use(new JWTStrategy(config));
...
```
### Test Usage without having an Access Token

@@ -146,2 +201,3 @@

}
var json = null;

@@ -153,3 +209,4 @@ try {

}
xssec.createSecurityContext(json.access_token, uaaService, function(error, securityContext) {
xssec.createSecurityContext(json.access_token, uaaService, function(error, securityContext, tokenInfo) {
if (error) {

@@ -160,2 +217,3 @@ console.log('Security Context creation failed');

console.log('Security Context created successfully');
console.log(tokenInfo.getPublicClaims());
});

@@ -202,4 +260,4 @@ }

* `access token` ... the access token as received from UAA in the "authorization Bearer" HTTP header
* `config` ... a structure with mandatory elements url, clientid and clientsecret
* `callback(error, securityContext)`
* `config` ... a structure with mandatory elements url, clientid and clientsecret or cache configuration
* `callback(error, securityContext, tokenInfo)`

@@ -260,5 +318,8 @@ ### getLogonName

### getTokenInfo
* returns the [TokenInfo](doc/TokenInfo.md) object, containing all information received from token.
### requestToken
Requests a token based on the given type. The type can be `constants.TYPE_USER_TOKEN` or `constants.TYPE_CLIENT_CREDENTIALS_TOKEN`. Prerequisite for the former is that the requesting client has `grant_type=user_token` and that the current user token includes the scope `uaa.user`.
Requests a token based on the given type. The type can be `constants.TYPE_USER_TOKEN` or `constants.TYPE_CLIENT_CREDENTIALS_TOKEN`.

@@ -265,0 +326,0 @@ * `serviceCredentials` ... the credentials of the service as JSON object. The attributes `clientid`, `clientsecret` and `url` (UAA) are mandatory. Note that the subdomain of the `url` will be adapted to the subdomain of the application token if necessary.

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