diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 187f500828c..df71b6545fb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -131,6 +131,7 @@ apps/web/src/translation-constants.ts @bitwarden/team-platform-dev .github/workflows/version-auto-bump.yml @bitwarden/team-platform-dev # ESLint custom rules libs/eslint @bitwarden/team-platform-dev +libs/eslint/components @bitwarden/team-ui-foundation # Typescript tooling tsconfig.base.json @bitwarden/team-platform-dev nx.json @bitwarden/team-platform-dev @@ -155,6 +156,7 @@ apps/desktop/desktop_native/core/src/ssh_agent @bitwarden/team-autofill-dev @bit ## UI Foundation ## .storybook @bitwarden/team-ui-foundation libs/components @bitwarden/team-ui-foundation +libs/assets @bitwarden/team-ui-foundation libs/ui @bitwarden/team-ui-foundation apps/browser/src/platform/popup/layout @bitwarden/team-ui-foundation apps/browser/src/popup/app-routing.animations.ts @bitwarden/team-ui-foundation diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 906bbbd7125..e646049c3d6 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -197,6 +197,7 @@ "nx", "oo7", "oslog", + "parse5", "pin-project", "pkg", "postcss", diff --git a/apps/browser/package.json b/apps/browser/package.json index e75d2b235db..0f47e5d93d3 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.8.0", + "version": "2025.8.1", "scripts": { "build": "npm run build:chrome", "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index f8dde376b35..8390bc59633 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4131,15 +4131,11 @@ "selectImportCollection": { "message": "Select a collection" }, - "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", - "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", - "placeholders": { - "destination": { - "content": "$1", - "example": "folder or collection" - } - } + "importTargetHintCollection": { + "message": "Select this option if you want the imported file contents moved to a collection" + }, + "importTargetHintFolder": { + "message": "Select this option if you want the imported file contents moved to a folder" }, "importUnassignedItemsError": { "message": "File contains unassigned items." @@ -5579,5 +5575,9 @@ }, "showLess": { "message": "Show less" + }, + "moreBreadcrumbs": { + "message": "More breadcrumbs", + "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." } } diff --git a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts index 014f2a7638b..63666440a76 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts @@ -24,7 +24,6 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { MessageSender } from "@bitwarden/common/platform/messaging"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -79,7 +78,6 @@ describe("AccountSecurityComponent", () => { { provide: PlatformUtilsService, useValue: platformUtilsService }, { provide: PolicyService, useValue: policyService }, { provide: PopupRouterCacheService, useValue: mock() }, - { provide: StateService, useValue: mock() }, { provide: ToastService, useValue: mock() }, { provide: UserVerificationService, useValue: mock() }, { provide: VaultTimeoutService, useValue: mock() }, diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index b41cfe14c4f..72a389ecf71 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -44,9 +44,9 @@ import { import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.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 { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { DialogRef, @@ -149,7 +149,6 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { public messagingService: MessagingService, private environmentService: EnvironmentService, private keyService: KeyService, - private stateService: StateService, private userVerificationService: UserVerificationService, private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, @@ -159,6 +158,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { private vaultNudgesService: NudgesService, private validationService: ValidationService, private configService: ConfigService, + private logService: LogService, ) {} async ngOnInit() { @@ -683,10 +683,16 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { } async openAcctFingerprintDialog() { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const publicKey = await firstValueFrom(this.keyService.userPublicKey$(activeUserId)); + if (publicKey == null) { + this.logService.error( + "[AccountSecurityComponent] No public key available for the user: " + + activeUserId + + " fingerprint can't be displayed.", + ); + return; + } const fingerprint = await this.keyService.getFingerprint(activeUserId, publicKey); const dialogRef = FingerprintDialogComponent.open(this.dialogService, { diff --git a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts index 62f9dbec824..2c9484c3a8b 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts @@ -41,6 +41,10 @@ describe("AutofillInlineMenuContentService", () => { autofillInlineMenuContentService as any, "sendExtensionMessage", ); + jest.spyOn(autofillInlineMenuContentService as any, "getPageIsOpaque"); + jest + .spyOn(autofillInlineMenuContentService as any, "getPageTopLayerInUse") + .mockResolvedValue(false); }); afterEach(() => { @@ -386,30 +390,45 @@ describe("AutofillInlineMenuContentService", () => { expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled(); }); + it("closes the inline menu if the page has content in the top layer", async () => { + document.querySelector("html").style.opacity = "1"; + document.body.style.opacity = "1"; + + jest + .spyOn(autofillInlineMenuContentService as any, "getPageTopLayerInUse") + .mockResolvedValue(true); + + await autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]); + + expect(autofillInlineMenuContentService["getPageIsOpaque"]).toHaveReturnedWith(true); + expect(autofillInlineMenuContentService["closeInlineMenu"]).toHaveBeenCalled(); + }); + it("closes the inline menu if the page body is not sufficiently opaque", async () => { document.querySelector("html").style.opacity = "0.9"; document.body.style.opacity = "0"; - autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]); + await autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]); - expect(autofillInlineMenuContentService["pageIsOpaque"]).toBe(false); + expect(autofillInlineMenuContentService["getPageIsOpaque"]).toHaveReturnedWith(false); expect(autofillInlineMenuContentService["closeInlineMenu"]).toHaveBeenCalled(); }); it("closes the inline menu if the page html is not sufficiently opaque", async () => { document.querySelector("html").style.opacity = "0.3"; document.body.style.opacity = "0.7"; - autofillInlineMenuContentService["handlePageMutations"]([mockHTMLMutationRecord]); + await autofillInlineMenuContentService["handlePageMutations"]([mockHTMLMutationRecord]); - expect(autofillInlineMenuContentService["pageIsOpaque"]).toBe(false); + expect(autofillInlineMenuContentService["getPageIsOpaque"]).toHaveReturnedWith(false); expect(autofillInlineMenuContentService["closeInlineMenu"]).toHaveBeenCalled(); }); it("does not close the inline menu if the page html and body is sufficiently opaque", async () => { document.querySelector("html").style.opacity = "0.9"; document.body.style.opacity = "1"; - autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]); + await autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]); + await waitForIdleCallback(); - expect(autofillInlineMenuContentService["pageIsOpaque"]).toBe(true); + expect(autofillInlineMenuContentService["getPageIsOpaque"]).toHaveReturnedWith(true); expect(autofillInlineMenuContentService["closeInlineMenu"]).not.toHaveBeenCalled(); }); diff --git a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts index de401bf7e28..c531215af88 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts @@ -33,7 +33,6 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte private listElement?: HTMLElement; private htmlMutationObserver: MutationObserver; private bodyMutationObserver: MutationObserver; - private pageIsOpaque = true; private inlineMenuElementsMutationObserver: MutationObserver; private containerElementMutationObserver: MutationObserver; private mutationObserverIterations = 0; @@ -52,7 +51,6 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte }; constructor() { - this.checkPageOpacity(); this.setupMutationObserver(); } @@ -405,20 +403,35 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte }); }; - private checkPageOpacity = () => { - this.pageIsOpaque = this.getPageIsOpaque(); + private checkPageRisks = async () => { + const pageIsOpaque = await this.getPageIsOpaque(); + const pageTopLayerInUse = await this.getPageTopLayerInUse(); - if (!this.pageIsOpaque) { + const risksFound = !pageIsOpaque || pageTopLayerInUse; + + if (risksFound) { this.closeInlineMenu(); } + + return risksFound; + }; + + /* + * Checks for known risks at the page level + */ + private handlePageMutations = async (mutations: MutationRecord[]) => { + if (mutations.some(({ type }) => type === "attributes")) { + await this.checkPageRisks(); + } }; - private handlePageMutations = (mutations: MutationRecord[]) => { - for (const mutation of mutations) { - if (mutation.type === "attributes") { - this.checkPageOpacity(); - } - } + /** + * Checks if the page top layer has content (will obscure/overlap the inline menu) + */ + private getPageTopLayerInUse = () => { + const pageHasOpenPopover = !!globalThis.document.querySelector(":popover-open"); + + return pageHasOpenPopover; }; /** @@ -427,7 +440,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte * of parents below the body. Assumes the target element will be a direct child of the page * `body` (enforced elsewhere). */ - private getPageIsOpaque() { + private getPageIsOpaque = () => { // These are computed style values, so we don't need to worry about non-float values // for `opacity`, here const htmlOpacity = globalThis.window.getComputedStyle( @@ -441,17 +454,16 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte const opacityThreshold = 0.6; return parseFloat(htmlOpacity) > opacityThreshold && parseFloat(bodyOpacity) > opacityThreshold; - } + }; /** * Processes the mutation of the element that contains the inline menu. Will trigger when an * idle moment in the execution of the main thread is detected. */ private processContainerElementMutation = async (containerElement: HTMLElement) => { - // If the computed opacity of the body and parent is not sufficiently opaque, tear - // down and prevent building the inline menu experience. - this.checkPageOpacity(); - if (!this.pageIsOpaque) { + // If the page contains risks, tear down and prevent building the inline menu experience. + const pageRisksFound = await this.checkPageRisks(); + if (pageRisksFound) { return; } diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index ac38fe2f894..11e00749bdf 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -18,6 +18,7 @@ import { } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { NoResults } from "@bitwarden/assets/svg"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; @@ -36,7 +37,6 @@ import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note. import { ButtonModule, DialogService, - Icons, ItemModule, NoItemsModule, SearchModule, @@ -102,7 +102,7 @@ export class Fido2Component implements OnInit, OnDestroy { protected equivalentDomainsURL: string; protected hostname: string; protected loading = false; - protected noResultsIcon = Icons.NoResults; + protected noResultsIcon = NoResults; protected passkeyAction: PasskeyActionValue = PasskeyActions.Register; protected PasskeyActions = PasskeyActions; protected hasSearched = false; diff --git a/apps/browser/src/autofill/popup/settings/blocked-domains.component.html b/apps/browser/src/autofill/popup/settings/blocked-domains.component.html index 8156525301b..6a08b4483af 100644 --- a/apps/browser/src/autofill/popup/settings/blocked-domains.component.html +++ b/apps/browser/src/autofill/popup/settings/blocked-domains.component.html @@ -30,7 +30,7 @@ diff --git a/apps/browser/src/platform/popup/layout/popup-header.component.html b/apps/browser/src/platform/popup/layout/popup-header.component.html index 014ebc86411..2aac161b9d5 100644 --- a/apps/browser/src/platform/popup/layout/popup-header.component.html +++ b/apps/browser/src/platform/popup/layout/popup-header.component.html @@ -19,8 +19,7 @@ bitIconButton="bwi-angle-left" type="button" *ngIf="showBackButton" - [title]="'back' | i18n" - [attr.aria-label]="'back' | i18n" + [label]="'back' | i18n" [bitAction]="backAction" >

diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index aeeed6f65ce..a7103fdfd3c 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -4,6 +4,16 @@ import { Component, importProvidersFrom } from "@angular/core"; import { RouterModule } from "@angular/router"; import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; +import { + GeneratorActive, + GeneratorInactive, + SendActive, + SendInactive, + SettingsActive, + SettingsInactive, + VaultActive, + VaultInactive, +} from "@bitwarden/assets/svg"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; @@ -13,7 +23,6 @@ import { BannerModule, ButtonModule, I18nMockService, - Icons, IconButtonModule, ItemModule, NoItemsModule, @@ -67,14 +76,10 @@ class ExtensionPoppedContainerComponent {} - + - + @@ -102,13 +107,7 @@ class MockAddButtonComponent {} @Component({ selector: "mock-popout-button", template: ` - + `, imports: [IconButtonModule], }) @@ -278,7 +277,13 @@ class MockSettingsPageComponent {} - + `, @@ -408,26 +413,26 @@ const navButtons = (showBerry = false) => [ { label: "vault", page: "/tabs/vault", - icon: Icons.VaultInactive, - iconActive: Icons.VaultActive, + icon: VaultInactive, + iconActive: VaultActive, }, { label: "generator", page: "/tabs/generator", - icon: Icons.GeneratorInactive, - iconActive: Icons.GeneratorActive, + icon: GeneratorInactive, + iconActive: GeneratorActive, }, { label: "send", page: "/tabs/send", - icon: Icons.SendInactive, - iconActive: Icons.SendActive, + icon: SendInactive, + iconActive: SendActive, }, { label: "settings", page: "/tabs/settings", - icon: Icons.SettingsInactive, - iconActive: Icons.SettingsActive, + icon: SettingsInactive, + iconActive: SettingsActive, showBerry: showBerry, }, ]; @@ -671,17 +676,13 @@ export const WithVirtualScrollChild: Story = { - + diff --git a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts index 8a897e2e21b..742b12bb7ad 100644 --- a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts @@ -3,8 +3,9 @@ import { Component, Input } from "@angular/core"; import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { Icon } from "@bitwarden/assets/svg"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Icon, IconModule, LinkModule } from "@bitwarden/components"; +import { IconModule, LinkModule } from "@bitwarden/components"; export type NavButton = { label: string; diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts index f75e9cc29a5..f9a4221b3ef 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts @@ -150,7 +150,7 @@ describe("Browser Utils Service", () => { callback(undefined); }); - const isViewOpen = await browserPlatformUtilsService.isViewOpen(); + const isViewOpen = await browserPlatformUtilsService.isPopupOpen(); expect(isViewOpen).toBe(false); }); @@ -160,7 +160,7 @@ describe("Browser Utils Service", () => { callback(message.command === "checkVaultPopupHeartbeat"); }); - const isViewOpen = await browserPlatformUtilsService.isViewOpen(); + const isViewOpen = await browserPlatformUtilsService.isPopupOpen(); expect(isViewOpen).toBe(true); }); @@ -173,7 +173,7 @@ describe("Browser Utils Service", () => { callback(undefined); }); - const isViewOpen = await browserPlatformUtilsService.isViewOpen(); + const isViewOpen = await browserPlatformUtilsService.isPopupOpen(); expect(isViewOpen).toBe(false); diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts index beac7616d8d..4c5869054b2 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts @@ -150,7 +150,7 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic * message to the popup and waiting for a response. If a response is received, * the view is open. */ - async isViewOpen(): Promise { + async isPopupOpen(): Promise { if (this.isSafari()) { // Query views on safari since chrome.runtime.sendMessage does not timeout and will hang. return BrowserApi.isPopupOpen(); diff --git a/apps/browser/src/platform/system-notifications/browser-system-notification.service.ts b/apps/browser/src/platform/system-notifications/browser-system-notification.service.ts new file mode 100644 index 00000000000..26b49515b82 --- /dev/null +++ b/apps/browser/src/platform/system-notifications/browser-system-notification.service.ts @@ -0,0 +1,60 @@ +import { map, merge, Observable } from "rxjs"; + +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { + ButtonLocation, + SystemNotificationClearInfo, + SystemNotificationCreateInfo, + SystemNotificationEvent, + SystemNotificationsService, +} from "@bitwarden/common/platform/system-notifications/system-notifications.service"; + +import { fromChromeEvent } from "../browser/from-chrome-event"; + +export class BrowserSystemNotificationService implements SystemNotificationsService { + notificationClicked$: Observable; + + constructor( + private logService: LogService, + private platformUtilsService: PlatformUtilsService, + ) { + this.notificationClicked$ = merge( + fromChromeEvent(chrome.notifications.onButtonClicked).pipe( + map(([notificationId, buttonIndex]) => ({ + id: notificationId, + buttonIdentifier: buttonIndex, + })), + ), + fromChromeEvent(chrome.notifications.onClicked).pipe( + map(([notificationId]: [string]) => ({ + id: notificationId, + buttonIdentifier: ButtonLocation.NotificationButton, + })), + ), + ); + } + + async create(createInfo: SystemNotificationCreateInfo): Promise { + return new Promise((resolve) => { + chrome.notifications.create( + { + iconUrl: chrome.runtime.getURL("images/icon128.png"), + message: createInfo.body, + type: "basic", + title: createInfo.title, + buttons: createInfo.buttons.map((value) => ({ title: value.title })), + }, + (notificationId) => resolve(notificationId), + ); + }); + } + + async clear(clearInfo: SystemNotificationClearInfo): Promise { + chrome.notifications.clear(clearInfo.id); + } + + isSupported(): boolean { + return "notifications" in chrome; + } +} diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index e2f4561e86c..34a37da425e 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -17,28 +17,31 @@ import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-ma import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { DevicesIcon, + RegistrationLockAltIcon, + RegistrationUserAddIcon, + TwoFactorTimeoutIcon, + DeviceVerificationIcon, + UserLockIcon, + VaultIcon, + LockIcon, +} from "@bitwarden/assets/svg"; +import { LoginComponent, LoginDecryptionOptionsComponent, LoginSecondaryContentComponent, LoginViaAuthRequestComponent, PasswordHintComponent, RegistrationFinishComponent, - RegistrationLockAltIcon, RegistrationStartComponent, RegistrationStartSecondaryComponent, RegistrationStartSecondaryComponentData, - RegistrationUserAddIcon, SsoComponent, - TwoFactorTimeoutIcon, TwoFactorAuthComponent, TwoFactorAuthGuard, NewDeviceVerificationComponent, - DeviceVerificationIcon, - UserLockIcon, - VaultIcon, } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { AnonLayoutWrapperData, Icons } from "@bitwarden/components"; +import { AnonLayoutWrapperData } from "@bitwarden/components"; import { LockComponent } from "@bitwarden/key-management-ui"; import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component"; @@ -497,7 +500,7 @@ const routes: Routes = [ path: "lock", canActivate: [lockGuard()], data: { - pageIcon: Icons.LockIcon, + pageIcon: LockIcon, pageTitle: { key: "yourVaultIsLockedV2", }, diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index fa1e6c237c9..bd7b41b6e5f 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -321,6 +321,9 @@ export class AppComponent implements OnInit, OnDestroy { } private async clearComponentStates() { + if (this.activeUserId == null) { + return; + } if (!(await firstValueFrom(this.tokenService.hasAccessToken$(this.activeUserId)))) { return; } diff --git a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts index 7a98f570fda..3b84eac2217 100644 --- a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts +++ b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts @@ -5,10 +5,9 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Data, NavigationEnd, Router, RouterModule } from "@angular/router"; import { Subject, filter, switchMap, takeUntil, tap } from "rxjs"; +import { ExtensionBitwardenLogo, Icon } from "@bitwarden/assets/svg"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { - Icon, - Icons, IconModule, Translation, AnonLayoutComponent, @@ -63,7 +62,7 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy { protected hideCardWrapper: boolean = false; protected theme: string; - protected logo = Icons.ExtensionBitwardenLogo; + protected logo = ExtensionBitwardenLogo; constructor( private router: Router, diff --git a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts index 2c3d09b79fb..4e6f2fb452d 100644 --- a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts +++ b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts @@ -9,6 +9,7 @@ import { } from "@storybook/angular"; import { of } from "rxjs"; +import { LockIcon, RegistrationCheckEmailIcon } from "@bitwarden/assets/svg"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service"; @@ -22,12 +23,7 @@ import { import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { - AnonLayoutWrapperDataService, - ButtonModule, - Icons, - I18nMockService, -} from "@bitwarden/components"; +import { AnonLayoutWrapperDataService, ButtonModule, I18nMockService } from "@bitwarden/components"; import { AccountSwitcherService } from "../../../auth/popup/account-switching/services/account-switcher.service"; import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service"; @@ -244,7 +240,7 @@ const initialData: ExtensionAnonLayoutWrapperData = { pageSubtitle: { key: "finishCreatingYourAccountBySettingAPassword", }, - pageIcon: Icons.LockIcon, + pageIcon: LockIcon, showAcctSwitcher: true, showBackButton: true, showLogo: true, @@ -258,7 +254,7 @@ const changedData: ExtensionAnonLayoutWrapperData = { pageSubtitle: { key: "checkYourEmail", }, - pageIcon: Icons.RegistrationCheckEmailIcon, + pageIcon: RegistrationCheckEmailIcon, showAcctSwitcher: false, showBackButton: false, showLogo: false, diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index f531ebd5ca7..358ed33408c 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -92,12 +92,13 @@ import { AbstractStorageService, ObservableStorageService, } from "@bitwarden/common/platform/abstractions/storage.service"; +import { ActionsService } from "@bitwarden/common/platform/actions"; import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- Used for dependency injection import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; import { flagEnabled } from "@bitwarden/common/platform/misc/flags"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling"; +import { ServerNotificationsService } from "@bitwarden/common/platform/server-notifications"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory"; @@ -113,6 +114,7 @@ import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/imp import { PrimarySecondaryStorageService } from "@bitwarden/common/platform/storage/primary-secondary-storage.service"; import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service"; import { SyncService } from "@bitwarden/common/platform/sync"; +import { SystemNotificationsService } from "@bitwarden/common/platform/system-notifications/system-notifications.service"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -160,13 +162,14 @@ import { InlineMenuFieldQualificationService } from "../../autofill/services/inl import { ForegroundBrowserBiometricsService } from "../../key-management/biometrics/foreground-browser-biometrics"; import { ExtensionLockComponentService } from "../../key-management/lock/services/extension-lock-component.service"; import { ForegroundVaultTimeoutService } from "../../key-management/vault-timeout/foreground-vault-timeout.service"; +import { BrowserActionsService } from "../../platform/actions/browser-actions.service"; import { BrowserApi } from "../../platform/browser/browser-api"; import { runInsideAngular } from "../../platform/browser/run-inside-angular.operator"; /* eslint-disable no-restricted-imports */ import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service"; import { ChromeMessageSender } from "../../platform/messaging/chrome-message.sender"; /* eslint-enable no-restricted-imports */ -import { ForegroundNotificationsService } from "../../platform/notifications/foreground-notifications.service"; +import { ForegroundServerNotificationsService } from "../../platform/notifications/foreground-server-notifications.service"; import { OffscreenDocumentService } from "../../platform/offscreen-document/abstractions/offscreen-document"; import { DefaultOffscreenDocumentService } from "../../platform/offscreen-document/offscreen-document.service"; import { PopupCompactModeService } from "../../platform/popup/layout/popup-compact-mode.service"; @@ -184,6 +187,7 @@ import { ForegroundTaskSchedulerService } from "../../platform/services/task-sch import { BrowserStorageServiceProvider } from "../../platform/storage/browser-storage-service.provider"; import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service"; import { ForegroundSyncService } from "../../platform/sync/foreground-sync.service"; +import { BrowserSystemNotificationService } from "../../platform/system-notifications/browser-system-notification.service"; import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging"; import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service"; import { Fido2UserVerificationService } from "../../vault/services/fido2-user-verification.service"; @@ -253,6 +257,11 @@ const safeProviders: SafeProvider[] = [ }, deps: [GlobalStateProvider], }), + safeProvider({ + provide: ActionsService, + useClass: BrowserActionsService, + deps: [LogService, PlatformUtilsService], + }), safeProvider({ provide: KeyService, useFactory: ( @@ -609,6 +618,11 @@ const safeProviders: SafeProvider[] = [ useClass: SsoUrlService, deps: [], }), + safeProvider({ + provide: SystemNotificationsService, + useClass: BrowserSystemNotificationService, + deps: [LogService, PlatformUtilsService], + }), safeProvider({ provide: LoginComponentService, useClass: ExtensionLoginComponentService, @@ -674,8 +688,8 @@ const safeProviders: SafeProvider[] = [ deps: [KeyService, MasterPasswordApiService, InternalMasterPasswordServiceAbstraction, WINDOW], }), safeProvider({ - provide: NotificationsService, - useClass: ForegroundNotificationsService, + provide: ServerNotificationsService, + useClass: ForegroundServerNotificationsService, deps: [LogService], }), safeProvider({ diff --git a/apps/browser/src/popup/tabs-v2.component.ts b/apps/browser/src/popup/tabs-v2.component.ts index 860b71794ff..f1e42799b35 100644 --- a/apps/browser/src/popup/tabs-v2.component.ts +++ b/apps/browser/src/popup/tabs-v2.component.ts @@ -2,9 +2,18 @@ import { Component } from "@angular/core"; import { map, Observable, startWith, switchMap } from "rxjs"; import { NudgesService } from "@bitwarden/angular/vault"; +import { + VaultInactive, + VaultActive, + GeneratorInactive, + GeneratorActive, + SendInactive, + SendActive, + SettingsInactive, + SettingsActive, +} from "@bitwarden/assets/svg"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { Icons } from "@bitwarden/components"; import { NavButton } from "../platform/popup/layout/popup-tab-navigation.component"; @@ -24,26 +33,26 @@ export class TabsV2Component { { label: "vault", page: "/tabs/vault", - icon: Icons.VaultInactive, - iconActive: Icons.VaultActive, + icon: VaultInactive, + iconActive: VaultActive, }, { label: "generator", page: "/tabs/generator", - icon: Icons.GeneratorInactive, - iconActive: Icons.GeneratorActive, + icon: GeneratorInactive, + iconActive: GeneratorActive, }, { label: "send", page: "/tabs/send", - icon: Icons.SendInactive, - iconActive: Icons.SendActive, + icon: SendInactive, + iconActive: SendActive, }, { label: "settings", page: "/tabs/settings", - icon: Icons.SettingsInactive, - iconActive: Icons.SettingsActive, + icon: SettingsInactive, + iconActive: SettingsActive, showBerry: hasBadges, }, ]; diff --git a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html index 5d313188d8f..c6ea52aff62 100644 --- a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html +++ b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html @@ -26,7 +26,7 @@ slot="end" bitIconButton="bwi-trash" [bitAction]="deleteSend" - appA11yTitle="{{ 'delete' | i18n }}" + label="{{ 'delete' | i18n }}" > diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts index dd9e95b64a1..30359e98fa0 100644 --- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts @@ -7,13 +7,13 @@ import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ActiveSendIcon } from "@bitwarden/assets/svg"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { ButtonModule, IconModule, ToastService } from "@bitwarden/components"; -import { ActiveSendIcon } from "@bitwarden/send-ui"; import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; import { PopupFooterComponent } from "../../../../platform/popup/layout/popup-footer.component"; diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts index 2fca3e41f88..5bd5386b1e0 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts @@ -4,15 +4,15 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { combineLatest, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { NoResults, NoSendsIcon } from "@bitwarden/assets/svg"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { ButtonModule, CalloutModule, Icons, NoItemsModule } from "@bitwarden/components"; +import { ButtonModule, CalloutModule, NoItemsModule } from "@bitwarden/components"; import { NewSendDropdownComponent, - NoSendsIcon, SendItemsService, SendListFiltersComponent, SendListFiltersService, @@ -59,7 +59,7 @@ export class SendV2Component implements OnDestroy { protected sendsLoading$ = this.sendItemsService.loading$; protected title: string = "allSends"; protected noItemIcon = NoSendsIcon; - protected noResultsIcon = Icons.NoResults; + protected noResultsIcon = NoResults; protected sendsDisabled = false; diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.html index 21b298fb30a..8f184c6a0c1 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.html @@ -38,7 +38,7 @@ type="button" buttonType="danger" bitIconButton="bwi-trash" - [appA11yTitle]="'delete' | i18n" + [label]="'delete' | i18n" > diff --git a/apps/browser/src/vault/popup/components/vault-v2/intro-carousel/intro-carousel.component.ts b/apps/browser/src/vault/popup/components/vault-v2/intro-carousel/intro-carousel.component.ts index 527f0f246af..22149f9dc86 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/intro-carousel/intro-carousel.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/intro-carousel/intro-carousel.component.ts @@ -2,9 +2,10 @@ import { Component } from "@angular/core"; import { Router } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { SecurityHandshake, LoginCards, SecureUser, SecureDevices } from "@bitwarden/assets/svg"; import { ButtonModule, DialogModule, IconModule, TypographyModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; -import { VaultCarouselModule, VaultIcons } from "@bitwarden/vault"; +import { VaultCarouselModule } from "@bitwarden/vault"; import { IntroCarouselService } from "../../../services/intro-carousel.service"; @@ -22,10 +23,10 @@ import { IntroCarouselService } from "../../../services/intro-carousel.service"; ], }) export class IntroCarouselComponent { - protected securityHandshake = VaultIcons.SecurityHandshake; - protected loginCards = VaultIcons.LoginCards; - protected secureUser = VaultIcons.SecureUser; - protected secureDevices = VaultIcons.SecureDevices; + protected securityHandshake = SecurityHandshake; + protected loginCards = LoginCards; + protected secureUser = SecureUser; + protected secureDevices = SecureDevices; constructor( private router: Router, diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html index 567d5277454..f4cc27171ad 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html @@ -7,7 +7,7 @@ size="small" appCopyField="username" [cipher]="cipher" - [appA11yTitle]="'copyUsername' | i18n" + [label]="'copyUsername' | i18n" > @@ -18,7 +18,7 @@ size="small" appCopyField="password" [cipher]="cipher" - [appA11yTitle]="'copyPassword' | i18n" + [label]="'copyPassword' | i18n" > @@ -28,7 +28,7 @@ size="small" appCopyField="totp" [cipher]="cipher" - [appA11yTitle]="'copyVerificationCode' | i18n" + [label]="'copyVerificationCode' | i18n" > @@ -40,7 +40,7 @@ type="button" bitIconButton="bwi-clone" size="small" - [appA11yTitle]="'copyFieldCipherName' | i18n: singleCopyableLogin.key : cipher.name" + [label]="'copyFieldCipherName' | i18n: singleCopyableLogin.key : cipher.name" [appCopyField]="singleCopyableLogin.field" [cipher]="cipher" > @@ -49,7 +49,7 @@ type="button" bitIconButton="bwi-clone" size="small" - [appA11yTitle]=" + [label]=" hasLoginValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n) " [disabled]="!hasLoginValues" @@ -86,7 +86,7 @@ size="small" appCopyField="cardNumber" [cipher]="cipher" - [appA11yTitle]="'copyNumber' | i18n" + [label]="'copyNumber' | i18n" > @@ -96,7 +96,7 @@ size="small" appCopyField="securityCode" [cipher]="cipher" - [appA11yTitle]="'copySecurityCode' | i18n" + [label]="'copySecurityCode' | i18n" > @@ -107,7 +107,7 @@ type="button" bitIconButton="bwi-clone" size="small" - [appA11yTitle]="'copyFieldCipherName' | i18n: singleCopyableCard.key : cipher.name" + [label]="'copyFieldCipherName' | i18n: singleCopyableCard.key : cipher.name" [appCopyField]="singleCopyableCard.field" [cipher]="cipher" showToast @@ -117,7 +117,7 @@ type="button" bitIconButton="bwi-clone" size="small" - [appA11yTitle]=" + [label]=" hasCardValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n) " [disabled]="!hasCardValues" @@ -142,7 +142,7 @@ type="button" bitIconButton="bwi-clone" size="small" - [appA11yTitle]="'copyFieldCipherName' | i18n: singleCopyableIdentity.key : cipher.name" + [label]="'copyFieldCipherName' | i18n: singleCopyableIdentity.key : cipher.name" [appCopyField]="singleCopyableIdentity.field" [cipher]="cipher" showToast @@ -152,7 +152,7 @@ type="button" bitIconButton="bwi-clone" size="small" - [appA11yTitle]=" + [label]=" hasIdentityValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n) " [disabled]="!hasIdentityValues" @@ -180,9 +180,7 @@ type="button" bitIconButton="bwi-clone" size="small" - [appA11yTitle]=" - hasSecureNoteValue ? ('copyNoteTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n) - " + [label]="hasSecureNoteValue ? ('copyNoteTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)" appCopyField="secureNote" [cipher]="cipher" > @@ -193,9 +191,7 @@ type="button" bitIconButton="bwi-clone" size="small" - [appA11yTitle]=" - hasSshKeyValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n) - " + [label]="hasSshKeyValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)" [disabled]="!hasSshKeyValues" [bitMenuTriggerFor]="sshKeyOptions" > diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html index 962f0c914f5..42e2779679a 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html @@ -3,8 +3,7 @@ type="button" bitIconButton="bwi-ellipsis-v" size="small" - [attr.aria-label]="'moreOptionsLabel' | i18n: cipher.name" - [title]="'moreOptionsTitle' | i18n: cipher.name" + [label]="'moreOptionsLabel' | i18n: cipher.name" [disabled]="decryptionFailure" [bitMenuTriggerFor]="moreOptions" > diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts index b5d35e2005e..b65138dac3a 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts @@ -76,10 +76,8 @@ describe("VaultGeneratorDialogComponent", () => { component.onValueGenerated("test-password"); fixture.detectChanges(); - const button = fixture.debugElement.query( - By.css("[data-testid='select-button']"), - ).nativeElement; - expect(button.disabled).toBe(false); + const button = fixture.debugElement.query(By.css("[data-testid='select-button']")); + expect(button.attributes["aria-disabled"]).toBe(undefined); }); it("should disable the button if no value has been generated", () => { @@ -90,10 +88,8 @@ describe("VaultGeneratorDialogComponent", () => { generator.algorithmSelected.emit({ useGeneratedValue: "Use Password" } as any); fixture.detectChanges(); - const button = fixture.debugElement.query( - By.css("[data-testid='select-button']"), - ).nativeElement; - expect(button.disabled).toBe(true); + const button = fixture.debugElement.query(By.css("[data-testid='select-button']")); + expect(button.attributes["aria-disabled"]).toBe("true"); }); it("should disable the button if no algorithm is selected", () => { @@ -104,10 +100,8 @@ describe("VaultGeneratorDialogComponent", () => { generator.valueGenerated.emit("test-password"); fixture.detectChanges(); - const button = fixture.debugElement.query( - By.css("[data-testid='select-button']"), - ).nativeElement; - expect(button.disabled).toBe(true); + const button = fixture.debugElement.query(By.css("[data-testid='select-button']")); + expect(button.attributes["aria-disabled"]).toBe("true"); }); it("should update button text when algorithm is selected", () => { diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.html index 91feaa433a9..1ab162b56fb 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.html @@ -8,7 +8,7 @@ bitIconButton="bwi-sliders" [buttonType]="'muted'" [bitDisclosureTriggerFor]="disclosureRef" - [appA11yTitle]="'filterVault' | i18n" + [label]="'filterVault' | i18n" aria-describedby="filters-applied" >

diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts index fe2baf463cf..72df3cba41a 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts @@ -5,12 +5,11 @@ import { FormsModule } from "@angular/forms"; import { Subject, Subscription, debounceTime, distinctUntilChanged, filter } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { SearchTextDebounceInterval } from "@bitwarden/common/vault/services/search.service"; import { SearchModule } from "@bitwarden/components"; import { VaultPopupItemsService } from "../../../services/vault-popup-items.service"; -const SearchTextDebounceInterval = 200; - @Component({ imports: [CommonModule, SearchModule, JslibModule, FormsModule], selector: "app-vault-v2-search", diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 0340c82369d..604cc6b73ef 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -18,6 +18,7 @@ import { import { JslibModule } from "@bitwarden/angular/jslib.module"; import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component"; +import { DeactivatedOrg, NoResults, VaultOpen } from "@bitwarden/assets/svg"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -28,11 +29,10 @@ import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; import { ButtonModule, DialogService, - Icons, NoItemsModule, TypographyModule, } from "@bitwarden/components"; -import { DecryptionFailureDialogComponent, VaultIcons } from "@bitwarden/vault"; +import { DecryptionFailureDialogComponent } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../../auth/popup/account-switching/current-account.component"; import { BrowserApi } from "../../../../platform/browser/browser-api"; @@ -136,9 +136,9 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { /** Visual state of the vault */ protected vaultState: VaultState | null = null; - protected vaultIcon = VaultIcons.Vault; - protected deactivatedIcon = VaultIcons.DeactivatedOrg; - protected noResultsIcon = Icons.NoResults; + protected vaultIcon = VaultOpen; + protected deactivatedIcon = DeactivatedOrg; + protected noResultsIcon = NoResults; protected VaultStateEnum = VaultState; diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html index 8c76db600ae..9b8380a4214 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html @@ -33,7 +33,7 @@ type="button" buttonType="danger" bitIconButton="bwi-trash" - [appA11yTitle]="(cipher.isDeleted ? 'deleteForever' : 'delete') | i18n" + [label]="(cipher.isDeleted ? 'deleteForever' : 'delete') | i18n" > diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.html b/apps/browser/src/vault/popup/settings/folders-v2.component.html index 8cea05f9c17..b36b5affc23 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.html +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.html @@ -25,7 +25,7 @@ slot="end" type="button" (click)="openAddEditFolderDialog(folder)" - [appA11yTitle]="'editFolderWithName' | i18n: folder.name" + [label]="'editFolderWithName' | i18n: folder.name" bitIconButton="bwi-pencil-square" class="tw-self-end" data-testid="edit-folder-button" diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.ts index 2264415f4fa..b749f651d53 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.ts @@ -3,6 +3,7 @@ import { Component } from "@angular/core"; import { filter, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { NoFolders } from "@bitwarden/assets/svg"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserId } from "@bitwarden/common/types/guid"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -15,7 +16,7 @@ import { ItemModule, NoItemsModule, } from "@bitwarden/components"; -import { AddEditFolderDialogComponent, VaultIcons } from "@bitwarden/vault"; +import { AddEditFolderDialogComponent } from "@bitwarden/vault"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; @@ -39,7 +40,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co export class FoldersV2Component { folders$: Observable; - NoFoldersIcon = VaultIcons.NoFolders; + NoFoldersIcon = NoFolders; private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); constructor( diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html index 11ed2674178..d1e70390844 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html @@ -37,8 +37,7 @@ type="button" bitIconButton="bwi-ellipsis-v" size="small" - [attr.aria-label]="'moreOptionsLabel' | i18n: cipher.name" - [title]="'moreOptionsTitle' | i18n: cipher.name" + [label]="'moreOptionsLabel' | i18n: cipher.name" [bitMenuTriggerFor]="moreOptions" > diff --git a/apps/browser/src/vault/popup/settings/trash.component.ts b/apps/browser/src/vault/popup/settings/trash.component.ts index d6e5f899bba..b9d6b621bba 100644 --- a/apps/browser/src/vault/popup/settings/trash.component.ts +++ b/apps/browser/src/vault/popup/settings/trash.component.ts @@ -2,8 +2,8 @@ import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, Component } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { EmptyTrash } from "@bitwarden/assets/svg"; import { CalloutModule, NoItemsModule } from "@bitwarden/components"; -import { VaultIcons } from "@bitwarden/vault"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; @@ -29,7 +29,7 @@ import { TrashListItemsContainerComponent } from "./trash-list-items-container/t export class TrashComponent { protected deletedCiphers$ = this.vaultPopupItemsService.deletedCiphers$; - protected emptyTrashIcon = VaultIcons.EmptyTrash; + protected emptyTrashIcon = EmptyTrash; constructor(private vaultPopupItemsService: VaultPopupItemsService) {} } diff --git a/apps/browser/tailwind.config.js b/apps/browser/tailwind.config.js index 02e1d86f5ac..1ad56562bb3 100644 --- a/apps/browser/tailwind.config.js +++ b/apps/browser/tailwind.config.js @@ -4,6 +4,7 @@ const config = require("../../libs/components/tailwind.config.base"); config.content = [ "./src/**/*.{html,ts}", "../../libs/components/src/**/*.{html,ts}", + "../../libs/assets/src/**/*.{html,ts}", "../../libs/auth/src/**/*.{html,ts}", "../../libs/key-management-ui/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts}", diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 58181cff6ce..a994ad3117c 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -606,6 +606,9 @@ export class GetCommand extends DownloadCommand { if (id === "me") { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const publicKey = await firstValueFrom(this.keyService.userPublicKey$(activeUserId)); + if (publicKey == null) { + return Response.error("No public key available for the active user."); + } fingerprint = await this.keyService.getFingerprint(activeUserId, publicKey); } else if (Utils.isGuid(id)) { try { diff --git a/apps/cli/src/platform/services/cli-platform-utils.service.ts b/apps/cli/src/platform/services/cli-platform-utils.service.ts index 7bed495bbf5..9e02b9dcca9 100644 --- a/apps/cli/src/platform/services/cli-platform-utils.service.ts +++ b/apps/cli/src/platform/services/cli-platform-utils.service.ts @@ -75,7 +75,7 @@ export class CliPlatformUtilsService implements PlatformUtilsService { return false; } - isViewOpen() { + isPopupOpen() { return Promise.resolve(false); } diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index e82ceb5a6e9..0268eed06ae 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -599,6 +599,7 @@ export class ServiceContainer { this.accountService, this.kdfConfigService, this.keyService, + this.stateProvider, customUserAgent, ); diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 37650c08b95..42eb7017e03 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.8.0", + "version": "2025.8.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index db079cd60f9..1cfe8a85386 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -12,29 +12,32 @@ import { unauthGuardFn, } from "@bitwarden/angular/auth/guards"; import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password"; +import { + DevicesIcon, + RegistrationLockAltIcon, + RegistrationUserAddIcon, + TwoFactorTimeoutIcon, + DeviceVerificationIcon, + UserLockIcon, + VaultIcon, + LockIcon, +} from "@bitwarden/assets/svg"; import { LoginComponent, LoginSecondaryContentComponent, LoginViaAuthRequestComponent, PasswordHintComponent, RegistrationFinishComponent, - RegistrationLockAltIcon, RegistrationStartComponent, RegistrationStartSecondaryComponent, RegistrationStartSecondaryComponentData, - RegistrationUserAddIcon, - UserLockIcon, - VaultIcon, LoginDecryptionOptionsComponent, - DevicesIcon, SsoComponent, - TwoFactorTimeoutIcon, TwoFactorAuthComponent, TwoFactorAuthGuard, NewDeviceVerificationComponent, - DeviceVerificationIcon, } from "@bitwarden/auth/angular"; -import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, Icons } from "@bitwarden/components"; +import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components"; import { LockComponent } from "@bitwarden/key-management-ui"; import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard"; @@ -260,7 +263,7 @@ const routes: Routes = [ path: "lock", canActivate: [lockGuard()], data: { - pageIcon: Icons.LockIcon, + pageIcon: LockIcon, pageTitle: { key: "yourVaultIsLockedV2", }, diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 04651ed0c10..c4e60e4ff93 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -62,7 +62,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { SystemService } from "@bitwarden/common/platform/abstractions/system.service"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/server-notifications"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; @@ -155,7 +155,7 @@ export class AppComponent implements OnInit, OnDestroy { private messagingService: MessagingService, private collectionService: CollectionService, private searchService: SearchService, - private notificationsService: NotificationsService, + private notificationsService: ServerNotificationsService, private platformUtilsService: PlatformUtilsService, private systemService: SystemService, private processReloadService: ProcessReloadServiceAbstraction, @@ -299,12 +299,22 @@ export class AppComponent implements OnInit, OnDestroy { break; case "showFingerprintPhrase": { const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), + getUserId(this.accountService.activeAccount$), ); const publicKey = await firstValueFrom(this.keyService.userPublicKey$(activeUserId)); - const fingerprint = await this.keyService.getFingerprint(activeUserId, publicKey); - const dialogRef = FingerprintDialogComponent.open(this.dialogService, { fingerprint }); - await firstValueFrom(dialogRef.closed); + if (publicKey == null) { + this.logService.error( + "[AppComponent] No public key available for the user: " + + activeUserId + + " fingerprint can't be displayed.", + ); + } else { + const fingerprint = await this.keyService.getFingerprint(activeUserId, publicKey); + const dialogRef = FingerprintDialogComponent.open(this.dialogService, { + fingerprint, + }); + await firstValueFrom(dialogRef.closed); + } break; } case "deleteAccount": diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 2c68821b6c7..6b511ff366d 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -13,7 +13,7 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/server-notifications"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; @@ -38,7 +38,7 @@ export class InitService { private i18nService: I18nServiceAbstraction, private eventUploadService: EventUploadServiceAbstraction, private twoFactorService: TwoFactorServiceAbstraction, - private notificationsService: NotificationsService, + private notificationsService: ServerNotificationsService, private platformUtilsService: PlatformUtilsServiceAbstraction, private stateService: StateServiceAbstraction, private keyService: KeyServiceAbstraction, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 72d40ed750f..300cf779cc3 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -702,6 +702,12 @@ "attachmentSaved": { "message": "Attachment saved" }, + "addAttachment": { + "message": "Add attachment" + }, + "maxFileSizeSansPunctuation": { + "message": "Maximum file size is 500 MB" + }, "file": { "message": "File" }, @@ -3491,15 +3497,11 @@ "selectImportCollection": { "message": "Select a collection" }, - "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", - "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", - "placeholders": { - "destination": { - "content": "$1", - "example": "folder or collection" - } - } + "importTargetHintCollection": { + "message": "Select this option if you want the imported file contents moved to a collection" + }, + "importTargetHintFolder": { + "message": "Select this option if you want the imported file contents moved to a folder" }, "importUnassignedItemsError": { "message": "File contains unassigned items." @@ -4077,5 +4079,9 @@ }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." + }, + "moreBreadcrumbs": { + "message": "More breadcrumbs", + "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." } } diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 44ee6ec862d..6daff35e115 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.8.0", + "version": "2025.8.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.8.0", + "version": "2025.8.1", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index ffe70bb7bd7..ea2e8affda2 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.8.0", + "version": "2025.8.1", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/platform/services/electron-platform-utils.service.ts b/apps/desktop/src/platform/services/electron-platform-utils.service.ts index 43b867b7a68..23fb29e932a 100644 --- a/apps/desktop/src/platform/services/electron-platform-utils.service.ts +++ b/apps/desktop/src/platform/services/electron-platform-utils.service.ts @@ -59,7 +59,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { return ipc.platform.isMacAppStore; } - isViewOpen(): Promise { + isPopupOpen(): Promise { return Promise.resolve(false); } diff --git a/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts index 000281c5807..e99e05e385a 100644 --- a/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts @@ -2,7 +2,7 @@ import { ScrollingModule } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { distinctUntilChanged } from "rxjs"; +import { distinctUntilChanged, debounceTime } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/vault/components/vault-items.component"; @@ -10,6 +10,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; +import { SearchTextDebounceInterval } from "@bitwarden/common/vault/services/search.service"; import { CipherViewLike, CipherViewLikeUtils, @@ -35,7 +36,7 @@ export class VaultItemsV2Component extends BaseVaultIt super(searchService, cipherService, accountService, restrictedItemTypesService); this.searchBarService.searchText$ - .pipe(distinctUntilChanged(), takeUntilDestroyed()) + .pipe(debounceTime(SearchTextDebounceInterval), distinctUntilChanged(), takeUntilDestroyed()) .subscribe((searchText) => { this.searchText = searchText!; }); diff --git a/apps/desktop/tailwind.config.js b/apps/desktop/tailwind.config.js index 5b4cab027ba..bf65ae8d7cb 100644 --- a/apps/desktop/tailwind.config.js +++ b/apps/desktop/tailwind.config.js @@ -4,6 +4,7 @@ const config = require("../../libs/components/tailwind.config.base"); config.content = [ "./src/**/*.{html,ts}", "../../libs/components/src/**/*.{html,ts}", + "../../libs/assets/src/**/*.{html,ts}", "../../libs/auth/src/**/*.{html,ts}", "../../libs/key-management-ui/src/**/*.{html,ts}", "../../libs/angular/src/**/*.{html,ts}", diff --git a/apps/web/src/404.html b/apps/web/src/404.html index 1a01aee40c7..b3b5a5713ca 100644 --- a/apps/web/src/404.html +++ b/apps/web/src/404.html @@ -15,23 +15,206 @@ - - Bitwarden + + Bitwarden +

+
+

Sorry, this page isn't available.

-
-

Sorry, this page isn't available.

+

+ The link you followed may be broken, or the page may have been removed. Try going back to + the previous page or see our + Help Center + for more information. +

+ Go to your web vault +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + `; +
-

- The link you followed may be broken, or the page may have been removed. Try going back to - the previous page or see our - Help Center for - more information. -

- - Go to your web vault -
-
- -
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/apps/web/src/app/admin-console/common/base.events.component.ts b/apps/web/src/app/admin-console/common/base.events.component.ts index 9d06be92eb8..ba315dee7fb 100644 --- a/apps/web/src/app/admin-console/common/base.events.component.ts +++ b/apps/web/src/app/admin-console/common/base.events.component.ts @@ -1,8 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Directive } from "@angular/core"; +import { Directive, OnDestroy } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; +import { ActivatedRoute } from "@angular/router"; +import { combineLatest, filter, map, Observable, Subject, switchMap, takeUntil } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EventResponse } from "@bitwarden/common/models/response/event.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { EventView } from "@bitwarden/common/models/view/event.view"; @@ -12,16 +17,17 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ToastService } from "@bitwarden/components"; -import { EventService } from "../../core"; +import { EventOptions, EventService } from "../../core"; import { EventExportService } from "../../tools/event-export"; @Directive() -export abstract class BaseEventsComponent { +export abstract class BaseEventsComponent implements OnDestroy { loading = true; loaded = false; events: EventView[]; dirtyDates = true; continuationToken: string; + canUseSM = false; abstract readonly exportFileName: string; @@ -30,6 +36,15 @@ export abstract class BaseEventsComponent { end: new FormControl(null), }); + protected canUseSM$: Observable; + protected activeOrganization$: Observable; + protected organizations$: Observable; + private destroySubject$ = new Subject(); + + protected get destroy$(): Observable { + return this.destroySubject$.asObservable(); + } + constructor( protected eventService: EventService, protected i18nService: I18nService, @@ -38,12 +53,39 @@ export abstract class BaseEventsComponent { protected logService: LogService, protected fileDownloadService: FileDownloadService, private toastService: ToastService, + protected activeRoute: ActivatedRoute, + protected accountService: AccountService, + protected organizationService: OrganizationService, ) { const defaultDates = this.eventService.getDefaultDateFilters(); this.start = defaultDates[0]; this.end = defaultDates[1]; } + protected initBase(): void { + this.organizations$ = this.accountService.activeAccount$.pipe( + filter((account): account is Account => !!account?.id), + switchMap((account) => this.organizationService.organizations$(account.id)), + ); + + this.activeOrganization$ = combineLatest([this.activeRoute.paramMap, this.organizations$]).pipe( + map(([params, orgs]) => orgs.find((org) => org.id === params.get("organizationId"))), + ); + + this.canUseSM$ = this.activeOrganization$.pipe( + map((org) => org?.canAccessSecretsManager ?? false), + ); + + this.canUseSM$.pipe(takeUntil(this.destroy$)).subscribe((value) => { + this.canUseSM = value; + }); + } + + ngOnDestroy(): void { + this.destroySubject$.next(); + this.destroySubject$.complete(); + } + get start(): string { return this.eventsForm.value.start; } @@ -139,7 +181,10 @@ export abstract class BaseEventsComponent { const events = await Promise.all( response.data.map(async (r) => { const userId = r.actingUserId == null ? r.userId : r.actingUserId; - const eventInfo = await this.eventService.getEventInfo(r); + const options = new EventOptions(); + options.disableLink = !this.canUseSM; + + const eventInfo = await this.eventService.getEventInfo(r, options); const user = this.getUserName(r, userId); const userName = user != null ? user.name : this.i18nService.t("unknown"); diff --git a/apps/web/src/app/admin-console/icons/index.ts b/apps/web/src/app/admin-console/icons/index.ts deleted file mode 100644 index e0c2c124af1..00000000000 --- a/apps/web/src/app/admin-console/icons/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./devices"; diff --git a/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts b/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts index 3f26e03e203..86b83d75ca4 100644 --- a/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts @@ -1,15 +1,11 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; -import { ButtonModule, NoItemsModule, svgIcon } from "@bitwarden/components"; +import { RestrictedView } from "@bitwarden/assets/svg"; +import { ButtonModule, NoItemsModule } from "@bitwarden/components"; import { SharedModule } from "../../../shared"; import { CollectionDialogTabType } from "../shared/components/collection-dialog"; -const icon = svgIcon` - - -`; - @Component({ selector: "collection-access-restricted", imports: [SharedModule, ButtonModule, NoItemsModule], @@ -38,7 +34,7 @@ const icon = svgIcon``, }) export class CollectionAccessRestrictedComponent { - protected icon = icon; + protected icon = RestrictedView; protected collectionDialogTabType = CollectionDialogTabType; @Input() canEditCollection = false; diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html index 50d34227b56..9c3e607d6eb 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html +++ b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html @@ -34,6 +34,7 @@ [bitMenuTriggerFor]="editCollectionMenu" size="small" type="button" + [label]="'editCollection' | i18n" > diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 87f309c6f66..8e4d844a871 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -34,6 +34,7 @@ import { Unassigned, } from "@bitwarden/admin-console/common"; import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; +import { Search } from "@bitwarden/assets/svg"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; @@ -66,7 +67,6 @@ import { BannerModule, DialogRef, DialogService, - Icons, NoItemsModule, ToastService, } from "@bitwarden/components"; @@ -170,7 +170,7 @@ export class VaultComponent implements OnInit, OnDestroy { activeFilter: VaultFilter = new VaultFilter(); protected showAddAccessToggle = false; - protected noItemIcon = Icons.Search; + protected noItemIcon = Search; protected performingInitialLoad = true; protected refreshing = false; protected processingEvent = false; diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts index 4b6e9a431b4..87b9d62f398 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts @@ -6,6 +6,7 @@ import { ActivatedRoute, RouterModule } from "@angular/router"; import { combineLatest, filter, map, Observable, switchMap, withLatestFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AdminConsoleLogo } from "@bitwarden/assets/svg"; import { canAccessBillingTab, canAccessGroupsTab, @@ -27,7 +28,7 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { getById } from "@bitwarden/common/platform/misc"; -import { BannerModule, IconModule, AdminConsoleLogo } from "@bitwarden/components"; +import { BannerModule, IconModule } from "@bitwarden/components"; import { OrganizationWarningsModule } from "@bitwarden/web-vault/app/billing/organizations/warnings/organization-warnings.module"; import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services"; import { NonIndividualSubscriber } from "@bitwarden/web-vault/app/billing/types"; diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts index 10f68695e88..8484b05283d 100644 --- a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts @@ -8,6 +8,8 @@ import { firstValueFrom, switchMap } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EventResponse } from "@bitwarden/common/models/response/event.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { EventView } from "@bitwarden/common/models/view/event.view"; @@ -26,7 +28,7 @@ import { EventService } from "../../../core"; import { SharedModule } from "../../../shared"; export interface EntityEventsDialogParams { - entity: "user" | "cipher"; + entity: "user" | "cipher" | "secret" | "project"; entityId: string; organizationId?: string; @@ -72,6 +74,8 @@ export class EntityEventsComponent implements OnInit, OnDestroy { private toastService: ToastService, private router: Router, private activeRoute: ActivatedRoute, + private accountService: AccountService, + protected organizationService: OrganizationService, ) {} async ngOnInit() { @@ -162,6 +166,22 @@ export class EntityEventsComponent implements OnInit, OnDestroy { dates[1], clearExisting ? null : this.continuationToken, ); + } else if (this.params.entity === "secret") { + response = await this.apiService.getEventsSecret( + this.params.organizationId, + this.params.entityId, + dates[0], + dates[1], + clearExisting ? null : this.continuationToken, + ); + } else if (this.params.entity === "project") { + response = await this.apiService.getEventsProject( + this.params.organizationId, + this.params.entityId, + dates[0], + dates[1], + clearExisting ? null : this.continuationToken, + ); } else { response = await this.apiService.getEventsCipher( this.params.entityId, diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.ts b/apps/web/src/app/admin-console/organizations/manage/events.component.ts index 07f6be7d7f6..f442d429767 100644 --- a/apps/web/src/app/admin-console/organizations/manage/events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/events.component.ts @@ -1,8 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { concatMap, firstValueFrom, lastValueFrom, Subject, takeUntil } from "rxjs"; +import { ActivatedRoute } from "@angular/router"; +import { concatMap, firstValueFrom, lastValueFrom, takeUntil } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; @@ -60,8 +60,6 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe placeholderEvents = placeholderEvents as EventView[]; private orgUsersUserIdMap = new Map(); - private destroy$ = new Subject(); - readonly ProductTierType = ProductTierType; protected isBreadcrumbEventLogsEnabled$ = this.configService.getFeatureFlag$( @@ -75,18 +73,18 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe i18nService: I18nService, exportService: EventExportService, platformUtilsService: PlatformUtilsService, - private router: Router, logService: LogService, private userNamePipe: UserNamePipe, - private organizationService: OrganizationService, + protected organizationService: OrganizationService, private organizationUserApiService: OrganizationUserApiService, private organizationApiService: OrganizationApiServiceAbstraction, private providerService: ProviderService, fileDownloadService: FileDownloadService, toastService: ToastService, - private accountService: AccountService, + protected accountService: AccountService, private dialogService: DialogService, private configService: ConfigService, + protected activeRoute: ActivatedRoute, ) { super( eventService, @@ -96,10 +94,15 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe logService, fileDownloadService, toastService, + activeRoute, + accountService, + organizationService, ); } async ngOnInit() { + this.initBase(); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.route.params .pipe( @@ -233,9 +236,4 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe } await this.load(); } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } } diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html index 101512dea04..cc90d10fb4a 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html @@ -80,7 +80,7 @@ bitIconButton="bwi-trash" bitFormButton [bitAction]="delete" - [appA11yTitle]="'delete' | i18n" + [label]="'delete' | i18n" > diff --git a/apps/web/src/app/admin-console/organizations/manage/groups.component.html b/apps/web/src/app/admin-console/organizations/manage/groups.component.html index 4518513ba7d..62d0b5b874b 100644 --- a/apps/web/src/app/admin-console/organizations/manage/groups.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/groups.component.html @@ -46,7 +46,7 @@ type="button" bitIconButton="bwi-ellipsis-v" size="small" - appA11yTitle="{{ 'options' | i18n }}" + label="{{ 'options' | i18n }}" > @@ -82,7 +82,7 @@ type="button" bitIconButton="bwi-ellipsis-v" size="small" - appA11yTitle="{{ 'options' | i18n }}" + label="{{ 'options' | i18n }}" > diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.html b/apps/web/src/app/admin-console/organizations/members/members.component.html index 49946806efc..7e0aa465bf3 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.html +++ b/apps/web/src/app/admin-console/organizations/members/members.component.html @@ -106,7 +106,7 @@ type="button" bitIconButton="bwi-ellipsis-v" size="small" - appA11yTitle="{{ 'options' | i18n }}" + label="{{ 'options' | i18n }}" *ngIf="showUserManagementControls$ | async" > @@ -350,7 +350,7 @@ type="button" bitIconButton="bwi-ellipsis-v" size="small" - appA11yTitle="{{ 'options' | i18n }}" + label="{{ 'options' | i18n }}" > diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts index a803f6ef7b5..5cb61197b99 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts @@ -51,7 +51,7 @@ const render: Story["render"] = (args) => ({ buttonType="danger" size="default" title="Delete" - aria-label="Delete"> + label="Delete"> `, diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html index e0ffc9a4bce..116af15f579 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html @@ -122,7 +122,7 @@ type="button" bitIconButton="bwi-close" buttonType="muted" - appA11yTitle="{{ 'remove' | i18n }} {{ item.labelName }}" + label="{{ 'remove' | i18n }} {{ item.labelName }}" [disabled]="disabled" (click)="selectionList.deselectItem(item.id); handleBlur()" > diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html index 4a91fcc2a41..dec257b3741 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html @@ -143,7 +143,7 @@ buttonType="danger" class="tw-ml-auto" bitFormButton - [appA11yTitle]="'delete' | i18n" + [label]="'delete' | i18n" [bitAction]="delete" [disabled]="loading" > diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts index 2ca566a0af2..c4fe0350006 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts @@ -4,10 +4,11 @@ import { CommonModule } from "@angular/common"; import { Component, inject } from "@angular/core"; import { Params } from "@angular/router"; +import { BitwardenLogo } from "@bitwarden/assets/svg"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { OrganizationSponsorshipResponse } from "@bitwarden/common/admin-console/models/response/organization-sponsorship.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { IconModule, Icons, ToastService } from "@bitwarden/components"; +import { IconModule, ToastService } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; import { BaseAcceptComponent } from "../../../common/base.accept.component"; @@ -22,7 +23,7 @@ import { BaseAcceptComponent } from "../../../common/base.accept.component"; imports: [CommonModule, I18nPipe, IconModule], }) export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent { - protected logo = Icons.BitwardenLogo; + protected logo = BitwardenLogo; failedShortMessage = "inviteAcceptFailedShort"; failedMessage = "inviteAcceptFailed"; diff --git a/apps/web/src/app/app.component.html b/apps/web/src/app/app.component.html index 471d89be1c1..638ad47fe4e 100644 --- a/apps/web/src/app/app.component.html +++ b/apps/web/src/app/app.component.html @@ -2,8 +2,10 @@ -
- Bitwarden +
+
+ Bitwarden +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + `; +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index ae20670c2dd..1cb95250611 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -22,7 +22,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/server-notifications"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -76,7 +76,7 @@ export class AppComponent implements OnDestroy, OnInit { private keyService: KeyService, private collectionService: CollectionService, private searchService: SearchService, - private notificationsService: NotificationsService, + private serverNotificationsService: ServerNotificationsService, private stateService: StateService, private eventUploadService: EventUploadService, protected policyListService: PolicyListService, @@ -88,14 +88,14 @@ export class AppComponent implements OnDestroy, OnInit { private accountService: AccountService, private processReloadService: ProcessReloadServiceAbstraction, private deviceTrustToastService: DeviceTrustToastService, - private readonly destoryRef: DestroyRef, + private readonly destroy: DestroyRef, private readonly documentLangSetter: DocumentLangSetter, private readonly tokenService: TokenService, ) { this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe(); const langSubscription = this.documentLangSetter.start(); - this.destoryRef.onDestroy(() => langSubscription.unsubscribe()); + this.destroy.onDestroy(() => langSubscription.unsubscribe()); } ngOnInit() { @@ -347,9 +347,9 @@ export class AppComponent implements OnDestroy, OnInit { private idleStateChanged() { if (this.isIdle) { - this.notificationsService.disconnectFromInactivity(); + this.serverNotificationsService.disconnectFromInactivity(); } else { - this.notificationsService.reconnectFromActivity(); + this.serverNotificationsService.reconnectFromActivity(); } } } diff --git a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts b/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts index d4a381159ab..443005473c4 100644 --- a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts +++ b/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts @@ -1,8 +1,7 @@ import { Component } from "@angular/core"; import { BaseLoginViaWebAuthnComponent } from "@bitwarden/angular/auth/components/base-login-via-webauthn.component"; -import { CreatePasskeyFailedIcon } from "@bitwarden/angular/auth/icons/create-passkey-failed.icon"; -import { CreatePasskeyIcon } from "@bitwarden/angular/auth/icons/create-passkey.icon"; +import { CreatePasskeyIcon, CreatePasskeyFailedIcon } from "@bitwarden/assets/svg"; @Component({ selector: "app-login-via-webauthn", @@ -10,5 +9,8 @@ import { CreatePasskeyIcon } from "@bitwarden/angular/auth/icons/create-passkey. standalone: false, }) export class LoginViaWebAuthnComponent extends BaseLoginViaWebAuthnComponent { - protected readonly Icons = { CreatePasskeyIcon, CreatePasskeyFailedIcon }; + protected readonly Icons = { + CreatePasskeyIcon, + CreatePasskeyFailedIcon, + }; } diff --git a/apps/web/src/app/auth/settings/account/profile.component.html b/apps/web/src/app/auth/settings/account/profile.component.html index 74e9cf08f89..a49e6c31d2e 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.html +++ b/apps/web/src/app/auth/settings/account/profile.component.html @@ -46,11 +46,14 @@
- - + @if (fingerprintMaterial && userPublicKey) { + + + } diff --git a/apps/web/src/app/auth/settings/account/profile.component.ts b/apps/web/src/app/auth/settings/account/profile.component.ts index a0572b846db..54f9ac58291 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -12,7 +12,10 @@ import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/upda import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserPublicKey } from "@bitwarden/common/types/key"; import { DialogService, ToastService } from "@bitwarden/components"; +import { KeyService } from "@bitwarden/key-management"; import { DynamicAvatarComponent } from "../../../components/dynamic-avatar.component"; import { SharedModule } from "../../../shared"; @@ -29,6 +32,7 @@ export class ProfileComponent implements OnInit, OnDestroy { loading = true; profile: ProfileResponse; fingerprintMaterial: string; + userPublicKey: UserPublicKey; managingOrganization$: Observable; private destroy$ = new Subject(); @@ -44,16 +48,24 @@ export class ProfileComponent implements OnInit, OnDestroy { private dialogService: DialogService, private toastService: ToastService, private organizationService: OrganizationService, + private keyService: KeyService, + private logService: LogService, ) {} async ngOnInit() { this.profile = await this.apiService.getProfile(); - this.loading = false; - this.fingerprintMaterial = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.fingerprintMaterial = userId; + const publicKey = await firstValueFrom(this.keyService.userPublicKey$(userId)); + if (publicKey == null) { + this.logService.error( + "[ProfileComponent] No public key available for the user: " + + userId + + " fingerprint can't be displayed.", + ); + } else { + this.userPublicKey = publicKey; + } this.managingOrganization$ = this.organizationService .organizations$(userId) @@ -70,6 +82,8 @@ export class ProfileComponent implements OnInit, OnDestroy { .subscribe((name) => { this.profile.name = name; }); + + this.loading = false; } openChangeAvatar = async () => { diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.html b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.html index 6e87d66d18b..1c04c03a8d2 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.html @@ -62,7 +62,7 @@ buttonType="danger" [bitAction]="delete" *ngIf="editMode" - appA11yTitle="{{ 'delete' | i18n }}" + label="{{ 'delete' | i18n }}" > diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html index 8a802e4f6af..70165a94fc3 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html @@ -89,7 +89,7 @@ @@ -212,7 +212,7 @@ diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html index 767934cf3da..eec9f74dd60 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html @@ -13,9 +13,6 @@ > {{ "twoStepLoginProviderEnabled" | i18n }} - -

{{ "twoFactorWebAuthnWarning1" | i18n }}

-
FIDO2 WebAuthn logo

- + diff --git a/libs/components/src/form-control/form-control.component.html b/libs/components/src/form-control/form-control.component.html index 3dc3f90c9b0..4fbb8e6342e 100644 --- a/libs/components/src/form-control/form-control.component.html +++ b/libs/components/src/form-control/form-control.component.html @@ -1,5 +1,5 @@