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:
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
@@ -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
|
||||
|
||||
7
.github/workflows/build-browser-target.yml
vendored
7
.github/workflows/build-browser-target.yml
vendored
@@ -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/**'
|
||||
|
||||
7
.github/workflows/build-cli-target.yml
vendored
7
.github/workflows/build-cli-target.yml
vendored
@@ -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/**'
|
||||
|
||||
7
.github/workflows/build-desktop-target.yml
vendored
7
.github/workflows/build-desktop-target.yml
vendored
@@ -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/**'
|
||||
|
||||
7
.github/workflows/build-web-target.yml
vendored
7
.github/workflows/build-web-target.yml
vendored
@@ -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/**'
|
||||
|
||||
2
.github/workflows/chromatic.yml
vendored
2
.github/workflows/chromatic.yml
vendored
@@ -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 }}
|
||||
|
||||
8
.github/workflows/scan.yml
vendored
8
.github/workflows/scan.yml
vendored
@@ -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:
|
||||
|
||||
45
.github/workflows/test.yml
vendored
45
.github/workflows/test.yml
vendored
@@ -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
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b
|
||||
useExisting: VaultTimeoutInputComponent,
|
||||
},
|
||||
],
|
||||
standalone: false,
|
||||
})
|
||||
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -11,6 +11,7 @@ export type IconProps = {
|
||||
color?: string;
|
||||
disabled?: boolean;
|
||||
theme: Theme;
|
||||
ariaHidden?: boolean;
|
||||
};
|
||||
|
||||
export type Option = {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -48,6 +48,7 @@ export function NotificationConfirmationBody({
|
||||
? NotificationConfirmationMessage({
|
||||
buttonAria,
|
||||
buttonText,
|
||||
error,
|
||||
itemName,
|
||||
message: confirmationMessage,
|
||||
messageDetails,
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
) {}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -81,6 +81,7 @@ describe("OpenAttachmentsComponent", () => {
|
||||
useValue: {
|
||||
get: getCipher,
|
||||
getKeyForCipherKeyDecryption: () => Promise.resolve(null),
|
||||
decrypt: jest.fn().mockResolvedValue(cipherView),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
93
apps/desktop/desktop_native/Cargo.lock
generated
93
apps/desktop/desktop_native/Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b
|
||||
useExisting: VaultTimeoutInputComponent,
|
||||
},
|
||||
],
|
||||
standalone: false,
|
||||
})
|
||||
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -3,5 +3,6 @@ import { Component } from "@angular/core";
|
||||
@Component({
|
||||
selector: "app-header",
|
||||
templateUrl: "header.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class HeaderComponent {}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user