diff --git a/src/MetadataService.js b/src/MetadataService.js index e5c8fad1..f45bdc25 100644 --- a/src/MetadataService.js +++ b/src/MetadataService.js @@ -15,6 +15,7 @@ export class MetadataService { this._settings = settings; this._jsonService = new JsonServiceCtor(['application/jwk-set+json']); + this._metadata_promise; } get metadataUrl() { @@ -43,24 +44,39 @@ export class MetadataService { } getMetadata() { - if (this._settings.metadata) { + // metadata was preloaded and no url was provided, so use the supplied data. + if (!this.metadataUrl && this._settings.metadata) { Log.debug("MetadataService.getMetadata: Returning metadata from settings"); return Promise.resolve(this._settings.metadata); } + // no url was provided and settings were not pre-loaded then throw an error. if (!this.metadataUrl) { Log.error("MetadataService.getMetadata: No authority or metadataUrl configured on settings"); return Promise.reject(new Error("No authority or metadataUrl configured on settings")); } + // if we've already started fetching metadata return the existing promise so we don't call it again. + if (this._metadata_promise) { + Log.debug("MetadataService.getMetadata: getting metadata from cache promise", this.metadataUrl); + return this._metadata_promise + } + Log.debug("MetadataService.getMetadata: getting metadata from", this.metadataUrl); - return this._jsonService.getJson(this.metadataUrl) + this._metadata_promise = this._jsonService.getJson(this.metadataUrl) .then(metadata => { Log.debug("MetadataService.getMetadata: json received"); - this._settings.metadata = metadata; - return metadata; + // overlay .well-known/openid-configuration over seeded setting. this allows consumers to set values + // like end_session_url for Auth0 when it is not available in the configuration endpoint. + // precedence was set on the assumption the issuers hosted configuration is always more accurate + // than what the developer seeded the client with. + if (!this._settings.metadata) this._settings.metadata = {} + Object.assign(this._settings.metadata, metadata); + return this._settings.metadata; }); + + return this._metadata_promise; } getIssuer() { diff --git a/test/unit/MetadataService.spec.js b/test/unit/MetadataService.spec.js index 561c4d0a..900a8469 100644 --- a/test/unit/MetadataService.spec.js +++ b/test/unit/MetadataService.spec.js @@ -93,24 +93,26 @@ describe("MetadataService", function() { it("should return metadata from json call", function(done) { settings.metadataUrl = "http://sts/metadata"; - stubJsonService.result = Promise.resolve("test"); + const expected = { test: "test" }; + stubJsonService.result = Promise.resolve(expected); let p = subject.getMetadata(); p.then(result => { - result.should.equal("test"); + result.should.deep.equal(expected); done(); }); }); it("should cache metadata from json call", function(done) { settings.metadataUrl = "http://sts/metadata"; - stubJsonService.result = Promise.resolve("test"); + const expected = { test: "test" }; + stubJsonService.result = Promise.resolve(expected); let p = subject.getMetadata(); p.then(result => { - settings.metadata.should.equal("test"); + settings.metadata.should.deep.equal(expected); done(); }); }); @@ -127,6 +129,27 @@ describe("MetadataService", function() { }); }); + it("should return merge openid-configuration from json call and injected metadata", function(done) { + settings.metadataUrl = "http://sts/metadata"; + settings.metadata = { + property1: "injected", + property2: "injected" + } + const response = { property2: "merged" }; + const expected = { + property1: "injected", + property2: "merged" + } + stubJsonService.result = Promise.resolve(response); + + let p = subject.getMetadata(); + + p.then(result => { + result.should.deep.equal(expected); + done(); + }); + }); + }); describe("_getMetadataProperty", function() {