1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-16 08:34:39 +00:00

Merge branch 'main' into PM-18801

This commit is contained in:
jaasen-livefront
2025-05-15 14:47:05 -07:00
527 changed files with 4087 additions and 1236 deletions

5
.github/CODEOWNERS vendored
View File

@@ -185,5 +185,8 @@ apps/web/src/locales/en/messages.json
**/entrypoint.sh
## Overrides
# tsconfig files are potentially dangerous and will be reviewed by platform to prevent misconfigurations
# For the time being platform owns tsconfig and jest config
# These overrides will be removed after Nx is implemented
# To track that effort please see https://bitwarden.atlassian.net/browse/PM-21636
**/tsconfig.json @bitwarden/team-platform-dev
**/jest.config.js @bitwarden/team-platform-dev

View File

@@ -8,10 +8,9 @@ name: Build Browser on PR Target
on:
pull_request_target:
types: [opened, synchronize]
branches-ignore:
- 'l10n_master'
- 'cf-pages'
types: [opened, synchronize, reopened]
branches:
- main
paths:
- 'apps/browser/**'
- 'libs/**'

View File

@@ -8,10 +8,9 @@ name: Build CLI on PR Target
on:
pull_request_target:
types: [opened, synchronize]
branches-ignore:
- 'l10n_master'
- 'cf-pages'
types: [opened, synchronize, reopened]
branches:
- main
paths:
- 'apps/cli/**'
- 'libs/**'

View File

@@ -9,10 +9,9 @@ name: Build Desktop on PR Target
on:
pull_request_target:
types: [opened, synchronize]
branches-ignore:
- 'l10n_master'
- 'cf-pages'
types: [opened, synchronize, reopened]
branches:
- main
paths:
- 'apps/desktop/**'
- 'libs/**'

View File

@@ -8,10 +8,9 @@ name: Build Web on PR Target
on:
pull_request_target:
types: [opened, synchronize]
branches-ignore:
- 'l10n_master'
- 'cf-pages'
types: [opened, synchronize, reopened]
branches:
- main
paths:
- 'apps/web/**'
- 'libs/**'

View File

@@ -73,7 +73,7 @@ jobs:
run: npm run build-storybook:ci
- name: Publish to Chromatic
uses: chromaui/action@8a12962215a66cd05b1ac5b0f1c08768d1aab155 # v11.25.0
uses: chromaui/action@e8cc4c31775280b175a3c440076c00d19a9014d7 # v11.28.2
with:
token: ${{ secrets.GITHUB_TOKEN }}
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

View File

@@ -7,8 +7,14 @@ on:
- "main"
- "rc"
- "hotfix-rc"
pull_request:
types: [opened, synchronize, reopened]
branches-ignore:
- main
pull_request_target:
types: [opened, synchronize]
types: [opened, synchronize, reopened]
branches:
- "main"
jobs:
check-run:

View File

@@ -8,7 +8,7 @@ on:
- "rc"
- "hotfix-rc-*"
pull_request:
types: [opened, synchronize]
types: [ opened, synchronize ]
jobs:
@@ -66,12 +66,15 @@ jobs:
reporter: jest-junit
fail-on-error: true
- name: Upload coverage to codecov.io
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
- name: Upload results to codecov.io
uses: codecov/test-results-action@f2dba722c67b86c6caa034178c6e4d35335f6706 # v1.1.0
- name: Upload test coverage
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: jest-coverage
path: ./coverage/lcov.info
rust:
name: Run Rust tests on ${{ matrix.os }}
runs-on: ${{ matrix.os || 'ubuntu-22.04' }}
@@ -148,7 +151,37 @@ jobs:
working-directory: ./apps/desktop/desktop_native
run: cargo llvm-cov --all-features --lcov --output-path lcov.info --workspace --no-cfg-coverage
- name: Upload to codecov.io
- name: Upload test coverage
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: rust-coverage
path: ./apps/desktop/desktop_native/lcov.info
upload-codecov:
name: Upload to Codecov
runs-on: ubuntu-22.04
needs:
- testing
- rust-coverage
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Download jest coverage
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: jest-coverage
path: ./
- name: Download rust coverage
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: rust-coverage
path: ./apps/desktop/desktop_native
- name: Upload coverage to codecov.io
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
with:
files: ./apps/desktop/desktop_native/lcov.info
files: |
./lcov.info
./apps/desktop/desktop_native/lcov.info

View File

@@ -1096,6 +1096,18 @@
"message": "updated in Bitwarden.",
"description": "Shown to user after item is updated."
},
"selectItemAriaLabel": {
"message": "Select $ITEMTYPE$, $ITEMNAME$",
"description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.",
"placeholders": {
"itemType": {
"content": "$1"
},
"itemName": {
"content": "$2"
}
}
},
"saveAsNewLoginAction": {
"message": "Save as new login",
"description": "Button text for saving login details as a new entry."
@@ -2207,15 +2219,6 @@
"vaultTimeoutAction1": {
"message": "Timeout action"
},
"newCustomizationOptionsCalloutTitle": {
"message": "New customization options"
},
"newCustomizationOptionsCalloutContent": {
"message": "Customize your vault experience with quick copy actions, compact mode, and more!"
},
"newCustomizationOptionsCalloutLink": {
"message": "View all Appearance settings"
},
"lock": {
"message": "Lock",
"description": "Verb form: to make secure or inaccessible by"
@@ -3593,7 +3596,7 @@
"orgTrustWarning1": {
"message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint."
},
"trustUser":{
"trustUser": {
"message": "Trust user"
},
"sendsNoItemsTitle": {
@@ -5281,8 +5284,14 @@
"hasItemsVaultNudgeTitle": {
"message": "Welcome to your vault!"
},
"hasItemsVaultNudgeBody": {
"message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else"
"hasItemsVaultNudgeBodyOne": {
"message": "Autofill items for the current page"
},
"hasItemsVaultNudgeBodyTwo": {
"message": "Favorite items for easy access"
},
"hasItemsVaultNudgeBodyThree": {
"message": "Search your vault for something else"
},
"newLoginNudgeTitle": {
"message": "Save time with autofill"
@@ -5336,4 +5345,4 @@
"noPermissionsViewPage": {
"message": "You do not have permissions to view this page. Try logging in with a different account."
}
}
}

View File

@@ -168,18 +168,21 @@ type Story = StoryObj<ExtensionAnonLayoutWrapperComponent>;
@Component({
selector: "bit-default-primary-outlet-example-component",
template: "<p>Primary Outlet Example: <br> your primary component goes here</p>",
standalone: false,
})
class DefaultPrimaryOutletExampleComponent {}
@Component({
selector: "bit-default-secondary-outlet-example-component",
template: "<p>Secondary Outlet Example: <br> your secondary component goes here</p>",
standalone: false,
})
class DefaultSecondaryOutletExampleComponent {}
@Component({
selector: "bit-default-env-selector-outlet-example-component",
template: "<p>Env Selector Outlet Example: <br> your env selector component goes here</p>",
standalone: false,
})
class DefaultEnvSelectorOutletExampleComponent {}
@@ -264,6 +267,7 @@ const changedData: ExtensionAnonLayoutWrapperData = {
template: `
<button type="button" bitButton buttonType="primary" (click)="toggleData()">Toggle Data</button>
`,
standalone: false,
})
export class DynamicContentExampleComponent {
initialData = true;

View File

@@ -5,5 +5,6 @@ import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/ang
@Component({
selector: "app-set-password",
templateUrl: "set-password.component.html",
standalone: false,
})
export class SetPasswordComponent extends BaseSetPasswordComponent {}

View File

@@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b
useExisting: VaultTimeoutInputComponent,
},
],
standalone: false,
})
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {}

View File

