mirror of
https://github.com/outline/outline.git
synced 2026-05-07 18:41:12 -05:00
3fd09ca0bf
* Fix OIDC well-known discovery for subdirectories - Fix URL construction in fetchOIDCConfiguration to properly handle issuer URLs with subdirectories - Replace incorrect use of new URL() constructor that was treating well-known path as absolute - Add proper path concatenation that preserves subdirectories in issuer URLs - Add comprehensive test cases for subdirectory scenarios - Fixes issue where https://auth.example.com/application/o/outline/ would incorrectly resolve to https://auth.example.com/.well-known/openid-configuration instead of https://auth.example.com/application/o/outline/.well-known/openid-configuration Fixes #9535 * Refactor to use wellKnownPath variable instead of hardcoded path - Use wellKnownPath.substring(1) to remove leading slash when appending to pathname - Eliminates duplication of the .well-known/openid-configuration path - Improves maintainability by using the existing variable consistently * Simplify logic by checking pathname does not end with slash - If pathname doesn't end with slash, append full wellKnownPath (with leading slash) - If pathname ends with slash, append wellKnownPath without leading slash - Eliminates need for substring() by using the slash logic more elegantly --------- Co-authored-by: codegen-sh[bot] <131295404+codegen-sh[bot]@users.noreply.github.com>
181 lines
5.7 KiB
TypeScript
181 lines
5.7 KiB
TypeScript
import fetchMock from "jest-fetch-mock";
|
|
import { fetchOIDCConfiguration } from "./oidcDiscovery";
|
|
|
|
beforeEach(() => {
|
|
fetchMock.resetMocks();
|
|
});
|
|
|
|
describe("fetchOIDCConfiguration", () => {
|
|
it("should fetch and parse OIDC configuration successfully", async () => {
|
|
const mockConfig = {
|
|
issuer: "https://example.com",
|
|
authorization_endpoint: "https://example.com/auth",
|
|
token_endpoint: "https://example.com/token",
|
|
userinfo_endpoint: "https://example.com/userinfo",
|
|
jwks_uri: "https://example.com/jwks",
|
|
end_session_endpoint: "https://example.com/logout",
|
|
scopes_supported: ["openid", "profile", "email"],
|
|
response_types_supported: ["code"],
|
|
grant_types_supported: ["authorization_code"],
|
|
};
|
|
|
|
fetchMock.mockResponseOnce(JSON.stringify(mockConfig));
|
|
|
|
const result = await fetchOIDCConfiguration("https://example.com");
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"https://example.com/.well-known/openid-configuration",
|
|
expect.objectContaining({
|
|
method: "GET",
|
|
headers: expect.objectContaining({
|
|
Accept: "application/json",
|
|
}),
|
|
})
|
|
);
|
|
|
|
expect(result).toEqual(mockConfig);
|
|
});
|
|
|
|
it("should handle issuer URL with trailing slash", async () => {
|
|
const mockConfig = {
|
|
issuer: "https://example.com/",
|
|
authorization_endpoint: "https://example.com/auth",
|
|
token_endpoint: "https://example.com/token",
|
|
userinfo_endpoint: "https://example.com/userinfo",
|
|
};
|
|
|
|
fetchMock.mockResponseOnce(JSON.stringify(mockConfig));
|
|
|
|
await fetchOIDCConfiguration("https://example.com/");
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"https://example.com/.well-known/openid-configuration",
|
|
expect.any(Object)
|
|
);
|
|
});
|
|
|
|
it("should throw error when HTTP request fails", async () => {
|
|
fetchMock.mockRejectOnce(new Error("Network error"));
|
|
|
|
await expect(fetchOIDCConfiguration("https://example.com")).rejects.toThrow(
|
|
"Network error"
|
|
);
|
|
});
|
|
|
|
it("should throw error when response is not ok", async () => {
|
|
fetchMock.mockResponseOnce("Not Found", { status: 404 });
|
|
|
|
await expect(fetchOIDCConfiguration("https://example.com")).rejects.toThrow(
|
|
"Failed to fetch OIDC configuration: 404 Not Found"
|
|
);
|
|
});
|
|
|
|
it("should throw error when required endpoints are missing", async () => {
|
|
const incompleteConfig = {
|
|
issuer: "https://example.com",
|
|
authorization_endpoint: "https://example.com/auth",
|
|
// Missing token_endpoint and userinfo_endpoint
|
|
};
|
|
|
|
fetchMock.mockResponseOnce(JSON.stringify(incompleteConfig));
|
|
|
|
await expect(fetchOIDCConfiguration("https://example.com")).rejects.toThrow(
|
|
"Missing token_endpoint in OIDC configuration"
|
|
);
|
|
});
|
|
|
|
it("should validate all required endpoints", async () => {
|
|
const configMissingAuth = {
|
|
issuer: "https://example.com",
|
|
token_endpoint: "https://example.com/token",
|
|
userinfo_endpoint: "https://example.com/userinfo",
|
|
};
|
|
|
|
fetchMock.mockResponseOnce(JSON.stringify(configMissingAuth));
|
|
|
|
await expect(fetchOIDCConfiguration("https://example.com")).rejects.toThrow(
|
|
"Missing authorization_endpoint in OIDC configuration"
|
|
);
|
|
});
|
|
|
|
it("should handle issuer URL with subdirectory path", async () => {
|
|
const mockConfig = {
|
|
issuer: "https://auth.example.com/application/o/outline/",
|
|
authorization_endpoint:
|
|
"https://auth.example.com/application/o/outline/auth",
|
|
token_endpoint: "https://auth.example.com/application/o/outline/token",
|
|
userinfo_endpoint:
|
|
"https://auth.example.com/application/o/outline/userinfo",
|
|
};
|
|
|
|
fetchMock.mockResponseOnce(JSON.stringify(mockConfig));
|
|
|
|
const result = await fetchOIDCConfiguration(
|
|
"https://auth.example.com/application/o/outline/"
|
|
);
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"https://auth.example.com/application/o/outline/.well-known/openid-configuration",
|
|
expect.objectContaining({
|
|
method: "GET",
|
|
headers: expect.objectContaining({
|
|
Accept: "application/json",
|
|
}),
|
|
})
|
|
);
|
|
|
|
expect(result).toEqual(mockConfig);
|
|
});
|
|
|
|
it("should handle issuer URL with subdirectory path without trailing slash", async () => {
|
|
const mockConfig = {
|
|
issuer: "https://auth.example.com/application/o/outline",
|
|
authorization_endpoint:
|
|
"https://auth.example.com/application/o/outline/auth",
|
|
token_endpoint: "https://auth.example.com/application/o/outline/token",
|
|
userinfo_endpoint:
|
|
"https://auth.example.com/application/o/outline/userinfo",
|
|
};
|
|
|
|
fetchMock.mockResponseOnce(JSON.stringify(mockConfig));
|
|
|
|
const result = await fetchOIDCConfiguration(
|
|
"https://auth.example.com/application/o/outline"
|
|
);
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"https://auth.example.com/application/o/outline/.well-known/openid-configuration",
|
|
expect.objectContaining({
|
|
method: "GET",
|
|
headers: expect.objectContaining({
|
|
Accept: "application/json",
|
|
}),
|
|
})
|
|
);
|
|
|
|
expect(result).toEqual(mockConfig);
|
|
});
|
|
|
|
it("should handle issuer URL that already contains well-known path", async () => {
|
|
const mockConfig = {
|
|
issuer: "https://example.com",
|
|
authorization_endpoint: "https://example.com/auth",
|
|
token_endpoint: "https://example.com/token",
|
|
userinfo_endpoint: "https://example.com/userinfo",
|
|
};
|
|
|
|
fetchMock.mockResponseOnce(JSON.stringify(mockConfig));
|
|
|
|
const result = await fetchOIDCConfiguration(
|
|
"https://example.com/.well-known/openid-configuration"
|
|
);
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"https://example.com/.well-known/openid-configuration",
|
|
expect.any(Object)
|
|
);
|
|
|
|
expect(result).toEqual(mockConfig);
|
|
});
|
|
});
|