1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-07 02:53:28 +00:00

Merge branch 'main' into PM-12985-Reports

This commit is contained in:
cd-bitwarden
2025-01-08 16:59:05 -05:00
committed by GitHub
139 changed files with 2937 additions and 2049 deletions

View File

@@ -15,12 +15,13 @@ 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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { PasswordRepromptService } from "@bitwarden/vault";
@@ -56,6 +57,8 @@ export class EmergencyAddEditCipherComponent extends BaseAddEditComponent implem
configService: ConfigService,
billingAccountProfileStateService: BillingAccountProfileStateService,
cipherAuthorizationService: CipherAuthorizationService,
toastService: ToastService,
sdkService: SdkService,
) {
super(
cipherService,
@@ -78,6 +81,8 @@ export class EmergencyAddEditCipherComponent extends BaseAddEditComponent implem
configService,
billingAccountProfileStateService,
cipherAuthorizationService,
toastService,
sdkService,
);
}

View File

@@ -4,6 +4,7 @@ import { firstValueFrom, of } from "rxjs";
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
import { UserId } from "@bitwarden/common/types/guid";
import { BiometricsStatus } from "@bitwarden/key-management";
import { WebLockComponentService } from "./web-lock-component.service";
@@ -86,7 +87,7 @@ describe("WebLockComponentService", () => {
},
biometrics: {
enabled: false,
disableReason: null,
biometricsStatus: BiometricsStatus.PlatformUnsupported,
},
});
});

View File

