@sap/xssec
Advanced tools
Comparing version 3.3.4 to 3.3.5
# Change Log | ||
All notable changes to this project will be documented in this file. | ||
## 3.3.5 - 2023-09-28 | ||
- Support for app2service and app2app for IAS | ||
## 3.3.4 - 2023-09-06 | ||
@@ -5,0 +8,0 @@ - Fix IAS token exchange with X509 binding |
@@ -16,3 +16,3 @@ # TokenInfo | ||
There are several ways to get the TokenInfo. | ||
If an error occurs NO security context is created. But the [TokenInfo](doc/TokenInfo.md) object is available, so you can do some logging. | ||
If an error occurs NO security context is created. But the [TokenInfo](TokenInfo.md) object is available, so you can do some logging. | ||
@@ -125,2 +125,2 @@ #### If you create the SecurityContext on your own | ||
* provide not only verify, but also sign functionality | ||
* easy create tokens for unittesting | ||
* easy create tokens for unittesting |
@@ -58,3 +58,3 @@ 'use strict'; | ||
getIdentityJwks(url, app_tid, client_id) { | ||
getIdentityJwks(url, serviceCredentials, token) { | ||
if (!url) { | ||
@@ -64,8 +64,13 @@ throw new Error("Cannot get JWKS from empty URL."); | ||
const keyParts = [url, app_tid || ""]; | ||
const jwksParams = { | ||
app_tid : token.getAppTID(), | ||
azp: token.getAzp() | ||
} | ||
const keyParts = [url, serviceCredentials.clientid, jwksParams.app_tid, jwksParams.azp]; | ||
const replicaKey = this.createCacheKey(keyParts); | ||
if (!this.#replicas.has(replicaKey)) { | ||
const service = new IdentityService(url, app_tid, client_id); | ||
const jwksReplica = new JwksReplica(service, this.expirationTime, this.refreshPeriod); | ||
const service = new IdentityService(serviceCredentials).withCustomDomain(url); | ||
const jwksReplica = new JwksReplica(service, this.expirationTime, this.refreshPeriod).withParams(jwksParams); | ||
@@ -83,3 +88,6 @@ this.#replicas.set(replicaKey, jwksReplica); | ||
return parts.map(s => `${s.length}:${s}`).join(":"); | ||
return parts.map(s => { | ||
s ??= ""; | ||
return `${s.length}:${s}`; | ||
}).join(":"); | ||
} | ||
@@ -86,0 +94,0 @@ } |
@@ -15,3 +15,4 @@ 'use strict'; | ||
#oAuth2Service; // OAuth2 service from the JWKS is fetched | ||
#cache; // map that holds the JWKS mapped by kid | ||
#params; // params to use when retrieving the JWKS | ||
#keys; // map that holds the JWKS mapped by kid | ||
#jwksUpdate; // promise for ongoing update of JWKS or undefined | ||
@@ -21,2 +22,4 @@ #expirationTime; // default expiration time that will be used for new JWK cache entries | ||
get params() {return this.#params;} | ||
constructor(oAuth2Service, expirationTime = JwksReplica.DEFAULT_EXPIRATION_TIME, refreshPeriod = JwksReplica.DEFAULT_REFRESH_PERIOD) { | ||
@@ -36,3 +39,3 @@ if(!oAuth2Service) { | ||
this.#oAuth2Service = oAuth2Service; | ||
this.#cache = new Map(); | ||
this.#keys = new Map(); | ||
this.#expirationTime = expirationTime; | ||
@@ -46,5 +49,11 @@ this.#refreshPeriod = refreshPeriod; | ||
/** Configures the replica with parameters that are used when retrieving the JWKS from the server. */ | ||
withParams(params) { | ||
this.#params = params; | ||
return this; | ||
} | ||
put(kid, value, expirationTime = this.expirationTime) { | ||
const jwk = new Jwk(kid, value, expirationTime); | ||
this.#cache.set(kid, jwk); | ||
this.#keys.set(kid, jwk); | ||
return jwk; | ||
@@ -54,3 +63,3 @@ } | ||
async get(kid) { | ||
let jwk = this.#cache.get(kid); | ||
let jwk = this.#keys.get(kid); | ||
@@ -60,3 +69,3 @@ if (jwk === undefined || jwk.expired) { | ||
await this.updateJwks(); | ||
jwk = this.#cache.get(kid); | ||
jwk = this.#keys.get(kid); | ||
@@ -77,3 +86,8 @@ if (jwk === undefined) { | ||
} | ||
this.updateJwks(); | ||
try { | ||
this.updateJwks(); | ||
} catch(e) { | ||
debugError("Asynchronous JWKS refresh failed.", e); | ||
} | ||
} | ||
@@ -86,3 +100,3 @@ | ||
if (this.#jwksUpdate === undefined) { | ||
this.#jwksUpdate = this.doUpdateJwks(); | ||
this.#jwksUpdate = this.#doUpdateJwks(); | ||
} | ||
@@ -93,9 +107,9 @@ | ||
async doUpdateJwks() { | ||
async #doUpdateJwks() { | ||
debugTrace(`Fetching JWKS from service ${JSON.stringify(this.#oAuth2Service)}.`) | ||
try { | ||
const jwks = await this.#oAuth2Service.fetchJwks(); | ||
const jwks = await this.#oAuth2Service.fetchJwks(this.params); | ||
debugTrace(`JWKS received from service ${JSON.stringify(this.#oAuth2Service)}.`) | ||
this.#cache.clear(); | ||
this.#keys.clear(); | ||
for (let key of jwks) { | ||
@@ -108,2 +122,3 @@ this.put(key.kid, key.value); | ||
debugError(`Error fetching JWKS from service ${JSON.stringify(this.#oAuth2Service)}.`, e); | ||
throw e; | ||
} finally { | ||
@@ -110,0 +125,0 @@ this.#jwksUpdate = undefined; |
@@ -237,3 +237,3 @@ 'use strict'; | ||
module.exports.fetchOIDCKey = function (serviceCredentialsUrl, app_tid, attributes, cb) { | ||
module.exports.fetchOIDCKey = function (serviceCredentialsUrl, params = {}, cb) { | ||
var options = { | ||
@@ -250,14 +250,7 @@ method: 'GET', | ||
if (app_tid && attributes && attributes.clientId) { | ||
// these two headers must be present both at the same time or not at all to prevent bad requests | ||
options.headers["x-app_tid"] = app_tid; | ||
options.headers["x-client_id"] = attributes.clientId; | ||
} | ||
if (attributes) { | ||
if (attributes.correlationId) { | ||
options.headers[CORRELATIONID_HEADER] = attributes.correlationId; | ||
} | ||
} | ||
params.client_id && (options.headers["x-client_id"] = params.client_id); | ||
params.app_tid && (options.headers["x-app_tid"] = params.app_tid); | ||
params.azp && (options.headers["x-azp"] = params.azp); | ||
params.correlationId && (options.headers[CORRELATIONID_HEADER] = params.correlationId); | ||
return _requestToNetwork(".oidc-jkws", options, cb); | ||
@@ -264,0 +257,0 @@ } |
@@ -11,51 +11,52 @@ 'use strict'; | ||
class IdentityService { | ||
#url; | ||
#app_tid; // optional zone id | ||
#serviceCredentials; | ||
#domain; | ||
#oidcInfo; | ||
#clientId; | ||
get url() { return this.#url; } | ||
get app_tid() { return this.#app_tid; } | ||
get clientId() { return this.#clientId; } | ||
get serviceCredentials() { return this.#serviceCredentials; } | ||
get url() { return this.#domain; } | ||
constructor(url, app_tid, client_id) { | ||
if (url === undefined) { | ||
throw new Error("IdentityService requires a url."); | ||
constructor(serviceCredentials) { | ||
if (serviceCredentials == null) { | ||
throw new Error("IdentityService requires service credentials."); | ||
} | ||
this.#url = url; | ||
this.#app_tid = app_tid; | ||
this.#clientId = client_id; | ||
this.#serviceCredentials = serviceCredentials; | ||
this.#domain = serviceCredentials.url; | ||
} | ||
async fetchOidcInfo() { | ||
return new Promise((res, rej) => { | ||
try { | ||
requests.requestOpenIDConfiguration(this.url, { clientId: this.clientId }, (err, oidcInfo) => { | ||
if (err) { | ||
return rej(err); | ||
} | ||
return res(oidcInfo); | ||
}); | ||
} catch (e) { | ||
return rej(e); | ||
} | ||
}); | ||
/** Configures OIDC calls from this service to target the given domain instead of the url from the service credentials. */ | ||
withCustomDomain(domain) { | ||
this.#domain = domain; | ||
return this; | ||
} | ||
async fetchJwks() { | ||
return new Promise(async (res, rej) => { | ||
if (!this.#oidcInfo) { | ||
async fetchOidcInfo() { | ||
if (!this.#oidcInfo) { | ||
this.#oidcInfo = await new Promise((res, rej) => { | ||
try { | ||
this.#oidcInfo = await this.fetchOidcInfo(); | ||
requests.requestOpenIDConfiguration(this.url, { clientId: this.serviceCredentials.clientId }, (err, oidcInfo) => { | ||
if (err) { | ||
return rej(err); | ||
} | ||
return res(oidcInfo); | ||
}); | ||
} catch (e) { | ||
return rej(e); | ||
} | ||
} | ||
}); | ||
} | ||
const jwksEndpoint = this.#oidcInfo["jwks_uri"]; | ||
return this.#oidcInfo; | ||
} | ||
async fetchJwks(params = {}) { | ||
params.client_id = this.serviceCredentials.clientid; | ||
await this.fetchOidcInfo(); | ||
const jwksEndpoint = this.#oidcInfo["jwks_uri"]; | ||
return new Promise(async (res, rej) => { | ||
try { | ||
requests.fetchOIDCKey(jwksEndpoint, this.app_tid, { clientId: this.clientId }, (err, json) => { | ||
requests.fetchOIDCKey(jwksEndpoint, params, (err, json) => { | ||
if (err) { | ||
@@ -70,3 +71,3 @@ return rej(err); | ||
key.value = pem; | ||
} catch(e) { | ||
} catch (e) { | ||
debugError(`Could not calculate PEM for key with kid ${key.kid}: ${e}. IdentityService: (${JSON.stringify(this)})`) | ||
@@ -100,6 +101,3 @@ } | ||
return { | ||
...this, | ||
url: this.url, | ||
app_tid : this.app_tid, | ||
client_id: this.clientId | ||
domain: this.domain | ||
} | ||
@@ -106,0 +104,0 @@ } |
@@ -76,3 +76,3 @@ 'use strict'; | ||
let issuer = payload["ias_iss"] ? payload["ias_iss"] : payload.iss; | ||
if (issuer.indexOf('http') !== 0) { | ||
if (issuer && issuer.indexOf('http') !== 0) { | ||
issuer = "https://" + issuer; | ||
@@ -132,2 +132,6 @@ } | ||
this.getAzp = function() { | ||
return payload.azp; | ||
} | ||
this.isTokenIssuedByXSUAA = function () { | ||
@@ -158,2 +162,2 @@ return payload.ext_attr ? payload.ext_attr.enhancer === "XSUAA" : false; | ||
module.exports = TokenInfo; | ||
module.exports = TokenInfo; |
@@ -244,3 +244,3 @@ 'use strict'; | ||
try { | ||
const jwks = await jwksManager.getIdentityJwks(issuer, token.getAppTID(), serviceCredentials.clientid); | ||
const jwks = await jwksManager.getIdentityJwks(issuer, serviceCredentials, token); | ||
jwk = await jwks.get(header.kid); | ||
@@ -247,0 +247,0 @@ } catch(e) { |
{ | ||
"name": "@sap/xssec", | ||
"version": "3.3.4", | ||
"version": "3.3.5", | ||
"description": "XS Advanced Container Security API for node.js", | ||
@@ -24,6 +24,8 @@ "main": "./lib", | ||
"engines": { | ||
"node": ">=12.0.0" | ||
"node": ">=16.1.0" | ||
}, | ||
"devDependencies": { | ||
"@sap/xsenv": "^3.1.1", | ||
"convert": "^4.12.0", | ||
"eslint": "^8.50.0", | ||
"istanbul": "^0.4.5", | ||
@@ -34,4 +36,3 @@ "jwt-decode": "^3.1.2", | ||
"should": "^13.2.1", | ||
"sinon": "^14.0.0", | ||
"convert": "^4.12.0" | ||
"sinon": "^14.0.0" | ||
}, | ||
@@ -41,3 +42,3 @@ "dependencies": { | ||
"debug": "^4.3.2", | ||
"jsonwebtoken": "^9.0.0", | ||
"jsonwebtoken": "^9.0.2", | ||
"lru-cache": "^6.0.0", | ||
@@ -44,0 +45,0 @@ "node-rsa": "^1.1.1", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
150449
2331
9
Updatedjsonwebtoken@^9.0.2