mirror of
https://github.com/bitwarden/browser.git
synced 2025-03-12 13:39:14 +01:00
Merge branch 'main' into autofill/pm-6546-blurring-of-autofilled-elements-causes-problems-in-blur-event-listeners
This commit is contained in:
commit
6058d1257c
@ -18,7 +18,6 @@ apps/desktop/src/auth/scripts/duo.js
|
|||||||
|
|
||||||
apps/web/config.js
|
apps/web/config.js
|
||||||
apps/web/scripts/*.js
|
apps/web/scripts/*.js
|
||||||
apps/web/src/theme.js
|
|
||||||
apps/web/tailwind.config.js
|
apps/web/tailwind.config.js
|
||||||
|
|
||||||
apps/cli/config/config.js
|
apps/cli/config/config.js
|
||||||
|
1
.github/renovate.json
vendored
1
.github/renovate.json
vendored
@ -37,7 +37,6 @@
|
|||||||
"base64-loader",
|
"base64-loader",
|
||||||
"buffer",
|
"buffer",
|
||||||
"bufferutil",
|
"bufferutil",
|
||||||
"clean-webpack-plugin",
|
|
||||||
"copy-webpack-plugin",
|
"copy-webpack-plugin",
|
||||||
"core-js",
|
"core-js",
|
||||||
"css-loader",
|
"css-loader",
|
||||||
|
4
.github/workflows/build-desktop.yml
vendored
4
.github/workflows/build-desktop.yml
vendored
@ -127,7 +127,9 @@ jobs:
|
|||||||
# Note, before updating the ubuntu version of the workflow, ensure the snap base image
|
# Note, before updating the ubuntu version of the workflow, ensure the snap base image
|
||||||
# is equal or greater than the new version. Otherwise there might be GLIBC version issues.
|
# is equal or greater than the new version. Otherwise there might be GLIBC version issues.
|
||||||
# The snap base for desktop is defined in `apps/desktop/electron-builder.json`
|
# The snap base for desktop is defined in `apps/desktop/electron-builder.json`
|
||||||
runs-on: ubuntu-22.04
|
# We are currently running on 20.04 until the Ubuntu 24.04 release is available, as moving
|
||||||
|
# to 22.04 now breaks users who are on 20.04 due to mismatched GLIBC versions.
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
needs: setup
|
needs: setup
|
||||||
env:
|
env:
|
||||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
35
.github/workflows/version-auto-bump.yml
vendored
35
.github/workflows/version-auto-bump.yml
vendored
@ -6,37 +6,11 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- desktop-v**
|
- desktop-v**
|
||||||
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump-version:
|
bump-version:
|
||||||
name: Bump Desktop Version
|
name: Bump Desktop Version
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Branch
|
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
|
||||||
|
|
||||||
- name: Calculate bumped version
|
|
||||||
id: version
|
|
||||||
env:
|
|
||||||
RELEASE_TAG: ${{ github.ref }}
|
|
||||||
run: |
|
|
||||||
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/[a-z]*-v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/')
|
|
||||||
CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/[a-z]*-v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/')
|
|
||||||
echo "Current Major: $CURR_MAJOR"
|
|
||||||
echo "Current Patch: $CURR_PATCH"
|
|
||||||
|
|
||||||
NEW_PATCH=$((CURR_PATCH+1))
|
|
||||||
|
|
||||||
echo "New patch: $NEW_PATCH"
|
|
||||||
|
|
||||||
NEW_VER=$CURR_MAJOR.$NEW_PATCH
|
|
||||||
echo "New Version: $NEW_VER"
|
|
||||||
echo "New Version: $NEW_VER" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "new_version=$NEW_VER" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Login to Azure - CI Subscription
|
- name: Login to Azure - CI Subscription
|
||||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||||
with:
|
with:
|
||||||
@ -49,10 +23,13 @@ jobs:
|
|||||||
keyvault: bitwarden-ci
|
keyvault: bitwarden-ci
|
||||||
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
||||||
|
|
||||||
- name: "Bump version to ${{ steps.version.outputs.new_version }}"
|
- name: Trigger Version Bump workflow
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
GH_TOKEN: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||||
run: |
|
run: |
|
||||||
echo '{"cut_rc_branch": "false", "version_number": "${{ steps.version.outputs.new_version }}",
|
echo '{"cut_rc_branch": "false", \
|
||||||
"bump_browser": "false", "bump_cli": "false", "bump_desktop": "true", "bump_web": "false"}' | \
|
"bump_browser": "false", \
|
||||||
|
"bump_cli": "false", \
|
||||||
|
"bump_desktop": "true", \
|
||||||
|
"bump_web": "false"}' | \
|
||||||
gh workflow run version-bump.yml --json --repo bitwarden/clients
|
gh workflow run version-bump.yml --json --repo bitwarden/clients
|
||||||
|
265
.github/workflows/version-bump.yml
vendored
265
.github/workflows/version-bump.yml
vendored
@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
name: Version Bump
|
name: Version Bump
|
||||||
run-name: Version Bump - v${{ inputs.version_number }}
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@ -21,9 +20,10 @@ on:
|
|||||||
description: "Bump Web?"
|
description: "Bump Web?"
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
version_number:
|
version_number_override:
|
||||||
description: "New version (example: '2024.1.0')"
|
description: "New version override (leave blank for automatic calculation, example: '2024.1.0')"
|
||||||
required: true
|
required: false
|
||||||
|
type: string
|
||||||
cut_rc_branch:
|
cut_rc_branch:
|
||||||
description: "Cut RC branch?"
|
description: "Cut RC branch?"
|
||||||
default: true
|
default: true
|
||||||
@ -31,22 +31,19 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump_version:
|
bump_version:
|
||||||
name: "Bump Version to v${{ inputs.version_number }}"
|
name: Bump Version
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
outputs:
|
||||||
|
version_browser: ${{ steps.set-final-version-output.outputs.version_browser }}
|
||||||
|
version_cli: ${{ steps.set-final-version-output.outputs.version_cli }}
|
||||||
|
version_desktop: ${{ steps.set-final-version-output.outputs.version_desktop }}
|
||||||
|
version_web: ${{ steps.set-final-version-output.outputs.version_web }}
|
||||||
steps:
|
steps:
|
||||||
- name: Login to Azure - Prod Subscription
|
- name: Validate version input
|
||||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
if: ${{ inputs.version_number_override != '' }}
|
||||||
|
uses: bitwarden/gh-actions/version-check@main
|
||||||
with:
|
with:
|
||||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
version: ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
- name: Retrieve secrets
|
|
||||||
id: retrieve-secrets
|
|
||||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
|
||||||
with:
|
|
||||||
keyvault: "bitwarden-ci"
|
|
||||||
secrets: "github-gpg-private-key,
|
|
||||||
github-gpg-private-key-passphrase,
|
|
||||||
github-pat-bitwarden-devops-bot-repo-scope"
|
|
||||||
|
|
||||||
- name: Checkout Branch
|
- name: Checkout Branch
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
@ -63,6 +60,20 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Login to Azure - CI Subscription
|
||||||
|
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||||
|
with:
|
||||||
|
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
|
||||||
|
- name: Retrieve secrets
|
||||||
|
id: retrieve-secrets
|
||||||
|
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||||
|
with:
|
||||||
|
keyvault: "bitwarden-ci"
|
||||||
|
secrets: "github-gpg-private-key,
|
||||||
|
github-gpg-private-key-passphrase,
|
||||||
|
github-pat-bitwarden-devops-bot-repo-scope"
|
||||||
|
|
||||||
- name: Import GPG key
|
- name: Import GPG key
|
||||||
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
|
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
|
||||||
with:
|
with:
|
||||||
@ -71,6 +82,11 @@ jobs:
|
|||||||
git_user_signingkey: true
|
git_user_signingkey: true
|
||||||
git_commit_gpgsign: true
|
git_commit_gpgsign: true
|
||||||
|
|
||||||
|
- name: Setup git
|
||||||
|
run: |
|
||||||
|
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
|
||||||
|
git config --local user.name "bitwarden-devops-bot"
|
||||||
|
|
||||||
- name: Create Version Branch
|
- name: Create Version Branch
|
||||||
id: create-branch
|
id: create-branch
|
||||||
run: |
|
run: |
|
||||||
@ -90,7 +106,7 @@ jobs:
|
|||||||
printf -v joined '%s,' "${CLIENTS[@]}"
|
printf -v joined '%s,' "${CLIENTS[@]}"
|
||||||
echo "client=${joined%,}" >> $GITHUB_OUTPUT
|
echo "client=${joined%,}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
NAME=version_bump_${{ github.ref_name }}_${{ inputs.version_number }}
|
NAME=version_bump_${{ github.ref_name }}_$(date +"%Y-%m-%d")
|
||||||
git switch -c $NAME
|
git switch -c $NAME
|
||||||
echo "name=$NAME" >> $GITHUB_OUTPUT
|
echo "name=$NAME" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
@ -99,13 +115,20 @@ jobs:
|
|||||||
########################
|
########################
|
||||||
|
|
||||||
### Browser
|
### Browser
|
||||||
- name: Browser - Verify input version
|
- name: Get current Browser version
|
||||||
if: ${{ inputs.bump_browser == true }}
|
if: ${{ inputs.bump_browser == true }}
|
||||||
env:
|
id: current-browser-version
|
||||||
NEW_VERSION: ${{ inputs.version_number }}
|
|
||||||
run: |
|
run: |
|
||||||
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
||||||
|
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
working-directory: apps/browser
|
||||||
|
|
||||||
|
- name: Browser - Verify input version
|
||||||
|
if: ${{ inputs.bump_browser == true && inputs.version_number_override != '' }}
|
||||||
|
env:
|
||||||
|
CURRENT_VERSION: ${{ steps.current-browser-version.outputs.version }}
|
||||||
|
NEW_VERSION: ${{ inputs.version_number_override }}
|
||||||
|
run: |
|
||||||
# Error if version has not changed.
|
# Error if version has not changed.
|
||||||
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
||||||
echo "Version has not changed."
|
echo "Version has not changed."
|
||||||
@ -122,23 +145,52 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
working-directory: apps/browser
|
working-directory: apps/browser
|
||||||
|
|
||||||
- name: Bump Browser Version
|
- name: Calculate next Browser release version
|
||||||
if: ${{ inputs.bump_browser == true }}
|
if: ${{ inputs.bump_browser == true && inputs.version_number_override == '' }}
|
||||||
run: npm version --workspace=@bitwarden/browser ${{ inputs.version_number }}
|
id: calculate-next-browser-version
|
||||||
|
uses: bitwarden/gh-actions/version-next@main
|
||||||
|
with:
|
||||||
|
version: ${{ steps.current-browser-version.outputs.version }}
|
||||||
|
|
||||||
- name: Bump Browser Version - Manifest
|
- name: Bump Browser Version - Version Override
|
||||||
if: ${{ inputs.bump_browser == true }}
|
if: ${{ inputs.bump_browser == true && inputs.version_number_override != '' }}
|
||||||
|
id: bump-browser-version-override
|
||||||
|
run: npm version --workspace=@bitwarden/browser ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
|
- name: Bump Browser Version - Automatic Calculation
|
||||||
|
if: ${{ inputs.bump_browser == true && inputs.version_number_override == '' }}
|
||||||
|
id: bump-browser-version-automatic
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.calculate-next-browser-version.outputs.version }}
|
||||||
|
run: npm version --workspace=@bitwarden/browser $VERSION
|
||||||
|
|
||||||
|
- name: Bump Browser Version - Manifest - Version Override
|
||||||
|
if: ${{ inputs.bump_browser == true && inputs.version_number_override != '' }}
|
||||||
uses: bitwarden/gh-actions/version-bump@main
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
with:
|
with:
|
||||||
version: ${{ inputs.version_number }}
|
|
||||||
file_path: "apps/browser/src/manifest.json"
|
file_path: "apps/browser/src/manifest.json"
|
||||||
|
version: ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
- name: Bump Browser Version - Manifest v3
|
- name: Bump Browser Version - Manifest - Automatic Calculation
|
||||||
if: ${{ inputs.bump_browser == true }}
|
if: ${{ inputs.bump_browser == true && inputs.version_number_override == '' }}
|
||||||
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
|
with:
|
||||||
|
file_path: "apps/browser/src/manifest.json"
|
||||||
|
version: ${{ steps.calculate-next-browser-version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Bump Browser Version - Manifest v3 - Version Override
|
||||||
|
if: ${{ inputs.bump_browser == true && inputs.version_number_override != '' }}
|
||||||
uses: bitwarden/gh-actions/version-bump@main
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
with:
|
with:
|
||||||
version: ${{ inputs.version_number }}
|
|
||||||
file_path: "apps/browser/src/manifest.v3.json"
|
file_path: "apps/browser/src/manifest.v3.json"
|
||||||
|
version: ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
|
- name: Bump Browser Version - Manifest v3 - Automatic Calculation
|
||||||
|
if: ${{ inputs.bump_browser == true && inputs.version_number_override == '' }}
|
||||||
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
|
with:
|
||||||
|
file_path: "apps/browser/src/manifest.v3.json"
|
||||||
|
version: ${{ steps.calculate-next-browser-version.outputs.version }}
|
||||||
|
|
||||||
- name: Run Prettier after Browser Version Bump
|
- name: Run Prettier after Browser Version Bump
|
||||||
if: ${{ inputs.bump_browser == true }}
|
if: ${{ inputs.bump_browser == true }}
|
||||||
@ -148,13 +200,20 @@ jobs:
|
|||||||
prettier --write apps/browser/src/manifest.v3.json
|
prettier --write apps/browser/src/manifest.v3.json
|
||||||
|
|
||||||
### CLI
|
### CLI
|
||||||
- name: CLI - Verify input version
|
- name: Get current CLI version
|
||||||
if: ${{ inputs.bump_cli == true }}
|
if: ${{ inputs.bump_cli == true }}
|
||||||
env:
|
id: current-cli-version
|
||||||
NEW_VERSION: ${{ inputs.version_number }}
|
|
||||||
run: |
|
run: |
|
||||||
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
||||||
|
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
working-directory: apps/cli
|
||||||
|
|
||||||
|
- name: CLI - Verify input version
|
||||||
|
if: ${{ inputs.bump_cli == true && inputs.version_number_override != '' }}
|
||||||
|
env:
|
||||||
|
CURRENT_VERSION: ${{ steps.current-cli-version.outputs.version }}
|
||||||
|
NEW_VERSION: ${{ inputs.version_number_override }}
|
||||||
|
run: |
|
||||||
# Error if version has not changed.
|
# Error if version has not changed.
|
||||||
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
||||||
echo "Version has not changed."
|
echo "Version has not changed."
|
||||||
@ -171,18 +230,40 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
working-directory: apps/cli
|
working-directory: apps/cli
|
||||||
|
|
||||||
- name: Bump CLI Version
|
- name: Calculate next CLI release version
|
||||||
if: ${{ inputs.bump_cli == true }}
|
if: ${{ inputs.bump_cli == true && inputs.version_number_override == '' }}
|
||||||
run: npm version --workspace=@bitwarden/cli ${{ inputs.version_number }}
|
id: calculate-next-cli-version
|
||||||
|
uses: bitwarden/gh-actions/version-next@main
|
||||||
|
with:
|
||||||
|
version: ${{ steps.current-cli-version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Bump CLI Version - Version Override
|
||||||
|
if: ${{ inputs.bump_cli == true && inputs.version_number_override != '' }}
|
||||||
|
id: bump-cli-version-override
|
||||||
|
run: npm version --workspace=@bitwarden/cli ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
|
- name: Bump CLI Version - Automatic Calculation
|
||||||
|
if: ${{ inputs.bump_cli == true && inputs.version_number_override == '' }}
|
||||||
|
id: bump-cli-version-automatic
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.calculate-next-cli-version.outputs.version }}
|
||||||
|
run: npm version --workspace=@bitwarden/cli $VERSION
|
||||||
|
|
||||||
### Desktop
|
### Desktop
|
||||||
- name: Desktop - Verify input version
|
- name: Get current Desktop version
|
||||||
if: ${{ inputs.bump_desktop == true }}
|
if: ${{ inputs.bump_desktop == true }}
|
||||||
env:
|
id: current-desktop-version
|
||||||
NEW_VERSION: ${{ inputs.version_number }}
|
|
||||||
run: |
|
run: |
|
||||||
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
||||||
|
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
working-directory: apps/desktop
|
||||||
|
|
||||||
|
- name: Desktop - Verify input version
|
||||||
|
if: ${{ inputs.bump_desktop == true && inputs.version_number_override != '' }}
|
||||||
|
env:
|
||||||
|
CURRENT_VERSION: ${{ steps.current-desktop-version.outputs.version }}
|
||||||
|
NEW_VERSION: ${{ inputs.version_number_override }}
|
||||||
|
run: |
|
||||||
# Error if version has not changed.
|
# Error if version has not changed.
|
||||||
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
||||||
echo "Version has not changed."
|
echo "Version has not changed."
|
||||||
@ -199,23 +280,52 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
working-directory: apps/desktop
|
working-directory: apps/desktop
|
||||||
|
|
||||||
- name: Bump Desktop Version - Root
|
- name: Calculate next Desktop release version
|
||||||
if: ${{ inputs.bump_desktop == true }}
|
if: ${{ inputs.bump_desktop == true && inputs.version_number_override == '' }}
|
||||||
run: npm version --workspace=@bitwarden/desktop ${{ inputs.version_number }}
|
id: calculate-next-desktop-version
|
||||||
|
uses: bitwarden/gh-actions/version-next@main
|
||||||
|
with:
|
||||||
|
version: ${{ steps.current-desktop-version.outputs.version }}
|
||||||
|
|
||||||
- name: Bump Desktop Version - App
|
- name: Bump Desktop Version - Root - Version Override
|
||||||
if: ${{ inputs.bump_desktop == true }}
|
if: ${{ inputs.bump_desktop == true && inputs.version_number_override != '' }}
|
||||||
run: npm version ${{ inputs.version_number }}
|
id: bump-desktop-version-override
|
||||||
|
run: npm version --workspace=@bitwarden/desktop ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
|
- name: Bump Desktop Version - Root - Automatic Calculation
|
||||||
|
if: ${{ inputs.bump_desktop == true && inputs.version_number_override == '' }}
|
||||||
|
id: bump-desktop-version-automatic
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.calculate-next-desktop-version.outputs.version }}
|
||||||
|
run: npm version --workspace=@bitwarden/desktop $VERSION
|
||||||
|
|
||||||
|
- name: Bump Desktop Version - App - Version Override
|
||||||
|
if: ${{ inputs.bump_desktop == true && inputs.version_number_override != '' }}
|
||||||
|
run: npm version ${{ inputs.version_number_override }}
|
||||||
|
working-directory: "apps/desktop/src"
|
||||||
|
|
||||||
|
- name: Bump Desktop Version - App - Automatic Calculation
|
||||||
|
if: ${{ inputs.bump_desktop == true && inputs.version_number_override == '' }}
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.calculate-next-desktop-version.outputs.version }}
|
||||||
|
run: npm version $VERSION
|
||||||
working-directory: "apps/desktop/src"
|
working-directory: "apps/desktop/src"
|
||||||
|
|
||||||
### Web
|
### Web
|
||||||
- name: Web - Verify input version
|
- name: Get current Web version
|
||||||
if: ${{ inputs.bump_web == true }}
|
if: ${{ inputs.bump_web == true }}
|
||||||
env:
|
id: current-web-version
|
||||||
NEW_VERSION: ${{ inputs.version_number }}
|
|
||||||
run: |
|
run: |
|
||||||
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
||||||
|
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
working-directory: apps/web
|
||||||
|
|
||||||
|
- name: Web - Verify input version
|
||||||
|
if: ${{ inputs.bump_web == true && inputs.version_number_override != '' }}
|
||||||
|
env:
|
||||||
|
CURRENT_VERSION: ${{ steps.current-web-version.outputs.version }}
|
||||||
|
NEW_VERSION: ${{ inputs.version_number_override }}
|
||||||
|
run: |
|
||||||
# Error if version has not changed.
|
# Error if version has not changed.
|
||||||
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
||||||
echo "Version has not changed."
|
echo "Version has not changed."
|
||||||
@ -232,16 +342,47 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
working-directory: apps/web
|
working-directory: apps/web
|
||||||
|
|
||||||
- name: Bump Web Version
|
- name: Calculate next Web release version
|
||||||
if: ${{ inputs.bump_web == true }}
|
if: ${{ inputs.bump_web == true && inputs.version_number_override == '' }}
|
||||||
run: npm version --workspace=@bitwarden/web-vault ${{ inputs.version_number }}
|
id: calculate-next-web-version
|
||||||
|
uses: bitwarden/gh-actions/version-next@main
|
||||||
|
with:
|
||||||
|
version: ${{ steps.current-web-version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Bump Web Version - Version Override
|
||||||
|
if: ${{ inputs.bump_web == true && inputs.version_number_override != '' }}
|
||||||
|
id: bump-web-version-override
|
||||||
|
run: npm version --workspace=@bitwarden/web-vault ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
|
- name: Bump Web Version - Automatic Calculation
|
||||||
|
if: ${{ inputs.bump_web == true && inputs.version_number_override == '' }}
|
||||||
|
id: bump-web-version-automatic
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.calculate-next-web-version.outputs.version }}
|
||||||
|
run: npm version --workspace=@bitwarden/web-vault $VERSION
|
||||||
|
|
||||||
########################
|
########################
|
||||||
|
|
||||||
- name: Setup git
|
- name: Set final version output
|
||||||
|
id: set-final-version-output
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
|
if [[ "${{ steps.bump-browser-version-override.outcome }}" = "success" ]]; then
|
||||||
git config --local user.name "bitwarden-devops-bot"
|
echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ steps.bump-browser-version-automatic.outcome }}" = "success" ]]; then
|
||||||
|
echo "version=${{ steps.calculate-next-browser-version.outputs.version }}" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ steps.bump-cli-version-override.outcome }}" = "success" ]]; then
|
||||||
|
echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ steps.bump-cli-version-automatic.outcome }}" = "success" ]]; then
|
||||||
|
echo "version=${{ steps.calculate-next-cli-version.outputs.version }}" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ steps.bump-desktop-version-override.outcome }}" = "success" ]]; then
|
||||||
|
echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ steps.bump-desktop-version-automatic.outcome }}" = "success" ]]; then
|
||||||
|
echo "version=${{ steps.calculate-next-desktop-version.outputs.version }}" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ steps.bump-web-version-override.outcome }}" = "success" ]]; then
|
||||||
|
echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ steps.bump-web-version-automatic.outcome }}" = "success" ]]; then
|
||||||
|
echo "version=${{ steps.calculate-next-web-version.outputs.version }}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Check if version changed
|
- name: Check if version changed
|
||||||
id: version-changed
|
id: version-changed
|
||||||
@ -257,7 +398,7 @@ jobs:
|
|||||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||||
env:
|
env:
|
||||||
CLIENT: ${{ steps.create-branch.outputs.client }}
|
CLIENT: ${{ steps.create-branch.outputs.client }}
|
||||||
VERSION: ${{ inputs.version_number }}
|
VERSION: ${{ steps.set-final-version-output.outputs.version }}
|
||||||
run: git commit -m "Bumped ${CLIENT} version to ${VERSION}" -a
|
run: git commit -m "Bumped ${CLIENT} version to ${VERSION}" -a
|
||||||
|
|
||||||
- name: Push changes
|
- name: Push changes
|
||||||
@ -272,7 +413,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||||
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
|
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
|
||||||
TITLE: "Bump ${{ steps.create-branch.outputs.client }} version to ${{ inputs.version_number }}"
|
TITLE: "Bump ${{ steps.create-branch.outputs.client }} version to ${{ steps.set-final-version-output.outputs.version }}"
|
||||||
run: |
|
run: |
|
||||||
PR_URL=$(gh pr create --title "$TITLE" \
|
PR_URL=$(gh pr create --title "$TITLE" \
|
||||||
--base "main" \
|
--base "main" \
|
||||||
@ -288,7 +429,7 @@ jobs:
|
|||||||
- [X] Other
|
- [X] Other
|
||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
Automated ${{ steps.create-branch.outputs.client }} version bump to ${{ inputs.version_number }}")
|
Automated ${{ steps.create-branch.outputs.client }} version bump to ${{ steps.set-final-version-output.outputs.version }}")
|
||||||
echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT
|
echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Approve PR
|
- name: Approve PR
|
||||||
@ -307,8 +448,8 @@ jobs:
|
|||||||
|
|
||||||
cut_rc:
|
cut_rc:
|
||||||
name: Cut RC branch
|
name: Cut RC branch
|
||||||
needs: bump_version
|
|
||||||
if: ${{ inputs.cut_rc_branch == true }}
|
if: ${{ inputs.cut_rc_branch == true }}
|
||||||
|
needs: bump_version
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Branch
|
- name: Checkout Branch
|
||||||
@ -320,7 +461,7 @@ jobs:
|
|||||||
- name: Browser - Verify version has been updated
|
- name: Browser - Verify version has been updated
|
||||||
if: ${{ inputs.bump_browser == true }}
|
if: ${{ inputs.bump_browser == true }}
|
||||||
env:
|
env:
|
||||||
NEW_VERSION: ${{ inputs.version_number }}
|
NEW_VERSION: ${{ needs.bump_version.outputs.version_browser }}
|
||||||
run: |
|
run: |
|
||||||
# Wait for version to change.
|
# Wait for version to change.
|
||||||
while : ; do
|
while : ; do
|
||||||
@ -338,7 +479,7 @@ jobs:
|
|||||||
- name: CLI - Verify version has been updated
|
- name: CLI - Verify version has been updated
|
||||||
if: ${{ inputs.bump_cli == true }}
|
if: ${{ inputs.bump_cli == true }}
|
||||||
env:
|
env:
|
||||||
NEW_VERSION: ${{ inputs.version_number }}
|
NEW_VERSION: ${{ needs.bump_version.outputs.version_cli }}
|
||||||
run: |
|
run: |
|
||||||
# Wait for version to change.
|
# Wait for version to change.
|
||||||
while : ; do
|
while : ; do
|
||||||
@ -356,7 +497,7 @@ jobs:
|
|||||||
- name: Desktop - Verify version has been updated
|
- name: Desktop - Verify version has been updated
|
||||||
if: ${{ inputs.bump_desktop == true }}
|
if: ${{ inputs.bump_desktop == true }}
|
||||||
env:
|
env:
|
||||||
NEW_VERSION: ${{ inputs.version_number }}
|
NEW_VERSION: ${{ needs.bump_version.outputs.version_desktop }}
|
||||||
run: |
|
run: |
|
||||||
# Wait for version to change.
|
# Wait for version to change.
|
||||||
while : ; do
|
while : ; do
|
||||||
@ -374,7 +515,7 @@ jobs:
|
|||||||
- name: Web - Verify version has been updated
|
- name: Web - Verify version has been updated
|
||||||
if: ${{ inputs.bump_web == true }}
|
if: ${{ inputs.bump_web == true }}
|
||||||
env:
|
env:
|
||||||
NEW_VERSION: ${{ inputs.version_number }}
|
NEW_VERSION: ${{ needs.bump_version.outputs.version_web }}
|
||||||
run: |
|
run: |
|
||||||
# Wait for version to change.
|
# Wait for version to change.
|
||||||
while : ; do
|
while : ; do
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@bitwarden/browser",
|
"name": "@bitwarden/browser",
|
||||||
"version": "2024.2.1",
|
"version": "2024.3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack",
|
"build": "webpack",
|
||||||
"build:mv3": "cross-env MANIFEST_VERSION=3 webpack",
|
"build:mv3": "cross-env MANIFEST_VERSION=3 webpack",
|
||||||
|
@ -360,7 +360,7 @@
|
|||||||
"message": "Gwefan"
|
"message": "Gwefan"
|
||||||
},
|
},
|
||||||
"toggleVisibility": {
|
"toggleVisibility": {
|
||||||
"message": "Toggle visibility"
|
"message": "Toglo gwelededd"
|
||||||
},
|
},
|
||||||
"manage": {
|
"manage": {
|
||||||
"message": "Rheoli"
|
"message": "Rheoli"
|
||||||
@ -381,7 +381,7 @@
|
|||||||
"message": "Ystyriwch ein helpu ni gydag adolygiad da!"
|
"message": "Ystyriwch ein helpu ni gydag adolygiad da!"
|
||||||
},
|
},
|
||||||
"browserNotSupportClipboard": {
|
"browserNotSupportClipboard": {
|
||||||
"message": "Your web browser does not support easy clipboard copying. Copy it manually instead."
|
"message": "Dyw eich porwr gwe ddim yn cefnogi copïo drwy'r clipfwrdd yn hawdd. Copïwch â llaw yn lle."
|
||||||
},
|
},
|
||||||
"verifyIdentity": {
|
"verifyIdentity": {
|
||||||
"message": "Gwirio'ch hunaniaeth"
|
"message": "Gwirio'ch hunaniaeth"
|
||||||
@ -415,7 +415,7 @@
|
|||||||
"message": "Cloi nawr"
|
"message": "Cloi nawr"
|
||||||
},
|
},
|
||||||
"lockAll": {
|
"lockAll": {
|
||||||
"message": "Lock all"
|
"message": "Cloi'r cwbl"
|
||||||
},
|
},
|
||||||
"immediately": {
|
"immediately": {
|
||||||
"message": "ar unwaith"
|
"message": "ar unwaith"
|
||||||
@ -2925,7 +2925,7 @@
|
|||||||
"message": "Newid i gyfrif"
|
"message": "Newid i gyfrif"
|
||||||
},
|
},
|
||||||
"activeAccount": {
|
"activeAccount": {
|
||||||
"message": "Active account"
|
"message": "Cyfrif gweithredol"
|
||||||
},
|
},
|
||||||
"availableAccounts": {
|
"availableAccounts": {
|
||||||
"message": "Cyfrifon ar gael"
|
"message": "Cyfrifon ar gael"
|
||||||
@ -2943,7 +2943,7 @@
|
|||||||
"message": "unlocked"
|
"message": "unlocked"
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"message": "server"
|
"message": "gweinydd"
|
||||||
},
|
},
|
||||||
"hostedAt": {
|
"hostedAt": {
|
||||||
"message": "hosted at"
|
"message": "hosted at"
|
||||||
@ -2952,7 +2952,7 @@
|
|||||||
"message": "Use your device or hardware key"
|
"message": "Use your device or hardware key"
|
||||||
},
|
},
|
||||||
"justOnce": {
|
"justOnce": {
|
||||||
"message": "Just once"
|
"message": "Unwaith yn unig"
|
||||||
},
|
},
|
||||||
"alwaysForThisSite": {
|
"alwaysForThisSite": {
|
||||||
"message": "Always for this site"
|
"message": "Always for this site"
|
||||||
@ -2971,7 +2971,7 @@
|
|||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
"overrideDefaultBrowserAutofillTitle": {
|
"overrideDefaultBrowserAutofillTitle": {
|
||||||
"message": "Make Bitwarden your default password manager?",
|
"message": "Hoffech chi wneud Bitwarden yn rheolydd cyfrineiriau rhagosodedig?",
|
||||||
"description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior"
|
"description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior"
|
||||||
},
|
},
|
||||||
"overrideDefaultBrowserAutofillDescription": {
|
"overrideDefaultBrowserAutofillDescription": {
|
||||||
|
@ -1500,7 +1500,7 @@
|
|||||||
"message": "无效 PIN 码。"
|
"message": "无效 PIN 码。"
|
||||||
},
|
},
|
||||||
"tooManyInvalidPinEntryAttemptsLoggingOut": {
|
"tooManyInvalidPinEntryAttemptsLoggingOut": {
|
||||||
"message": "无效的 PIN 输入尝试次数过多,正在注销。"
|
"message": "无效的 PIN 输入尝试次数过多,正在退出登录。"
|
||||||
},
|
},
|
||||||
"unlockWithBiometrics": {
|
"unlockWithBiometrics": {
|
||||||
"message": "使用生物识别解锁"
|
"message": "使用生物识别解锁"
|
||||||
|
@ -26,6 +26,10 @@ import {
|
|||||||
factory,
|
factory,
|
||||||
FactoryOptions,
|
FactoryOptions,
|
||||||
} from "../../../platform/background/service-factories/factory-options";
|
} from "../../../platform/background/service-factories/factory-options";
|
||||||
|
import {
|
||||||
|
globalStateProviderFactory,
|
||||||
|
GlobalStateProviderInitOptions,
|
||||||
|
} from "../../../platform/background/service-factories/global-state-provider.factory";
|
||||||
import {
|
import {
|
||||||
i18nServiceFactory,
|
i18nServiceFactory,
|
||||||
I18nServiceInitOptions,
|
I18nServiceInitOptions,
|
||||||
@ -84,7 +88,8 @@ export type LoginStrategyServiceInitOptions = LoginStrategyServiceFactoryOptions
|
|||||||
PolicyServiceInitOptions &
|
PolicyServiceInitOptions &
|
||||||
PasswordStrengthServiceInitOptions &
|
PasswordStrengthServiceInitOptions &
|
||||||
DeviceTrustCryptoServiceInitOptions &
|
DeviceTrustCryptoServiceInitOptions &
|
||||||
AuthRequestServiceInitOptions;
|
AuthRequestServiceInitOptions &
|
||||||
|
GlobalStateProviderInitOptions;
|
||||||
|
|
||||||
export function loginStrategyServiceFactory(
|
export function loginStrategyServiceFactory(
|
||||||
cache: { loginStrategyService?: LoginStrategyServiceAbstraction } & CachedServices,
|
cache: { loginStrategyService?: LoginStrategyServiceAbstraction } & CachedServices,
|
||||||
@ -113,6 +118,7 @@ export function loginStrategyServiceFactory(
|
|||||||
await policyServiceFactory(cache, opts),
|
await policyServiceFactory(cache, opts),
|
||||||
await deviceTrustCryptoServiceFactory(cache, opts),
|
await deviceTrustCryptoServiceFactory(cache, opts),
|
||||||
await authRequestServiceFactory(cache, opts),
|
await authRequestServiceFactory(cache, opts),
|
||||||
|
await globalStateProviderFactory(cache, opts),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
|
||||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||||
|
|
||||||
import { NotificationQueueMessageTypes } from "../../enums/notification-queue-message-type.enum";
|
import { NotificationQueueMessageTypes } from "../../enums/notification-queue-message-type.enum";
|
||||||
@ -111,6 +112,7 @@ type NotificationBackgroundExtensionMessageHandlers = {
|
|||||||
collectPageDetailsResponse: ({ message }: BackgroundMessageParam) => Promise<void>;
|
collectPageDetailsResponse: ({ message }: BackgroundMessageParam) => Promise<void>;
|
||||||
bgGetEnableChangedPasswordPrompt: () => Promise<boolean>;
|
bgGetEnableChangedPasswordPrompt: () => Promise<boolean>;
|
||||||
bgGetEnableAddedLoginPrompt: () => Promise<boolean>;
|
bgGetEnableAddedLoginPrompt: () => Promise<boolean>;
|
||||||
|
bgGetExcludedDomains: () => Promise<NeverDomains>;
|
||||||
getWebVaultUrlForNotification: () => string;
|
getWebVaultUrlForNotification: () => string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,9 +4,11 @@ import { firstValueFrom } from "rxjs";
|
|||||||
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
|
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
||||||
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { UserNotificationSettingsService } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
import { UserNotificationSettingsService } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
|
||||||
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||||
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
||||||
@ -47,8 +49,10 @@ describe("NotificationBackground", () => {
|
|||||||
const folderService = mock<FolderService>();
|
const folderService = mock<FolderService>();
|
||||||
const stateService = mock<BrowserStateService>();
|
const stateService = mock<BrowserStateService>();
|
||||||
const userNotificationSettingsService = mock<UserNotificationSettingsService>();
|
const userNotificationSettingsService = mock<UserNotificationSettingsService>();
|
||||||
|
const domainSettingsService = mock<DomainSettingsService>();
|
||||||
const environmentService = mock<EnvironmentService>();
|
const environmentService = mock<EnvironmentService>();
|
||||||
const logService = mock<LogService>();
|
const logService = mock<LogService>();
|
||||||
|
const themeStateService = mock<ThemeStateService>();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
notificationBackground = new NotificationBackground(
|
notificationBackground = new NotificationBackground(
|
||||||
@ -59,8 +63,10 @@ describe("NotificationBackground", () => {
|
|||||||
folderService,
|
folderService,
|
||||||
stateService,
|
stateService,
|
||||||
userNotificationSettingsService,
|
userNotificationSettingsService,
|
||||||
|
domainSettingsService,
|
||||||
environmentService,
|
environmentService,
|
||||||
logService,
|
logService,
|
||||||
|
themeStateService,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5,10 +5,13 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
|||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { NOTIFICATION_BAR_LIFESPAN_MS } from "@bitwarden/common/autofill/constants";
|
import { NOTIFICATION_BAR_LIFESPAN_MS } from "@bitwarden/common/autofill/constants";
|
||||||
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||||
|
import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
@ -60,6 +63,7 @@ export default class NotificationBackground {
|
|||||||
bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab),
|
bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab),
|
||||||
bgGetEnableChangedPasswordPrompt: () => this.getEnableChangedPasswordPrompt(),
|
bgGetEnableChangedPasswordPrompt: () => this.getEnableChangedPasswordPrompt(),
|
||||||
bgGetEnableAddedLoginPrompt: () => this.getEnableAddedLoginPrompt(),
|
bgGetEnableAddedLoginPrompt: () => this.getEnableAddedLoginPrompt(),
|
||||||
|
bgGetExcludedDomains: () => this.getExcludedDomains(),
|
||||||
getWebVaultUrlForNotification: () => this.getWebVaultUrl(),
|
getWebVaultUrlForNotification: () => this.getWebVaultUrl(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -71,8 +75,10 @@ export default class NotificationBackground {
|
|||||||
private folderService: FolderService,
|
private folderService: FolderService,
|
||||||
private stateService: BrowserStateService,
|
private stateService: BrowserStateService,
|
||||||
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
|
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
|
||||||
|
private domainSettingsService: DomainSettingsService,
|
||||||
private environmentService: EnvironmentService,
|
private environmentService: EnvironmentService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
|
private themeStateService: ThemeStateService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
@ -99,6 +105,13 @@ export default class NotificationBackground {
|
|||||||
return await firstValueFrom(this.userNotificationSettingsService.enableAddedLoginPrompt$);
|
return await firstValueFrom(this.userNotificationSettingsService.enableAddedLoginPrompt$);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the neverDomains setting from the domain settings service.
|
||||||
|
*/
|
||||||
|
async getExcludedDomains(): Promise<NeverDomains> {
|
||||||
|
return await firstValueFrom(this.domainSettingsService.neverDomains$);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the notification queue for any messages that need to be sent to the
|
* Checks the notification queue for any messages that need to be sent to the
|
||||||
* specified tab. If no tab is specified, the current tab will be used.
|
* specified tab. If no tab is specified, the current tab will be used.
|
||||||
@ -154,7 +167,7 @@ export default class NotificationBackground {
|
|||||||
const notificationType = notificationQueueMessage.type;
|
const notificationType = notificationQueueMessage.type;
|
||||||
const typeData: Record<string, any> = {
|
const typeData: Record<string, any> = {
|
||||||
isVaultLocked: notificationQueueMessage.wasVaultLocked,
|
isVaultLocked: notificationQueueMessage.wasVaultLocked,
|
||||||
theme: await this.stateService.getTheme(),
|
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (notificationType) {
|
switch (notificationType) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { mock, mockReset } from "jest-mock-extended";
|
import { mock, mockReset } from "jest-mock-extended";
|
||||||
|
import { of } from "rxjs";
|
||||||
|
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
||||||
@ -10,6 +11,7 @@ import { AutofillSettingsService } from "@bitwarden/common/autofill/services/aut
|
|||||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/services/i18n.service";
|
||||||
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
import { SettingsService } from "@bitwarden/common/services/settings.service";
|
import { SettingsService } from "@bitwarden/common/services/settings.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||||
@ -17,8 +19,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
|||||||
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
||||||
|
|
||||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||||
import BrowserPlatformUtilsService from "../../platform/services/browser-platform-utils.service";
|
|
||||||
import { BrowserStateService } from "../../platform/services/browser-state.service";
|
import { BrowserStateService } from "../../platform/services/browser-state.service";
|
||||||
|
import { BrowserPlatformUtilsService } from "../../platform/services/platform-utils/browser-platform-utils.service";
|
||||||
import { AutofillService } from "../services/abstractions/autofill.service";
|
import { AutofillService } from "../services/abstractions/autofill.service";
|
||||||
import {
|
import {
|
||||||
createAutofillPageDetailsMock,
|
createAutofillPageDetailsMock,
|
||||||
@ -53,6 +55,7 @@ describe("OverlayBackground", () => {
|
|||||||
const autofillSettingsService = mock<AutofillSettingsService>();
|
const autofillSettingsService = mock<AutofillSettingsService>();
|
||||||
const i18nService = mock<I18nService>();
|
const i18nService = mock<I18nService>();
|
||||||
const platformUtilsService = mock<BrowserPlatformUtilsService>();
|
const platformUtilsService = mock<BrowserPlatformUtilsService>();
|
||||||
|
const themeStateService = mock<ThemeStateService>();
|
||||||
const initOverlayElementPorts = async (options = { initList: true, initButton: true }) => {
|
const initOverlayElementPorts = async (options = { initList: true, initButton: true }) => {
|
||||||
const { initList, initButton } = options;
|
const { initList, initButton } = options;
|
||||||
if (initButton) {
|
if (initButton) {
|
||||||
@ -79,12 +82,15 @@ describe("OverlayBackground", () => {
|
|||||||
autofillSettingsService,
|
autofillSettingsService,
|
||||||
i18nService,
|
i18nService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
|
themeStateService,
|
||||||
);
|
);
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(overlayBackground as any, "getOverlayVisibility")
|
.spyOn(overlayBackground as any, "getOverlayVisibility")
|
||||||
.mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus);
|
.mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus);
|
||||||
|
|
||||||
|
themeStateService.selectedTheme$ = of(ThemeType.Light);
|
||||||
|
|
||||||
void overlayBackground.init();
|
void overlayBackground.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -993,7 +999,7 @@ describe("OverlayBackground", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("gets the system theme", async () => {
|
it("gets the system theme", async () => {
|
||||||
jest.spyOn(overlayBackground["stateService"], "getTheme").mockResolvedValue(ThemeType.System);
|
themeStateService.selectedTheme$ = of(ThemeType.System);
|
||||||
|
|
||||||
await initOverlayElementPorts({ initList: true, initButton: false });
|
await initOverlayElementPorts({ initList: true, initButton: false });
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
@ -11,6 +11,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { buildCipherIcon } from "@bitwarden/common/vault/icon/build-cipher-icon";
|
import { buildCipherIcon } from "@bitwarden/common/vault/icon/build-cipher-icon";
|
||||||
@ -96,6 +97,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private themeStateService: ThemeStateService,
|
||||||
) {
|
) {
|
||||||
this.iconsServerUrl = this.environmentService.getIconsUrl();
|
this.iconsServerUrl = this.environmentService.getIconsUrl();
|
||||||
}
|
}
|
||||||
@ -695,7 +697,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
command: `initAutofillOverlay${isOverlayListPort ? "List" : "Button"}`,
|
command: `initAutofillOverlay${isOverlayListPort ? "List" : "Button"}`,
|
||||||
authStatus: await this.getAuthStatus(),
|
authStatus: await this.getAuthStatus(),
|
||||||
styleSheetUrl: chrome.runtime.getURL(`overlay/${isOverlayListPort ? "list" : "button"}.css`),
|
styleSheetUrl: chrome.runtime.getURL(`overlay/${isOverlayListPort ? "list" : "button"}.css`),
|
||||||
theme: await this.stateService.getTheme(),
|
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
|
||||||
translations: this.getTranslations(),
|
translations: this.getTranslations(),
|
||||||
ciphers: isOverlayListPort ? this.getOverlayCipherData() : null,
|
ciphers: isOverlayListPort ? this.getOverlayCipherData() : null,
|
||||||
});
|
});
|
||||||
|
@ -6,10 +6,6 @@ import {
|
|||||||
EventCollectionServiceInitOptions,
|
EventCollectionServiceInitOptions,
|
||||||
eventCollectionServiceFactory,
|
eventCollectionServiceFactory,
|
||||||
} from "../../../background/service-factories/event-collection-service.factory";
|
} from "../../../background/service-factories/event-collection-service.factory";
|
||||||
import {
|
|
||||||
settingsServiceFactory,
|
|
||||||
SettingsServiceInitOptions,
|
|
||||||
} from "../../../background/service-factories/settings-service.factory";
|
|
||||||
import {
|
import {
|
||||||
CachedServices,
|
CachedServices,
|
||||||
factory,
|
factory,
|
||||||
@ -38,6 +34,10 @@ import {
|
|||||||
AutofillSettingsServiceInitOptions,
|
AutofillSettingsServiceInitOptions,
|
||||||
autofillSettingsServiceFactory,
|
autofillSettingsServiceFactory,
|
||||||
} from "./autofill-settings-service.factory";
|
} from "./autofill-settings-service.factory";
|
||||||
|
import {
|
||||||
|
DomainSettingsServiceInitOptions,
|
||||||
|
domainSettingsServiceFactory,
|
||||||
|
} from "./domain-settings-service.factory";
|
||||||
|
|
||||||
type AutoFillServiceOptions = FactoryOptions;
|
type AutoFillServiceOptions = FactoryOptions;
|
||||||
|
|
||||||
@ -48,8 +48,8 @@ export type AutoFillServiceInitOptions = AutoFillServiceOptions &
|
|||||||
TotpServiceInitOptions &
|
TotpServiceInitOptions &
|
||||||
EventCollectionServiceInitOptions &
|
EventCollectionServiceInitOptions &
|
||||||
LogServiceInitOptions &
|
LogServiceInitOptions &
|
||||||
SettingsServiceInitOptions &
|
UserVerificationServiceInitOptions &
|
||||||
UserVerificationServiceInitOptions;
|
DomainSettingsServiceInitOptions;
|
||||||
|
|
||||||
export function autofillServiceFactory(
|
export function autofillServiceFactory(
|
||||||
cache: { autofillService?: AbstractAutoFillService } & CachedServices,
|
cache: { autofillService?: AbstractAutoFillService } & CachedServices,
|
||||||
@ -67,7 +67,7 @@ export function autofillServiceFactory(
|
|||||||
await totpServiceFactory(cache, opts),
|
await totpServiceFactory(cache, opts),
|
||||||
await eventCollectionServiceFactory(cache, opts),
|
await eventCollectionServiceFactory(cache, opts),
|
||||||
await logServiceFactory(cache, opts),
|
await logServiceFactory(cache, opts),
|
||||||
await settingsServiceFactory(cache, opts),
|
await domainSettingsServiceFactory(cache, opts),
|
||||||
await userVerificationServiceFactory(cache, opts),
|
await userVerificationServiceFactory(cache, opts),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
import { DefaultDomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CachedServices,
|
||||||
|
factory,
|
||||||
|
FactoryOptions,
|
||||||
|
} from "../../../platform/background/service-factories/factory-options";
|
||||||
|
import {
|
||||||
|
stateProviderFactory,
|
||||||
|
StateProviderInitOptions,
|
||||||
|
} from "../../../platform/background/service-factories/state-provider.factory";
|
||||||
|
|
||||||
|
export type DomainSettingsServiceInitOptions = FactoryOptions & StateProviderInitOptions;
|
||||||
|
|
||||||
|
export function domainSettingsServiceFactory(
|
||||||
|
cache: { domainSettingsService?: DefaultDomainSettingsService } & CachedServices,
|
||||||
|
opts: DomainSettingsServiceInitOptions,
|
||||||
|
): Promise<DefaultDomainSettingsService> {
|
||||||
|
return factory(
|
||||||
|
cache,
|
||||||
|
"domainSettingsService",
|
||||||
|
opts,
|
||||||
|
async () => new DefaultDomainSettingsService(await stateProviderFactory(cache, opts)),
|
||||||
|
);
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { UriMatchType } from "@bitwarden/common/vault/enums";
|
|
||||||
|
|
||||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ export default class WebRequestBackground {
|
|||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
||||||
domain,
|
domain,
|
||||||
null,
|
null,
|
||||||
UriMatchType.Host,
|
UriMatchStrategy.Host,
|
||||||
);
|
);
|
||||||
if (ciphers == null || ciphers.length !== 1) {
|
if (ciphers == null || ciphers.length !== 1) {
|
||||||
error();
|
error();
|
||||||
|
@ -6,7 +6,7 @@ import AutofillField from "../models/autofill-field";
|
|||||||
import { WatchedForm } from "../models/watched-form";
|
import { WatchedForm } from "../models/watched-form";
|
||||||
import { NotificationBarIframeInitData } from "../notification/abstractions/notification-bar";
|
import { NotificationBarIframeInitData } from "../notification/abstractions/notification-bar";
|
||||||
import { FormData } from "../services/abstractions/autofill.service";
|
import { FormData } from "../services/abstractions/autofill.service";
|
||||||
import { GlobalSettings, UserSettings } from "../types";
|
import { UserSettings } from "../types";
|
||||||
import {
|
import {
|
||||||
getFromLocalStorage,
|
getFromLocalStorage,
|
||||||
sendExtensionMessage,
|
sendExtensionMessage,
|
||||||
@ -94,10 +94,11 @@ async function loadNotificationBar() {
|
|||||||
"bgGetEnableChangedPasswordPrompt",
|
"bgGetEnableChangedPasswordPrompt",
|
||||||
);
|
);
|
||||||
const enableAddedLoginPrompt = await sendExtensionMessage("bgGetEnableAddedLoginPrompt");
|
const enableAddedLoginPrompt = await sendExtensionMessage("bgGetEnableAddedLoginPrompt");
|
||||||
|
const excludedDomains = await sendExtensionMessage("bgGetExcludedDomains");
|
||||||
|
|
||||||
let showNotificationBar = true;
|
let showNotificationBar = true;
|
||||||
// Look up the active user id from storage
|
// Look up the active user id from storage
|
||||||
const activeUserIdKey = "activeUserId";
|
const activeUserIdKey = "activeUserId";
|
||||||
const globalStorageKey = "global";
|
|
||||||
let activeUserId: string;
|
let activeUserId: string;
|
||||||
|
|
||||||
const activeUserStorageValue = await getFromLocalStorage(activeUserIdKey);
|
const activeUserStorageValue = await getFromLocalStorage(activeUserIdKey);
|
||||||
@ -109,9 +110,6 @@ async function loadNotificationBar() {
|
|||||||
const userSettingsStorageValue = await getFromLocalStorage(activeUserId);
|
const userSettingsStorageValue = await getFromLocalStorage(activeUserId);
|
||||||
if (userSettingsStorageValue[activeUserId]) {
|
if (userSettingsStorageValue[activeUserId]) {
|
||||||
const userSettings: UserSettings = userSettingsStorageValue[activeUserId].settings;
|
const userSettings: UserSettings = userSettingsStorageValue[activeUserId].settings;
|
||||||
const globalSettings: GlobalSettings = (await getFromLocalStorage(globalStorageKey))[
|
|
||||||
globalStorageKey
|
|
||||||
];
|
|
||||||
|
|
||||||
// Do not show the notification bar on the Bitwarden vault
|
// Do not show the notification bar on the Bitwarden vault
|
||||||
// because they can add logins and change passwords there
|
// because they can add logins and change passwords there
|
||||||
@ -122,8 +120,8 @@ async function loadNotificationBar() {
|
|||||||
// show the notification bar on (for login detail collection or password change).
|
// show the notification bar on (for login detail collection or password change).
|
||||||
// It is managed in the Settings > Excluded Domains page in the browser extension.
|
// It is managed in the Settings > Excluded Domains page in the browser extension.
|
||||||
// Example: '{"bitwarden.com":null}'
|
// Example: '{"bitwarden.com":null}'
|
||||||
const excludedDomainsDict = globalSettings.neverDomains;
|
|
||||||
if (!excludedDomainsDict || !(window.location.hostname in excludedDomainsDict)) {
|
if (!excludedDomains || !(window.location.hostname in excludedDomains)) {
|
||||||
if (enableAddedLoginPrompt || enableChangedPasswordPrompt) {
|
if (enableAddedLoginPrompt || enableChangedPasswordPrompt) {
|
||||||
// If the user has not disabled both notifications, then handle the initial page change (null -> actual page)
|
// If the user has not disabled both notifications, then handle the initial page change (null -> actual page)
|
||||||
handlePageChange();
|
handlePageChange();
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
|
||||||
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
|
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
|
||||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||||
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
|
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
|
||||||
|
import {
|
||||||
|
UriMatchStrategy,
|
||||||
|
UriMatchStrategySetting,
|
||||||
|
} from "@bitwarden/common/models/domain/domain-service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { UriMatchType } from "@bitwarden/common/vault/enums";
|
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||||
@ -28,16 +30,15 @@ export class AutofillComponent implements OnInit {
|
|||||||
enableAutoFillOnPageLoad = false;
|
enableAutoFillOnPageLoad = false;
|
||||||
autoFillOnPageLoadDefault = false;
|
autoFillOnPageLoadDefault = false;
|
||||||
autoFillOnPageLoadOptions: any[];
|
autoFillOnPageLoadOptions: any[];
|
||||||
defaultUriMatch = UriMatchType.Domain;
|
defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain;
|
||||||
uriMatchOptions: any[];
|
uriMatchOptions: any[];
|
||||||
autofillKeyboardHelperText: string;
|
autofillKeyboardHelperText: string;
|
||||||
accountSwitcherEnabled = false;
|
accountSwitcherEnabled = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private settingsService: SettingsService,
|
private domainSettingsService: DomainSettingsService,
|
||||||
private autofillService: AutofillService,
|
private autofillService: AutofillService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||||
@ -61,12 +62,12 @@ export class AutofillComponent implements OnInit {
|
|||||||
{ name: i18nService.t("autoFillOnPageLoadNo"), value: false },
|
{ name: i18nService.t("autoFillOnPageLoadNo"), value: false },
|
||||||
];
|
];
|
||||||
this.uriMatchOptions = [
|
this.uriMatchOptions = [
|
||||||
{ name: i18nService.t("baseDomain"), value: UriMatchType.Domain },
|
{ name: i18nService.t("baseDomain"), value: UriMatchStrategy.Domain },
|
||||||
{ name: i18nService.t("host"), value: UriMatchType.Host },
|
{ name: i18nService.t("host"), value: UriMatchStrategy.Host },
|
||||||
{ name: i18nService.t("startsWith"), value: UriMatchType.StartsWith },
|
{ name: i18nService.t("startsWith"), value: UriMatchStrategy.StartsWith },
|
||||||
{ name: i18nService.t("regEx"), value: UriMatchType.RegularExpression },
|
{ name: i18nService.t("regEx"), value: UriMatchStrategy.RegularExpression },
|
||||||
{ name: i18nService.t("exact"), value: UriMatchType.Exact },
|
{ name: i18nService.t("exact"), value: UriMatchStrategy.Exact },
|
||||||
{ name: i18nService.t("never"), value: UriMatchType.Never },
|
{ name: i18nService.t("never"), value: UriMatchStrategy.Never },
|
||||||
];
|
];
|
||||||
|
|
||||||
this.accountSwitcherEnabled = enableAccountSwitching();
|
this.accountSwitcherEnabled = enableAccountSwitching();
|
||||||
@ -94,8 +95,10 @@ export class AutofillComponent implements OnInit {
|
|||||||
this.autofillSettingsService.autofillOnPageLoadDefault$,
|
this.autofillSettingsService.autofillOnPageLoadDefault$,
|
||||||
);
|
);
|
||||||
|
|
||||||
const defaultUriMatch = await this.stateService.getDefaultUriMatch();
|
const defaultUriMatch = await firstValueFrom(
|
||||||
this.defaultUriMatch = defaultUriMatch == null ? UriMatchType.Domain : defaultUriMatch;
|
this.domainSettingsService.defaultUriMatchStrategy$,
|
||||||
|
);
|
||||||
|
this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch;
|
||||||
|
|
||||||
const command = await this.platformUtilsService.getAutofillKeyboardShortcut();
|
const command = await this.platformUtilsService.getAutofillKeyboardShortcut();
|
||||||
await this.setAutofillKeyboardHelperText(command);
|
await this.setAutofillKeyboardHelperText(command);
|
||||||
@ -119,7 +122,7 @@ export class AutofillComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveDefaultUriMatch() {
|
async saveDefaultUriMatch() {
|
||||||
await this.stateService.setDefaultUriMatch(this.defaultUriMatch);
|
await this.domainSettingsService.setDefaultUriMatchStrategy(this.defaultUriMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setAutofillKeyboardHelperText(command: string) {
|
private async setAutofillKeyboardHelperText(command: string) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { UriMatchType, CipherType } from "@bitwarden/common/vault/enums";
|
import { UriMatchStrategySetting } from "@bitwarden/common/models/domain/domain-service";
|
||||||
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
import AutofillField from "../../models/autofill-field";
|
import AutofillField from "../../models/autofill-field";
|
||||||
@ -40,7 +41,7 @@ export interface GenerateFillScriptOptions {
|
|||||||
allowTotpAutofill: boolean;
|
allowTotpAutofill: boolean;
|
||||||
cipher: CipherView;
|
cipher: CipherView;
|
||||||
tabUrl: string;
|
tabUrl: string;
|
||||||
defaultUriMatch: UriMatchType;
|
defaultUriMatch: UriMatchStrategySetting;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class AutofillService {
|
export abstract class AutofillService {
|
||||||
|
@ -1,19 +1,25 @@
|
|||||||
import { mock, mockReset } from "jest-mock-extended";
|
import { mock, mockReset } from "jest-mock-extended";
|
||||||
|
import { of } from "rxjs";
|
||||||
|
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
|
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
|
||||||
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
|
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
|
||||||
import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
|
||||||
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
|
|
||||||
import { SettingsService } from "@bitwarden/common/services/settings.service";
|
|
||||||
import {
|
import {
|
||||||
FieldType,
|
DefaultDomainSettingsService,
|
||||||
LinkedIdType,
|
DomainSettingsService,
|
||||||
LoginLinkedId,
|
} from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
UriMatchType,
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
CipherType,
|
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
|
||||||
} from "@bitwarden/common/vault/enums";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
|
||||||
|
import {
|
||||||
|
FakeStateProvider,
|
||||||
|
FakeAccountService,
|
||||||
|
mockAccountServiceWith,
|
||||||
|
} from "@bitwarden/common/spec";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
import { FieldType, LinkedIdType, LoginLinkedId, CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||||
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@ -47,15 +53,24 @@ import {
|
|||||||
import { AutoFillConstants, IdentityAutoFillConstants } from "./autofill-constants";
|
import { AutoFillConstants, IdentityAutoFillConstants } from "./autofill-constants";
|
||||||
import AutofillService from "./autofill.service";
|
import AutofillService from "./autofill.service";
|
||||||
|
|
||||||
|
const mockEquivalentDomains = [
|
||||||
|
["example.com", "exampleapp.com", "example.co.uk", "ejemplo.es"],
|
||||||
|
["bitwarden.com", "bitwarden.co.uk", "sm-bitwarden.com"],
|
||||||
|
["example.co.uk", "exampleapp.co.uk"],
|
||||||
|
];
|
||||||
|
|
||||||
describe("AutofillService", () => {
|
describe("AutofillService", () => {
|
||||||
let autofillService: AutofillService;
|
let autofillService: AutofillService;
|
||||||
const cipherService = mock<CipherService>();
|
const cipherService = mock<CipherService>();
|
||||||
const stateService = mock<BrowserStateService>();
|
const stateService = mock<BrowserStateService>();
|
||||||
const autofillSettingsService = mock<AutofillSettingsService>();
|
const autofillSettingsService = mock<AutofillSettingsService>();
|
||||||
|
const mockUserId = Utils.newGuid() as UserId;
|
||||||
|
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
||||||
|
const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService);
|
||||||
|
let domainSettingsService: DomainSettingsService;
|
||||||
const totpService = mock<TotpService>();
|
const totpService = mock<TotpService>();
|
||||||
const eventCollectionService = mock<EventCollectionService>();
|
const eventCollectionService = mock<EventCollectionService>();
|
||||||
const logService = mock<LogService>();
|
const logService = mock<LogService>();
|
||||||
const settingsService = mock<SettingsService>();
|
|
||||||
const userVerificationService = mock<UserVerificationService>();
|
const userVerificationService = mock<UserVerificationService>();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -66,9 +81,12 @@ describe("AutofillService", () => {
|
|||||||
totpService,
|
totpService,
|
||||||
eventCollectionService,
|
eventCollectionService,
|
||||||
logService,
|
logService,
|
||||||
settingsService,
|
domainSettingsService,
|
||||||
userVerificationService,
|
userVerificationService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
|
||||||
|
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -407,6 +425,8 @@ describe("AutofillService", () => {
|
|||||||
autofillOptions.cipher.login.matchesUri = jest.fn().mockReturnValue(true);
|
autofillOptions.cipher.login.matchesUri = jest.fn().mockReturnValue(true);
|
||||||
autofillOptions.cipher.login.username = "username";
|
autofillOptions.cipher.login.username = "username";
|
||||||
autofillOptions.cipher.login.password = "password";
|
autofillOptions.cipher.login.password = "password";
|
||||||
|
|
||||||
|
jest.spyOn(autofillService, "getDefaultUriMatchStrategy").mockResolvedValue(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("given a set of autofill options that are incomplete", () => {
|
describe("given a set of autofill options that are incomplete", () => {
|
||||||
@ -468,7 +488,6 @@ describe("AutofillService", () => {
|
|||||||
|
|
||||||
it("will autofill login data for a page", async () => {
|
it("will autofill login data for a page", async () => {
|
||||||
jest.spyOn(stateService, "getCanAccessPremium");
|
jest.spyOn(stateService, "getCanAccessPremium");
|
||||||
jest.spyOn(stateService, "getDefaultUriMatch");
|
|
||||||
jest.spyOn(autofillService as any, "generateFillScript");
|
jest.spyOn(autofillService as any, "generateFillScript");
|
||||||
jest.spyOn(autofillService as any, "generateLoginFillScript");
|
jest.spyOn(autofillService as any, "generateLoginFillScript");
|
||||||
jest.spyOn(logService, "info");
|
jest.spyOn(logService, "info");
|
||||||
@ -479,7 +498,7 @@ describe("AutofillService", () => {
|
|||||||
|
|
||||||
const currentAutofillPageDetails = autofillOptions.pageDetails[0];
|
const currentAutofillPageDetails = autofillOptions.pageDetails[0];
|
||||||
expect(stateService.getCanAccessPremium).toHaveBeenCalled();
|
expect(stateService.getCanAccessPremium).toHaveBeenCalled();
|
||||||
expect(stateService.getDefaultUriMatch).toHaveBeenCalled();
|
expect(autofillService["getDefaultUriMatchStrategy"]).toHaveBeenCalled();
|
||||||
expect(autofillService["generateFillScript"]).toHaveBeenCalledWith(
|
expect(autofillService["generateFillScript"]).toHaveBeenCalledWith(
|
||||||
currentAutofillPageDetails.details,
|
currentAutofillPageDetails.details,
|
||||||
{
|
{
|
||||||
@ -1488,7 +1507,7 @@ describe("AutofillService", () => {
|
|||||||
};
|
};
|
||||||
defaultLoginUriView = mock<LoginUriView>({
|
defaultLoginUriView = mock<LoginUriView>({
|
||||||
uri: "https://www.example.com",
|
uri: "https://www.example.com",
|
||||||
match: UriMatchType.Domain,
|
match: UriMatchStrategy.Domain,
|
||||||
});
|
});
|
||||||
options = createGenerateFillScriptOptionsMock();
|
options = createGenerateFillScriptOptionsMock();
|
||||||
options.cipher.login = mock<LoginView>({
|
options.cipher.login = mock<LoginView>({
|
||||||
@ -1559,13 +1578,13 @@ describe("AutofillService", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("skips adding any login uri views that have a UriMatchType of Never to the list of saved urls", async () => {
|
it("skips adding any login uri views that have a UriMatchStrategySetting of Never to the list of saved urls", async () => {
|
||||||
const secondUriView = mock<LoginUriView>({
|
const secondUriView = mock<LoginUriView>({
|
||||||
uri: "https://www.second-example.com",
|
uri: "https://www.second-example.com",
|
||||||
});
|
});
|
||||||
const thirdUriView = mock<LoginUriView>({
|
const thirdUriView = mock<LoginUriView>({
|
||||||
uri: "https://www.third-example.com",
|
uri: "https://www.third-example.com",
|
||||||
match: UriMatchType.Never,
|
match: UriMatchStrategy.Never,
|
||||||
});
|
});
|
||||||
options.cipher.login.uris = [defaultLoginUriView, secondUriView, thirdUriView];
|
options.cipher.login.uris = [defaultLoginUriView, secondUriView, thirdUriView];
|
||||||
|
|
||||||
@ -2752,31 +2771,32 @@ describe("AutofillService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("inUntrustedIframe", () => {
|
describe("inUntrustedIframe", () => {
|
||||||
it("returns a false value if the passed pageUrl is equal to the options tabUrl", () => {
|
it("returns a false value if the passed pageUrl is equal to the options tabUrl", async () => {
|
||||||
const pageUrl = "https://www.example.com";
|
const pageUrl = "https://www.example.com";
|
||||||
const tabUrl = "https://www.example.com";
|
const tabUrl = "https://www.example.com";
|
||||||
const generateFillScriptOptions = createGenerateFillScriptOptionsMock({ tabUrl });
|
const generateFillScriptOptions = createGenerateFillScriptOptionsMock({ tabUrl });
|
||||||
generateFillScriptOptions.cipher.login.matchesUri = jest.fn().mockReturnValueOnce(true);
|
generateFillScriptOptions.cipher.login.matchesUri = jest.fn().mockReturnValueOnce(true);
|
||||||
jest.spyOn(settingsService, "getEquivalentDomains");
|
|
||||||
|
|
||||||
const result = autofillService["inUntrustedIframe"](pageUrl, generateFillScriptOptions);
|
const result = await autofillService["inUntrustedIframe"](pageUrl, generateFillScriptOptions);
|
||||||
|
|
||||||
expect(settingsService.getEquivalentDomains).not.toHaveBeenCalled();
|
|
||||||
expect(generateFillScriptOptions.cipher.login.matchesUri).not.toHaveBeenCalled();
|
expect(generateFillScriptOptions.cipher.login.matchesUri).not.toHaveBeenCalled();
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a false value if the passed pageUrl matches the domain of the tabUrl", () => {
|
it("returns a false value if the passed pageUrl matches the domain of the tabUrl", async () => {
|
||||||
const pageUrl = "https://subdomain.example.com";
|
const pageUrl = "https://subdomain.example.com";
|
||||||
const tabUrl = "https://www.example.com";
|
const tabUrl = "https://www.example.com";
|
||||||
const equivalentDomains = new Set(["example.com"]);
|
const equivalentDomains = new Set([
|
||||||
|
"ejemplo.es",
|
||||||
|
"example.co.uk",
|
||||||
|
"example.com",
|
||||||
|
"exampleapp.com",
|
||||||
|
]);
|
||||||
const generateFillScriptOptions = createGenerateFillScriptOptionsMock({ tabUrl });
|
const generateFillScriptOptions = createGenerateFillScriptOptionsMock({ tabUrl });
|
||||||
generateFillScriptOptions.cipher.login.matchesUri = jest.fn().mockReturnValueOnce(true);
|
generateFillScriptOptions.cipher.login.matchesUri = jest.fn().mockReturnValueOnce(true);
|
||||||
jest.spyOn(settingsService as any, "getEquivalentDomains").mockReturnValue(equivalentDomains);
|
|
||||||
|
|
||||||
const result = autofillService["inUntrustedIframe"](pageUrl, generateFillScriptOptions);
|
const result = await autofillService["inUntrustedIframe"](pageUrl, generateFillScriptOptions);
|
||||||
|
|
||||||
expect(settingsService.getEquivalentDomains).toHaveBeenCalledWith(pageUrl);
|
|
||||||
expect(generateFillScriptOptions.cipher.login.matchesUri).toHaveBeenCalledWith(
|
expect(generateFillScriptOptions.cipher.login.matchesUri).toHaveBeenCalledWith(
|
||||||
pageUrl,
|
pageUrl,
|
||||||
equivalentDomains,
|
equivalentDomains,
|
||||||
@ -2785,17 +2805,21 @@ describe("AutofillService", () => {
|
|||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a true value if the passed pageUrl does not match the domain of the tabUrl", () => {
|
it("returns a true value if the passed pageUrl does not match the domain of the tabUrl", async () => {
|
||||||
|
const equivalentDomains = new Set([
|
||||||
|
"ejemplo.es",
|
||||||
|
"example.co.uk",
|
||||||
|
"example.com",
|
||||||
|
"exampleapp.com",
|
||||||
|
]);
|
||||||
|
domainSettingsService.equivalentDomains$ = of([["not-example.com"]]);
|
||||||
const pageUrl = "https://subdomain.example.com";
|
const pageUrl = "https://subdomain.example.com";
|
||||||
const tabUrl = "https://www.not-example.com";
|
const tabUrl = "https://www.not-example.com";
|
||||||
const equivalentDomains = new Set(["not-example.com"]);
|
|
||||||
const generateFillScriptOptions = createGenerateFillScriptOptionsMock({ tabUrl });
|
const generateFillScriptOptions = createGenerateFillScriptOptionsMock({ tabUrl });
|
||||||
generateFillScriptOptions.cipher.login.matchesUri = jest.fn().mockReturnValueOnce(false);
|
generateFillScriptOptions.cipher.login.matchesUri = jest.fn().mockReturnValueOnce(false);
|
||||||
jest.spyOn(settingsService as any, "getEquivalentDomains").mockReturnValue(equivalentDomains);
|
|
||||||
|
|
||||||
const result = autofillService["inUntrustedIframe"](pageUrl, generateFillScriptOptions);
|
const result = await autofillService["inUntrustedIframe"](pageUrl, generateFillScriptOptions);
|
||||||
|
|
||||||
expect(settingsService.getEquivalentDomains).toHaveBeenCalledWith(pageUrl);
|
|
||||||
expect(generateFillScriptOptions.cipher.login.matchesUri).toHaveBeenCalledWith(
|
expect(generateFillScriptOptions.cipher.login.matchesUri).toHaveBeenCalledWith(
|
||||||
pageUrl,
|
pageUrl,
|
||||||
equivalentDomains,
|
equivalentDomains,
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||||
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
|
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
|
import {
|
||||||
|
UriMatchStrategySetting,
|
||||||
|
UriMatchStrategy,
|
||||||
|
} from "@bitwarden/common/models/domain/domain-service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||||
import { FieldType, UriMatchType, CipherType } from "@bitwarden/common/vault/enums";
|
import { FieldType, CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
|
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
|
||||||
@ -48,7 +52,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
private totpService: TotpService,
|
private totpService: TotpService,
|
||||||
private eventCollectionService: EventCollectionService,
|
private eventCollectionService: EventCollectionService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private settingsService: SettingsService,
|
private domainSettingsService: DomainSettingsService,
|
||||||
private userVerificationService: UserVerificationService,
|
private userVerificationService: UserVerificationService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -215,6 +219,13 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
return await firstValueFrom(this.autofillSettingsService.autofillOnPageLoad$);
|
return await firstValueFrom(this.autofillSettingsService.autofillOnPageLoad$);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the default URI match strategy setting from the domain settings service.
|
||||||
|
*/
|
||||||
|
async getDefaultUriMatchStrategy(): Promise<UriMatchStrategySetting> {
|
||||||
|
return await firstValueFrom(this.domainSettingsService.defaultUriMatchStrategy$);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Autofill a given tab with a given login item
|
* Autofill a given tab with a given login item
|
||||||
* @param {AutoFillOptions} options Instructions about the autofill operation, including tab and login item
|
* @param {AutoFillOptions} options Instructions about the autofill operation, including tab and login item
|
||||||
@ -229,7 +240,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
let totp: string | null = null;
|
let totp: string | null = null;
|
||||||
|
|
||||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||||
const defaultUriMatch = (await this.stateService.getDefaultUriMatch()) ?? UriMatchType.Domain;
|
const defaultUriMatch = await this.getDefaultUriMatchStrategy();
|
||||||
|
|
||||||
if (!canAccessPremium) {
|
if (!canAccessPremium) {
|
||||||
options.cipher.login.totp = null;
|
options.cipher.login.totp = null;
|
||||||
@ -579,9 +590,9 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
let totp: AutofillField = null;
|
let totp: AutofillField = null;
|
||||||
const login = options.cipher.login;
|
const login = options.cipher.login;
|
||||||
fillScript.savedUrls =
|
fillScript.savedUrls =
|
||||||
login?.uris?.filter((u) => u.match != UriMatchType.Never).map((u) => u.uri) ?? [];
|
login?.uris?.filter((u) => u.match != UriMatchStrategy.Never).map((u) => u.uri) ?? [];
|
||||||
|
|
||||||
fillScript.untrustedIframe = this.inUntrustedIframe(pageDetails.url, options);
|
fillScript.untrustedIframe = await this.inUntrustedIframe(pageDetails.url, options);
|
||||||
|
|
||||||
let passwordFields = AutofillService.loadPasswordFields(
|
let passwordFields = AutofillService.loadPasswordFields(
|
||||||
pageDetails,
|
pageDetails,
|
||||||
@ -1066,7 +1077,10 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
* @returns {boolean} `true` if the iframe is untrusted and a warning should be shown, `false` otherwise
|
* @returns {boolean} `true` if the iframe is untrusted and a warning should be shown, `false` otherwise
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private inUntrustedIframe(pageUrl: string, options: GenerateFillScriptOptions): boolean {
|
private async inUntrustedIframe(
|
||||||
|
pageUrl: string,
|
||||||
|
options: GenerateFillScriptOptions,
|
||||||
|
): Promise<boolean> {
|
||||||
// If the pageUrl (from the content script) matches the tabUrl (from the sender tab), we are not in an iframe
|
// If the pageUrl (from the content script) matches the tabUrl (from the sender tab), we are not in an iframe
|
||||||
// This also avoids a false positive if no URI is saved and the user triggers auto-fill anyway
|
// This also avoids a false positive if no URI is saved and the user triggers auto-fill anyway
|
||||||
if (pageUrl === options.tabUrl) {
|
if (pageUrl === options.tabUrl) {
|
||||||
@ -1076,7 +1090,9 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
// Check the pageUrl against cipher URIs using the configured match detection.
|
// Check the pageUrl against cipher URIs using the configured match detection.
|
||||||
// Remember: if we are in this function, the tabUrl already matches a saved URI for the login.
|
// Remember: if we are in this function, the tabUrl already matches a saved URI for the login.
|
||||||
// We need to verify the pageUrl also matches.
|
// We need to verify the pageUrl also matches.
|
||||||
const equivalentDomains = this.settingsService.getEquivalentDomains(pageUrl);
|
const equivalentDomains = await firstValueFrom(
|
||||||
|
this.domainSettingsService.getUrlEquivalentDomains(pageUrl),
|
||||||
|
);
|
||||||
const matchesUri = options.cipher.login.matchesUri(
|
const matchesUri = options.cipher.login.matchesUri(
|
||||||
pageUrl,
|
pageUrl,
|
||||||
equivalentDomains,
|
equivalentDomains,
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
|
||||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||||
import { UriMatchType, CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
@ -112,7 +113,7 @@ function createGenerateFillScriptOptionsMock(customFields = {}): GenerateFillScr
|
|||||||
allowTotpAutofill: false,
|
allowTotpAutofill: false,
|
||||||
cipher: mock<CipherView>(),
|
cipher: mock<CipherView>(),
|
||||||
tabUrl: "https://jest-testing-website.com",
|
tabUrl: "https://jest-testing-website.com",
|
||||||
defaultUriMatch: UriMatchType.Domain,
|
defaultUriMatch: UriMatchStrategy.Domain,
|
||||||
...customFields,
|
...customFields,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Region } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { Region } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
|
||||||
import { VaultTimeoutAction } from "@bitwarden/common/src/enums/vault-timeout-action.enum";
|
import { VaultTimeoutAction } from "@bitwarden/common/src/enums/vault-timeout-action.enum";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
|
|
||||||
@ -32,15 +31,10 @@ export type UserSettings = {
|
|||||||
utcDate: string;
|
utcDate: string;
|
||||||
version: string;
|
version: string;
|
||||||
};
|
};
|
||||||
settings: {
|
|
||||||
equivalentDomains: string[][];
|
|
||||||
};
|
|
||||||
vaultTimeout: number;
|
vaultTimeout: number;
|
||||||
vaultTimeoutAction: VaultTimeoutAction;
|
vaultTimeoutAction: VaultTimeoutAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GlobalSettings = Pick<GlobalState, "neverDomains">;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A HTMLElement (usually a form element) with additional custom properties added by this script
|
* A HTMLElement (usually a form element) with additional custom properties added by this script
|
||||||
*/
|
*/
|
||||||
|
@ -56,6 +56,10 @@ import {
|
|||||||
BadgeSettingsServiceAbstraction,
|
BadgeSettingsServiceAbstraction,
|
||||||
BadgeSettingsService,
|
BadgeSettingsService,
|
||||||
} from "@bitwarden/common/autofill/services/badge-settings.service";
|
} from "@bitwarden/common/autofill/services/badge-settings.service";
|
||||||
|
import {
|
||||||
|
DomainSettingsService,
|
||||||
|
DefaultDomainSettingsService,
|
||||||
|
} from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import {
|
import {
|
||||||
UserNotificationSettingsService,
|
UserNotificationSettingsService,
|
||||||
UserNotificationSettingsServiceAbstraction,
|
UserNotificationSettingsServiceAbstraction,
|
||||||
@ -112,6 +116,7 @@ import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state
|
|||||||
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
||||||
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
||||||
/* eslint-enable import/no-restricted-paths */
|
/* eslint-enable import/no-restricted-paths */
|
||||||
|
import { DefaultThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
|
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
|
||||||
import { ApiService } from "@bitwarden/common/services/api.service";
|
import { ApiService } from "@bitwarden/common/services/api.service";
|
||||||
import { AuditService } from "@bitwarden/common/services/audit.service";
|
import { AuditService } from "@bitwarden/common/services/audit.service";
|
||||||
@ -195,13 +200,14 @@ import { BrowserStateService as StateServiceAbstraction } from "../platform/serv
|
|||||||
import { BrowserConfigService } from "../platform/services/browser-config.service";
|
import { BrowserConfigService } from "../platform/services/browser-config.service";
|
||||||
import { BrowserCryptoService } from "../platform/services/browser-crypto.service";
|
import { BrowserCryptoService } from "../platform/services/browser-crypto.service";
|
||||||
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
||||||
import { BrowserI18nService } from "../platform/services/browser-i18n.service";
|
|
||||||
import BrowserLocalStorageService from "../platform/services/browser-local-storage.service";
|
import BrowserLocalStorageService from "../platform/services/browser-local-storage.service";
|
||||||
import BrowserMessagingPrivateModeBackgroundService from "../platform/services/browser-messaging-private-mode-background.service";
|
import BrowserMessagingPrivateModeBackgroundService from "../platform/services/browser-messaging-private-mode-background.service";
|
||||||
import BrowserMessagingService from "../platform/services/browser-messaging.service";
|
import BrowserMessagingService from "../platform/services/browser-messaging.service";
|
||||||
import BrowserPlatformUtilsService from "../platform/services/browser-platform-utils.service";
|
|
||||||
import { BrowserStateService } from "../platform/services/browser-state.service";
|
import { BrowserStateService } from "../platform/services/browser-state.service";
|
||||||
|
import I18nService from "../platform/services/i18n.service";
|
||||||
import { LocalBackedSessionStorageService } from "../platform/services/local-backed-session-storage.service";
|
import { LocalBackedSessionStorageService } from "../platform/services/local-backed-session-storage.service";
|
||||||
|
import { BackgroundPlatformUtilsService } from "../platform/services/platform-utils/background-platform-utils.service";
|
||||||
|
import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service";
|
||||||
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
|
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
|
||||||
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
||||||
import { BrowserSendService } from "../services/browser-send.service";
|
import { BrowserSendService } from "../services/browser-send.service";
|
||||||
@ -258,6 +264,7 @@ export default class MainBackground {
|
|||||||
userNotificationSettingsService: UserNotificationSettingsServiceAbstraction;
|
userNotificationSettingsService: UserNotificationSettingsServiceAbstraction;
|
||||||
autofillSettingsService: AutofillSettingsServiceAbstraction;
|
autofillSettingsService: AutofillSettingsServiceAbstraction;
|
||||||
badgeSettingsService: BadgeSettingsServiceAbstraction;
|
badgeSettingsService: BadgeSettingsServiceAbstraction;
|
||||||
|
domainSettingsService: DomainSettingsService;
|
||||||
systemService: SystemServiceAbstraction;
|
systemService: SystemServiceAbstraction;
|
||||||
eventCollectionService: EventCollectionServiceAbstraction;
|
eventCollectionService: EventCollectionServiceAbstraction;
|
||||||
eventUploadService: EventUploadServiceAbstraction;
|
eventUploadService: EventUploadServiceAbstraction;
|
||||||
@ -352,7 +359,7 @@ export default class MainBackground {
|
|||||||
this.cryptoFunctionService = new WebCryptoFunctionService(self);
|
this.cryptoFunctionService = new WebCryptoFunctionService(self);
|
||||||
this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService);
|
this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService);
|
||||||
this.storageService = new BrowserLocalStorageService();
|
this.storageService = new BrowserLocalStorageService();
|
||||||
this.secureStorageService = new BrowserLocalStorageService();
|
this.secureStorageService = this.storageService; // secure storage is not supported in browsers, so we use local storage and warn users when it is used
|
||||||
this.memoryStorageService = BrowserApi.isManifestVersion(3)
|
this.memoryStorageService = BrowserApi.isManifestVersion(3)
|
||||||
? new LocalBackedSessionStorageService(
|
? new LocalBackedSessionStorageService(
|
||||||
new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false),
|
new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false),
|
||||||
@ -438,31 +445,16 @@ export default class MainBackground {
|
|||||||
migrationRunner,
|
migrationRunner,
|
||||||
);
|
);
|
||||||
this.userNotificationSettingsService = new UserNotificationSettingsService(this.stateProvider);
|
this.userNotificationSettingsService = new UserNotificationSettingsService(this.stateProvider);
|
||||||
this.platformUtilsService = new BrowserPlatformUtilsService(
|
this.platformUtilsService = new BackgroundPlatformUtilsService(
|
||||||
this.messagingService,
|
this.messagingService,
|
||||||
(clipboardValue, clearMs) => {
|
(clipboardValue, clearMs) => this.clearClipboard(clipboardValue, clearMs),
|
||||||
if (this.systemService != null) {
|
async () => this.biometricUnlock(),
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
this.systemService.clearClipboard(clipboardValue, clearMs);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
if (this.nativeMessagingBackground != null) {
|
|
||||||
const promise = this.nativeMessagingBackground.getResponse();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.nativeMessagingBackground.send({ command: "biometricUnlock" });
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise.then((result) => result.response === "unlocked");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
self,
|
self,
|
||||||
);
|
);
|
||||||
this.i18nService = new BrowserI18nService(BrowserApi.getUILanguage(), this.stateService);
|
|
||||||
|
const themeStateService = new DefaultThemeStateService(this.globalStateProvider);
|
||||||
|
|
||||||
|
this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider);
|
||||||
this.cryptoService = new BrowserCryptoService(
|
this.cryptoService = new BrowserCryptoService(
|
||||||
this.keyGenerationService,
|
this.keyGenerationService,
|
||||||
this.cryptoFunctionService,
|
this.cryptoFunctionService,
|
||||||
@ -475,7 +467,7 @@ export default class MainBackground {
|
|||||||
this.biometricStateService,
|
this.biometricStateService,
|
||||||
);
|
);
|
||||||
this.tokenService = new TokenService(this.stateService);
|
this.tokenService = new TokenService(this.stateService);
|
||||||
this.appIdService = new AppIdService(this.storageService);
|
this.appIdService = new AppIdService(this.globalStateProvider);
|
||||||
this.apiService = new ApiService(
|
this.apiService = new ApiService(
|
||||||
this.tokenService,
|
this.tokenService,
|
||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
@ -483,6 +475,7 @@ export default class MainBackground {
|
|||||||
this.appIdService,
|
this.appIdService,
|
||||||
(expired: boolean) => this.logout(expired),
|
(expired: boolean) => this.logout(expired),
|
||||||
);
|
);
|
||||||
|
this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider);
|
||||||
this.settingsService = new BrowserSettingsService(this.stateService);
|
this.settingsService = new BrowserSettingsService(this.stateService);
|
||||||
this.fileUploadService = new FileUploadService(this.logService);
|
this.fileUploadService = new FileUploadService(this.logService);
|
||||||
this.cipherFileUploadService = new CipherFileUploadService(
|
this.cipherFileUploadService = new CipherFileUploadService(
|
||||||
@ -588,6 +581,7 @@ export default class MainBackground {
|
|||||||
this.policyService,
|
this.policyService,
|
||||||
this.deviceTrustCryptoService,
|
this.deviceTrustCryptoService,
|
||||||
this.authRequestService,
|
this.authRequestService,
|
||||||
|
this.globalStateProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.ssoLoginService = new SsoLoginService(this.stateProvider);
|
this.ssoLoginService = new SsoLoginService(this.stateProvider);
|
||||||
@ -607,7 +601,7 @@ export default class MainBackground {
|
|||||||
|
|
||||||
this.cipherService = new CipherService(
|
this.cipherService = new CipherService(
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
this.settingsService,
|
this.domainSettingsService,
|
||||||
this.apiService,
|
this.apiService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.searchService,
|
this.searchService,
|
||||||
@ -694,7 +688,7 @@ export default class MainBackground {
|
|||||||
this.providerService = new ProviderService(this.stateProvider);
|
this.providerService = new ProviderService(this.stateProvider);
|
||||||
this.syncService = new SyncService(
|
this.syncService = new SyncService(
|
||||||
this.apiService,
|
this.apiService,
|
||||||
this.settingsService,
|
this.domainSettingsService,
|
||||||
this.folderService,
|
this.folderService,
|
||||||
this.cipherService,
|
this.cipherService,
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
@ -731,7 +725,7 @@ export default class MainBackground {
|
|||||||
this.totpService,
|
this.totpService,
|
||||||
this.eventCollectionService,
|
this.eventCollectionService,
|
||||||
this.logService,
|
this.logService,
|
||||||
this.settingsService,
|
this.domainSettingsService,
|
||||||
this.userVerificationService,
|
this.userVerificationService,
|
||||||
);
|
);
|
||||||
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
|
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
|
||||||
@ -795,6 +789,7 @@ export default class MainBackground {
|
|||||||
this.authService,
|
this.authService,
|
||||||
this.stateService,
|
this.stateService,
|
||||||
this.vaultSettingsService,
|
this.vaultSettingsService,
|
||||||
|
this.domainSettingsService,
|
||||||
this.logService,
|
this.logService,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -864,8 +859,10 @@ export default class MainBackground {
|
|||||||
this.folderService,
|
this.folderService,
|
||||||
this.stateService,
|
this.stateService,
|
||||||
this.userNotificationSettingsService,
|
this.userNotificationSettingsService,
|
||||||
|
this.domainSettingsService,
|
||||||
this.environmentService,
|
this.environmentService,
|
||||||
this.logService,
|
this.logService,
|
||||||
|
themeStateService,
|
||||||
);
|
);
|
||||||
this.overlayBackground = new OverlayBackground(
|
this.overlayBackground = new OverlayBackground(
|
||||||
this.cipherService,
|
this.cipherService,
|
||||||
@ -877,6 +874,7 @@ export default class MainBackground {
|
|||||||
this.autofillSettingsService,
|
this.autofillSettingsService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
|
themeStateService,
|
||||||
);
|
);
|
||||||
this.filelessImporterBackground = new FilelessImporterBackground(
|
this.filelessImporterBackground = new FilelessImporterBackground(
|
||||||
this.configService,
|
this.configService,
|
||||||
@ -969,7 +967,7 @@ export default class MainBackground {
|
|||||||
await this.stateService.init();
|
await this.stateService.init();
|
||||||
|
|
||||||
await this.vaultTimeoutService.init(true);
|
await this.vaultTimeoutService.init(true);
|
||||||
await (this.i18nService as BrowserI18nService).init();
|
await (this.i18nService as I18nService).init();
|
||||||
await (this.eventUploadService as EventUploadService).init(true);
|
await (this.eventUploadService as EventUploadService).init(true);
|
||||||
await this.runtimeBackground.init();
|
await this.runtimeBackground.init();
|
||||||
await this.notificationBackground.init();
|
await this.notificationBackground.init();
|
||||||
@ -1097,7 +1095,6 @@ export default class MainBackground {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.syncService.setLastSync(new Date(0), userId),
|
this.syncService.setLastSync(new Date(0), userId),
|
||||||
this.cryptoService.clearKeys(userId),
|
this.cryptoService.clearKeys(userId),
|
||||||
this.settingsService.clear(userId),
|
|
||||||
this.cipherService.clear(userId),
|
this.cipherService.clear(userId),
|
||||||
this.folderService.clear(userId),
|
this.folderService.clear(userId),
|
||||||
this.collectionService.clear(userId),
|
this.collectionService.clear(userId),
|
||||||
@ -1222,6 +1219,23 @@ export default class MainBackground {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async clearClipboard(clipboardValue: string, clearMs: number) {
|
||||||
|
if (this.systemService != null) {
|
||||||
|
await this.systemService.clearClipboard(clipboardValue, clearMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async biometricUnlock(): Promise<boolean> {
|
||||||
|
if (this.nativeMessagingBackground == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responsePromise = this.nativeMessagingBackground.getResponse();
|
||||||
|
await this.nativeMessagingBackground.send({ command: "biometricUnlock" });
|
||||||
|
const response = await responsePromise;
|
||||||
|
return response.response === "unlocked";
|
||||||
|
}
|
||||||
|
|
||||||
private async fullSync(override = false) {
|
private async fullSync(override = false) {
|
||||||
const syncInternal = 6 * 60 * 60 * 1000; // 6 hours
|
const syncInternal = 6 * 60 * 60 * 1000; // 6 hours
|
||||||
const lastSync = await this.syncService.getLastSync();
|
const lastSync = await this.syncService.getLastSync();
|
||||||
|
@ -122,7 +122,7 @@ export class NativeMessagingBackground {
|
|||||||
break;
|
break;
|
||||||
case "disconnected":
|
case "disconnected":
|
||||||
if (this.connecting) {
|
if (this.connecting) {
|
||||||
reject("startDesktop");
|
reject(new Error("startDesktop"));
|
||||||
}
|
}
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
this.port.disconnect();
|
this.port.disconnect();
|
||||||
@ -203,7 +203,7 @@ export class NativeMessagingBackground {
|
|||||||
this.connected = false;
|
this.connected = false;
|
||||||
|
|
||||||
const reason = error != null ? "desktopIntegrationDisabled" : null;
|
const reason = error != null ? "desktopIntegrationDisabled" : null;
|
||||||
reject(reason);
|
reject(new Error(reason));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import { AutofillService } from "../autofill/services/abstractions/autofill.serv
|
|||||||
import { BrowserApi } from "../platform/browser/browser-api";
|
import { BrowserApi } from "../platform/browser/browser-api";
|
||||||
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
|
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
|
||||||
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
||||||
import BrowserPlatformUtilsService from "../platform/services/browser-platform-utils.service";
|
import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service";
|
||||||
import { AbortManager } from "../vault/background/abort-manager";
|
import { AbortManager } from "../vault/background/abort-manager";
|
||||||
import { Fido2Service } from "../vault/services/abstractions/fido2.service";
|
import { Fido2Service } from "../vault/services/abstractions/fido2.service";
|
||||||
|
|
||||||
@ -68,6 +68,7 @@ export default class RuntimeBackground {
|
|||||||
"checkFido2FeatureEnabled",
|
"checkFido2FeatureEnabled",
|
||||||
"fido2RegisterCredentialRequest",
|
"fido2RegisterCredentialRequest",
|
||||||
"fido2GetCredentialRequest",
|
"fido2GetCredentialRequest",
|
||||||
|
"biometricUnlock",
|
||||||
];
|
];
|
||||||
|
|
||||||
if (messagesWithResponse.includes(msg.command)) {
|
if (messagesWithResponse.includes(msg.command)) {
|
||||||
@ -305,6 +306,14 @@ export default class RuntimeBackground {
|
|||||||
);
|
);
|
||||||
case "switchAccount": {
|
case "switchAccount": {
|
||||||
await this.main.switchAccount(msg.userId);
|
await this.main.switchAccount(msg.userId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "clearClipboard": {
|
||||||
|
await this.main.clearClipboard(msg.clipboardValue, msg.timeoutMs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "biometricUnlock": {
|
||||||
|
return await this.main.biometricUnlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "__MSG_extName__",
|
"name": "__MSG_extName__",
|
||||||
"short_name": "__MSG_appName__",
|
"short_name": "__MSG_appName__",
|
||||||
"version": "2024.2.1",
|
"version": "2024.3.0",
|
||||||
"description": "__MSG_extDesc__",
|
"description": "__MSG_extDesc__",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Bitwarden Inc.",
|
"author": "Bitwarden Inc.",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"minimum_chrome_version": "102.0",
|
"minimum_chrome_version": "102.0",
|
||||||
"name": "__MSG_extName__",
|
"name": "__MSG_extName__",
|
||||||
"short_name": "__MSG_appName__",
|
"short_name": "__MSG_appName__",
|
||||||
"version": "2024.2.1",
|
"version": "2024.3.0",
|
||||||
"description": "__MSG_extDesc__",
|
"description": "__MSG_extDesc__",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Bitwarden Inc.",
|
"author": "Bitwarden Inc.",
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import { DiskStorageOptions } from "@koa/multer";
|
|
||||||
|
|
||||||
import { AppIdService as AbstractAppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService as AbstractAppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
||||||
|
|
||||||
import { FactoryOptions, CachedServices, factory } from "./factory-options";
|
import { FactoryOptions, CachedServices, factory } from "./factory-options";
|
||||||
import { diskStorageServiceFactory } from "./storage-service.factory";
|
import {
|
||||||
|
GlobalStateProviderInitOptions,
|
||||||
|
globalStateProviderFactory,
|
||||||
|
} from "./global-state-provider.factory";
|
||||||
|
|
||||||
type AppIdServiceFactoryOptions = FactoryOptions;
|
type AppIdServiceFactoryOptions = FactoryOptions;
|
||||||
|
|
||||||
export type AppIdServiceInitOptions = AppIdServiceFactoryOptions & DiskStorageOptions;
|
export type AppIdServiceInitOptions = AppIdServiceFactoryOptions & GlobalStateProviderInitOptions;
|
||||||
|
|
||||||
export function appIdServiceFactory(
|
export function appIdServiceFactory(
|
||||||
cache: { appIdService?: AbstractAppIdService } & CachedServices,
|
cache: { appIdService?: AbstractAppIdService } & CachedServices,
|
||||||
@ -18,6 +19,6 @@ export function appIdServiceFactory(
|
|||||||
cache,
|
cache,
|
||||||
"appIdService",
|
"appIdService",
|
||||||
opts,
|
opts,
|
||||||
async () => new AppIdService(await diskStorageServiceFactory(cache, opts)),
|
async () => new AppIdService(await globalStateProviderFactory(cache, opts)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,10 @@ import { I18nService as BaseI18nService } from "@bitwarden/common/platform/servi
|
|||||||
import I18nService from "../../services/i18n.service";
|
import I18nService from "../../services/i18n.service";
|
||||||
|
|
||||||
import { FactoryOptions, CachedServices, factory } from "./factory-options";
|
import { FactoryOptions, CachedServices, factory } from "./factory-options";
|
||||||
|
import {
|
||||||
|
GlobalStateProviderInitOptions,
|
||||||
|
globalStateProviderFactory,
|
||||||
|
} from "./global-state-provider.factory";
|
||||||
|
|
||||||
type I18nServiceFactoryOptions = FactoryOptions & {
|
type I18nServiceFactoryOptions = FactoryOptions & {
|
||||||
i18nServiceOptions: {
|
i18nServiceOptions: {
|
||||||
@ -11,7 +15,7 @@ type I18nServiceFactoryOptions = FactoryOptions & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type I18nServiceInitOptions = I18nServiceFactoryOptions;
|
export type I18nServiceInitOptions = I18nServiceFactoryOptions & GlobalStateProviderInitOptions;
|
||||||
|
|
||||||
export async function i18nServiceFactory(
|
export async function i18nServiceFactory(
|
||||||
cache: { i18nService?: AbstractI18nService } & CachedServices,
|
cache: { i18nService?: AbstractI18nService } & CachedServices,
|
||||||
@ -21,7 +25,11 @@ export async function i18nServiceFactory(
|
|||||||
cache,
|
cache,
|
||||||
"i18nService",
|
"i18nService",
|
||||||
opts,
|
opts,
|
||||||
() => new I18nService(opts.i18nServiceOptions.systemLanguage),
|
async () =>
|
||||||
|
new I18nService(
|
||||||
|
opts.i18nServiceOptions.systemLanguage,
|
||||||
|
await globalStateProviderFactory(cache, opts),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (!(service as BaseI18nService as any).inited) {
|
if (!(service as BaseI18nService as any).inited) {
|
||||||
await (service as BaseI18nService).init();
|
await (service as BaseI18nService).init();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
|
||||||
import BrowserPlatformUtilsService from "../../services/browser-platform-utils.service";
|
import { BackgroundPlatformUtilsService } from "../../services/platform-utils/background-platform-utils.service";
|
||||||
|
|
||||||
import { CachedServices, factory, FactoryOptions } from "./factory-options";
|
import { CachedServices, factory, FactoryOptions } from "./factory-options";
|
||||||
import { MessagingServiceInitOptions, messagingServiceFactory } from "./messaging-service.factory";
|
import { MessagingServiceInitOptions, messagingServiceFactory } from "./messaging-service.factory";
|
||||||
@ -25,7 +25,7 @@ export function platformUtilsServiceFactory(
|
|||||||
"platformUtilsService",
|
"platformUtilsService",
|
||||||
opts,
|
opts,
|
||||||
async () =>
|
async () =>
|
||||||
new BrowserPlatformUtilsService(
|
new BackgroundPlatformUtilsService(
|
||||||
await messagingServiceFactory(cache, opts),
|
await messagingServiceFactory(cache, opts),
|
||||||
opts.platformUtilsServiceOptions.clipboardWriteCallback,
|
opts.platformUtilsServiceOptions.clipboardWriteCallback,
|
||||||
opts.platformUtilsServiceOptions.biometricCallback,
|
opts.platformUtilsServiceOptions.biometricCallback,
|
||||||
|
@ -3,7 +3,7 @@ import { Observable } from "rxjs";
|
|||||||
import { DeviceType } from "@bitwarden/common/enums";
|
import { DeviceType } from "@bitwarden/common/enums";
|
||||||
|
|
||||||
import { TabMessage } from "../../types/tab-messages";
|
import { TabMessage } from "../../types/tab-messages";
|
||||||
import BrowserPlatformUtilsService from "../services/browser-platform-utils.service";
|
import { BrowserPlatformUtilsService } from "../services/platform-utils/browser-platform-utils.service";
|
||||||
|
|
||||||
export class BrowserApi {
|
export class BrowserApi {
|
||||||
static isWebExtensionsApi: boolean = typeof browser !== "undefined";
|
static isWebExtensionsApi: boolean = typeof browser !== "undefined";
|
||||||
|
@ -17,7 +17,7 @@ import { Account } from "../../models/account";
|
|||||||
import IconDetails from "../../vault/background/models/icon-details";
|
import IconDetails from "../../vault/background/models/icon-details";
|
||||||
import { cipherServiceFactory } from "../../vault/background/service_factories/cipher-service.factory";
|
import { cipherServiceFactory } from "../../vault/background/service_factories/cipher-service.factory";
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
import { BrowserApi } from "../browser/browser-api";
|
||||||
import BrowserPlatformUtilsService from "../services/browser-platform-utils.service";
|
import { BrowserPlatformUtilsService } from "../services/platform-utils/browser-platform-utils.service";
|
||||||
|
|
||||||
export type BadgeOptions = {
|
export type BadgeOptions = {
|
||||||
tab?: chrome.tabs.Tab;
|
tab?: chrome.tabs.Tab;
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { ReplaySubject } from "rxjs";
|
|
||||||
|
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
|
|
||||||
import { browserSession, sessionSync } from "../decorators/session-sync-observable";
|
|
||||||
|
|
||||||
import I18nService from "./i18n.service";
|
|
||||||
|
|
||||||
@browserSession
|
|
||||||
export class BrowserI18nService extends I18nService {
|
|
||||||
@sessionSync({ initializer: (s: string) => s })
|
|
||||||
protected _locale: ReplaySubject<string>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
systemLanguage: string,
|
|
||||||
private stateService: StateService,
|
|
||||||
) {
|
|
||||||
super(systemLanguage);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,18 @@
|
|||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
||||||
|
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
export default class I18nService extends BaseI18nService {
|
export default class I18nService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string) {
|
constructor(systemLanguage: string, globalStateProvider: GlobalStateProvider) {
|
||||||
super(systemLanguage, null, async (formattedLocale: string) => {
|
super(
|
||||||
// Deprecated
|
systemLanguage,
|
||||||
const file = await fetch(this.localesDirectory + formattedLocale + "/messages.json");
|
null,
|
||||||
return await file.json();
|
async (formattedLocale: string) => {
|
||||||
});
|
// Deprecated
|
||||||
|
const file = await fetch(this.localesDirectory + formattedLocale + "/messages.json");
|
||||||
|
return await file.json();
|
||||||
|
},
|
||||||
|
globalStateProvider,
|
||||||
|
);
|
||||||
|
|
||||||
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found
|
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found
|
||||||
this.supportedTranslationLocales = [
|
this.supportedTranslationLocales = [
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
|
||||||
|
import { BrowserPlatformUtilsService } from "./browser-platform-utils.service";
|
||||||
|
|
||||||
|
export class BackgroundPlatformUtilsService extends BrowserPlatformUtilsService {
|
||||||
|
constructor(
|
||||||
|
private messagingService: MessagingService,
|
||||||
|
clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
||||||
|
biometricCallback: () => Promise<boolean>,
|
||||||
|
win: Window & typeof globalThis,
|
||||||
|
) {
|
||||||
|
super(clipboardWriteCallback, biometricCallback, win);
|
||||||
|
}
|
||||||
|
|
||||||
|
override showToast(
|
||||||
|
type: "error" | "success" | "warning" | "info",
|
||||||
|
title: string,
|
||||||
|
text: string | string[],
|
||||||
|
options?: any,
|
||||||
|
): void {
|
||||||
|
this.messagingService.send("showToast", {
|
||||||
|
text: text,
|
||||||
|
title: title,
|
||||||
|
type: type,
|
||||||
|
options: options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,26 @@
|
|||||||
import { DeviceType } from "@bitwarden/common/enums";
|
import { DeviceType } from "@bitwarden/common/enums";
|
||||||
|
|
||||||
import { flushPromises } from "../../autofill/spec/testing-utils";
|
import { flushPromises } from "../../../autofill/spec/testing-utils";
|
||||||
import { SafariApp } from "../../browser/safariApp";
|
import { SafariApp } from "../../../browser/safariApp";
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
import { BrowserApi } from "../../browser/browser-api";
|
||||||
|
import BrowserClipboardService from "../browser-clipboard.service";
|
||||||
|
|
||||||
import BrowserClipboardService from "./browser-clipboard.service";
|
import { BrowserPlatformUtilsService } from "./browser-platform-utils.service";
|
||||||
import BrowserPlatformUtilsService from "./browser-platform-utils.service";
|
|
||||||
|
class TestBrowserPlatformUtilsService extends BrowserPlatformUtilsService {
|
||||||
|
constructor(clipboardSpy: jest.Mock, win: Window & typeof globalThis) {
|
||||||
|
super(clipboardSpy, null, win);
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast(
|
||||||
|
type: "error" | "success" | "warning" | "info",
|
||||||
|
title: string,
|
||||||
|
text: string | string[],
|
||||||
|
options?: any,
|
||||||
|
): void {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe("Browser Utils Service", () => {
|
describe("Browser Utils Service", () => {
|
||||||
let browserPlatformUtilsService: BrowserPlatformUtilsService;
|
let browserPlatformUtilsService: BrowserPlatformUtilsService;
|
||||||
@ -13,10 +28,8 @@ describe("Browser Utils Service", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(window as any).matchMedia = jest.fn().mockReturnValueOnce({});
|
(window as any).matchMedia = jest.fn().mockReturnValueOnce({});
|
||||||
browserPlatformUtilsService = new BrowserPlatformUtilsService(
|
browserPlatformUtilsService = new TestBrowserPlatformUtilsService(
|
||||||
null,
|
|
||||||
clipboardWriteCallbackSpy,
|
clipboardWriteCallbackSpy,
|
||||||
null,
|
|
||||||
window,
|
window,
|
||||||
);
|
);
|
||||||
});
|
});
|
@ -1,20 +1,17 @@
|
|||||||
import { ClientType, DeviceType } from "@bitwarden/common/enums";
|
import { ClientType, DeviceType } from "@bitwarden/common/enums";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
||||||
import {
|
import {
|
||||||
ClipboardOptions,
|
ClipboardOptions,
|
||||||
PlatformUtilsService,
|
PlatformUtilsService,
|
||||||
} from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
} from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
|
||||||
import { SafariApp } from "../../browser/safariApp";
|
import { SafariApp } from "../../../browser/safariApp";
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
import { BrowserApi } from "../../browser/browser-api";
|
||||||
|
import BrowserClipboardService from "../browser-clipboard.service";
|
||||||
|
|
||||||
import BrowserClipboardService from "./browser-clipboard.service";
|
export abstract class BrowserPlatformUtilsService implements PlatformUtilsService {
|
||||||
|
|
||||||
export default class BrowserPlatformUtilsService implements PlatformUtilsService {
|
|
||||||
private static deviceCache: DeviceType = null;
|
private static deviceCache: DeviceType = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private messagingService: MessagingService,
|
|
||||||
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
||||||
private biometricCallback: () => Promise<boolean>,
|
private biometricCallback: () => Promise<boolean>,
|
||||||
private globalContext: Window | ServiceWorkerGlobalScope,
|
private globalContext: Window | ServiceWorkerGlobalScope,
|
||||||
@ -193,19 +190,12 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
showToast(
|
abstract showToast(
|
||||||
type: "error" | "success" | "warning" | "info",
|
type: "error" | "success" | "warning" | "info",
|
||||||
title: string,
|
title: string,
|
||||||
text: string | string[],
|
text: string | string[],
|
||||||
options?: any,
|
options?: any,
|
||||||
): void {
|
): void;
|
||||||
this.messagingService.send("showToast", {
|
|
||||||
text: text,
|
|
||||||
title: title,
|
|
||||||
type: type,
|
|
||||||
options: options,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
isDev(): boolean {
|
isDev(): boolean {
|
||||||
return process.env.ENV === "development";
|
return process.env.ENV === "development";
|
||||||
@ -279,11 +269,10 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
|
|||||||
|
|
||||||
async supportsBiometric() {
|
async supportsBiometric() {
|
||||||
const platformInfo = await BrowserApi.getPlatformInfo();
|
const platformInfo = await BrowserApi.getPlatformInfo();
|
||||||
if (platformInfo.os === "android") {
|
if (platformInfo.os === "mac" || platformInfo.os === "win") {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticateBiometric() {
|
authenticateBiometric() {
|
@ -0,0 +1,40 @@
|
|||||||
|
import { SecurityContext } from "@angular/core";
|
||||||
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
|
import { ToastrService } from "ngx-toastr";
|
||||||
|
|
||||||
|
import { BrowserPlatformUtilsService } from "./browser-platform-utils.service";
|
||||||
|
|
||||||
|
export class ForegroundPlatformUtilsService extends BrowserPlatformUtilsService {
|
||||||
|
constructor(
|
||||||
|
private sanitizer: DomSanitizer,
|
||||||
|
private toastrService: ToastrService,
|
||||||
|
clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
||||||
|
biometricCallback: () => Promise<boolean>,
|
||||||
|
win: Window & typeof globalThis,
|
||||||
|
) {
|
||||||
|
super(clipboardWriteCallback, biometricCallback, win);
|
||||||
|
}
|
||||||
|
|
||||||
|
override showToast(
|
||||||
|
type: "error" | "success" | "warning" | "info",
|
||||||
|
title: string,
|
||||||
|
text: string | string[],
|
||||||
|
options?: any,
|
||||||
|
): void {
|
||||||
|
if (typeof text === "string") {
|
||||||
|
// Already in the correct format
|
||||||
|
} else if (text.length === 1) {
|
||||||
|
text = text[0];
|
||||||
|
} else {
|
||||||
|
let message = "";
|
||||||
|
text.forEach(
|
||||||
|
(t: string) =>
|
||||||
|
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>"),
|
||||||
|
);
|
||||||
|
text = message;
|
||||||
|
options.enableHtml = true;
|
||||||
|
}
|
||||||
|
this.toastrService.show(text, title, options, "toast-" + type);
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +1,17 @@
|
|||||||
import {
|
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
SecurityContext,
|
|
||||||
} from "@angular/core";
|
|
||||||
import { DomSanitizer } from "@angular/platform-browser";
|
|
||||||
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
||||||
import { IndividualConfig, ToastrService } from "ngx-toastr";
|
import { ToastrService } from "ngx-toastr";
|
||||||
import { filter, concatMap, Subject, takeUntil, firstValueFrom } from "rxjs";
|
import { filter, concatMap, Subject, takeUntil, firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
|
||||||
import { DialogService, SimpleDialogOptions } from "@bitwarden/components";
|
import { DialogService, SimpleDialogOptions } from "@bitwarden/components";
|
||||||
|
|
||||||
import { BrowserApi } from "../platform/browser/browser-api";
|
import { BrowserApi } from "../platform/browser/browser-api";
|
||||||
import { ZonedMessageListenerService } from "../platform/browser/zoned-message-listener.service";
|
import { ZonedMessageListenerService } from "../platform/browser/zoned-message-listener.service";
|
||||||
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
|
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
|
||||||
|
import { ForegroundPlatformUtilsService } from "../platform/services/platform-utils/foreground-platform-utils.service";
|
||||||
|
|
||||||
import { routerTransition } from "./app-routing.animations";
|
import { routerTransition } from "./app-routing.animations";
|
||||||
import { DesktopSyncVerificationDialogComponent } from "./components/desktop-sync-verification-dialog.component";
|
import { DesktopSyncVerificationDialogComponent } from "./components/desktop-sync-verification-dialog.component";
|
||||||
@ -46,11 +37,9 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private stateService: BrowserStateService,
|
private stateService: BrowserStateService,
|
||||||
private messagingService: MessagingService,
|
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
private sanitizer: DomSanitizer,
|
private platformUtilsService: ForegroundPlatformUtilsService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private browserMessagingApi: ZonedMessageListenerService,
|
private browserMessagingApi: ZonedMessageListenerService,
|
||||||
) {}
|
) {}
|
||||||
@ -217,31 +206,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private showToast(msg: any) {
|
private showToast(msg: any) {
|
||||||
let message = "";
|
this.platformUtilsService.showToast(msg.type, msg.title, msg.text, msg.options);
|
||||||
|
|
||||||
const options: Partial<IndividualConfig> = {};
|
|
||||||
|
|
||||||
if (typeof msg.text === "string") {
|
|
||||||
message = msg.text;
|
|
||||||
} else if (msg.text.length === 1) {
|
|
||||||
message = msg.text[0];
|
|
||||||
} else {
|
|
||||||
msg.text.forEach(
|
|
||||||
(t: string) =>
|
|
||||||
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>"),
|
|
||||||
);
|
|
||||||
options.enableHtml = true;
|
|
||||||
}
|
|
||||||
if (msg.options != null) {
|
|
||||||
if (msg.options.trustedHtml === true) {
|
|
||||||
options.enableHtml = true;
|
|
||||||
}
|
|
||||||
if (msg.options.timeout != null && msg.options.timeout > 0) {
|
|
||||||
options.timeOut = msg.options.timeout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async showDialog(msg: SimpleDialogOptions) {
|
private async showDialog(msg: SimpleDialogOptions) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { enableProdMode } from "@angular/core";
|
import { enableProdMode } from "@angular/core";
|
||||||
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
|
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
|
||||||
|
|
||||||
import BrowserPlatformUtilsService from "../platform/services/browser-platform-utils.service";
|
import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service";
|
||||||
|
|
||||||
require("./scss/popup.scss");
|
require("./scss/popup.scss");
|
||||||
require("./scss/tailwind.css");
|
require("./scss/tailwind.css");
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { DOCUMENT } from "@angular/common";
|
||||||
|
import { Inject, Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@ -19,11 +20,13 @@ export class InitService {
|
|||||||
private logService: LogServiceAbstraction,
|
private logService: LogServiceAbstraction,
|
||||||
private themingService: AbstractThemingService,
|
private themingService: AbstractThemingService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
|
@Inject(DOCUMENT) private document: Document,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
return async () => {
|
return async () => {
|
||||||
await this.stateService.init();
|
await this.stateService.init();
|
||||||
|
await this.i18nService.init();
|
||||||
|
|
||||||
if (!BrowserPopupUtils.inPopup(window)) {
|
if (!BrowserPopupUtils.inPopup(window)) {
|
||||||
window.document.body.classList.add("body-full");
|
window.document.body.classList.add("body-full");
|
||||||
@ -34,7 +37,7 @@ export class InitService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const htmlEl = window.document.documentElement;
|
const htmlEl = window.document.documentElement;
|
||||||
await this.themingService.monitorThemeChanges();
|
this.themingService.applyThemeChangesTo(this.document);
|
||||||
htmlEl.classList.add("locale_" + this.i18nService.translationLocale);
|
htmlEl.classList.add("locale_" + this.i18nService.translationLocale);
|
||||||
|
|
||||||
// Workaround for slow performance on external monitors on Chrome + MacOS
|
// Workaround for slow performance on external monitors on Chrome + MacOS
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { APP_INITIALIZER, LOCALE_ID, NgModule, NgZone } from "@angular/core";
|
import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core";
|
||||||
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
|
import { ToastrService } from "ngx-toastr";
|
||||||
|
|
||||||
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards";
|
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards";
|
||||||
import { ThemingService } from "@bitwarden/angular/platform/services/theming/theming.service";
|
import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service";
|
||||||
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
|
||||||
import {
|
import {
|
||||||
MEMORY_STORAGE,
|
MEMORY_STORAGE,
|
||||||
SECURE_STORAGE,
|
SECURE_STORAGE,
|
||||||
OBSERVABLE_DISK_STORAGE,
|
OBSERVABLE_DISK_STORAGE,
|
||||||
OBSERVABLE_MEMORY_STORAGE,
|
OBSERVABLE_MEMORY_STORAGE,
|
||||||
|
SYSTEM_THEME_OBSERVABLE,
|
||||||
} from "@bitwarden/angular/services/injection-tokens";
|
} from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||||
import {
|
import {
|
||||||
@ -44,7 +46,6 @@ import {
|
|||||||
UserNotificationSettingsService,
|
UserNotificationSettingsService,
|
||||||
UserNotificationSettingsServiceAbstraction,
|
UserNotificationSettingsServiceAbstraction,
|
||||||
} from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
} from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
|
||||||
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
|
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
@ -60,10 +61,7 @@ import {
|
|||||||
} from "@bitwarden/common/platform/abstractions/log.service";
|
} from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import {
|
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
StateService as BaseStateServiceAbstraction,
|
|
||||||
StateService,
|
|
||||||
} from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import {
|
import {
|
||||||
AbstractMemoryStorageService,
|
AbstractMemoryStorageService,
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
@ -75,7 +73,11 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l
|
|||||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
||||||
import { DerivedStateProvider, StateProvider } from "@bitwarden/common/platform/state";
|
import {
|
||||||
|
DerivedStateProvider,
|
||||||
|
GlobalStateProvider,
|
||||||
|
StateProvider,
|
||||||
|
} from "@bitwarden/common/platform/state";
|
||||||
import { SearchService } from "@bitwarden/common/services/search.service";
|
import { SearchService } from "@bitwarden/common/services/search.service";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username";
|
import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username";
|
||||||
@ -89,14 +91,9 @@ import {
|
|||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||||
import { CipherFileUploadService } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service";
|
import { CipherFileUploadService } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service";
|
||||||
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
|
import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
import {
|
|
||||||
FolderService as FolderServiceAbstraction,
|
|
||||||
InternalFolderService,
|
|
||||||
} from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
|
||||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||||
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
|
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
import { ImportServiceAbstraction } from "@bitwarden/importer/core";
|
import { ImportServiceAbstraction } from "@bitwarden/importer/core";
|
||||||
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
|
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
|
||||||
@ -112,11 +109,12 @@ import { BrowserStateService as StateServiceAbstraction } from "../../platform/s
|
|||||||
import { BrowserConfigService } from "../../platform/services/browser-config.service";
|
import { BrowserConfigService } from "../../platform/services/browser-config.service";
|
||||||
import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service";
|
import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service";
|
||||||
import { BrowserFileDownloadService } from "../../platform/services/browser-file-download.service";
|
import { BrowserFileDownloadService } from "../../platform/services/browser-file-download.service";
|
||||||
import { BrowserI18nService } from "../../platform/services/browser-i18n.service";
|
|
||||||
import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service";
|
import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service";
|
||||||
import BrowserMessagingPrivateModePopupService from "../../platform/services/browser-messaging-private-mode-popup.service";
|
import BrowserMessagingPrivateModePopupService from "../../platform/services/browser-messaging-private-mode-popup.service";
|
||||||
import BrowserMessagingService from "../../platform/services/browser-messaging.service";
|
import BrowserMessagingService from "../../platform/services/browser-messaging.service";
|
||||||
import { BrowserStateService } from "../../platform/services/browser-state.service";
|
import { BrowserStateService } from "../../platform/services/browser-state.service";
|
||||||
|
import I18nService from "../../platform/services/i18n.service";
|
||||||
|
import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service";
|
||||||
import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
|
import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
|
||||||
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
|
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
|
||||||
import { BrowserSendService } from "../../services/browser-send.service";
|
import { BrowserSendService } from "../../services/browser-send.service";
|
||||||
@ -158,11 +156,6 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
DebounceNavigationService,
|
DebounceNavigationService,
|
||||||
DialogService,
|
DialogService,
|
||||||
PopupCloseWarningService,
|
PopupCloseWarningService,
|
||||||
{
|
|
||||||
provide: LOCALE_ID,
|
|
||||||
useFactory: () => getBgService<I18nServiceAbstraction>("i18nService")().translationLocale,
|
|
||||||
deps: [],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
useFactory: (initService: InitService) => initService.init(),
|
useFactory: (initService: InitService) => initService.init(),
|
||||||
@ -219,17 +212,6 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
useFactory: () => new WebCryptoFunctionService(window),
|
useFactory: () => new WebCryptoFunctionService(window),
|
||||||
deps: [],
|
deps: [],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: InternalFolderService,
|
|
||||||
useExisting: FolderServiceAbstraction,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: FolderApiServiceAbstraction,
|
|
||||||
useFactory: (folderService: InternalFolderService, apiService: ApiService) => {
|
|
||||||
return new FolderApiService(folderService, apiService);
|
|
||||||
},
|
|
||||||
deps: [InternalFolderService, ApiService],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: CollectionService,
|
provide: CollectionService,
|
||||||
useFactory: getBgService<CollectionService>("collectionService"),
|
useFactory: getBgService<CollectionService>("collectionService"),
|
||||||
@ -254,10 +236,10 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
{ provide: TokenService, useFactory: getBgService<TokenService>("tokenService"), deps: [] },
|
{ provide: TokenService, useFactory: getBgService<TokenService>("tokenService"), deps: [] },
|
||||||
{
|
{
|
||||||
provide: I18nServiceAbstraction,
|
provide: I18nServiceAbstraction,
|
||||||
useFactory: (stateService: BrowserStateService) => {
|
useFactory: (globalStateProvider: GlobalStateProvider) => {
|
||||||
return new BrowserI18nService(BrowserApi.getUILanguage(), stateService);
|
return new I18nService(BrowserApi.getUILanguage(), globalStateProvider);
|
||||||
},
|
},
|
||||||
deps: [StateService],
|
deps: [GlobalStateProvider],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: CryptoService,
|
provide: CryptoService,
|
||||||
@ -295,8 +277,32 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: PlatformUtilsService,
|
provide: PlatformUtilsService,
|
||||||
useFactory: getBgService<PlatformUtilsService>("platformUtilsService"),
|
useExisting: ForegroundPlatformUtilsService,
|
||||||
deps: [],
|
},
|
||||||
|
{
|
||||||
|
provide: ForegroundPlatformUtilsService,
|
||||||
|
useClass: ForegroundPlatformUtilsService,
|
||||||
|
useFactory: (sanitizer: DomSanitizer, toastrService: ToastrService) => {
|
||||||
|
return new ForegroundPlatformUtilsService(
|
||||||
|
sanitizer,
|
||||||
|
toastrService,
|
||||||
|
(clipboardValue: string, clearMs: number) => {
|
||||||
|
void BrowserApi.sendMessage("clearClipboard", { clipboardValue, clearMs });
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
const response = await BrowserApi.sendMessageWithResponse<{
|
||||||
|
result: boolean;
|
||||||
|
error: string;
|
||||||
|
}>("biometricUnlock");
|
||||||
|
if (!response.result) {
|
||||||
|
throw response.error;
|
||||||
|
}
|
||||||
|
return response.result;
|
||||||
|
},
|
||||||
|
window,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
deps: [DomSanitizer, ToastrService],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: PasswordStrengthServiceAbstraction,
|
provide: PasswordStrengthServiceAbstraction,
|
||||||
@ -353,7 +359,6 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
useClass: BrowserLocalStorageService,
|
useClass: BrowserLocalStorageService,
|
||||||
deps: [],
|
deps: [],
|
||||||
},
|
},
|
||||||
{ provide: AppIdService, useFactory: getBgService<AppIdService>("appIdService"), deps: [] },
|
|
||||||
{
|
{
|
||||||
provide: AutofillService,
|
provide: AutofillService,
|
||||||
useFactory: getBgService<AutofillService>("autofillService"),
|
useFactory: getBgService<AutofillService>("autofillService"),
|
||||||
@ -394,11 +399,6 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
useFactory: getBgService<NotificationsService>("notificationsService"),
|
useFactory: getBgService<NotificationsService>("notificationsService"),
|
||||||
deps: [],
|
deps: [],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: LogServiceAbstraction,
|
|
||||||
useFactory: getBgService<ConsoleLogService>("logService"),
|
|
||||||
deps: [],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: OrganizationService,
|
provide: OrganizationService,
|
||||||
useFactory: (stateService: StateServiceAbstraction, stateProvider: StateProvider) => {
|
useFactory: (stateService: StateServiceAbstraction, stateProvider: StateProvider) => {
|
||||||
@ -421,7 +421,7 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SECURE_STORAGE,
|
provide: SECURE_STORAGE,
|
||||||
useFactory: getBgService<AbstractStorageService>("secureStorageService"),
|
useExisting: AbstractStorageService, // Secure storage is not available in the browser, so we use normal storage instead and warn users when it is used.
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: MEMORY_STORAGE,
|
provide: MEMORY_STORAGE,
|
||||||
@ -488,11 +488,8 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
deps: [StateServiceAbstraction],
|
deps: [StateServiceAbstraction],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: AbstractThemingService,
|
provide: SYSTEM_THEME_OBSERVABLE,
|
||||||
useFactory: (
|
useFactory: (platformUtilsService: PlatformUtilsService) => {
|
||||||
stateService: StateServiceAbstraction,
|
|
||||||
platformUtilsService: PlatformUtilsService,
|
|
||||||
) => {
|
|
||||||
// Safari doesn't properly handle the (prefers-color-scheme) media query in the popup window, it always returns light.
|
// Safari doesn't properly handle the (prefers-color-scheme) media query in the popup window, it always returns light.
|
||||||
// In Safari, we have to use the background page instead, which comes with limitations like not dynamically changing the extension theme when the system theme is changed.
|
// In Safari, we have to use the background page instead, which comes with limitations like not dynamically changing the extension theme when the system theme is changed.
|
||||||
let windowContext = window;
|
let windowContext = window;
|
||||||
@ -501,9 +498,9 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
windowContext = backgroundWindow;
|
windowContext = backgroundWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ThemingService(stateService, windowContext, document);
|
return AngularThemingService.createSystemThemeFromWindow(windowContext);
|
||||||
},
|
},
|
||||||
deps: [StateServiceAbstraction, PlatformUtilsService],
|
deps: [PlatformUtilsService],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: ConfigService,
|
provide: ConfigService,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
@ -30,6 +32,7 @@ export class ExcludedDomainsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
private domainSettingsService: DomainSettingsService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private broadcasterService: BroadcasterService,
|
private broadcasterService: BroadcasterService,
|
||||||
@ -40,7 +43,7 @@ export class ExcludedDomainsComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const savedDomains = await this.stateService.getNeverDomains();
|
const savedDomains = await firstValueFrom(this.domainSettingsService.neverDomains$);
|
||||||
if (savedDomains) {
|
if (savedDomains) {
|
||||||
for (const uri of Object.keys(savedDomains)) {
|
for (const uri of Object.keys(savedDomains)) {
|
||||||
this.excludedDomains.push({ uri: uri, showCurrentUris: false });
|
this.excludedDomains.push({ uri: uri, showCurrentUris: false });
|
||||||
@ -107,7 +110,7 @@ export class ExcludedDomainsComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.stateService.setNeverDomains(savedDomains);
|
await this.domainSettingsService.setNeverDomains(savedDomains);
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.router.navigate(["/tabs/settings"]);
|
this.router.navigate(["/tabs/settings"]);
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
|
||||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
||||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||||
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
|
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
|
||||||
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||||
import { ClearClipboardDelaySetting } from "@bitwarden/common/autofill/types";
|
import { ClearClipboardDelaySetting } from "@bitwarden/common/autofill/types";
|
||||||
|
import {
|
||||||
|
UriMatchStrategy,
|
||||||
|
UriMatchStrategySetting,
|
||||||
|
} from "@bitwarden/common/models/domain/domain-service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||||
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||||
import { UriMatchType } from "@bitwarden/common/vault/enums";
|
|
||||||
|
|
||||||
import { enableAccountSwitching } from "../../platform/flags";
|
import { enableAccountSwitching } from "../../platform/flags";
|
||||||
|
|
||||||
@ -36,7 +40,7 @@ export class OptionsComponent implements OnInit {
|
|||||||
showClearClipboard = true;
|
showClearClipboard = true;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
themeOptions: any[];
|
themeOptions: any[];
|
||||||
defaultUriMatch = UriMatchType.Domain;
|
defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain;
|
||||||
uriMatchOptions: any[];
|
uriMatchOptions: any[];
|
||||||
clearClipboard: ClearClipboardDelaySetting;
|
clearClipboard: ClearClipboardDelaySetting;
|
||||||
clearClipboardOptions: any[];
|
clearClipboardOptions: any[];
|
||||||
@ -50,9 +54,10 @@ export class OptionsComponent implements OnInit {
|
|||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
|
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
|
||||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||||
|
private domainSettingsService: DomainSettingsService,
|
||||||
private badgeSettingsService: BadgeSettingsServiceAbstraction,
|
private badgeSettingsService: BadgeSettingsServiceAbstraction,
|
||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
private themingService: AbstractThemingService,
|
private themeStateService: ThemeStateService,
|
||||||
private settingsService: SettingsService,
|
private settingsService: SettingsService,
|
||||||
private vaultSettingsService: VaultSettingsService,
|
private vaultSettingsService: VaultSettingsService,
|
||||||
) {
|
) {
|
||||||
@ -64,12 +69,12 @@ export class OptionsComponent implements OnInit {
|
|||||||
{ name: i18nService.t("solarizedDark"), value: ThemeType.SolarizedDark },
|
{ name: i18nService.t("solarizedDark"), value: ThemeType.SolarizedDark },
|
||||||
];
|
];
|
||||||
this.uriMatchOptions = [
|
this.uriMatchOptions = [
|
||||||
{ name: i18nService.t("baseDomain"), value: UriMatchType.Domain },
|
{ name: i18nService.t("baseDomain"), value: UriMatchStrategy.Domain },
|
||||||
{ name: i18nService.t("host"), value: UriMatchType.Host },
|
{ name: i18nService.t("host"), value: UriMatchStrategy.Host },
|
||||||
{ name: i18nService.t("startsWith"), value: UriMatchType.StartsWith },
|
{ name: i18nService.t("startsWith"), value: UriMatchStrategy.StartsWith },
|
||||||
{ name: i18nService.t("regEx"), value: UriMatchType.RegularExpression },
|
{ name: i18nService.t("regEx"), value: UriMatchStrategy.RegularExpression },
|
||||||
{ name: i18nService.t("exact"), value: UriMatchType.Exact },
|
{ name: i18nService.t("exact"), value: UriMatchStrategy.Exact },
|
||||||
{ name: i18nService.t("never"), value: UriMatchType.Never },
|
{ name: i18nService.t("never"), value: UriMatchStrategy.Never },
|
||||||
];
|
];
|
||||||
this.clearClipboardOptions = [
|
this.clearClipboardOptions = [
|
||||||
{ name: i18nService.t("never"), value: null },
|
{ name: i18nService.t("never"), value: null },
|
||||||
@ -120,10 +125,12 @@ export class OptionsComponent implements OnInit {
|
|||||||
|
|
||||||
this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$);
|
this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$);
|
||||||
|
|
||||||
this.theme = await this.stateService.getTheme();
|
this.theme = await firstValueFrom(this.themeStateService.selectedTheme$);
|
||||||
|
|
||||||
const defaultUriMatch = await this.stateService.getDefaultUriMatch();
|
const defaultUriMatch = await firstValueFrom(
|
||||||
this.defaultUriMatch = defaultUriMatch == null ? UriMatchType.Domain : defaultUriMatch;
|
this.domainSettingsService.defaultUriMatchStrategy$,
|
||||||
|
);
|
||||||
|
this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch;
|
||||||
|
|
||||||
this.clearClipboard = await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$);
|
this.clearClipboard = await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$);
|
||||||
}
|
}
|
||||||
@ -179,11 +186,7 @@ export class OptionsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveTheme() {
|
async saveTheme() {
|
||||||
await this.themingService.updateConfiguredTheme(this.theme);
|
await this.themeStateService.setSelectedTheme(this.theme);
|
||||||
}
|
|
||||||
|
|
||||||
async saveDefaultUriMatch() {
|
|
||||||
await this.stateService.setDefaultUriMatch(this.defaultUriMatch);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveClearClipboard() {
|
async saveClearClipboard() {
|
||||||
|
@ -397,7 +397,7 @@ export class SettingsComponent implements OnInit {
|
|||||||
// Handle connection errors
|
// Handle connection errors
|
||||||
this.form.controls.biometric.setValue(false);
|
this.form.controls.biometric.setValue(false);
|
||||||
|
|
||||||
const error = BiometricErrors[e as BiometricErrorTypes];
|
const error = BiometricErrors[e.message as BiometricErrorTypes];
|
||||||
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
import { AccountSettingsSettings } from "@bitwarden/common/platform/models/domain/account";
|
|
||||||
import { SettingsService } from "@bitwarden/common/services/settings.service";
|
import { SettingsService } from "@bitwarden/common/services/settings.service";
|
||||||
|
|
||||||
import { browserSession, sessionSync } from "../platform/decorators/session-sync-observable";
|
import { browserSession, sessionSync } from "../platform/decorators/session-sync-observable";
|
||||||
|
|
||||||
@browserSession
|
@browserSession
|
||||||
export class BrowserSettingsService extends SettingsService {
|
export class BrowserSettingsService extends SettingsService {
|
||||||
@sessionSync({ initializer: (obj: string[][]) => obj })
|
|
||||||
protected _settings: BehaviorSubject<AccountSettingsSettings>;
|
|
||||||
|
|
||||||
@sessionSync({ initializer: (b: boolean) => b })
|
@sessionSync({ initializer: (b: boolean) => b })
|
||||||
protected _disableFavicon: BehaviorSubject<boolean>;
|
protected _disableFavicon: BehaviorSubject<boolean>;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,10 @@ import {
|
|||||||
AutofillSettingsServiceInitOptions,
|
AutofillSettingsServiceInitOptions,
|
||||||
autofillSettingsServiceFactory,
|
autofillSettingsServiceFactory,
|
||||||
} from "../../../autofill/background/service_factories/autofill-settings-service.factory";
|
} from "../../../autofill/background/service_factories/autofill-settings-service.factory";
|
||||||
|
import {
|
||||||
|
DomainSettingsServiceInitOptions,
|
||||||
|
domainSettingsServiceFactory,
|
||||||
|
} from "../../../autofill/background/service_factories/domain-settings-service.factory";
|
||||||
import {
|
import {
|
||||||
CipherFileUploadServiceInitOptions,
|
CipherFileUploadServiceInitOptions,
|
||||||
cipherFileUploadServiceFactory,
|
cipherFileUploadServiceFactory,
|
||||||
@ -13,10 +17,6 @@ import {
|
|||||||
searchServiceFactory,
|
searchServiceFactory,
|
||||||
SearchServiceInitOptions,
|
SearchServiceInitOptions,
|
||||||
} from "../../../background/service-factories/search-service.factory";
|
} from "../../../background/service-factories/search-service.factory";
|
||||||
import {
|
|
||||||
SettingsServiceInitOptions,
|
|
||||||
settingsServiceFactory,
|
|
||||||
} from "../../../background/service-factories/settings-service.factory";
|
|
||||||
import {
|
import {
|
||||||
apiServiceFactory,
|
apiServiceFactory,
|
||||||
ApiServiceInitOptions,
|
ApiServiceInitOptions,
|
||||||
@ -51,13 +51,13 @@ type CipherServiceFactoryOptions = FactoryOptions;
|
|||||||
|
|
||||||
export type CipherServiceInitOptions = CipherServiceFactoryOptions &
|
export type CipherServiceInitOptions = CipherServiceFactoryOptions &
|
||||||
CryptoServiceInitOptions &
|
CryptoServiceInitOptions &
|
||||||
SettingsServiceInitOptions &
|
|
||||||
ApiServiceInitOptions &
|
ApiServiceInitOptions &
|
||||||
CipherFileUploadServiceInitOptions &
|
CipherFileUploadServiceInitOptions &
|
||||||
I18nServiceInitOptions &
|
I18nServiceInitOptions &
|
||||||
SearchServiceInitOptions &
|
SearchServiceInitOptions &
|
||||||
StateServiceInitOptions &
|
StateServiceInitOptions &
|
||||||
AutofillSettingsServiceInitOptions &
|
AutofillSettingsServiceInitOptions &
|
||||||
|
DomainSettingsServiceInitOptions &
|
||||||
EncryptServiceInitOptions &
|
EncryptServiceInitOptions &
|
||||||
ConfigServiceInitOptions;
|
ConfigServiceInitOptions;
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ export function cipherServiceFactory(
|
|||||||
async () =>
|
async () =>
|
||||||
new CipherService(
|
new CipherService(
|
||||||
await cryptoServiceFactory(cache, opts),
|
await cryptoServiceFactory(cache, opts),
|
||||||
await settingsServiceFactory(cache, opts),
|
await domainSettingsServiceFactory(cache, opts),
|
||||||
await apiServiceFactory(cache, opts),
|
await apiServiceFactory(cache, opts),
|
||||||
await i18nServiceFactory(cache, opts),
|
await i18nServiceFactory(cache, opts),
|
||||||
await searchServiceFactory(cache, opts),
|
await searchServiceFactory(cache, opts),
|
||||||
|
@ -3,6 +3,7 @@ import { ConnectedPosition } from "@angular/cdk/overlay";
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
@ -52,6 +53,7 @@ export class Fido2UseBrowserLinkComponent {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
private domainSettingsService: DomainSettingsService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
) {}
|
) {}
|
||||||
@ -89,7 +91,7 @@ export class Fido2UseBrowserLinkComponent {
|
|||||||
* @param uri - The domain uri to exclude from future FIDO2 prompts.
|
* @param uri - The domain uri to exclude from future FIDO2 prompts.
|
||||||
*/
|
*/
|
||||||
private async handleDomainExclusion(uri: string) {
|
private async handleDomainExclusion(uri: string) {
|
||||||
const exisitingDomains = await this.stateService.getNeverDomains();
|
const exisitingDomains = await firstValueFrom(this.domainSettingsService.neverDomains$);
|
||||||
|
|
||||||
const validDomain = Utils.getHostname(uri);
|
const validDomain = Utils.getHostname(uri);
|
||||||
const savedDomains: { [name: string]: unknown } = {
|
const savedDomains: { [name: string]: unknown } = {
|
||||||
@ -97,9 +99,7 @@ export class Fido2UseBrowserLinkComponent {
|
|||||||
};
|
};
|
||||||
savedDomains[validDomain] = null;
|
savedDomains[validDomain] = null;
|
||||||
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
await this.domainSettingsService.setNeverDomains(savedDomains);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
this.stateService.setNeverDomains(savedDomains);
|
|
||||||
|
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"success",
|
"success",
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
combineLatest,
|
combineLatest,
|
||||||
concatMap,
|
concatMap,
|
||||||
filter,
|
filter,
|
||||||
|
firstValueFrom,
|
||||||
map,
|
map,
|
||||||
Observable,
|
Observable,
|
||||||
Subject,
|
Subject,
|
||||||
@ -13,7 +14,7 @@ import {
|
|||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
|
|
||||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@ -72,7 +73,7 @@ export class Fido2Component implements OnInit, OnDestroy {
|
|||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private passwordRepromptService: PasswordRepromptService,
|
private passwordRepromptService: PasswordRepromptService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private settingsService: SettingsService,
|
private domainSettingsService: DomainSettingsService,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
@ -133,7 +134,9 @@ export class Fido2Component implements OnInit, OnDestroy {
|
|||||||
concatMap(async (message) => {
|
concatMap(async (message) => {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case "ConfirmNewCredentialRequest": {
|
case "ConfirmNewCredentialRequest": {
|
||||||
const equivalentDomains = this.settingsService.getEquivalentDomains(this.url);
|
const equivalentDomains = await firstValueFrom(
|
||||||
|
this.domainSettingsService.getUrlEquivalentDomains(this.url),
|
||||||
|
);
|
||||||
|
|
||||||
this.ciphers = (await this.cipherService.getAllDecrypted()).filter(
|
this.ciphers = (await this.cipherService.getAllDecrypted()).filter(
|
||||||
(cipher) => cipher.type === CipherType.Login && !cipher.isDeleted,
|
(cipher) => cipher.type === CipherType.Login && !cipher.isDeleted,
|
||||||
@ -317,7 +320,9 @@ export class Fido2Component implements OnInit, OnDestroy {
|
|||||||
this.ciphers,
|
this.ciphers,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const equivalentDomains = this.settingsService.getEquivalentDomains(this.url);
|
const equivalentDomains = await firstValueFrom(
|
||||||
|
this.domainSettingsService.getUrlEquivalentDomains(this.url),
|
||||||
|
);
|
||||||
this.displayedCiphers = this.ciphers.filter((cipher) =>
|
this.displayedCiphers = this.ciphers.filter((cipher) =>
|
||||||
cipher.login.matchesUri(this.url, equivalentDomains),
|
cipher.login.matchesUri(this.url, equivalentDomains),
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
|
||||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
@ -138,9 +137,6 @@ const plugins = [
|
|||||||
entryModule: "src/popup/app.module#AppModule",
|
entryModule: "src/popup/app.module#AppModule",
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
}),
|
}),
|
||||||
new CleanWebpackPlugin({
|
|
||||||
cleanAfterEveryBuildPatterns: ["!popup/fonts/**/*"],
|
|
||||||
}),
|
|
||||||
new webpack.ProvidePlugin({
|
new webpack.ProvidePlugin({
|
||||||
process: "process/browser.js",
|
process: "process/browser.js",
|
||||||
}),
|
}),
|
||||||
@ -244,6 +240,7 @@ const mainConfig = {
|
|||||||
output: {
|
output: {
|
||||||
filename: "[name].js",
|
filename: "[name].js",
|
||||||
path: path.resolve(__dirname, "build"),
|
path: path.resolve(__dirname, "build"),
|
||||||
|
clean: true,
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
noParse: /\.wasm$/,
|
noParse: /\.wasm$/,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@bitwarden/cli",
|
"name": "@bitwarden/cli",
|
||||||
"description": "A secure and free password manager for all of your devices.",
|
"description": "A secure and free password manager for all of your devices.",
|
||||||
"version": "2024.2.1",
|
"version": "2024.3.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bitwarden",
|
"bitwarden",
|
||||||
"password",
|
"password",
|
||||||
|
@ -273,8 +273,8 @@ export class LoginCommand {
|
|||||||
selectedProvider.type === TwoFactorProviderType.Email
|
selectedProvider.type === TwoFactorProviderType.Email
|
||||||
) {
|
) {
|
||||||
const emailReq = new TwoFactorEmailRequest();
|
const emailReq = new TwoFactorEmailRequest();
|
||||||
emailReq.email = this.loginStrategyService.email;
|
emailReq.email = await this.loginStrategyService.getEmail();
|
||||||
emailReq.masterPasswordHash = this.loginStrategyService.masterPasswordHash;
|
emailReq.masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash();
|
||||||
await this.apiService.postTwoFactorEmail(emailReq);
|
await this.apiService.postTwoFactorEmail(emailReq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,10 @@ import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.ser
|
|||||||
import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service";
|
import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
|
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
|
||||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||||
|
import {
|
||||||
|
DefaultDomainSettingsService,
|
||||||
|
DomainSettingsService,
|
||||||
|
} from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { ClientType } from "@bitwarden/common/enums";
|
import { ClientType } from "@bitwarden/common/enums";
|
||||||
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
|
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
|
||||||
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
||||||
@ -190,6 +194,7 @@ export class Main {
|
|||||||
pinCryptoService: PinCryptoServiceAbstraction;
|
pinCryptoService: PinCryptoServiceAbstraction;
|
||||||
stateService: StateService;
|
stateService: StateService;
|
||||||
autofillSettingsService: AutofillSettingsServiceAbstraction;
|
autofillSettingsService: AutofillSettingsServiceAbstraction;
|
||||||
|
domainSettingsService: DomainSettingsService;
|
||||||
organizationService: OrganizationService;
|
organizationService: OrganizationService;
|
||||||
providerService: ProviderService;
|
providerService: ProviderService;
|
||||||
twoFactorService: TwoFactorService;
|
twoFactorService: TwoFactorService;
|
||||||
@ -231,7 +236,6 @@ export class Main {
|
|||||||
p = path.join(process.env.HOME, ".config/Bitwarden CLI");
|
p = path.join(process.env.HOME, ".config/Bitwarden CLI");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.i18nService = new I18nService("en", "./locales");
|
|
||||||
this.platformUtilsService = new CliPlatformUtilsService(ClientType.Cli, packageJson);
|
this.platformUtilsService = new CliPlatformUtilsService(ClientType.Cli, packageJson);
|
||||||
this.logService = new ConsoleLogService(
|
this.logService = new ConsoleLogService(
|
||||||
this.platformUtilsService.isDev(),
|
this.platformUtilsService.isDev(),
|
||||||
@ -270,6 +274,8 @@ export class Main {
|
|||||||
storageServiceProvider,
|
storageServiceProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.i18nService = new I18nService("en", "./locales", this.globalStateProvider);
|
||||||
|
|
||||||
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
||||||
storageServiceProvider,
|
storageServiceProvider,
|
||||||
stateEventRegistrarService,
|
stateEventRegistrarService,
|
||||||
@ -332,7 +338,7 @@ export class Main {
|
|||||||
this.stateProvider,
|
this.stateProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.appIdService = new AppIdService(this.storageService);
|
this.appIdService = new AppIdService(this.globalStateProvider);
|
||||||
this.tokenService = new TokenService(this.stateService);
|
this.tokenService = new TokenService(this.stateService);
|
||||||
|
|
||||||
const customUserAgent =
|
const customUserAgent =
|
||||||
@ -357,6 +363,7 @@ export class Main {
|
|||||||
this.containerService = new ContainerService(this.cryptoService, this.encryptService);
|
this.containerService = new ContainerService(this.cryptoService, this.encryptService);
|
||||||
|
|
||||||
this.settingsService = new SettingsService(this.stateService);
|
this.settingsService = new SettingsService(this.stateService);
|
||||||
|
this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider);
|
||||||
|
|
||||||
this.fileUploadService = new FileUploadService(this.logService);
|
this.fileUploadService = new FileUploadService(this.logService);
|
||||||
|
|
||||||
@ -457,6 +464,7 @@ export class Main {
|
|||||||
this.policyService,
|
this.policyService,
|
||||||
this.deviceTrustCryptoService,
|
this.deviceTrustCryptoService,
|
||||||
this.authRequestService,
|
this.authRequestService,
|
||||||
|
this.globalStateProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.authService = new AuthService(
|
this.authService = new AuthService(
|
||||||
@ -479,7 +487,7 @@ export class Main {
|
|||||||
|
|
||||||
this.cipherService = new CipherService(
|
this.cipherService = new CipherService(
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
this.settingsService,
|
this.domainSettingsService,
|
||||||
this.apiService,
|
this.apiService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.searchService,
|
this.searchService,
|
||||||
@ -549,7 +557,7 @@ export class Main {
|
|||||||
|
|
||||||
this.syncService = new SyncService(
|
this.syncService = new SyncService(
|
||||||
this.apiService,
|
this.apiService,
|
||||||
this.settingsService,
|
this.domainSettingsService,
|
||||||
this.folderService,
|
this.folderService,
|
||||||
this.cipherService,
|
this.cipherService,
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
@ -645,7 +653,6 @@ export class Main {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.syncService.setLastSync(new Date(0)),
|
this.syncService.setLastSync(new Date(0)),
|
||||||
this.cryptoService.clearKeys(),
|
this.cryptoService.clearKeys(),
|
||||||
this.settingsService.clear(userId),
|
|
||||||
this.cipherService.clear(userId),
|
this.cipherService.clear(userId),
|
||||||
this.folderService.clear(userId),
|
this.folderService.clear(userId),
|
||||||
this.collectionService.clear(userId as UserId),
|
this.collectionService.clear(userId as UserId),
|
||||||
@ -665,8 +672,7 @@ export class Main {
|
|||||||
await this.stateService.init();
|
await this.stateService.init();
|
||||||
this.containerService.attachToGlobal(global);
|
this.containerService.attachToGlobal(global);
|
||||||
await this.environmentService.setUrlsFromStorage();
|
await this.environmentService.setUrlsFromStorage();
|
||||||
const locale = await this.stateService.getLocale();
|
await this.i18nService.init();
|
||||||
await this.i18nService.init(locale);
|
|
||||||
this.twoFactorService.init();
|
this.twoFactorService.init();
|
||||||
this.configService.init();
|
this.configService.init();
|
||||||
|
|
||||||
|
@ -2,18 +2,28 @@ import * as fs from "fs";
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
||||||
|
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
export class I18nService extends BaseI18nService {
|
export class I18nService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string, localesDirectory: string) {
|
constructor(
|
||||||
super(systemLanguage, localesDirectory, (formattedLocale: string) => {
|
systemLanguage: string,
|
||||||
const filePath = path.join(
|
localesDirectory: string,
|
||||||
__dirname,
|
globalStateProvider: GlobalStateProvider,
|
||||||
this.localesDirectory + "/" + formattedLocale + "/messages.json",
|
) {
|
||||||
);
|
super(
|
||||||
const localesJson = fs.readFileSync(filePath, "utf8");
|
systemLanguage,
|
||||||
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, "")); // strip the BOM
|
localesDirectory,
|
||||||
return Promise.resolve(locales);
|
(formattedLocale: string) => {
|
||||||
});
|
const filePath = path.join(
|
||||||
|
__dirname,
|
||||||
|
this.localesDirectory + "/" + formattedLocale + "/messages.json",
|
||||||
|
);
|
||||||
|
const localesJson = fs.readFileSync(filePath, "utf8");
|
||||||
|
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, "")); // strip the BOM
|
||||||
|
return Promise.resolve(locales);
|
||||||
|
},
|
||||||
|
globalStateProvider,
|
||||||
|
);
|
||||||
|
|
||||||
this.supportedTranslationLocales = ["en"];
|
this.supportedTranslationLocales = ["en"];
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
const nodeExternals = require("webpack-node-externals");
|
const nodeExternals = require("webpack-node-externals");
|
||||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||||
@ -23,7 +22,6 @@ const moduleRules = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
new CleanWebpackPlugin(),
|
|
||||||
new CopyWebpackPlugin({
|
new CopyWebpackPlugin({
|
||||||
patterns: [{ from: "./src/locales", to: "locales" }],
|
patterns: [{ from: "./src/locales", to: "locales" }],
|
||||||
}),
|
}),
|
||||||
@ -71,6 +69,7 @@ const webpackConfig = {
|
|||||||
output: {
|
output: {
|
||||||
filename: "[name].js",
|
filename: "[name].js",
|
||||||
path: path.resolve(__dirname, "build"),
|
path: path.resolve(__dirname, "build"),
|
||||||
|
clean: true,
|
||||||
},
|
},
|
||||||
module: { rules: moduleRules },
|
module: { rules: moduleRules },
|
||||||
plugins: plugins,
|
plugins: plugins,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@bitwarden/desktop",
|
"name": "@bitwarden/desktop",
|
||||||
"description": "A secure and free password manager for all of your devices.",
|
"description": "A secure and free password manager for all of your devices.",
|
||||||
"version": "2024.2.2",
|
"version": "2024.3.1",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bitwarden",
|
"bitwarden",
|
||||||
"password",
|
"password",
|
||||||
|
@ -3,7 +3,6 @@ import { FormBuilder } from "@angular/forms";
|
|||||||
import { BehaviorSubject, firstValueFrom, Observable, Subject } from "rxjs";
|
import { BehaviorSubject, firstValueFrom, Observable, Subject } from "rxjs";
|
||||||
import { concatMap, debounceTime, filter, map, switchMap, takeUntil, tap } from "rxjs/operators";
|
import { concatMap, debounceTime, filter, map, switchMap, takeUntil, tap } from "rxjs/operators";
|
||||||
|
|
||||||
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
||||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||||
@ -21,6 +20,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
|
|||||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||||
import { ThemeType, KeySuffixOptions } from "@bitwarden/common/platform/enums";
|
import { ThemeType, KeySuffixOptions } from "@bitwarden/common/platform/enums";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { SetPinComponent } from "../../auth/components/set-pin.component";
|
import { SetPinComponent } from "../../auth/components/set-pin.component";
|
||||||
@ -116,7 +116,7 @@ export class SettingsComponent implements OnInit {
|
|||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private modalService: ModalService,
|
private modalService: ModalService,
|
||||||
private themingService: AbstractThemingService,
|
private themeStateService: ThemeStateService,
|
||||||
private settingsService: SettingsService,
|
private settingsService: SettingsService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private userVerificationService: UserVerificationServiceAbstraction,
|
private userVerificationService: UserVerificationServiceAbstraction,
|
||||||
@ -263,8 +263,8 @@ export class SettingsComponent implements OnInit {
|
|||||||
await this.stateService.getEnableBrowserIntegrationFingerprint(),
|
await this.stateService.getEnableBrowserIntegrationFingerprint(),
|
||||||
enableDuckDuckGoBrowserIntegration:
|
enableDuckDuckGoBrowserIntegration:
|
||||||
await this.stateService.getEnableDuckDuckGoBrowserIntegration(),
|
await this.stateService.getEnableDuckDuckGoBrowserIntegration(),
|
||||||
theme: await this.stateService.getTheme(),
|
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
|
||||||
locale: (await this.stateService.getLocale()) ?? null,
|
locale: await firstValueFrom(this.i18nService.userSetLocale$),
|
||||||
};
|
};
|
||||||
this.form.setValue(initialValues, { emitEvent: false });
|
this.form.setValue(initialValues, { emitEvent: false });
|
||||||
|
|
||||||
@ -553,11 +553,11 @@ export class SettingsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveLocale() {
|
async saveLocale() {
|
||||||
await this.stateService.setLocale(this.form.value.locale);
|
await this.i18nService.setLocale(this.form.value.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveTheme() {
|
async saveTheme() {
|
||||||
await this.themingService.updateConfiguredTheme(this.form.value.theme);
|
await this.themeStateService.setSelectedTheme(this.form.value.theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveMinOnCopyToClipboard() {
|
async saveMinOnCopyToClipboard() {
|
||||||
|
@ -577,7 +577,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
await this.eventUploadService.uploadEvents(userBeingLoggedOut);
|
await this.eventUploadService.uploadEvents(userBeingLoggedOut);
|
||||||
await this.syncService.setLastSync(new Date(0), userBeingLoggedOut);
|
await this.syncService.setLastSync(new Date(0), userBeingLoggedOut);
|
||||||
await this.cryptoService.clearKeys(userBeingLoggedOut);
|
await this.cryptoService.clearKeys(userBeingLoggedOut);
|
||||||
await this.settingsService.clear(userBeingLoggedOut);
|
|
||||||
await this.cipherService.clear(userBeingLoggedOut);
|
await this.cipherService.clear(userBeingLoggedOut);
|
||||||
await this.folderService.clear(userBeingLoggedOut);
|
await this.folderService.clear(userBeingLoggedOut);
|
||||||
await this.collectionService.clear(userBeingLoggedOut);
|
await this.collectionService.clear(userBeingLoggedOut);
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
import { Injectable } from "@angular/core";
|
|
||||||
|
|
||||||
import { ThemingService } from "@bitwarden/angular/platform/services/theming/theming.service";
|
|
||||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DesktopThemingService extends ThemingService {
|
|
||||||
protected async getSystemTheme(): Promise<ThemeType> {
|
|
||||||
return await ipc.platform.getSystemTheme();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected monitorSystemThemeChanges(): void {
|
|
||||||
ipc.platform.onSystemThemeUpdated((theme: ThemeType) => this.updateSystemTheme(theme));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +1,4 @@
|
|||||||
|
import { DOCUMENT } from "@angular/common";
|
||||||
import { Inject, Injectable } from "@angular/core";
|
import { Inject, Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
||||||
@ -38,6 +39,7 @@ export class InitService {
|
|||||||
private themingService: AbstractThemingService,
|
private themingService: AbstractThemingService,
|
||||||
private encryptService: EncryptService,
|
private encryptService: EncryptService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
|
@Inject(DOCUMENT) private document: Document,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@ -52,14 +54,13 @@ export class InitService {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.syncService.fullSync(true);
|
this.syncService.fullSync(true);
|
||||||
await this.vaultTimeoutService.init(true);
|
await this.vaultTimeoutService.init(true);
|
||||||
const locale = await this.stateService.getLocale();
|
await (this.i18nService as I18nRendererService).init();
|
||||||
await (this.i18nService as I18nRendererService).init(locale);
|
|
||||||
(this.eventUploadService as EventUploadService).init(true);
|
(this.eventUploadService as EventUploadService).init(true);
|
||||||
this.twoFactorService.init();
|
this.twoFactorService.init();
|
||||||
setTimeout(() => this.notificationsService.init(), 3000);
|
setTimeout(() => this.notificationsService.init(), 3000);
|
||||||
const htmlEl = this.win.document.documentElement;
|
const htmlEl = this.win.document.documentElement;
|
||||||
htmlEl.classList.add("os_" + this.platformUtilsService.getDeviceString());
|
htmlEl.classList.add("os_" + this.platformUtilsService.getDeviceString());
|
||||||
await this.themingService.monitorThemeChanges();
|
this.themingService.applyThemeChangesTo(this.document);
|
||||||
let installAction = null;
|
let installAction = null;
|
||||||
const installedVersion = await this.stateService.getInstalledVersion();
|
const installedVersion = await this.stateService.getInstalledVersion();
|
||||||
const currentVersion = await this.platformUtilsService.getApplicationVersion();
|
const currentVersion = await this.platformUtilsService.getApplicationVersion();
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { APP_INITIALIZER, InjectionToken, NgModule } from "@angular/core";
|
import { APP_INITIALIZER, InjectionToken, NgModule } from "@angular/core";
|
||||||
|
|
||||||
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
|
||||||
import {
|
import {
|
||||||
SECURE_STORAGE,
|
SECURE_STORAGE,
|
||||||
STATE_FACTORY,
|
STATE_FACTORY,
|
||||||
@ -11,6 +10,7 @@ import {
|
|||||||
OBSERVABLE_MEMORY_STORAGE,
|
OBSERVABLE_MEMORY_STORAGE,
|
||||||
OBSERVABLE_DISK_STORAGE,
|
OBSERVABLE_DISK_STORAGE,
|
||||||
WINDOW,
|
WINDOW,
|
||||||
|
SYSTEM_THEME_OBSERVABLE,
|
||||||
} from "@bitwarden/angular/services/injection-tokens";
|
} from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||||
@ -43,7 +43,7 @@ import { GlobalState } from "@bitwarden/common/platform/models/domain/global-sta
|
|||||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
import { SystemService } from "@bitwarden/common/platform/services/system.service";
|
import { SystemService } from "@bitwarden/common/platform/services/system.service";
|
||||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state";
|
||||||
// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage
|
// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage
|
||||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
@ -60,13 +60,13 @@ import { ElectronRendererSecureStorageService } from "../../platform/services/el
|
|||||||
import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service";
|
import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service";
|
||||||
import { ElectronStateService } from "../../platform/services/electron-state.service";
|
import { ElectronStateService } from "../../platform/services/electron-state.service";
|
||||||
import { I18nRendererService } from "../../platform/services/i18n.renderer.service";
|
import { I18nRendererService } from "../../platform/services/i18n.renderer.service";
|
||||||
|
import { fromIpcSystemTheme } from "../../platform/utils/from-ipc-system-theme";
|
||||||
import { EncryptedMessageHandlerService } from "../../services/encrypted-message-handler.service";
|
import { EncryptedMessageHandlerService } from "../../services/encrypted-message-handler.service";
|
||||||
import { NativeMessageHandlerService } from "../../services/native-message-handler.service";
|
import { NativeMessageHandlerService } from "../../services/native-message-handler.service";
|
||||||
import { NativeMessagingService } from "../../services/native-messaging.service";
|
import { NativeMessagingService } from "../../services/native-messaging.service";
|
||||||
import { SearchBarService } from "../layout/search/search-bar.service";
|
import { SearchBarService } from "../layout/search/search-bar.service";
|
||||||
|
|
||||||
import { DesktopFileDownloadService } from "./desktop-file-download.service";
|
import { DesktopFileDownloadService } from "./desktop-file-download.service";
|
||||||
import { DesktopThemingService } from "./desktop-theming.service";
|
|
||||||
import { InitService } from "./init.service";
|
import { InitService } from "./init.service";
|
||||||
import { RendererCryptoFunctionService } from "./renderer-crypto-function.service";
|
import { RendererCryptoFunctionService } from "./renderer-crypto-function.service";
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
|
|||||||
{
|
{
|
||||||
provide: I18nServiceAbstraction,
|
provide: I18nServiceAbstraction,
|
||||||
useClass: I18nRendererService,
|
useClass: I18nRendererService,
|
||||||
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY],
|
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY, GlobalStateProvider],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: MessagingServiceAbstraction,
|
provide: MessagingServiceAbstraction,
|
||||||
@ -149,8 +149,8 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
|
|||||||
useClass: DesktopFileDownloadService,
|
useClass: DesktopFileDownloadService,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: AbstractThemingService,
|
provide: SYSTEM_THEME_OBSERVABLE,
|
||||||
useClass: DesktopThemingService,
|
useFactory: () => fromIpcSystemTheme(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: EncryptedMessageHandlerService,
|
provide: EncryptedMessageHandlerService,
|
||||||
|
@ -404,7 +404,7 @@
|
|||||||
"message": "길이"
|
"message": "길이"
|
||||||
},
|
},
|
||||||
"passwordMinLength": {
|
"passwordMinLength": {
|
||||||
"message": "Minimum password length"
|
"message": "최소 비밀번호 길이"
|
||||||
},
|
},
|
||||||
"uppercase": {
|
"uppercase": {
|
||||||
"message": "대문자 (A-Z)"
|
"message": "대문자 (A-Z)"
|
||||||
@ -561,10 +561,10 @@
|
|||||||
"message": "계정 생성이 완료되었습니다! 이제 로그인하실 수 있습니다."
|
"message": "계정 생성이 완료되었습니다! 이제 로그인하실 수 있습니다."
|
||||||
},
|
},
|
||||||
"youSuccessfullyLoggedIn": {
|
"youSuccessfullyLoggedIn": {
|
||||||
"message": "You successfully logged in"
|
"message": "로그인에 성공했습니다."
|
||||||
},
|
},
|
||||||
"youMayCloseThisWindow": {
|
"youMayCloseThisWindow": {
|
||||||
"message": "You may close this window"
|
"message": "이제 창을 닫으실 수 있습니다."
|
||||||
},
|
},
|
||||||
"masterPassSent": {
|
"masterPassSent": {
|
||||||
"message": "마스터 비밀번호 힌트가 담긴 이메일을 보냈습니다."
|
"message": "마스터 비밀번호 힌트가 담긴 이메일을 보냈습니다."
|
||||||
@ -780,7 +780,7 @@
|
|||||||
"message": "문의하기"
|
"message": "문의하기"
|
||||||
},
|
},
|
||||||
"helpAndFeedback": {
|
"helpAndFeedback": {
|
||||||
"message": "Help and feedback"
|
"message": "도움말 및 피드백"
|
||||||
},
|
},
|
||||||
"getHelp": {
|
"getHelp": {
|
||||||
"message": "도움말"
|
"message": "도움말"
|
||||||
@ -1399,7 +1399,7 @@
|
|||||||
"message": "잘못된 PIN 코드입니다."
|
"message": "잘못된 PIN 코드입니다."
|
||||||
},
|
},
|
||||||
"tooManyInvalidPinEntryAttemptsLoggingOut": {
|
"tooManyInvalidPinEntryAttemptsLoggingOut": {
|
||||||
"message": "Too many invalid PIN entry attempts. Logging out."
|
"message": "잘못된 PIN 입력 시도가 너무 많습니다. 로그아웃 합니다."
|
||||||
},
|
},
|
||||||
"unlockWithWindowsHello": {
|
"unlockWithWindowsHello": {
|
||||||
"message": "Windows Hello를 사용하여 잠금 해제"
|
"message": "Windows Hello를 사용하여 잠금 해제"
|
||||||
@ -1889,7 +1889,7 @@
|
|||||||
"message": "Verification required for this action. Set a PIN to continue."
|
"message": "Verification required for this action. Set a PIN to continue."
|
||||||
},
|
},
|
||||||
"setPin": {
|
"setPin": {
|
||||||
"message": "Set PIN"
|
"message": "PIN 설정"
|
||||||
},
|
},
|
||||||
"verifyWithBiometrics": {
|
"verifyWithBiometrics": {
|
||||||
"message": "Verify with biometrics"
|
"message": "Verify with biometrics"
|
||||||
|
@ -97,7 +97,6 @@ export class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logService = new ElectronLogMainService(null, app.getPath("userData"));
|
this.logService = new ElectronLogMainService(null, app.getPath("userData"));
|
||||||
this.i18nService = new I18nMainService("en", "./locales/");
|
|
||||||
|
|
||||||
const storageDefaults: any = {};
|
const storageDefaults: any = {};
|
||||||
// Default vault timeout to "on restart", and action to "lock"
|
// Default vault timeout to "on restart", and action to "lock"
|
||||||
@ -112,6 +111,8 @@ export class Main {
|
|||||||
);
|
);
|
||||||
const globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
const globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
||||||
|
|
||||||
|
this.i18nService = new I18nMainService("en", "./locales/", globalStateProvider);
|
||||||
|
|
||||||
const accountService = new AccountServiceImplementation(
|
const accountService = new AccountServiceImplementation(
|
||||||
new NoopMessagingService(),
|
new NoopMessagingService(),
|
||||||
this.logService,
|
this.logService,
|
||||||
@ -218,8 +219,7 @@ export class Main {
|
|||||||
this.migrationRunner.run().then(
|
this.migrationRunner.run().then(
|
||||||
async () => {
|
async () => {
|
||||||
await this.windowMain.init();
|
await this.windowMain.init();
|
||||||
const locale = await this.stateService.getLocale();
|
await this.i18nService.init();
|
||||||
await this.i18nService.init(locale != null ? locale : app.getLocale());
|
|
||||||
this.messagingMain.init();
|
this.messagingMain.init();
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
@ -246,8 +246,7 @@ export class WindowMain {
|
|||||||
// Retrieve the background color
|
// Retrieve the background color
|
||||||
// Resolves background color missmatch when starting the application.
|
// Resolves background color missmatch when starting the application.
|
||||||
async getBackgroundColor(): Promise<string> {
|
async getBackgroundColor(): Promise<string> {
|
||||||
const data: { theme?: string } = await this.storageService.get("global");
|
let theme = await this.storageService.get("global_theming_selection");
|
||||||
let theme = data?.theme;
|
|
||||||
|
|
||||||
if (theme == null || theme === "system") {
|
if (theme == null || theme === "system") {
|
||||||
theme = nativeTheme.shouldUseDarkColors ? "dark" : "light";
|
theme = nativeTheme.shouldUseDarkColors ? "dark" : "light";
|
||||||
|
4
apps/desktop/src/package-lock.json
generated
4
apps/desktop/src/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@bitwarden/desktop",
|
"name": "@bitwarden/desktop",
|
||||||
"version": "2024.2.2",
|
"version": "2024.3.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@bitwarden/desktop",
|
"name": "@bitwarden/desktop",
|
||||||
"version": "2024.2.2",
|
"version": "2024.3.1",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bitwarden/desktop-native": "file:../desktop_native"
|
"@bitwarden/desktop-native": "file:../desktop_native"
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "@bitwarden/desktop",
|
"name": "@bitwarden/desktop",
|
||||||
"productName": "Bitwarden",
|
"productName": "Bitwarden",
|
||||||
"description": "A secure and free password manager for all of your devices.",
|
"description": "A secure and free password manager for all of your devices.",
|
||||||
"version": "2024.2.2",
|
"version": "2024.3.1",
|
||||||
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||||
"homepage": "https://bitwarden.com",
|
"homepage": "https://bitwarden.com",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
import { ipcMain } from "electron";
|
import { app, ipcMain } from "electron";
|
||||||
|
|
||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
||||||
|
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
export class I18nMainService extends BaseI18nService {
|
export class I18nMainService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string, localesDirectory: string) {
|
constructor(
|
||||||
super(systemLanguage, localesDirectory, (formattedLocale: string) =>
|
systemLanguage: string,
|
||||||
this.readLanguageFile(formattedLocale),
|
localesDirectory: string,
|
||||||
|
globalStateProvider: GlobalStateProvider,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
systemLanguage,
|
||||||
|
localesDirectory,
|
||||||
|
(formattedLocale: string) => this.readLanguageFile(formattedLocale),
|
||||||
|
globalStateProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle("getLanguageFile", async (event, formattedLocale: string) =>
|
ipcMain.handle("getLanguageFile", async (event, formattedLocale: string) =>
|
||||||
@ -76,6 +84,12 @@ export class I18nMainService extends BaseI18nService {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async init(): Promise<void> {
|
||||||
|
// Set system language to electron language
|
||||||
|
this.systemLanguage = app.getLocale();
|
||||||
|
await super.init();
|
||||||
|
}
|
||||||
|
|
||||||
private readLanguageFile(formattedLocale: string): Promise<any> {
|
private readLanguageFile(formattedLocale: string): Promise<any> {
|
||||||
// Check that the provided locale only contains letters and dashes and underscores to avoid possible path traversal
|
// Check that the provided locale only contains letters and dashes and underscores to avoid possible path traversal
|
||||||
if (!/^[a-zA-Z_-]+$/.test(formattedLocale)) {
|
if (!/^[a-zA-Z_-]+$/.test(formattedLocale)) {
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
||||||
|
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
export class I18nRendererService extends BaseI18nService {
|
export class I18nRendererService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string, localesDirectory: string) {
|
constructor(
|
||||||
super(systemLanguage, localesDirectory, (formattedLocale: string) => {
|
systemLanguage: string,
|
||||||
return ipc.platform.getLanguageFile(formattedLocale);
|
localesDirectory: string,
|
||||||
});
|
globalStateProvider: GlobalStateProvider,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
systemLanguage,
|
||||||
|
localesDirectory,
|
||||||
|
(formattedLocale: string) => {
|
||||||
|
return ipc.platform.getLanguageFile(formattedLocale);
|
||||||
|
},
|
||||||
|
globalStateProvider,
|
||||||
|
);
|
||||||
|
|
||||||
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found
|
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found
|
||||||
this.supportedTranslationLocales = [
|
this.supportedTranslationLocales = [
|
||||||
|
15
apps/desktop/src/platform/utils/from-ipc-system-theme.ts
Normal file
15
apps/desktop/src/platform/utils/from-ipc-system-theme.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { defer, fromEventPattern, merge } from "rxjs";
|
||||||
|
|
||||||
|
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns An observable watching the system theme via IPC channels
|
||||||
|
*/
|
||||||
|
export const fromIpcSystemTheme = () => {
|
||||||
|
return merge(
|
||||||
|
defer(() => ipc.platform.getSystemTheme()),
|
||||||
|
fromEventPattern<ThemeType>((handler) =>
|
||||||
|
ipc.platform.onSystemThemeUpdated((theme) => handler(theme)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
@ -1,7 +1,6 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { merge } = require("webpack-merge");
|
const { merge } = require("webpack-merge");
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
|
||||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||||
const configurator = require("./config/config");
|
const configurator = require("./config/config");
|
||||||
const { EnvironmentPlugin } = require("webpack");
|
const { EnvironmentPlugin } = require("webpack");
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { merge } = require("webpack-merge");
|
const { merge } = require("webpack-merge");
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
|
||||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||||
const configurator = require("./config/config");
|
const configurator = require("./config/config");
|
||||||
const { EnvironmentPlugin } = require("webpack");
|
const { EnvironmentPlugin } = require("webpack");
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@bitwarden/web-vault",
|
"name": "@bitwarden/web-vault",
|
||||||
"version": "2024.2.5",
|
"version": "2024.3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:oss": "webpack",
|
"build:oss": "webpack",
|
||||||
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",
|
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",
|
||||||
|
@ -16,7 +16,10 @@
|
|||||||
></i>
|
></i>
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<bit-tab-group *ngIf="!loading" [(selectedIndex)]="tabIndex">
|
<bit-tab-group
|
||||||
|
*ngIf="!loading && organization$ | async as organization"
|
||||||
|
[(selectedIndex)]="tabIndex"
|
||||||
|
>
|
||||||
<bit-tab [label]="'role' | i18n">
|
<bit-tab [label]="'role' | i18n">
|
||||||
<ng-container *ngIf="!editMode">
|
<ng-container *ngIf="!editMode">
|
||||||
<p>{{ "inviteUserDesc" | i18n }}</p>
|
<p>{{ "inviteUserDesc" | i18n }}</p>
|
||||||
@ -60,7 +63,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!flexibleCollectionsEnabled" class="tw-mb-2 tw-flex tw-items-baseline">
|
<div
|
||||||
|
*ngIf="!organization.flexibleCollections"
|
||||||
|
class="tw-mb-2 tw-flex tw-items-baseline"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
id="userTypeManager"
|
id="userTypeManager"
|
||||||
@ -116,11 +122,11 @@
|
|||||||
formControlName="type"
|
formControlName="type"
|
||||||
name="type"
|
name="type"
|
||||||
class="tw-relative tw-bottom-[-1px] tw-mr-2"
|
class="tw-relative tw-bottom-[-1px] tw-mr-2"
|
||||||
[attr.disabled]="!canUseCustomPermissions || null"
|
[attr.disabled]="!organization.useCustomPermissions || null"
|
||||||
/>
|
/>
|
||||||
<label class="tw-m-0" for="userTypeCustom">
|
<label class="tw-m-0" for="userTypeCustom">
|
||||||
{{ "custom" | i18n }}
|
{{ "custom" | i18n }}
|
||||||
<ng-container *ngIf="!canUseCustomPermissions; else enterprise">
|
<ng-container *ngIf="!organization.useCustomPermissions; else enterprise">
|
||||||
<div class="text-base tw-block tw-font-normal tw-text-muted">
|
<div class="text-base tw-block tw-font-normal tw-text-muted">
|
||||||
{{ "customDescNonEnterpriseStart" | i18n
|
{{ "customDescNonEnterpriseStart" | i18n
|
||||||
}}<a href="https://bitwarden.com/contact/" target="_blank" rel="noreferrer">{{
|
}}<a href="https://bitwarden.com/contact/" target="_blank" rel="noreferrer">{{
|
||||||
@ -138,7 +144,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<ng-container *ngIf="customUserTypeSelected">
|
<ng-container *ngIf="customUserTypeSelected">
|
||||||
<ng-container *ngIf="!flexibleCollectionsEnabled; else customPermissionsFC">
|
<ng-container *ngIf="!organization.flexibleCollections; else customPermissionsFC">
|
||||||
<h3 class="mt-4 d-flex tw-font-semibold">
|
<h3 class="mt-4 d-flex tw-font-semibold">
|
||||||
{{ "permissions" | i18n }}
|
{{ "permissions" | i18n }}
|
||||||
</h3>
|
</h3>
|
||||||
@ -365,7 +371,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="canUseSecretsManager">
|
<ng-container *ngIf="organization.useSecretsManager">
|
||||||
<h3 class="mt-4">
|
<h3 class="mt-4">
|
||||||
{{ "secretsManager" | i18n }}
|
{{ "secretsManager" | i18n }}
|
||||||
<a
|
<a
|
||||||
@ -401,14 +407,14 @@
|
|||||||
[columnHeader]="'groups' | i18n"
|
[columnHeader]="'groups' | i18n"
|
||||||
[selectorLabelText]="'selectGroups' | i18n"
|
[selectorLabelText]="'selectGroups' | i18n"
|
||||||
[emptySelectionText]="'noGroupsAdded' | i18n"
|
[emptySelectionText]="'noGroupsAdded' | i18n"
|
||||||
[flexibleCollectionsEnabled]="flexibleCollectionsEnabled"
|
[flexibleCollectionsEnabled]="organization.flexibleCollections"
|
||||||
></bit-access-selector>
|
></bit-access-selector>
|
||||||
</bit-tab>
|
</bit-tab>
|
||||||
<bit-tab [label]="'collections' | i18n">
|
<bit-tab [label]="'collections' | i18n">
|
||||||
<div *ngIf="organization.useGroups" class="tw-mb-6">
|
<div *ngIf="organization.useGroups" class="tw-mb-6">
|
||||||
{{ "userPermissionOverrideHelper" | i18n }}
|
{{ "userPermissionOverrideHelper" | i18n }}
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!flexibleCollectionsEnabled" class="tw-mb-6">
|
<div *ngIf="!organization.flexibleCollections" class="tw-mb-6">
|
||||||
<bit-form-control>
|
<bit-form-control>
|
||||||
<input type="checkbox" bitCheckbox formControlName="accessAllCollections" />
|
<input type="checkbox" bitCheckbox formControlName="accessAllCollections" />
|
||||||
<bit-label>
|
<bit-label>
|
||||||
@ -434,7 +440,7 @@
|
|||||||
[columnHeader]="'collection' | i18n"
|
[columnHeader]="'collection' | i18n"
|
||||||
[selectorLabelText]="'selectCollections' | i18n"
|
[selectorLabelText]="'selectCollections' | i18n"
|
||||||
[emptySelectionText]="'noCollectionsAdded' | i18n"
|
[emptySelectionText]="'noCollectionsAdded' | i18n"
|
||||||
[flexibleCollectionsEnabled]="flexibleCollectionsEnabled"
|
[flexibleCollectionsEnabled]="organization.flexibleCollections"
|
||||||
></bit-access-selector
|
></bit-access-selector
|
||||||
></bit-tab>
|
></bit-tab>
|
||||||
</bit-tab-group>
|
</bit-tab-group>
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||||
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
import { Component, Inject, OnDestroy } from "@angular/core";
|
||||||
import { FormBuilder, Validators } from "@angular/forms";
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
import { combineLatest, of, shareReplay, Subject, switchMap, takeUntil } from "rxjs";
|
import {
|
||||||
|
combineLatest,
|
||||||
|
firstValueFrom,
|
||||||
|
Observable,
|
||||||
|
of,
|
||||||
|
shareReplay,
|
||||||
|
Subject,
|
||||||
|
switchMap,
|
||||||
|
takeUntil,
|
||||||
|
} from "rxjs";
|
||||||
|
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||||
@ -18,7 +27,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { flagEnabled } from "../../../../../../utils/flags";
|
|
||||||
import { CollectionAdminService } from "../../../../../vault/core/collection-admin.service";
|
import { CollectionAdminService } from "../../../../../vault/core/collection-admin.service";
|
||||||
import {
|
import {
|
||||||
CollectionAccessSelectionView,
|
CollectionAccessSelectionView,
|
||||||
@ -66,7 +74,7 @@ export enum MemberDialogResult {
|
|||||||
@Component({
|
@Component({
|
||||||
templateUrl: "member-dialog.component.html",
|
templateUrl: "member-dialog.component.html",
|
||||||
})
|
})
|
||||||
export class MemberDialogComponent implements OnInit, OnDestroy {
|
export class MemberDialogComponent implements OnDestroy {
|
||||||
loading = true;
|
loading = true;
|
||||||
editMode = false;
|
editMode = false;
|
||||||
isRevoked = false;
|
isRevoked = false;
|
||||||
@ -74,12 +82,10 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
access: "all" | "selected" = "selected";
|
access: "all" | "selected" = "selected";
|
||||||
collections: CollectionView[] = [];
|
collections: CollectionView[] = [];
|
||||||
organizationUserType = OrganizationUserType;
|
organizationUserType = OrganizationUserType;
|
||||||
canUseCustomPermissions: boolean;
|
|
||||||
PermissionMode = PermissionMode;
|
PermissionMode = PermissionMode;
|
||||||
canUseSecretsManager: boolean;
|
|
||||||
showNoMasterPasswordWarning = false;
|
showNoMasterPasswordWarning = false;
|
||||||
|
|
||||||
protected organization: Organization;
|
protected organization$: Observable<Organization>;
|
||||||
protected collectionAccessItems: AccessItemView[] = [];
|
protected collectionAccessItems: AccessItemView[] = [];
|
||||||
protected groupAccessItems: AccessItemView[] = [];
|
protected groupAccessItems: AccessItemView[] = [];
|
||||||
protected tabIndex: MemberDialogTab;
|
protected tabIndex: MemberDialogTab;
|
||||||
@ -130,7 +136,6 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
private dialogRef: DialogRef<MemberDialogResult>,
|
private dialogRef: DialogRef<MemberDialogResult>,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private organizationService: OrganizationService,
|
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
// TODO: We should really look into consolidating naming conventions for these services
|
// TODO: We should really look into consolidating naming conventions for these services
|
||||||
private collectionAdminService: CollectionAdminService,
|
private collectionAdminService: CollectionAdminService,
|
||||||
@ -139,28 +144,26 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
private organizationUserService: OrganizationUserService,
|
private organizationUserService: OrganizationUserService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private configService: ConfigServiceAbstraction,
|
private configService: ConfigServiceAbstraction,
|
||||||
) {}
|
organizationService: OrganizationService,
|
||||||
|
) {
|
||||||
|
this.organization$ = organizationService
|
||||||
|
.get$(this.params.organizationId)
|
||||||
|
.pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
this.editMode = this.params.organizationUserId != null;
|
this.editMode = this.params.organizationUserId != null;
|
||||||
this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role;
|
this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role;
|
||||||
this.title = this.i18nService.t(this.editMode ? "editMember" : "inviteMember");
|
this.title = this.i18nService.t(this.editMode ? "editMember" : "inviteMember");
|
||||||
|
|
||||||
const organization$ = of(this.organizationService.get(this.params.organizationId)).pipe(
|
const groups$ = this.organization$.pipe(
|
||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
switchMap((organization) =>
|
||||||
);
|
organization.useGroups
|
||||||
const groups$ = organization$.pipe(
|
? this.groupService.getAll(this.params.organizationId)
|
||||||
switchMap((organization) => {
|
: of([] as GroupView[]),
|
||||||
if (!organization.useGroups) {
|
),
|
||||||
return of([] as GroupView[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.groupService.getAll(this.params.organizationId);
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
combineLatest({
|
combineLatest({
|
||||||
organization: organization$,
|
organization: this.organization$,
|
||||||
collections: this.collectionAdminService.getAll(this.params.organizationId),
|
collections: this.collectionAdminService.getAll(this.params.organizationId),
|
||||||
userDetails: this.params.organizationUserId
|
userDetails: this.params.organizationUserId
|
||||||
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
|
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
|
||||||
@ -169,23 +172,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
})
|
})
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
.subscribe(({ organization, collections, userDetails, groups }) => {
|
.subscribe(({ organization, collections, userDetails, groups }) => {
|
||||||
this.organization = organization;
|
this.setFormValidators(organization);
|
||||||
this.canUseCustomPermissions = organization.useCustomPermissions;
|
|
||||||
this.canUseSecretsManager = organization.useSecretsManager && flagEnabled("secretsManager");
|
|
||||||
|
|
||||||
const emailsControlValidators = [
|
|
||||||
Validators.required,
|
|
||||||
commaSeparatedEmails,
|
|
||||||
orgSeatLimitReachedValidator(
|
|
||||||
this.organization,
|
|
||||||
this.params.allOrganizationUserEmails,
|
|
||||||
this.i18nService.t("subscriptionUpgrade", organization.seats),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
const emailsControl = this.formGroup.get("emails");
|
|
||||||
emailsControl.setValidators(emailsControlValidators);
|
|
||||||
emailsControl.updateValueAndValidity();
|
|
||||||
|
|
||||||
this.collectionAccessItems = [].concat(
|
this.collectionAccessItems = [].concat(
|
||||||
collections.map((c) => mapCollectionToAccessItemView(c)),
|
collections.map((c) => mapCollectionToAccessItemView(c)),
|
||||||
@ -196,77 +183,101 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (this.params.organizationUserId) {
|
if (this.params.organizationUserId) {
|
||||||
if (!userDetails) {
|
this.loadOrganizationUser(userDetails, groups, collections);
|
||||||
throw new Error("Could not find user to edit.");
|
|
||||||
}
|
|
||||||
this.isRevoked = userDetails.status === OrganizationUserStatusType.Revoked;
|
|
||||||
this.showNoMasterPasswordWarning =
|
|
||||||
userDetails.status > OrganizationUserStatusType.Invited &&
|
|
||||||
userDetails.hasMasterPassword === false;
|
|
||||||
const assignedCollectionsPermissions = {
|
|
||||||
editAssignedCollections: userDetails.permissions.editAssignedCollections,
|
|
||||||
deleteAssignedCollections: userDetails.permissions.deleteAssignedCollections,
|
|
||||||
manageAssignedCollections:
|
|
||||||
userDetails.permissions.editAssignedCollections &&
|
|
||||||
userDetails.permissions.deleteAssignedCollections,
|
|
||||||
};
|
|
||||||
const allCollectionsPermissions = {
|
|
||||||
createNewCollections: userDetails.permissions.createNewCollections,
|
|
||||||
editAnyCollection: userDetails.permissions.editAnyCollection,
|
|
||||||
deleteAnyCollection: userDetails.permissions.deleteAnyCollection,
|
|
||||||
manageAllCollections:
|
|
||||||
userDetails.permissions.createNewCollections &&
|
|
||||||
userDetails.permissions.editAnyCollection &&
|
|
||||||
userDetails.permissions.deleteAnyCollection,
|
|
||||||
};
|
|
||||||
if (userDetails.type === OrganizationUserType.Custom) {
|
|
||||||
this.permissionsGroup.patchValue({
|
|
||||||
accessEventLogs: userDetails.permissions.accessEventLogs,
|
|
||||||
accessImportExport: userDetails.permissions.accessImportExport,
|
|
||||||
accessReports: userDetails.permissions.accessReports,
|
|
||||||
manageGroups: userDetails.permissions.manageGroups,
|
|
||||||
manageSso: userDetails.permissions.manageSso,
|
|
||||||
managePolicies: userDetails.permissions.managePolicies,
|
|
||||||
manageUsers: userDetails.permissions.manageUsers,
|
|
||||||
manageResetPassword: userDetails.permissions.manageResetPassword,
|
|
||||||
manageAssignedCollectionsGroup: assignedCollectionsPermissions,
|
|
||||||
manageAllCollectionsGroup: allCollectionsPermissions,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectionsFromGroups = groups
|
|
||||||
.filter((group) => userDetails.groups.includes(group.id))
|
|
||||||
.flatMap((group) =>
|
|
||||||
group.collections.map((accessSelection) => {
|
|
||||||
const collection = collections.find((c) => c.id === accessSelection.id);
|
|
||||||
return { group, collection, accessSelection };
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.collectionAccessItems = this.collectionAccessItems.concat(
|
|
||||||
collectionsFromGroups.map(({ collection, accessSelection, group }) =>
|
|
||||||
mapCollectionToAccessItemView(collection, accessSelection, group),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const accessSelections = mapToAccessSelections(userDetails);
|
|
||||||
const groupAccessSelections = mapToGroupAccessSelections(userDetails.groups);
|
|
||||||
|
|
||||||
this.formGroup.removeControl("emails");
|
|
||||||
this.formGroup.patchValue({
|
|
||||||
type: userDetails.type,
|
|
||||||
externalId: userDetails.externalId,
|
|
||||||
accessAllCollections: userDetails.accessAll,
|
|
||||||
access: accessSelections,
|
|
||||||
accessSecretsManager: userDetails.accessSecretsManager,
|
|
||||||
groups: groupAccessSelections,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setFormValidators(organization: Organization) {
|
||||||
|
const emailsControlValidators = [
|
||||||
|
Validators.required,
|
||||||
|
commaSeparatedEmails,
|
||||||
|
orgSeatLimitReachedValidator(
|
||||||
|
organization,
|
||||||
|
this.params.allOrganizationUserEmails,
|
||||||
|
this.i18nService.t("subscriptionUpgrade", organization.seats),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const emailsControl = this.formGroup.get("emails");
|
||||||
|
emailsControl.setValidators(emailsControlValidators);
|
||||||
|
emailsControl.updateValueAndValidity();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadOrganizationUser(
|
||||||
|
userDetails: OrganizationUserAdminView,
|
||||||
|
groups: GroupView[],
|
||||||
|
collections: CollectionView[],
|
||||||
|
) {
|
||||||
|
if (!userDetails) {
|
||||||
|
throw new Error("Could not find user to edit.");
|
||||||
|
}
|
||||||
|
this.isRevoked = userDetails.status === OrganizationUserStatusType.Revoked;
|
||||||
|
this.showNoMasterPasswordWarning =
|
||||||
|
userDetails.status > OrganizationUserStatusType.Invited &&
|
||||||
|
userDetails.hasMasterPassword === false;
|
||||||
|
const assignedCollectionsPermissions = {
|
||||||
|
editAssignedCollections: userDetails.permissions.editAssignedCollections,
|
||||||
|
deleteAssignedCollections: userDetails.permissions.deleteAssignedCollections,
|
||||||
|
manageAssignedCollections:
|
||||||
|
userDetails.permissions.editAssignedCollections &&
|
||||||
|
userDetails.permissions.deleteAssignedCollections,
|
||||||
|
};
|
||||||
|
const allCollectionsPermissions = {
|
||||||
|
createNewCollections: userDetails.permissions.createNewCollections,
|
||||||
|
editAnyCollection: userDetails.permissions.editAnyCollection,
|
||||||
|
deleteAnyCollection: userDetails.permissions.deleteAnyCollection,
|
||||||
|
manageAllCollections:
|
||||||
|
userDetails.permissions.createNewCollections &&
|
||||||
|
userDetails.permissions.editAnyCollection &&
|
||||||
|
userDetails.permissions.deleteAnyCollection,
|
||||||
|
};
|
||||||
|
if (userDetails.type === OrganizationUserType.Custom) {
|
||||||
|
this.permissionsGroup.patchValue({
|
||||||
|
accessEventLogs: userDetails.permissions.accessEventLogs,
|
||||||
|
accessImportExport: userDetails.permissions.accessImportExport,
|
||||||
|
accessReports: userDetails.permissions.accessReports,
|
||||||
|
manageGroups: userDetails.permissions.manageGroups,
|
||||||
|
manageSso: userDetails.permissions.manageSso,
|
||||||
|
managePolicies: userDetails.permissions.managePolicies,
|
||||||
|
manageUsers: userDetails.permissions.manageUsers,
|
||||||
|
manageResetPassword: userDetails.permissions.manageResetPassword,
|
||||||
|
manageAssignedCollectionsGroup: assignedCollectionsPermissions,
|
||||||
|
manageAllCollectionsGroup: allCollectionsPermissions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionsFromGroups = groups
|
||||||
|
.filter((group) => userDetails.groups.includes(group.id))
|
||||||
|
.flatMap((group) =>
|
||||||
|
group.collections.map((accessSelection) => {
|
||||||
|
const collection = collections.find((c) => c.id === accessSelection.id);
|
||||||
|
return { group, collection, accessSelection };
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.collectionAccessItems = this.collectionAccessItems.concat(
|
||||||
|
collectionsFromGroups.map(({ collection, accessSelection, group }) =>
|
||||||
|
mapCollectionToAccessItemView(collection, accessSelection, group),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const accessSelections = mapToAccessSelections(userDetails);
|
||||||
|
const groupAccessSelections = mapToGroupAccessSelections(userDetails.groups);
|
||||||
|
|
||||||
|
this.formGroup.removeControl("emails");
|
||||||
|
this.formGroup.patchValue({
|
||||||
|
type: userDetails.type,
|
||||||
|
externalId: userDetails.externalId,
|
||||||
|
accessAllCollections: userDetails.accessAll,
|
||||||
|
access: accessSelections,
|
||||||
|
accessSecretsManager: userDetails.accessSecretsManager,
|
||||||
|
groups: groupAccessSelections,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
check(c: CollectionView, select?: boolean) {
|
check(c: CollectionView, select?: boolean) {
|
||||||
(c as any).checked = select == null ? !(c as any).checked : select;
|
(c as any).checked = select == null ? !(c as any).checked : select;
|
||||||
if (!(c as any).checked) {
|
if (!(c as any).checked) {
|
||||||
@ -335,7 +346,9 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.canUseCustomPermissions && this.customUserTypeSelected) {
|
const organization = await firstValueFrom(this.organization$);
|
||||||
|
|
||||||
|
if (!organization.useCustomPermissions && this.customUserTypeSelected) {
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"error",
|
"error",
|
||||||
null,
|
null,
|
||||||
@ -363,8 +376,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
await this.userService.save(userView);
|
await this.userService.save(userView);
|
||||||
} else {
|
} else {
|
||||||
userView.id = this.params.organizationUserId;
|
userView.id = this.params.organizationUserId;
|
||||||
const maxEmailsCount =
|
const maxEmailsCount = organization.planProductType === ProductType.TeamsStarter ? 10 : 20;
|
||||||
this.organization.planProductType === ProductType.TeamsStarter ? 10 : 20;
|
|
||||||
const emails = [...new Set(this.formGroup.value.emails.trim().split(/\s*,\s*/))];
|
const emails = [...new Set(this.formGroup.value.emails.trim().split(/\s*,\s*/))];
|
||||||
if (emails.length > maxEmailsCount) {
|
if (emails.length > maxEmailsCount) {
|
||||||
this.formGroup.controls.emails.setErrors({
|
this.formGroup.controls.emails.setErrors({
|
||||||
@ -373,8 +385,8 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
this.organization.hasReseller &&
|
organization.hasReseller &&
|
||||||
this.params.numConfirmedMembers + emails.length > this.organization.seats
|
this.params.numConfirmedMembers + emails.length > organization.seats
|
||||||
) {
|
) {
|
||||||
this.formGroup.controls.emails.setErrors({
|
this.formGroup.controls.emails.setErrors({
|
||||||
tooManyEmails: { message: this.i18nService.t("seatLimitReachedContactYourProvider") },
|
tooManyEmails: { message: this.i18nService.t("seatLimitReachedContactYourProvider") },
|
||||||
@ -515,10 +527,6 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get flexibleCollectionsEnabled() {
|
|
||||||
return this.organization?.flexibleCollections;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly ProductType = ProductType;
|
protected readonly ProductType = ProductType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +275,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.syncService.setLastSync(new Date(0)),
|
this.syncService.setLastSync(new Date(0)),
|
||||||
this.cryptoService.clearKeys(),
|
this.cryptoService.clearKeys(),
|
||||||
this.settingsService.clear(userId),
|
|
||||||
this.cipherService.clear(userId),
|
this.cipherService.clear(userId),
|
||||||
this.folderService.clear(userId),
|
this.folderService.clear(userId),
|
||||||
this.collectionService.clear(userId),
|
this.collectionService.clear(userId),
|
||||||
|
@ -23,14 +23,20 @@ import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/comm
|
|||||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
/* eslint-disable import/no-restricted-paths -- Implementation for memory storage */
|
/* eslint-disable import/no-restricted-paths -- Implementation for memory storage */
|
||||||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
|
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||||
/* eslint-enable import/no-restricted-paths -- Implementation for memory storage */
|
/* eslint-enable import/no-restricted-paths -- Implementation for memory storage */
|
||||||
|
import {
|
||||||
|
DefaultThemeStateService,
|
||||||
|
ThemeStateService,
|
||||||
|
} from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
|
|
||||||
import { PolicyListService } from "../admin-console/core/policy-list.service";
|
import { PolicyListService } from "../admin-console/core/policy-list.service";
|
||||||
import { HtmlStorageService } from "../core/html-storage.service";
|
import { HtmlStorageService } from "../core/html-storage.service";
|
||||||
@ -74,7 +80,7 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service";
|
|||||||
{
|
{
|
||||||
provide: I18nServiceAbstraction,
|
provide: I18nServiceAbstraction,
|
||||||
useClass: I18nService,
|
useClass: I18nService,
|
||||||
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY],
|
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY, GlobalStateProvider],
|
||||||
},
|
},
|
||||||
{ provide: AbstractStorageService, useClass: HtmlStorageService },
|
{ provide: AbstractStorageService, useClass: HtmlStorageService },
|
||||||
{
|
{
|
||||||
@ -132,6 +138,13 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service";
|
|||||||
OBSERVABLE_DISK_LOCAL_STORAGE,
|
OBSERVABLE_DISK_LOCAL_STORAGE,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: ThemeStateService,
|
||||||
|
useFactory: (globalStateProvider: GlobalStateProvider) =>
|
||||||
|
// Web chooses to have Light as the default theme
|
||||||
|
new DefaultThemeStateService(globalStateProvider, ThemeType.Light),
|
||||||
|
deps: [GlobalStateProvider],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreModule {
|
export class CoreModule {
|
||||||
|
@ -1,20 +1,30 @@
|
|||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
||||||
|
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
import { SupportedTranslationLocales } from "../../translation-constants";
|
import { SupportedTranslationLocales } from "../../translation-constants";
|
||||||
|
|
||||||
export class I18nService extends BaseI18nService {
|
export class I18nService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string, localesDirectory: string) {
|
constructor(
|
||||||
super(systemLanguage || "en-US", localesDirectory, async (formattedLocale: string) => {
|
systemLanguage: string,
|
||||||
const filePath =
|
localesDirectory: string,
|
||||||
this.localesDirectory +
|
globalStateProvider: GlobalStateProvider,
|
||||||
"/" +
|
) {
|
||||||
formattedLocale +
|
super(
|
||||||
"/messages.json?cache=" +
|
systemLanguage || "en-US",
|
||||||
process.env.CACHE_TAG;
|
localesDirectory,
|
||||||
const localesResult = await fetch(filePath);
|
async (formattedLocale: string) => {
|
||||||
const locales = await localesResult.json();
|
const filePath =
|
||||||
return locales;
|
this.localesDirectory +
|
||||||
});
|
"/" +
|
||||||
|
formattedLocale +
|
||||||
|
"/messages.json?cache=" +
|
||||||
|
process.env.CACHE_TAG;
|
||||||
|
const localesResult = await fetch(filePath);
|
||||||
|
const locales = await localesResult.json();
|
||||||
|
return locales;
|
||||||
|
},
|
||||||
|
globalStateProvider,
|
||||||
|
);
|
||||||
|
|
||||||
this.supportedTranslationLocales = SupportedTranslationLocales;
|
this.supportedTranslationLocales = SupportedTranslationLocales;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { DOCUMENT } from "@angular/common";
|
||||||
import { Inject, Injectable } from "@angular/core";
|
import { Inject, Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
||||||
@ -18,8 +19,6 @@ import { ContainerService } from "@bitwarden/common/platform/services/container.
|
|||||||
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
|
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
|
||||||
import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service";
|
import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service";
|
||||||
|
|
||||||
import { I18nService } from "../core/i18n.service";
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InitService {
|
export class InitService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -35,6 +34,7 @@ export class InitService {
|
|||||||
private themingService: AbstractThemingService,
|
private themingService: AbstractThemingService,
|
||||||
private encryptService: EncryptService,
|
private encryptService: EncryptService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
|
@Inject(DOCUMENT) private document: Document,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@ -50,13 +50,12 @@ export class InitService {
|
|||||||
|
|
||||||
setTimeout(() => this.notificationsService.init(), 3000);
|
setTimeout(() => this.notificationsService.init(), 3000);
|
||||||
await this.vaultTimeoutService.init(true);
|
await this.vaultTimeoutService.init(true);
|
||||||
const locale = await this.stateService.getLocale();
|
await this.i18nService.init();
|
||||||
await (this.i18nService as I18nService).init(locale);
|
|
||||||
(this.eventUploadService as EventUploadService).init(true);
|
(this.eventUploadService as EventUploadService).init(true);
|
||||||
this.twoFactorService.init();
|
this.twoFactorService.init();
|
||||||
const htmlEl = this.win.document.documentElement;
|
const htmlEl = this.win.document.documentElement;
|
||||||
htmlEl.classList.add("locale_" + this.i18nService.translationLocale);
|
htmlEl.classList.add("locale_" + this.i18nService.translationLocale);
|
||||||
await this.themingService.monitorThemeChanges();
|
this.themingService.applyThemeChangesTo(this.document);
|
||||||
const containerService = new ContainerService(this.cryptoService, this.encryptService);
|
const containerService = new ContainerService(this.cryptoService, this.encryptService);
|
||||||
containerService.attachToGlobal(this.win);
|
containerService.attachToGlobal(this.win);
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
|
||||||
import { GlobalState as BaseGlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
import { GlobalState as BaseGlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||||
|
|
||||||
export class GlobalState extends BaseGlobalState {
|
export class GlobalState extends BaseGlobalState {
|
||||||
theme?: ThemeType = ThemeType.Light;
|
|
||||||
rememberEmail = true;
|
rememberEmail = true;
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,24 @@
|
|||||||
import { APP_INITIALIZER, NgModule } from "@angular/core";
|
import { APP_INITIALIZER, NgModule } from "@angular/core";
|
||||||
|
import { Observable, of } from "rxjs";
|
||||||
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { TranslationService } from "@bitwarden/common/platform/services/translation.service";
|
||||||
|
|
||||||
import eng from "../../../locales/en/messages.json";
|
import eng from "../../../locales/en/messages.json";
|
||||||
|
|
||||||
class PreloadedEnglishI18nService extends BaseI18nService {
|
class PreloadedEnglishI18nService extends TranslationService implements I18nService {
|
||||||
|
translationLocale = "en";
|
||||||
|
userSetLocale$: Observable<string | undefined> = of("en");
|
||||||
|
locale$: Observable<string> = of("en");
|
||||||
constructor() {
|
constructor() {
|
||||||
super("en", "", () => {
|
super("en", "", () => {
|
||||||
return Promise.resolve(eng);
|
return Promise.resolve(eng);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLocale(): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function i18nInitializer(i18nService: I18nService): () => Promise<void> {
|
function i18nInitializer(i18nService: I18nService): () => Promise<void> {
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
[ariaLabel]="['organization' | i18n, activeOrganization.name].join(' ')"
|
[ariaLabel]="['organization' | i18n, activeOrganization.name].join(' ')"
|
||||||
icon="bwi-business"
|
icon="bwi-business"
|
||||||
[route]="['../', activeOrganization.id]"
|
[route]="['../', activeOrganization.id]"
|
||||||
|
[routerLinkActiveOptions]="{ exact: true }"
|
||||||
[(open)]="open"
|
[(open)]="open"
|
||||||
[exactMatch]="true"
|
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
slot="end"
|
slot="end"
|
||||||
@ -21,7 +21,7 @@
|
|||||||
[ariaLabel]="['organization' | i18n, org.name].join(' ')"
|
[ariaLabel]="['organization' | i18n, org.name].join(' ')"
|
||||||
[route]="['../', org.id]"
|
[route]="['../', org.id]"
|
||||||
(mainContentClicked)="toggle()"
|
(mainContentClicked)="toggle()"
|
||||||
[exactMatch]="true"
|
[routerLinkActiveOptions]="{ exact: true }"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
slot="end"
|
slot="end"
|
||||||
|
@ -2,7 +2,6 @@ import { Component, OnInit } from "@angular/core";
|
|||||||
import { FormBuilder } from "@angular/forms";
|
import { FormBuilder } from "@angular/forms";
|
||||||
import { concatMap, filter, firstValueFrom, map, Observable, Subject, takeUntil, tap } from "rxjs";
|
import { concatMap, filter, firstValueFrom, map, Observable, Subject, takeUntil, tap } from "rxjs";
|
||||||
|
|
||||||
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
|
||||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
||||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
@ -14,6 +13,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -35,7 +35,6 @@ export class PreferencesComponent implements OnInit {
|
|||||||
themeOptions: any[];
|
themeOptions: any[];
|
||||||
|
|
||||||
private startingLocale: string;
|
private startingLocale: string;
|
||||||
private startingTheme: ThemeType;
|
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
form = this.formBuilder.group({
|
form = this.formBuilder.group({
|
||||||
@ -54,7 +53,7 @@ export class PreferencesComponent implements OnInit {
|
|||||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
private themingService: AbstractThemingService,
|
private themeStateService: ThemeStateService,
|
||||||
private settingsService: SettingsService,
|
private settingsService: SettingsService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
) {
|
) {
|
||||||
@ -141,11 +140,10 @@ export class PreferencesComponent implements OnInit {
|
|||||||
this.vaultTimeoutSettingsService.vaultTimeoutAction$(),
|
this.vaultTimeoutSettingsService.vaultTimeoutAction$(),
|
||||||
),
|
),
|
||||||
enableFavicons: !(await this.settingsService.getDisableFavicon()),
|
enableFavicons: !(await this.settingsService.getDisableFavicon()),
|
||||||
theme: await this.stateService.getTheme(),
|
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
|
||||||
locale: (await this.stateService.getLocale()) ?? null,
|
locale: (await firstValueFrom(this.i18nService.userSetLocale$)) ?? null,
|
||||||
};
|
};
|
||||||
this.startingLocale = initialFormValues.locale;
|
this.startingLocale = initialFormValues.locale;
|
||||||
this.startingTheme = initialFormValues.theme;
|
|
||||||
this.form.setValue(initialFormValues, { emitEvent: false });
|
this.form.setValue(initialFormValues, { emitEvent: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,11 +163,8 @@ export class PreferencesComponent implements OnInit {
|
|||||||
values.vaultTimeoutAction,
|
values.vaultTimeoutAction,
|
||||||
);
|
);
|
||||||
await this.settingsService.setDisableFavicon(!values.enableFavicons);
|
await this.settingsService.setDisableFavicon(!values.enableFavicons);
|
||||||
if (values.theme !== this.startingTheme) {
|
await this.themeStateService.setSelectedTheme(values.theme);
|
||||||
await this.themingService.updateConfiguredTheme(values.theme);
|
await this.i18nService.setLocale(values.locale);
|
||||||
this.startingTheme = values.theme;
|
|
||||||
}
|
|
||||||
await this.stateService.setLocale(values.locale);
|
|
||||||
if (values.locale !== this.startingLocale) {
|
if (values.locale !== this.startingLocale) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Toegang"
|
"message": "Toegang"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Access level"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Uitgeteken"
|
"message": "Uitgeteken"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "وصول"
|
"message": "وصول"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Access level"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "تم تسجيل الخروج"
|
"message": "تم تسجيل الخروج"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Müraciət"
|
"message": "Müraciət"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Müraciət səviyyəsi"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Çıxış edildi"
|
"message": "Çıxış edildi"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Доступ"
|
"message": "Доступ"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Access level"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Вы выйшлі"
|
"message": "Вы выйшлі"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Достъп"
|
"message": "Достъп"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Ниво на достъп"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Бяхте отписани"
|
"message": "Бяхте отписани"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Access"
|
"message": "Access"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Access level"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Logged out"
|
"message": "Logged out"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Pristup"
|
"message": "Pristup"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Access level"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Logged out"
|
"message": "Logged out"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Accés"
|
"message": "Accés"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Access level"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Sessió tancada"
|
"message": "Sessió tancada"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Přístup"
|
"message": "Přístup"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Úroveň přístupu"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Odhlášení"
|
"message": "Odhlášení"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Access"
|
"message": "Access"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Access level"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Logged out"
|
"message": "Logged out"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Adgang"
|
"message": "Adgang"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Adgangsniveau"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Logget ud"
|
"message": "Logget ud"
|
||||||
},
|
},
|
||||||
@ -669,7 +672,7 @@
|
|||||||
"message": "Kryptering ikke understøttet"
|
"message": "Kryptering ikke understøttet"
|
||||||
},
|
},
|
||||||
"enablePasskeyEncryption": {
|
"enablePasskeyEncryption": {
|
||||||
"message": "Set up encryption"
|
"message": "Opsæt kryptering"
|
||||||
},
|
},
|
||||||
"usedForEncryption": {
|
"usedForEncryption": {
|
||||||
"message": "Bruges til kryptering"
|
"message": "Bruges til kryptering"
|
||||||
@ -5937,10 +5940,10 @@
|
|||||||
"message": "Start DUO og følg trinene for at fuldføre indlogningen."
|
"message": "Start DUO og følg trinene for at fuldføre indlogningen."
|
||||||
},
|
},
|
||||||
"duoRequiredByOrgForAccount": {
|
"duoRequiredByOrgForAccount": {
|
||||||
"message": "DUO totrins indlogning kræves for kontoen."
|
"message": "Duo totrinsindlogning kræves for kontoen."
|
||||||
},
|
},
|
||||||
"launchDuo": {
|
"launchDuo": {
|
||||||
"message": "Start DUO"
|
"message": "Start Duo"
|
||||||
},
|
},
|
||||||
"turnOn": {
|
"turnOn": {
|
||||||
"message": "Slå til"
|
"message": "Slå til"
|
||||||
@ -6731,7 +6734,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"teamsStarterPlanInvLimitReachedManageBilling": {
|
"teamsStarterPlanInvLimitReachedManageBilling": {
|
||||||
"message": "Teams Starter plans may have up to $SEATCOUNT$ members. Upgrade to your plan to invite more members.",
|
"message": "Teams Starter kan have op til $SEATCOUNT$ medlemmer. Opgradér til en betalt abonnementstype for at invitere flere deltagerer.",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"seatcount": {
|
"seatcount": {
|
||||||
"content": "$1",
|
"content": "$1",
|
||||||
@ -7474,7 +7477,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"seeDetailedInstructions": {
|
"seeDetailedInstructions": {
|
||||||
"message": "See detailed instructions on our help site at",
|
"message": "Se detaljeret vejledning på vores hjælpewebsted på",
|
||||||
"description": "This is followed a by a hyperlink to the help website."
|
"description": "This is followed a by a hyperlink to the help website."
|
||||||
},
|
},
|
||||||
"installBrowserExtension": {
|
"installBrowserExtension": {
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Zugriff"
|
"message": "Zugriff"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Zugriffsebene"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Ausgeloggt"
|
"message": "Ausgeloggt"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Πρόσβαση"
|
"message": "Πρόσβαση"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Access level"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Αποσυνδεθήκατε"
|
"message": "Αποσυνδεθήκατε"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Access"
|
"message": "Access"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Access level"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Logged out"
|
"message": "Logged out"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Access"
|
"message": "Access"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Access level"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Logged out"
|
"message": "Logged out"
|
||||||
},
|
},
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Access"
|
"message": "Access"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Access level"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Adiaŭita"
|
"message": "Adiaŭita"
|
||||||
},
|
},
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user