@@ -8,6 +8,7 @@ import { postLogoutMessageListener$ } from "./utils/post-logout-message-listener
@Component({
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
standalone: false,
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
onSuccessfulChangePassword: () => Promise<void> = this.doOnSuccessfulChangePassword.bind(this);

View File

@@ -894,9 +894,7 @@ export default class NotificationBackground {
private async getDecryptedCipherById(cipherId: string, userId: UserId) {
const cipher = await this.cipherService.get(cipherId, userId);
if (cipher != null && cipher.type === CipherType.Login) {
return await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId),
);
return await this.cipherService.decrypt(cipher, userId);
}
return null;
}

View File

@@ -33,6 +33,9 @@ export function OptionSelectionButton({
class=${selectionButtonStyles({ disabled, toggledOn, theme })}
title=${text}
type="button"
aria-haspopup="menu"
aria-expanded=${toggledOn}
aria-controls="option-menu"
@click=${handleButtonClick}
>
${buttonIcon ?? nothing}

View File

@@ -11,6 +11,7 @@ export type IconProps = {
color?: string;
disabled?: boolean;
theme: Theme;
ariaHidden?: boolean;
};
export type Option = {

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function AngleDown({ color, disabled, theme }: IconProps) {
export function AngleDown({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 8" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 8"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M13.53.47a.75.75 0 0 0-1.06 0L7 5.94 1.53.47A.75.75 0 1 0 .47 1.53l6 6a.75.75 0 0 0 1.06 0l6-6a.75.75 0 0 0 0-1.06Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function AngleUp({ color, disabled, theme }: IconProps) {
export function AngleUp({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 8" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 8"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M.47 7.53a.75.75 0 0 0 1.06 0L7 2.06l5.47 5.47a.75.75 0 1 0 1.06-1.06l-6-6a.75.75 0 0 0-1.06 0l-6 6a.75.75 0 0 0 0 1.06Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Business({ color, disabled, theme }: IconProps) {
export function Business({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 16" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 12 16"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M3.25 3a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5ZM7.25 3a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5ZM7.25 6a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5ZM6.5 9.75A.75.75 0 0 1 7.25 9h1.5a.75.75 0 0 1 0 1.5h-1.5a.75.75 0 0 1-.75-.75ZM2.5 6.75A.75.75 0 0 1 3.25 6h1.5a.75.75 0 0 1 0 1.5h-1.5a.75.75 0 0 1-.75-.75ZM3.25 9a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Close({ color, disabled, theme }: IconProps) {
export function Close({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 14"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M.22.22a.75.75 0 0 1 1.06 0L7 5.94 12.72.22a.75.75 0 1 1 1.06 1.06L8.06 7l5.72 5.72a.75.75 0 1 1-1.06 1.06L7 8.06l-5.72 5.72a.75.75 0 0 1-1.06-1.06L5.94 7 .22 1.28a.75.75 0 0 1 0-1.06Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function CollectionShared({ color, disabled, theme }: IconProps) {
export function CollectionShared({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 14"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M3.5.75A.75.75 0 0 1 4.25 0h5.5a.75.75 0 0 1 0 1.5h-5.5A.75.75 0 0 1 3.5.75ZM2.25 2a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5h-9.5ZM6 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM10 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM7 11.46a1.928 1.928 0 0 0-.586-1.386 2.035 2.035 0 0 0-2.828 0A1.928 1.928 0 0 0 3 11.461c0 .298.241.539.54.539h2.92a.54.54 0 0 0 .54-.54ZM8 11.46a2.928 2.928 0 0 0-.371-1.426A2.005 2.005 0 0 1 9 9.5a2.035 2.035 0 0 1 1.414.574A1.928 1.928 0 0 1 11 11.461a.54.54 0 0 1-.54.539H7.904c.063-.168.097-.35.097-.54Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function ExclamationTriangle({ color, disabled, theme }: IconProps) {
export function ExclamationTriangle({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 15" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 15"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M9 11C9 11.5523 8.55229 12 8 12C7.44772 12 7 11.5523 7 11C7 10.4477 7.44772 10 8 10C8.55229 10 9 10.4477 9 11Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function ExternalLink({ color, disabled, theme }: IconProps) {
export function ExternalLink({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 14"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M1.5 2.75c0-.69.56-1.25 1.25-1.25h3.5a.75.75 0 0 0 0-1.5h-3.5A2.75 2.75 0 0 0 0 2.75v8.5A2.75 2.75 0 0 0 2.75 14h8.5A2.75 2.75 0 0 0 14 11.25v-3.5a.75.75 0 0 0-1.5 0v3.5c0 .69-.56 1.25-1.25 1.25h-8.5c-.69 0-1.25-.56-1.25-1.25v-8.5Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Family({ color, disabled, theme }: IconProps) {
export function Family({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
fill-rule="evenodd"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Folder({ color, disabled, theme }: IconProps) {
export function Folder({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 13" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 13"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M2 0a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8L6.586.586A2 2 0 0 0 5.172 0H2Zm5.379 3.5L5.525 1.646a.5.5 0 0 0-.353-.146H2a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h12a.5.5 0 0 0 .5-.5V4a.5.5 0 0 0-.5-.5H7.379Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Globe({ color, disabled, theme }: IconProps) {
export function Globe({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0Zm0 14.5c.23 0 .843-.226 1.487-1.514.524-1.048.906-2.526.994-4.236H5.519c.088 1.71.47 3.188.994 4.236C7.157 14.274 7.77 14.5 8 14.5ZM5.52 7.25h4.96c-.087-1.71-.47-3.188-.993-4.236C8.843 1.726 8.23 1.5 8 1.5c-.23 0-.843.226-1.487 1.514C5.99 4.062 5.607 5.54 5.52 7.25Zm6.463 0h2.474a6.506 6.506 0 0 0-3.766-5.168c.718 1.305 1.197 3.125 1.292 5.168Zm-7.966 0c.095-2.043.574-3.863 1.292-5.168A6.506 6.506 0 0 0 1.543 7.25h2.474Zm7.966 1.5c-.095 2.043-.574 3.863-1.292 5.168a6.506 6.506 0 0 0 3.766-5.168h-2.474Zm-6.677 5.185c-.718-1.305-1.197-3.125-1.292-5.168H1.54a6.506 6.506 0 0 0 3.766 5.168Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function PencilSquare({ color, disabled, theme }: IconProps) {
export function PencilSquare({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" fill="none" aria-hidden="true">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 15 15"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M11.013.677a1.75 1.75 0 0 1 2.474 0l.836.836a1.75 1.75 0 0 1 0 2.475L9.03 9.28a.75.75 0 0 1-.348.197l-3 .75a.75.75 0 0 1-.91-.91l.75-3a.75.75 0 0 1 .198-.348L11.013.677Zm1.414 1.06a.25.25 0 0 0-.354 0l-.646.647a.75.75 0 0 1 .103.086l1 1a.751.751 0 0 1 .087.103l.646-.646a.25.25 0 0 0 0-.353l-.836-.836Zm-.854 2.88a.752.752 0 0 1-.103-.087l-1-1a.756.756 0 0 1-.087-.103L6.928 6.884 6.531 8.47l1.586-.397 3.456-3.456Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Shield({ color, theme }: IconProps) {
export function Shield({ ariaHidden = true, color, theme }: IconProps) {
const shapeColor = color || themes[theme].brandLogo;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 16" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 16"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M13.469.2A.647.647 0 0 0 13 0H1a.639.639 0 0 0-.468.2.641.641 0 0 0-.2.468v8a4.81 4.81 0 0 0 .348 1.777c.216.557.507 1.083.865 1.563.367.48.779.925 1.229 1.329.417.383.857.741 1.317 1.073.4.284.82.553 1.26.807.44.254.75.425.932.515.183.09.33.16.44.208.087.041.181.062.277.06a.58.58 0 0 0 .27-.063c.113-.05.259-.118.444-.208s.5-.262.932-.515c.432-.253.857-.523 1.26-.807.46-.332.9-.69 1.319-1.073.45-.404.861-.849 1.228-1.33.357-.48.648-1.005.865-1.562a4.79 4.79 0 0 0 .348-1.777v-8A.63.63 0 0 0 13.47.2Zm-1.547 8.54c0 2.9-4.921 5.392-4.921 5.392V1.714h4.92v7.027Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function User({ color, disabled, theme }: IconProps) {
export function User({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 15" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 15"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M9.203 7.339a4 4 0 1 0-4.407 0A7.033 7.033 0 0 0 2.05 8.953a6.655 6.655 0 0 0-1.517 2.162A6.393 6.393 0 0 0 0 13.667C0 14.403.597 15 1.333 15h11.334c.736 0 1.333-.597 1.333-1.333 0-.876-.181-1.743-.533-2.552a6.654 6.654 0 0 0-1.517-2.162 7.032 7.032 0 0 0-2.747-1.614ZM9.5 4a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Zm2.592 7.714c.247.57.384 1.175.405 1.786H1.503a4.897 4.897 0 0 1 .405-1.786 5.156 5.156 0 0 1 1.177-1.675 5.534 5.534 0 0 1 1.787-1.136A5.805 5.805 0 0 1 7 8.5c.732 0 1.456.137 2.128.403.673.265 1.28.652 1.787 1.136a5.156 5.156 0 0 1 1.177 1.675Z"

View File

@@ -132,6 +132,7 @@ export const mockI18n = {
saveFailure: "Error saving",
saveFailureDetails: "Oh no! We couldn't save this. Try entering the details manually.",
saveLogin: "Save login",
selectItemAriaLabel: "Select $ITEMTYPE$, $ITEMNAME$",
typeLogin: "Login",
updateLoginAction: "Update login",
updateLogin: "Update existing login",

View File

@@ -48,6 +48,7 @@ export function NotificationConfirmationBody({
? NotificationConfirmationMessage({
buttonAria,
buttonText,
error,
itemName,
message: confirmationMessage,
messageDetails,

View File

@@ -8,6 +8,7 @@ import { spacing, themes, typography } from "../../constants/styles";
export type NotificationConfirmationMessageProps = {
buttonAria?: string;
buttonText?: string;
error?: string;
itemName?: string;
message?: string;
messageDetails?: string;
@@ -18,6 +19,7 @@ export type NotificationConfirmationMessageProps = {
export function NotificationConfirmationMessage({
buttonAria,
buttonText,
error,
itemName,
message,
messageDetails,
@@ -29,7 +31,11 @@ export function NotificationConfirmationMessage({
${message || buttonText
? html`
<div class=${singleLineWrapperStyles}>
<span class=${itemNameStyles(theme)} title=${itemName}> ${itemName} </span>
${!error && itemName
? html`
<span class=${itemNameStyles(theme)} title=${itemName}> ${itemName} </span>
`
: nothing}
<span
title=${message || buttonText}
class=${notificationConfirmationMessageStyles(theme)}

View File

@@ -13,11 +13,19 @@ const { css } = createEmotion({
});
export type OptionItemProps = Option & {
contextLabel?: string;
theme: Theme;
handleSelection: () => void;
};
export function OptionItem({ icon, text, value, theme, handleSelection }: OptionItemProps) {
export function OptionItem({
contextLabel,
icon,
text,
theme,
value,
handleSelection,
}: OptionItemProps) {
const handleSelectionKeyUpProxy = (event: KeyboardEvent) => {
const listenedForKeys = new Set(["Enter", "Space"]);
if (listenedForKeys.has(event.code) && event.target instanceof Element) {
@@ -29,12 +37,18 @@ export function OptionItem({ icon, text, value, theme, handleSelection }: Option
const iconProps: IconProps = { color: themes[theme].text.main, theme };
const itemIcon = icon?.(iconProps);
const ariaLabel =
contextLabel && text
? chrome.i18n.getMessage("selectItemAriaLabel", [contextLabel, text])
: text;
return html`<div
class=${optionItemStyles}
key=${value}
tabindex="0"
title=${text}
role="option"
aria-label=${ariaLabel}
@click=${handleSelection}
@keyup=${handleSelectionKeyUpProxy}
>

View File

@@ -33,17 +33,41 @@ export function OptionItems({
const isSafari = false;
return html`
<div class=${optionsStyles({ theme, topOffset })} key="container">
<div
class=${optionsStyles({ theme, topOffset })}
key="container"
@keyup=${(e: KeyboardEvent) => handleMenuKeyUp(e)}
>
${label ? html`<div class=${optionsLabelStyles({ theme })}>${label}</div>` : nothing}
<div class=${optionsWrapper({ isSafari, theme })}>
${options.map((option) =>
OptionItem({ ...option, theme, handleSelection: () => handleOptionSelection(option) }),
OptionItem({
...option,
theme,
contextLabel: label,
handleSelection: () => handleOptionSelection(option),
}),
)}
</div>
</div>
`;
}
function handleMenuKeyUp(event: KeyboardEvent) {
const items = [
...(event.currentTarget as HTMLElement).querySelectorAll<HTMLElement>('[tabindex="0"]'),
];
const index = items.indexOf(document.activeElement as HTMLElement);
const direction = event.key === "ArrowDown" ? 1 : event.key === "ArrowUp" ? -1 : 0;
if (index === -1 || direction === 0) {
return;
}
event.preventDefault();
items[(index + direction + items.length) % items.length]?.focus();
}
const optionsStyles = ({ theme, topOffset }: { theme: Theme; topOffset: number }) => css`
${typography.body1}

View File

@@ -48,10 +48,18 @@ export class OptionSelection extends LitElement {
@state()
private selection?: Option;
private handleButtonClick = (event: Event) => {
private static currentOpenInstance: OptionSelection | null = null;
private handleButtonClick = async (event: Event) => {
if (!this.disabled) {
// Menu is about to be shown
if (!this.showMenu) {
const isOpening = !this.showMenu;
if (isOpening) {
if (OptionSelection.currentOpenInstance && OptionSelection.currentOpenInstance !== this) {
OptionSelection.currentOpenInstance.showMenu = false;
}
OptionSelection.currentOpenInstance = this;
this.menuTopOffset = this.offsetTop;
// Distance from right edge of button to left edge of the viewport
@@ -71,9 +79,29 @@ export class OptionSelection extends LitElement {
optionsMenuItemMaxWidth + optionItemIconWidth + 2 + 8 + 12 * 2;
this.menuIsEndJustified = distanceFromViewportRightEdge < maxDifferenceThreshold;
} else {
if (OptionSelection.currentOpenInstance === this) {
OptionSelection.currentOpenInstance = null;
}
}
this.showMenu = !this.showMenu;
this.showMenu = isOpening;
if (this.showMenu) {
await this.updateComplete;
const firstItem = this.querySelector('#option-menu [tabindex="0"]') as HTMLElement;
firstItem?.focus();
}
}
};
private handleFocusOut = (event: FocusEvent) => {
const relatedTarget = event.relatedTarget;
if (!(relatedTarget instanceof Node) || !this.contains(relatedTarget)) {
this.showMenu = false;
if (OptionSelection.currentOpenInstance === this) {
OptionSelection.currentOpenInstance = null;
}
}
};
@@ -95,7 +123,10 @@ export class OptionSelection extends LitElement {
}
return html`
<div class=${optionSelectionStyles({ menuIsEndJustified: this.menuIsEndJustified })}>
<div
class=${optionSelectionStyles({ menuIsEndJustified: this.menuIsEndJustified })}
@focusout=${this.handleFocusOut}
>
${OptionSelectionButton({
disabled: this.disabled,
icon: this.selection?.icon,

View File

@@ -17,7 +17,7 @@ export class OverlayNotificationsContentService
private notificationBarIframeElement: HTMLIFrameElement | null = null;
private currentNotificationBarType: string | null = null;
private removeTabFromNotificationQueueTypes = new Set(["add", "change"]);
private notificationRefreshFlag: boolean;
private notificationRefreshFlag: boolean = false;
private notificationBarElementStyles: Partial<CSSStyleDeclaration> = {
height: "82px",
width: "430px",
@@ -57,6 +57,7 @@ export class OverlayNotificationsContentService
void sendExtensionMessage("checkNotificationQueue");
void sendExtensionMessage("notificationRefreshFlagValue").then((notificationRefreshFlag) => {
this.notificationRefreshFlag = !!notificationRefreshFlag;
this.setNotificationRefreshBarHeight();
});
}
@@ -223,15 +224,31 @@ export class OverlayNotificationsContentService
this.notificationBarElement.id = "bit-notification-bar";
setElementStyles(this.notificationBarElement, this.notificationBarElementStyles, true);
if (this.notificationRefreshFlag) {
setElementStyles(this.notificationBarElement, { height: "400px", right: "0" }, true);
}
this.setNotificationRefreshBarHeight();
this.notificationBarElement.appendChild(this.notificationBarIframeElement);
}
}
/**
* Sets the height of the notification bar based on the value of `notificationRefreshFlag`.
* If the flag is `true`, the bar is expanded to 400px and aligned right.
* If the flag is `false`, `null`, or `undefined`, it defaults to height of 82px.
* Skips if the notification bar element has not yet been created.
*
*/
private setNotificationRefreshBarHeight() {
const isNotificationV3 = !!this.notificationRefreshFlag;
if (!this.notificationBarElement) {
return;
}
if (isNotificationV3) {
setElementStyles(this.notificationBarElement, { height: "400px", right: "0" }, true);
}
}
/**
* Sets up the message listener for the initialization of the notification bar.
* This will send the initialization data to the notification bar iframe.

View File

@@ -216,9 +216,7 @@ export class Fido2Component implements OnInit, OnDestroy {
this.ciphers = await Promise.all(
message.cipherIds.map(async (cipherId) => {
const cipher = await this.cipherService.get(cipherId, activeUserId);
return cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
return this.cipherService.decrypt(cipher, activeUserId);
}),
);
@@ -237,9 +235,7 @@ export class Fido2Component implements OnInit, OnDestroy {
this.ciphers = await Promise.all(
message.existingCipherIds.map(async (cipherId) => {
const cipher = await this.cipherService.get(cipherId, activeUserId);
return cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
return this.cipherService.decrypt(cipher, activeUserId);
}),
);

View File

@@ -4,14 +4,14 @@ import { CommonModule } from "@angular/common";
import { Component, DestroyRef, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import {
FormBuilder,
FormControl,
FormGroup,
FormsModule,
ReactiveFormsModule,
FormBuilder,
FormGroup,
FormControl,
} from "@angular/forms";
import { RouterModule } from "@angular/router";
import { Observable, filter, firstValueFrom, map, switchMap } from "rxjs";
import { filter, firstValueFrom, Observable, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -55,7 +55,7 @@ import {
SelectModule,
TypographyModule,
} from "@bitwarden/components";
import { SpotlightComponent, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault";
import { NudgesService, NudgeType, SpotlightComponent } from "@bitwarden/vault";
import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service";
import { BrowserApi } from "../../../platform/browser/browser-api";
@@ -108,9 +108,7 @@ export class AutofillComponent implements OnInit {
protected showSpotlightNudge$: Observable<boolean> = this.accountService.activeAccount$.pipe(
filter((account): account is Account => account !== null),
switchMap((account) =>
this.vaultNudgesService
.showNudge$(VaultNudgeType.AutofillNudge, account.id)
.pipe(map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed)),
this.nudgesService.showNudgeSpotlight$(NudgeType.AutofillNudge, account.id),
),
);
@@ -155,7 +153,7 @@ export class AutofillComponent implements OnInit {
private configService: ConfigService,
private formBuilder: FormBuilder,
private destroyRef: DestroyRef,
private vaultNudgesService: VaultNudgesService,
private nudgesService: NudgesService,
private accountService: AccountService,
private autofillBrowserSettingsService: AutofillBrowserSettingsService,
) {
@@ -343,8 +341,8 @@ export class AutofillComponent implements OnInit {
}
async dismissSpotlight() {
await this.vaultNudgesService.dismissNudge(
VaultNudgeType.AutofillNudge,
await this.nudgesService.dismissNudge(
NudgeType.AutofillNudge,
await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)),
);
}

View File

@@ -183,6 +183,7 @@ import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-st
import { SendService } from "@bitwarden/common/tools/send/services/send.service";
import { InternalSendService as InternalSendServiceAbstraction } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service";
import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service";
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
@@ -199,6 +200,7 @@ import {
DefaultCipherAuthorizationService,
} from "@bitwarden/common/vault/services/cipher-authorization.service";
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service";
import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service";
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service";
@@ -408,6 +410,7 @@ export default class MainBackground {
endUserNotificationService: EndUserNotificationService;
inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
taskService: TaskService;
cipherEncryptionService: CipherEncryptionService;
ipcContentScriptManagerService: IpcContentScriptManagerService;
ipcService: IpcService;
@@ -856,6 +859,11 @@ export default class MainBackground {
this.bulkEncryptService = new FallbackBulkEncryptService(this.encryptService);
this.cipherEncryptionService = new DefaultCipherEncryptionService(
this.sdkService,
this.logService,
);
this.cipherService = new CipherService(
this.keyService,
this.domainSettingsService,
@@ -871,6 +879,7 @@ export default class MainBackground {
this.stateProvider,
this.accountService,
this.logService,
this.cipherEncryptionService,
);
this.folderService = new FolderService(
this.keyService,

View File

@@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward
@Component({
selector: "app-remove-password",
templateUrl: "remove-password.component.html",
standalone: false,
})
export class RemovePasswordComponent extends BaseRemovePasswordComponent {}

View File

@@ -13,7 +13,10 @@ import { PopupRouterCacheService, popupRouterCacheGuard } from "./popup-router-c
const flushPromises = async () => await new Promise(process.nextTick);
@Component({ template: "" })
@Component({
template: "",
standalone: false,
})
export class EmptyComponent {}
describe("Popup router cache guard", () => {

View File

@@ -19,10 +19,16 @@ import {
import { PopupViewCacheService } from "./popup-view-cache.service";
@Component({ template: "" })
@Component({
template: "",
standalone: false,
})
export class EmptyComponent {}
@Component({ template: "" })
@Component({
template: "",
standalone: false,
})
export class TestComponent {
private viewCacheService = inject(PopupViewCacheService);

View File

@@ -43,6 +43,7 @@ import { DesktopSyncVerificationDialogComponent } from "./components/desktop-syn
</div>
<bit-toast-container></bit-toast-container>
`,
standalone: false,
})
export class AppComponent implements OnInit, OnDestroy {
private compactModeService = inject(PopupCompactModeService);

View File

@@ -22,5 +22,6 @@ import { UserVerificationComponent as BaseComponent } from "@bitwarden/angular/a
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
]),
],
standalone: false,
})
export class UserVerificationComponent extends BaseComponent {}

View File

@@ -6,18 +6,19 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { Icons } from "@bitwarden/components";
import { VaultNudgesService } from "@bitwarden/vault";
import { NudgesService } from "@bitwarden/vault";
import { NavButton } from "../platform/popup/layout/popup-tab-navigation.component";
@Component({
selector: "app-tabs-v2",
templateUrl: "./tabs-v2.component.html",
standalone: false,
})
export class TabsV2Component {
private hasActiveBadges$ = this.accountService.activeAccount$
.pipe(getUserId)
.pipe(switchMap((userId) => this.vaultNudgesService.hasActiveBadges$(userId)));
.pipe(switchMap((userId) => this.nudgesService.hasActiveBadges$(userId)));
protected navButtons$: Observable<NavButton[]> = combineLatest([
this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge),
this.hasActiveBadges$,
@@ -53,7 +54,7 @@ export class TabsV2Component {
}),
);
constructor(
private vaultNudgesService: VaultNudgesService,
private nudgesService: NudgesService,
private accountService: AccountService,
private readonly configService: ConfigService,
) {}

View File

@@ -50,7 +50,7 @@
<a
bit-item-content
routerLink="/vault-settings"
(click)="dismissBadge(VaultNudgeType.EmptyVaultNudge)"
(click)="dismissBadge(NudgeType.EmptyVaultNudge)"
>
<i slot="start" class="bwi bwi-vault" aria-hidden="true"></i>
<div class="tw-flex tw-items-center tw-justify-center">
@@ -60,7 +60,7 @@
Will make this dynamic when more nudges are added
-->
<span
*ngIf="!(showVaultBadge$ | async)?.hasBadgeDismissed"
*ngIf="showVaultBadge$ | async"
bitBadge
variant="notification"
[attr.aria-label]="'nudgeBadgeAria' | i18n"
@@ -91,7 +91,7 @@
<div class="tw-flex tw-items-center tw-justify-center">
<p class="tw-pr-2">{{ "downloadBitwardenOnAllDevices" | i18n }}</p>
<span
*ngIf="(downloadBitwardenNudgeStatus$ | async)?.hasBadgeDismissed === false"
*ngIf="downloadBitwardenNudgeStatus$ | async"
bitBadge
variant="notification"
[attr.aria-label]="'nudgeBadgeAria' | i18n"

View File

@@ -17,7 +17,7 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { UserId } from "@bitwarden/common/types/guid";
import { BadgeComponent, ItemModule } from "@bitwarden/components";
import { NudgeStatus, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault";
import { NudgesService, NudgeType } from "@bitwarden/vault";
import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component";
import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service";
@@ -42,7 +42,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
],
})
export class SettingsV2Component implements OnInit {
VaultNudgeType = VaultNudgeType;
NudgeType = NudgeType;
activeUserId: UserId | null = null;
protected isBrowserAutofillSettingOverridden = false;
@@ -51,21 +51,21 @@ export class SettingsV2Component implements OnInit {
shareReplay({ bufferSize: 1, refCount: true }),
);
accountSecurityNudgeStatus$: Observable<NudgeStatus> = this.authenticatedAccount$.pipe(
accountSecurityNudgeStatus$: Observable<boolean> = this.authenticatedAccount$.pipe(
switchMap((account) =>
this.vaultNudgesService.showNudge$(VaultNudgeType.AccountSecurity, account.id),
this.nudgesService.showNudgeBadge$(NudgeType.AccountSecurity, account.id),
),
);
downloadBitwardenNudgeStatus$: Observable<NudgeStatus> = this.authenticatedAccount$.pipe(
downloadBitwardenNudgeStatus$: Observable<boolean> = this.authenticatedAccount$.pipe(
switchMap((account) =>
this.vaultNudgesService.showNudge$(VaultNudgeType.DownloadBitwarden, account.id),
this.nudgesService.showNudgeBadge$(NudgeType.DownloadBitwarden, account.id),
),
);
showVaultBadge$: Observable<NudgeStatus> = this.authenticatedAccount$.pipe(
showVaultBadge$: Observable<boolean> = this.authenticatedAccount$.pipe(
switchMap((account) =>
this.vaultNudgesService.showNudge$(VaultNudgeType.EmptyVaultNudge, account.id),
this.nudgesService.showNudgeBadge$(NudgeType.EmptyVaultNudge, account.id),
),
);
@@ -74,9 +74,9 @@ export class SettingsV2Component implements OnInit {
this.authenticatedAccount$,
]).pipe(
switchMap(([defaultBrowserAutofillDisabled, account]) =>
this.vaultNudgesService.showNudge$(VaultNudgeType.AutofillNudge, account.id).pipe(
map((nudgeStatus) => {
return !defaultBrowserAutofillDisabled && nudgeStatus.hasBadgeDismissed === false;
this.nudgesService.showNudgeBadge$(NudgeType.AutofillNudge, account.id).pipe(
map((badgeStatus) => {
return !defaultBrowserAutofillDisabled && badgeStatus;
}),
),
),
@@ -87,7 +87,7 @@ export class SettingsV2Component implements OnInit {
);
constructor(
private readonly vaultNudgesService: VaultNudgesService,
private readonly nudgesService: NudgesService,
private readonly accountService: AccountService,
private readonly autofillBrowserSettingsService: AutofillBrowserSettingsService,
private readonly configService: ConfigService,
@@ -100,10 +100,10 @@ export class SettingsV2Component implements OnInit {
);
}
async dismissBadge(type: VaultNudgeType) {
if (!(await firstValueFrom(this.showVaultBadge$)).hasBadgeDismissed) {
async dismissBadge(type: NudgeType) {
if (await firstValueFrom(this.showVaultBadge$)) {
const account = await firstValueFrom(this.authenticatedAccount$);
await this.vaultNudgesService.dismissNudge(type, account.id as UserId, true);
await this.nudgesService.dismissNudge(type, account.id as UserId, true);
}
}
}

View File

@@ -11,7 +11,6 @@ import { CollectionService } from "@bitwarden/admin-console/common";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { OrgKey, UserKey } from "@bitwarden/common/types/key";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
@@ -66,11 +65,7 @@ export class AssignCollections {
route.queryParams.pipe(
switchMap(async ({ cipherId }) => {
const cipherDomain = await this.cipherService.get(cipherId, userId);
const key: UserKey | OrgKey = await this.cipherService.getKeyForCipherKeyDecryption(
cipherDomain,
userId,
);
return cipherDomain.decrypt(key);
return await this.cipherService.decrypt(cipherDomain, userId);
}),
),
),

View File

@@ -81,6 +81,7 @@ describe("OpenAttachmentsComponent", () => {
useValue: {
get: getCipher,
getKeyForCipherKeyDecryption: () => Promise.resolve(null),
decrypt: jest.fn().mockResolvedValue(cipherView),
},
},
{

View File

@@ -81,9 +81,7 @@ export class OpenAttachmentsComponent implements OnInit {
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId);
const cipher = await cipherDomain.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId),
);
const cipher = await this.cipherService.decrypt(cipherDomain, activeUserId);
if (!cipher.organizationId) {
this.cipherIsAPartOfFreeOrg = false;

View File

@@ -40,12 +40,9 @@
type="button"
bitIconButton="bwi-clone"
size="small"
[appA11yTitle]="
'copyFieldValue' | i18n: singleCopiableLogin.key : singleCopiableLogin.value
"
[appCopyClick]="singleCopiableLogin.value"
[valueLabel]="singleCopiableLogin.key"
showToast
[appA11yTitle]="singleCopiableLogin.key"
[appCopyField]="$any(singleCopiableLogin.field)"
[cipher]="cipher"
></button>
<ng-container *ngIf="!singleCopiableLogin">
<button

View File

@@ -15,6 +15,7 @@ import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy
type CipherItem = {
value: string;
key: string;
field?: string;
};
@Component({
@@ -43,15 +44,23 @@ export class ItemCopyActionsComponent {
);
}
/*
* singleCopiableLogin uses appCopyField instead of appCopyClick. This allows for the TOTP
* code to be copied correctly. See #14167
*/
get singleCopiableLogin() {
const loginItems: CipherItem[] = [
{ value: this.cipher.login.username, key: "username" },
{ value: this.cipher.login.password, key: "password" },
{ value: this.cipher.login.totp, key: "totp" },
{ value: this.cipher.login.username, key: "copyUsername", field: "username" },
{ value: this.cipher.login.password, key: "copyPassword", field: "password" },
{ value: this.cipher.login.totp, key: "copyVerificationCode", field: "totp" },
];
// If both the password and username are visible but the password is hidden, return the username
if (!this.cipher.viewPassword && this.cipher.login.username && this.cipher.login.password) {
return { value: this.cipher.login.username, key: this.i18nService.t("username") };
return {
value: this.cipher.login.username,
key: this.i18nService.t("copyUsername"),
field: "username",
};
}
return this.findSingleCopiableItem(loginItems);
}
@@ -78,12 +87,10 @@ export class ItemCopyActionsComponent {
* Given a list of CipherItems, if there is only one item with a value,
* return it with the translated key. Otherwise return null
*/
findSingleCopiableItem(items: { value: string; key: string }[]): CipherItem | null {
const singleItemWithValue = items.find(
(key) => key.value && items.every((f) => f === key || !f.value),
);
return singleItemWithValue
? { value: singleItemWithValue.value, key: this.i18nService.t(singleItemWithValue.key) }
findSingleCopiableItem(items: CipherItem[]): CipherItem | null {
const itemsWithValue = items.filter(({ value }) => !!value);
return itemsWithValue.length === 1
? { ...itemsWithValue[0], key: this.i18nService.t(itemsWithValue[0].key) }
: null;
}

View File

@@ -1,29 +0,0 @@
<ng-container *ngIf="showNewCustomizationSettingsCallout">
<button
type="button"
class="tw-absolute tw-bottom-[12px] tw-right-[47px]"
[bitPopoverTriggerFor]="newCustomizationOptionsCallout"
[position]="'above-end'"
[popoverOpen]="true"
#triggerRef="popoverTrigger"
></button>
<bit-popover
[title]="'newCustomizationOptionsCalloutTitle' | i18n"
#newCustomizationOptionsCallout
(closed)="dismissCallout()"
>
<div bitTypography="body2" (click)="goToAppearance()">
{{ "newCustomizationOptionsCalloutContent" | i18n }}
<a
tabIndex="0"
bitLink
class="tw-font-bold"
linkType="primary"
routerLink="/appearance"
(keydown.enter)="goToAppearance()"
>
{{ "newCustomizationOptionsCalloutLink" | i18n }}
</a>
</div>
</bit-popover>
</ng-container>

View File

@@ -1,81 +0,0 @@
import { CommonModule } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { UserId } from "@bitwarden/common/types/guid";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { ButtonModule, PopoverModule } from "@bitwarden/components";
import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service";
import { VaultPageService } from "../vault-page.service";
@Component({
selector: "new-settings-callout",
templateUrl: "new-settings-callout.component.html",
standalone: true,
imports: [PopoverModule, JslibModule, CommonModule, ButtonModule],
providers: [VaultPageService],
})
export class NewSettingsCalloutComponent implements OnInit, OnDestroy {
protected showNewCustomizationSettingsCallout = false;
protected activeUserId: UserId | null = null;
constructor(
private accountService: AccountService,
private vaultProfileService: VaultProfileService,
private vaultPageService: VaultPageService,
private router: Router,
private logService: LogService,
private copyButtonService: VaultPopupCopyButtonsService,
private vaultSettingsService: VaultSettingsService,
) {}
async ngOnInit() {
this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const showQuickCopyActions = await firstValueFrom(this.copyButtonService.showQuickCopyActions$);
const clickItemsToAutofillVaultView = await firstValueFrom(
this.vaultSettingsService.clickItemsToAutofillVaultView$,
);
let profileCreatedDate: Date;
try {
profileCreatedDate = await this.vaultProfileService.getProfileCreationDate(this.activeUserId);
} catch (e) {
this.logService.error("Error getting profile creation date", e);
// Default to before the cutoff date to ensure the callout is shown
profileCreatedDate = new Date("2024-12-24");
}
const hasCalloutBeenDismissed = await firstValueFrom(
this.vaultPageService.isCalloutDismissed(this.activeUserId),
);
this.showNewCustomizationSettingsCallout =
!showQuickCopyActions &&
!clickItemsToAutofillVaultView &&
!hasCalloutBeenDismissed &&
profileCreatedDate < new Date("2024-12-25");
}
async goToAppearance() {
await this.router.navigate(["/appearance"]);
}
async dismissCallout() {
if (this.activeUserId) {
await this.vaultPageService.dismissCallout(this.activeUserId);
}
}
async ngOnDestroy() {
await this.dismissCallout();
}
}

View File

@@ -1,35 +0,0 @@
import { inject, Injectable } from "@angular/core";
import { map, Observable } from "rxjs";
import {
BANNERS_DISMISSED_DISK,
StateProvider,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
export const NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY = new UserKeyDefinition<boolean>(
BANNERS_DISMISSED_DISK,
"newCustomizationOptionsCalloutDismissed",
{
deserializer: (calloutDismissed) => calloutDismissed,
clearOn: [], // Do not clear dismissed callouts
},
);
@Injectable()
export class VaultPageService {
private stateProvider = inject(StateProvider);
isCalloutDismissed(userId: UserId): Observable<boolean> {
return this.stateProvider
.getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY)
.state$.pipe(map((dismissed) => !!dismissed));
}
async dismissCallout(userId: UserId): Promise<void> {
await this.stateProvider
.getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY)
.update(() => true);
}
}

View File

@@ -69,8 +69,6 @@ export class PasswordHistoryV2Component implements OnInit {
const activeUserId = activeAccount.id as UserId;
const cipher = await this.cipherService.get(cipherId, activeUserId);
this.cipher = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
this.cipher = await this.cipherService.decrypt(cipher, activeUserId);
}
}

View File

@@ -36,7 +36,7 @@
[subtitle]="'emptyVaultNudgeBody' | i18n"
[buttonText]="'emptyVaultNudgeButton' | i18n"
(onButtonClick)="navigateToImport()"
(onDismiss)="dismissVaultNudgeSpotlight(VaultNudgeType.EmptyVaultNudge)"
(onDismiss)="dismissVaultNudgeSpotlight(NudgeType.EmptyVaultNudge)"
>
</bit-spotlight>
</ng-container>
@@ -44,9 +44,13 @@
<div class="tw-mb-4" *ngIf="showHasItemsVaultSpotlight$ | async">
<bit-spotlight
[title]="'hasItemsVaultNudgeTitle' | i18n"
[subtitle]="'hasItemsVaultNudgeBody' | i18n"
(onDismiss)="dismissVaultNudgeSpotlight(VaultNudgeType.HasVaultItems)"
(onDismiss)="dismissVaultNudgeSpotlight(NudgeType.HasVaultItems)"
>
<ul class="tw-pl-4 tw-text-main" bitTypography="body2">
<li>{{ "hasItemsVaultNudgeBodyOne" | i18n }}</li>
<li>{{ "hasItemsVaultNudgeBodyTwo" | i18n }}</li>
<li>{{ "hasItemsVaultNudgeBodyThree" | i18n }}</li>
</ul>
</bit-spotlight>
</div>
<vault-at-risk-password-callout
@@ -103,5 +107,4 @@
></app-vault-list-items-container>
</div>
</ng-container>
<new-settings-callout></new-settings-callout>
</popup-page>

View File

@@ -19,16 +19,23 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { ButtonModule, DialogService, Icons, NoItemsModule } from "@bitwarden/components";
import {
ButtonModule,
DialogService,
Icons,
NoItemsModule,
TypographyModule,
} from "@bitwarden/components";
import {
DecryptionFailureDialogComponent,
NudgesService,
NudgeType,
SpotlightComponent,
VaultIcons,
VaultNudgesService,
VaultNudgeType,
} from "@bitwarden/vault";
import { CurrentAccountComponent } from "../../../../auth/popup/account-switching/current-account.component";
@@ -49,9 +56,7 @@ import {
NewItemDropdownV2Component,
NewItemInitialValues,
} from "./new-item-dropdown/new-item-dropdown-v2.component";
import { NewSettingsCalloutComponent } from "./new-settings-callout/new-settings-callout.component";
import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component";
import { VaultPageService } from "./vault-page.service";
import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from ".";
@@ -83,27 +88,24 @@ enum VaultState {
ScrollingModule,
VaultHeaderV2Component,
AtRiskPasswordCalloutComponent,
NewSettingsCalloutComponent,
SpotlightComponent,
RouterModule,
TypographyModule,
],
providers: [VaultPageService],
})
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
@ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement;
VaultNudgeType = VaultNudgeType;
NudgeType = NudgeType;
cipherType = CipherType;
private activeUserId$ = this.accountService.activeAccount$.pipe(getUserId);
showEmptyVaultSpotlight$: Observable<boolean> = this.activeUserId$.pipe(
switchMap((userId) =>
this.vaultNudgesService.showNudge$(VaultNudgeType.EmptyVaultNudge, userId),
this.nudgesService.showNudgeSpotlight$(NudgeType.EmptyVaultNudge, userId),
),
map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed),
);
showHasItemsVaultSpotlight$: Observable<boolean> = this.activeUserId$.pipe(
switchMap((userId) => this.vaultNudgesService.showNudge$(VaultNudgeType.HasVaultItems, userId)),
map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed),
switchMap((userId) => this.nudgesService.showNudgeSpotlight$(NudgeType.HasVaultItems, userId)),
);
activeUserId: UserId | null = null;
@@ -144,7 +146,6 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
protected noResultsIcon = Icons.NoResults;
protected VaultStateEnum = VaultState;
protected showNewCustomizationSettingsCallout = false;
constructor(
private vaultPopupItemsService: VaultPopupItemsService,
@@ -156,8 +157,9 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
private dialogService: DialogService,
private vaultCopyButtonsService: VaultPopupCopyButtonsService,
private introCarouselService: IntroCarouselService,
private vaultNudgesService: VaultNudgesService,
private nudgesService: NudgesService,
private router: Router,
private i18nService: I18nService,
) {
combineLatest([
this.vaultPopupItemsService.emptyVault$,
@@ -225,8 +227,8 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
}
}
async dismissVaultNudgeSpotlight(type: VaultNudgeType) {
await this.vaultNudgesService.dismissNudge(type, this.activeUserId as UserId);
async dismissVaultNudgeSpotlight(type: NudgeType) {
await this.nudgesService.dismissNudge(type, this.activeUserId as UserId);
}
protected readonly FeatureFlag = FeatureFlag;

View File

@@ -82,6 +82,7 @@ describe("ViewV2Component", () => {
getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}),
deleteWithServer: jest.fn().mockResolvedValue(undefined),
softDeleteWithServer: jest.fn().mockResolvedValue(undefined),
decrypt: jest.fn().mockResolvedValue(mockCipher),
};
beforeEach(async () => {

View File

@@ -203,9 +203,7 @@ export class ViewV2Component {
async getCipherData(id: string, userId: UserId) {
const cipher = await this.cipherService.get(id, userId);
return await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId),
);
return await this.cipherService.decrypt(cipher, userId);
}
async editCipher() {

View File

@@ -7,7 +7,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { CardComponent, LinkModule, TypographyModule } from "@bitwarden/components";
import { VaultNudgesService, VaultNudgeType } from "@bitwarden/vault";
import { NudgesService, NudgeType } from "@bitwarden/vault";
import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component";
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
@@ -32,12 +32,12 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
})
export class DownloadBitwardenComponent implements OnInit {
constructor(
private vaultNudgeService: VaultNudgesService,
private nudgesService: NudgesService,
private accountService: AccountService,
) {}
async ngOnInit() {
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
await this.vaultNudgeService.dismissNudge(VaultNudgeType.DownloadBitwarden, userId);
await this.nudgesService.dismissNudge(NudgeType.DownloadBitwarden, userId);
}
}

View File

@@ -59,15 +59,11 @@ export class ShareCommand {
return Response.badRequest("This item already belongs to an organization.");
}
const cipherView = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
const cipherView = await this.cipherService.decrypt(cipher, activeUserId);
try {
await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId);
const updatedCipher = await this.cipherService.get(cipher.id, activeUserId);
const decCipher = await updatedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
);
const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId);
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {

View File

@@ -90,9 +90,7 @@ export class EditCommand {
return Response.notFound();
}
let cipherView = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
let cipherView = await this.cipherService.decrypt(cipher, activeUserId);
if (cipherView.isDeleted) {
return Response.badRequest("You may not edit a deleted item. Use the restore command first.");
}
@@ -100,9 +98,7 @@ export class EditCommand {
const encCipher = await this.cipherService.encrypt(cipherView, activeUserId);
try {
const updatedCipher = await this.cipherService.updateWithServer(encCipher);
const decCipher = await updatedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
);
const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId);
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {
@@ -132,12 +128,7 @@ export class EditCommand {
cipher,
activeUserId,
);
const decCipher = await updatedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(
updatedCipher,
await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)),
),
);
const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId);
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {

View File

@@ -116,9 +116,7 @@ export class GetCommand extends DownloadCommand {
if (Utils.isGuid(id)) {
const cipher = await this.cipherService.get(id, activeUserId);
if (cipher != null) {
decCipher = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
decCipher = await this.cipherService.decrypt(cipher, activeUserId);
}
} else if (id.trim() !== "") {
let ciphers = await this.cipherService.getAllDecrypted(activeUserId);

View File

@@ -139,12 +139,14 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s
import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider";
import { SendService } from "@bitwarden/common/tools/send/services/send.service";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service";
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import {
CipherAuthorizationService,
DefaultCipherAuthorizationService,
} from "@bitwarden/common/vault/services/cipher-authorization.service";
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service";
import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service";
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service";
@@ -284,6 +286,7 @@ export class ServiceContainer {
ssoUrlService: SsoUrlService;
masterPasswordApiService: MasterPasswordApiServiceAbstraction;
bulkEncryptService: FallbackBulkEncryptService;
cipherEncryptionService: CipherEncryptionService;
constructor() {
let p = null;
@@ -679,6 +682,11 @@ export class ServiceContainer {
this.accountService,
);
this.cipherEncryptionService = new DefaultCipherEncryptionService(
this.sdkService,
this.logService,
);
this.cipherService = new CipherService(
this.keyService,
this.domainSettingsService,
@@ -694,6 +702,7 @@ export class ServiceContainer {
this.stateProvider,
this.accountService,
this.logService,
this.cipherEncryptionService,
);
this.folderService = new FolderService(

View File

@@ -93,9 +93,7 @@ export class CreateCommand {
const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId);
try {
const newCipher = await this.cipherService.createWithServer(cipher);
const decCipher = await newCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(newCipher, activeUserId),
);
const decCipher = await this.cipherService.decrypt(newCipher, activeUserId);
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {
@@ -162,9 +160,7 @@ export class CreateCommand {
new Uint8Array(fileBuf).buffer,
activeUserId,
);
const decCipher = await updatedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
);
const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId);
return Response.success(new CipherResponse(decCipher));
} catch (e) {
return Response.error(e);

View File

@@ -3045,9 +3045,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.43.1"
version = "1.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "492a604e2fd7f814268a378409e6c92b5525d747d10db9a229723f55a417958c"
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
dependencies = [
"backtrace",
"bytes",
@@ -3479,9 +3479,9 @@ dependencies = [
[[package]]
name = "widestring"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
[[package]]
name = "winapi"
@@ -3568,7 +3568,7 @@ dependencies = [
"windows-interface 0.59.1",
"windows-link",
"windows-result 0.3.2",
"windows-strings 0.4.0",
"windows-strings",
]
[[package]]
@@ -3643,13 +3643,13 @@ dependencies = [
[[package]]
name = "windows-registry"
version = "0.4.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
checksum = "ad1da3e436dc7653dfdf3da67332e22bff09bb0e28b0239e1624499c7830842e"
dependencies = [
"windows-link",
"windows-result 0.3.2",
"windows-strings 0.3.1",
"windows-targets 0.53.0",
"windows-strings",
]
[[package]]
@@ -3670,15 +3670,6 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.0"
@@ -3730,29 +3721,13 @@ dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b"
dependencies = [
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
@@ -3765,12 +3740,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
@@ -3783,12 +3752,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
@@ -3801,24 +3764,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
@@ -3831,12 +3782,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_plugin_authenticator"
version = "0.0.0"
@@ -3858,12 +3803,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
@@ -3876,12 +3815,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
@@ -3894,12 +3827,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.3"

View File

@@ -50,17 +50,17 @@ simplelog = "=0.12.2"
ssh-encoding = "=0.2.0"
ssh-key = {version = "=0.6.7", default-features = false }
sysinfo = "0.35.0"
thiserror = "=1.0.69"
tokio = "=1.43.1"
thiserror = "=2.0.12"
tokio = "=1.45.0"
tokio-stream = "=0.1.15"
tokio-util = "=0.7.13"
typenum = "=1.18.0"
uniffi = "=0.28.3"
widestring = "=1.1.0"
widestring = "=1.2.0"
windows = "=0.61.1"
windows-core = "=0.61.0"
windows-future = "=0.2.0"
windows-registry = "=0.4.0"
windows-registry = "=0.5.1"
zbus = "=4.4.0"
zbus_polkit = "=4.0.0"
zeroizing-alloc = "=0.1.0"

View File

@@ -243,7 +243,7 @@
},
"snap": {
"summary": "Bitwarden is a secure and free password manager for all of your devices.",
"description": "**Installation**\nBitwarden requires access to the `password-manager-service`. Please enable it through permissions or by running `sudo snap connect bitwarden:password-manager-service` after installation. See https://btwrdn.com/install-snap for details.",
"description": "Password Manager\n**Installation**\nBitwarden requires access to the `password-manager-service`. Please enable it through permissions or by running `sudo snap connect bitwarden:password-manager-service` after installation. See https://btwrdn.com/install-snap for details.",
"autoStart": true,
"base": "core22",
"confinement": "strict",

View File

@@ -54,6 +54,7 @@ import { NativeMessagingManifestService } from "../services/native-messaging-man
@Component({
selector: "app-settings",
templateUrl: "settings.component.html",
standalone: false,
})
export class SettingsComponent implements OnInit, OnDestroy {
// For use in template

View File

@@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b
useExisting: VaultTimeoutInputComponent,
},
],
standalone: false,
})
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {}

View File

@@ -11,7 +11,16 @@ import {
} from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { Router } from "@angular/router";
import { filter, firstValueFrom, map, Subject, switchMap, takeUntil, timeout } from "rxjs";
import {
filter,
firstValueFrom,
lastValueFrom,
map,
Subject,
switchMap,
takeUntil,
timeout,
} from "rxjs";
import { CollectionService } from "@bitwarden/admin-console/common";
import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction";
@@ -56,11 +65,11 @@ import { CipherType } from "@bitwarden/common/vault/enums";
import { DialogRef, DialogService, ToastOptions, ToastService } from "@bitwarden/components";
import { CredentialGeneratorHistoryDialogComponent } from "@bitwarden/generator-components";
import { KeyService, BiometricStateService } from "@bitwarden/key-management";
import { AddEditFolderDialogComponent, AddEditFolderDialogResult } from "@bitwarden/vault";
import { DeleteAccountComponent } from "../auth/delete-account.component";
import { PremiumComponent } from "../billing/app/accounts/premium.component";
import { MenuAccount, MenuUpdateRequest } from "../main/menu/menu.updater";
import { FolderAddEditComponent } from "../vault/app/vault/folder-add-edit.component";
import { SettingsComponent } from "./accounts/settings.component";
import { ExportDesktopComponent } from "./tools/export/export-desktop.component";
@@ -78,7 +87,6 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
<ng-template #settings></ng-template>
<ng-template #premium></ng-template>
<ng-template #passwordHistory></ng-template>
<ng-template #appFolderAddEdit></ng-template>
<ng-template #exportVault></ng-template>
<ng-template #appGenerator></ng-template>
<ng-template #loginApproval></ng-template>
@@ -93,6 +101,7 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
<bit-toast-container></bit-toast-container>
`,
standalone: false,
})
export class AppComponent implements OnInit, OnDestroy {
@ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
@@ -101,8 +110,6 @@ export class AppComponent implements OnInit, OnDestroy {
passwordHistoryRef: ViewContainerRef;
@ViewChild("exportVault", { read: ViewContainerRef, static: true })
exportVaultModalRef: ViewContainerRef;
@ViewChild("appFolderAddEdit", { read: ViewContainerRef, static: true })
folderAddEditModalRef: ViewContainerRef;
@ViewChild("appGenerator", { read: ViewContainerRef, static: true })
generatorModalRef: ViewContainerRef;
@ViewChild("loginApproval", { read: ViewContainerRef, static: true })
@@ -464,25 +471,11 @@ export class AppComponent implements OnInit, OnDestroy {
async addFolder() {
this.modalService.closeAll();
const [modal, childComponent] = await this.modalService.openViewRef(
FolderAddEditComponent,
this.folderAddEditModalRef,
(comp) => (comp.folderId = null),
);
this.modal = modal;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
childComponent.onSavedFolder.subscribe(async () => {
this.modal.close();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.syncService.fullSync(false);
});
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
const dialogRef = AddEditFolderDialogComponent.open(this.dialogService);
const result = await lastValueFrom(dialogRef.closed);
if (result === AddEditFolderDialogResult.Created) {
await this.syncService.fullSync(false);
}
}
async openGenerator() {

View File

@@ -8,6 +8,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
@Component({
selector: "app-avatar",
template: `<img *ngIf="src" [src]="src" [ngClass]="{ 'rounded-circle': circle }" />`,
standalone: false,
})
export class AvatarComponent implements OnChanges, OnInit {
@Input() size = 45;

View File

@@ -54,6 +54,7 @@ type InactiveAccount = ActiveAccount & {
transition("* => void", animate("100ms linear", style({ opacity: 0 }))),
]),
],
standalone: false,
})
export class AccountSwitcherComponent implements OnInit {
activeAccount$: Observable<ActiveAccount | null>;

View File

@@ -3,5 +3,6 @@ import { Component } from "@angular/core";
@Component({
selector: "app-header",
templateUrl: "header.component.html",
standalone: false,
})
export class HeaderComponent {}

View File

@@ -11,6 +11,7 @@ import { SearchBarService, SearchBarState } from "./search-bar.service";
@Component({
selector: "app-search",
templateUrl: "search.component.html",
standalone: false,
})
export class SearchComponent implements OnInit, OnDestroy {
state: SearchBarState;

View File

@@ -28,6 +28,7 @@ const BroadcasterSubscriptionId = "SetPasswordComponent";
@Component({
selector: "app-set-password",
templateUrl: "set-password.component.html",
standalone: false,
})
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnInit, OnDestroy {
constructor(

View File

@@ -5,5 +5,6 @@ import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "
@Component({
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
standalone: false,
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {}

View File

@@ -199,9 +199,7 @@ export class DesktopAutofillService implements OnDestroy {
return;
}
const decrypted = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
const decrypted = await this.cipherService.decrypt(cipher, activeUserId);
const fido2Credential = decrypted.login.fido2Credentials?.[0];
if (!fido2Credential) {

View File

@@ -13,6 +13,7 @@ import { DialogService, ToastService } from "@bitwarden/components";
@Component({
selector: "app-premium",
templateUrl: "premium.component.html",
standalone: false,
})
export class PremiumComponent extends BasePremiumComponent {
constructor(

View File

@@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward
@Component({
selector: "app-remove-password",
templateUrl: "remove-password.component.html",
standalone: false,
})
export class RemovePasswordComponent extends BaseRemovePasswordComponent {}

View File

@@ -1103,9 +1103,6 @@
"addNewItem": {
"message": "New item"
},
"addNewFolder": {
"message": "New folder"
},
"view": {
"message": "View"
},
@@ -3709,6 +3706,15 @@
"move": {
"message": "Move"
},
"newFolder": {
"message": "New folder"
},
"folderName": {
"message": "Folder Name"
},
"folderHintText": {
"message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums"
},
"newLoginNudgeTitle": {
"message": "Save time with autofill"
},

View File

@@ -114,8 +114,8 @@ export class FileMenu extends FirstMenu implements IMenubarMenu {
private get addNewFolder(): MenuItemConstructorOptions {
return {
id: "addNewFolder",
label: this.localize("addNewFolder"),
id: "newFolder",
label: this.localize("newFolder"),
click: () => this.sendMessage("newFolder"),
enabled: !this._isLocked,
};

View File

@@ -1,6 +1,6 @@
import { dialog, shell } from "electron";
import log from "electron-log";
import { autoUpdater } from "electron-updater";
import { autoUpdater, UpdateDownloadedEvent, VerifyUpdateSupport } from "electron-updater";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -15,6 +15,8 @@ export class UpdaterMain {
private doingUpdateCheck = false;
private doingUpdateCheckWithFeedback = false;
private canUpdate = false;
private updateDownloaded: UpdateDownloadedEvent = null;
private originalRolloutFunction: VerifyUpdateSupport = null;
constructor(
private i18nService: I18nService,
@@ -22,6 +24,8 @@ export class UpdaterMain {
) {
autoUpdater.logger = log;
this.originalRolloutFunction = autoUpdater.isUserWithinRollout;
const linuxCanUpdate = process.platform === "linux" && isAppImage();
const windowsCanUpdate =
process.platform === "win32" && !isWindowsStore() && !isWindowsPortable();
@@ -57,20 +61,16 @@ export class UpdaterMain {
});
if (result.response === 0) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
autoUpdater.downloadUpdate();
await autoUpdater.downloadUpdate();
} else {
this.reset();
}
}
});
autoUpdater.on("update-not-available", () => {
autoUpdater.on("update-not-available", async () => {
if (this.doingUpdateCheckWithFeedback && this.windowMain.win != null) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
dialog.showMessageBox(this.windowMain.win, {
await dialog.showMessageBox(this.windowMain.win, {
message: this.i18nService.t("noUpdatesAvailable"),
buttons: [this.i18nService.t("ok")],
defaultId: 0,
@@ -86,22 +86,8 @@ export class UpdaterMain {
return;
}
const result = await dialog.showMessageBox(this.windowMain.win, {
type: "info",
title: this.i18nService.t("bitwarden") + " - " + this.i18nService.t("restartToUpdate"),
message: this.i18nService.t("restartToUpdate"),
detail: this.i18nService.t("restartToUpdateDesc", info.version),
buttons: [this.i18nService.t("restart"), this.i18nService.t("later")],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
// Quit and install have a different window logic, setting `isQuitting` just to be safe.
this.windowMain.isQuitting = true;
autoUpdater.quitAndInstall(true, true);
}
this.updateDownloaded = info;
await this.promptRestartUpdate(info);
});
autoUpdater.on("error", (error) => {
@@ -117,15 +103,22 @@ export class UpdaterMain {
}
async checkForUpdate(withFeedback = false) {
if (this.doingUpdateCheck || isDev()) {
if (isDev()) {
return;
}
if (this.updateDownloaded && withFeedback) {
await this.promptRestartUpdate(this.updateDownloaded);
return;
}
if (this.doingUpdateCheck) {
return;
}
if (!this.canUpdate) {
if (withFeedback) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
shell.openExternal("https://github.com/bitwarden/clients/releases");
void shell.openExternal("https://github.com/bitwarden/clients/releases");
}
return;
@@ -134,6 +127,10 @@ export class UpdaterMain {
this.doingUpdateCheckWithFeedback = withFeedback;
if (withFeedback) {
autoUpdater.autoDownload = false;
// If the user has explicitly checked for updates, we want to bypass
// the current staging rollout percentage
autoUpdater.isUserWithinRollout = (info) => true;
}
await autoUpdater.checkForUpdates();
@@ -141,7 +138,29 @@ export class UpdaterMain {
private reset() {
autoUpdater.autoDownload = true;
// Reset the rollout check to the default behavior
autoUpdater.isUserWithinRollout = this.originalRolloutFunction;
this.doingUpdateCheck = false;
this.updateDownloaded = null;
}
private async promptRestartUpdate(info: UpdateDownloadedEvent) {
const result = await dialog.showMessageBox(this.windowMain.win, {
type: "info",
title: this.i18nService.t("bitwarden") + " - " + this.i18nService.t("restartToUpdate"),
message: this.i18nService.t("restartToUpdate"),
detail: this.i18nService.t("restartToUpdateDesc", info.version),
buttons: [this.i18nService.t("restart"), this.i18nService.t("later")],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
// Quit and install have a different window logic, setting `isQuitting` just to be safe.
this.windowMain.isQuitting = true;
autoUpdater.quitAndInstall(true, true);
}
}
private userDisabledUpdates(): boolean {

View File

@@ -207,9 +207,7 @@ export class EncryptedMessageHandlerService {
return { status: "failure" };
}
const cipherView = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
const cipherView = await this.cipherService.decrypt(cipher, activeUserId);
cipherView.name = credentialUpdatePayload.name;
cipherView.login.password = credentialUpdatePayload.password;
cipherView.login.username = credentialUpdatePayload.userName;

View File

@@ -7,6 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
@Component({
selector: "app-vault-add-edit-custom-fields",
templateUrl: "add-edit-custom-fields.component.html",
standalone: false,
})
export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent {
constructor(i18nService: I18nService, eventCollectionService: EventCollectionService) {

View File

@@ -30,6 +30,7 @@ const BroadcasterSubscriptionId = "AddEditComponent";
@Component({
selector: "app-vault-add-edit",
templateUrl: "add-edit.component.html",
standalone: false,
})
export class AddEditComponent extends BaseAddEditComponent implements OnInit, OnChanges, OnDestroy {
@ViewChild("form")

View File

@@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -17,6 +18,7 @@ import { KeyService } from "@bitwarden/key-management";
@Component({
selector: "app-vault-attachments",
templateUrl: "attachments.component.html",
standalone: false,
})
export class AttachmentsComponent extends BaseAttachmentsComponent {
constructor(
@@ -33,6 +35,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
toastService: ToastService,
configService: ConfigService,
) {
super(
cipherService,
@@ -49,6 +52,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
billingAccountProfileStateService,
accountService,
toastService,
configService,
);
}
}

View File

@@ -13,6 +13,7 @@ import { ToastService } from "@bitwarden/components";
@Component({
selector: "app-vault-collections",
templateUrl: "collections.component.html",
standalone: false,
})
export class CollectionsComponent extends BaseCollectionsComponent {
constructor(

View File

@@ -14,6 +14,7 @@ import { KeyService } from "@bitwarden/key-management";
@Component({
selector: "app-folder-add-edit",
templateUrl: "folder-add-edit.component.html",
standalone: false,
})
export class FolderAddEditComponent extends BaseFolderAddEditComponent {
constructor(

View File

@@ -10,6 +10,7 @@ import { ToastService } from "@bitwarden/components";
@Component({
selector: "app-password-history",
templateUrl: "password-history.component.html",
standalone: false,
})
export class PasswordHistoryComponent extends BasePasswordHistoryComponent {
constructor(

View File

@@ -13,6 +13,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
@Component({
selector: "app-vault-share",
templateUrl: "share.component.html",
standalone: false,
})
export class ShareComponent extends BaseShareComponent {
constructor(

View File

@@ -5,5 +5,6 @@ import { CollectionFilterComponent as BaseCollectionFilterComponent } from "@bit
@Component({
selector: "app-collection-filter",
templateUrl: "collection-filter.component.html",
standalone: false,
})
export class CollectionFilterComponent extends BaseCollectionFilterComponent {}

View File

@@ -5,5 +5,6 @@ import { FolderFilterComponent as BaseFolderFilterComponent } from "@bitwarden/a
@Component({
selector: "app-folder-filter",
templateUrl: "folder-filter.component.html",
standalone: false,
})
export class FolderFilterComponent extends BaseFolderFilterComponent {}

View File

@@ -12,6 +12,7 @@ import { ToastService } from "@bitwarden/components";
@Component({
selector: "app-organization-filter",
templateUrl: "organization-filter.component.html",
standalone: false,
})
export class OrganizationFilterComponent extends BaseOrganizationFilterComponent {
get show() {

View File

@@ -5,5 +5,6 @@ import { StatusFilterComponent as BaseStatusFilterComponent } from "@bitwarden/a
@Component({
selector: "app-status-filter",
templateUrl: "status-filter.component.html",
standalone: false,
})
export class StatusFilterComponent extends BaseStatusFilterComponent {}

View File

@@ -5,6 +5,7 @@ import { TypeFilterComponent as BaseTypeFilterComponent } from "@bitwarden/angul
@Component({
selector: "app-type-filter",
templateUrl: "type-filter.component.html",
standalone: false,
})
export class TypeFilterComponent extends BaseTypeFilterComponent {
constructor() {

View File

@@ -5,5 +5,6 @@ import { VaultFilterComponent as BaseVaultFilterComponent } from "@bitwarden/ang
@Component({
selector: "app-vault-filter",
templateUrl: "vault-filter.component.html",
standalone: false,
})
export class VaultFilterComponent extends BaseVaultFilterComponent {}

View File

@@ -14,6 +14,7 @@ import { SearchBarService } from "../../../app/layout/search/search-bar.service"
@Component({
selector: "app-vault-items",
templateUrl: "vault-items.component.html",
standalone: false,
})
export class VaultItemsComponent extends BaseVaultItemsComponent {
constructor(

View File

@@ -8,9 +8,8 @@ import {
ViewChild,
ViewContainerRef,
} from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom, Subject, takeUntil, switchMap } from "rxjs";
import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom } from "rxjs";
import { filter, map, take } from "rxjs/operators";
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
@@ -31,13 +30,13 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { SyncService } from "@bitwarden/common/platform/sync";
import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import {
BadgeModule,
ButtonModule,
@@ -47,6 +46,8 @@ import {
} from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common";
import {
AddEditFolderDialogComponent,
AddEditFolderDialogResult,
AttachmentDialogResult,
AttachmentsV2Component,
ChangeLoginPasswordService,
@@ -68,7 +69,6 @@ import { DesktopCredentialGenerationService } from "../../../services/desktop-ci
import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service";
import { invokeMenu, RendererMenuItem } from "../../../utils";
import { FolderAddEditComponent } from "./folder-add-edit.component";
import { ItemFooterComponent } from "./item-footer.component";
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
@@ -177,6 +177,7 @@ export class VaultV2Component implements OnInit, OnDestroy {
private formConfigService: CipherFormConfigService,
private premiumUpgradePromptService: PremiumUpgradePromptService,
private collectionService: CollectionService,
private folderService: FolderService,
) {}
async ngOnInit() {
@@ -538,6 +539,7 @@ export class VaultV2Component implements OnInit, OnDestroy {
}
this.addType = type || this.activeFilter.cipherType;
this.cipher = new CipherView();
this.cipherId = null;
await this.buildFormConfig("add");
this.action = "add";
this.prefillCipherFromFilter();
@@ -633,38 +635,25 @@ export class VaultV2Component implements OnInit, OnDestroy {
}
async editFolder(folderId: string) {
if (this.modal != null) {
this.modal.close();
}
if (this.folderAddEditModalRef == null) {
return;
}
const [modal, childComponent] = await this.modalService
.openViewRef(
FolderAddEditComponent,
this.folderAddEditModalRef,
(comp) => (comp.folderId = folderId),
)
.catch(() => [null, null] as any);
this.modal = modal;
if (childComponent) {
childComponent.onSavedFolder.subscribe(async (folder: FolderView) => {
this.modal?.close();
await this.vaultFilterComponent
?.reloadCollectionsAndFolders(this.activeFilter)
.catch(() => {});
});
childComponent.onDeletedFolder.subscribe(async (folder: FolderView) => {
this.modal?.close();
await this.vaultFilterComponent
?.reloadCollectionsAndFolders(this.activeFilter)
.catch(() => {});
});
}
if (this.modal) {
this.modal.onClosed.pipe(takeUntilDestroyed()).subscribe(() => {
this.modal = null;
});
const folderView = await firstValueFrom(
this.folderService.getDecrypted$(folderId, this.activeUserId),
);
const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, {
editFolderConfig: {
folder: {
...folderView,
},
},
});
const result = await lastValueFrom(dialogRef.closed);
if (
result === AddEditFolderDialogResult.Deleted ||
result === AddEditFolderDialogResult.Created
) {
await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter);
}
}

Some files were not shown because too many files have changed in this diff Show More