@@ -6,6 +6,7 @@ import {
UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common";
import { UserId } from "@bitwarden/common/types/guid";
import { BiometricsStatus } from "@bitwarden/key-management";
import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular";
export class WebLockComponentService implements LockComponentService {
@@ -45,7 +46,7 @@ export class WebLockComponentService implements LockComponentService {
},
biometrics: {
enabled: false,
disableReason: null,
biometricsStatus: BiometricsStatus.PlatformUnsupported,
},
};
return unlockOpts;

View File

@@ -1,27 +1,27 @@
import { BiometricsService } from "@bitwarden/key-management";
import { UserId } from "@bitwarden/common/types/guid";
import { UserKey } from "@bitwarden/common/types/key";
import { BiometricsService, BiometricsStatus } from "@bitwarden/key-management";
export class WebBiometricsService extends BiometricsService {
async supportsBiometric(): Promise<boolean> {
async authenticateWithBiometrics(): Promise<boolean> {
return false;
}
async isBiometricUnlockAvailable(): Promise<boolean> {
async getBiometricsStatus(): Promise<BiometricsStatus> {
return BiometricsStatus.PlatformUnsupported;
}
async unlockWithBiometricsForUser(userId: UserId): Promise<UserKey | null> {
return null;
}
async getBiometricsStatusForUser(userId: UserId): Promise<BiometricsStatus> {
return BiometricsStatus.PlatformUnsupported;
}
async getShouldAutopromptNow(): Promise<boolean> {
return false;
}
async authenticateBiometric(): Promise<boolean> {
throw new Error("Method not implemented.");
}
async biometricsNeedsSetup(): Promise<boolean> {
throw new Error("Method not implemented.");
}
async biometricsSupportsAutoSetup(): Promise<boolean> {
throw new Error("Method not implemented.");
}
async biometricsSetup(): Promise<void> {
throw new Error("Method not implemented.");
}
async setShouldAutopromptNow(value: boolean): Promise<void> {}
}

View File

@@ -40,6 +40,7 @@ import {
CipherFormGenerationService,
CipherFormModule,
CipherViewComponent,
DecryptionFailureDialogComponent,
} from "@bitwarden/vault";
import { SharedModule } from "../../../shared/shared.module";
@@ -114,6 +115,7 @@ export enum VaultItemDialogResult {
CipherAttachmentsComponent,
AsyncActionsModule,
ItemModule,
DecryptionFailureDialogComponent,
],
providers: [
{ provide: PremiumUpgradePromptService, useClass: WebVaultPremiumUpgradePromptService },
@@ -252,6 +254,14 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
this.cipher = await this.getDecryptedCipherView(this.formConfig);
if (this.cipher) {
if (this.cipher.decryptionFailure) {
this.dialogService.open(DecryptionFailureDialogComponent, {
data: { cipherIds: [this.cipher.id] },
});
this.dialogRef.close();
return;
}
this.collections = this.formConfig.collections.filter((c) =>
this.cipher.collectionIds?.includes(c.id),
);

View File

@@ -4,7 +4,7 @@
type="checkbox"
bitCheckbox
appStopProp
[disabled]="disabled"
[disabled]="disabled || cipher.decryptionFailure"
[checked]="checked"
(change)="$event ? this.checkedToggled.next() : null"
[attr.aria-label]="'vaultItemSelect' | i18n"
@@ -20,7 +20,7 @@
class="tw-overflow-hidden tw-text-ellipsis tw-text-start tw-leading-snug"
[disabled]="disabled"
[routerLink]="[]"
[queryParams]="{ itemId: cipher.id, action: extensionRefreshEnabled ? 'view' : null }"
[queryParams]="{ itemId: cipher.id, action: clickAction }"
queryParamsHandling="merge"
[replaceUrl]="extensionRefreshEnabled"
title="{{ 'editItemWithName' | i18n: cipher.name }}"
@@ -76,6 +76,25 @@
</td>
<td bitCell [ngClass]="RowHeightClass" class="tw-text-right">
<button
*ngIf="cipher.decryptionFailure"
[disabled]="disabled || !canManageCollection"
[bitMenuTriggerFor]="corruptedCipherOptions"
size="small"
bitIconButton="bwi-ellipsis-v"
type="button"
appA11yTitle="{{ 'options' | i18n }}"
appStopProp
></button>
<bit-menu #corruptedCipherOptions>
<button bitMenuItem *ngIf="canManageCollection" (click)="deleteCipher()" type="button">
<span class="tw-text-danger">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
{{ (cipher.isDeleted ? "permanentlyDelete" : "delete") | i18n }}
</span>
</button>
</bit-menu>
<button
*ngIf="!cipher.decryptionFailure"
[disabled]="disabled || disableMenu"
[bitMenuTriggerFor]="cipherOptions"
size="small"

View File

@@ -78,6 +78,13 @@ export class VaultCipherRowComponent implements OnInit {
}
}
protected get clickAction() {
if (this.cipher.decryptionFailure) {
return "showFailedToDecrypt";
}
return this.extensionRefreshEnabled ? "view" : null;
}
protected get showTotpCopyButton() {
return (
(this.cipher.login?.hasTotp ?? false) &&

View File

@@ -35,6 +35,7 @@
[(ngModel)]="cipher.type"
class="form-control"
[disabled]="cipher.isDeleted"
(change)="typeChange()"
appAutofocus
>
<option *ngFor="let o of typeOptions" [ngValue]="o.value">{{ o.name }}</option>

View File

@@ -21,13 +21,14 @@ 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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
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 { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { PasswordRepromptService } from "@bitwarden/vault";
@@ -73,6 +74,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
configService: ConfigService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
cipherAuthorizationService: CipherAuthorizationService,
toastService: ToastService,
sdkService: SdkService,
) {
super(
cipherService,
@@ -93,6 +96,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
datePipe,
configService,
cipherAuthorizationService,
toastService,
sdkService,
);
}

View File

@@ -99,6 +99,10 @@
<i class="bwi bwi-sticky-note" slot="start" aria-hidden="true"></i>
{{ "note" | i18n }}
</button>
<button type="button" bitMenuItem (click)="addCipher(CipherType.SshKey)">
<i class="bwi bwi-key" slot="start" aria-hidden="true"></i>
{{ "typeSshKey" | i18n }}
</button>
<bit-menu-divider />
<button type="button" bitMenuItem (click)="addFolder()">
<i class="bwi bwi-fw bwi-folder" aria-hidden="true"></i>

View File

@@ -29,6 +29,7 @@ import {
map,
shareReplay,
switchMap,
take,
takeUntil,
tap,
} from "rxjs/operators";
@@ -75,6 +76,7 @@ import { DialogService, Icons, ToastService } from "@bitwarden/components";
import {
CipherFormConfig,
CollectionAssignmentResult,
DecryptionFailureDialogComponent,
DefaultCipherFormConfigService,
PasswordRepromptService,
} from "@bitwarden/vault";
@@ -144,6 +146,7 @@ const SearchTextDebounceInterval = 200;
VaultFilterModule,
VaultItemsModule,
SharedModule,
DecryptionFailureDialogComponent,
],
providers: [
RoutedVaultFilterService,
@@ -359,13 +362,16 @@ export class VaultComponent implements OnInit, OnDestroy {
]).pipe(
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
concatMap(async ([ciphers, filter, searchText]) => {
const failedCiphers = await firstValueFrom(this.cipherService.failedToDecryptCiphers$);
const filterFunction = createFilterFunction(filter);
// Append any failed to decrypt ciphers to the top of the cipher list
const allCiphers = [...failedCiphers, ...ciphers];
if (await this.searchService.isSearchable(searchText)) {
return await this.searchService.searchCiphers(searchText, [filterFunction], ciphers);
return await this.searchService.searchCiphers(searchText, [filterFunction], allCiphers);
}
return ciphers.filter(filterFunction);
return allCiphers.filter(filterFunction);
}),
shareReplay({ refCount: true, bufferSize: 1 }),
);
@@ -436,6 +442,18 @@ export class VaultComponent implements OnInit, OnDestroy {
action = "view";
}
if (action == "showFailedToDecrypt") {
DecryptionFailureDialogComponent.open(this.dialogService, {
cipherIds: [cipherId as CipherId],
});
await this.router.navigate([], {
queryParams: { itemId: null, cipherId: null, action: null },
queryParamsHandling: "merge",
replaceUrl: true,
});
return;
}
if (action === "view") {
await this.viewCipherById(cipherId);
} else {
@@ -458,6 +476,20 @@ export class VaultComponent implements OnInit, OnDestroy {
)
.subscribe();
firstSetup$
.pipe(
switchMap(() => this.cipherService.failedToDecryptCiphers$),
map((ciphers) => ciphers.filter((c) => !c.isDeleted)),
filter((ciphers) => ciphers.length > 0),
take(1),
takeUntil(this.destroy$),
)
.subscribe((ciphers) => {
DecryptionFailureDialogComponent.open(this.dialogService, {
cipherIds: ciphers.map((c) => c.id as CipherId),
});
});
this.unpaidSubscriptionDialog$.pipe(takeUntil(this.destroy$)).subscribe();
firstSetup$
@@ -747,16 +779,26 @@ export class VaultComponent implements OnInit, OnDestroy {
null,
cipherType,
);
const collectionId =
this.activeFilter.collectionId !== "AllCollections" && this.activeFilter.collectionId != null
? this.activeFilter.collectionId
: null;
let organizationId =
this.activeFilter.organizationId !== "MyVault" && this.activeFilter.organizationId != null
? this.activeFilter.organizationId
: null;
// Attempt to get the organization ID from the collection if present
if (collectionId) {
const organizationIdFromCollection = (
await firstValueFrom(this.vaultFilterService.filteredCollections$)
).find((c) => c.id === this.activeFilter.collectionId)?.organizationId;
if (organizationIdFromCollection) {
organizationId = organizationIdFromCollection;
}
}
cipherFormConfig.initialValues = {
organizationId:
this.activeFilter.organizationId !== "MyVault" && this.activeFilter.organizationId != null
? (this.activeFilter.organizationId as OrganizationId)
: null,
collectionIds:
this.activeFilter.collectionId !== "AllCollections" &&
this.activeFilter.collectionId != null
? [this.activeFilter.collectionId as CollectionId]
: [],
organizationId: organizationId as OrganizationId,
collectionIds: [collectionId as CollectionId],
folderId: this.activeFilter.folderId,
};

View File

@@ -16,6 +16,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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.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";
@@ -23,7 +24,7 @@ import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { PasswordRepromptService } from "@bitwarden/vault";
@@ -59,6 +60,8 @@ export class AddEditComponent extends BaseAddEditComponent {
configService: ConfigService,
billingAccountProfileStateService: BillingAccountProfileStateService,
cipherAuthorizationService: CipherAuthorizationService,
toastService: ToastService,
sdkService: SdkService,
) {
super(
cipherService,
@@ -81,6 +84,8 @@ export class AddEditComponent extends BaseAddEditComponent {
configService,
billingAccountProfileStateService,
cipherAuthorizationService,
toastService,
sdkService,
);
}

View File

@@ -38,9 +38,9 @@ import {
import {
CollectionAdminService,
CollectionAdminView,
Unassigned,
CollectionService,
CollectionView,
Unassigned,
} from "@bitwarden/admin-console/common";
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service";
@@ -71,16 +71,17 @@ import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
import {
BannerModule,
DialogService,
Icons,
NoItemsModule,
ToastService,
BannerModule,
} from "@bitwarden/components";
import {
CipherFormConfig,
CipherFormConfigService,
CollectionAssignmentResult,
DecryptionFailureDialogComponent,
PasswordRepromptService,
} from "@bitwarden/vault";
@@ -134,6 +135,7 @@ import {
import { CollectionAccessRestrictedComponent } from "./collection-access-restricted.component";
import { AdminConsoleCipherFormConfigService } from "./services/admin-console-cipher-form-config.service";
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
const BroadcasterSubscriptionId = "OrgVaultComponent";
const SearchTextDebounceInterval = 200;
@@ -549,11 +551,24 @@ export class VaultComponent implements OnInit, OnDestroy {
if (cipher) {
let action = qParams.action;
// Default to "view" if extension refresh is enabled
if (action == null && this.extensionRefreshEnabled) {
action = "view";
}
if (action == "showFailedToDecrypt") {
DecryptionFailureDialogComponent.open(this.dialogService, {
cipherIds: [cipherId as CipherId],
});
await this.router.navigate([], {
queryParams: { itemId: null, cipherId: null, action: null },
queryParamsHandling: "merge",
replaceUrl: true,
});
return;
}
if (action === "view") {
await this.viewCipherById(cipher);
} else {

View File

@@ -5676,6 +5676,20 @@
"error": {
"message": "Error"
},
"decryptionError": {
"message": "Decryption error"
},
"couldNotDecryptVaultItemsBelow": {
"message": "Bitwarden could not decrypt the vault item(s) listed below."
},
"contactCSToAvoidDataLossPart1": {
"message": "Contact customer success",
"description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'"
},
"contactCSToAvoidDataLossPart2": {
"message": "to avoid additional data loss.",
"description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'"
},
"accountRecoveryManageUsers": {
"message": "Manage users must also be granted with the manage account recovery permission"
},