@sap/xssec
Advanced tools
Comparing version 3.5.0 to 3.6.0
# Change Log | ||
All notable changes to this project will be documented in this file. | ||
## 3.6.0 - 2023-11-24 | ||
- adapt optimized IAS server API | ||
## 3.5.0 - 2023-11-14 | ||
@@ -5,0 +8,0 @@ - update dependencies (e.g. axios 0 -> 1) |
@@ -65,5 +65,5 @@ 'use strict'; | ||
getIdentityJwks(url, serviceCredentials, token, attributes = {}) { | ||
if (!url) { | ||
throw new Error("Cannot get JWKS from empty URL."); | ||
getIdentityJwks(issuerDomain, serviceCredentials, token, attributes = {}) { | ||
if (!issuerDomain) { | ||
throw new Error("Cannot get JWKS from empty domain."); | ||
} | ||
@@ -79,3 +79,3 @@ | ||
if (!attributes.disableCache) { | ||
const keyParts = {url, ...jwksParams}; | ||
const keyParts = {domain: issuerDomain, ...jwksParams}; | ||
replicaKey = this.createCacheKey(keyParts); | ||
@@ -87,3 +87,3 @@ | ||
if (!jwksReplica) { | ||
const service = new IdentityService(serviceCredentials).withCustomDomain(url); | ||
const service = new IdentityService(serviceCredentials).withCustomDomain(issuerDomain); | ||
jwksReplica = new JwksReplica(service, this.expirationTime, this.refreshPeriod).withParams(jwksParams); | ||
@@ -90,0 +90,0 @@ |
@@ -10,9 +10,11 @@ 'use strict'; | ||
const PROTOCOL = "https://"; | ||
class IdentityService { | ||
#serviceCredentials; | ||
#domain; | ||
#url; | ||
#oidcInfo; | ||
get serviceCredentials() { return this.#serviceCredentials; } | ||
get url() { return this.#domain; } | ||
get url() { return this.#url; } | ||
@@ -25,3 +27,3 @@ constructor(serviceCredentials) { | ||
this.#serviceCredentials = serviceCredentials; | ||
this.#domain = serviceCredentials.url; | ||
this.#url = serviceCredentials.url; | ||
} | ||
@@ -31,3 +33,3 @@ | ||
withCustomDomain(domain) { | ||
this.#domain = domain; | ||
this.#url = domain.startsWith(PROTOCOL) ? domain : `${PROTOCOL}${domain}`; | ||
return this; | ||
@@ -105,3 +107,3 @@ } | ||
return { | ||
domain: this.url | ||
url: this.url | ||
} | ||
@@ -108,0 +110,0 @@ } |
@@ -162,37 +162,2 @@ 'use strict'; | ||
function checkIssuer(issuer, domains) { | ||
if (!issuer) { | ||
throw "issuer is empty"; | ||
} | ||
//make sure we have a protocol at the beginning of the url | ||
if (issuer.indexOf('http') !== 0) { | ||
issuer = "https://" + issuer; | ||
} | ||
const myURL = new url.URL(issuer); | ||
if (myURL.protocol !== 'https:') { | ||
if (myURL.hostname !== 'localhost') { | ||
throw "Issuer has wrong protocol (" + myURL.protocol + ")"; | ||
} | ||
} | ||
if (myURL.hash) { | ||
throw "Issuer has unallowed hash value (" + myURL.hash + ")"; | ||
} | ||
if (myURL.search) { | ||
throw "Issuer has unallowed query value (" + myURL.search + ")"; | ||
} | ||
for (let i = 0; i < domains.length; ++i) { | ||
if (myURL.hostname.endsWith(domains[i])) { | ||
return; | ||
} | ||
} | ||
throw "Issuer not found in domain list " + domains; | ||
} | ||
this.validateToken = function (accessToken, cb) { | ||
@@ -207,3 +172,3 @@ function returnError(code, errorString) { | ||
//make sure we have at least an array of 1 domain | ||
const domains = Array.isArray(serviceCredentials.domains) ? serviceCredentials.domains : [serviceCredentials.domain]; | ||
const trustedDomains = Array.isArray(serviceCredentials.domains) ? serviceCredentials.domains : [serviceCredentials.domain]; | ||
const token = new TokenInfo(accessToken); | ||
@@ -213,6 +178,7 @@ | ||
const issuer = token.getIssuer(); | ||
let issuerDomain; | ||
try { | ||
checkIssuer(issuer, domains) | ||
issuerDomain = this.getIssuerDomain(issuer, trustedDomains); | ||
} catch (e) { | ||
return returnError(401, "Issuer validation failed (iss=" + issuer + ") message=" + e); | ||
return returnError(401, `Issuer validation failed for issuer ${issuer}, message=${e}`); | ||
} | ||
@@ -235,3 +201,3 @@ | ||
try { | ||
const jwks = await jwksManager.getIdentityJwks(issuer, serviceCredentials, token, attributes); | ||
const jwks = await jwksManager.getIdentityJwks(issuerDomain, serviceCredentials, token, attributes); | ||
jwk = await jwks.get(header.kid); | ||
@@ -268,2 +234,42 @@ } catch(e) { | ||
} | ||
/** | ||
* Returns the issuer domain based on the issuer of the token but validates it against a list of trusted domains. | ||
* @param {string} issuer issuer from token | ||
* @param {Array<string>} trustedDomains a list of trusted domains | ||
* @returns domain of issuer if is either a trusted domain or a subdomain of a trusted domain | ||
* @throws Error if issuer is empty, not trusted or not a valid URL | ||
*/ | ||
this.getIssuerDomain = function (issuer, trustedDomains = []) { | ||
if(!issuer) { | ||
throw new Error("No issuer found."); | ||
} | ||
const httpsScheme = "https://"; | ||
const issuerUrl = issuer.startsWith(httpsScheme) ? issuer : `${httpsScheme}${issuer}`; | ||
try { | ||
new URL(issuerUrl); | ||
} catch(e) { | ||
throw new Error("Issuer was not a valid URL suitable for https.", e); | ||
} | ||
const issuerDomain = issuerUrl.substring(httpsScheme.length); | ||
for(let d of trustedDomains) { | ||
const validSubdomainPattern = `^[a-zA-Z0-9-]{1,63}\\.${escapeStringForRegex(d)}$`; // a string that ends with .<trustedDomain> and contains 1-63 letters, digits or '-' before that for the subdomain | ||
if(issuerDomain === d || issuerDomain.match(new RegExp(validSubdomainPattern))) { | ||
return issuerDomain; | ||
} | ||
} | ||
throw new Error("Issuer domain was not a trusted domain or a subdomain of a trusted domain.") | ||
} | ||
/** | ||
* Escapes Regex special characters in the given string, so that the string can be used for a literal match inside a Regex. | ||
* Regex.escape is only a proposal at the time of writing. | ||
* The source of this code is https://github.com/tc39/proposal-regex-escaping/blob/main/polyfill.js | ||
*/ | ||
function escapeStringForRegex(s) { | ||
return String(s).replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'); | ||
} | ||
} | ||
@@ -270,0 +276,0 @@ |
{ | ||
"name": "@sap/xssec", | ||
"version": "3.5.0", | ||
"version": "3.6.0", | ||
"description": "XS Advanced Container Security API for node.js", | ||
@@ -5,0 +5,0 @@ "main": "./lib", |
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
158929
2465