1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 01:03:35 +00:00

Merge branch 'main' of https://github.com/bitwarden/clients into vault/pm-10426/admin-console-add-edit

This commit is contained in:
Nick Krantz
2024-09-25 10:28:39 -05:00
141 changed files with 1786 additions and 701 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@bitwarden/browser",
"version": "2024.9.1",
"version": "2024.9.2",
"scripts": {
"build": "cross-env MANIFEST_VERSION=3 webpack",
"build:mv2": "webpack",

View File

@@ -2374,6 +2374,10 @@
"message": "Are you sure you want to delete this Send?",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"deleteSendPermanentConfirmation": {
"message": "Are you sure you want to permanently delete this Send?",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"editSend": {
"message": "Edit Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -4201,6 +4205,9 @@
"enableAnimations": {
"message": "Enable animations"
},
"showAnimations": {
"message": "Show animations"
},
"addAccount": {
"message": "Add account"
},

View File

@@ -20,6 +20,7 @@ import {
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import { parseYearMonthExpiry } from "@bitwarden/common/autofill/utils";
import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import {
@@ -1898,11 +1899,21 @@ export class OverlayBackground implements OverlayBackgroundInterface {
const cardView = new CardView();
cardView.cardholderName = card.cardholderName || "";
cardView.number = card.number || "";
cardView.expMonth = card.expirationMonth || "";
cardView.expYear = card.expirationYear || "";
cardView.code = card.cvv || "";
cardView.brand = card.number ? CardView.getCardBrandByPatterns(card.number) : "";
// If there's a combined expiration date value and no individual month or year values,
// try to parse them from the combined value
if (card.expirationDate && !card.expirationMonth && !card.expirationYear) {
const [parsedYear, parsedMonth] = parseYearMonthExpiry(card.expirationDate);
cardView.expMonth = parsedMonth || "";
cardView.expYear = parsedYear || "";
} else {
cardView.expMonth = card.expirationMonth || "";
cardView.expYear = card.expirationYear || "";
}
const cipherView = new CipherView();
cipherView.name = "";
cipherView.folderId = null;

View File

@@ -300,8 +300,6 @@ export class CreditCardAutoFillConstants {
"cb-type",
];
static readonly CardExpiryDateDelimiters: string[] = ["/", "-", ".", " "];
// Note, these are expressions of user-guidance for the expected expiry date format to be used
static readonly CardExpiryDateFormats: CardExpiryDateFormat[] = [
// English

View File

@@ -6,11 +6,15 @@ 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 { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
import {
AutofillOverlayVisibility,
CardExpiryDateDelimiters,
} from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@@ -30,7 +34,6 @@ import { CardView } from "@bitwarden/common/vault/models/view/card.view";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view";
import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils";
import { BrowserApi } from "../../platform/browser/browser-api";
import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service";
@@ -1397,8 +1400,7 @@ export default class AutofillService implements AutofillServiceInterface {
if (expectedExpiryDateFormat) {
const { Month, MonthShort, Year } = expiryDateFormatPatterns;
const expiryDateDelimitersPattern =
"\\" + CreditCardAutoFillConstants.CardExpiryDateDelimiters.join("\\");
const expiryDateDelimitersPattern = "\\" + CardExpiryDateDelimiters.join("\\");
// assign the delimiter from the expected format string
delimiter =
@@ -1450,8 +1452,7 @@ export default class AutofillService implements AutofillServiceInterface {
let expectedDateFormat = null;
let dateFormatPatterns = null;
const expiryDateDelimitersPattern =
"\\" + CreditCardAutoFillConstants.CardExpiryDateDelimiters.join("\\");
const expiryDateDelimitersPattern = "\\" + CardExpiryDateDelimiters.join("\\");
CreditCardAutoFillConstants.CardExpiryDateFormats.find((dateFormat) => {
dateFormatPatterns = dateFormat;
@@ -1489,6 +1490,8 @@ export default class AutofillService implements AutofillServiceInterface {
return false;
});
});
// @TODO if expectedDateFormat is still null, and there is a `pattern` attribute, cycle
// through generated formatted values, checking against the provided regex pattern
return [expectedDateFormat, dateFormatPatterns];
}

View File

@@ -419,7 +419,7 @@ export default class MainBackground {
this.logService = new ConsoleLogService(isDev);
this.cryptoFunctionService = new WebCryptoFunctionService(self);
this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService);
this.storageService = new BrowserLocalStorageService();
this.storageService = new BrowserLocalStorageService(this.logService);
this.intraprocessMessagingSubject = new Subject<Message<Record<string, unknown>>>();
@@ -693,6 +693,7 @@ export default class MainBackground {
this.collectionService = new CollectionService(
this.cryptoService,
this.encryptService,
this.i18nService,
this.stateProvider,
);
@@ -803,9 +804,11 @@ export default class MainBackground {
this.cipherFileUploadService,
this.configService,
this.stateProvider,
this.accountService,
);
this.folderService = new FolderService(
this.cryptoService,
this.encryptService,
this.i18nService,
this.cipherService,
this.stateProvider,
@@ -977,6 +980,7 @@ export default class MainBackground {
this.i18nService,
this.collectionService,
this.cryptoService,
this.encryptService,
this.pinService,
this.accountService,
);
@@ -986,8 +990,10 @@ export default class MainBackground {
this.cipherService,
this.pinService,
this.cryptoService,
this.encryptService,
this.cryptoFunctionService,
this.kdfConfigService,
this.accountService,
);
this.organizationVaultExportService = new OrganizationVaultExportService(
@@ -995,6 +1001,7 @@ export default class MainBackground {
this.apiService,
this.pinService,
this.cryptoService,
this.encryptService,
this.cryptoFunctionService,
this.collectionService,
this.kdfConfigService,
@@ -1098,6 +1105,7 @@ export default class MainBackground {
);
this.nativeMessagingBackground = new NativeMessagingBackground(
this.cryptoService,
this.encryptService,
this.cryptoFunctionService,
this.runtimeBackground,
this.messagingService,

View File

@@ -6,6 +6,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -73,6 +74,7 @@ export class NativeMessagingBackground {
constructor(
private cryptoService: CryptoService,
private encryptService: EncryptService,
private cryptoFunctionService: CryptoFunctionService,
private runtimeBackground: RuntimeBackground,
private messagingService: MessagingService,
@@ -227,7 +229,7 @@ export class NativeMessagingBackground {
await this.secureCommunication();
}
return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret);
return await this.encryptService.encrypt(JSON.stringify(message), this.sharedSecret);
}
getResponse(): Promise<any> {
@@ -273,7 +275,7 @@ export class NativeMessagingBackground {
let message = rawMessage as ReceiveMessage;
if (!this.platformUtilsService.isSafari()) {
message = JSON.parse(
await this.cryptoService.decryptToUtf8(rawMessage as EncString, this.sharedSecret),
await this.encryptService.decryptToUtf8(rawMessage as EncString, this.sharedSecret),
);
}

View File

@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
"version": "2024.9.1",
"version": "2024.9.2",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",

View File

@@ -3,7 +3,7 @@
"minimum_chrome_version": "102.0",
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
"version": "2024.9.1",
"version": "2024.9.2",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",

View File

@@ -1,10 +1,67 @@
import AbstractChromeStorageService from "./abstractions/abstract-chrome-storage-api.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import AbstractChromeStorageService, {
SerializedValue,
} from "./abstractions/abstract-chrome-storage-api.service";
export default class BrowserLocalStorageService extends AbstractChromeStorageService {
constructor() {
constructor(private readonly logService: LogService) {
super(chrome.storage.local);
}
override async get<T>(key: string): Promise<T> {
return await this.getWithRetries<T>(key, 0);
}
private async getWithRetries<T>(key: string, retryNum: number): Promise<T> {
// See: https://github.com/EFForg/privacybadger/pull/2980
const MAX_RETRIES = 5;
const WAIT_TIME = 200;
const store = await this.getStore(key);
if (store == null) {
if (retryNum >= MAX_RETRIES) {
throw new Error(`Failed to get a value for key '${key}', see logs for more details.`);
}
retryNum++;
this.logService.warning(`Retrying attempt to get value for key '${key}' in ${WAIT_TIME}ms`);
await new Promise<void>((resolve) => setTimeout(resolve, WAIT_TIME));
return await this.getWithRetries(key, retryNum);
}
// We have a store
return this.processGetObject<T>(store[key] as T | SerializedValue);
}
private async getStore(key: string) {
if (this.chromeStorageApi == null) {
this.logService.warning(
`chrome.storage.local was not initialized while retrieving key '${key}'.`,
);
return null;
}
return new Promise<{ [key: string]: unknown }>((resolve) => {
this.chromeStorageApi.get(key, (store) => {
if (chrome.runtime.lastError) {
this.logService.warning(`Failed to get value for key '${key}'`, chrome.runtime.lastError);
resolve(null);
return;
}
if (store == null) {
this.logService.warning(`Store was empty while retrieving value for key '${key}'`);
resolve(null);
return;
}
resolve(store);
});
});
}
async fillBuffer() {
// Write 4MB of data in chrome.storage.local, log files will hold 4MB of data (by default)
// before forcing a compaction. To force a compaction and have it remove previously saved data,

View File

@@ -304,7 +304,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: AbstractStorageService,
useClass: BrowserLocalStorageService,
deps: [],
deps: [LogService],
}),
safeProvider({
provide: AutofillServiceAbstraction,

View File

@@ -13,5 +13,14 @@
<button bitButton type="submit" form="sendForm" buttonType="primary" #submitBtn>
{{ "save" | i18n }}
</button>
<button
*ngIf="config?.mode !== 'add'"
type="button"
buttonType="danger"
class="tw-ml-auto bwi-lg"
bitIconButton="bwi-trash"
[bitAction]="deleteSend"
appA11yTitle="{{ 'delete' | i18n }}"
></button>
</popup-footer>
</popup-page>

View File

@@ -8,8 +8,16 @@ import { map, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendId } from "@bitwarden/common/types/guid";
import { AsyncActionsModule, ButtonModule, SearchModule } from "@bitwarden/components";
import {
AsyncActionsModule,
ButtonModule,
DialogService,
IconButtonModule,
SearchModule,
ToastService,
} from "@bitwarden/components";
import {
DefaultSendFormConfigService,
SendFormConfig,
@@ -58,6 +66,7 @@ export type AddEditQueryParams = Partial<Record<keyof QueryParams, string>>;
JslibModule,
FormsModule,
ButtonModule,
IconButtonModule,
PopupPageComponent,
PopupHeaderComponent,
PopupFooterComponent,
@@ -81,6 +90,9 @@ export class SendAddEditComponent {
private location: Location,
private i18nService: I18nService,
private addEditFormConfigService: SendFormConfigService,
private sendApiService: SendApiService,
private toastService: ToastService,
private dialogService: DialogService,
) {
this.subscribeToParams();
}
@@ -92,6 +104,37 @@ export class SendAddEditComponent {
this.location.back();
}
deleteSend = async () => {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "deleteSend" },
content: { key: "deleteSendPermanentConfirmation" },
type: "warning",
});
if (!confirmed) {
return;
}
try {
await this.sendApiService.delete(this.config.originalSend?.id);
} catch (e) {
this.toastService.showToast({
variant: "error",
title: null,
message: e.message,
});
return;
}
this.location.back();
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("deletedSend"),
});
};
/**
* Subscribes to the route query parameters and builds the configuration based on the parameters.
*/

View File

@@ -1,9 +1,13 @@
import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { BehaviorSubject } from "rxjs";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
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 { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { Folder } from "@bitwarden/common/vault/models/domain/folder";
@@ -25,6 +29,7 @@ describe("AddEditFolderDialogComponent", () => {
const save = jest.fn().mockResolvedValue(null);
const deleteFolder = jest.fn().mockResolvedValue(null);
const openSimpleDialog = jest.fn().mockResolvedValue(true);
const getUserKeyWithLegacySupport = jest.fn().mockResolvedValue("");
const error = jest.fn();
const close = jest.fn();
const showToast = jest.fn();
@@ -41,12 +46,29 @@ describe("AddEditFolderDialogComponent", () => {
close.mockClear();
showToast.mockClear();
const userId = "" as UserId;
const accountInfo: AccountInfo = {
email: "",
emailVerified: true,
name: undefined,
};
await TestBed.configureTestingModule({
imports: [AddEditFolderDialogComponent, NoopAnimationsModule],
providers: [
{ provide: I18nService, useValue: { t: (key: string) => key } },
{ provide: FolderService, useValue: { encrypt } },
{ provide: FolderApiServiceAbstraction, useValue: { save, delete: deleteFolder } },
{
provide: AccountService,
useValue: { activeAccount$: new BehaviorSubject({ id: userId, ...accountInfo }) },
},
{
provide: CryptoService,
useValue: {
getUserKeyWithLegacySupport,
},
},
{ provide: LogService, useValue: { error } },
{ provide: ToastService, useValue: { showToast } },
{ provide: DIALOG_DATA, useValue: dialogData },
@@ -82,7 +104,7 @@ describe("AddEditFolderDialogComponent", () => {
const newFolder = new FolderView();
newFolder.name = "New Folder";
expect(encrypt).toHaveBeenCalledWith(newFolder);
expect(encrypt).toHaveBeenCalledWith(newFolder, "");
expect(save).toHaveBeenCalled();
});
@@ -137,10 +159,13 @@ describe("AddEditFolderDialogComponent", () => {
component.folderForm.controls.name.setValue("Edited Folder");
await component.submit();
expect(encrypt).toHaveBeenCalledWith({
...dialogData.editFolderConfig.folder,
name: "Edited Folder",
});
expect(encrypt).toHaveBeenCalledWith(
{
...dialogData.editFolderConfig.folder,
name: "Edited Folder",
},
"",
);
});
it("deletes the folder", async () => {

View File

@@ -11,8 +11,11 @@ import {
} from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
import { firstValueFrom } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
@@ -68,6 +71,8 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit {
private formBuilder: FormBuilder,
private folderService: FolderService,
private folderApiService: FolderApiServiceAbstraction,
private accountService: AccountService,
private cryptoService: CryptoService,
private toastService: ToastService,
private i18nService: I18nService,
private logService: LogService,
@@ -107,7 +112,9 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit {
this.folder.name = this.folderForm.controls.name.value;
try {
const folder = await this.folderService.encrypt(this.folder);
const activeUserId = await firstValueFrom(this.accountService.activeAccount$);
const userKey = await this.cryptoService.getUserKeyWithLegacySupport(activeUserId.id);
const folder = await this.folderService.encrypt(this.folder, userKey);
await this.folderApiService.save(folder);
this.toastService.showToast({

View File

@@ -14,6 +14,7 @@ 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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
@@ -30,20 +31,19 @@ import {
import { PremiumUpgradePromptService } from "../../../../../../../../libs/common/src/vault/abstractions/premium-upgrade-prompt.service";
import { CipherViewComponent } from "../../../../../../../../libs/vault/src/cipher-view";
import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component";
import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component";
import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component";
import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service";
import { BrowserPremiumUpgradePromptService } from "../../../services/browser-premium-upgrade-prompt.service";
import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
import { BrowserViewPasswordHistoryService } from "../../../services/browser-view-password-history.service";
import { PopupFooterComponent } from "./../../../../../platform/popup/layout/popup-footer.component";
import { PopupHeaderComponent } from "./../../../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "./../../../../../platform/popup/layout/popup-page.component";
import { VaultPopupAutofillService } from "./../../../services/vault-popup-autofill.service";
@Component({
selector: "app-view-v2",
templateUrl: "view-v2.component.html",
standalone: true,
providers: [
{ provide: PremiumUpgradePromptService, useClass: BrowserPremiumUpgradePromptService },
],
imports: [
CommonModule,
SearchModule,
@@ -58,6 +58,10 @@ import { VaultPopupAutofillService } from "../../../services/vault-popup-autofil
AsyncActionsModule,
PopOutComponent,
],
providers: [
{ provide: ViewPasswordHistoryService, useClass: BrowserViewPasswordHistoryService },
{ provide: PremiumUpgradePromptService, useClass: BrowserPremiumUpgradePromptService },
],
})
export class ViewV2Component {
headerText: string;

View File

@@ -12,6 +12,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils";
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";
@@ -23,7 +24,6 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils";
import { DialogService } from "@bitwarden/components";
import { PasswordRepromptService } from "@bitwarden/vault";

View File

@@ -8,6 +8,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -28,6 +29,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
cipherService: CipherService,
i18nService: I18nService,
cryptoService: CryptoService,
encryptService: EncryptService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
private location: Location,
@@ -44,6 +46,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
cipherService,
i18nService,
cryptoService,
encryptService,
platformUtilsService,
apiService,
window,

View File

@@ -13,6 +13,7 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -80,6 +81,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
tokenService: TokenService,
i18nService: I18nService,
cryptoService: CryptoService,
encryptService: EncryptService,
platformUtilsService: PlatformUtilsService,
auditService: AuditService,
private route: ActivatedRoute,
@@ -108,6 +110,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
tokenService,
i18nService,
cryptoService,
encryptService,
platformUtilsService,
auditService,
window,

View File

@@ -0,0 +1,28 @@
import { TestBed } from "@angular/core/testing";
import { Router } from "@angular/router";
import { mock, MockProxy } from "jest-mock-extended";
import { BrowserViewPasswordHistoryService } from "./browser-view-password-history.service";
describe("BrowserViewPasswordHistoryService", () => {
let service: BrowserViewPasswordHistoryService;
let router: MockProxy<Router>;
beforeEach(async () => {
router = mock<Router>();
await TestBed.configureTestingModule({
providers: [BrowserViewPasswordHistoryService, { provide: Router, useValue: router }],
}).compileComponents();
service = TestBed.inject(BrowserViewPasswordHistoryService);
});
describe("viewPasswordHistory", () => {
it("navigates to the password history screen", async () => {
await service.viewPasswordHistory("test");
expect(router.navigate).toHaveBeenCalledWith(["/cipher-password-history"], {
queryParams: { cipherId: "test" },
});
});
});
});

View File

@@ -0,0 +1,18 @@
import { inject } from "@angular/core";
import { Router } from "@angular/router";
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
/**
* This class handles the premium upgrade process for the browser extension.
*/
export class BrowserViewPasswordHistoryService implements ViewPasswordHistoryService {
private router = inject(Router);
/**
* Navigates to the password history screen.
*/
async viewPasswordHistory(cipherId: string) {
await this.router.navigate(["/cipher-password-history"], { queryParams: { cipherId } });
}
}

View File

@@ -1,4 +1,4 @@
<popup-page>
<popup-page [loading]="formLoading">
<popup-header slot="header" [pageTitle]="'appearance' | i18n" showBackButton>
<ng-container slot="end">
<app-pop-out></app-pop-out>
@@ -23,10 +23,15 @@
<bit-label>{{ "showNumberOfAutofillSuggestions" | i18n }}</bit-label>
</bit-form-control>
<bit-form-control disableMargin>
<bit-form-control>
<input bitCheckbox formControlName="enableFavicon" type="checkbox" />
<bit-label>{{ "enableFavicon" | i18n }}</bit-label>
</bit-form-control>
<bit-form-control disableMargin>
<input bitCheckbox formControlName="enableAnimations" type="checkbox" />
<bit-label>{{ "showAnimations" | i18n }}</bit-label>
</bit-form-control>
</bit-card>
</form>
</popup-page>

View File

@@ -5,6 +5,7 @@ import { BehaviorSubject } from "rxjs";
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { AnimationControlService } from "@bitwarden/common/platform/abstractions/animation-control.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
@@ -41,14 +42,17 @@ describe("AppearanceV2Component", () => {
const showFavicons$ = new BehaviorSubject<boolean>(true);
const enableBadgeCounter$ = new BehaviorSubject<boolean>(true);
const selectedTheme$ = new BehaviorSubject<ThemeType>(ThemeType.Nord);
const enableRoutingAnimation$ = new BehaviorSubject<boolean>(true);
const setSelectedTheme = jest.fn().mockResolvedValue(undefined);
const setShowFavicons = jest.fn().mockResolvedValue(undefined);
const setEnableBadgeCounter = jest.fn().mockResolvedValue(undefined);
const setEnableRoutingAnimation = jest.fn().mockResolvedValue(undefined);
beforeEach(async () => {
setSelectedTheme.mockClear();
setShowFavicons.mockClear();
setEnableBadgeCounter.mockClear();
setEnableRoutingAnimation.mockClear();
await TestBed.configureTestingModule({
imports: [AppearanceV2Component],
@@ -58,11 +62,15 @@ describe("AppearanceV2Component", () => {
{ provide: MessagingService, useValue: mock<MessagingService>() },
{ provide: I18nService, useValue: { t: (key: string) => key } },
{ provide: DomainSettingsService, useValue: { showFavicons$, setShowFavicons } },
{ provide: ThemeStateService, useValue: { selectedTheme$, setSelectedTheme } },
{
provide: AnimationControlService,
useValue: { enableRoutingAnimation$, setEnableRoutingAnimation },
},
{
provide: BadgeSettingsServiceAbstraction,
useValue: { enableBadgeCounter$, setEnableBadgeCounter },
},
{ provide: ThemeStateService, useValue: { selectedTheme$, setSelectedTheme } },
],
})
.overrideComponent(AppearanceV2Component, {
@@ -82,6 +90,7 @@ describe("AppearanceV2Component", () => {
it("populates the form with the user's current settings", () => {
expect(component.appearanceForm.value).toEqual({
enableAnimations: true,
enableFavicon: true,
enableBadgeCounter: true,
theme: ThemeType.Nord,
@@ -106,5 +115,11 @@ describe("AppearanceV2Component", () => {
expect(setEnableBadgeCounter).toHaveBeenCalledWith(false);
});
it("updates the animation setting", () => {
component.appearanceForm.controls.enableAnimations.setValue(false);
expect(setEnableRoutingAnimation).toHaveBeenCalledWith(false);
});
});
});

View File

@@ -7,6 +7,7 @@ import { firstValueFrom } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { AnimationControlService } from "@bitwarden/common/platform/abstractions/animation-control.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
@@ -41,8 +42,12 @@ export class AppearanceV2Component implements OnInit {
enableFavicon: false,
enableBadgeCounter: true,
theme: ThemeType.System,
enableAnimations: true,
});
/** To avoid flashes of inaccurate values, only show the form after the entire form is populated. */
formLoading = true;
/** Available theme options */
themeOptions: { name: string; value: ThemeType }[];
@@ -53,6 +58,7 @@ export class AppearanceV2Component implements OnInit {
private themeStateService: ThemeStateService,
private formBuilder: FormBuilder,
private destroyRef: DestroyRef,
private animationControlService: AnimationControlService,
i18nService: I18nService,
) {
this.themeOptions = [
@@ -66,14 +72,20 @@ export class AppearanceV2Component implements OnInit {
const enableFavicon = await firstValueFrom(this.domainSettingsService.showFavicons$);
const enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$);
const theme = await firstValueFrom(this.themeStateService.selectedTheme$);
const enableAnimations = await firstValueFrom(
this.animationControlService.enableRoutingAnimation$,
);
// Set initial values for the form
this.appearanceForm.setValue({
enableFavicon,
enableBadgeCounter,
theme,
enableAnimations,
});
this.formLoading = false;
this.appearanceForm.controls.theme.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((newTheme) => {
@@ -91,6 +103,12 @@ export class AppearanceV2Component implements OnInit {
.subscribe((enableBadgeCounter) => {
void this.updateBadgeCounter(enableBadgeCounter);
});
this.appearanceForm.controls.enableAnimations.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((enableBadgeCounter) => {
void this.updateAnimations(enableBadgeCounter);
});
}
async updateFavicon(enableFavicon: boolean) {
@@ -105,4 +123,8 @@ export class AppearanceV2Component implements OnInit {
async saveTheme(newTheme: ThemeType) {
await this.themeStateService.setSelectedTheme(newTheme);
}
async updateAnimations(enableAnimations: boolean) {
await this.animationControlService.setEnableRoutingAnimation(enableAnimations);
}
}

View File

@@ -4,6 +4,8 @@ import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/vault/components/folder-add-edit.component";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.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";
@@ -20,6 +22,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent implement
constructor(
folderService: FolderService,
folderApiService: FolderApiServiceAbstraction,
accountService: AccountService,
cryptoService: CryptoService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
private router: Router,
@@ -31,6 +35,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent implement
super(
folderService,
folderApiService,
accountService,
cryptoService,
i18nService,
platformUtilsService,
logService,

View File

@@ -1,7 +1,7 @@
{
"name": "@bitwarden/cli",
"description": "A secure and free password manager for all of your devices.",
"version": "2024.9.0",
"version": "2024.9.1",
"keywords": [
"bitwarden",
"password",

View File

@@ -1,6 +1,6 @@
import * as fet from "node-fetch";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
@@ -9,7 +9,7 @@ import { FileResponse } from "../models/response/file.response";
import { CliUtils } from "../utils";
export abstract class DownloadCommand {
constructor(protected cryptoService: CryptoService) {}
constructor(protected encryptService: EncryptService) {}
protected async saveAttachmentToFile(
url: string,
@@ -26,7 +26,7 @@ export abstract class DownloadCommand {
try {
const encBuf = await EncArrayBuffer.fromResponse(response);
const decBuf = await this.cryptoService.decryptFromBytes(encBuf, key);
const decBuf = await this.encryptService.decryptToBytes(encBuf, key);
if (process.env.BW_SERVE === "true") {
const res = new FileResponse(Buffer.from(decBuf), fileName);
return Response.success(res);

View File

@@ -7,6 +7,7 @@ import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
import { FolderExport } from "@bitwarden/common/models/export/folder.export";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
@@ -25,6 +26,7 @@ export class EditCommand {
private cipherService: CipherService,
private folderService: FolderService,
private cryptoService: CryptoService,
private encryptService: EncryptService,
private apiService: ApiService,
private folderApiService: FolderApiServiceAbstraction,
private accountService: AccountService,
@@ -139,7 +141,10 @@ export class EditCommand {
let folderView = await folder.decrypt();
folderView = FolderExport.toView(req, folderView);
const encFolder = await this.folderService.encrypt(folderView);
const activeUserId = await firstValueFrom(this.accountService.activeAccount$);
const userKey = await this.cryptoService.getUserKeyWithLegacySupport(activeUserId.id);
const encFolder = await this.folderService.encrypt(folderView, userKey);
try {
await this.folderApiService.save(encFolder);
const updatedFolder = await this.folderService.get(folder.id);
@@ -187,7 +192,7 @@ export class EditCommand {
(u) => new SelectionReadOnlyRequest(u.id, u.readOnly, u.hidePasswords, u.manage),
);
const request = new CollectionRequest();
request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString;
request.name = (await this.encryptService.encrypt(req.name, orgKey)).encryptedString;
request.externalId = req.externalId;
request.groups = groups;
request.users = users;

View File

@@ -20,6 +20,7 @@ import { LoginExport } from "@bitwarden/common/models/export/login.export";
import { SecureNoteExport } from "@bitwarden/common/models/export/secure-note.export";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
@@ -56,7 +57,8 @@ export class GetCommand extends DownloadCommand {
private collectionService: CollectionService,
private totpService: TotpService,
private auditService: AuditService,
cryptoService: CryptoService,
private cryptoService: CryptoService,
encryptService: EncryptService,
private stateService: StateService,
private searchService: SearchService,
private apiService: ApiService,
@@ -65,7 +67,7 @@ export class GetCommand extends DownloadCommand {
private accountProfileService: BillingAccountProfileStateService,
private accountService: AccountService,
) {
super(cryptoService);
super(encryptService);
}
async run(object: string, id: string, cmdOptions: Record<string, any>): Promise<Response> {
@@ -451,7 +453,7 @@ export class GetCommand extends DownloadCommand {
const response = await this.apiService.getCollectionAccessDetails(options.organizationId, id);
const decCollection = new CollectionView(response);
decCollection.name = await this.cryptoService.decryptToUtf8(
decCollection.name = await this.encryptService.decryptToUtf8(
new EncString(response.name),
orgKey,
);

View File

@@ -57,6 +57,7 @@ export class OssServeConfigurator {
this.serviceContainer.totpService,
this.serviceContainer.auditService,
this.serviceContainer.cryptoService,
this.serviceContainer.encryptService,
this.serviceContainer.stateService,
this.serviceContainer.searchService,
this.serviceContainer.apiService,
@@ -79,6 +80,7 @@ export class OssServeConfigurator {
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.cryptoService,
this.serviceContainer.encryptService,
this.serviceContainer.apiService,
this.serviceContainer.folderApiService,
this.serviceContainer.billingAccountProfileStateService,
@@ -89,6 +91,7 @@ export class OssServeConfigurator {
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.cryptoService,
this.serviceContainer.encryptService,
this.serviceContainer.apiService,
this.serviceContainer.folderApiService,
this.serviceContainer.accountService,
@@ -150,7 +153,7 @@ export class OssServeConfigurator {
this.serviceContainer.sendService,
this.serviceContainer.environmentService,
this.serviceContainer.searchService,
this.serviceContainer.cryptoService,
this.serviceContainer.encryptService,
);
this.sendEditCommand = new SendEditCommand(
this.serviceContainer.sendService,

View File

@@ -494,6 +494,7 @@ export class ServiceContainer {
this.collectionService = new CollectionService(
this.cryptoService,
this.encryptService,
this.i18nService,
this.stateProvider,
);
@@ -631,10 +632,12 @@ export class ServiceContainer {
this.cipherFileUploadService,
this.configService,
this.stateProvider,
this.accountService,
);
this.folderService = new FolderService(
this.cryptoService,
this.encryptService,
this.i18nService,
this.cipherService,
this.stateProvider,
@@ -721,6 +724,7 @@ export class ServiceContainer {
this.i18nService,
this.collectionService,
this.cryptoService,
this.encryptService,
this.pinService,
this.accountService,
);
@@ -730,8 +734,10 @@ export class ServiceContainer {
this.cipherService,
this.pinService,
this.cryptoService,
this.encryptService,
this.cryptoFunctionService,
this.kdfConfigService,
this.accountService,
);
this.organizationExportService = new OrganizationVaultExportService(
@@ -739,6 +745,7 @@ export class ServiceContainer {
this.apiService,
this.pinService,
this.cryptoService,
this.encryptService,
this.cryptoFunctionService,
this.collectionService,
this.kdfConfigService,

View File

@@ -2,7 +2,7 @@ import { OptionValues } from "commander";
import { firstValueFrom } from "rxjs";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
@@ -17,9 +17,9 @@ export class SendGetCommand extends DownloadCommand {
private sendService: SendService,
private environmentService: EnvironmentService,
private searchService: SearchService,
cryptoService: CryptoService,
encryptService: EncryptService,
) {
super(cryptoService);
super(encryptService);
}
async run(id: string, options: OptionValues) {

View File

@@ -2,10 +2,10 @@ import { OptionValues } from "commander";
import * as inquirer from "inquirer";
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@@ -27,14 +27,14 @@ export class SendReceiveCommand extends DownloadCommand {
private sendAccessRequest: SendAccessRequest;
constructor(
private apiService: ApiService,
cryptoService: CryptoService,
private cryptoService: CryptoService,
encryptService: EncryptService,
private cryptoFunctionService: CryptoFunctionService,
private platformUtilsService: PlatformUtilsService,
private environmentService: EnvironmentService,
private sendApiService: SendApiService,
) {
super(cryptoService);
super(encryptService);
}
async run(url: string, options: OptionValues): Promise<Response> {

View File

@@ -100,8 +100,8 @@ export class SendProgram extends BaseProgram {
})
.action(async (url: string, options: OptionValues) => {
const cmd = new SendReceiveCommand(
this.serviceContainer.apiService,
this.serviceContainer.cryptoService,
this.serviceContainer.encryptService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.platformUtilsService,
this.serviceContainer.environmentService,
@@ -143,6 +143,7 @@ export class SendProgram extends BaseProgram {
this.serviceContainer.totpService,
this.serviceContainer.auditService,
this.serviceContainer.cryptoService,
this.serviceContainer.encryptService,
this.serviceContainer.stateService,
this.serviceContainer.searchService,
this.serviceContainer.apiService,
@@ -187,7 +188,7 @@ export class SendProgram extends BaseProgram {
this.serviceContainer.sendService,
this.serviceContainer.environmentService,
this.serviceContainer.searchService,
this.serviceContainer.cryptoService,
this.serviceContainer.encryptService,
);
const response = await cmd.run(id, options);
this.processResponse(response);
@@ -246,7 +247,7 @@ export class SendProgram extends BaseProgram {
this.serviceContainer.sendService,
this.serviceContainer.environmentService,
this.serviceContainer.searchService,
this.serviceContainer.cryptoService,
this.serviceContainer.encryptService,
);
const cmd = new SendEditCommand(
this.serviceContainer.sendService,

View File

@@ -178,6 +178,7 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.totpService,
this.serviceContainer.auditService,
this.serviceContainer.cryptoService,
this.serviceContainer.encryptService,
this.serviceContainer.stateService,
this.serviceContainer.searchService,
this.serviceContainer.apiService,
@@ -224,6 +225,7 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.cryptoService,
this.serviceContainer.encryptService,
this.serviceContainer.apiService,
this.serviceContainer.folderApiService,
this.serviceContainer.billingAccountProfileStateService,
@@ -272,6 +274,7 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.cryptoService,
this.serviceContainer.encryptService,
this.serviceContainer.apiService,
this.serviceContainer.folderApiService,
this.serviceContainer.accountService,

View File

@@ -12,6 +12,7 @@ import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
import { FolderExport } from "@bitwarden/common/models/export/folder.export";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
@@ -31,6 +32,7 @@ export class CreateCommand {
private cipherService: CipherService,
private folderService: FolderService,
private cryptoService: CryptoService,
private encryptService: EncryptService,
private apiService: ApiService,
private folderApiService: FolderApiServiceAbstraction,
private accountProfileService: BillingAccountProfileStateService,
@@ -167,7 +169,9 @@ export class CreateCommand {
}
private async createFolder(req: FolderExport) {
const folder = await this.folderService.encrypt(FolderExport.toView(req));
const activeAccountId = await firstValueFrom(this.accountService.activeAccount$);
const userKey = await this.cryptoService.getUserKeyWithLegacySupport(activeAccountId.id);
const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey);
try {
await this.folderApiService.save(folder);
const newFolder = await this.folderService.get(folder.id);
@@ -210,7 +214,7 @@ export class CreateCommand {
(u) => new SelectionReadOnlyRequest(u.id, u.readOnly, u.hidePasswords, u.manage),
);
const request = new CollectionRequest();
request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString;
request.name = (await this.encryptService.encrypt(req.name, orgKey)).encryptedString;
request.externalId = req.externalId;
request.groups = groups;
request.users = users;

View File

@@ -1,7 +1,7 @@
{
"name": "@bitwarden/desktop",
"description": "A secure and free password manager for all of your devices.",
"version": "2024.9.1",
"version": "2024.9.2",
"keywords": [
"bitwarden",
"password",

View File

@@ -234,7 +234,7 @@ const safeProviders: SafeProvider[] = [
provide: NativeMessageHandlerService,
deps: [
StateServiceAbstraction,
CryptoServiceAbstraction,
EncryptService,
CryptoFunctionServiceAbstraction,
MessagingServiceAbstraction,
EncryptedMessageHandlerService,

View File

@@ -1,12 +1,12 @@
{
"name": "@bitwarden/desktop",
"version": "2024.9.1",
"version": "2024.9.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@bitwarden/desktop",
"version": "2024.9.1",
"version": "2024.9.2",
"license": "GPL-3.0",
"dependencies": {
"@bitwarden/desktop-napi": "file:../desktop_native/napi",

View File

@@ -2,7 +2,7 @@
"name": "@bitwarden/desktop",
"productName": "Bitwarden",
"description": "A secure and free password manager for all of your devices.",
"version": "2024.9.1",
"version": "2024.9.2",
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
"homepage": "https://bitwarden.com",
"license": "GPL-3.0",

View File

@@ -3,7 +3,7 @@ import { firstValueFrom } from "rxjs";
import { NativeMessagingVersion } from "@bitwarden/common/enums";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@@ -31,7 +31,7 @@ export class NativeMessageHandlerService {
constructor(
private stateService: StateService,
private cryptoService: CryptoService,
private encryptService: EncryptService,
private cryptoFunctionService: CryptoFunctionService,
private messagingService: MessagingService,
private encryptedMessageHandlerService: EncryptedMessageHandlerService,
@@ -162,7 +162,7 @@ export class NativeMessageHandlerService {
payload: DecryptedCommandData,
key: SymmetricCryptoKey,
): Promise<EncString> {
return await this.cryptoService.encrypt(JSON.stringify(payload), key);
return await this.encryptService.encrypt(JSON.stringify(payload), key);
}
private async decryptPayload(message: EncryptedMessage): Promise<DecryptedCommandData> {
@@ -182,7 +182,7 @@ export class NativeMessageHandlerService {
}
try {
let decryptedResult = await this.cryptoService.decryptToUtf8(
let decryptedResult = await this.encryptService.decryptToUtf8(
message.encryptedCommand as EncString,
this.ddgSharedSecret,
);

View File

@@ -6,6 +6,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
@@ -33,6 +34,7 @@ export class NativeMessagingService {
constructor(
private cryptoFunctionService: CryptoFunctionService,
private cryptoService: CryptoService,
private encryptService: EncryptService,
private logService: LogService,
private messagingService: MessagingService,
private desktopSettingService: DesktopSettingsService,
@@ -111,7 +113,7 @@ export class NativeMessagingService {
}
const message: LegacyMessage = JSON.parse(
await this.cryptoService.decryptToUtf8(
await this.encryptService.decryptToUtf8(
rawMessage as EncString,
SymmetricCryptoKey.fromString(await ipc.platform.ephemeralStore.getEphemeralValue(appId)),
),
@@ -224,7 +226,7 @@ export class NativeMessagingService {
private async send(message: any, appId: string) {
message.timestamp = Date.now();
const encrypted = await this.cryptoService.encrypt(
const encrypted = await this.encryptService.encrypt(
JSON.stringify(message),
SymmetricCryptoKey.fromString(await ipc.platform.ephemeralStore.getEphemeralValue(appId)),
);

View File

@@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -22,6 +23,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
cipherService: CipherService,
i18nService: I18nService,
cryptoService: CryptoService,
encryptService: EncryptService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService,
@@ -36,6 +38,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
cipherService,
i18nService,
cryptoService,
encryptService,
platformUtilsService,
apiService,
window,

View File

@@ -2,6 +2,8 @@ import { Component } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/vault/components/folder-add-edit.component";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.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";
@@ -17,6 +19,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
constructor(
folderService: FolderService,
folderApiService: FolderApiServiceAbstraction,
accountService: AccountService,
cryptoService: CryptoService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
logService: LogService,
@@ -26,6 +30,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
super(
folderService,
folderApiService,
accountService,
cryptoService,
i18nService,
platformUtilsService,
logService,

View File

@@ -19,6 +19,7 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -48,6 +49,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
tokenService: TokenService,
i18nService: I18nService,
cryptoService: CryptoService,
encryptService: EncryptService,
platformUtilsService: PlatformUtilsService,
auditService: AuditService,
broadcasterService: BroadcasterService,
@@ -72,6 +74,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
tokenService,
i18nService,
cryptoService,
encryptService,
platformUtilsService,
auditService,
window,

View File

@@ -1,6 +1,6 @@
{
"name": "@bitwarden/web-vault",
"version": "2024.9.1",
"version": "2024.9.2",
"scripts": {
"build:oss": "webpack",
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",

View File

@@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -26,6 +27,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen
cipherService: CipherService,
i18nService: I18nService,
cryptoService: CryptoService,
encryptService: EncryptService,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
@@ -40,6 +42,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen
cipherService,
i18nService,
cryptoService,
encryptService,
platformUtilsService,
apiService,
window,

View File

@@ -11,7 +11,6 @@ import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/mod
import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request";
import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response";
import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
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";
@@ -127,13 +126,6 @@ export class TwoFactorAuthenticatorComponent
}
protected override async disableMethod() {
const twoFactorAuthenticatorTokenFeatureFlag = await this.configService.getFeatureFlag(
FeatureFlag.AuthenticatorTwoFactorToken,
);
if (twoFactorAuthenticatorTokenFeatureFlag === false) {
return super.disableMethod();
}
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "disable" },
content: { key: "twoStepDisableDesc" },

View File

@@ -332,20 +332,16 @@
<!-- Payment info -->
<ng-container *ngIf="formGroup.value.productTier !== productTypes.Free">
<h2 bitTypography="h4">{{ "paymentMethod" | i18n }}</h2>
<p *ngIf="!showPayment && !deprecateStripeSourcesAPI">
<p *ngIf="!showPayment && (paymentSource || billing?.paymentSource)">
<i class="bwi bwi-fw" [ngClass]="paymentSourceClasses"></i>
{{ billing?.paymentSource?.description }}
<span class="ml-2 tw-text-primary-600 tw-cursor-pointer" (click)="toggleShowPayment()">{{
"changePaymentMethod" | i18n
}}</span>
<a></a>
</p>
<p *ngIf="!showPayment && deprecateStripeSourcesAPI">
<i class="bwi bwi-fw" [ngClass]="paymentSourceClasses"></i>
{{ paymentSource?.description }}
<span class="ml-2 tw-text-primary-600 tw-cursor-pointer" (click)="toggleShowPayment()">{{
"changePaymentMethod" | i18n
}}</span>
{{
deprecateStripeSourcesAPI
? paymentSource?.description
: billing?.paymentSource?.description
}}
<span class="ml-2 tw-text-primary-600 tw-cursor-pointer" (click)="toggleShowPayment()">
{{ "changePaymentMethod" | i18n }}
</span>
<a></a>
</p>
<app-payment

View File

@@ -403,11 +403,13 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
}
get upgradeRequiresPaymentMethod() {
return (
this.organization?.productTierType === ProductTierType.Free &&
!this.showFree &&
!this.billing?.paymentSource
);
const isFreeTier = this.organization?.productTierType === ProductTierType.Free;
const shouldHideFree = !this.showFree;
const hasNoPaymentSource = this.deprecateStripeSourcesAPI
? !this.paymentSource
: !this.billing?.paymentSource;
return isFreeTier && shouldHideFree && hasNoPaymentSource;
}
get selectedSecretsManagerPlan() {

View File

@@ -23,16 +23,19 @@ import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/
import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request";
import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request";
import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { PaymentMethodType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request";
import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request";
import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request";
import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
@@ -147,19 +150,20 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService,
private encryptService: EncryptService,
private router: Router,
private syncService: SyncService,
private policyService: PolicyService,
private organizationService: OrganizationService,
private logService: LogService,
private messagingService: MessagingService,
private formBuilder: FormBuilder,
private organizationApiService: OrganizationApiServiceAbstraction,
private providerApiService: ProviderApiServiceAbstraction,
private toastService: ToastService,
private configService: ConfigService,
private billingApiService: BillingApiServiceAbstraction,
) {
this.selfHosted = platformUtilsService.isSelfHost();
this.selfHosted = this.platformUtilsService.isSelfHost();
}
async ngOnInit() {
@@ -590,7 +594,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
if (this.createOrganization) {
const orgKey = await this.cryptoService.makeOrgKey<OrgKey>();
const key = orgKey[0].encryptedString;
const collection = await this.cryptoService.encrypt(
const collection = await this.encryptService.encrypt(
this.i18nService.t("defaultCollection"),
orgKey[1],
);
@@ -658,21 +662,26 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
this.buildSecretsManagerRequest(request);
if (this.upgradeRequiresPaymentMethod) {
let type: PaymentMethodType;
let token: string;
if (this.deprecateStripeSourcesAPI) {
({ type, token } = await this.paymentV2Component.tokenize());
const updatePaymentMethodRequest = new UpdatePaymentMethodRequest();
updatePaymentMethodRequest.paymentSource = await this.paymentV2Component.tokenize();
const expandedTaxInfoUpdateRequest = new ExpandedTaxInfoUpdateRequest();
expandedTaxInfoUpdateRequest.country = this.taxComponent.country;
expandedTaxInfoUpdateRequest.postalCode = this.taxComponent.postalCode;
updatePaymentMethodRequest.taxInformation = expandedTaxInfoUpdateRequest;
await this.billingApiService.updateOrganizationPaymentMethod(
this.organizationId,
updatePaymentMethodRequest,
);
} else {
[token, type] = await this.paymentComponent.createPaymentToken();
const [paymentToken, paymentMethodType] = await this.paymentComponent.createPaymentToken();
const paymentRequest = new PaymentRequest();
paymentRequest.paymentToken = paymentToken;
paymentRequest.paymentMethodType = paymentMethodType;
paymentRequest.country = this.taxComponent.taxFormGroup?.value.country;
paymentRequest.postalCode = this.taxComponent.taxFormGroup?.value.postalCode;
await this.organizationApiService.updatePayment(this.organizationId, paymentRequest);
}
const paymentRequest = new PaymentRequest();
paymentRequest.paymentToken = token;
paymentRequest.paymentMethodType = type;
paymentRequest.country = this.taxComponent.taxFormGroup?.value.country;
paymentRequest.postalCode = this.taxComponent.taxFormGroup?.value.postalCode;
await this.organizationApiService.updatePayment(this.organizationId, paymentRequest);
}
// Backfill pub/priv key if necessary
@@ -744,7 +753,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
);
const providerKey = await this.cryptoService.getProviderKey(this.providerId);
providerRequest.organizationCreateRequest.key = (
await this.cryptoService.encrypt(orgKey.key, providerKey)
await this.encryptService.encrypt(orgKey.key, providerKey)
).encryptedString;
const orgId = (
await this.apiService.postProviderCreateOrganization(this.providerId, providerRequest)

View File

@@ -1,6 +1,6 @@
import { Component, Input } from "@angular/core";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@@ -26,7 +26,7 @@ export class SendAccessFileComponent {
constructor(
private i18nService: I18nService,
private toastService: ToastService,
private cryptoService: CryptoService,
private encryptService: EncryptService,
private fileDownloadService: FileDownloadService,
private sendApiService: SendApiService,
) {}
@@ -62,7 +62,7 @@ export class SendAccessFileComponent {
try {
const encBuf = await EncArrayBuffer.fromResponse(response);
const decBuf = await this.cryptoService.decryptFromBytes(encBuf, this.decKey);
const decBuf = await this.encryptService.decryptToBytes(encBuf, this.decKey);
this.fileDownloadService.download({
fileName: this.send.file.fileName,
blobData: decBuf,

View File

@@ -3,6 +3,7 @@ import { Injectable } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { CollectionData } from "@bitwarden/common/vault/models/data/collection.data";
@@ -23,6 +24,7 @@ export class CollectionAdminService {
constructor(
private apiService: ApiService,
private cryptoService: CryptoService,
private encryptService: EncryptService,
private collectionService: CollectionService,
) {}
@@ -116,7 +118,7 @@ export class CollectionAdminService {
const promises = collections.map(async (c) => {
const view = new CollectionAdminView();
view.id = c.id;
view.name = await this.cryptoService.decryptToUtf8(new EncString(c.name), orgKey);
view.name = await this.encryptService.decryptToUtf8(new EncString(c.name), orgKey);
view.externalId = c.externalId;
view.organizationId = c.organizationId;
@@ -146,7 +148,7 @@ export class CollectionAdminService {
}
const collection = new CollectionRequest();
collection.externalId = model.externalId;
collection.name = (await this.cryptoService.encrypt(model.name, key)).encryptedString;
collection.name = (await this.encryptService.encrypt(model.name, key)).encryptedString;
collection.groups = model.groups.map(
(group) =>
new SelectionReadOnlyRequest(group.id, group.readOnly, group.hidePasswords, group.manage),

View File

@@ -8,6 +8,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { isCardExpired } from "@bitwarden/common/autofill/utils";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { EventType } from "@bitwarden/common/enums";
@@ -24,7 +25,6 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { Launchable } from "@bitwarden/common/vault/interfaces/launchable";
import { isCardExpired } from "@bitwarden/common/vault/utils";
import { DialogService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { PasswordRepromptService } from "@bitwarden/vault";

View File

@@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -25,6 +26,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
cipherService: CipherService,
i18nService: I18nService,
cryptoService: CryptoService,
encryptService: EncryptService,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
@@ -39,6 +41,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
cipherService,
i18nService,
cryptoService,
encryptService,
platformUtilsService,
apiService,
window,

View File

@@ -1,8 +1,11 @@
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { firstValueFrom } from "rxjs";
import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/vault/components/folder-add-edit.component";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.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";
@@ -19,6 +22,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
constructor(
folderService: FolderService,
folderApiService: FolderApiServiceAbstraction,
protected accountSerivce: AccountService,
protected cryptoService: CryptoService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
logService: LogService,
@@ -31,6 +36,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
super(
folderService,
folderApiService,
accountSerivce,
cryptoService,
i18nService,
platformUtilsService,
logService,
@@ -73,7 +80,9 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
}
try {
const folder = await this.folderService.encrypt(this.folder);
const activeAccountId = (await firstValueFrom(this.accountSerivce.activeAccount$)).id;
const userKey = await this.cryptoService.getUserKeyWithLegacySupport(activeAccountId);
const folder = await this.folderService.encrypt(this.folder, userKey);
this.formPromise = this.folderApiService.save(folder);
await this.formPromise;
this.platformUtilsService.showToast(

View File

@@ -0,0 +1,40 @@
<bit-dialog dialogSize="small" background="alt">
<span bitDialogTitle>
{{ "passwordHistory" | i18n }}
</span>
<ng-container bitDialogContent>
<div *ngIf="history && history.length">
<bit-item *ngFor="let h of history">
<div class="tw-pl-3 tw-py-2">
<bit-color-password
class="tw-text-base"
[password]="h.password"
[showCount]="false"
></bit-color-password>
<div class="tw-text-sm tw-text-muted">{{ h.lastUsedDate | date: "medium" }}</div>
</div>
<ng-container slot="end">
<bit-item-action>
<button
type="button"
bitIconButton="bwi-clone"
aria-label="Copy"
appStopClick
(click)="copy(h.password)"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</bit-item-action>
</ng-container>
</bit-item>
</div>
<div class="no-items" *ngIf="!history || !history.length">
<p>{{ "noPasswordsInList" | i18n }}</p>
</div>
</ng-container>
<ng-container bitDialogFooter>
<button bitButton (click)="close()" buttonType="primary" type="button">
{{ "close" | i18n }}
</button>
</ng-container>
</bit-dialog>

View File

@@ -0,0 +1,131 @@
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { OnInit, Inject, Component } from "@angular/core";
import { firstValueFrom, map } from "rxjs";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { CipherId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordHistoryView } from "@bitwarden/common/vault/models/view/password-history.view";
import {
AsyncActionsModule,
DialogModule,
DialogService,
ToastService,
ItemModule,
} from "@bitwarden/components";
import { SharedModule } from "../../shared/shared.module";
/**
* The parameters for the password history dialog.
*/
export interface ViewPasswordHistoryDialogParams {
cipherId: CipherId;
}
/**
* A dialog component that displays the password history for a cipher.
*/
@Component({
selector: "app-vault-password-history",
templateUrl: "password-history.component.html",
standalone: true,
imports: [CommonModule, AsyncActionsModule, DialogModule, ItemModule, SharedModule],
})
export class PasswordHistoryComponent implements OnInit {
/**
* The ID of the cipher to display the password history for.
*/
cipherId: CipherId;
/**
* The password history for the cipher.
*/
history: PasswordHistoryView[] = [];
/**
* The constructor for the password history dialog component.
* @param params The parameters passed to the password history dialog.
* @param cipherService The cipher service - used to get the cipher to display the password history for.
* @param platformUtilsService The platform utils service - used to copy passwords to the clipboard.
* @param i18nService The i18n service - used to translate strings.
* @param accountService The account service - used to get the active account to decrypt the cipher.
* @param win The window object - used to copy passwords to the clipboard.
* @param toastService The toast service - used to display feedback to the user when a password is copied.
* @param dialogRef The dialog reference - used to close the dialog.
**/
constructor(
@Inject(DIALOG_DATA) public params: ViewPasswordHistoryDialogParams,
protected cipherService: CipherService,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
protected accountService: AccountService,
@Inject(WINDOW) private win: Window,
protected toastService: ToastService,
private dialogRef: DialogRef<PasswordHistoryComponent>,
) {
/**
* Set the cipher ID from the parameters.
*/
this.cipherId = params.cipherId;
}
async ngOnInit() {
await this.init();
}
/**
* Copies a password to the clipboard.
* @param password The password to copy.
*/
copy(password: string) {
const copyOptions = this.win != null ? { window: this.win } : undefined;
this.platformUtilsService.copyToClipboard(password, copyOptions);
this.toastService.showToast({
variant: "info",
title: "",
message: this.i18nService.t("valueCopied", this.i18nService.t("password")),
});
}
/**
* Initializes the password history dialog component.
*/
protected async init() {
const cipher = await this.cipherService.get(this.cipherId);
const activeAccount = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a: { id: string | undefined }) => a)),
);
if (!activeAccount || !activeAccount.id) {
throw new Error("Active account is not available.");
}
const activeUserId = activeAccount.id as UserId;
const decCipher = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory;
}
/**
* Closes the password history dialog.
*/
close() {
this.dialogRef.close();
}
}
/**
* Strongly typed wrapper around the dialog service to open the password history dialog.
*/
export function openPasswordHistoryDialog(
dialogService: DialogService,
config: DialogConfig<ViewPasswordHistoryDialogParams>,
) {
return dialogService.open(PasswordHistoryComponent, config);
}

View File

@@ -8,6 +8,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
@@ -22,6 +23,7 @@ import { PremiumUpgradePromptService } from "../../../../../../libs/common/src/v
import { CipherViewComponent } from "../../../../../../libs/vault/src/cipher-view/cipher-view.component";
import { SharedModule } from "../../shared/shared.module";
import { WebVaultPremiumUpgradePromptService } from "../services/web-premium-upgrade-prompt.service";
import { WebViewPasswordHistoryService } from "../services/web-view-password-history.service";
export interface ViewCipherDialogParams {
cipher: CipherView;
@@ -57,6 +59,7 @@ export interface ViewCipherDialogCloseResult {
standalone: true,
imports: [CipherViewComponent, CommonModule, AsyncActionsModule, DialogModule, SharedModule],
providers: [
{ provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService },
{ provide: PremiumUpgradePromptService, useClass: WebVaultPremiumUpgradePromptService },
],
})

View File

@@ -5,6 +5,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -31,6 +32,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
cipherService: CipherService,
i18nService: I18nService,
cryptoService: CryptoService,
encryptService: EncryptService,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
@@ -45,6 +47,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
cipherService,
i18nService,
cryptoService,
encryptService,
stateService,
platformUtilsService,
apiService,

View File

@@ -11,7 +11,6 @@ import { ActivatedRoute, Params, Router } from "@angular/router";
import {
BehaviorSubject,
combineLatest,
defer,
firstValueFrom,
lastValueFrom,
Observable,
@@ -303,27 +302,10 @@ export class VaultComponent implements OnInit, OnDestroy {
this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
this.allCollectionsWithoutUnassigned$ = combineLatest([
organizationId$.pipe(switchMap((orgId) => this.collectionAdminService.getAll(orgId))),
defer(() => this.collectionService.getAllDecrypted()),
]).pipe(
map(([adminCollections, syncCollections]) => {
const syncCollectionDict = Object.fromEntries(syncCollections.map((c) => [c.id, c]));
return adminCollections.map((collection) => {
const currentId: any = collection.id;
const match = syncCollectionDict[currentId];
if (match) {
collection.manage = match.manage;
collection.readOnly = match.readOnly;
collection.hidePasswords = match.hidePasswords;
}
return collection;
});
}),
shareReplay({ refCount: true, bufferSize: 1 }),
this.allCollectionsWithoutUnassigned$ = this.refresh$.pipe(
switchMap(() => organizationId$),
switchMap((orgId) => this.collectionAdminService.getAll(orgId)),
shareReplay({ refCount: false, bufferSize: 1 }),
);
this.editableCollections$ = this.allCollectionsWithoutUnassigned$.pipe(
@@ -356,8 +338,8 @@ export class VaultComponent implements OnInit, OnDestroy {
shareReplay({ refCount: true, bufferSize: 1 }),
);
const allCiphers$ = organization$.pipe(
concatMap(async (organization) => {
const allCiphers$ = combineLatest([organization$, this.refresh$]).pipe(
switchMap(async ([organization]) => {
// If user swaps organization reset the addAccessToggle
if (!this.showAddAccessToggle || organization) {
this.addAccessToggle(0);
@@ -381,13 +363,13 @@ export class VaultComponent implements OnInit, OnDestroy {
await this.searchService.indexCiphers(ciphers, organization.id);
return ciphers;
}),
shareReplay({ refCount: true, bufferSize: 1 }),
);
const allCipherMap$ = allCiphers$.pipe(
map((ciphers) => {
return Object.fromEntries(ciphers.map((c) => [c.id, c]));
}),
shareReplay({ refCount: true, bufferSize: 1 }),
);
const nestedCollections$ = allCollections$.pipe(

View File

@@ -0,0 +1,45 @@
import { Overlay } from "@angular/cdk/overlay";
import { TestBed } from "@angular/core/testing";
import { CipherId } from "@bitwarden/common/types/guid";
import { DialogService } from "@bitwarden/components";
import { openPasswordHistoryDialog } from "../individual-vault/password-history.component";
import { WebViewPasswordHistoryService } from "./web-view-password-history.service";
jest.mock("../individual-vault/password-history.component", () => ({
openPasswordHistoryDialog: jest.fn(),
}));
describe("WebViewPasswordHistoryService", () => {
let service: WebViewPasswordHistoryService;
let dialogService: DialogService;
beforeEach(async () => {
const mockDialogService = {
open: jest.fn(),
};
await TestBed.configureTestingModule({
providers: [
WebViewPasswordHistoryService,
{ provide: DialogService, useValue: mockDialogService },
Overlay,
],
}).compileComponents();
service = TestBed.inject(WebViewPasswordHistoryService);
dialogService = TestBed.inject(DialogService);
});
describe("viewPasswordHistory", () => {
it("calls openPasswordHistoryDialog with the correct parameters", async () => {
const mockCipherId = "cipher-id" as CipherId;
await service.viewPasswordHistory(mockCipherId);
expect(openPasswordHistoryDialog).toHaveBeenCalledWith(dialogService, {
data: { cipherId: mockCipherId },
});
});
});
});

View File

@@ -0,0 +1,23 @@
import { Injectable } from "@angular/core";
import { CipherId } from "@bitwarden/common/types/guid";
import { DialogService } from "@bitwarden/components";
import { ViewPasswordHistoryService } from "../../../../../../libs/common/src/vault/abstractions/view-password-history.service";
import { openPasswordHistoryDialog } from "../individual-vault/password-history.component";
/**
* This service is used to display the password history dialog in the web vault.
*/
@Injectable()
export class WebViewPasswordHistoryService implements ViewPasswordHistoryService {
constructor(private dialogService: DialogService) {}
/**
* Opens the password history dialog for the given cipher ID.
* @param cipherId The ID of the cipher to view the password history for.
*/
async viewPasswordHistory(cipherId: CipherId) {
openPasswordHistoryDialog(this.dialogService, { data: { cipherId } });
}
}