Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OIDC: accept homeserver URL or domain in .well-known discovery functions #3426

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions spec/unit/autodiscovery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,26 @@ describe("AutoDiscovery", function () {
]);
});

it("should handle a homeserver url that includes a protocol", async function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "http://example.org/.well-known/matrix/client").respond(200, {});
AutoDiscovery.findClientConfig("http://example.org");
await httpBackend.flushAllExpected();

// .well-known fetched using protocol from provided domain
httpBackend.verifyNoOutstandingRequests();
});

it("should handle a homeserver domain that does not include a protocol", async function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "https://example.org/.well-known/matrix/client").respond(200, {});
AutoDiscovery.findClientConfig("example.org");
await httpBackend.flushAllExpected();

// https protocol added to domain for request
httpBackend.verifyNoOutstandingRequests();
});

it("should return PROMPT when .well-known 404s", function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/.well-known/matrix/client").respond(404, {});
Expand Down Expand Up @@ -794,4 +814,36 @@ describe("AutoDiscovery", function () {
}),
]);
});

describe("getRawClientConfig()", () => {
it("should throw when homeserver is undefined", () => {
expect(AutoDiscovery.getRawClientConfig(undefined)).rejects.toThrow(
"'homeserver' must be a string of non-zero length",
);
});
it("should throw when homeserver is not a a string", () => {
expect(AutoDiscovery.getRawClientConfig({ url: "test.com" } as any)).rejects.toThrow(
"'homeserver' must be a string of non-zero length",
);
});
it("should fetch wellknown when homeserver includes protocol", async () => {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "http://example.org/.well-known/matrix/client").respond(200, "<html>", true);

AutoDiscovery.getRawClientConfig("http://example.org");
await httpBackend.flushAllExpected();

httpBackend.verifyNoOutstandingRequests();
});

it("should fetch wellknown with https when homeserver does not include protocol", async () => {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "https://example.org/.well-known/matrix/client").respond(200, "<html>", true);

AutoDiscovery.getRawClientConfig("example.org");
await httpBackend.flushAllExpected();

httpBackend.verifyNoOutstandingRequests();
});
});
});
37 changes: 26 additions & 11 deletions src/autodiscovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ export interface ClientConfig extends Omit<IClientWellKnown, "m.homeserver" | "m
"m.identity_server": WellKnownConfig;
}

/**
* Normalizes homeserver domain or URL to a valid URL origin
* @param homeserver - homeserver domain or url
* @returns homeserver url with protocol
*/
const normalizeHomeserver = (homeserver: string): string => {
try {
return new URL(homeserver).origin;
} catch (err) {
return new URL(`https://${homeserver}`).origin;
}
};

/**
* Utilities for automatically discovery resources, such as homeservers
* for users to log in to.
Expand Down Expand Up @@ -266,15 +279,15 @@ export class AutoDiscovery {
* and identity server URL the client would want. Additional details
* may also be discovered, and will be transparently included in the
* response object unaltered.
* @param domain - The homeserver domain to perform discovery
* on. For example, "matrix.org".
* @param homeserver - The homeserver domain or url to perform discovery
* on. For example, "matrix.org", "https://matrix.org"
* @returns Promise which resolves to the discovered
* configuration, which may include error states. Rejects on unexpected
* failure, not when discovery fails.
*/
public static async findClientConfig(domain: string): Promise<ClientConfig> {
if (!domain || typeof domain !== "string" || domain.length === 0) {
throw new Error("'domain' must be a string of non-zero length");
public static async findClientConfig(homeserver: string): Promise<ClientConfig> {
if (!homeserver || typeof homeserver !== "string" || homeserver.length === 0) {
throw new Error("'homeserver' must be a string of non-zero length");
}

// We use a .well-known lookup for all cases. According to the spec, we
Expand Down Expand Up @@ -308,7 +321,8 @@ export class AutoDiscovery {

// Step 1: Actually request the .well-known JSON file and make sure it
// at least has a homeserver definition.
const wellknown = await this.fetchWellKnownObject(`https://${domain}/.well-known/matrix/client`);
const homeserverUrl = normalizeHomeserver(homeserver);
const wellknown = await this.fetchWellKnownObject(`${homeserverUrl}/.well-known/matrix/client`);
if (!wellknown || wellknown.action !== AutoDiscoveryAction.SUCCESS) {
logger.error("No response or error when parsing .well-known");
if (wellknown.reason) logger.error(wellknown.reason);
Expand All @@ -334,16 +348,17 @@ export class AutoDiscovery {
* Gets the raw discovery client configuration for the given domain name.
* Should only be used if there's no validation to be done on the resulting
* object, otherwise use findClientConfig().
* @param domain - The domain to get the client config for.
* @param homeserver- The homeserver domain or url to get client config for. For example, "matrix.org", "https://matrix.org"
* @returns Promise which resolves to the domain's client config. Can
* be an empty object.
*/
public static async getRawClientConfig(domain?: string): Promise<IClientWellKnown> {
if (!domain || typeof domain !== "string" || domain.length === 0) {
throw new Error("'domain' must be a string of non-zero length");
public static async getRawClientConfig(homeserver?: string): Promise<IClientWellKnown> {
if (!homeserver || typeof homeserver !== "string" || homeserver.length === 0) {
throw new Error("'homeserver' must be a string of non-zero length");
}

const response = await this.fetchWellKnownObject(`https://${domain}/.well-known/matrix/client`);
const homeserverUrl = normalizeHomeserver(homeserver);
const response = await this.fetchWellKnownObject(`${homeserverUrl}/.well-known/matrix/client`);
if (!response) return {};
return response.raw ?? {};
}
Expand Down
Loading