775ac7b58c
you must login with an BTP account in order to see the app
109 lines
No EOL
3.4 KiB
JavaScript
109 lines
No EOL
3.4 KiB
JavaScript
'use strict';
|
|
|
|
const debug = require('debug');
|
|
const debugError = debug('xssec:JwksReplica');
|
|
debugError.log = console.error.bind(console);
|
|
|
|
const requests = require('../requests');
|
|
const nodeRSA = require('node-rsa');
|
|
|
|
const PROTOCOL = "https://";
|
|
|
|
class IdentityService {
|
|
#serviceCredentials;
|
|
#url;
|
|
#oidcInfo;
|
|
|
|
get serviceCredentials() { return this.#serviceCredentials; }
|
|
get url() { return this.#url; }
|
|
|
|
constructor(serviceCredentials) {
|
|
if (serviceCredentials == null) {
|
|
throw new Error("IdentityService requires service credentials.");
|
|
}
|
|
|
|
this.#serviceCredentials = serviceCredentials;
|
|
this.#url = serviceCredentials.url;
|
|
}
|
|
|
|
/** Configures OIDC calls from this service to target the given domain instead of the url from the service credentials. */
|
|
withCustomDomain(domain) {
|
|
this.#url = domain.startsWith(PROTOCOL) ? domain : `${PROTOCOL}${domain}`;
|
|
return this;
|
|
}
|
|
|
|
async fetchOidcInfo() {
|
|
if (!this.#oidcInfo) {
|
|
this.#oidcInfo = await new Promise((res, rej) => {
|
|
try {
|
|
requests.requestOpenIDConfiguration(this.url, { clientId: this.serviceCredentials.clientId }, (err, oidcInfo) => {
|
|
if (err) {
|
|
return rej(err);
|
|
}
|
|
|
|
return res(oidcInfo);
|
|
});
|
|
} catch (e) {
|
|
return rej(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
return this.#oidcInfo;
|
|
}
|
|
|
|
async fetchJwks(params = {}) {
|
|
if(params.client_id && params.client_id !== this.serviceCredentials.clientid) {
|
|
return Promise.reject("Invalid state: IdentityService#fetchJwks called with client_id value that is different from the client_id of the IdentityService object.");
|
|
}
|
|
|
|
await this.fetchOidcInfo();
|
|
const jwksEndpoint = this.#oidcInfo["jwks_uri"];
|
|
|
|
return new Promise((res, rej) => {
|
|
try {
|
|
requests.fetchOIDCKey(jwksEndpoint, params, (err, json) => {
|
|
if (err) {
|
|
return rej(err);
|
|
}
|
|
|
|
const jwks = json.keys
|
|
.map(key => {
|
|
try {
|
|
const pem = this.createPem(key);
|
|
key.value = pem;
|
|
} catch (e) {
|
|
debugError(`Could not calculate PEM for key with kid ${key.kid}: ${e}. IdentityService: (${JSON.stringify(this)})`)
|
|
}
|
|
|
|
return key;
|
|
})
|
|
.filter(key => key.value !== undefined);
|
|
return res(jwks);
|
|
});
|
|
} catch (e) {
|
|
return rej(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
createPem(key) {
|
|
if (key.kty !== "RSA") {
|
|
throw new Error("KTY '" + key.kty + "' not supported");
|
|
}
|
|
|
|
const modulus = Buffer.from(key.n, 'base64');
|
|
const exponent = Buffer.from(key.e, 'base64');
|
|
|
|
const pubKey = new nodeRSA().importKey({ n: modulus, e: exponent }, 'components-public');
|
|
return pubKey.exportKey('pkcs8-public-pem');
|
|
}
|
|
|
|
toJSON() {
|
|
return {
|
|
url: this.url
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = IdentityService; |