1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-18650] [PM-18640] Fix change login password service defects (#13596)

* [PM-18650] Ensure http url is returned if well-known cannot be confirmed

* [PM-18650] Modify getChangePasswordUrl logic to check each cipher URL until a valid well-known url is found
This commit is contained in:
Shane Melton
2025-03-05 11:49:54 -08:00
committed by GitHub
parent e058953e7d
commit a7643ebab0
3 changed files with 53 additions and 15 deletions

View File

@@ -4,6 +4,8 @@ export abstract class ChangeLoginPasswordService {
/**
* Attempts to find a well-known change password URL for the given cipher. Only works for Login ciphers with at
* least one http/https URL. If no well-known change password URL is found, the first URL is returned.
* Checks each URL until the first reliable one well-known URL is found, otherwise returns the first URL.
*
* Non-Login ciphers and Logins with no valid http/https URLs return null.
*/
abstract getChangePasswordUrl(cipher: CipherView): Promise<string | null>;

View File

@@ -131,13 +131,13 @@ describe("DefaultChangeLoginPasswordService", () => {
const cipher = {
type: CipherType.Login,
login: Object.assign(new LoginView(), {
uris: [{ uri: "https://example.com" }],
uris: [{ uri: "https://example.com/" }],
}),
} as CipherView;
const url = await service.getChangePasswordUrl(cipher);
expect(url).toBe("https://example.com");
expect(url).toBe("https://example.com/");
});
it("should return the original URI when the well-known URL is not found", async () => {
@@ -146,12 +146,42 @@ describe("DefaultChangeLoginPasswordService", () => {
const cipher = {
type: CipherType.Login,
login: Object.assign(new LoginView(), {
uris: [{ uri: "https://example.com" }],
uris: [{ uri: "https://example.com/" }],
}),
} as CipherView;
const url = await service.getChangePasswordUrl(cipher);
expect(url).toBe("https://example.com");
expect(url).toBe("https://example.com/");
});
it("should try the next URI if the first one fails", async () => {
mockApiService.nativeFetch.mockImplementation((request) => {
if (
request.url.endsWith("resource-that-should-not-exist-whose-status-code-should-not-be-200")
) {
return Promise.resolve(mockShouldNotExistResponse);
}
if (request.url.endsWith(".well-known/change-password")) {
if (request.url.includes("working.com")) {
return Promise.resolve(mockWellKnownResponse);
}
return Promise.resolve(new Response("Not Found", { status: 404 }));
}
throw new Error("Unexpected request");
});
const cipher = {
type: CipherType.Login,
login: Object.assign(new LoginView(), {
uris: [{ uri: "https://no-wellknown.com/" }, { uri: "https://working.com/" }],
}),
} as CipherView;
const url = await service.getChangePasswordUrl(cipher);
expect(url).toBe("https://working.com/.well-known/change-password");
});
});

View File

@@ -20,25 +20,31 @@ export class DefaultChangeLoginPasswordService implements ChangeLoginPasswordSer
return null;
}
// Find the first valid URL that is an HTTP or HTTPS URL
const url = cipher.login.uris
// Filter for valid URLs that are HTTP(S)
const urls = cipher.login.uris
.map((m) => Utils.getUrl(m.uri))
.find((m) => m != null && (m.protocol === "http:" || m.protocol === "https:"));
.filter((m) => m != null && (m.protocol === "http:" || m.protocol === "https:"));
if (url == null) {
if (urls.length === 0) {
return null;
}
const [reliable, wellKnownChangeUrl] = await Promise.all([
this.hasReliableHttpStatusCode(url.origin),
this.getWellKnownChangePasswordUrl(url.origin),
]);
for (const url of urls) {
const [reliable, wellKnownChangeUrl] = await Promise.all([
this.hasReliableHttpStatusCode(url.origin),
this.getWellKnownChangePasswordUrl(url.origin),
]);
if (!reliable || wellKnownChangeUrl == null) {
return url.origin;
// Some servers return a 200 OK for a resource that should not exist
// Which means we cannot trust the well-known URL is valid, so we skip it
// to avoid potentially sending users to a 404 page
if (reliable && wellKnownChangeUrl != null) {
return wellKnownChangeUrl;
}
}
return wellKnownChangeUrl;
// No reliable well-known URL found, fallback to the first URL
return urls[0].href;
}
/**