mirror of
https://github.com/bitwarden/browser
synced 2026-02-07 04:03:29 +00:00
Allow change-password-service to be invoked in non-Angular contexts.
This commit is contained in:
@@ -43,6 +43,7 @@ import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||
import { TaskService } from "@bitwarden/common/vault/tasks";
|
||||
import { SecurityTaskType } from "@bitwarden/common/vault/tasks/enums";
|
||||
import { SecurityTask } from "@bitwarden/common/vault/tasks/models/security-task";
|
||||
import { DefaultChangeLoginPasswordService } from "@bitwarden/vault";
|
||||
|
||||
import { openUnlockPopout } from "../../auth/popup/utils/auth-popout-window";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
@@ -58,7 +59,6 @@ import {
|
||||
import { CollectionView } from "../content/components/common-types";
|
||||
import { NotificationQueueMessageType } from "../enums/notification-queue-message-type.enum";
|
||||
import { AutofillService } from "../services/abstractions/autofill.service";
|
||||
import { TemporaryNotificationChangeLoginService } from "../services/notification-change-login-password.service";
|
||||
|
||||
import {
|
||||
AddChangePasswordQueueMessage,
|
||||
@@ -401,8 +401,10 @@ export default class NotificationBackground {
|
||||
): Promise<boolean> {
|
||||
const { activeUserId, securityTask, cipher } = message.data;
|
||||
const domain = Utils.getDomain(sender.tab.url);
|
||||
const passwordChangeUri =
|
||||
await new TemporaryNotificationChangeLoginService().getChangePasswordUrl(cipher);
|
||||
const passwordChangeUri = await DefaultChangeLoginPasswordService.create(
|
||||
{ nativeFetch: fetch },
|
||||
{ getClientType: () => "browser" },
|
||||
).getChangePasswordUrl(cipher);
|
||||
|
||||
const authStatus = await this.getAuthStatus();
|
||||
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
// Duplicates Default Change Login Password Service, for now
|
||||
// Since the former is an Angular injectable service, and we
|
||||
// need to use the function inside of lit components.
|
||||
// If primary service can be abstracted, that would be ideal.
|
||||
|
||||
export class TemporaryNotificationChangeLoginService {
|
||||
async getChangePasswordUrl(cipher: CipherView, fallback = false): Promise<string | null> {
|
||||
// Ensure we have a cipher with at least one URI
|
||||
if (cipher.type !== CipherType.Login || cipher.login == null || !cipher.login.hasUris) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Filter for valid URLs that are HTTP(S)
|
||||
const urls = cipher.login.uris
|
||||
.map((m) => Utils.getUrl(m.uri))
|
||||
.filter((m) => m != null && (m.protocol === "http:" || m.protocol === "https:"));
|
||||
|
||||
if (urls.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const url of urls) {
|
||||
const [reliable, wellKnownChangeUrl] = await Promise.all([
|
||||
this.hasReliableHttpStatusCode(url.origin),
|
||||
this.getWellKnownChangePasswordUrl(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;
|
||||
}
|
||||
}
|
||||
|
||||
// No reliable well-known URL found, fallback to the first URL
|
||||
|
||||
// @TODO reimplement option in original service to indicate if no URL found.
|
||||
// return urls[0].href; (originally)
|
||||
return fallback ? urls[0].href : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the server returns a non-200 status code for a resource that should not exist.
|
||||
* See https://w3c.github.io/webappsec-change-password-url/response-code-reliability.html#semantics
|
||||
* @param urlOrigin The origin of the URL to check
|
||||
*/
|
||||
private async hasReliableHttpStatusCode(urlOrigin: string): Promise<boolean> {
|
||||
try {
|
||||
const url = new URL(
|
||||
"./.well-known/resource-that-should-not-exist-whose-status-code-should-not-be-200",
|
||||
urlOrigin,
|
||||
);
|
||||
|
||||
const request = new Request(url, {
|
||||
method: "GET",
|
||||
mode: "same-origin",
|
||||
credentials: "omit",
|
||||
cache: "no-store",
|
||||
redirect: "follow",
|
||||
});
|
||||
|
||||
const response = await fetch(request);
|
||||
return !response.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a well-known change password URL for the given origin. Attempts to fetch the URL to ensure a valid response
|
||||
* is returned. Returns null if the request throws or the response is not 200 OK.
|
||||
* See https://w3c.github.io/webappsec-change-password-url/
|
||||
* @param urlOrigin The origin of the URL to check
|
||||
*/
|
||||
private async getWellKnownChangePasswordUrl(urlOrigin: string): Promise<string | null> {
|
||||
try {
|
||||
const url = new URL("./.well-known/change-password", urlOrigin);
|
||||
|
||||
const request = new Request(url, {
|
||||
method: "GET",
|
||||
mode: "same-origin",
|
||||
credentials: "omit",
|
||||
cache: "no-store",
|
||||
redirect: "follow",
|
||||
});
|
||||
|
||||
const response = await fetch(request);
|
||||
|
||||
return response.ok ? url.toString() : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,38 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
import { ChangeLoginPasswordService } from "../abstractions/change-login-password.service";
|
||||
|
||||
interface PartialApiService {
|
||||
nativeFetch(request: Request): Promise<Response>;
|
||||
}
|
||||
interface PartialPlatformUtilsService {
|
||||
getClientType(): string;
|
||||
}
|
||||
@Injectable()
|
||||
export class DefaultChangeLoginPasswordService implements ChangeLoginPasswordService {
|
||||
private apiService: PartialApiService;
|
||||
private platformUtilsService: PartialPlatformUtilsService;
|
||||
|
||||
// Constructor for Angular DI
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
) {}
|
||||
@Inject("IApiService") apiService: PartialApiService,
|
||||
@Inject("IPlatformUtilsService") platformUtilsService: PartialPlatformUtilsService,
|
||||
) {
|
||||
this.apiService = apiService;
|
||||
this.platformUtilsService = platformUtilsService;
|
||||
}
|
||||
|
||||
// Static factory method for standalone creation
|
||||
static create(apiService: PartialApiService, platformUtilsService: PartialPlatformUtilsService) {
|
||||
const instance = new DefaultChangeLoginPasswordService(null!, null!);
|
||||
instance.apiService = apiService;
|
||||
instance.platformUtilsService = platformUtilsService;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
|
||||
Reference in New Issue
Block a user