mirror of
https://github.com/bitwarden/browser
synced 2025-12-22 19:23:52 +00:00
[PM-12047] Remove usage of ActiveUserState from cipher.service (#12814)
* Cipher service web changes * Updated browser client to pass user id to cipher service observable changes * Cli changes * desktop changes * Fixed test * Libs changes * Fixed merge conflicts * Fixed merge conflicts * removed duplicate reference fixed conflict * Fixed test * Fixed test * Fixed test * Fixed desturcturing issue on failed to decrypt ciphers cipher service * Updated abstraction to use method syntax * Fixed conflicts * Fixed test on add edit v2 Passed active userId to delete function * Used getUserId utility function * made vault changes * made suggestion changes * made suggestion changes * made suggestion changes * Replace getUserId function calls with pipe operator syntax for better consistency * fixed merge conflicts * revert mistake made of usinf account activity during merge conflict fix * fixed conflicts * fixed tests
This commit is contained in:
@@ -825,6 +825,7 @@ describe("NotificationBackground", () => {
|
||||
queueMessage.newPassword,
|
||||
message.edit,
|
||||
sender.tab,
|
||||
"testId",
|
||||
);
|
||||
expect(updateWithServerSpy).toHaveBeenCalled();
|
||||
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
||||
@@ -862,6 +863,7 @@ describe("NotificationBackground", () => {
|
||||
queueMessage.password,
|
||||
message.edit,
|
||||
sender.tab,
|
||||
"testId",
|
||||
);
|
||||
expect(editItemSpy).not.toHaveBeenCalled();
|
||||
expect(createWithServerSpy).not.toHaveBeenCalled();
|
||||
@@ -895,6 +897,7 @@ describe("NotificationBackground", () => {
|
||||
queueMessage.newPassword,
|
||||
message.edit,
|
||||
sender.tab,
|
||||
"testId",
|
||||
);
|
||||
expect(editItemSpy).toHaveBeenCalled();
|
||||
expect(updateWithServerSpy).not.toHaveBeenCalled();
|
||||
@@ -904,10 +907,13 @@ describe("NotificationBackground", () => {
|
||||
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
||||
command: "editedCipher",
|
||||
});
|
||||
expect(setAddEditCipherInfoSpy).toHaveBeenCalledWith({
|
||||
cipher: cipherView,
|
||||
collectionIds: cipherView.collectionIds,
|
||||
});
|
||||
expect(setAddEditCipherInfoSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
cipher: cipherView,
|
||||
collectionIds: cipherView.collectionIds,
|
||||
},
|
||||
"testId",
|
||||
);
|
||||
expect(openAddEditVaultItemPopoutSpy).toHaveBeenCalledWith(sender.tab, {
|
||||
cipherId: cipherView.id,
|
||||
});
|
||||
@@ -945,7 +951,7 @@ describe("NotificationBackground", () => {
|
||||
queueMessage,
|
||||
message.folder,
|
||||
);
|
||||
expect(editItemSpy).toHaveBeenCalledWith(cipherView, sender.tab);
|
||||
expect(editItemSpy).toHaveBeenCalledWith(cipherView, "testId", sender.tab);
|
||||
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
||||
command: "closeNotificationBar",
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import {
|
||||
ExtensionCommand,
|
||||
ExtensionCommandType,
|
||||
@@ -22,6 +23,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
@@ -87,8 +89,6 @@ export default class NotificationBackground {
|
||||
bgGetDecryptedCiphers: () => this.getNotificationCipherData(),
|
||||
};
|
||||
|
||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
||||
|
||||
constructor(
|
||||
private autofillService: AutofillService,
|
||||
private cipherService: CipherService,
|
||||
@@ -151,7 +151,13 @@ export default class NotificationBackground {
|
||||
firstValueFrom(this.environmentService.environment$),
|
||||
]);
|
||||
const iconsServerUrl = env.getIconsUrl();
|
||||
const decryptedCiphers = await this.cipherService.getAllDecryptedForUrl(currentTab.url);
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
const decryptedCiphers = await this.cipherService.getAllDecryptedForUrl(
|
||||
currentTab.url,
|
||||
activeUserId,
|
||||
);
|
||||
|
||||
return decryptedCiphers.map((view) => {
|
||||
const { id, name, reprompt, favorite, login } = view;
|
||||
@@ -304,7 +310,14 @@ export default class NotificationBackground {
|
||||
return;
|
||||
}
|
||||
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url);
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
if (activeUserId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url, activeUserId);
|
||||
const usernameMatches = ciphers.filter(
|
||||
(c) => c.login.username != null && c.login.username.toLowerCase() === normalizedUsername,
|
||||
);
|
||||
@@ -382,7 +395,14 @@ export default class NotificationBackground {
|
||||
}
|
||||
|
||||
let id: string = null;
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url);
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
if (activeUserId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url, activeUserId);
|
||||
if (changeData.currentPassword != null) {
|
||||
const passwordMatches = ciphers.filter(
|
||||
(c) => c.login.password === changeData.currentPassword,
|
||||
@@ -535,37 +555,42 @@ export default class NotificationBackground {
|
||||
|
||||
this.notificationQueue.splice(i, 1);
|
||||
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
|
||||
if (queueMessage.type === NotificationQueueMessageType.ChangePassword) {
|
||||
const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId);
|
||||
await this.updatePassword(cipherView, queueMessage.newPassword, edit, tab);
|
||||
const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId, activeUserId);
|
||||
await this.updatePassword(cipherView, queueMessage.newPassword, edit, tab, activeUserId);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the vault was locked, check if a cipher needs updating instead of creating a new one
|
||||
if (queueMessage.wasVaultLocked) {
|
||||
const allCiphers = await this.cipherService.getAllDecryptedForUrl(queueMessage.uri);
|
||||
const allCiphers = await this.cipherService.getAllDecryptedForUrl(
|
||||
queueMessage.uri,
|
||||
activeUserId,
|
||||
);
|
||||
const existingCipher = allCiphers.find(
|
||||
(c) =>
|
||||
c.login.username != null && c.login.username.toLowerCase() === queueMessage.username,
|
||||
);
|
||||
|
||||
if (existingCipher != null) {
|
||||
await this.updatePassword(existingCipher, queueMessage.password, edit, tab);
|
||||
await this.updatePassword(existingCipher, queueMessage.password, edit, tab, activeUserId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
folderId = (await this.folderExists(folderId)) ? folderId : null;
|
||||
folderId = (await this.folderExists(folderId, activeUserId)) ? folderId : null;
|
||||
const newCipher = this.convertAddLoginQueueMessageToCipherView(queueMessage, folderId);
|
||||
|
||||
if (edit) {
|
||||
await this.editItem(newCipher, tab);
|
||||
await this.editItem(newCipher, activeUserId, tab);
|
||||
await BrowserApi.tabSendMessage(tab, { command: "closeNotificationBar" });
|
||||
return;
|
||||
}
|
||||
|
||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
||||
|
||||
const cipher = await this.cipherService.encrypt(newCipher, activeUserId);
|
||||
try {
|
||||
await this.cipherService.createWithServer(cipher);
|
||||
@@ -588,24 +613,25 @@ export default class NotificationBackground {
|
||||
* @param newPassword - The new password to update the cipher with
|
||||
* @param edit - Identifies if the cipher should be edited or simply updated
|
||||
* @param tab - The tab that the message was sent from
|
||||
* @param userId - The active account user ID
|
||||
*/
|
||||
private async updatePassword(
|
||||
cipherView: CipherView,
|
||||
newPassword: string,
|
||||
edit: boolean,
|
||||
tab: chrome.tabs.Tab,
|
||||
userId: UserId,
|
||||
) {
|
||||
cipherView.login.password = newPassword;
|
||||
|
||||
if (edit) {
|
||||
await this.editItem(cipherView, tab);
|
||||
await this.editItem(cipherView, userId, tab);
|
||||
await BrowserApi.tabSendMessage(tab, { command: "closeNotificationBar" });
|
||||
await BrowserApi.tabSendMessage(tab, { command: "editedCipher" });
|
||||
return;
|
||||
}
|
||||
|
||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
||||
const cipher = await this.cipherService.encrypt(cipherView, activeUserId);
|
||||
const cipher = await this.cipherService.encrypt(cipherView, userId);
|
||||
try {
|
||||
// We've only updated the password, no need to broadcast editedCipher message
|
||||
await this.cipherService.updateWithServer(cipher);
|
||||
@@ -622,33 +648,34 @@ export default class NotificationBackground {
|
||||
* and opens the add/edit vault item popout.
|
||||
*
|
||||
* @param cipherView - The cipher to edit
|
||||
* @param userId - The active account user ID
|
||||
* @param senderTab - The tab that the message was sent from
|
||||
*/
|
||||
private async editItem(cipherView: CipherView, senderTab: chrome.tabs.Tab) {
|
||||
await this.cipherService.setAddEditCipherInfo({
|
||||
cipher: cipherView,
|
||||
collectionIds: cipherView.collectionIds,
|
||||
});
|
||||
private async editItem(cipherView: CipherView, userId: UserId, senderTab: chrome.tabs.Tab) {
|
||||
await this.cipherService.setAddEditCipherInfo(
|
||||
{
|
||||
cipher: cipherView,
|
||||
collectionIds: cipherView.collectionIds,
|
||||
},
|
||||
userId,
|
||||
);
|
||||
|
||||
await this.openAddEditVaultItemPopout(senderTab, { cipherId: cipherView.id });
|
||||
}
|
||||
|
||||
private async folderExists(folderId: string) {
|
||||
private async folderExists(folderId: string, userId: UserId) {
|
||||
if (Utils.isNullOrWhitespace(folderId) || folderId === "null") {
|
||||
return false;
|
||||
}
|
||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
||||
const folders = await firstValueFrom(this.folderService.folderViews$(activeUserId));
|
||||
const folders = await firstValueFrom(this.folderService.folderViews$(userId));
|
||||
return folders.some((x) => x.id === folderId);
|
||||
}
|
||||
|
||||
private async getDecryptedCipherById(cipherId: string) {
|
||||
const cipher = await this.cipherService.get(cipherId);
|
||||
private async getDecryptedCipherById(cipherId: string, userId: UserId) {
|
||||
const cipher = await this.cipherService.get(cipherId, userId);
|
||||
if (cipher != null && cipher.type === CipherType.Login) {
|
||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
||||
|
||||
return await cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -685,7 +712,9 @@ export default class NotificationBackground {
|
||||
* Returns the first value found from the folder service's folderViews$ observable.
|
||||
*/
|
||||
private async getFolderData() {
|
||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
return await firstValueFrom(this.folderService.folderViews$(activeUserId));
|
||||
}
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@ describe("OverlayBackground", () => {
|
||||
inlineMenuFieldQualificationService,
|
||||
themeStateService,
|
||||
totpService,
|
||||
accountService,
|
||||
generatedPasswordCallbackMock,
|
||||
addPasswordCallbackMock,
|
||||
);
|
||||
@@ -849,7 +850,7 @@ describe("OverlayBackground", () => {
|
||||
await flushPromises();
|
||||
|
||||
expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled();
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, [
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId, [
|
||||
CipherType.Card,
|
||||
CipherType.Identity,
|
||||
]);
|
||||
@@ -872,7 +873,7 @@ describe("OverlayBackground", () => {
|
||||
await flushPromises();
|
||||
|
||||
expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled();
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url);
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId);
|
||||
expect(cipherService.sortCiphersByLastUsedThenName).toHaveBeenCalled();
|
||||
expect(overlayBackground["inlineMenuCiphers"]).toStrictEqual(
|
||||
new Map([
|
||||
@@ -891,7 +892,7 @@ describe("OverlayBackground", () => {
|
||||
await flushPromises();
|
||||
|
||||
expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled();
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, [
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId, [
|
||||
CipherType.Card,
|
||||
CipherType.Identity,
|
||||
]);
|
||||
|
||||
@@ -13,8 +13,10 @@ import {
|
||||
} from "rxjs";
|
||||
import { parse } from "tldts";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { getOptionalUserId, getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import {
|
||||
AutofillOverlayVisibility,
|
||||
SHOW_AUTOFILL_BUTTON,
|
||||
@@ -34,6 +36,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
@@ -225,6 +228,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService,
|
||||
private themeStateService: ThemeStateService,
|
||||
private totpService: TotpService,
|
||||
private accountService: AccountService,
|
||||
private generatePasswordCallback: () => Promise<string>,
|
||||
private addPasswordCallback: (password: string) => Promise<void>,
|
||||
) {
|
||||
@@ -405,13 +409,20 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
currentTab: chrome.tabs.Tab,
|
||||
updateAllCipherTypes: boolean,
|
||||
): Promise<CipherView[]> {
|
||||
if (updateAllCipherTypes || !this.cardAndIdentityCiphers) {
|
||||
return this.getAllCipherTypeViews(currentTab);
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
if (!activeUserId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const cipherViews = (await this.cipherService.getAllDecryptedForUrl(currentTab.url || "")).sort(
|
||||
(a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b),
|
||||
);
|
||||
if (updateAllCipherTypes || !this.cardAndIdentityCiphers) {
|
||||
return this.getAllCipherTypeViews(currentTab, activeUserId);
|
||||
}
|
||||
|
||||
const cipherViews = (
|
||||
await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", activeUserId)
|
||||
).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b));
|
||||
|
||||
return this.cardAndIdentityCiphers
|
||||
? cipherViews.concat(...this.cardAndIdentityCiphers)
|
||||
@@ -422,15 +433,19 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* Queries all cipher types from the user's vault returns them sorted by last used.
|
||||
*
|
||||
* @param currentTab - The current tab
|
||||
* @param userId - The active user id
|
||||
*/
|
||||
private async getAllCipherTypeViews(currentTab: chrome.tabs.Tab): Promise<CipherView[]> {
|
||||
private async getAllCipherTypeViews(
|
||||
currentTab: chrome.tabs.Tab,
|
||||
userId: UserId,
|
||||
): Promise<CipherView[]> {
|
||||
if (!this.cardAndIdentityCiphers) {
|
||||
this.cardAndIdentityCiphers = new Set([]);
|
||||
}
|
||||
|
||||
this.cardAndIdentityCiphers.clear();
|
||||
const cipherViews = (
|
||||
await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", [
|
||||
await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", userId, [
|
||||
CipherType.Card,
|
||||
CipherType.Identity,
|
||||
])
|
||||
@@ -2399,10 +2414,14 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
|
||||
try {
|
||||
this.closeInlineMenu(sender);
|
||||
await this.cipherService.setAddEditCipherInfo({
|
||||
cipher: cipherView,
|
||||
collectionIds: cipherView.collectionIds,
|
||||
});
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
await this.cipherService.setAddEditCipherInfo(
|
||||
{
|
||||
cipher: cipherView,
|
||||
collectionIds: cipherView.collectionIds,
|
||||
},
|
||||
activeUserId,
|
||||
);
|
||||
|
||||
await this.openAddEditVaultItemPopout(sender.tab, {
|
||||
cipherId: cipherView.id,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@@ -14,6 +18,7 @@ export default class WebRequestBackground {
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
private cipherService: CipherService,
|
||||
private authService: AuthService,
|
||||
private accountService: AccountService,
|
||||
private readonly webRequest: typeof chrome.webRequest,
|
||||
) {
|
||||
this.isFirefox = platformUtilsService.isFirefox();
|
||||
@@ -55,7 +60,16 @@ export default class WebRequestBackground {
|
||||
|
||||
// eslint-disable-next-line
|
||||
private async resolveAuthCredentials(domain: string, success: Function, error: Function) {
|
||||
if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) {
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
if (activeUserId == null) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
|
||||
const authStatus = await firstValueFrom(this.authService.authStatusFor$(activeUserId));
|
||||
if (authStatus < AuthenticationStatus.Unlocked) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
@@ -63,6 +77,7 @@ export default class WebRequestBackground {
|
||||
try {
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
||||
domain,
|
||||
activeUserId,
|
||||
null,
|
||||
UriMatchStrategy.Host,
|
||||
);
|
||||
|
||||
@@ -2,6 +2,8 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||
@@ -14,6 +16,9 @@ describe("CipherContextMenuHandler", () => {
|
||||
let authService: MockProxy<AuthService>;
|
||||
let cipherService: MockProxy<CipherService>;
|
||||
|
||||
const mockUserId = "UserId" as UserId;
|
||||
const accountService = mockAccountServiceWith(mockUserId);
|
||||
|
||||
let sut: CipherContextMenuHandler;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -24,7 +29,12 @@ describe("CipherContextMenuHandler", () => {
|
||||
|
||||
jest.spyOn(MainContextMenuHandler, "removeAll").mockResolvedValue();
|
||||
|
||||
sut = new CipherContextMenuHandler(mainContextMenuHandler, authService, cipherService);
|
||||
sut = new CipherContextMenuHandler(
|
||||
mainContextMenuHandler,
|
||||
authService,
|
||||
cipherService,
|
||||
accountService,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
@@ -119,10 +129,11 @@ describe("CipherContextMenuHandler", () => {
|
||||
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com", [
|
||||
CipherType.Card,
|
||||
CipherType.Identity,
|
||||
]);
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(
|
||||
"https://test.com",
|
||||
mockUserId,
|
||||
[CipherType.Card, CipherType.Identity],
|
||||
);
|
||||
|
||||
expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledTimes(3);
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
@@ -14,6 +18,7 @@ export class CipherContextMenuHandler {
|
||||
private mainContextMenuHandler: MainContextMenuHandler,
|
||||
private authService: AuthService,
|
||||
private cipherService: CipherService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
async update(url: string) {
|
||||
@@ -35,7 +40,14 @@ export class CipherContextMenuHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(url, [
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
if (activeUserId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(url, activeUserId, [
|
||||
CipherType.Card,
|
||||
CipherType.Identity,
|
||||
]);
|
||||
|
||||
@@ -61,6 +61,8 @@ describe("ContextMenuClickedHandler", () => {
|
||||
return cipherView;
|
||||
};
|
||||
|
||||
const mockUserId = "UserId" as UserId;
|
||||
|
||||
let copyToClipboard: CopyToClipboardAction;
|
||||
let generatePasswordToClipboard: GeneratePasswordToClipboardAction;
|
||||
let autofill: AutofillAction;
|
||||
@@ -79,7 +81,7 @@ describe("ContextMenuClickedHandler", () => {
|
||||
autofill = jest.fn<Promise<void>, [tab: chrome.tabs.Tab, cipher: CipherView]>();
|
||||
authService = mock();
|
||||
cipherService = mock();
|
||||
accountService = mockAccountServiceWith("userId" as UserId);
|
||||
accountService = mockAccountServiceWith(mockUserId as UserId);
|
||||
totpService = mock();
|
||||
eventCollectionService = mock();
|
||||
|
||||
@@ -191,7 +193,11 @@ describe("ContextMenuClickedHandler", () => {
|
||||
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com", []);
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(
|
||||
"https://test.com",
|
||||
mockUserId,
|
||||
[],
|
||||
);
|
||||
|
||||
expect(copyToClipboard).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -215,7 +221,11 @@ describe("ContextMenuClickedHandler", () => {
|
||||
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com", []);
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(
|
||||
"https://test.com",
|
||||
mockUserId,
|
||||
[],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import {
|
||||
AUTOFILL_CARD_ID,
|
||||
AUTOFILL_ID,
|
||||
@@ -105,6 +106,13 @@ export class ContextMenuClickedHandler {
|
||||
menuItemId as string,
|
||||
);
|
||||
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
if (activeUserId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCreateCipherAction) {
|
||||
// pass; defer to logic below
|
||||
} else if (menuItemId === NOOP_COMMAND_SUFFIX) {
|
||||
@@ -120,12 +128,13 @@ export class ContextMenuClickedHandler {
|
||||
// in scenarios like unlock on autofill
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
||||
tab.url,
|
||||
activeUserId,
|
||||
additionalCiphersToGet,
|
||||
);
|
||||
|
||||
cipher = ciphers[0];
|
||||
} else {
|
||||
const ciphers = await this.cipherService.getAllDecrypted();
|
||||
const ciphers = await this.cipherService.getAllDecrypted(activeUserId);
|
||||
cipher = ciphers.find(({ id }) => id === menuItemId);
|
||||
}
|
||||
|
||||
@@ -133,9 +142,6 @@ export class ContextMenuClickedHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
await this.accountService.setAccountActivity(activeUserId, new Date());
|
||||
switch (info.parentMenuItemId) {
|
||||
case AUTOFILL_ID:
|
||||
|
||||
@@ -110,6 +110,7 @@ describe("OverlayBackground", () => {
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
themeStateService,
|
||||
accountService,
|
||||
);
|
||||
|
||||
jest
|
||||
@@ -205,7 +206,7 @@ describe("OverlayBackground", () => {
|
||||
await overlayBackground.updateOverlayCiphers();
|
||||
|
||||
expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled();
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url);
|
||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId);
|
||||
expect(overlayBackground["cipherService"].sortCiphersByLastUsedThenName).toHaveBeenCalled();
|
||||
expect(overlayBackground["overlayLoginCiphers"]).toStrictEqual(
|
||||
new Map([
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { SHOW_AUTOFILL_BUTTON } from "@bitwarden/common/autofill/constants";
|
||||
@@ -106,6 +107,7 @@ class LegacyOverlayBackground implements OverlayBackgroundInterface {
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private themeStateService: ThemeStateService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -152,9 +154,13 @@ class LegacyOverlayBackground implements OverlayBackgroundInterface {
|
||||
}
|
||||
|
||||
this.overlayLoginCiphers = new Map();
|
||||
const ciphersViews = (await this.cipherService.getAllDecryptedForUrl(currentTab.url)).sort(
|
||||
(a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b),
|
||||
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
const ciphersViews = (
|
||||
await this.cipherService.getAllDecryptedForUrl(currentTab.url, activeUserId)
|
||||
).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b));
|
||||
for (let cipherIndex = 0; cipherIndex < ciphersViews.length; cipherIndex++) {
|
||||
this.overlayLoginCiphers.set(`overlay-cipher-${cipherIndex}`, ciphersViews[cipherIndex]);
|
||||
}
|
||||
@@ -660,10 +666,16 @@ class LegacyOverlayBackground implements OverlayBackgroundInterface {
|
||||
cipherView.type = CipherType.Login;
|
||||
cipherView.login = loginView;
|
||||
|
||||
await this.cipherService.setAddEditCipherInfo({
|
||||
cipher: cipherView,
|
||||
collectionIds: cipherView.collectionIds,
|
||||
});
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
await this.cipherService.setAddEditCipherInfo(
|
||||
{
|
||||
cipher: cipherView,
|
||||
collectionIds: cipherView.collectionIds,
|
||||
},
|
||||
activeUserId,
|
||||
);
|
||||
|
||||
await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id });
|
||||
await BrowserApi.sendMessage("inlineAutofillMenuRefreshAddEditCipher");
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -186,7 +187,10 @@ export class Fido2Component implements OnInit, OnDestroy {
|
||||
this.domainSettingsService.getUrlEquivalentDomains(this.url),
|
||||
);
|
||||
|
||||
this.ciphers = (await this.cipherService.getAllDecrypted()).filter(
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getUserId),
|
||||
);
|
||||
this.ciphers = (await this.cipherService.getAllDecrypted(activeUserId)).filter(
|
||||
(cipher) => cipher.type === CipherType.Login && !cipher.isDeleted,
|
||||
);
|
||||
|
||||
@@ -211,7 +215,7 @@ export class Fido2Component implements OnInit, OnDestroy {
|
||||
|
||||
this.ciphers = await Promise.all(
|
||||
message.cipherIds.map(async (cipherId) => {
|
||||
const cipher = await this.cipherService.get(cipherId);
|
||||
const cipher = await this.cipherService.get(cipherId, activeUserId);
|
||||
return cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
@@ -232,7 +236,7 @@ export class Fido2Component implements OnInit, OnDestroy {
|
||||
|
||||
this.ciphers = await Promise.all(
|
||||
message.existingCipherIds.map(async (cipherId) => {
|
||||
const cipher = await this.cipherService.get(cipherId);
|
||||
const cipher = await this.cipherService.get(cipherId, activeUserId);
|
||||
return cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
|
||||
@@ -769,7 +769,10 @@ describe("AutofillService", () => {
|
||||
);
|
||||
expect(autofillService["generateLoginFillScript"]).toHaveBeenCalled();
|
||||
expect(logService.info).not.toHaveBeenCalled();
|
||||
expect(cipherService.updateLastUsedDate).toHaveBeenCalledWith(autofillOptions.cipher.id);
|
||||
expect(cipherService.updateLastUsedDate).toHaveBeenCalledWith(
|
||||
autofillOptions.cipher.id,
|
||||
mockUserId,
|
||||
);
|
||||
expect(chrome.tabs.sendMessage).toHaveBeenCalledWith(
|
||||
autofillOptions.pageDetails[0].tab.id,
|
||||
{
|
||||
@@ -1030,8 +1033,8 @@ describe("AutofillService", () => {
|
||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, false);
|
||||
|
||||
expect(cipherService.getNextCipherForUrl).not.toHaveBeenCalled();
|
||||
expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true);
|
||||
expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, true);
|
||||
expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true);
|
||||
expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true);
|
||||
expect(autofillService.doAutoFill).not.toHaveBeenCalled();
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
@@ -1044,7 +1047,7 @@ describe("AutofillService", () => {
|
||||
|
||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true);
|
||||
|
||||
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url);
|
||||
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId);
|
||||
expect(cipherService.getLastLaunchedForUrl).not.toHaveBeenCalled();
|
||||
expect(cipherService.getLastUsedForUrl).not.toHaveBeenCalled();
|
||||
expect(autofillService.doAutoFill).not.toHaveBeenCalled();
|
||||
@@ -1074,7 +1077,7 @@ describe("AutofillService", () => {
|
||||
|
||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
||||
|
||||
expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true);
|
||||
expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true);
|
||||
expect(cipherService.getLastUsedForUrl).not.toHaveBeenCalled();
|
||||
expect(cipherService.updateLastUsedIndexForUrl).not.toHaveBeenCalled();
|
||||
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
||||
@@ -1104,8 +1107,8 @@ describe("AutofillService", () => {
|
||||
|
||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
||||
|
||||
expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true);
|
||||
expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, true);
|
||||
expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true);
|
||||
expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true);
|
||||
expect(cipherService.updateLastUsedIndexForUrl).not.toHaveBeenCalled();
|
||||
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
||||
tab: tab,
|
||||
@@ -1132,7 +1135,7 @@ describe("AutofillService", () => {
|
||||
|
||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
||||
|
||||
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url);
|
||||
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId);
|
||||
expect(cipherService.updateLastUsedIndexForUrl).toHaveBeenCalledWith(tab.url);
|
||||
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
||||
tab: tab,
|
||||
@@ -1163,7 +1166,7 @@ describe("AutofillService", () => {
|
||||
|
||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true);
|
||||
|
||||
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url);
|
||||
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId);
|
||||
expect(userVerificationService.hasMasterPasswordAndMasterKeyHash).toHaveBeenCalled();
|
||||
expect(autofillService["openVaultItemPasswordRepromptPopout"]).toHaveBeenCalledWith(tab, {
|
||||
cipherId: cipher.id,
|
||||
@@ -1189,7 +1192,7 @@ describe("AutofillService", () => {
|
||||
|
||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true);
|
||||
|
||||
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url);
|
||||
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId);
|
||||
expect(autofillService["openVaultItemPasswordRepromptPopout"]).not.toHaveBeenCalled();
|
||||
expect(autofillService.doAutoFill).not.toHaveBeenCalled();
|
||||
expect(result).toBeNull();
|
||||
|
||||
@@ -8,6 +8,7 @@ import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import {
|
||||
AutofillOverlayVisibility,
|
||||
CardExpiryDateDelimiters,
|
||||
@@ -464,7 +465,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
|
||||
didAutofill = true;
|
||||
if (!options.skipLastUsed) {
|
||||
await this.cipherService.updateLastUsedDate(options.cipher.id);
|
||||
await this.cipherService.updateLastUsedDate(options.cipher.id, activeAccount.id);
|
||||
}
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
@@ -527,17 +528,29 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
autoSubmitLogin = false,
|
||||
): Promise<string | null> {
|
||||
let cipher: CipherView;
|
||||
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
if (activeUserId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (fromCommand) {
|
||||
cipher = await this.cipherService.getNextCipherForUrl(tab.url);
|
||||
cipher = await this.cipherService.getNextCipherForUrl(tab.url, activeUserId);
|
||||
} else {
|
||||
const lastLaunchedCipher = await this.cipherService.getLastLaunchedForUrl(tab.url, true);
|
||||
const lastLaunchedCipher = await this.cipherService.getLastLaunchedForUrl(
|
||||
tab.url,
|
||||
activeUserId,
|
||||
true,
|
||||
);
|
||||
if (
|
||||
lastLaunchedCipher &&
|
||||
Date.now().valueOf() - lastLaunchedCipher.localData?.lastLaunched?.valueOf() < 30000
|
||||
) {
|
||||
cipher = lastLaunchedCipher;
|
||||
} else {
|
||||
cipher = await this.cipherService.getLastUsedForUrl(tab.url, true);
|
||||
cipher = await this.cipherService.getLastUsedForUrl(tab.url, activeUserId, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,12 +639,19 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
let cipher: CipherView;
|
||||
let cacheKey = "";
|
||||
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
if (activeUserId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cipherType === CipherType.Card) {
|
||||
cacheKey = "cardCiphers";
|
||||
cipher = await this.cipherService.getNextCardCipher();
|
||||
cipher = await this.cipherService.getNextCardCipher(activeUserId);
|
||||
} else {
|
||||
cacheKey = "identityCiphers";
|
||||
cipher = await this.cipherService.getNextIdentityCipher();
|
||||
cipher = await this.cipherService.getNextIdentityCipher(activeUserId);
|
||||
}
|
||||
|
||||
if (!cipher || !cacheKey || (cipher.reprompt === CipherRepromptType.Password && !fromCommand)) {
|
||||
|
||||
@@ -1258,6 +1258,7 @@ export default class MainBackground {
|
||||
this.mainContextMenuHandler,
|
||||
this.authService,
|
||||
this.cipherService,
|
||||
this.accountService,
|
||||
);
|
||||
|
||||
if (chrome.webRequest != null && chrome.webRequest.onAuthRequired != null) {
|
||||
@@ -1265,6 +1266,7 @@ export default class MainBackground {
|
||||
this.platformUtilsService,
|
||||
this.cipherService,
|
||||
this.authService,
|
||||
this.accountService,
|
||||
chrome.webRequest,
|
||||
);
|
||||
}
|
||||
@@ -1636,6 +1638,7 @@ export default class MainBackground {
|
||||
this.i18nService,
|
||||
this.platformUtilsService,
|
||||
this.themeStateService,
|
||||
this.accountService,
|
||||
);
|
||||
} else {
|
||||
this.overlayBackground = new OverlayBackground(
|
||||
@@ -1653,6 +1656,7 @@ export default class MainBackground {
|
||||
this.inlineMenuFieldQualificationService,
|
||||
this.themeStateService,
|
||||
this.totpService,
|
||||
this.accountService,
|
||||
() => this.generatePassword(),
|
||||
(password) => this.addPasswordToHistory(password),
|
||||
);
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
// @ts-strict-ignore
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@@ -22,6 +24,7 @@ export class UpdateBadge {
|
||||
private authService: AuthService;
|
||||
private badgeSettingsService: BadgeSettingsServiceAbstraction;
|
||||
private cipherService: CipherService;
|
||||
private accountService: AccountService;
|
||||
private badgeAction: typeof chrome.action | typeof chrome.browserAction;
|
||||
private sidebarAction: OperaSidebarAction | FirefoxSidebarAction;
|
||||
private win: Window & typeof globalThis;
|
||||
@@ -34,6 +37,7 @@ export class UpdateBadge {
|
||||
this.badgeSettingsService = services.badgeSettingsService;
|
||||
this.authService = services.authService;
|
||||
this.cipherService = services.cipherService;
|
||||
this.accountService = services.accountService;
|
||||
}
|
||||
|
||||
async run(opts?: { tabId?: number; windowId?: number }): Promise<void> {
|
||||
@@ -87,7 +91,14 @@ export class UpdateBadge {
|
||||
return;
|
||||
}
|
||||
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(opts?.tab?.url);
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
if (!activeUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(opts?.tab?.url, activeUserId);
|
||||
let countText = ciphers.length == 0 ? "" : ciphers.length.toString();
|
||||
if (ciphers.length > 9) {
|
||||
countText = "9+";
|
||||
|
||||
@@ -179,7 +179,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
await this.clearComponentStates();
|
||||
}
|
||||
if (url.startsWith("/tabs/")) {
|
||||
await this.cipherService.setAddEditCipherInfo(null);
|
||||
await this.cipherService.setAddEditCipherInfo(null, this.activeUserId);
|
||||
}
|
||||
(window as any).previousPopupUrl = url;
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject, Observable } from "rxjs";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
@@ -58,9 +61,9 @@ describe("AddEditV2Component", () => {
|
||||
collect.mockClear();
|
||||
|
||||
addEditCipherInfo$ = new BehaviorSubject<AddEditCipherInfo | null>(null);
|
||||
cipherServiceMock = mock<CipherService>();
|
||||
cipherServiceMock.addEditCipherInfo$ =
|
||||
addEditCipherInfo$.asObservable() as Observable<AddEditCipherInfo>;
|
||||
cipherServiceMock = mock<CipherService>({
|
||||
addEditCipherInfo$: jest.fn().mockReturnValue(addEditCipherInfo$),
|
||||
});
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AddEditV2Component],
|
||||
@@ -81,6 +84,7 @@ describe("AddEditV2Component", () => {
|
||||
canDeleteCipher$: jest.fn().mockReturnValue(true),
|
||||
},
|
||||
},
|
||||
{ provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) },
|
||||
],
|
||||
})
|
||||
.overrideProvider(CipherFormConfigService, {
|
||||
|
||||
@@ -9,10 +9,12 @@ import { firstValueFrom, map, Observable, switchMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -180,6 +182,7 @@ export class AddEditV2Component implements OnInit {
|
||||
private toastService: ToastService,
|
||||
private dialogService: DialogService,
|
||||
protected cipherAuthorizationService: CipherAuthorizationService,
|
||||
private accountService: AccountService,
|
||||
) {
|
||||
this.subscribeToParams();
|
||||
}
|
||||
@@ -281,9 +284,15 @@ export class AddEditV2Component implements OnInit {
|
||||
|
||||
config.initialValues = this.setInitialValuesFromParams(params);
|
||||
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getUserId),
|
||||
);
|
||||
|
||||
// The browser notification bar and overlay use addEditCipherInfo$ to pass modified cipher details to the form
|
||||
// Attempt to fetch them here and overwrite the initialValues if present
|
||||
const cachedCipherInfo = await firstValueFrom(this.cipherService.addEditCipherInfo$);
|
||||
const cachedCipherInfo = await firstValueFrom(
|
||||
this.cipherService.addEditCipherInfo$(activeUserId),
|
||||
);
|
||||
|
||||
if (cachedCipherInfo != null) {
|
||||
// Cached cipher info has priority over queryParams
|
||||
@@ -292,7 +301,7 @@ export class AddEditV2Component implements OnInit {
|
||||
...mapAddEditCipherInfoToInitialValues(cachedCipherInfo),
|
||||
};
|
||||
// Be sure to clear the "cached" cipher info, so it doesn't get used again
|
||||
await this.cipherService.setAddEditCipherInfo(null);
|
||||
await this.cipherService.setAddEditCipherInfo(null, activeUserId);
|
||||
}
|
||||
|
||||
if (["edit", "partial-edit"].includes(config.mode) && config.originalCipher?.id) {
|
||||
@@ -371,7 +380,8 @@ export class AddEditV2Component implements OnInit {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.deleteCipher();
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
await this.deleteCipher(activeUserId);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
return false;
|
||||
@@ -388,10 +398,10 @@ export class AddEditV2Component implements OnInit {
|
||||
return true;
|
||||
};
|
||||
|
||||
protected deleteCipher() {
|
||||
protected deleteCipher(userId: UserId) {
|
||||
return this.config.originalCipher.deletedDate
|
||||
? this.cipherService.deleteWithServer(this.config.originalCipher.id)
|
||||
: this.cipherService.softDeleteWithServer(this.config.originalCipher.id);
|
||||
? this.cipherService.deleteWithServer(this.config.originalCipher.id, userId)
|
||||
: this.cipherService.softDeleteWithServer(this.config.originalCipher.id, userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,13 @@ import { Component } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ReactiveFormsModule } from "@angular/forms";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { Observable, combineLatest, first, map, switchMap } from "rxjs";
|
||||
import { Observable, combineLatest, filter, first, map, switchMap } from "rxjs";
|
||||
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { OrgKey, UserKey } from "@bitwarden/common/types/key";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import {
|
||||
@@ -58,16 +59,19 @@ export class AssignCollections {
|
||||
private accountService: AccountService,
|
||||
route: ActivatedRoute,
|
||||
) {
|
||||
const cipher$: Observable<CipherView> = route.queryParams.pipe(
|
||||
switchMap(({ cipherId }) => this.cipherService.get(cipherId)),
|
||||
switchMap((cipherDomain) =>
|
||||
this.accountService.activeAccount$.pipe(
|
||||
map((account) => account?.id),
|
||||
switchMap((userId) =>
|
||||
this.cipherService
|
||||
.getKeyForCipherKeyDecryption(cipherDomain, userId)
|
||||
.then(cipherDomain.decrypt.bind(cipherDomain)),
|
||||
),
|
||||
const cipher$: Observable<CipherView> = this.accountService.activeAccount$.pipe(
|
||||
map((account) => account?.id),
|
||||
filter((userId) => userId != null),
|
||||
switchMap((userId) =>
|
||||
route.queryParams.pipe(
|
||||
switchMap(async ({ cipherId }) => {
|
||||
const cipherDomain = await this.cipherService.get(cipherId, userId);
|
||||
const key: UserKey | OrgKey = await this.cipherService.getKeyForCipherKeyDecryption(
|
||||
cipherDomain,
|
||||
userId,
|
||||
);
|
||||
return cipherDomain.decrypt(key);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -77,10 +77,10 @@ export class OpenAttachmentsComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
const cipherDomain = await this.cipherService.get(this.cipherId);
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId);
|
||||
const cipher = await cipherDomain.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId),
|
||||
);
|
||||
|
||||
@@ -59,7 +59,9 @@ describe("VaultHeaderV2Component", () => {
|
||||
providers: [
|
||||
{
|
||||
provide: CipherService,
|
||||
useValue: mock<CipherService>({ cipherViews$: new BehaviorSubject([]) }),
|
||||
useValue: mock<CipherService>({
|
||||
cipherViews$: jest.fn().mockReturnValue(new BehaviorSubject([])),
|
||||
}),
|
||||
},
|
||||
{ provide: VaultSettingsService, useValue: mock<VaultSettingsService>() },
|
||||
{ provide: FolderService, useValue: mock<FolderService>() },
|
||||
|
||||
@@ -22,6 +22,8 @@ import { Router } from "@angular/router";
|
||||
import { firstValueFrom, Observable, map } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherId } from "@bitwarden/common/types/guid";
|
||||
@@ -265,6 +267,7 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
|
||||
private router: Router,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private dialogService: DialogService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -311,7 +314,8 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
|
||||
this.viewCipherTimeout = null;
|
||||
}
|
||||
|
||||
await this.cipherService.updateLastLaunchedDate(cipher.id);
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
await this.cipherService.updateLastLaunchedDate(cipher.id, activeUserId);
|
||||
|
||||
await BrowserApi.createNewTab(cipher.login.launchUri);
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject, Subject } from "rxjs";
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -19,6 +21,7 @@ import { PasswordHistoryV2Component } from "./vault-password-history-v2.componen
|
||||
describe("PasswordHistoryV2Component", () => {
|
||||
let fixture: ComponentFixture<PasswordHistoryV2Component>;
|
||||
const params$ = new Subject();
|
||||
const mockUserId = "acct-1" as UserId;
|
||||
|
||||
const mockCipherView = {
|
||||
id: "111-222-333",
|
||||
@@ -45,9 +48,7 @@ describe("PasswordHistoryV2Component", () => {
|
||||
{ provide: CipherService, useValue: mock<CipherService>({ get: getCipher }) },
|
||||
{
|
||||
provide: AccountService,
|
||||
useValue: mock<AccountService>({
|
||||
activeAccount$: new BehaviorSubject({ id: "acct-1" } as Account),
|
||||
}),
|
||||
useValue: mockAccountServiceWith(mockUserId),
|
||||
},
|
||||
{ provide: PopupRouterCacheService, useValue: { back } },
|
||||
{ provide: ActivatedRoute, useValue: { queryParams: params$ } },
|
||||
@@ -64,7 +65,7 @@ describe("PasswordHistoryV2Component", () => {
|
||||
|
||||
tick(100);
|
||||
|
||||
expect(getCipher).toHaveBeenCalledWith(mockCipherView.id);
|
||||
expect(getCipher).toHaveBeenCalledWith(mockCipherView.id, mockUserId);
|
||||
}));
|
||||
|
||||
it("navigates back when a cipherId is not in the params", () => {
|
||||
|
||||
@@ -58,8 +58,6 @@ export class PasswordHistoryV2Component implements OnInit {
|
||||
|
||||
/** Load the cipher based on the given Id */
|
||||
private async loadCipher(cipherId: string) {
|
||||
const cipher = await this.cipherService.get(cipherId);
|
||||
|
||||
const activeAccount = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a: { id: string | undefined }) => a)),
|
||||
);
|
||||
@@ -69,6 +67,8 @@ export class PasswordHistoryV2Component implements OnInit {
|
||||
}
|
||||
|
||||
const activeUserId = activeAccount.id as UserId;
|
||||
|
||||
const cipher = await this.cipherService.get(cipherId, activeUserId);
|
||||
this.cipher = await cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
|
||||
@@ -3,9 +3,20 @@ import { CommonModule } from "@angular/common";
|
||||
import { AfterViewInit, Component, DestroyRef, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { RouterLink } from "@angular/router";
|
||||
import { combineLatest, filter, map, Observable, shareReplay, switchMap, take } from "rxjs";
|
||||
import {
|
||||
combineLatest,
|
||||
filter,
|
||||
map,
|
||||
firstValueFrom,
|
||||
Observable,
|
||||
shareReplay,
|
||||
switchMap,
|
||||
take,
|
||||
} from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
@@ -96,6 +107,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||
private vaultPopupItemsService: VaultPopupItemsService,
|
||||
private vaultPopupListFiltersService: VaultPopupListFiltersService,
|
||||
private vaultScrollPositionService: VaultPopupScrollPositionService,
|
||||
private accountService: AccountService,
|
||||
private destroyRef: DestroyRef,
|
||||
private cipherService: CipherService,
|
||||
private dialogService: DialogService,
|
||||
@@ -136,7 +148,10 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.cipherService.failedToDecryptCiphers$
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
|
||||
this.cipherService
|
||||
.failedToDecryptCiphers$(activeUserId)
|
||||
.pipe(
|
||||
map((ciphers) => ciphers.filter((c) => !c.isDeleted)),
|
||||
filter((ciphers) => ciphers.length > 0),
|
||||
|
||||
@@ -150,7 +150,7 @@ describe("ViewV2Component", () => {
|
||||
|
||||
flush(); // Resolve all promises
|
||||
|
||||
expect(mockCipherService.get).toHaveBeenCalledWith("122-333-444");
|
||||
expect(mockCipherService.get).toHaveBeenCalledWith("122-333-444", mockUserId);
|
||||
expect(component.cipher).toEqual(mockCipher);
|
||||
}));
|
||||
|
||||
|
||||
@@ -5,13 +5,14 @@ import { Component } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom, map, Observable, switchMap } from "rxjs";
|
||||
import { firstValueFrom, Observable, switchMap } from "rxjs";
|
||||
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import {
|
||||
AUTOFILL_ID,
|
||||
COPY_PASSWORD_ID,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
|
||||
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
|
||||
@@ -87,6 +89,8 @@ type LoadAction =
|
||||
],
|
||||
})
|
||||
export class ViewV2Component {
|
||||
private activeUserId: UserId;
|
||||
|
||||
headerText: string;
|
||||
cipher: CipherView;
|
||||
organization$: Observable<Organization>;
|
||||
@@ -117,14 +121,20 @@ export class ViewV2Component {
|
||||
subscribeToParams(): void {
|
||||
this.route.queryParams
|
||||
.pipe(
|
||||
switchMap(async (params): Promise<CipherView> => {
|
||||
switchMap(async (params) => {
|
||||
this.loadAction = params.action;
|
||||
this.senderTabId = params.senderTabId ? parseInt(params.senderTabId, 10) : undefined;
|
||||
return await this.getCipherData(params.cipherId);
|
||||
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getUserId),
|
||||
);
|
||||
const cipher = await this.getCipherData(params.cipherId, activeUserId);
|
||||
return { activeUserId, cipher };
|
||||
}),
|
||||
switchMap(async (cipher) => {
|
||||
switchMap(async ({ activeUserId, cipher }) => {
|
||||
this.cipher = cipher;
|
||||
this.headerText = this.setHeader(cipher.type);
|
||||
this.activeUserId = activeUserId;
|
||||
|
||||
if (this.loadAction) {
|
||||
await this._handleLoadAction(this.loadAction, this.senderTabId);
|
||||
@@ -159,13 +169,10 @@ export class ViewV2Component {
|
||||
}
|
||||
}
|
||||
|
||||
async getCipherData(id: string) {
|
||||
const cipher = await this.cipherService.get(id);
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
async getCipherData(id: string, userId: UserId) {
|
||||
const cipher = await this.cipherService.get(id, userId);
|
||||
return await cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -213,7 +220,7 @@ export class ViewV2Component {
|
||||
|
||||
restore = async (): Promise<void> => {
|
||||
try {
|
||||
await this.cipherService.restoreWithServer(this.cipher.id);
|
||||
await this.cipherService.restoreWithServer(this.cipher.id, this.activeUserId);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
@@ -228,8 +235,8 @@ export class ViewV2Component {
|
||||
|
||||
protected deleteCipher() {
|
||||
return this.cipher.isDeleted
|
||||
? this.cipherService.deleteWithServer(this.cipher.id)
|
||||
: this.cipherService.softDeleteWithServer(this.cipher.id);
|
||||
? this.cipherService.deleteWithServer(this.cipher.id, this.activeUserId)
|
||||
: this.cipherService.softDeleteWithServer(this.cipher.id, this.activeUserId);
|
||||
}
|
||||
|
||||
protected showFooter(): boolean {
|
||||
|
||||
@@ -15,6 +15,8 @@ import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
||||
import { LocalData } from "@bitwarden/common/vault/models/data/local.data";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
import { InlineMenuFieldQualificationService } from "../../../autofill/services/inline-menu-field-qualification.service";
|
||||
@@ -34,6 +36,10 @@ describe("VaultPopupItemsService", () => {
|
||||
let mockCollections: CollectionView[];
|
||||
let activeUserLastSync$: BehaviorSubject<Date>;
|
||||
|
||||
let ciphersSubject: BehaviorSubject<Record<CipherId, CipherData>>;
|
||||
let localDataSubject: BehaviorSubject<Record<CipherId, LocalData>>;
|
||||
let failedToDecryptCiphersSubject: BehaviorSubject<CipherView[]>;
|
||||
|
||||
const cipherServiceMock = mock<CipherService>();
|
||||
const vaultSettingsServiceMock = mock<VaultSettingsService>();
|
||||
const organizationServiceMock = mock<OrganizationService>();
|
||||
@@ -60,9 +66,21 @@ describe("VaultPopupItemsService", () => {
|
||||
cipherList[3].favorite = true;
|
||||
|
||||
cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList);
|
||||
cipherServiceMock.ciphers$ = new BehaviorSubject(null);
|
||||
cipherServiceMock.localData$ = new BehaviorSubject(null);
|
||||
cipherServiceMock.failedToDecryptCiphers$ = new BehaviorSubject([]);
|
||||
|
||||
ciphersSubject = new BehaviorSubject<Record<CipherId, CipherData>>({});
|
||||
localDataSubject = new BehaviorSubject<Record<CipherId, LocalData>>({});
|
||||
failedToDecryptCiphersSubject = new BehaviorSubject<CipherView[]>([]);
|
||||
|
||||
cipherServiceMock.ciphers$.mockImplementation((userId: UserId) =>
|
||||
ciphersSubject.asObservable(),
|
||||
);
|
||||
cipherServiceMock.localData$.mockImplementation((userId: UserId) =>
|
||||
localDataSubject.asObservable(),
|
||||
);
|
||||
cipherServiceMock.failedToDecryptCiphers$.mockImplementation((userId: UserId) =>
|
||||
failedToDecryptCiphersSubject.asObservable(),
|
||||
);
|
||||
|
||||
searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers);
|
||||
cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) =>
|
||||
ciphers.filter((c) => ["0", "1"].includes(c.id)),
|
||||
@@ -118,6 +136,7 @@ describe("VaultPopupItemsService", () => {
|
||||
{ provide: CollectionService, useValue: collectionService },
|
||||
{ provide: VaultPopupAutofillService, useValue: vaultAutofillServiceMock },
|
||||
{ provide: SyncService, useValue: syncServiceMock },
|
||||
{ provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) },
|
||||
{
|
||||
provide: InlineMenuFieldQualificationService,
|
||||
useValue: inlineMenuFieldQualificationServiceMock,
|
||||
@@ -155,7 +174,7 @@ describe("VaultPopupItemsService", () => {
|
||||
|
||||
await tracker.expectEmission();
|
||||
|
||||
(cipherServiceMock.ciphers$ as BehaviorSubject<any>).next(null);
|
||||
ciphersSubject.next({});
|
||||
|
||||
await tracker.expectEmission();
|
||||
|
||||
@@ -169,7 +188,7 @@ describe("VaultPopupItemsService", () => {
|
||||
|
||||
await tracker.expectEmission();
|
||||
|
||||
(cipherServiceMock.localData$ as BehaviorSubject<any>).next(null);
|
||||
localDataSubject.next({});
|
||||
|
||||
await tracker.expectEmission();
|
||||
|
||||
@@ -373,7 +392,7 @@ describe("VaultPopupItemsService", () => {
|
||||
|
||||
cipherServiceMock.getAllDecrypted.mockResolvedValue(ciphers);
|
||||
|
||||
(cipherServiceMock.ciphers$ as BehaviorSubject<any>).next(null);
|
||||
ciphersSubject.next({});
|
||||
|
||||
const deletedCiphers = await firstValueFrom(service.deletedCiphers$);
|
||||
expect(deletedCiphers.length).toBe(1);
|
||||
@@ -422,7 +441,7 @@ describe("VaultPopupItemsService", () => {
|
||||
it("should cycle when cipherService.ciphers$ emits", async () => {
|
||||
// Restart tracking
|
||||
tracked = new ObservableTracker(service.loading$);
|
||||
(cipherServiceMock.ciphers$ as BehaviorSubject<any>).next(null);
|
||||
ciphersSubject.next({});
|
||||
|
||||
await trackedCiphers.pauseUntilReceived(2);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { inject, Injectable, NgZone } from "@angular/core";
|
||||
import { Injectable, NgZone } from "@angular/core";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
@@ -86,16 +86,19 @@ export class VaultPopupItemsService {
|
||||
* Observable that contains the list of all decrypted ciphers.
|
||||
* @private
|
||||
*/
|
||||
private _allDecryptedCiphers$: Observable<CipherView[]> = merge(
|
||||
this.cipherService.ciphers$,
|
||||
this.cipherService.localData$,
|
||||
).pipe(
|
||||
runInsideAngular(inject(NgZone)), // Workaround to ensure cipher$ state provider emissions are run inside Angular
|
||||
tap(() => this._ciphersLoading$.next()),
|
||||
waitUntilSync(this.syncService),
|
||||
switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())),
|
||||
withLatestFrom(this.cipherService.failedToDecryptCiphers$),
|
||||
map(([ciphers, failedToDecryptCiphers]) => [...failedToDecryptCiphers, ...ciphers]),
|
||||
private _allDecryptedCiphers$: Observable<CipherView[]> = this.accountService.activeAccount$.pipe(
|
||||
map((a) => a?.id),
|
||||
filter((userId) => userId != null),
|
||||
switchMap((userId) =>
|
||||
merge(this.cipherService.ciphers$(userId), this.cipherService.localData$(userId)).pipe(
|
||||
runInsideAngular(this.ngZone),
|
||||
tap(() => this._ciphersLoading$.next()),
|
||||
waitUntilSync(this.syncService),
|
||||
switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted(userId))),
|
||||
withLatestFrom(this.cipherService.failedToDecryptCiphers$(userId)),
|
||||
map(([ciphers, failedToDecryptCiphers]) => [...failedToDecryptCiphers, ...ciphers]),
|
||||
),
|
||||
),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
@@ -281,6 +284,7 @@ export class VaultPopupItemsService {
|
||||
private vaultPopupAutofillService: VaultPopupAutofillService,
|
||||
private syncService: SyncService,
|
||||
private accountService: AccountService,
|
||||
private ngZone: NgZone,
|
||||
) {}
|
||||
|
||||
applyFilter(newSearchText: string) {
|
||||
|
||||
@@ -41,7 +41,7 @@ describe("VaultPopupListFiltersService", () => {
|
||||
} as unknown as FolderService;
|
||||
|
||||
const cipherService = {
|
||||
cipherViews$,
|
||||
cipherViews$: () => cipherViews$,
|
||||
} as unknown as CipherService;
|
||||
|
||||
const organizationService = {
|
||||
|
||||
@@ -93,16 +93,6 @@ export class VaultPopupListFiltersService {
|
||||
*/
|
||||
private cipherViews: CipherView[] = [];
|
||||
|
||||
/**
|
||||
* Observable of cipher views
|
||||
*/
|
||||
private cipherViews$: Observable<CipherView[]> = this.cipherService.cipherViews$.pipe(
|
||||
tap((cipherViews) => {
|
||||
this.cipherViews = Object.values(cipherViews);
|
||||
}),
|
||||
map((ciphers) => Object.values(ciphers)),
|
||||
);
|
||||
|
||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
||||
|
||||
constructor(
|
||||
@@ -271,8 +261,16 @@ export class VaultPopupListFiltersService {
|
||||
* Folder array structured to be directly passed to `ChipSelectComponent`
|
||||
*/
|
||||
folders$: Observable<ChipSelectOption<FolderView>[]> = this.activeUserId$.pipe(
|
||||
switchMap((userId) =>
|
||||
combineLatest([
|
||||
switchMap((userId) => {
|
||||
// Observable of cipher views
|
||||
const cipherViews$ = this.cipherService.cipherViews$(userId).pipe(
|
||||
tap((cipherViews) => {
|
||||
this.cipherViews = Object.values(cipherViews);
|
||||
}),
|
||||
map((ciphers) => Object.values(ciphers)),
|
||||
);
|
||||
|
||||
return combineLatest([
|
||||
this.filters$.pipe(
|
||||
distinctUntilChanged(
|
||||
(previousFilter, currentFilter) =>
|
||||
@@ -281,7 +279,7 @@ export class VaultPopupListFiltersService {
|
||||
),
|
||||
),
|
||||
this.folderService.folderViews$(userId),
|
||||
this.cipherViews$,
|
||||
cipherViews$,
|
||||
]).pipe(
|
||||
map(([filters, folders, cipherViews]): [PopupListFilter, FolderView[], CipherView[]] => {
|
||||
if (folders.length === 1 && folders[0].id === null) {
|
||||
@@ -330,8 +328,8 @@ export class VaultPopupListFiltersService {
|
||||
map((folders) =>
|
||||
folders.nestedList.map((f) => this.convertToChipSelectOption(f, "bwi-folder")),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ChangeDetectionStrategy, Component, Input } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { CipherId } from "@bitwarden/common/types/guid";
|
||||
@@ -65,6 +68,7 @@ export class TrashListItemsContainerComponent {
|
||||
private i18nService: I18nService,
|
||||
private dialogService: DialogService,
|
||||
private passwordRepromptService: PasswordRepromptService,
|
||||
private accountService: AccountService,
|
||||
private router: Router,
|
||||
) {}
|
||||
|
||||
@@ -81,7 +85,8 @@ export class TrashListItemsContainerComponent {
|
||||
|
||||
async restore(cipher: CipherView) {
|
||||
try {
|
||||
await this.cipherService.restoreWithServer(cipher.id);
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
await this.cipherService.restoreWithServer(cipher.id, activeUserId);
|
||||
|
||||
await this.router.navigate(["/trash"]);
|
||||
this.toastService.showToast({
|
||||
@@ -112,7 +117,8 @@ export class TrashListItemsContainerComponent {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.cipherService.deleteWithServer(cipher.id);
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
await this.cipherService.deleteWithServer(cipher.id, activeUserId);
|
||||
|
||||
await this.router.navigate(["/trash"]);
|
||||
this.toastService.showToast({
|
||||
|
||||
Reference in New Issue
Block a user