1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-13 06:54:07 +00:00

Merge branch 'main' into autofill/pm-12763-modify-autofill-animation

This commit is contained in:
Cesar Gonzalez
2024-10-31 07:39:38 -05:00
committed by GitHub
120 changed files with 1357 additions and 616 deletions

View File

@@ -35,6 +35,10 @@
"matchPackageNames": ["@types/jest", "jest", "ts-jest", "jest-preset-angular"],
"matchUpdateTypes": "major"
},
{
"groupName": "macOS/iOS bindings",
"matchPackageNames": ["core-foundation", "security-framework", "security-framework-sys"]
},
{
"matchPackageNames": [
"@ngtools/webpack",

View File

@@ -28,7 +28,7 @@ jobs:
run: echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: 'eu-web-${{ steps.setup.outputs.branch }}'
fetch-depth: 0

View File

@@ -42,7 +42,7 @@ jobs:
node_version: ${{ steps.retrieve-node-version.outputs.node_version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get Package Version
id: gen_vars
@@ -72,7 +72,7 @@ jobs:
working-directory: apps/browser
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Testing locales - extName length
run: |
@@ -110,10 +110,10 @@ jobs:
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -241,10 +241,10 @@ jobs:
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -354,7 +354,7 @@ jobs:
- build-safari
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Login to Azure
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0

View File

@@ -42,7 +42,7 @@ jobs:
node_version: ${{ steps.retrieve-node-version.outputs.node_version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get Package Version
id: retrieve-package-version
@@ -83,7 +83,7 @@ jobs:
_WIN_PKG_VERSION: 3.5
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Unix Vars
run: |
@@ -92,7 +92,7 @@ jobs:
awk '{print tolower($0)}')" >> $GITHUB_ENV
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -161,7 +161,7 @@ jobs:
_WIN_PKG_VERSION: 3.5
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Windows builder
run: |
@@ -170,7 +170,7 @@ jobs:
choco install nasm --no-progress
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -311,7 +311,7 @@ jobs:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Print environment
run: |

View File

@@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Verify
run: |
@@ -66,7 +66,7 @@ jobs:
working-directory: apps/desktop
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get Package Version
id: retrieve-version
@@ -139,10 +139,10 @@ jobs:
working-directory: apps/desktop
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -168,7 +168,7 @@ jobs:
working-directory: ./
- name: Cache Native Module
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
id: cache
with:
path: |
@@ -249,10 +249,10 @@ jobs:
NODE_OPTIONS: --max_old_space_size=4096
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -298,7 +298,7 @@ jobs:
working-directory: ./
- name: Cache Native Module
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
id: cache
with:
path: |
@@ -457,10 +457,10 @@ jobs:
working-directory: apps/desktop
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -483,14 +483,14 @@ jobs:
- name: Cache Build
id: build-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Cache Safari
id: safari-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@@ -583,7 +583,7 @@ jobs:
working-directory: ./
- name: Cache Native Module
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
id: cache
with:
path: |
@@ -623,10 +623,10 @@ jobs:
working-directory: apps/desktop
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -649,14 +649,14 @@ jobs:
- name: Get Build Cache
id: build-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@@ -749,7 +749,7 @@ jobs:
working-directory: ./
- name: Cache Native Module
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
id: cache
with:
path: |
@@ -842,10 +842,10 @@ jobs:
working-directory: apps/desktop
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -868,14 +868,14 @@ jobs:
- name: Get Build Cache
id: build-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@@ -975,7 +975,7 @@ jobs:
working-directory: ./
- name: Cache Native Module
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
id: cache
with:
path: |
@@ -1089,10 +1089,10 @@ jobs:
working-directory: apps/desktop
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -1110,14 +1110,14 @@ jobs:
- name: Get Build Cache
id: build-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@@ -1210,7 +1210,7 @@ jobs:
working-directory: ./
- name: Cache Native Module
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
id: cache
with:
path: |
@@ -1280,7 +1280,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Login to Azure
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0

View File

@@ -44,7 +44,7 @@ jobs:
node_version: ${{ steps.retrieve-node-version.outputs.node_version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get GitHub sha as version
id: version
@@ -90,10 +90,10 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -156,7 +156,7 @@ jobs:
_VERSION: ${{ needs.setup.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Check Branch to Publish
env:
@@ -254,7 +254,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Login to Azure
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0

View File

@@ -24,7 +24,7 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
@@ -37,13 +37,13 @@ jobs:
echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: ${{ steps.retrieve-node-version.outputs.node_version }}
- name: Cache NPM
id: npm-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-chromatic-${{ hashFiles('**/package-lock.json') }}
@@ -56,7 +56,7 @@ jobs:
run: npm run build-storybook:ci
- name: Publish to Chromatic
uses: chromaui/action@bbbf288765438d5fd2be13e1d80d542a39e74108 # v11.12.1
uses: chromaui/action@dd2eecb9bef44f54774581f4163b0327fd8cf607 # v11.16.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

View File

@@ -22,7 +22,7 @@ jobs:
crowdin_project_id: "308189"
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Login to Azure
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0

View File

@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Lint filenames (no capital characters)
run: |
@@ -47,7 +47,7 @@ jobs:
echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'

View File

@@ -14,9 +14,9 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Checkout base branch repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.base.sha }}
path: base

View File

@@ -91,7 +91,7 @@ jobs:
_PKG_VERSION: ${{ needs.setup.outputs.release-version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Login to Azure
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
@@ -128,7 +128,7 @@ jobs:
_PKG_VERSION: ${{ needs.setup.outputs.release-version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Login to Azure
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
@@ -168,7 +168,7 @@ jobs:
_PKG_VERSION: ${{ needs.setup.outputs.release-version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Login to Azure
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0

View File

@@ -183,7 +183,7 @@ jobs:
_RELEASE_TAG: ${{ needs.setup.outputs.tag-name }}
steps:
- name: Checkout Repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Login to Azure
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
@@ -227,7 +227,7 @@ jobs:
_RELEASE_TAG: ${{ needs.setup.outputs.tag-name }}
steps:
- name: Checkout Repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Print Environment
run: |

View File

@@ -26,7 +26,7 @@ jobs:
tag_version: ${{ steps.version.outputs.tag }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Branch check
if: ${{ inputs.publish_type != 'Dry Run' }}
@@ -66,7 +66,7 @@ jobs:
echo "Github Release Option: $_RELEASE_OPTION"
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
########## ACR ##########
- name: Login to Azure - PROD Subscription

View File

@@ -26,7 +26,7 @@ jobs:
release-version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Branch check
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
@@ -55,7 +55,7 @@ jobs:
needs: setup
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Testing locales - extName length
run: |

View File

@@ -26,7 +26,7 @@ jobs:
release-version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Branch check
if: ${{ inputs.release_type != 'Dry Run' }}

View File

@@ -23,7 +23,7 @@ jobs:
node_version: ${{ steps.retrieve-node-version.outputs.node_version }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Branch check
run: |
@@ -124,12 +124,12 @@ jobs:
working-directory: apps/desktop
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ needs.setup.outputs.branch-name }}
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -214,12 +214,12 @@ jobs:
NODE_OPTIONS: --max_old_space_size=4096
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ needs.setup.outputs.branch-name }}
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -403,12 +403,12 @@ jobs:
working-directory: apps/desktop
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ needs.setup.outputs.branch-name }}
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -426,14 +426,14 @@ jobs:
- name: Cache Build
id: build-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Cache Safari
id: safari-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@@ -537,12 +537,12 @@ jobs:
working-directory: apps/desktop
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ needs.setup.outputs.branch-name }}
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -560,14 +560,14 @@ jobs:
- name: Get Build Cache
id: build-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@@ -750,12 +750,12 @@ jobs:
working-directory: apps/desktop
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ needs.setup.outputs.branch-name }}
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -773,14 +773,14 @@ jobs:
- name: Get Build Cache
id: build-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@@ -1010,7 +1010,7 @@ jobs:
- release
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup git config
run: |

View File

@@ -26,7 +26,7 @@ jobs:
release-channel: ${{ steps.release-channel.outputs.channel }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Branch check
if: ${{ github.event.inputs.release_type != 'Dry Run' }}

View File

@@ -23,7 +23,7 @@ jobs:
tag_version: ${{ steps.version.outputs.tag }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Branch check
if: ${{ github.event.inputs.release_type != 'Dry Run' }}

View File

@@ -26,7 +26,7 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
@@ -46,7 +46,7 @@ jobs:
--output-path . ${{ env.INCREMENTAL }}
- name: Upload Checkmarx results to GitHub
uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13
uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
with:
sarif_file: cx_result.sarif
@@ -60,7 +60,7 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}

View File

@@ -40,7 +40,7 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get Node Version
id: retrieve-node-version
@@ -50,7 +50,7 @@ jobs:
echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -120,7 +120,7 @@ jobs:
sudo apt-get install -y gnome-keyring dbus-x11
- name: Check out repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Build
working-directory: ./apps/desktop/desktop_native

View File

@@ -57,7 +57,7 @@ jobs:
fi
- name: Checkout Branch
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: main
@@ -85,7 +85,7 @@ jobs:
github-gpg-private-key-passphrase"
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
uses: crazy-max/ghaction-import-gpg@cb9bde2e2525e640591a934b1fd28eef1dcaf5e5 # v6.2.0
with:
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
@@ -532,7 +532,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout Branch
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: main

View File

@@ -2877,6 +2877,20 @@
"generateEmail": {
"message": "Generate email"
},
"generatorBoundariesHint": {
"message": "Value must be between $MIN$ and $MAX$",
"description": "Explains spin box minimum and maximum values to the user",
"placeholders": {
"min": {
"content": "$1",
"example": "8"
},
"max": {
"content": "$2",
"example": "128"
}
}
},
"usernameType": {
"message": "Username type"
},
@@ -3220,9 +3234,6 @@
}
}
},
"loggingInOn": {
"message": "Logging in on"
},
"opensInANewWindow": {
"message": "Opens in a new window"
},

View File

@@ -1,7 +1,11 @@
import { matches, mock } from "jest-mock-extended";
import { BehaviorSubject, ReplaySubject, firstValueFrom, of, timeout } from "rxjs";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import {
Account,
AccountInfo,
AccountService,
} from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
@@ -14,7 +18,7 @@ import { AccountSwitcherService } from "./account-switcher.service";
describe("AccountSwitcherService", () => {
let accountsSubject: BehaviorSubject<Record<UserId, AccountInfo>>;
let activeAccountSubject: BehaviorSubject<{ id: UserId } & AccountInfo>;
let activeAccountSubject: BehaviorSubject<Account | null>;
let authStatusSubject: ReplaySubject<Record<UserId, AuthenticationStatus>>;
const accountService = mock<AccountService>();
@@ -29,7 +33,7 @@ describe("AccountSwitcherService", () => {
beforeEach(() => {
jest.resetAllMocks();
accountsSubject = new BehaviorSubject<Record<UserId, AccountInfo>>(null);
activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>(null);
activeAccountSubject = new BehaviorSubject<Account | null>(null);
authStatusSubject = new ReplaySubject<Record<UserId, AuthenticationStatus>>(1);
// Use subject to allow for easy updates

View File

@@ -57,7 +57,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
availableVaultTimeoutActions: VaultTimeoutAction[] = [];
vaultTimeoutOptions: VaultTimeoutOption[];
vaultTimeoutPolicyCallout: Observable<{
timeout: { hours: number; minutes: number };
timeout: { hours: string; minutes: string };
action: VaultTimeoutAction;
}>;
supportsBiometric: boolean;
@@ -105,8 +105,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
let timeout;
if (policy.data?.minutes) {
timeout = {
hours: Math.floor(policy.data?.minutes / 60),
minutes: policy.data?.minutes % 60,
hours: Math.floor(policy.data?.minutes / 60).toString(),
minutes: (policy.data?.minutes % 60).toString(),
};
}
return { timeout: timeout, action: policy.data?.action };

View File

@@ -629,6 +629,9 @@ describe("OverlayBackground", () => {
it("skips updating the inline menu list if the user has the inline menu set to open on button click", async () => {
inlineMenuVisibilityMock$.next(AutofillOverlayVisibility.OnButtonClick);
jest
.spyOn(overlayBackground as any, "checkIsInlineMenuListVisible")
.mockReturnValue(false);
tabsSendMessageSpy.mockImplementation((_tab, message, _options) => {
if (message.command === "checkFocusedFieldHasValue") {
return Promise.resolve(true);

View File

@@ -1009,7 +1009,10 @@ export class OverlayBackground implements OverlayBackgroundInterface {
this.logService.error(error),
);
if ((await this.getInlineMenuVisibility()) === AutofillOverlayVisibility.OnButtonClick) {
if (
!this.checkIsInlineMenuListVisible() &&
(await this.getInlineMenuVisibility()) === AutofillOverlayVisibility.OnButtonClick
) {
return;
}
@@ -2579,7 +2582,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
* @param sender
*/
private resetFocusedFieldSubFrameOffsets(sender: chrome.runtime.MessageSender) {
if (this.focusedFieldData.frameId > 0 && this.subFrameOffsetsForTab[sender.tab.id]) {
if (this.focusedFieldData?.frameId > 0 && this.subFrameOffsetsForTab[sender.tab.id]) {
this.subFrameOffsetsForTab[sender.tab.id].set(this.focusedFieldData.frameId, null);
}
}
@@ -2592,6 +2595,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
*/
private async triggerSubFrameFocusInRebuild(sender: chrome.runtime.MessageSender) {
this.cancelInlineMenuFadeInAndPositionUpdate();
this.resetFocusedFieldSubFrameOffsets(sender);
this.rebuildSubFrameOffsets$.next(sender);
this.repositionInlineMenu$.next(sender);
}

View File

@@ -21,6 +21,7 @@ import AutofillInit from "./autofill-init";
domQueryService,
domElementVisibilityService,
inlineMenuFieldQualificationService,
inlineMenuContentService,
);
windowContext.bitwardenAutofillInit = new AutofillInit(

View File

@@ -24,6 +24,7 @@ import AutofillInit from "./autofill-init";
domQueryService,
domElementVisibilityService,
inlineMenuFieldQualificationService,
inlineMenuContentService,
);
windowContext.bitwardenAutofillInit = new AutofillInit(

View File

@@ -1,4 +1,4 @@
import { mock } from "jest-mock-extended";
import { mock, MockProxy } from "jest-mock-extended";
import { EVENTS } from "@bitwarden/common/autofill/constants";
import { CipherType } from "@bitwarden/common/vault/enums";
@@ -13,6 +13,7 @@ import {
import AutofillField from "../models/autofill-field";
import AutofillForm from "../models/autofill-form";
import AutofillPageDetails from "../models/autofill-page-details";
import { AutofillInlineMenuContentService } from "../overlay/inline-menu/abstractions/autofill-inline-menu-content.service";
import { createAutofillFieldMock } from "../spec/autofill-mocks";
import {
flushPromises,
@@ -35,6 +36,7 @@ describe("AutofillOverlayContentService", () => {
let domElementVisibilityService: DomElementVisibilityService;
let autofillInit: AutofillInit;
let inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
let inlineMenuContentService: MockProxy<AutofillInlineMenuContentService>;
let autofillOverlayContentService: AutofillOverlayContentService;
let sendExtensionMessageSpy: jest.SpyInstance;
const sendResponseSpy = jest.fn();
@@ -44,10 +46,12 @@ describe("AutofillOverlayContentService", () => {
inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
domQueryService = new DomQueryService();
domElementVisibilityService = new DomElementVisibilityService();
inlineMenuContentService = mock<AutofillInlineMenuContentService>();
autofillOverlayContentService = new AutofillOverlayContentService(
domQueryService,
domElementVisibilityService,
inlineMenuFieldQualificationService,
inlineMenuContentService,
);
autofillInit = new AutofillInit(
domQueryService,

View File

@@ -28,6 +28,7 @@ import {
} from "../enums/autofill-overlay.enum";
import AutofillField from "../models/autofill-field";
import AutofillPageDetails from "../models/autofill-page-details";
import { AutofillInlineMenuContentService } from "../overlay/inline-menu/abstractions/autofill-inline-menu-content.service";
import { ElementWithOpId, FillableFormFieldElement, FormFieldElement } from "../types";
import {
currentlyInSandboxedIframe,
@@ -155,6 +156,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
private domQueryService: DomQueryService,
private domElementVisibilityService: DomElementVisibilityService,
private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService,
private inlineMenuContentService?: AutofillInlineMenuContentService,
) {}
/**
@@ -1580,7 +1582,8 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
if (activeElement) {
return (
activeElement === this.mostRecentlyFocusedField ||
activeElement.contains(this.mostRecentlyFocusedField)
activeElement.contains(this.mostRecentlyFocusedField) ||
this.inlineMenuContentService?.isElementInlineMenu(activeElement as HTMLElement)
);
}

View File

@@ -940,13 +940,16 @@ export default class AutofillService implements AutofillServiceInterface {
if (options.allowTotpAutofill) {
await Promise.all(
totps.map(async (t) => {
totps.map(async (t, i) => {
if (Object.prototype.hasOwnProperty.call(filledFields, t.opid)) {
return;
}
filledFields[t.opid] = t;
const totpValue = await this.totpService.getCode(login.totp);
let totpValue = await this.totpService.getCode(login.totp);
if (totpValue.length == totps.length) {
totpValue = totpValue.charAt(i);
}
AutofillService.fillByOpid(fillScript, t, totpValue);
}),
);

View File

@@ -1347,14 +1347,17 @@ export default class MainBackground {
if (flagEnabled("sdk")) {
// Warn if the SDK for some reason can't be initialized
let supported = false;
let error: Error;
try {
supported = await firstValueFrom(this.sdkService.supported$);
} catch (e) {
// Do nothing.
error = e;
}
if (!supported) {
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
this.sdkService
.failedToInitialize("background", error)
.catch((e) => this.logService.error(e));
}
}

View File

@@ -3,30 +3,36 @@
</div>
<footer class="tw-bg-background tw-border-0 tw-border-t tw-border-secondary-300 tw-border-solid">
<div class="tw-max-w-screen-sm tw-mx-auto">
<div class="tw-flex tw-flex-1">
<a
*ngFor="let button of navButtons"
class="tw-flex-1 tw-group tw-flex tw-flex-col tw-items-center tw-gap-1 tw-px-0.5 tw-pb-2 tw-pt-3 tw-no-underline hover:tw-no-underline hover:tw-text-primary-600 hover:tw-bg-primary-100 tw-border-2 tw-border-solid tw-border-transparent focus-visible:tw-rounded-lg focus-visible:tw-border-primary-500"
[ngClass]="rla.isActive ? 'tw-font-bold tw-text-primary-600' : 'tw-text-muted'"
[title]="button.label"
[routerLink]="button.page"
routerLinkActive
#rla="routerLinkActive"
ariaCurrentWhenActive="page"
>
<i *ngIf="!rla.isActive" class="bwi bwi-lg bwi-{{ button.iconKey }}" aria-hidden="true"></i>
<i
*ngIf="rla.isActive"
class="bwi bwi-lg bwi-{{ button.iconKeyActive }}"
aria-hidden="true"
></i>
<span
class="tw-truncate tw-max-w-full"
[ngClass]="!rla.isActive && 'group-hover:tw-underline'"
>
{{ button.label }}
</span>
</a>
</div>
<nav>
<ul class="tw-flex tw-flex-1 tw-mb-0 tw-p-0">
<li *ngFor="let button of navButtons" class="tw-flex-1 tw-list-none">
<button
class="tw-w-full tw-flex tw-flex-col tw-items-center tw-gap-1 tw-px-0.5 tw-pb-2 tw-pt-3 tw-bg-transparent tw-no-underline hover:tw-no-underline hover:tw-text-primary-600 hover:tw-bg-primary-100 tw-border-2 tw-border-solid tw-border-transparent focus-visible:tw-rounded-lg focus-visible:tw-border-primary-600"
[ngClass]="rla.isActive ? 'tw-font-bold tw-text-primary-600' : 'tw-text-muted'"
[title]="button.label"
[routerLink]="button.page"
routerLinkActive
#rla="routerLinkActive"
ariaCurrentWhenActive="page"
type="button"
role="link"
>
<i
*ngIf="!rla.isActive"
class="bwi bwi-lg bwi-{{ button.iconKey }}"
aria-hidden="true"
></i>
<i
*ngIf="rla.isActive"
class="bwi bwi-lg bwi-{{ button.iconKeyActive }}"
aria-hidden="true"
></i>
<span class="tw-truncate tw-max-w-full">
{{ button.label }}
</span>
</button>
</li>
</ul>
</nav>
</div>
</footer>

View File

@@ -1,7 +1,7 @@
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, inject } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
import { Subject, takeUntil, firstValueFrom, concatMap, filter, tap, catchError, of } from "rxjs";
import { Subject, takeUntil, firstValueFrom, concatMap, filter, tap } from "rxjs";
import { LogoutReason } from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -71,21 +71,24 @@ export class AppComponent implements OnInit, OnDestroy {
) {
if (flagEnabled("sdk")) {
// Warn if the SDK for some reason can't be initialized
this.sdkService.supported$
.pipe(
takeUntilDestroyed(),
catchError(() => {
return of(false);
}),
)
.subscribe((supported) => {
this.sdkService.supported$.pipe(takeUntilDestroyed()).subscribe({
next: (supported) => {
if (!supported) {
this.logService.debug("SDK is not supported");
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
this.sdkService
.failedToInitialize("popup", undefined)
.catch((e) => this.logService.error(e));
} else {
this.logService.debug("SDK is supported");
}
});
},
error: (e: unknown) => {
this.sdkService
.failedToInitialize("popup", e as Error)
.catch((e) => this.logService.error(e));
this.logService.error(e);
},
});
}
}

View File

@@ -80,7 +80,7 @@
"papaparse": "5.4.1",
"proper-lockfile": "4.1.2",
"rxjs": "7.8.1",
"tldts": "6.1.52",
"tldts": "6.1.56",
"zxcvbn": "4.4.2"
}
}

View File

@@ -872,7 +872,7 @@ export class ServiceContainer {
}
if (!supported) {
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
this.sdkService.failedToInitialize("cli").catch((e) => this.logService.error(e));
}
}
}

View File

@@ -710,9 +710,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "futures"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
@@ -1965,9 +1965,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.11"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
dependencies = [
"bytes",
"futures-core",

View File

@@ -30,7 +30,7 @@ arboard = { version = "=3.4.1", default-features = false, features = [
base64 = "=0.22.1"
cbc = { version = "=0.1.2", features = ["alloc"] }
dirs = "=5.0.1"
futures = "=0.3.30"
futures = "=0.3.31"
interprocess = { version = "=2.2.1", features = ["tokio"] }
libc = "=0.2.159"
log = "=0.4.22"
@@ -40,7 +40,7 @@ scopeguard = "=1.2.0"
sha2 = "=0.10.8"
thiserror = "=1.0.61"
tokio = { version = "=1.38.0", features = ["io-util", "sync", "macros"] }
tokio-util = "=0.7.11"
tokio-util = "=0.7.12"
typenum = "=1.17.0"
[target.'cfg(windows)'.dependencies]

View File

@@ -14,11 +14,11 @@
"module-alias": "2.2.3",
"node-ipc": "9.2.1",
"ts-node": "10.9.2",
"uuid": "10.0.0",
"uuid": "11.0.1",
"yargs": "17.7.2"
},
"devDependencies": {
"@types/node": "20.16.11",
"@types/node": "20.17.1",
"@types/node-ipc": "9.2.3",
"typescript": "4.7.4"
}
@@ -106,9 +106,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.16.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz",
"integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==",
"version": "20.17.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.1.tgz",
"integrity": "sha512-j2VlPv1NnwPJbaCNv69FO/1z4lId0QmGvpT41YxitRtWlg96g/j8qcv2RKsLKe2F6OJgyXhupN1Xo17b2m139Q==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
@@ -421,16 +421,16 @@
"license": "MIT"
},
"node_modules/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.1.tgz",
"integrity": "sha512-wt9UB5EcLhnboy1UvA1mvGPXkIIrHSu+3FmUksARfdVw9tuPf3CH/CohxO0Su1ApoKAeT6BVzAJIvjTuQVSmuQ==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": {

View File

@@ -19,11 +19,11 @@
"module-alias": "2.2.3",
"node-ipc": "9.2.1",
"ts-node": "10.9.2",
"uuid": "10.0.0",
"uuid": "11.0.1",
"yargs": "17.7.2"
},
"devDependencies": {
"@types/node": "20.16.11",
"@types/node": "20.17.1",
"@types/node-ipc": "9.2.3",
"typescript": "4.7.4"
},

View File

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

View File

@@ -0,0 +1,11 @@
[Desktop Entry]
Name=Bitwarden
Exec=bitwarden %u
Terminal=false
Type=Application
Icon=com.bitwarden.desktop
StartupWMClass=Bitwarden
GenericName=Password Manager
Comment=A secure and free password manager for all of your devices.
MimeType=x-scheme-handler/bitwarden;
Categories=System;Security;

View File

@@ -180,7 +180,7 @@ export class AppComponent implements OnInit, OnDestroy {
.subscribe((supported) => {
if (!supported) {
this.logService.debug("SDK is not supported");
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
this.sdkService.failedToInitialize("desktop").catch((e) => this.logService.error(e));
} else {
this.logService.debug("SDK is supported");
}

View File

@@ -2393,6 +2393,20 @@
"generateEmail": {
"message": "Generate email"
},
"generatorBoundariesHint": {
"message": "Value must be between $MIN$ and $MAX$",
"description": "Explains spin box minimum and maximum values to the user",
"placeholders": {
"min": {
"content": "$1",
"example": "8"
},
"max": {
"content": "$2",
"example": "128"
}
}
},
"usernameType": {
"message": "Username type"
},
@@ -2790,9 +2804,6 @@
"message": "EU",
"description": "European Union"
},
"loggingInOn": {
"message": "Logging in on"
},
"selfHostedServer": {
"message": "self-hosted"
},

View File

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

View File

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

View File

@@ -4,6 +4,15 @@
"notifications": "https://notifications.usdev.bitwarden.pw",
"scim": "https://scim.usdev.bitwarden.pw"
},
"additionalRegions": [
{
"key": "USDEV",
"domain": "usdev.bitwarden.pw",
"urls": {
"webVault": "https://vault.usdev.bitwarden.pw"
}
}
],
"flags": {
"showPasswordless": true
}

View File

@@ -23,14 +23,15 @@
<bit-tab [label]="'role' | i18n">
<ng-container *ngIf="!editMode">
<p bitTypography="body1">{{ "inviteUserDesc" | i18n }}</p>
<bit-form-field>
<bit-form-field *ngIf="remainingSeats$ | async as remainingSeats">
<bit-label>{{ "email" | i18n }}</bit-label>
<input id="emails" type="text" appAutoFocus bitInput formControlName="emails" />
<bit-hint>{{
"inviteMultipleEmailDesc"
| i18n
: (organization.productTierType === ProductTierType.TeamsStarter ? "10" : "20")
<bit-hint *ngIf="remainingSeats > 1; else singleSeat">{{
"inviteMultipleEmailDesc" | i18n: remainingSeats
}}</bit-hint>
<ng-template #singleSeat>
<bit-hint>{{ "inviteSingleEmailDesc" | i18n: remainingSeats }}</bit-hint>
</ng-template>
</bit-form-field>
</ng-container>
<bit-radio-group formControlName="type">

View File

@@ -89,6 +89,7 @@ export class MemberDialogComponent implements OnDestroy {
PermissionMode = PermissionMode;
showNoMasterPasswordWarning = false;
isOnSecretsManagerStandalone: boolean;
remainingSeats$: Observable<number>;
protected organization$: Observable<Organization>;
protected collectionAccessItems: AccessItemView[] = [];
@@ -250,6 +251,10 @@ export class MemberDialogComponent implements OnDestroy {
this.loading = false;
});
this.remainingSeats$ = this.organization$.pipe(
map((organization) => organization.seats - this.params.numConfirmedMembers),
);
}
private setFormValidators(organization: Organization) {

View File

@@ -15,7 +15,7 @@
</div>
</ng-container>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" bitFormButton type="submit">
<button bitButton buttonType="primary" [disabled]="saveDisabled" bitFormButton type="submit">
{{ "save" | i18n }}
</button>
<button bitButton buttonType="secondary" bitDialogClose type="button">

View File

@@ -42,6 +42,7 @@ export class PolicyEditComponent implements AfterViewInit {
policyType = PolicyType;
loading = true;
enabled = false;
saveDisabled = false;
defaultTypes: any[];
policyComponent: BasePolicyComponent;
@@ -72,6 +73,8 @@ export class PolicyEditComponent implements AfterViewInit {
this.policyComponent.policy = this.data.policy;
this.policyComponent.policyResponse = this.policyResponse;
this.saveDisabled = !this.policyResponse.canToggleState;
this.cdr.detectChanges();
}

View File

@@ -1,6 +1,11 @@
<bit-callout type="warning">
{{ "singleOrgPolicyWarning" | i18n }}
<bit-callout *ngIf="accountDeprovisioningEnabled$ | async; else disabledBlock" type="warning">
{{ "singleOrgPolicyMemberWarning" | i18n }}
</bit-callout>
<ng-template #disabledBlock>
<bit-callout type="warning">
{{ "singleOrgPolicyWarning" | i18n }}
</bit-callout>
</ng-template>
<bit-form-control>
<input type="checkbox" bitCheckbox [formControl]="enabled" id="enabled" />

View File

@@ -1,4 +1,5 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { firstValueFrom, Observable } from "rxjs";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
@@ -19,7 +20,7 @@ export class SingleOrgPolicy extends BasePolicy {
selector: "policy-single-org",
templateUrl: "single-org.component.html",
})
export class SingleOrgPolicyComponent extends BasePolicyComponent {
export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnInit {
constructor(
private i18nService: I18nService,
private configService: ConfigService,
@@ -27,6 +28,23 @@ export class SingleOrgPolicyComponent extends BasePolicyComponent {
super();
}
protected accountDeprovisioningEnabled$: Observable<boolean> = this.configService.getFeatureFlag$(
FeatureFlag.AccountDeprovisioning,
);
async ngOnInit() {
super.ngOnInit();
const isAccountDeprovisioningEnabled = await firstValueFrom(this.accountDeprovisioningEnabled$);
this.policy.description = isAccountDeprovisioningEnabled
? "singleOrgPolicyDesc"
: "singleOrgDesc";
if (!this.policyResponse.canToggleState) {
this.enabled.disable();
}
}
async buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
if (await this.configService.getFeatureFlag(FeatureFlag.Pm13322AddPolicyDefinitions)) {
// We are now relying on server-side validation only
@@ -48,6 +66,15 @@ export class SingleOrgPolicyComponent extends BasePolicyComponent {
),
);
}
if (
(await firstValueFrom(this.accountDeprovisioningEnabled$)) &&
!this.policyResponse.canToggleState
) {
throw new Error(
this.i18nService.t("disableRequiredError", this.i18nService.t("singleOrg")),
);
}
}
return super.buildRequest(policiesEnabledMap);

View File

@@ -1,34 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "deleteProvider" | i18n }}</p>
<div class="card">
<div class="card-body">
<app-callout type="warning">{{ "deleteProviderWarning" | i18n }}</app-callout>
<p class="text-center">
<strong>{{ name }}</strong>
</p>
<p>{{ "deleteProviderRecoverConfirmDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<button
type="submit"
class="btn btn-danger btn-block btn-submit"
[disabled]="form.loading"
>
<span>{{ "deleteProvider" | i18n }}</span>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -104,7 +104,7 @@ export class AppComponent implements OnDestroy, OnInit {
.subscribe((supported) => {
if (!supported) {
this.logService.debug("SDK is not supported");
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
this.sdkService.failedToInitialize("web").catch((e) => this.logService.error(e));
} else {
this.logService.debug("SDK is supported");
}

View File

@@ -3,7 +3,7 @@
<bit-container>
<app-profile></app-profile>
<div *ngIf="showChangeEmail" class="tw-mt-16">
<div *ngIf="showChangeEmail$ | async" class="tw-mt-16">
<h1 bitTypography="h1">{{ "changeEmail" | i18n }}</h1>
<app-change-email></app-change-email>
</div>

View File

@@ -1,5 +1,5 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { lastValueFrom, map, Observable, of, switchMap } from "rxjs";
import { combineLatest, from, lastValueFrom, map, Observable } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
@@ -21,7 +21,7 @@ export class AccountComponent implements OnInit {
@ViewChild("deauthorizeSessionsTemplate", { read: ViewContainerRef, static: true })
deauthModalRef: ViewContainerRef;
showChangeEmail = true;
showChangeEmail$: Observable<boolean>;
showPurgeVault$: Observable<boolean>;
constructor(
@@ -33,21 +33,36 @@ export class AccountComponent implements OnInit {
) {}
async ngOnInit() {
this.showChangeEmail = await this.userVerificationService.hasMasterPassword();
this.showPurgeVault$ = this.configService
.getFeatureFlag$(FeatureFlag.AccountDeprovisioning)
.pipe(
switchMap((isAccountDeprovisioningEnabled) =>
isAccountDeprovisioningEnabled
? this.organizationService.organizations$.pipe(
map(
(organizations) =>
!organizations.some((o) => o.userIsManagedByOrganization === true),
),
)
: of(true),
),
);
const isAccountDeprovisioningEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.AccountDeprovisioning,
);
const userIsManagedByOrganization$ = this.organizationService.organizations$.pipe(
map((organizations) => organizations.some((o) => o.userIsManagedByOrganization === true)),
);
const hasMasterPassword$ = from(this.userVerificationService.hasMasterPassword());
this.showChangeEmail$ = combineLatest([
hasMasterPassword$,
isAccountDeprovisioningEnabled$,
userIsManagedByOrganization$,
]).pipe(
map(
([hasMasterPassword, isAccountDeprovisioningEnabled, userIsManagedByOrganization]) =>
hasMasterPassword && (!isAccountDeprovisioningEnabled || !userIsManagedByOrganization),
),
);
this.showPurgeVault$ = combineLatest([
isAccountDeprovisioningEnabled$,
userIsManagedByOrganization$,
]).pipe(
map(
([isAccountDeprovisioningEnabled, userIsManagedByOrganization]) =>
!isAccountDeprovisioningEnabled || !userIsManagedByOrganization,
),
);
}
async deauthorizeSessions() {

View File

@@ -1,9 +1,15 @@
<h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5 !tw-text-danger">{{ "dangerZone" | i18n }}</h1>
<div class="tw-rounded tw-border tw-border-solid tw-border-danger-600 tw-p-5">
<p>{{ "dangerZoneDesc" | i18n }}</p>
<p>
{{
(accountDeprovisioningEnabled$ | async) && content.children.length === 1
? ("dangerZoneDescSingular" | i18n)
: ("dangerZoneDesc" | i18n)
}}
</p>
<div class="tw-flex tw-flex-row tw-gap-2">
<div #content class="tw-flex tw-flex-row tw-gap-2">
<ng-content></ng-content>
</div>
</div>

View File

@@ -1,6 +1,10 @@
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { TypographyModule } from "@bitwarden/components";
/**
@@ -10,6 +14,15 @@ import { TypographyModule } from "@bitwarden/components";
selector: "app-danger-zone",
templateUrl: "danger-zone.component.html",
standalone: true,
imports: [TypographyModule, JslibModule],
imports: [TypographyModule, JslibModule, CommonModule],
})
export class DangerZoneComponent {}
export class DangerZoneComponent implements OnInit {
constructor(private configService: ConfigService) {}
accountDeprovisioningEnabled$: Observable<boolean>;
ngOnInit(): void {
this.accountDeprovisioningEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.AccountDeprovisioning,
);
}
}

View File

@@ -36,6 +36,12 @@
Customize
</button>
</div>
<div *ngIf="managingOrganization$ | async as managingOrganization">
{{ "accountIsManagedMessage" | i18n: managingOrganization?.name }}
<a href="https://bitwarden.com/help/claimed-accounts">
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<app-account-fingerprint
[fingerprintMaterial]="fingerprintMaterial"
fingerprintLabel="{{ 'yourAccountsFingerprint' | i18n }}"

View File

@@ -1,11 +1,15 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { firstValueFrom, map, Subject, takeUntil } from "rxjs";
import { firstValueFrom, map, Observable, of, Subject, switchMap, takeUntil } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { DialogService, ToastService } from "@bitwarden/components";
@@ -19,6 +23,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
loading = true;
profile: ProfileResponse;
fingerprintMaterial: string;
managingOrganization$: Observable<Organization>;
private destroy$ = new Subject<void>();
protected formGroup = new FormGroup({
@@ -32,6 +37,8 @@ export class ProfileComponent implements OnInit, OnDestroy {
private accountService: AccountService,
private dialogService: DialogService,
private toastService: ToastService,
private configService: ConfigService,
private organizationService: OrganizationService,
) {}
async ngOnInit() {
@@ -40,6 +47,19 @@ export class ProfileComponent implements OnInit, OnDestroy {
this.fingerprintMaterial = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.managingOrganization$ = this.configService
.getFeatureFlag$(FeatureFlag.AccountDeprovisioning)
.pipe(
switchMap((isAccountDeprovisioningEnabled) =>
isAccountDeprovisioningEnabled
? this.organizationService.organizations$.pipe(
map((organizations) =>
organizations.find((o) => o.userIsManagedByOrganization === true),
),
)
: of(null),
),
);
this.formGroup.get("name").setValue(this.profile.name);
this.formGroup.get("email").setValue(this.profile.email);

View File

@@ -1,7 +1,7 @@
import { Injectable } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { AccountInfo } from "@bitwarden/common/auth/abstractions/account.service";
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
@@ -46,10 +46,7 @@ export class UserKeyRotationService {
* Creates a new user key and re-encrypts all required data with the it.
* @param masterPassword current master password (used for validation)
*/
async rotateUserKeyAndEncryptedData(
masterPassword: string,
user: { id: UserId } & AccountInfo,
): Promise<void> {
async rotateUserKeyAndEncryptedData(masterPassword: string, user: Account): Promise<void> {
this.logService.info("[Userkey rotation] Starting user key rotation...");
if (!masterPassword) {
this.logService.info("[Userkey rotation] Invalid master password provided. Aborting!");

View File

@@ -40,7 +40,6 @@ import { flagEnabled, Flags } from "../utils/flags";
import { VerifyRecoverDeleteOrgComponent } from "./admin-console/organizations/manage/verify-recover-delete-org.component";
import { AcceptFamilySponsorshipComponent } from "./admin-console/organizations/sponsorships/accept-family-sponsorship.component";
import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizations/sponsorships/families-for-enterprise-setup.component";
import { VerifyRecoverDeleteProviderComponent } from "./admin-console/providers/verify-recover-delete-provider.component";
import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component";
import { deepLinkGuard } from "./auth/guards/deep-link.guard";
import { HintComponent } from "./auth/hint.component";
@@ -156,12 +155,6 @@ const routes: Routes = [
canActivate: [unauthGuardFn()],
data: { titleId: "deleteOrganization" },
},
{
path: "verify-recover-delete-provider",
component: VerifyRecoverDeleteProviderComponent,
canActivate: [unauthGuardFn()],
data: { titleId: "deleteAccount" } satisfies RouteDataProperties,
},
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,

View File

@@ -17,8 +17,6 @@ import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "../admin-console/organizations/tools/reused-passwords-report.component";
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../admin-console/organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../admin-console/organizations/tools/weak-passwords-report.component";
import { ProvidersComponent } from "../admin-console/providers/providers.component";
import { VerifyRecoverDeleteProviderComponent } from "../admin-console/providers/verify-recover-delete-provider.component";
import { HintComponent } from "../auth/hint.component";
import { RecoverDeleteComponent } from "../auth/recover-delete.component";
import { RecoverTwoFactorComponent } from "../auth/recover-two-factor.component";
@@ -149,7 +147,6 @@ import { SharedModule } from "./shared.module";
PremiumBadgeComponent,
ProfileComponent,
ChangeAvatarDialogComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
@@ -176,7 +173,6 @@ import { SharedModule } from "./shared.module";
UpdateTempPasswordComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
VerifyRecoverDeleteProviderComponent,
],
exports: [
UserVerificationModule,
@@ -218,7 +214,6 @@ import { SharedModule } from "./shared.module";
PremiumBadgeComponent,
ProfileComponent,
ChangeAvatarDialogComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
@@ -246,7 +241,6 @@ import { SharedModule } from "./shared.module";
UserLayoutComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
VerifyRecoverDeleteProviderComponent,
HeaderModule,
DangerZoneComponent,
],

View File

@@ -22,6 +22,9 @@
<bit-tab label="Raw Data + members">
<tools-password-health-members></tools-password-health-members>
</bit-tab>
<bit-tab label="Raw Data + uri">
<tools-password-health-members-uri></tools-password-health-members-uri>
</bit-tab>
<!-- <bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}">
<h2 bitTypography="h2">{{ "allApplications" | i18n }}</h2>
<tools-application-table></tools-application-table>

View File

@@ -11,6 +11,7 @@ import { HeaderModule } from "../../layouts/header/header.module";
import { ApplicationTableComponent } from "./application-table.component";
import { NotifiedMembersTableComponent } from "./notified-members-table.component";
import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component";
import { PasswordHealthMembersComponent } from "./password-health-members.component";
import { PasswordHealthComponent } from "./password-health.component";
@@ -32,6 +33,7 @@ export enum AccessIntelligenceTabType {
HeaderModule,
PasswordHealthComponent,
PasswordHealthMembersComponent,
PasswordHealthMembersURIComponent,
NotifiedMembersTableComponent,
TabsModule,
],

View File

@@ -11,5 +11,5 @@ import { ButtonModule, NoItemsModule, Icons } from "@bitwarden/components";
imports: [ButtonModule, CommonModule, JslibModule, NoItemsModule],
})
export class NoPriorityAppsComponent {
noItemsIcon = Icons.NoResults;
noItemsIcon = Icons.Security;
}

View File

@@ -0,0 +1,55 @@
<bit-container>
<p>{{ "passwordsReportDesc" | i18n }}</p>
<div *ngIf="loading">
<i
class="bwi bwi-spinner bwi-spin tw-text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="tw-mt-4" *ngIf="!loading">
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr bitRow>
<th bitCell bitSortable="hostURI">{{ "application" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "weakness" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "totalMembers" | i18n }}</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>
<ng-container>
<span>{{ r.hostURI }}</span>
</ng-container>
</td>
<td bitCell class="tw-text-right">
<span
bitBadge
*ngIf="passwordStrengthMap.has(r.id)"
[variant]="passwordStrengthMap.get(r.id)[1]"
>
{{ passwordStrengthMap.get(r.id)[0] | i18n }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="passwordUseMap.has(r.login.password)" variant="warning">
{{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="exposedPasswordMap.has(r.id)" variant="warning">
{{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }}
</span>
</td>
<td bitCell class="tw-text-right" data-testid="total-membership">
{{ totalMembersMap.get(r.id) || 0 }}
</td>
</tr>
</ng-template>
</bit-table>
</div>
</bit-container>

View File

@@ -0,0 +1,61 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { ActivatedRoute, convertToParamMap } from "@angular/router";
import { mock, MockProxy } from "jest-mock-extended";
import { of } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/access-intelligence";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { TableModule } from "@bitwarden/components";
import { LooseComponentsModule } from "../../shared";
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component";
describe("PasswordHealthMembersUriComponent", () => {
let component: PasswordHealthMembersURIComponent;
let fixture: ComponentFixture<PasswordHealthMembersURIComponent>;
let cipherServiceMock: MockProxy<CipherService>;
const passwordHealthServiceMock = mock<PasswordHealthService>();
const activeRouteParams = convertToParamMap({ organizationId: "orgId" });
beforeEach(async () => {
cipherServiceMock = mock<CipherService>();
await TestBed.configureTestingModule({
imports: [PasswordHealthMembersURIComponent, PipesModule, TableModule, LooseComponentsModule],
providers: [
{ provide: CipherService, useValue: cipherServiceMock },
{ provide: I18nService, useValue: mock<I18nService>() },
{ provide: AuditService, useValue: mock<AuditService>() },
{ provide: OrganizationService, useValue: mock<OrganizationService>() },
{
provide: PasswordStrengthServiceAbstraction,
useValue: mock<PasswordStrengthServiceAbstraction>(),
},
{ provide: PasswordHealthService, useValue: passwordHealthServiceMock },
{
provide: ActivatedRoute,
useValue: {
paramMap: of(activeRouteParams),
url: of([]),
},
},
],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PasswordHealthMembersURIComponent);
component = fixture.componentInstance;
});
it("should initialize component", () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,108 @@
import { CommonModule } from "@angular/common";
import { Component, DestroyRef, inject, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute } from "@angular/router";
import { map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
// eslint-disable-next-line no-restricted-imports
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/access-intelligence";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
BadgeModule,
BadgeVariant,
ContainerComponent,
TableDataSource,
TableModule,
} from "@bitwarden/components";
// eslint-disable-next-line no-restricted-imports
import { HeaderModule } from "../../layouts/header/header.module";
// eslint-disable-next-line no-restricted-imports
import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module";
// eslint-disable-next-line no-restricted-imports
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
@Component({
standalone: true,
selector: "tools-password-health-members-uri",
templateUrl: "password-health-members-uri.component.html",
imports: [
BadgeModule,
OrganizationBadgeModule,
CommonModule,
ContainerComponent,
PipesModule,
JslibModule,
HeaderModule,
TableModule,
],
providers: [PasswordHealthService],
})
export class PasswordHealthMembersURIComponent implements OnInit {
passwordStrengthMap = new Map<string, [string, BadgeVariant]>();
weakPasswordCiphers: CipherView[] = [];
passwordUseMap = new Map<string, number>();
exposedPasswordMap = new Map<string, number>();
totalMembersMap = new Map<string, number>();
dataSource = new TableDataSource<CipherView>();
reportCiphers: (CipherView & { hostURI: string })[] = [];
reportCipherURIs: string[] = [];
organization: Organization;
loading = true;
private destroyRef = inject(DestroyRef);
constructor(
protected cipherService: CipherService,
protected passwordStrengthService: PasswordStrengthServiceAbstraction,
protected organizationService: OrganizationService,
protected auditService: AuditService,
protected i18nService: I18nService,
protected activatedRoute: ActivatedRoute,
) {}
ngOnInit() {
this.activatedRoute.paramMap
.pipe(
takeUntilDestroyed(this.destroyRef),
map(async (params) => {
const organizationId = params.get("organizationId");
await this.setCiphers(organizationId);
}),
)
.subscribe();
}
async setCiphers(organizationId: string) {
const passwordHealthService = new PasswordHealthService(
this.passwordStrengthService,
this.auditService,
this.cipherService,
organizationId,
);
await passwordHealthService.generateReport();
this.dataSource.data = passwordHealthService.groupCiphersByLoginUri();
this.exposedPasswordMap = passwordHealthService.exposedPasswordMap;
this.passwordStrengthMap = passwordHealthService.passwordStrengthMap;
this.passwordUseMap = passwordHealthService.passwordUseMap;
this.totalMembersMap = passwordHealthService.totalMembersMap;
this.loading = false;
}
}

View File

@@ -1,15 +1,38 @@
<bit-container>
<p>{{ "passwordsReportDesc" | i18n }}</p>
<div *ngIf="loading">
<i
class="bwi bwi-spinner bwi-spin tw-text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
<p>{{ "passwordsReportDesc" | i18n }}</p>
<div *ngIf="loading">
<i
class="bwi bwi-spinner bwi-spin tw-text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="tw-mt-4" *ngIf="!dataSource.data.length">
<tools-no-priority-apps></tools-no-priority-apps>
</div>
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!loading && dataSource.data.length">
<div class="tw-flex tw-gap-6">
<tools-card
class="tw-flex-1"
[title]="'atRiskMembers' | i18n"
[value]="totalMembersMap.size - 3"
[maxValue]="totalMembersMap.size"
>
</tools-card>
<tools-card
class="tw-flex-1"
[title]="'atRiskApplications' | i18n"
[value]="totalMembersMap.size - 1"
[maxValue]="totalMembersMap.size"
>
</tools-card>
</div>
<div *ngIf="!dataSource.data.length">
<tools-no-priority-apps></tools-no-priority-apps>
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
<bit-search class="tw-grow" [formControl]="searchControl"></bit-search>
<button class="tw-rounded-lg" type="button" buttonType="secondary" bitButton>
<i class="bwi bwi-star-f tw-mr-2"></i>
{{ "markAppAsCritical" | i18n }}
</button>
</div>
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!loading && dataSource.data.length">
<div class="tw-flex tw-gap-6">
@@ -30,7 +53,15 @@
</div>
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
<bit-search class="tw-grow" [formControl]="searchControl"></bit-search>
<button class="tw-rounded-lg" type="button" buttonType="secondary" bitButton>
<button
class="tw-rounded-lg"
type="button"
buttonType="secondary"
[disabled]="!selectedIds.size"
bitButton
[bitAction]="markAppsAsCritical"
appA11yTitle="{{ 'markAppAsCritical' | i18n }}"
>
<i class="bwi bwi-star-f tw-mr-2"></i>
{{ "markAppAsCritical" | i18n }}
</button>
@@ -47,9 +78,14 @@
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async">
<tr bitRow *ngFor="let r of rows$ | async; trackBy: trackByFunction">
<td bitCell>
<app-vault-icon [cipher]="r"></app-vault-icon>
<input
bitCheckbox
type="checkbox"
[checked]="selectedIds.has(r.id)"
(change)="onCheckboxChange(r.id, $event)"
/>
</td>
<td bitCell>
<ng-container>
@@ -84,4 +120,4 @@
</ng-template>
</bit-table>
</div>
</bit-container>
</div>

View File

@@ -11,7 +11,13 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { BadgeVariant, SearchModule, TableDataSource, TableModule } from "@bitwarden/components";
import {
BadgeVariant,
SearchModule,
TableDataSource,
TableModule,
ToastService,
} from "@bitwarden/components";
import { CardComponent } from "@bitwarden/tools-card";
import { HeaderModule } from "../../layouts/header/header.module";
@@ -53,6 +59,8 @@ export class PasswordHealthMembersComponent implements OnInit {
loading = true;
selectedIds: Set<number> = new Set<number>();
protected searchControl = new FormControl("", { nonNullable: true });
private destroyRef = inject(DestroyRef);
@@ -63,6 +71,7 @@ export class PasswordHealthMembersComponent implements OnInit {
protected auditService: AuditService,
protected i18nService: I18nService,
protected activatedRoute: ActivatedRoute,
protected toastService: ToastService,
) {
this.searchControl.valueChanges
.pipe(debounceTime(200), takeUntilDestroyed())
@@ -91,7 +100,7 @@ export class PasswordHealthMembersComponent implements OnInit {
await passwordHealthService.generateReport();
this.dataSource.data = passwordHealthService.reportCiphers;
this.dataSource.data = []; //passwordHealthService.reportCiphers;
this.exposedPasswordMap = passwordHealthService.exposedPasswordMap;
this.passwordStrengthMap = passwordHealthService.passwordStrengthMap;
@@ -99,4 +108,32 @@ export class PasswordHealthMembersComponent implements OnInit {
this.totalMembersMap = passwordHealthService.totalMembersMap;
this.loading = false;
}
markAppsAsCritical = async () => {
// TODO: Send to API once implemented
return new Promise((resolve) => {
setTimeout(() => {
this.selectedIds.clear();
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("appsMarkedAsCritical"),
});
resolve(true);
}, 1000);
});
};
trackByFunction(_: number, item: CipherView) {
return item.id;
}
onCheckboxChange(id: number, event: Event) {
const isChecked = (event.target as HTMLInputElement).checked;
if (isChecked) {
this.selectedIds.add(id);
} else {
this.selectedIds.delete(id);
}
}
}

View File

@@ -65,6 +65,9 @@
"markAppAsCritical": {
"message": "Mark app as critical"
},
"appsMarkedAsCritical": {
"message": "Apps marked as critical"
},
"application": {
"message": "Application"
},
@@ -1583,38 +1586,6 @@
"specialCharacters": {
"message": "Special characters (!@#$%^&*)"
},
"uppercaseDescription": {
"message": "Include uppercase characters",
"description": "Tooltip for the password generator uppercase character checkbox"
},
"uppercaseLabel": {
"message": "A-Z",
"description": "Label for the password generator uppercase character checkbox"
},
"lowercaseDescription": {
"message": "Include lowercase characters",
"description": "Full description for the password generator lowercase character checkbox"
},
"lowercaseLabel": {
"message": "a-z",
"description": "Label for the password generator lowercase character checkbox"
},
"numbersDescription": {
"message": "Include numbers",
"description": "Full description for the password generator numbers checkbox"
},
"numbersLabel": {
"message": "0-9",
"description": "Label for the password generator numbers checkbox"
},
"specialCharactersDescription": {
"message": "Include special characters",
"description": "Full description for the password generator special characters checkbox"
},
"specialCharactersLabel": {
"message": "!@#$%^&*",
"description": "Label for the password generator special characters checkbox"
},
"numWords": {
"message": "Number of words"
},
@@ -1745,6 +1716,9 @@
"dangerZoneDesc": {
"message": "Careful, these actions are not reversible!"
},
"dangerZoneDescSingular": {
"message": "Careful, this action is not reversible!"
},
"deauthorizeSessions": {
"message": "Deauthorize sessions"
},
@@ -1757,6 +1731,15 @@
"sessionsDeauthorized": {
"message": "All sessions deauthorized"
},
"accountIsManagedMessage": {
"message": "This account is managed by $ORGANIZATIONNAME$",
"placeholders": {
"organizationName": {
"content": "$1",
"example": "Organization"
}
}
},
"purgeVault": {
"message": "Purge vault"
},
@@ -3214,6 +3197,9 @@
}
}
},
"inviteSingleEmailDesc": {
"message": "You have 1 invite remaining."
},
"userUsingTwoStep": {
"message": "This user is using two-step login to protect their account."
},
@@ -4681,12 +4667,18 @@
"singleOrgDesc": {
"message": "Restrict members from joining other organizations."
},
"singleOrgPolicyDesc": {
"message": "Restrict members from joining other organizations. This policy is required for organizations that have enabled domain verification."
},
"singleOrgBlockCreateMessage": {
"message": "Your current organization has a policy that does not allow you to join more than one organization. Please contact your organization admins or sign up from a different Bitwarden account."
},
"singleOrgPolicyWarning": {
"message": "Organization members who are not owners or admins and are already a member of another organization will be removed from your organization."
},
"singleOrgPolicyMemberWarning": {
"message": "Non-compliant members will be placed in revoked status until they leave all other organizations. Administrators are exempt and can restore members once compliance is met."
},
"requireSso": {
"message": "Require single sign-on authentication"
},
@@ -6403,6 +6395,20 @@
"generateEmail": {
"message": "Generate email"
},
"generatorBoundariesHint": {
"message": "Value must be between $MIN$ and $MAX$",
"description": "Explains spin box minimum and maximum values to the user",
"placeholders": {
"min": {
"content": "$1",
"example": "8"
},
"max": {
"content": "$2",
"example": "128"
}
}
},
"usernameType": {
"message": "Username type"
},
@@ -9506,10 +9512,6 @@
"message": "!@#$%^&*",
"description": "Label for the password generator special characters checkbox"
},
"avoidAmbiguous": {
"message": "Avoid ambiguous characters",
"description": "Label for the avoid ambiguous characters checkbox."
},
"addAttachment": {
"message": "Add attachment"
},
@@ -9528,5 +9530,11 @@
},
"selfHostingTitleProper": {
"message": "Self-Hosting"
},
"verified-domain-single-org-warning" : {
"message": "Verifying a domain will turn on the single organization policy."
},
"single-org-revoked-user-warning": {
"message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations."
}
}

View File

@@ -12,6 +12,13 @@ export const mockCiphers: any[] = [
organizationUseTotp: false,
login: {
password: "123",
hasUris: true,
uris: [
{ uri: "www.google.com" },
{ uri: "accounts.google.com" },
{ uri: "https://www.google.com" },
{ uri: "https://www.google.com/login" },
],
},
edit: false,
viewPassword: true,

View File

@@ -64,5 +64,16 @@ export const mockMemberCipherDetailsResponse: { data: any[] } = {
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
],
},
{
UserName: "Chris Finch Tester",
Email: "chris.finch@wernhamhogg.uk",
UsesKeyConnector: true,
cipherIds: [
"cbea34a8-bde4-46ad-9d19-b05001228ab2",
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
"cbea34a8-bde4-46ad-9d19-b05001228ab1",
],
},
],
};

View File

@@ -99,12 +99,12 @@ describe("PasswordHealthService", () => {
it("should calculate total members per cipher", () => {
expect(service.totalMembersMap.size).toBeGreaterThan(0);
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab1")).toBe(2);
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toBe(4);
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toBe(5);
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab1")).toBe(3);
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toBe(5);
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toBe(6);
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001227nm5")).toBe(4);
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001227nm7")).toBe(1);
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228xy4")).toBe(6);
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228xy4")).toBe(7);
});
});

View File

@@ -1,5 +1,9 @@
import { Inject, Injectable } from "@angular/core";
// eslint-disable-next-line no-restricted-imports
import { mockCiphers } from "@bitwarden/bit-common/tools/reports/access-intelligence/services/ciphers.mock";
// eslint-disable-next-line no-restricted-imports
import { mockMemberCipherDetailsResponse } from "@bitwarden/bit-common/tools/reports/access-intelligence/services/member-cipher-details-response.mock";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
@@ -8,9 +12,6 @@ import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { BadgeVariant } from "@bitwarden/components";
import { mockCiphers } from "./ciphers.mock";
import { mockMemberCipherDetailsResponse } from "./member-cipher-details-response.mock";
@Injectable()
export class PasswordHealthService {
reportCiphers: CipherView[] = [];
@@ -157,10 +158,27 @@ export class PasswordHealthService {
}
}
private checkForExistingCipher(ciph: CipherView) {
checkForExistingCipher(ciph: CipherView) {
if (!this.reportCipherIds.includes(ciph.id)) {
this.reportCipherIds.push(ciph.id);
this.reportCiphers.push(ciph);
}
}
groupCiphersByLoginUri(): CipherView[] {
const cipherViews: CipherView[] = [];
const cipherUris: string[] = [];
const ciphers = this.reportCiphers;
ciphers.forEach((ciph) => {
const uris = ciph.login?.uris ?? [];
uris.map((u: { uri: string }) => {
const uri = Utils.getHostname(u.uri).replace("www.", "");
cipherUris.push(uri);
cipherViews.push({ ...ciph, hostURI: uri } as CipherView & { hostURI: string });
});
});
return cipherViews;
}
}

View File

@@ -1,14 +1,24 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, Params } from "@angular/router";
import { concatMap, Observable, Subject, take, takeUntil } from "rxjs";
import {
concatMap,
firstValueFrom,
map,
Observable,
Subject,
take,
takeUntil,
withLatestFrom,
} from "rxjs";
import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction";
import { OrgDomainServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain.service.abstraction";
import { OrganizationDomainResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain.response";
import { HttpStatusCode } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { DialogService, ToastService } from "@bitwarden/components";
@@ -31,13 +41,13 @@ export class DomainVerificationComponent implements OnInit, OnDestroy {
constructor(
private route: ActivatedRoute,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private orgDomainApiService: OrgDomainApiServiceAbstraction,
private orgDomainService: OrgDomainServiceAbstraction,
private dialogService: DialogService,
private validationService: ValidationService,
private toastService: ToastService,
private configService: ConfigService,
) {}
// eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -64,13 +74,38 @@ export class DomainVerificationComponent implements OnInit, OnDestroy {
this.loading = false;
}
addDomain() {
async addDomain() {
const domainAddEditDialogData: DomainAddEditDialogData = {
organizationId: this.organizationId,
orgDomain: null,
existingDomainNames: this.getExistingDomainNames(),
};
await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.AccountDeprovisioning).pipe(
withLatestFrom(this.orgDomains$),
map(async ([accountDeprovisioningEnabled, organizationDomains]) => {
if (
accountDeprovisioningEnabled &&
organizationDomains.every((domain) => domain.verifiedDate === null)
) {
await this.dialogService.openSimpleDialog({
title: { key: "verified-domain-single-org-warning" },
content: { key: "single-org-revoked-user-warning" },
cancelButtonText: { key: "cancel" },
acceptButtonText: { key: "confirm" },
acceptAction: () => this.openAddDomainDialog(domainAddEditDialogData),
type: "info",
});
} else {
await this.openAddDomainDialog(domainAddEditDialogData);
}
}),
),
);
}
private async openAddDomainDialog(domainAddEditDialogData: DomainAddEditDialogData) {
this.dialogService.open(DomainAddEditDialogComponent, {
data: domainAddEditDialogData,
});

View File

@@ -4,7 +4,6 @@ import { RouterModule, Routes } from "@angular/router";
import { authGuard } from "@bitwarden/angular/auth/guards";
import { AnonLayoutWrapperComponent } from "@bitwarden/auth/angular";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
import { ProvidersComponent } from "@bitwarden/web-vault/app/admin-console/providers/providers.component";
import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/frontend-layout.component";
import { UserLayoutComponent } from "@bitwarden/web-vault/app/layouts/user-layout.component";
@@ -22,6 +21,7 @@ import { AcceptProviderComponent } from "./manage/accept-provider.component";
import { EventsComponent } from "./manage/events.component";
import { MembersComponent } from "./manage/members.component";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProvidersComponent } from "./providers.component";
import { AccountComponent } from "./settings/account.component";
import { SetupProviderComponent } from "./setup/setup-provider.component";
import { SetupComponent } from "./setup/setup.component";

View File

@@ -32,10 +32,12 @@ import { MembersComponent } from "./manage/members.component";
import { UserAddEditComponent } from "./manage/user-add-edit.component";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProvidersRoutingModule } from "./providers-routing.module";
import { ProvidersComponent } from "./providers.component";
import { WebProviderService } from "./services/web-provider.service";
import { AccountComponent } from "./settings/account.component";
import { SetupProviderComponent } from "./setup/setup-provider.component";
import { SetupComponent } from "./setup/setup.component";
import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-provider.component";
@NgModule({
imports: [
@@ -73,6 +75,8 @@ import { SetupComponent } from "./setup/setup.component";
ProviderBillingHistoryComponent,
ProviderSubscriptionComponent,
ProviderSubscriptionStatusComponent,
ProvidersComponent,
VerifyRecoverDeleteProviderComponent,
],
providers: [WebProviderService],
})

View File

@@ -0,0 +1,15 @@
<p class="lead tw-text-center tw-mb-4">{{ "deleteProvider" | i18n }}</p>
<app-callout type="warning">{{ "deleteProviderWarning" | i18n }}</app-callout>
<p class="tw-text-center">
<strong>{{ name }}</strong>
</p>
<p>{{ "deleteProviderRecoverConfirmDesc" | i18n }}</p>
<hr />
<div class="tw-flex">
<button bitButton [bitAction]="submit" buttonType="danger" type="button" [block]="true">
<span>{{ "deleteProvider" | i18n }}</span>
</button>
<a routerLink="/login" bitButton buttonType="secondary" [block]="true" class="tw-ml-2 tw-mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -5,8 +5,6 @@ import { firstValueFrom } from "rxjs";
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
import { ProviderVerifyRecoverDeleteRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-verify-recover-delete.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
@Component({
@@ -16,7 +14,6 @@ import { ToastService } from "@bitwarden/components";
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class VerifyRecoverDeleteProviderComponent implements OnInit {
name: string;
formPromise: Promise<any>;
private providerId: string;
private token: string;
@@ -24,10 +21,8 @@ export class VerifyRecoverDeleteProviderComponent implements OnInit {
constructor(
private router: Router,
private providerApiService: ProviderApiServiceAbstraction,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private route: ActivatedRoute,
private logService: LogService,
private toastService: ToastService,
) {}
@@ -42,22 +37,14 @@ export class VerifyRecoverDeleteProviderComponent implements OnInit {
}
}
async submit() {
try {
const request = new ProviderVerifyRecoverDeleteRequest(this.token);
this.formPromise = this.providerApiService.providerRecoverDeleteToken(
this.providerId,
request,
);
await this.formPromise;
this.toastService.showToast({
variant: "success",
title: this.i18nService.t("providerDeleted"),
message: this.i18nService.t("providerDeletedDesc"),
});
await this.router.navigate(["/"]);
} catch (e) {
this.logService.error(e);
}
}
submit = async () => {
const request = new ProviderVerifyRecoverDeleteRequest(this.token);
await this.providerApiService.providerRecoverDeleteToken(this.providerId, request);
this.toastService.showToast({
variant: "success",
title: this.i18nService.t("providerDeleted"),
message: this.i18nService.t("providerDeletedDesc"),
});
await this.router.navigate(["/"]);
};
}

View File

@@ -1,9 +1,13 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { unauthGuardFn } from "@bitwarden/angular/auth/guards";
import { AnonLayoutWrapperComponent } from "@bitwarden/auth/angular";
import { deepLinkGuard } from "@bitwarden/web-vault/app/auth/guards/deep-link.guard";
import { RouteDataProperties } from "@bitwarden/web-vault/app/core";
import { ProvidersModule } from "./admin-console/providers/providers.module";
import { VerifyRecoverDeleteProviderComponent } from "./admin-console/providers/verify-recover-delete-provider.component";
const routes: Routes = [
{
@@ -17,6 +21,18 @@ const routes: Routes = [
loadChildren: async () =>
(await import("./secrets-manager/secrets-manager.module")).SecretsManagerModule,
},
{
path: "verify-recover-delete-provider",
component: AnonLayoutWrapperComponent,
canActivate: [unauthGuardFn()],
children: [
{
path: "",
component: VerifyRecoverDeleteProviderComponent,
data: { titleId: "deleteAccount" } satisfies RouteDataProperties,
},
],
},
];
@NgModule({

View File

@@ -4,7 +4,7 @@
} as data"
>
<div class="environment-selector-btn">
{{ "loggingInOn" | i18n }}:
{{ "accessing" | i18n }}:
<button
type="button"
(click)="toggle(null)"

View File

@@ -1,14 +1,19 @@
import { animate, state, style, transition, trigger } from "@angular/animations";
import { ConnectedPosition } from "@angular/cdk/overlay";
import { Component, EventEmitter, Output, Input, OnInit, OnDestroy } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { ActivatedRoute } from "@angular/router";
import { Observable, map, Subject, takeUntil } from "rxjs";
import { SelfHostedEnvConfigDialogComponent } from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import {
EnvironmentService,
Region,
RegionConfig,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { DialogService, ToastService } from "@bitwarden/components";
export const ExtensionDefaultOverlayPosition: ConnectedPosition[] = [
{
@@ -56,7 +61,7 @@ export interface EnvironmentSelectorRouteData {
],
})
export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
@Output() onOpenSelfHostedSettings = new EventEmitter();
@Output() onOpenSelfHostedSettings = new EventEmitter<void>();
@Input() overlayPosition: ConnectedPosition[] = [
{
originX: "start",
@@ -79,8 +84,11 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
constructor(
protected environmentService: EnvironmentService,
protected router: Router,
private route: ActivatedRoute,
private dialogService: DialogService,
private configService: ConfigService,
private toastService: ToastService,
private i18nService: I18nService,
) {}
ngOnInit() {
@@ -102,8 +110,25 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
return;
}
/**
* Opens the self-hosted settings dialog.
*
* If the `UnauthenticatedExtensionUIRefresh` feature flag is enabled,
* the self-hosted settings dialog is opened directly. Otherwise, the
* `onOpenSelfHostedSettings` event is emitted.
*/
if (option === Region.SelfHosted) {
this.onOpenSelfHostedSettings.emit();
if (await this.configService.getFeatureFlag(FeatureFlag.UnauthenticatedExtensionUIRefresh)) {
if (await SelfHostedEnvConfigDialogComponent.open(this.dialogService)) {
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("environmentSaved"),
});
}
} else {
this.onOpenSelfHostedSettings.emit();
}
return;
}

View File

@@ -5,7 +5,11 @@ import { MockProxy, mock } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { EmptyComponent } from "@bitwarden/angular/platform/guard/feature-flag.guard.spec";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import {
Account,
AccountInfo,
AccountService,
} from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
@@ -30,7 +34,7 @@ describe("AuthGuard", () => {
keyConnectorServiceRequiresAccountConversion,
);
const accountService: MockProxy<AccountService> = mock<AccountService>();
const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>(null);
const activeAccountSubject = new BehaviorSubject<Account | null>(null);
accountService.activeAccount$ = activeAccountSubject;
activeAccountSubject.next(
Object.assign(

View File

@@ -6,7 +6,11 @@ import { BehaviorSubject, of } from "rxjs";
import { EmptyComponent } from "@bitwarden/angular/platform/guard/feature-flag.guard.spec";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import {
Account,
AccountInfo,
AccountService,
} from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
@@ -56,7 +60,7 @@ describe("lockGuard", () => {
userVerificationService.hasMasterPassword.mockResolvedValue(setupParams.hasMasterPassword);
const accountService: MockProxy<AccountService> = mock<AccountService>();
const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>(null);
const activeAccountSubject = new BehaviorSubject<Account | null>(null);
accountService.activeAccount$ = activeAccountSubject;
activeAccountSubject.next(
Object.assign(

View File

@@ -55,3 +55,6 @@ export * from "./lock/lock-component.service";
// vault timeout
export * from "./vault-timeout-input/vault-timeout-input.component";
// self hosted environment configuration dialog
export * from "./self-hosted-env-config-dialog/self-hosted-env-config-dialog.component";

View File

@@ -7,7 +7,7 @@ import { BehaviorSubject, firstValueFrom, Subject, switchMap, take, takeUntil }
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
@@ -26,7 +26,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { KeySuffixOptions } from "@bitwarden/common/platform/enums";
import { SyncService } from "@bitwarden/common/platform/sync";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { UserId } from "@bitwarden/common/types/guid";
import { UserKey } from "@bitwarden/common/types/key";
import {
AsyncActionsModule,
@@ -73,7 +72,7 @@ const clientTypeToSuccessRouteRecord: Partial<Record<ClientType, string>> = {
export class LockV2Component implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
activeAccount: { id: UserId | undefined } & AccountInfo;
activeAccount: Account | null;
clientType: ClientType;
ClientType = ClientType;
@@ -202,11 +201,15 @@ export class LockV2Component implements OnInit, OnDestroy {
.subscribe();
}
private async handleActiveAccountChange(activeAccount: { id: UserId | undefined } & AccountInfo) {
private async handleActiveAccountChange(activeAccount: Account | null) {
this.activeAccount = activeAccount;
this.resetDataOnActiveAccountChange();
if (activeAccount == null) {
return;
}
this.setEmailAsPageSubtitle(activeAccount.email);
this.unlockOptions = await firstValueFrom(

View File

@@ -18,7 +18,8 @@ import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstraction
import { CaptchaIFrame } from "@bitwarden/common/auth/captcha-iframe";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { ClientType } from "@bitwarden/common/enums";
import { ClientType, HttpStatusCode } from "@bitwarden/common/enums";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@@ -26,6 +27,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SyncService } from "@bitwarden/common/platform/sync";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
@@ -136,6 +138,7 @@ export class LoginComponent implements OnInit, OnDestroy {
private syncService: SyncService,
private toastService: ToastService,
private logService: LogService,
private validationService: ValidationService,
) {
this.clientType = this.platformUtilsService.getClientType();
this.loginViaAuthRequestSupported = this.loginComponentService.isLoginViaAuthRequestSupported();
@@ -182,19 +185,54 @@ export class LoginComponent implements OnInit, OnDestroy {
null,
);
const authResult = await this.loginStrategyService.logIn(credentials);
try {
const authResult = await this.loginStrategyService.logIn(credentials);
await this.saveEmailSettings();
await this.handleAuthResult(authResult);
await this.saveEmailSettings();
await this.handleAuthResult(authResult);
if (this.clientType === ClientType.Desktop) {
if (this.captchaSiteKey) {
const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute("style", "width:335px");
if (this.clientType === ClientType.Desktop) {
if (this.captchaSiteKey) {
const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute("style", "width:335px");
}
}
} catch (error) {
this.logService.error(error);
this.handleSubmitError(error);
}
};
/**
* Handles the error from the submit function.
*
* @param error The error object.
*/
private handleSubmitError(error: unknown) {
// Handle error responses
if (error instanceof ErrorResponse) {
switch (error.statusCode) {
case HttpStatusCode.BadRequest: {
if (error.message.toLowerCase().includes("username or password is incorrect")) {
this.formGroup.controls.masterPassword.setErrors({
error: {
message: this.i18nService.t("invalidMasterPassword"),
},
});
}
break;
}
default: {
// Allow all other errors to be handled by toast
this.validationService.showError(error);
}
}
} else {
// Allow all other errors to be handled by toast
this.validationService.showError(error);
}
}
/**
* Handles the result of the authentication process.
*

View File

@@ -15,7 +15,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService, FormFieldModule, SelectModule, ToastService } from "@bitwarden/components";
import { RegistrationSelfHostedEnvConfigDialogComponent } from "./registration-self-hosted-env-config-dialog.component";
import { SelfHostedEnvConfigDialogComponent } from "../../self-hosted-env-config-dialog/self-hosted-env-config-dialog.component";
/**
* Component for selecting the environment to register with in the email verification registration flow.
@@ -125,9 +125,7 @@ export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy {
}
if (selectedRegion === Region.SelfHosted) {
return from(
RegistrationSelfHostedEnvConfigDialogComponent.open(this.dialogService),
).pipe(
return from(SelfHostedEnvConfigDialogComponent.open(this.dialogService)).pipe(
tap((result: boolean | undefined) =>
this.handleSelfHostedEnvConfigDialogResult(result, prevSelectedRegion),
),

View File

@@ -10,7 +10,7 @@ import {
ValidationErrors,
ValidatorFn,
} from "@angular/forms";
import { Subject, firstValueFrom } from "rxjs";
import { Subject, firstValueFrom, take, filter, takeUntil } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
@@ -54,8 +54,8 @@ function selfHostedEnvSettingsFormValidator(): ValidatorFn {
*/
@Component({
standalone: true,
selector: "auth-registration-self-hosted-env-config-dialog",
templateUrl: "registration-self-hosted-env-config-dialog.component.html",
selector: "self-hosted-env-config-dialog",
templateUrl: "self-hosted-env-config-dialog.component.html",
imports: [
CommonModule,
JslibModule,
@@ -68,14 +68,14 @@ function selfHostedEnvSettingsFormValidator(): ValidatorFn {
AsyncActionsModule,
],
})
export class RegistrationSelfHostedEnvConfigDialogComponent implements OnInit, OnDestroy {
export class SelfHostedEnvConfigDialogComponent implements OnInit, OnDestroy {
/**
* Opens the dialog.
* @param dialogService - Dialog service.
* @returns Promise that resolves to true if the dialog was closed with a successful result, false otherwise.
*/
static async open(dialogService: DialogService): Promise<boolean> {
const dialogRef = dialogService.open<boolean>(RegistrationSelfHostedEnvConfigDialogComponent, {
const dialogRef = dialogService.open<boolean>(SelfHostedEnvConfigDialogComponent, {
disableClose: false,
});
@@ -131,7 +131,33 @@ export class RegistrationSelfHostedEnvConfigDialogComponent implements OnInit, O
private environmentService: EnvironmentService,
) {}
ngOnInit() {}
ngOnInit() {
/**
* Populate the form with the current self-hosted environment settings.
*/
this.environmentService.environment$
.pipe(
take(1),
filter((env) => {
const region = env.getRegion();
return region === Region.SelfHosted;
}),
takeUntil(this.destroy$),
)
.subscribe({
next: (env) => {
const urls = env.getUrls();
this.formGroup.patchValue({
baseUrl: urls.base || "",
webVaultUrl: urls.webVault || "",
apiUrl: urls.api || "",
identityUrl: urls.identity || "",
iconsUrl: urls.icons || "",
notificationsUrl: urls.notifications || "",
});
},
});
}
submit = async () => {
this.showErrorSummary = false;

View File

@@ -295,7 +295,7 @@ describe("LoginStrategyService", () => {
new IdentityTokenResponse({
ForcePasswordReset: false,
Kdf: KdfType.PBKDF2_SHA256,
KdfIterations: PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min - 1,
KdfIterations: PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN - 1,
Key: "KEY",
PrivateKey: "PRIVATE_KEY",
ResetMasterPassword: false,
@@ -309,7 +309,7 @@ describe("LoginStrategyService", () => {
apiService.postPrelogin.mockResolvedValue(
new PreloginResponse({
Kdf: KdfType.PBKDF2_SHA256,
KdfIterations: PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min - 1,
KdfIterations: PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN - 1,
}),
);
@@ -321,7 +321,7 @@ describe("LoginStrategyService", () => {
});
await expect(sut.logIn(credentials)).rejects.toThrow(
`PBKDF2 iterations must be between ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min} and ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.max}`,
`PBKDF2 iterations must be at least ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN}, but was ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN - 1}; possible pre-login downgrade attack detected.`,
);
});
});

View File

@@ -1,7 +1,7 @@
import { mock } from "jest-mock-extended";
import { ReplaySubject, combineLatest, map } from "rxjs";
import { AccountInfo, AccountService } from "../src/auth/abstractions/account.service";
import { Account, AccountInfo, AccountService } from "../src/auth/abstractions/account.service";
import { UserId } from "../src/types/guid";
export function mockAccountServiceWith(
@@ -30,7 +30,7 @@ export class FakeAccountService implements AccountService {
// eslint-disable-next-line rxjs/no-exposed-subjects -- test class
accountsSubject = new ReplaySubject<Record<UserId, AccountInfo>>(1);
// eslint-disable-next-line rxjs/no-exposed-subjects -- test class
activeAccountSubject = new ReplaySubject<{ id: UserId } & AccountInfo>(1);
activeAccountSubject = new ReplaySubject<Account | null>(1);
// eslint-disable-next-line rxjs/no-exposed-subjects -- test class
accountActivitySubject = new ReplaySubject<Record<UserId, Date>>(1);
private _activeUserId: UserId;

View File

@@ -8,6 +8,7 @@ export class PolicyResponse extends BaseResponse {
type: PolicyType;
data: any;
enabled: boolean;
canToggleState: boolean;
constructor(response: any) {
super(response);
@@ -16,5 +17,6 @@ export class PolicyResponse extends BaseResponse {
this.type = this.getResponseProperty("Type");
this.data = this.getResponseProperty("Data");
this.enabled = this.getResponseProperty("Enabled");
this.canToggleState = this.getResponseProperty("CanToggleState") ?? true;
}
}

View File

@@ -12,6 +12,8 @@ export type AccountInfo = {
name: string | undefined;
};
export type Account = { id: UserId } & AccountInfo;
export function accountInfoEqual(a: AccountInfo, b: AccountInfo) {
if (a == null && b == null) {
return true;
@@ -32,7 +34,8 @@ export function accountInfoEqual(a: AccountInfo, b: AccountInfo) {
export abstract class AccountService {
accounts$: Observable<Record<UserId, AccountInfo>>;
activeAccount$: Observable<{ id: UserId | undefined } & AccountInfo>;
activeAccount$: Observable<Account | null>;
/**
* Observable of the last activity time for each account.
@@ -41,7 +44,7 @@ export abstract class AccountService {
/** Account list in order of descending recency */
sortedUserIds$: Observable<UserId[]>;
/** Next account that is not the current active account */
nextUpAccount$: Observable<{ id: UserId } & AccountInfo>;
nextUpAccount$: Observable<Account>;
/**
* Updates the `accounts$` observable with the new account data.
*

View File

@@ -13,7 +13,7 @@ export type KdfConfig = PBKDF2KdfConfig | Argon2KdfConfig;
*/
export class PBKDF2KdfConfig {
static ITERATIONS = new RangeWithDefault(600_000, 2_000_000, 600_000);
static PRELOGIN_ITERATIONS = new RangeWithDefault(5000, 2_000_000, 600_000);
static PRELOGIN_ITERATIONS_MIN = 5000;
kdfType: KdfType.PBKDF2_SHA256 = KdfType.PBKDF2_SHA256;
iterations: number;
@@ -38,9 +38,9 @@ export class PBKDF2KdfConfig {
* A Valid PBKDF2 KDF configuration has KDF iterations between the 5000 and 2_000_000.
*/
validateKdfConfigForPrelogin(): void {
if (!PBKDF2KdfConfig.PRELOGIN_ITERATIONS.inRange(this.iterations)) {
if (PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN > this.iterations) {
throw new Error(
`PBKDF2 iterations must be between ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min} and ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.max}`,
`PBKDF2 iterations must be at least ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN}, but was ${this.iterations}; possible pre-login downgrade attack detected.`,
);
}
}
@@ -58,9 +58,9 @@ export class Argon2KdfConfig {
static PARALLELISM = new RangeWithDefault(1, 16, 4);
static ITERATIONS = new RangeWithDefault(2, 10, 3);
static PRELOGIN_MEMORY = Argon2KdfConfig.MEMORY;
static PRELOGIN_PARALLELISM = Argon2KdfConfig.PARALLELISM;
static PRELOGIN_ITERATIONS = Argon2KdfConfig.ITERATIONS;
static PRELOGIN_MEMORY_MIN = 16;
static PRELOGIN_PARALLELISM_MIN = 1;
static PRELOGIN_ITERATIONS_MIN = 2;
kdfType: KdfType.Argon2id = KdfType.Argon2id;
iterations: number;
@@ -86,7 +86,7 @@ export class Argon2KdfConfig {
if (!Argon2KdfConfig.MEMORY.inRange(this.memory)) {
throw new Error(
`Argon2 memory must be between ${Argon2KdfConfig.MEMORY.min}mb and ${Argon2KdfConfig.MEMORY.max}mb`,
`Argon2 memory must be between ${Argon2KdfConfig.MEMORY.min} MiB and ${Argon2KdfConfig.MEMORY.max} MiB`,
);
}
@@ -101,21 +101,21 @@ export class Argon2KdfConfig {
* Validates the Argon2 KDF configuration for pre-login.
*/
validateKdfConfigForPrelogin(): void {
if (!Argon2KdfConfig.PRELOGIN_ITERATIONS.inRange(this.iterations)) {
if (Argon2KdfConfig.PRELOGIN_ITERATIONS_MIN > this.iterations) {
throw new Error(
`Argon2 iterations must be between ${Argon2KdfConfig.PRELOGIN_ITERATIONS.min} and ${Argon2KdfConfig.PRELOGIN_ITERATIONS.max}`,
`Argon2 iterations must be at least ${Argon2KdfConfig.PRELOGIN_ITERATIONS_MIN}, but was ${this.iterations}; possible pre-login downgrade attack detected.`,
);
}
if (!Argon2KdfConfig.PRELOGIN_MEMORY.inRange(this.memory)) {
if (Argon2KdfConfig.PRELOGIN_MEMORY_MIN > this.memory) {
throw new Error(
`Argon2 memory must be between ${Argon2KdfConfig.PRELOGIN_MEMORY.min}mb and ${Argon2KdfConfig.PRELOGIN_MEMORY.max}mb`,
`Argon2 memory must be at least ${Argon2KdfConfig.PRELOGIN_MEMORY_MIN} MiB, but was ${this.memory} MiB; possible pre-login downgrade attack detected.`,
);
}
if (!Argon2KdfConfig.PRELOGIN_PARALLELISM.inRange(this.parallelism)) {
if (Argon2KdfConfig.PRELOGIN_PARALLELISM_MIN > this.parallelism) {
throw new Error(
`Argon2 parallelism must be between ${Argon2KdfConfig.PRELOGIN_PARALLELISM.min} and ${Argon2KdfConfig.PRELOGIN_PARALLELISM.max}.`,
`Argon2 parallelism must be at least ${Argon2KdfConfig.PRELOGIN_PARALLELISM_MIN}, but was ${this.parallelism}; possible pre-login downgrade attack detected.`,
);
}
}

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