mirror of
https://github.com/bitwarden/browser
synced 2026-02-19 19:04:01 +00:00
Merge branch 'main' of github.com:bitwarden/clients into arch/remove-gulp
# Conflicts: # package-lock.json
This commit is contained in:
16
.github/renovate.json
vendored
16
.github/renovate.json
vendored
@@ -110,6 +110,8 @@
|
||||
},
|
||||
{
|
||||
"matchPackageNames": [
|
||||
"@electron/notarize",
|
||||
"@electron/rebuild",
|
||||
"@types/argon2-browser",
|
||||
"@types/chrome",
|
||||
"@types/firefox-webext-browser",
|
||||
@@ -119,6 +121,12 @@
|
||||
"argon2",
|
||||
"argon2-browser",
|
||||
"big-integer",
|
||||
"electron-builder",
|
||||
"electron-log",
|
||||
"electron-reload",
|
||||
"electron-store",
|
||||
"electron-updater",
|
||||
"electron",
|
||||
"node-forge",
|
||||
"rxjs",
|
||||
"type-fest",
|
||||
@@ -197,19 +205,11 @@
|
||||
},
|
||||
{
|
||||
"matchPackageNames": [
|
||||
"@electron/notarize",
|
||||
"@electron/rebuild",
|
||||
"@microsoft/signalr-protocol-msgpack",
|
||||
"@microsoft/signalr",
|
||||
"@types/jsdom",
|
||||
"@types/papaparse",
|
||||
"@types/zxcvbn",
|
||||
"electron-builder",
|
||||
"electron-log",
|
||||
"electron-reload",
|
||||
"electron-store",
|
||||
"electron-updater",
|
||||
"electron",
|
||||
"jsdom",
|
||||
"jszip",
|
||||
"oidc-client-ts",
|
||||
|
||||
@@ -1,124 +1,130 @@
|
||||
name: Version Bump
|
||||
name: Repository management
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
task:
|
||||
default: "Version Bump"
|
||||
description: "Task to execute"
|
||||
options:
|
||||
- "Version Bump"
|
||||
- "Version Bump and Cut rc"
|
||||
required: true
|
||||
type: choice
|
||||
bump_browser:
|
||||
description: "Bump Browser?"
|
||||
description: "Bump Browser version?"
|
||||
type: boolean
|
||||
default: false
|
||||
bump_cli:
|
||||
description: "Bump CLI?"
|
||||
description: "Bump CLI version?"
|
||||
type: boolean
|
||||
default: false
|
||||
bump_desktop:
|
||||
description: "Bump Desktop?"
|
||||
description: "Bump Desktop version?"
|
||||
type: boolean
|
||||
default: false
|
||||
bump_web:
|
||||
description: "Bump Web?"
|
||||
description: "Bump Web version?"
|
||||
type: boolean
|
||||
default: false
|
||||
target_ref:
|
||||
default: "main"
|
||||
description: "Branch/Tag to target for cut"
|
||||
required: true
|
||||
type: string
|
||||
version_number_override:
|
||||
description: "New version override (leave blank for automatic calculation, example: '2024.1.0')"
|
||||
required: false
|
||||
type: string
|
||||
cut_rc_branch:
|
||||
description: "Cut RC branch?"
|
||||
default: true
|
||||
type: boolean
|
||||
enable_slack_notification:
|
||||
description: "Enable Slack notifications for upcoming release?"
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
name: Setup
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
branch: ${{ steps.set-branch.outputs.branch }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
steps:
|
||||
- name: Set branch
|
||||
id: set-branch
|
||||
env:
|
||||
TASK: ${{ inputs.task }}
|
||||
run: |
|
||||
if [[ "$TASK" == "Version Bump" ]]; then
|
||||
BRANCH="none"
|
||||
elif [[ "$TASK" == "Version Bump and Cut rc" ]]; then
|
||||
BRANCH="rc"
|
||||
fi
|
||||
|
||||
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
private-key: ${{ secrets.BW_GHAPP_KEY }}
|
||||
|
||||
|
||||
cut_branch:
|
||||
name: Cut branch
|
||||
if: ${{ needs.setup.outputs.branch == 'rc' }}
|
||||
needs: setup
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out target ref
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: ${{ inputs.target_ref }}
|
||||
token: ${{ needs.setup.outputs.token }}
|
||||
|
||||
- name: Check if ${{ needs.setup.outputs.branch }} branch exists
|
||||
env:
|
||||
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
|
||||
run: |
|
||||
if [[ $(git ls-remote --heads origin $BRANCH_NAME) ]]; then
|
||||
echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Cut branch
|
||||
env:
|
||||
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
|
||||
run: |
|
||||
git switch --quiet --create $BRANCH_NAME
|
||||
git push --quiet --set-upstream origin $BRANCH_NAME
|
||||
|
||||
|
||||
bump_version:
|
||||
name: Bump Version
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ always() }}
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- cut_branch
|
||||
- setup
|
||||
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:
|
||||
- name: Validate version input
|
||||
- name: Validate version input format
|
||||
if: ${{ inputs.version_number_override != '' }}
|
||||
uses: bitwarden/gh-actions/version-check@main
|
||||
with:
|
||||
version: ${{ inputs.version_number_override }}
|
||||
|
||||
- name: Slack Notification Check
|
||||
run: |
|
||||
if [[ "${{ inputs.enable_slack_notification }}" == true ]]; then
|
||||
echo "Slack notifications enabled."
|
||||
else
|
||||
echo "Slack notifications disabled."
|
||||
fi
|
||||
|
||||
- name: Checkout Branch
|
||||
- name: Check out branch
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ needs.setup.outputs.token }}
|
||||
|
||||
- name: Check if RC branch exists
|
||||
if: ${{ inputs.cut_rc_branch == true }}
|
||||
- name: Configure Git
|
||||
run: |
|
||||
remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
|
||||
if [[ "${remote_rc_branch_check}" -gt 0 ]]; then
|
||||
echo "Remote RC branch exists."
|
||||
echo "Please delete current RC branch before running again."
|
||||
exit 1
|
||||
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"
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@cb9bde2e2525e640591a934b1fd28eef1dcaf5e5 # v6.2.0
|
||||
with:
|
||||
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
|
||||
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
||||
git_user_signingkey: 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
|
||||
id: create-branch
|
||||
run: |
|
||||
CLIENTS=()
|
||||
if [[ ${{ inputs.bump_browser }} == true ]]; then
|
||||
CLIENTS+=("browser")
|
||||
fi
|
||||
if [[ ${{ inputs.bump_cli }} == true ]]; then
|
||||
CLIENTS+=("cli")
|
||||
fi
|
||||
if [[ ${{ inputs.bump_desktop }} == true ]]; then
|
||||
CLIENTS+=("desktop")
|
||||
fi
|
||||
if [[ ${{ inputs.bump_web }} == true ]]; then
|
||||
CLIENTS+=("web")
|
||||
fi
|
||||
printf -v joined '%s,' "${CLIENTS[@]}"
|
||||
echo "client=${joined%,}" >> $GITHUB_OUTPUT
|
||||
|
||||
NAME=version_bump_${{ github.ref_name }}_$(date +"%Y-%m-%d")
|
||||
git switch -c $NAME
|
||||
echo "name=$NAME" >> $GITHUB_OUTPUT
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "Github Actions"
|
||||
|
||||
########################
|
||||
# VERSION BUMP SECTION #
|
||||
@@ -165,7 +171,9 @@ jobs:
|
||||
- name: Bump Browser Version - Version Override
|
||||
if: ${{ inputs.bump_browser == true && inputs.version_number_override != '' }}
|
||||
id: bump-browser-version-override
|
||||
run: npm version --workspace=@bitwarden/browser ${{ inputs.version_number_override }}
|
||||
env:
|
||||
VERSION: ${{ inputs.version_number_override }}
|
||||
run: npm version --workspace=@bitwarden/browser $VERSION
|
||||
|
||||
- name: Bump Browser Version - Automatic Calculation
|
||||
if: ${{ inputs.bump_browser == true && inputs.version_number_override == '' }}
|
||||
@@ -250,7 +258,9 @@ jobs:
|
||||
- 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 }}
|
||||
env:
|
||||
VERSION: ${{ inputs.version_number_override }}
|
||||
run: npm version --workspace=@bitwarden/cli $VERSION
|
||||
|
||||
- name: Bump CLI Version - Automatic Calculation
|
||||
if: ${{ inputs.bump_cli == true && inputs.version_number_override == '' }}
|
||||
@@ -300,7 +310,9 @@ jobs:
|
||||
- name: Bump Desktop Version - Root - Version Override
|
||||
if: ${{ inputs.bump_desktop == true && inputs.version_number_override != '' }}
|
||||
id: bump-desktop-version-override
|
||||
run: npm version --workspace=@bitwarden/desktop ${{ inputs.version_number_override }}
|
||||
env:
|
||||
VERSION: ${{ inputs.version_number_override }}
|
||||
run: npm version --workspace=@bitwarden/desktop $VERSION
|
||||
|
||||
- name: Bump Desktop Version - Root - Automatic Calculation
|
||||
if: ${{ inputs.bump_desktop == true && inputs.version_number_override == '' }}
|
||||
@@ -311,7 +323,9 @@ jobs:
|
||||
|
||||
- name: Bump Desktop Version - App - Version Override
|
||||
if: ${{ inputs.bump_desktop == true && inputs.version_number_override != '' }}
|
||||
run: npm version ${{ inputs.version_number_override }}
|
||||
env:
|
||||
VERSION: ${{ inputs.version_number_override }}
|
||||
run: npm version $VERSION
|
||||
working-directory: "apps/desktop/src"
|
||||
|
||||
- name: Bump Desktop Version - App - Automatic Calculation
|
||||
@@ -362,7 +376,9 @@ jobs:
|
||||
- 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 }}
|
||||
env:
|
||||
VERSION: ${{ inputs.version_number_override }}
|
||||
run: npm version --workspace=@bitwarden/web-vault $VERSION
|
||||
|
||||
- name: Bump Web Version - Automatic Calculation
|
||||
if: ${{ inputs.bump_web == true && inputs.version_number_override == '' }}
|
||||
@@ -375,27 +391,29 @@ jobs:
|
||||
|
||||
- name: Set final version output
|
||||
id: set-final-version-output
|
||||
env:
|
||||
VERSION: ${{ inputs.version_number_override }}
|
||||
run: |
|
||||
if [[ "${{ steps.bump-browser-version-override.outcome }}" = "success" ]]; then
|
||||
echo "version_browser=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
|
||||
echo "version_browser=$VERSION" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ steps.bump-browser-version-automatic.outcome }}" = "success" ]]; then
|
||||
echo "version_browser=${{ steps.calculate-next-browser-version.outputs.version }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
if [[ "${{ steps.bump-cli-version-override.outcome }}" = "success" ]]; then
|
||||
echo "version_cli=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
|
||||
echo "version_cli=$VERSION" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ steps.bump-cli-version-automatic.outcome }}" = "success" ]]; then
|
||||
echo "version_cli=${{ steps.calculate-next-cli-version.outputs.version }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
if [[ "${{ steps.bump-desktop-version-override.outcome }}" = "success" ]]; then
|
||||
echo "version_desktop=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
|
||||
echo "version_desktop=$VERSION" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ steps.bump-desktop-version-automatic.outcome }}" = "success" ]]; then
|
||||
echo "version_desktop=${{ steps.calculate-next-desktop-version.outputs.version }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
if [[ "${{ steps.bump-web-version-override.outcome }}" = "success" ]]; then
|
||||
echo "version_web=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
|
||||
echo "version_web=$VERSION" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ steps.bump-web-version-automatic.outcome }}" = "success" ]]; then
|
||||
echo "version_web=${{ steps.calculate-next-web-version.outputs.version }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
@@ -416,199 +434,52 @@ jobs:
|
||||
|
||||
- name: Push changes
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
env:
|
||||
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
|
||||
run: git push -u origin $PR_BRANCH
|
||||
run: git push
|
||||
|
||||
- name: Generate PR message
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
id: pr-message
|
||||
run: |
|
||||
MESSAGE=""
|
||||
if [[ "${{ inputs.bump_browser }}" == "true" ]]; then
|
||||
MESSAGE+=$' Browser version bump to ${{ steps.set-final-version-output.outputs.version_browser }}\n'
|
||||
fi
|
||||
|
||||
if [[ "${{ inputs.bump_cli }}" == "true" ]]; then
|
||||
MESSAGE+=$' CLI version bump to ${{ steps.set-final-version-output.outputs.version_cli }}\n'
|
||||
fi
|
||||
|
||||
if [[ "${{ inputs.bump_desktop }}" == "true" ]]; then
|
||||
MESSAGE+=$' Desktop version bump to ${{ steps.set-final-version-output.outputs.version_desktop }}\n'
|
||||
fi
|
||||
|
||||
if [[ "${{ inputs.bump_web }}" == "true" ]]; then
|
||||
MESSAGE+=$' Web version bump to ${{ steps.set-final-version-output.outputs.version_web }}\n'
|
||||
fi
|
||||
|
||||
echo "MESSAGE<<EOF" >> $GITHUB_ENV
|
||||
echo "$MESSAGE" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
private-key: ${{ secrets.BW_GHAPP_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- name: Create Version PR
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
id: create-pr
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
|
||||
TITLE: "Bump client(s) version"
|
||||
run: |
|
||||
PR_URL=$(gh pr create --title "$TITLE" \
|
||||
--base "main" \
|
||||
--head "$PR_BRANCH" \
|
||||
--label "version update" \
|
||||
--label "automated pr" \
|
||||
--body "
|
||||
## Type of change
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature development
|
||||
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
||||
- [ ] Build/deploy pipeline (DevOps)
|
||||
- [X] Other
|
||||
|
||||
## Objective
|
||||
$MESSAGE")
|
||||
|
||||
echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Approve PR
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
|
||||
run: gh pr review $PR_NUMBER --approve
|
||||
|
||||
- name: Merge PR
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
|
||||
run: gh pr merge $PR_NUMBER --squash --auto --delete-branch
|
||||
|
||||
- name: Report upcoming browser release version to Slack
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && steps.set-final-version-output.outputs.version_browser != '' && inputs.enable_slack_notification == true }}
|
||||
uses: bitwarden/gh-actions/report-upcoming-release-version@main
|
||||
with:
|
||||
version: ${{ steps.set-final-version-output.outputs.version_browser }}
|
||||
project: browser
|
||||
AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Report upcoming cli release version to Slack
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && steps.set-final-version-output.outputs.version_cli != '' && inputs.enable_slack_notification == true }}
|
||||
uses: bitwarden/gh-actions/report-upcoming-release-version@main
|
||||
with:
|
||||
version: ${{ steps.set-final-version-output.outputs.version_cli }}
|
||||
project: cli
|
||||
AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Report upcoming desktop release version to Slack
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && steps.set-final-version-output.outputs.version_desktop != '' && inputs.enable_slack_notification == true }}
|
||||
uses: bitwarden/gh-actions/report-upcoming-release-version@main
|
||||
with:
|
||||
version: ${{ steps.set-final-version-output.outputs.version_desktop }}
|
||||
project: desktop
|
||||
AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Report upcoming web release version to Slack
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && steps.set-final-version-output.outputs.version_web != '' && inputs.enable_slack_notification == true }}
|
||||
uses: bitwarden/gh-actions/report-upcoming-release-version@main
|
||||
with:
|
||||
version: ${{ steps.set-final-version-output.outputs.version_web }}
|
||||
project: web
|
||||
AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
cut_rc:
|
||||
name: Cut RC branch
|
||||
if: ${{ inputs.cut_rc_branch == true }}
|
||||
needs: bump_version
|
||||
runs-on: ubuntu-22.04
|
||||
cherry_pick:
|
||||
name: Cherry-Pick Commit(s)
|
||||
if: ${{ needs.setup.outputs.branch == 'rc' }}
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- bump_version
|
||||
- setup
|
||||
steps:
|
||||
- name: Checkout Branch
|
||||
- name: Check out main branch
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ needs.setup.outputs.token }}
|
||||
|
||||
### Browser
|
||||
- name: Browser - Verify version has been updated
|
||||
if: ${{ inputs.bump_browser == true }}
|
||||
env:
|
||||
NEW_VERSION: ${{ needs.bump_version.outputs.version_browser }}
|
||||
- name: Configure Git
|
||||
run: |
|
||||
# Wait for version to change.
|
||||
while : ; do
|
||||
echo "Waiting for version to be updated..."
|
||||
git pull --force
|
||||
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "Github Actions"
|
||||
|
||||
# If the versions don't match we continue the loop, otherwise we break out of the loop.
|
||||
[[ "$NEW_VERSION" != "$CURRENT_VERSION" ]] || break
|
||||
sleep 10
|
||||
done
|
||||
working-directory: apps/browser
|
||||
|
||||
### CLI
|
||||
- name: CLI - Verify version has been updated
|
||||
if: ${{ inputs.bump_cli == true }}
|
||||
env:
|
||||
NEW_VERSION: ${{ needs.bump_version.outputs.version_cli }}
|
||||
- name: Perform cherry-pick(s)
|
||||
run: |
|
||||
# Wait for version to change.
|
||||
while : ; do
|
||||
echo "Waiting for version to be updated..."
|
||||
git pull --force
|
||||
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
||||
# Function for cherry-picking
|
||||
cherry_pick () {
|
||||
local package_path="apps/$1/package.json"
|
||||
local source_branch=$2
|
||||
local destination_branch=$3
|
||||
|
||||
# If the versions don't match we continue the loop, otherwise we break out of the loop.
|
||||
[[ "$NEW_VERSION" != "$CURRENT_VERSION" ]] || break
|
||||
sleep 10
|
||||
done
|
||||
working-directory: apps/cli
|
||||
# Get project commit/version from source branch
|
||||
git switch $source_branch
|
||||
SOURCE_COMMIT=$(git log --reverse --pretty=format:"%H" --max-count=1 $package_path)
|
||||
SOURCE_VERSION=$(cat $package_path | jq -r '.version')
|
||||
|
||||
### Desktop
|
||||
- name: Desktop - Verify version has been updated
|
||||
if: ${{ inputs.bump_desktop == true }}
|
||||
env:
|
||||
NEW_VERSION: ${{ needs.bump_version.outputs.version_desktop }}
|
||||
run: |
|
||||
# Wait for version to change.
|
||||
while : ; do
|
||||
echo "Waiting for version to be updated..."
|
||||
git pull --force
|
||||
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
||||
# Get project commit/version from destination branch
|
||||
git switch $destination_branch
|
||||
DESTINATION_VERSION=$(cat $package_path | jq -r '.version')
|
||||
|
||||
# If the versions don't match we continue the loop, otherwise we break out of the loop.
|
||||
[[ "$NEW_VERSION" != "$CURRENT_VERSION" ]] || break
|
||||
sleep 10
|
||||
done
|
||||
working-directory: apps/desktop
|
||||
if [[ "$DESTINATION_VERSION" != "$SOURCE_VERSION" ]]; then
|
||||
git cherry-pick --strategy-option=theirs -x $SOURCE_COMMIT
|
||||
git push -u origin $destination_branch
|
||||
fi
|
||||
|
||||
### Web
|
||||
- name: Web - Verify version has been updated
|
||||
if: ${{ inputs.bump_web == true }}
|
||||
env:
|
||||
NEW_VERSION: ${{ needs.bump_version.outputs.version_web }}
|
||||
run: |
|
||||
# Wait for version to change.
|
||||
while : ; do
|
||||
echo "Waiting for version to be updated..."
|
||||
git pull --force
|
||||
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
||||
|
||||
# If the versions don't match we continue the loop, otherwise we break out of the loop.
|
||||
[[ "$NEW_VERSION" != "$CURRENT_VERSION" ]] || break
|
||||
sleep 10
|
||||
done
|
||||
working-directory: apps/web
|
||||
|
||||
- name: Cut RC branch
|
||||
run: |
|
||||
git switch --quiet --create rc
|
||||
git push --quiet --set-upstream origin rc
|
||||
# Cherry-pick from 'main' into 'rc'
|
||||
cherry_pick browser main rc
|
||||
cherry_pick cli main rc
|
||||
cherry_pick desktop main rc
|
||||
cherry_pick web main rc
|
||||
64
.github/workflows/version-auto-bump.yml
vendored
64
.github/workflows/version-auto-bump.yml
vendored
@@ -8,27 +8,55 @@ on:
|
||||
jobs:
|
||||
bump-version:
|
||||
name: Bump Desktop Version
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
||||
id: app-token
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
private-key: ${{ secrets.BW_GHAPP_KEY }}
|
||||
|
||||
- name: Retrieve bot secrets
|
||||
id: retrieve-bot-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
- name: Check out target ref
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
keyvault: bitwarden-ci
|
||||
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
||||
ref: main
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Trigger Version Bump workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||
- name: Configure Git
|
||||
run: |
|
||||
echo '{"cut_rc_branch": "false",
|
||||
"bump_browser": "false",
|
||||
"bump_cli": "false",
|
||||
"bump_desktop": "true",
|
||||
"bump_web": "false"}' | \
|
||||
gh workflow run version-bump.yml --json --repo bitwarden/clients
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "Github Actions"
|
||||
|
||||
- name: Get current Desktop version
|
||||
id: current-desktop-version
|
||||
run: |
|
||||
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
||||
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||
working-directory: apps/desktop
|
||||
|
||||
- name: Calculate next Desktop release version
|
||||
id: calculate-next-desktop-version
|
||||
uses: bitwarden/gh-actions/version-next@main
|
||||
with:
|
||||
version: ${{ steps.current-desktop-version.outputs.version }}
|
||||
|
||||
- name: Bump Desktop Version - Root - Automatic Calculation
|
||||
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 - Automatic Calculation
|
||||
env:
|
||||
VERSION: ${{ steps.calculate-next-desktop-version.outputs.version }}
|
||||
run: npm version $VERSION
|
||||
working-directory: "apps/desktop/src"
|
||||
|
||||
- name: Commit files
|
||||
env:
|
||||
VERSION: ${{ steps.calculate-next-desktop-version.outputs.version }}
|
||||
run: git commit -m "Bumped Desktop client to $VERSION" -a
|
||||
|
||||
- name: Push changes
|
||||
run: git push
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<ng-container slot="end">
|
||||
<app-pop-out></app-pop-out>
|
||||
<app-current-account *ngIf="showAcctSwitcher"></app-current-account>
|
||||
<app-current-account *ngIf="showAcctSwitcher && hasLoggedInAccount"></app-current-account>
|
||||
</ng-container>
|
||||
</popup-header>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp
|
||||
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
|
||||
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
|
||||
import { CurrentAccountComponent } from "../account-switching/current-account.component";
|
||||
import { AccountSwitcherService } from "../account-switching/services/account-switcher.service";
|
||||
|
||||
import { ExtensionBitwardenLogo } from "./extension-bitwarden-logo.icon";
|
||||
|
||||
@@ -50,6 +51,7 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
||||
protected pageIcon: Icon;
|
||||
protected showReadonlyHostname: boolean;
|
||||
protected maxWidth: "md" | "3xl";
|
||||
protected hasLoggedInAccount: boolean = false;
|
||||
|
||||
protected theme: string;
|
||||
protected logo = ExtensionBitwardenLogo;
|
||||
@@ -59,6 +61,7 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
||||
private route: ActivatedRoute,
|
||||
private i18nService: I18nService,
|
||||
private extensionAnonLayoutWrapperDataService: AnonLayoutWrapperDataService,
|
||||
private accountSwitcherService: AccountSwitcherService,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
@@ -68,6 +71,12 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
||||
// Listen for page changes and update the page data appropriately
|
||||
this.listenForPageDataChanges();
|
||||
this.listenForServiceDataChanges();
|
||||
|
||||
this.accountSwitcherService.availableAccounts$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((accounts) => {
|
||||
this.hasLoggedInAccount = accounts.some((account) => account.id !== "addAccount");
|
||||
});
|
||||
}
|
||||
|
||||
private listenForPageDataChanges() {
|
||||
|
||||
@@ -27,6 +27,7 @@ import { ButtonModule, I18nMockService } from "@bitwarden/components";
|
||||
|
||||
import { RegistrationCheckEmailIcon } from "../../../../../../libs/auth/src/angular/icons/registration-check-email.icon";
|
||||
import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service";
|
||||
import { AccountSwitcherService } from "../account-switching/services/account-switcher.service";
|
||||
|
||||
import { ExtensionAnonLayoutWrapperDataService } from "./extension-anon-layout-wrapper-data.service";
|
||||
import {
|
||||
@@ -45,6 +46,7 @@ const decorators = (options: {
|
||||
applicationVersion?: string;
|
||||
clientType?: ClientType;
|
||||
hostName?: string;
|
||||
accounts?: any[];
|
||||
}) => {
|
||||
return [
|
||||
componentWrapperDecorator(
|
||||
@@ -83,6 +85,13 @@ const decorators = (options: {
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: AccountSwitcherService,
|
||||
useValue: {
|
||||
availableAccounts$: of(options.accounts || []),
|
||||
SPECIAL_ADD_ACCOUNT_ID: "addAccount",
|
||||
} as Partial<AccountSwitcherService>,
|
||||
},
|
||||
{
|
||||
provide: AuthService,
|
||||
useValue: {
|
||||
@@ -300,3 +309,64 @@ export const DynamicContentExample: Story = {
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
export const HasLoggedInAccountExample: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: "<router-outlet></router-outlet>",
|
||||
}),
|
||||
decorators: decorators({
|
||||
components: [DefaultPrimaryOutletExampleComponent],
|
||||
routes: [
|
||||
{
|
||||
path: "**",
|
||||
redirectTo: "has-logged-in-account",
|
||||
pathMatch: "full",
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
component: ExtensionAnonLayoutWrapperComponent,
|
||||
children: [
|
||||
{
|
||||
path: "has-logged-in-account",
|
||||
data: {
|
||||
hasLoggedInAccount: true,
|
||||
showAcctSwitcher: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: DefaultPrimaryOutletExampleComponent,
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
component: DefaultSecondaryOutletExampleComponent,
|
||||
outlet: "secondary",
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
component: DefaultEnvSelectorOutletExampleComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
accounts: [
|
||||
{
|
||||
name: "Test User",
|
||||
email: "testuser@bitwarden.com",
|
||||
id: "123e4567-e89b-12d3-a456-426614174000",
|
||||
server: "bitwarden.com",
|
||||
status: 2,
|
||||
isActive: false,
|
||||
},
|
||||
{
|
||||
name: "addAccount",
|
||||
id: "addAccount",
|
||||
isActive: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
import { Subject, firstValueFrom, switchMap, takeUntil } from "rxjs";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { Subject, firstValueFrom, switchMap, takeUntil, tap } from "rxjs";
|
||||
|
||||
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
|
||||
import { LoginEmailServiceAbstraction, RegisterRouteService } from "@bitwarden/auth/common";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
@@ -38,9 +40,13 @@ export class HomeComponent implements OnInit, OnDestroy {
|
||||
private accountSwitcherService: AccountSwitcherService,
|
||||
private registerRouteService: RegisterRouteService,
|
||||
private toastService: ToastService,
|
||||
private configService: ConfigService,
|
||||
private route: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.listenForUnauthUiRefreshFlagChanges();
|
||||
|
||||
const email = await firstValueFrom(this.loginEmailService.loginEmail$);
|
||||
const rememberEmail = this.loginEmailService.getRememberEmail();
|
||||
|
||||
@@ -70,6 +76,29 @@ export class HomeComponent implements OnInit, OnDestroy {
|
||||
this.destroyed$.complete();
|
||||
}
|
||||
|
||||
private listenForUnauthUiRefreshFlagChanges() {
|
||||
this.configService
|
||||
.getFeatureFlag$(FeatureFlag.UnauthenticatedExtensionUIRefresh)
|
||||
.pipe(
|
||||
tap(async (flag) => {
|
||||
// If the flag is turned ON, we must force a reload to ensure the correct UI is shown
|
||||
if (flag) {
|
||||
const uniqueQueryParams = {
|
||||
...this.route.queryParams,
|
||||
// adding a unique timestamp to the query params to force a reload
|
||||
t: new Date().getTime().toString(),
|
||||
};
|
||||
|
||||
await this.router.navigate(["/login"], {
|
||||
queryParams: uniqueQueryParams,
|
||||
});
|
||||
}
|
||||
}),
|
||||
takeUntil(this.destroyed$),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
get availableAccounts$() {
|
||||
return this.accountSwitcherService.availableAccounts$;
|
||||
}
|
||||
|
||||
@@ -274,7 +274,11 @@ export class NativeMessagingBackground {
|
||||
let message = rawMessage as ReceiveMessage;
|
||||
if (!this.platformUtilsService.isSafari()) {
|
||||
message = JSON.parse(
|
||||
await this.encryptService.decryptToUtf8(rawMessage as EncString, this.sharedSecret),
|
||||
await this.encryptService.decryptToUtf8(
|
||||
rawMessage as EncString,
|
||||
this.sharedSecret,
|
||||
"ipc-desktop-ipc-channel-key",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,11 +58,33 @@ export class BrowserApi {
|
||||
}
|
||||
|
||||
static async createWindow(options: chrome.windows.CreateData): Promise<chrome.windows.Window> {
|
||||
return new Promise((resolve) =>
|
||||
chrome.windows.create(options, (window) => {
|
||||
resolve(window);
|
||||
}),
|
||||
);
|
||||
return new Promise((resolve) => {
|
||||
chrome.windows.create(options, async (newWindow) => {
|
||||
if (!BrowserApi.isSafariApi) {
|
||||
return resolve(newWindow);
|
||||
}
|
||||
// Safari doesn't close the default extension popup when a new window is created so we need to
|
||||
// manually trigger the close by focusing the main window after the new window is created
|
||||
const allWindows = await new Promise<chrome.windows.Window[]>((resolve) => {
|
||||
chrome.windows.getAll({ windowTypes: ["normal"] }, (windows) => resolve(windows));
|
||||
});
|
||||
|
||||
const mainWindow = allWindows.find((window) => window.id !== newWindow.id);
|
||||
|
||||
// No main window found, resolve the new window
|
||||
if (mainWindow == null || !mainWindow.id) {
|
||||
return resolve(newWindow);
|
||||
}
|
||||
|
||||
// Focus the main window to close the extension popup
|
||||
chrome.windows.update(mainWindow.id, { focused: true }, () => {
|
||||
// Refocus the newly created window
|
||||
chrome.windows.update(newWindow.id, { focused: true }, () => {
|
||||
resolve(newWindow);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
144
apps/desktop/desktop_native/Cargo.lock
generated
144
apps/desktop/desktop_native/Cargo.lock
generated
@@ -4,18 +4,18 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.22.0"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
@@ -39,9 +39,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.86"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
|
||||
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
@@ -216,17 +216,17 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.73"
|
||||
version = "0.3.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
|
||||
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -289,9 +289,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.7.2"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
|
||||
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
@@ -304,9 +304,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.28"
|
||||
version = "1.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1"
|
||||
checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
@@ -439,9 +439,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.128"
|
||||
version = "1.0.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54ccead7d199d584d139148b04b4a368d1ec7556a1d9ea2548febb1b9d49f9a4"
|
||||
checksum = "cbdc8cca144dce1c4981b5c9ab748761619979e515c3d53b5df385c677d1d007"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
@@ -451,9 +451,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.128"
|
||||
version = "1.0.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c77953e99f01508f89f55c494bfa867171ef3a6c8cea03d26975368f2121a5c1"
|
||||
checksum = "c5764c3142ab44fcf857101d12c0ddf09c34499900557c764f5ad0597159d1fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
@@ -466,15 +466,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.128"
|
||||
version = "1.0.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65777e06cc48f0cb0152024c77d6cf9e4bdb4408e7b48bea993d42fa0f5b02b6"
|
||||
checksum = "d422aff542b4fa28c2ce8e5cc202d42dbf24702345c1fba3087b2d3f8a1b90ff"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.128"
|
||||
version = "1.0.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60"
|
||||
checksum = "a1719100f31492cd6adeeab9a0f46cdbc846e615fdb66d7b398aa46ec7fdd06f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -759,9 +759,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.3.0"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
|
||||
checksum = "3f1fa2f9765705486b33fd2acf1577f8ec449c2ba1f318ae5447697b7c08d210"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
@@ -844,9 +844,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.29.0"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
@@ -937,9 +937,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.0"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
|
||||
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -965,15 +965,6 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.6.0"
|
||||
@@ -1142,11 +1133,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
|
||||
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1163,9 +1154,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "napi"
|
||||
version = "2.16.11"
|
||||
version = "2.16.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53575dfa17f208dd1ce3a2da2da4659aae393b256a472f2738a8586a6c4107fd"
|
||||
checksum = "214f07a80874bb96a8433b3cdfc84980d56c7b02e1a0d7ba4ba0db5cef785e2b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"ctor",
|
||||
@@ -1450,9 +1441,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.14"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
@@ -1518,9 +1509,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.87"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1601,9 +1592,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.0"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -1645,9 +1636,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
version = "0.38.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
@@ -1705,18 +1696,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.210"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.210"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1824,9 +1815,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.79"
|
||||
version = "2.0.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
|
||||
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1854,9 +1845,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.12.0"
|
||||
version = "3.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
|
||||
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
@@ -1876,18 +1867,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.61"
|
||||
version = "1.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||
checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.61"
|
||||
version = "1.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2034,12 +2025,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree_magic_mini"
|
||||
version = "3.1.5"
|
||||
version = "3.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469a727cac55b41448315cc10427c069c618ac59bb6a4480283fcd811749bdc2"
|
||||
checksum = "aac5e8971f245c3389a5a76e648bfc80803ae066a1243a75db0064d7c1129d63"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"home",
|
||||
"memchr",
|
||||
"nom",
|
||||
"once_cell",
|
||||
@@ -2115,9 +2105,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.6"
|
||||
version = "0.31.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d"
|
||||
checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"rustix",
|
||||
@@ -2497,9 +2487,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "4.3.1"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "851238c133804e0aa888edf4a0229481c753544ca12a60fd1c3230c8a500fe40"
|
||||
checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
@@ -2535,9 +2525,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "4.3.1"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d5a3f12c20bd473be3194af6b49d50d7bb804ef3192dc70eddedb26b85d9da7"
|
||||
checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
@@ -2593,9 +2583,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "4.1.2"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1724a2b330760dc7d2a8402d841119dc869ef120b139d29862d6980e9c75bfc9"
|
||||
checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
@@ -2606,9 +2596,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "4.1.2"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55025a7a518ad14518fb243559c058a2e5b848b015e31f1d90414f36e3317859"
|
||||
checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
@@ -2619,9 +2609,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc242db087efc22bd9ade7aa7809e4ba828132edc312871584a6b4391bdf8786"
|
||||
checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -23,7 +23,7 @@ sys = [
|
||||
|
||||
[dependencies]
|
||||
aes = "=0.8.4"
|
||||
anyhow = "=1.0.86"
|
||||
anyhow = "=1.0.93"
|
||||
arboard = { version = "=3.4.1", default-features = false, features = [
|
||||
"wayland-data-control",
|
||||
] }
|
||||
@@ -38,7 +38,7 @@ rand = "=0.8.5"
|
||||
retry = "=2.0.0"
|
||||
scopeguard = "=1.2.0"
|
||||
sha2 = "=0.10.8"
|
||||
thiserror = "=1.0.61"
|
||||
thiserror = "=1.0.68"
|
||||
tokio = { version = "=1.41.0", features = ["io-util", "sync", "macros"] }
|
||||
tokio-util = "=0.7.12"
|
||||
typenum = "=1.17.0"
|
||||
@@ -68,5 +68,5 @@ security-framework-sys = { version = "=2.11.0", optional = true }
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
gio = { version = "=0.19.5", optional = true }
|
||||
libsecret = { version = "=0.5.0", optional = true }
|
||||
zbus = { version = "=4.3.1", optional = true }
|
||||
zbus = { version = "=4.4.0", optional = true }
|
||||
zbus_polkit = { version = "=4.0.0", optional = true }
|
||||
|
||||
@@ -14,9 +14,9 @@ default = []
|
||||
manual_test = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "=1.0.86"
|
||||
anyhow = "=1.0.93"
|
||||
desktop_core = { path = "../core" }
|
||||
napi = { version = "=2.16.11", features = ["async"] }
|
||||
napi = { version = "=2.16.13", features = ["async"] }
|
||||
napi-derive = "=2.16.12"
|
||||
tokio = { version = "1.38.0" }
|
||||
tokio-util = "0.7.11"
|
||||
|
||||
@@ -7,7 +7,7 @@ version = "0.0.0"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = "=1.0.86"
|
||||
anyhow = "=1.0.93"
|
||||
desktop_core = { path = "../core", default-features = false }
|
||||
futures = "0.3.30"
|
||||
log = "0.4.22"
|
||||
|
||||
@@ -125,9 +125,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
|
||||
@@ -219,7 +219,11 @@ export default class NativeMessageService {
|
||||
key: string,
|
||||
): Promise<DecryptedCommandData> {
|
||||
const sharedKey = await this.getSharedKeyForKey(key);
|
||||
const decrypted = await this.encryptService.decryptToUtf8(payload, sharedKey);
|
||||
const decrypted = await this.encryptService.decryptToUtf8(
|
||||
payload,
|
||||
sharedKey,
|
||||
"native-messaging-session",
|
||||
);
|
||||
|
||||
return JSON.parse(decrypted);
|
||||
}
|
||||
|
||||
@@ -58,30 +58,46 @@ async function run(context) {
|
||||
id = identities[0].id;
|
||||
}
|
||||
|
||||
console.log(`Signing proxy binary before the main bundle, using identity '${id}'`);
|
||||
console.log(
|
||||
`Signing proxy binary before the main bundle, using identity '${id}', for build ${context.electronPlatformName}`,
|
||||
);
|
||||
|
||||
const appName = context.packager.appInfo.productFilename;
|
||||
const appPath = `${context.appOutDir}/${appName}.app`;
|
||||
const proxyPath = path.join(appPath, "Contents", "MacOS", "desktop_proxy");
|
||||
const inheritProxyPath = path.join(appPath, "Contents", "MacOS", "desktop_proxy.inherit");
|
||||
|
||||
const packageId = "com.bitwarden.desktop";
|
||||
const entitlementsName = "entitlements.desktop_proxy.plist";
|
||||
const entitlementsPath = path.join(__dirname, "..", "resources", entitlementsName);
|
||||
child_process.execSync(
|
||||
`codesign -s '${id}' -i ${packageId} -f --timestamp --options runtime --entitlements ${entitlementsPath} ${proxyPath}`,
|
||||
);
|
||||
|
||||
const inheritProxyPath = path.join(appPath, "Contents", "MacOS", "desktop_proxy.inherit");
|
||||
const inheritEntitlementsName = "entitlements.desktop_proxy.inherit.plist";
|
||||
const inheritEntitlementsPath = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"resources",
|
||||
inheritEntitlementsName,
|
||||
);
|
||||
child_process.execSync(
|
||||
`codesign -s '${id}' -i ${packageId} -f --timestamp --options runtime --entitlements ${inheritEntitlementsPath} ${inheritProxyPath}`,
|
||||
);
|
||||
if (is_mas) {
|
||||
const entitlementsName = "entitlements.desktop_proxy.plist";
|
||||
const entitlementsPath = path.join(__dirname, "..", "resources", entitlementsName);
|
||||
child_process.execSync(
|
||||
`codesign -s '${id}' -i ${packageId} -f --timestamp --options runtime --entitlements ${entitlementsPath} ${proxyPath}`,
|
||||
);
|
||||
|
||||
const inheritEntitlementsName = "entitlements.desktop_proxy.inherit.plist";
|
||||
const inheritEntitlementsPath = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"resources",
|
||||
inheritEntitlementsName,
|
||||
);
|
||||
child_process.execSync(
|
||||
`codesign -s '${id}' -i ${packageId} -f --timestamp --options runtime --entitlements ${inheritEntitlementsPath} ${inheritProxyPath}`,
|
||||
);
|
||||
} else {
|
||||
// For non-Appstore builds, we don't need the inherit binary as they are not sandboxed,
|
||||
// but we sign and include it anyway for consistency. It should be removed once DDG supports the proxy directly.
|
||||
const entitlementsName = "entitlements.mac.plist";
|
||||
const entitlementsPath = path.join(__dirname, "..", "resources", entitlementsName);
|
||||
child_process.execSync(
|
||||
`codesign -s '${id}' -i ${packageId} -f --timestamp --options runtime --entitlements ${entitlementsPath} ${proxyPath}`,
|
||||
);
|
||||
child_process.execSync(
|
||||
`codesign -s '${id}' -i ${packageId} -f --timestamp --options runtime --entitlements ${entitlementsPath} ${inheritProxyPath}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
import { Subject, takeUntil, tap } from "rxjs";
|
||||
|
||||
import { LoginComponentV1 as BaseLoginComponent } from "@bitwarden/angular/auth/components/login-v1.component";
|
||||
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
|
||||
@@ -14,8 +14,10 @@ import {
|
||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -76,6 +78,7 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe
|
||||
webAuthnLoginService: WebAuthnLoginServiceAbstraction,
|
||||
registerRouteService: RegisterRouteService,
|
||||
toastService: ToastService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
super(
|
||||
devicesApiService,
|
||||
@@ -105,6 +108,8 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.listenForUnauthUiRefreshFlagChanges();
|
||||
|
||||
await super.ngOnInit();
|
||||
await this.getLoginWithDevice(this.loggedEmail);
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||
@@ -137,6 +142,29 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe
|
||||
this.componentDestroyed$.complete();
|
||||
}
|
||||
|
||||
private listenForUnauthUiRefreshFlagChanges() {
|
||||
this.configService
|
||||
.getFeatureFlag$(FeatureFlag.UnauthenticatedExtensionUIRefresh)
|
||||
.pipe(
|
||||
tap(async (flag) => {
|
||||
// If the flag is turned ON, we must force a reload to ensure the correct UI is shown
|
||||
if (flag) {
|
||||
const uniqueQueryParams = {
|
||||
...this.route.queryParams,
|
||||
// adding a unique timestamp to the query params to force a reload
|
||||
t: new Date().getTime().toString(),
|
||||
};
|
||||
|
||||
await this.router.navigate(["/"], {
|
||||
queryParams: uniqueQueryParams,
|
||||
});
|
||||
}
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
async settings() {
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||
EnvironmentComponent,
|
||||
|
||||
@@ -185,6 +185,7 @@ export class NativeMessageHandlerService {
|
||||
let decryptedResult = await this.encryptService.decryptToUtf8(
|
||||
message.encryptedCommand as EncString,
|
||||
this.ddgSharedSecret,
|
||||
"ddg-shared-key",
|
||||
);
|
||||
|
||||
decryptedResult = this.trimNullCharsFromMessage(decryptedResult);
|
||||
|
||||
@@ -114,6 +114,7 @@ export class NativeMessagingService {
|
||||
await this.encryptService.decryptToUtf8(
|
||||
rawMessage as EncString,
|
||||
SymmetricCryptoKey.fromString(await ipc.platform.ephemeralStore.getEphemeralValue(appId)),
|
||||
`native-messaging-session-${appId}`,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -40,9 +40,9 @@
|
||||
></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
<bit-nav-item
|
||||
*ngIf="isAccessIntelligenceFeatureEnabled"
|
||||
[text]="'accessIntelligence' | i18n"
|
||||
route="access-intelligence"
|
||||
*ngIf="isRiskInsightsFeatureEnabled"
|
||||
[text]="'riskInsights' | i18n"
|
||||
route="risk-insights"
|
||||
></bit-nav-item>
|
||||
<bit-nav-group
|
||||
icon="bwi-billing"
|
||||
|
||||
@@ -51,7 +51,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
showPaymentAndHistory$: Observable<boolean>;
|
||||
hideNewOrgButton$: Observable<boolean>;
|
||||
organizationIsUnmanaged$: Observable<boolean>;
|
||||
isAccessIntelligenceFeatureEnabled = false;
|
||||
isRiskInsightsFeatureEnabled = false;
|
||||
|
||||
private _destroy = new Subject<void>();
|
||||
|
||||
@@ -71,7 +71,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
async ngOnInit() {
|
||||
document.body.classList.remove("layout_frontend");
|
||||
|
||||
this.isAccessIntelligenceFeatureEnabled = await this.configService.getFeatureFlag(
|
||||
this.isRiskInsightsFeatureEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.AccessIntelligence,
|
||||
);
|
||||
|
||||
|
||||
@@ -63,10 +63,10 @@ const routes: Routes = [
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "access-intelligence",
|
||||
path: "risk-insights",
|
||||
loadChildren: () =>
|
||||
import("../../tools/access-intelligence/access-intelligence.module").then(
|
||||
(m) => m.AccessIntelligenceModule,
|
||||
import("../../tools/risk-insights/risk-insights.module").then(
|
||||
(m) => m.RiskInsightsModule,
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -123,20 +123,22 @@ export class AccountComponent implements OnInit, OnDestroy {
|
||||
this.canEditSubscription = organization.canEditSubscription;
|
||||
this.canUseApi = organization.useApi;
|
||||
|
||||
// Update disabled states - reactive forms prefers not using disabled attribute
|
||||
// Disabling these fields for self hosted orgs is deprecated
|
||||
// This block can be completely removed as part of
|
||||
// https://bitwarden.atlassian.net/browse/PM-10863
|
||||
if (!this.limitCollectionCreationDeletionSplitFeatureFlagIsEnabled) {
|
||||
if (!this.selfHosted) {
|
||||
this.formGroup.get("orgName").enable();
|
||||
this.collectionManagementFormGroup.get("limitCollectionCreationDeletion").enable();
|
||||
this.collectionManagementFormGroup.get("allowAdminAccessToAllCollectionItems").enable();
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.selfHosted && this.canEditSubscription) {
|
||||
this.formGroup.get("billingEmail").enable();
|
||||
// Update disabled states - reactive forms prefers not using disabled attribute
|
||||
if (!this.selfHosted) {
|
||||
this.formGroup.get("orgName").enable();
|
||||
if (this.canEditSubscription) {
|
||||
this.formGroup.get("billingEmail").enable();
|
||||
}
|
||||
}
|
||||
|
||||
// Org Response
|
||||
|
||||
@@ -48,16 +48,7 @@
|
||||
}}</span>
|
||||
</dd>
|
||||
<dt>{{ "nextCharge" | i18n }}</dt>
|
||||
<dd *ngIf="!enableTimeThreshold">
|
||||
{{
|
||||
nextInvoice
|
||||
? (nextInvoice.date | date: "mediumDate") +
|
||||
", " +
|
||||
(nextInvoice.amount | currency: "$")
|
||||
: "-"
|
||||
}}
|
||||
</dd>
|
||||
<dd *ngIf="enableTimeThreshold">
|
||||
<dd>
|
||||
{{
|
||||
nextInvoice
|
||||
? (sub.subscription.periodEndDate | date: "mediumDate") +
|
||||
|
||||
@@ -38,13 +38,9 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
sub: SubscriptionResponse;
|
||||
selfHosted = false;
|
||||
cloudWebVaultUrl: string;
|
||||
enableTimeThreshold: boolean;
|
||||
|
||||
cancelPromise: Promise<any>;
|
||||
reinstatePromise: Promise<any>;
|
||||
protected enableTimeThreshold$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.EnableTimeThreshold,
|
||||
);
|
||||
|
||||
protected deprecateStripeSourcesAPI$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.AC2476_DeprecateStripeSourcesAPI,
|
||||
@@ -69,7 +65,6 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
async ngOnInit() {
|
||||
this.cloudWebVaultUrl = await firstValueFrom(this.environmentService.cloudWebVaultUrl$);
|
||||
await this.load();
|
||||
this.enableTimeThreshold = await firstValueFrom(this.enableTimeThreshold$);
|
||||
this.firstLoaded = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +48,7 @@
|
||||
<dt [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||
{{ "subscriptionExpiration" | i18n }}
|
||||
</dt>
|
||||
<dd [ngClass]="{ 'tw-text-danger': isExpired }" *ngIf="!enableTimeThreshold">
|
||||
{{ nextInvoice ? (nextInvoice.date | date: "mediumDate") : "-" }}
|
||||
</dd>
|
||||
<dd [ngClass]="{ 'tw-text-danger': isExpired }" *ngIf="enableTimeThreshold">
|
||||
<dd [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||
{{ nextInvoice ? (sub.subscription.periodEndDate | date: "mediumDate") : "-" }}
|
||||
</dd>
|
||||
</ng-container>
|
||||
|
||||
@@ -52,7 +52,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
loading = true;
|
||||
locale: string;
|
||||
showUpdatedSubscriptionStatusSection$: Observable<boolean>;
|
||||
enableTimeThreshold: boolean;
|
||||
preSelectedProductTier: ProductTierType = ProductTierType.Free;
|
||||
showSubscription = true;
|
||||
showSelfHost = false;
|
||||
@@ -65,10 +64,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
FeatureFlag.EnableConsolidatedBilling,
|
||||
);
|
||||
|
||||
protected enableTimeThreshold$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.EnableTimeThreshold,
|
||||
);
|
||||
|
||||
protected enableUpgradePasswordManagerSub$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.EnableUpgradePasswordManagerSub,
|
||||
);
|
||||
@@ -117,7 +112,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
this.showUpdatedSubscriptionStatusSection$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.AC1795_UpdatedSubscriptionStatusSection,
|
||||
);
|
||||
this.enableTimeThreshold = await firstValueFrom(this.enableTimeThreshold$);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -298,9 +292,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
return this.i18nService.t("subscriptionUpgrade", this.sub.seats.toString());
|
||||
}
|
||||
} else if (this.sub.maxAutoscaleSeats === this.sub.seats && this.sub.seats != null) {
|
||||
if (!this.enableTimeThreshold) {
|
||||
return this.i18nService.t("subscriptionMaxReached", this.sub.seats.toString());
|
||||
}
|
||||
const seatAdjustmentMessage = this.sub.plan.isAnnual
|
||||
? "annualSubscriptionUserSeatsMessage"
|
||||
: "monthlySubscriptionUserSeatsMessage";
|
||||
@@ -311,21 +302,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
} else if (this.userOrg.productTierType === ProductTierType.TeamsStarter) {
|
||||
return this.i18nService.t("subscriptionUserSeatsWithoutAdditionalSeatsOption", 10);
|
||||
} else if (this.sub.maxAutoscaleSeats == null) {
|
||||
if (!this.enableTimeThreshold) {
|
||||
return this.i18nService.t("subscriptionUserSeatsUnlimitedAutoscale");
|
||||
}
|
||||
|
||||
const seatAdjustmentMessage = this.sub.plan.isAnnual
|
||||
? "annualSubscriptionUserSeatsMessage"
|
||||
: "monthlySubscriptionUserSeatsMessage";
|
||||
return this.i18nService.t(seatAdjustmentMessage);
|
||||
} else {
|
||||
if (!this.enableTimeThreshold) {
|
||||
return this.i18nService.t(
|
||||
"subscriptionUserSeatsLimitedAutoscale",
|
||||
this.sub.maxAutoscaleSeats.toString(),
|
||||
);
|
||||
}
|
||||
const seatAdjustmentMessage = this.sub.plan.isAnnual
|
||||
? "annualSubscriptionUserSeatsMessage"
|
||||
: "monthlySubscriptionUserSeatsMessage";
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { AccessIntelligenceRoutingModule } from "./access-intelligence-routing.module";
|
||||
import { AccessIntelligenceComponent } from "./access-intelligence.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [AccessIntelligenceComponent, AccessIntelligenceRoutingModule],
|
||||
})
|
||||
export class AccessIntelligenceModule {}
|
||||
@@ -1,120 +0,0 @@
|
||||
<p>{{ "passwordsReportDesc" | i18n }}</p>
|
||||
<div *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!loading && dataSource.data.length">
|
||||
<div class="tw-flex tw-gap-6">
|
||||
<tools-card
|
||||
class="tw-flex-1"
|
||||
[title]="'atRiskMembers' | i18n"
|
||||
[value]="totalMembersMap.size - 3"
|
||||
[maxValue]="totalMembersMap.size"
|
||||
>
|
||||
</tools-card>
|
||||
<tools-card
|
||||
class="tw-flex-1"
|
||||
[title]="'atRiskApplications' | i18n"
|
||||
[value]="totalMembersMap.size - 1"
|
||||
[maxValue]="totalMembersMap.size"
|
||||
>
|
||||
</tools-card>
|
||||
</div>
|
||||
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
|
||||
<bit-search class="tw-grow" [formControl]="searchControl"></bit-search>
|
||||
<button class="tw-rounded-lg" type="button" buttonType="secondary" bitButton>
|
||||
<i class="bwi bwi-star-f tw-mr-2"></i>
|
||||
{{ "markAppAsCritical" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!loading && dataSource.data.length">
|
||||
<div class="tw-flex tw-gap-6">
|
||||
<tools-card
|
||||
class="tw-flex-1"
|
||||
[title]="'atRiskMembers' | i18n"
|
||||
[value]="totalMembersMap.size - 3"
|
||||
[maxValue]="totalMembersMap.size"
|
||||
>
|
||||
</tools-card>
|
||||
<tools-card
|
||||
class="tw-flex-1"
|
||||
[title]="'atRiskApplications' | i18n"
|
||||
[value]="totalMembersMap.size - 1"
|
||||
[maxValue]="totalMembersMap.size"
|
||||
>
|
||||
</tools-card>
|
||||
</div>
|
||||
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
|
||||
<bit-search class="tw-grow" [formControl]="searchControl"></bit-search>
|
||||
<button
|
||||
class="tw-rounded-lg"
|
||||
type="button"
|
||||
buttonType="secondary"
|
||||
[disabled]="!selectedIds.size"
|
||||
bitButton
|
||||
[bitAction]="markAppsAsCritical"
|
||||
appA11yTitle="{{ 'markAppAsCritical' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-star-f tw-mr-2"></i>
|
||||
{{ "markAppAsCritical" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<bit-table [dataSource]="dataSource">
|
||||
<ng-container header>
|
||||
<tr bitRow>
|
||||
<th bitCell></th>
|
||||
<th bitCell bitSortable="name">{{ "name" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "weakness" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "totalMembers" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body let-rows$>
|
||||
<tr bitRow *ngFor="let r of rows$ | async; trackBy: trackByFunction">
|
||||
<td bitCell>
|
||||
<input
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
[checked]="selectedIds.has(r.id)"
|
||||
(change)="onCheckboxChange(r.id, $event)"
|
||||
/>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<ng-container>
|
||||
<span>{{ r.name }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ r.subTitle }}</small>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<span
|
||||
bitBadge
|
||||
*ngIf="passwordStrengthMap.has(r.id)"
|
||||
[variant]="passwordStrengthMap.get(r.id)[1]"
|
||||
>
|
||||
{{ passwordStrengthMap.get(r.id)[0] | i18n }}
|
||||
</span>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<span bitBadge *ngIf="passwordUseMap.has(r.login.password)" variant="warning">
|
||||
{{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }}
|
||||
</span>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<span bitBadge *ngIf="exposedPasswordMap.has(r.id)" variant="warning">
|
||||
{{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }}
|
||||
</span>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right" data-testid="total-membership">
|
||||
{{ totalMembersMap.get(r.id) || 0 }}
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -12,8 +12,8 @@ import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared";
|
||||
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
|
||||
import { AccessIntelligenceTabType } from "./access-intelligence.component";
|
||||
import { applicationTableMockData } from "./application-table.mock";
|
||||
import { RiskInsightsTabType } from "./risk-insights.component";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -49,8 +49,8 @@ export class CriticalApplicationsComponent implements OnInit {
|
||||
}
|
||||
|
||||
goToAllAppsTab = async () => {
|
||||
await this.router.navigate([`organizations/${this.organizationId}/access-intelligence`], {
|
||||
queryParams: { tabIndex: AccessIntelligenceTabType.AllApps },
|
||||
await this.router.navigate([`organizations/${this.organizationId}/risk-insights`], {
|
||||
queryParams: { tabIndex: RiskInsightsTabType.AllApps },
|
||||
queryParamsHandling: "merge",
|
||||
});
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/access-intelligence";
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -6,7 +6,7 @@ import { map } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/access-intelligence";
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
@@ -0,0 +1,64 @@
|
||||
<p>{{ "passwordsReportDesc" | i18n }}</p>
|
||||
<div *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="tw-flex tw-flex-col" *ngIf="!loading && dataSource.data.length">
|
||||
<bit-table [dataSource]="dataSource">
|
||||
<ng-container header>
|
||||
<tr bitRow>
|
||||
<th bitCell></th>
|
||||
<th bitCell bitSortable="name">{{ "name" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "weakness" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "totalMembers" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body let-rows$>
|
||||
<tr bitRow *ngFor="let r of rows$ | async; trackBy: trackByFunction">
|
||||
<td bitCell>
|
||||
<input
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
[checked]="selectedIds.has(r.id)"
|
||||
(change)="onCheckboxChange(r.id, $event)"
|
||||
/>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<ng-container>
|
||||
<span>{{ r.name }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ r.subTitle }}</small>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<span
|
||||
bitBadge
|
||||
*ngIf="passwordStrengthMap.has(r.id)"
|
||||
[variant]="passwordStrengthMap.get(r.id)[1]"
|
||||
>
|
||||
{{ passwordStrengthMap.get(r.id)[0] | i18n }}
|
||||
</span>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<span bitBadge *ngIf="passwordUseMap.has(r.login.password)" variant="warning">
|
||||
{{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }}
|
||||
</span>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<span bitBadge *ngIf="exposedPasswordMap.has(r.id)" variant="warning">
|
||||
{{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }}
|
||||
</span>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right" data-testid="total-membership">
|
||||
{{ totalMembersMap.get(r.id) || 0 }}
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</div>
|
||||
@@ -5,7 +5,7 @@ import { ActivatedRoute } from "@angular/router";
|
||||
import { debounceTime, map } from "rxjs";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/access-intelligence";
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
@@ -4,7 +4,7 @@ import { mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/access-intelligence";
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
@@ -6,7 +6,7 @@ import { map } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/access-intelligence";
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
@@ -4,15 +4,15 @@ import { RouterModule, Routes } from "@angular/router";
|
||||
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
import { AccessIntelligenceComponent } from "./access-intelligence.component";
|
||||
import { RiskInsightsComponent } from "./risk-insights.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: AccessIntelligenceComponent,
|
||||
component: RiskInsightsComponent,
|
||||
canActivate: [canAccessFeature(FeatureFlag.AccessIntelligence)],
|
||||
data: {
|
||||
titleId: "accessIntelligence",
|
||||
titleId: "RiskInsights",
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -21,4 +21,4 @@ const routes: Routes = [
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AccessIntelligenceRoutingModule {}
|
||||
export class RiskInsightsRoutingModule {}
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="tw-mb-1 text-primary" bitTypography="body1">{{ "accessIntelligence" | i18n }}</div>
|
||||
<div class="tw-mb-1 text-primary" bitTypography="body1">{{ "riskInsights" | i18n }}</div>
|
||||
<h1 bitTypography="h1">{{ "passwordRisk" | i18n }}</h1>
|
||||
<div class="tw-text-muted">{{ "discoverAtRiskPasswords" | i18n }}</div>
|
||||
<div class="tw-bg-primary-100 tw-rounded-lg tw-w-full tw-px-8 tw-py-2 tw-my-4">
|
||||
@@ -15,7 +15,7 @@ import { PasswordHealthMembersURIComponent } from "./password-health-members-uri
|
||||
import { PasswordHealthMembersComponent } from "./password-health-members.component";
|
||||
import { PasswordHealthComponent } from "./password-health.component";
|
||||
|
||||
export enum AccessIntelligenceTabType {
|
||||
export enum RiskInsightsTabType {
|
||||
AllApps = 0,
|
||||
CriticalApps = 1,
|
||||
NotifiedMembers = 2,
|
||||
@@ -23,7 +23,7 @@ export enum AccessIntelligenceTabType {
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl: "./access-intelligence.component.html",
|
||||
templateUrl: "./risk-insights.component.html",
|
||||
imports: [
|
||||
AllApplicationsComponent,
|
||||
AsyncActionsModule,
|
||||
@@ -39,8 +39,8 @@ export enum AccessIntelligenceTabType {
|
||||
TabsModule,
|
||||
],
|
||||
})
|
||||
export class AccessIntelligenceComponent {
|
||||
tabIndex: AccessIntelligenceTabType;
|
||||
export class RiskInsightsComponent {
|
||||
tabIndex: RiskInsightsTabType;
|
||||
dataLastUpdated = new Date();
|
||||
|
||||
apps: any[] = [];
|
||||
@@ -70,7 +70,7 @@ export class AccessIntelligenceComponent {
|
||||
private router: Router,
|
||||
) {
|
||||
route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => {
|
||||
this.tabIndex = !isNaN(tabIndex) ? tabIndex : AccessIntelligenceTabType.AllApps;
|
||||
this.tabIndex = !isNaN(tabIndex) ? tabIndex : RiskInsightsTabType.AllApps;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { RiskInsightsRoutingModule } from "./risk-insights-routing.module";
|
||||
import { RiskInsightsComponent } from "./risk-insights.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [RiskInsightsComponent, RiskInsightsRoutingModule],
|
||||
})
|
||||
export class RiskInsightsModule {}
|
||||
@@ -7,6 +7,7 @@
|
||||
*ngIf="showCipherView"
|
||||
[cipher]="cipher"
|
||||
[collections]="collections"
|
||||
[isAdminConsole]="formConfig.isAdminConsole"
|
||||
></app-cipher-view>
|
||||
<vault-cipher-form
|
||||
*ngIf="loadForm"
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"criticalApplications": {
|
||||
"message": "Critical applications"
|
||||
},
|
||||
"accessIntelligence": {
|
||||
"message": "Access Intelligence"
|
||||
"riskInsights": {
|
||||
"message": "Risk Insights"
|
||||
},
|
||||
"passwordRisk": {
|
||||
"message": "Password Risk"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mockCiphers } from "@bitwarden/bit-common/tools/reports/access-intelligence/services/ciphers.mock";
|
||||
import { mockCiphers } from "@bitwarden/bit-common/tools/reports/risk-insights/services/ciphers.mock";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mockMemberCipherDetailsResponse } from "@bitwarden/bit-common/tools/reports/access-intelligence/services/member-cipher-details-response.mock";
|
||||
import { mockMemberCipherDetailsResponse } from "@bitwarden/bit-common/tools/reports/risk-insights/services/member-cipher-details-response.mock";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
@@ -41,6 +41,7 @@ module.exports = {
|
||||
"<rootDir>/libs/platform/jest.config.js",
|
||||
"<rootDir>/libs/node/jest.config.js",
|
||||
"<rootDir>/libs/vault/jest.config.js",
|
||||
"<rootDir>/libs/key-management/jest.config.js",
|
||||
],
|
||||
|
||||
// Workaround for a memory leak that crashes tests in CI:
|
||||
|
||||
@@ -38,18 +38,20 @@
|
||||
<div class="box-content">
|
||||
<div
|
||||
class="environment-selector-dialog"
|
||||
data-testid="environment-selector-dialog"
|
||||
[@transformPanel]="'open'"
|
||||
cdkTrapFocus
|
||||
cdkTrapFocusAutoCapture
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<ng-container *ngFor="let region of availableRegions">
|
||||
<ng-container *ngFor="let region of availableRegions; let i = index">
|
||||
<button
|
||||
type="button"
|
||||
class="environment-selector-dialog-item"
|
||||
(click)="toggle(region.key)"
|
||||
[attr.aria-pressed]="data.selectedRegion === region ? 'true' : 'false'"
|
||||
[attr.data-testid]="'environment-selector-dialog-item-' + i"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw bwi-sm bwi-check"
|
||||
@@ -66,6 +68,7 @@
|
||||
class="environment-selector-dialog-item"
|
||||
(click)="toggle(ServerEnvironmentType.SelfHosted)"
|
||||
[attr.aria-pressed]="data.selectedRegion ? 'false' : 'true'"
|
||||
data-testid="environment-selector-dialog-item-self-hosted"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw bwi-sm bwi-check"
|
||||
|
||||
@@ -178,6 +178,7 @@ import { BulkEncryptServiceImplementation } from "@bitwarden/common/platform/ser
|
||||
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
|
||||
import { DefaultBroadcasterService } from "@bitwarden/common/platform/services/default-broadcaster.service";
|
||||
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
|
||||
import { DefaultServerSettingsService } from "@bitwarden/common/platform/services/default-server-settings.service";
|
||||
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
|
||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||
@@ -1322,6 +1323,11 @@ const safeProviders: SafeProvider[] = [
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: DefaultServerSettingsService,
|
||||
useClass: DefaultServerSettingsService,
|
||||
deps: [ConfigService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: RegisterRouteService,
|
||||
useClass: RegisterRouteService,
|
||||
|
||||
@@ -699,7 +699,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected deleteCipher() {
|
||||
const asAdmin = this.organization?.canEditAllCiphers;
|
||||
const asAdmin = this.organization?.canEditAllCiphers || !this.cipher.collectionIds;
|
||||
return this.cipher.isDeleted
|
||||
? this.cipherService.deleteWithServer(this.cipher.id, asAdmin)
|
||||
: this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin);
|
||||
|
||||
@@ -4,13 +4,14 @@ import { RouterModule } from "@angular/router";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { RegisterRouteService } from "@bitwarden/auth/common";
|
||||
import { DefaultServerSettingsService } from "@bitwarden/common/platform/services/default-server-settings.service";
|
||||
import { LinkModule } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, JslibModule, LinkModule, RouterModule],
|
||||
template: `
|
||||
<div class="tw-text-center">
|
||||
<div class="tw-text-center" *ngIf="!(isUserRegistrationDisabled$ | async)">
|
||||
{{ "newToBitwarden" | i18n }}
|
||||
<a bitLink [routerLink]="registerRoute$ | async">{{ "createAccount" | i18n }}</a>
|
||||
</div>
|
||||
@@ -18,7 +19,10 @@ import { LinkModule } from "@bitwarden/components";
|
||||
})
|
||||
export class LoginSecondaryContentComponent {
|
||||
registerRouteService = inject(RegisterRouteService);
|
||||
serverSettingsService = inject(DefaultServerSettingsService);
|
||||
|
||||
// TODO: remove when email verification flag is removed
|
||||
protected registerRoute$ = this.registerRouteService.registerRoute$();
|
||||
|
||||
protected isUserRegistrationDisabled$ = this.serverSettingsService.isUserRegistrationDisabled$;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
-->
|
||||
|
||||
<form [bitSubmit]="submit" [formGroup]="formGroup">
|
||||
<ng-container *ngIf="loginUiState === LoginUiState.EMAIL_ENTRY">
|
||||
<div [ngClass]="{ 'tw-invisible tw-h-0': loginUiState !== LoginUiState.EMAIL_ENTRY }">
|
||||
<!-- Email Address input -->
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
|
||||
@@ -82,9 +82,9 @@
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="loginUiState === LoginUiState.MASTER_PASSWORD_ENTRY">
|
||||
<div [ngClass]="{ 'tw-invisible tw-h-0': loginUiState !== LoginUiState.MASTER_PASSWORD_ENTRY }">
|
||||
<!-- Master Password input -->
|
||||
<bit-form-field class="!tw-mb-1">
|
||||
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||
@@ -140,5 +140,5 @@
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CommonModule } from "@angular/common";
|
||||
import { Component, ElementRef, Input, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router, RouterModule } from "@angular/router";
|
||||
import { firstValueFrom, Subject, take, takeUntil } from "rxjs";
|
||||
import { firstValueFrom, Subject, take, takeUntil, tap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
@@ -19,9 +19,11 @@ import { CaptchaIFrame } from "@bitwarden/common/auth/captcha-iframe";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { ClientType, HttpStatusCode } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -139,12 +141,16 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
private toastService: ToastService,
|
||||
private logService: LogService,
|
||||
private validationService: ValidationService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.clientType = this.platformUtilsService.getClientType();
|
||||
this.loginViaAuthRequestSupported = this.loginComponentService.isLoginViaAuthRequestSupported();
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
// TODO: remove this when the UnauthenticatedExtensionUIRefresh feature flag is removed.
|
||||
this.listenForUnauthUiRefreshFlagChanges();
|
||||
|
||||
await this.defaultOnInit();
|
||||
|
||||
if (this.clientType === ClientType.Desktop) {
|
||||
@@ -162,6 +168,29 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
private listenForUnauthUiRefreshFlagChanges() {
|
||||
this.configService
|
||||
.getFeatureFlag$(FeatureFlag.UnauthenticatedExtensionUIRefresh)
|
||||
.pipe(
|
||||
tap(async (flag) => {
|
||||
// If the flag is turned OFF, we must force a reload to ensure the correct UI is shown
|
||||
if (!flag) {
|
||||
const uniqueQueryParams = {
|
||||
...this.activatedRoute.queryParams,
|
||||
// adding a unique timestamp to the query params to force a reload
|
||||
t: new Date().getTime().toString(), // Adding a unique timestamp as a query parameter
|
||||
};
|
||||
|
||||
await this.router.navigate(["/"], {
|
||||
queryParams: uniqueQueryParams,
|
||||
});
|
||||
}
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
submit = async (): Promise<void> => {
|
||||
if (this.clientType === ClientType.Desktop) {
|
||||
if (this.loginUiState !== LoginUiState.MASTER_PASSWORD_ENTRY) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<form [formGroup]="formGroup" *ngIf="!hideEnvSelector">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "creatingAccountOn" | i18n }}</bit-label>
|
||||
<bit-select formControlName="selectedRegion">
|
||||
<bit-select formControlName="selectedRegion" (closed)="onSelectClosed()">
|
||||
<bit-option
|
||||
*ngFor="let regionConfig of availableRegionConfigs"
|
||||
[value]="regionConfig"
|
||||
|
||||
@@ -109,6 +109,9 @@ export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy {
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for changes to the selected region and updates the form value and emits the selected region.
|
||||
*/
|
||||
private listenForSelectedRegionChanges() {
|
||||
this.selectedRegion.valueChanges
|
||||
.pipe(
|
||||
@@ -124,16 +127,12 @@ export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy {
|
||||
return of(null);
|
||||
}
|
||||
|
||||
if (selectedRegion === Region.SelfHosted) {
|
||||
return from(SelfHostedEnvConfigDialogComponent.open(this.dialogService)).pipe(
|
||||
tap((result: boolean | undefined) =>
|
||||
this.handleSelfHostedEnvConfigDialogResult(result, prevSelectedRegion),
|
||||
),
|
||||
);
|
||||
if (selectedRegion !== Region.SelfHosted) {
|
||||
this.selectedRegionChange.emit(selectedRegion);
|
||||
return from(this.environmentService.setEnvironment(selectedRegion.key));
|
||||
}
|
||||
|
||||
this.selectedRegionChange.emit(selectedRegion);
|
||||
return from(this.environmentService.setEnvironment(selectedRegion.key));
|
||||
return of(null);
|
||||
},
|
||||
),
|
||||
takeUntil(this.destroy$),
|
||||
@@ -170,6 +169,17 @@ export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when the select is closed.
|
||||
* If the selected region is self-hosted, opens the self-hosted environment settings dialog.
|
||||
*/
|
||||
protected async onSelectClosed() {
|
||||
if (this.selectedRegion.value === Region.SelfHosted) {
|
||||
const result = await SelfHostedEnvConfigDialogComponent.open(this.dialogService);
|
||||
return this.handleSelfHostedEnvConfigDialogResult(result, this.selectedRegion.value);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
|
||||
@@ -17,7 +17,6 @@ export enum FeatureFlag {
|
||||
InlineMenuFieldQualification = "inline-menu-field-qualification",
|
||||
MemberAccessReport = "ac-2059-member-access-report",
|
||||
TwoFactorComponentRefactor = "two-factor-component-refactor",
|
||||
EnableTimeThreshold = "PM-5864-dollar-threshold",
|
||||
InlineMenuPositioningImprovements = "inline-menu-positioning-improvements",
|
||||
ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner",
|
||||
VaultBulkManagementAction = "vault-bulk-management-action",
|
||||
@@ -63,7 +62,6 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.InlineMenuFieldQualification]: FALSE,
|
||||
[FeatureFlag.MemberAccessReport]: FALSE,
|
||||
[FeatureFlag.TwoFactorComponentRefactor]: FALSE,
|
||||
[FeatureFlag.EnableTimeThreshold]: FALSE,
|
||||
[FeatureFlag.InlineMenuPositioningImprovements]: FALSE,
|
||||
[FeatureFlag.ProviderClientVaultPrivacyBanner]: FALSE,
|
||||
[FeatureFlag.VaultBulkManagementAction]: FALSE,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { SemVer } from "semver";
|
||||
|
||||
import { FeatureFlag, FeatureFlagValueType } from "../../../enums/feature-flag.enum";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { ServerSettings } from "../../models/domain/server-settings";
|
||||
import { Region } from "../environment.service";
|
||||
|
||||
import { ServerConfig } from "./server-config";
|
||||
@@ -10,6 +11,8 @@ import { ServerConfig } from "./server-config";
|
||||
export abstract class ConfigService {
|
||||
/** The server config of the currently active user */
|
||||
serverConfig$: Observable<ServerConfig | null>;
|
||||
/** The server settings of the currently active user */
|
||||
serverSettings$: Observable<ServerSettings | null>;
|
||||
/** The cloud region of the currently active user */
|
||||
cloudRegion$: Observable<Region>;
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
ThirdPartyServerConfigData,
|
||||
EnvironmentServerConfigData,
|
||||
} from "../../models/data/server-config.data";
|
||||
import { ServerSettings } from "../../models/domain/server-settings";
|
||||
|
||||
const dayInMilliseconds = 24 * 3600 * 1000;
|
||||
|
||||
@@ -16,6 +17,7 @@ export class ServerConfig {
|
||||
environment?: EnvironmentServerConfigData;
|
||||
utcDate: Date;
|
||||
featureStates: { [key: string]: AllowedFeatureFlagTypes } = {};
|
||||
settings: ServerSettings;
|
||||
|
||||
constructor(serverConfigData: ServerConfigData) {
|
||||
this.version = serverConfigData.version;
|
||||
@@ -24,6 +26,7 @@ export class ServerConfig {
|
||||
this.utcDate = new Date(serverConfigData.utcDate);
|
||||
this.environment = serverConfigData.environment;
|
||||
this.featureStates = serverConfigData.featureStates;
|
||||
this.settings = serverConfigData.settings;
|
||||
|
||||
if (this.server?.name == null && this.server?.url == null) {
|
||||
this.server = null;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
export type SharedFlags = {
|
||||
showPasswordless?: boolean;
|
||||
sdk?: boolean;
|
||||
prereleaseBuild?: boolean;
|
||||
};
|
||||
|
||||
// required to avoid linting errors when there are no flags
|
||||
|
||||
@@ -16,6 +16,9 @@ describe("ServerConfigData", () => {
|
||||
name: "test",
|
||||
url: "https://test.com",
|
||||
},
|
||||
settings: {
|
||||
disableUserRegistration: false,
|
||||
},
|
||||
environment: {
|
||||
cloudRegion: Region.EU,
|
||||
vault: "https://vault.com",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Jsonify } from "type-fest";
|
||||
|
||||
import { AllowedFeatureFlagTypes } from "../../../enums/feature-flag.enum";
|
||||
import { Region } from "../../abstractions/environment.service";
|
||||
import { ServerSettings } from "../domain/server-settings";
|
||||
import {
|
||||
ServerConfigResponse,
|
||||
ThirdPartyServerConfigResponse,
|
||||
@@ -15,6 +16,7 @@ export class ServerConfigData {
|
||||
environment?: EnvironmentServerConfigData;
|
||||
utcDate: string;
|
||||
featureStates: { [key: string]: AllowedFeatureFlagTypes } = {};
|
||||
settings: ServerSettings;
|
||||
|
||||
constructor(serverConfigResponse: Partial<ServerConfigResponse>) {
|
||||
this.version = serverConfigResponse?.version;
|
||||
@@ -27,6 +29,7 @@ export class ServerConfigData {
|
||||
? new EnvironmentServerConfigData(serverConfigResponse.environment)
|
||||
: null;
|
||||
this.featureStates = serverConfigResponse?.featureStates;
|
||||
this.settings = new ServerSettings(serverConfigResponse.settings);
|
||||
}
|
||||
|
||||
static fromJSON(obj: Jsonify<ServerConfigData>): ServerConfigData {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ServerSettings } from "./server-settings";
|
||||
|
||||
describe("ServerSettings", () => {
|
||||
describe("disableUserRegistration", () => {
|
||||
it("defaults disableUserRegistration to false", () => {
|
||||
const settings = new ServerSettings();
|
||||
expect(settings.disableUserRegistration).toBe(false);
|
||||
});
|
||||
|
||||
it("sets disableUserRegistration to true when provided", () => {
|
||||
const settings = new ServerSettings({ disableUserRegistration: true });
|
||||
expect(settings.disableUserRegistration).toBe(true);
|
||||
});
|
||||
|
||||
it("sets disableUserRegistration to false when provided", () => {
|
||||
const settings = new ServerSettings({ disableUserRegistration: false });
|
||||
expect(settings.disableUserRegistration).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
export class ServerSettings {
|
||||
disableUserRegistration: boolean;
|
||||
|
||||
constructor(data?: ServerSettings) {
|
||||
this.disableUserRegistration = data?.disableUserRegistration ?? false;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AllowedFeatureFlagTypes } from "../../../enums/feature-flag.enum";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { Region } from "../../abstractions/environment.service";
|
||||
import { ServerSettings } from "../domain/server-settings";
|
||||
|
||||
export class ServerConfigResponse extends BaseResponse {
|
||||
version: string;
|
||||
@@ -8,6 +9,7 @@ export class ServerConfigResponse extends BaseResponse {
|
||||
server: ThirdPartyServerConfigResponse;
|
||||
environment: EnvironmentServerConfigResponse;
|
||||
featureStates: { [key: string]: AllowedFeatureFlagTypes } = {};
|
||||
settings: ServerSettings;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
@@ -21,6 +23,7 @@ export class ServerConfigResponse extends BaseResponse {
|
||||
this.server = new ThirdPartyServerConfigResponse(this.getResponseProperty("Server"));
|
||||
this.environment = new EnvironmentServerConfigResponse(this.getResponseProperty("Environment"));
|
||||
this.featureStates = this.getResponseProperty("FeatureStates");
|
||||
this.settings = new ServerSettings(this.getResponseProperty("Settings"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import { Environment, EnvironmentService, Region } from "../../abstractions/envi
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { devFlagEnabled, devFlagValue } from "../../misc/flags";
|
||||
import { ServerConfigData } from "../../models/data/server-config.data";
|
||||
import { ServerSettings } from "../../models/domain/server-settings";
|
||||
import { CONFIG_DISK, KeyDefinition, StateProvider, UserKeyDefinition } from "../../state";
|
||||
|
||||
export const RETRIEVAL_INTERVAL = devFlagEnabled("configRetrievalIntervalMs")
|
||||
@@ -57,6 +58,8 @@ export class DefaultConfigService implements ConfigService {
|
||||
|
||||
serverConfig$: Observable<ServerConfig>;
|
||||
|
||||
serverSettings$: Observable<ServerSettings>;
|
||||
|
||||
cloudRegion$: Observable<Region>;
|
||||
|
||||
constructor(
|
||||
@@ -111,6 +114,10 @@ export class DefaultConfigService implements ConfigService {
|
||||
this.cloudRegion$ = this.serverConfig$.pipe(
|
||||
map((config) => config?.environment?.cloudRegion ?? Region.US),
|
||||
);
|
||||
|
||||
this.serverSettings$ = this.serverConfig$.pipe(
|
||||
map((config) => config?.settings ?? new ServerSettings()),
|
||||
);
|
||||
}
|
||||
|
||||
getFeatureFlag$<Flag extends FeatureFlag>(key: Flag) {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { ConfigService } from "../abstractions/config/config.service";
|
||||
import { ServerSettings } from "../models/domain/server-settings";
|
||||
|
||||
import { DefaultServerSettingsService } from "./default-server-settings.service";
|
||||
|
||||
describe("DefaultServerSettingsService", () => {
|
||||
let service: DefaultServerSettingsService;
|
||||
let configServiceMock: { serverSettings$: any };
|
||||
|
||||
beforeEach(() => {
|
||||
configServiceMock = { serverSettings$: of() };
|
||||
service = new DefaultServerSettingsService(configServiceMock as ConfigService);
|
||||
});
|
||||
|
||||
describe("getSettings$", () => {
|
||||
it("returns server settings", () => {
|
||||
const mockSettings = new ServerSettings({ disableUserRegistration: true });
|
||||
configServiceMock.serverSettings$ = of(mockSettings);
|
||||
|
||||
service.getSettings$().subscribe((settings) => {
|
||||
expect(settings).toEqual(mockSettings);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("isUserRegistrationDisabled$", () => {
|
||||
it("returns true when user registration is disabled", () => {
|
||||
const mockSettings = new ServerSettings({ disableUserRegistration: true });
|
||||
configServiceMock.serverSettings$ = of(mockSettings);
|
||||
|
||||
service.isUserRegistrationDisabled$.subscribe((isDisabled: boolean) => {
|
||||
expect(isDisabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("returns false when user registration is enabled", () => {
|
||||
const mockSettings = new ServerSettings({ disableUserRegistration: false });
|
||||
configServiceMock.serverSettings$ = of(mockSettings);
|
||||
|
||||
service.isUserRegistrationDisabled$.subscribe((isDisabled: boolean) => {
|
||||
expect(isDisabled).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Observable } from "rxjs";
|
||||
import { map } from "rxjs/operators";
|
||||
|
||||
import { ConfigService } from "../abstractions/config/config.service";
|
||||
import { ServerSettings } from "../models/domain/server-settings";
|
||||
|
||||
export class DefaultServerSettingsService {
|
||||
constructor(private configService: ConfigService) {}
|
||||
|
||||
getSettings$(): Observable<ServerSettings> {
|
||||
return this.configService.serverSettings$;
|
||||
}
|
||||
|
||||
get isUserRegistrationDisabled$(): Observable<boolean> {
|
||||
return this.getSettings$().pipe(
|
||||
map((settings: ServerSettings) => settings.disableUserRegistration),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,7 @@ import { AppIdService } from "../platform/abstractions/app-id.service";
|
||||
import { EnvironmentService } from "../platform/abstractions/environment.service";
|
||||
import { LogService } from "../platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "../platform/abstractions/platform-utils.service";
|
||||
import { flagEnabled } from "../platform/misc/flags";
|
||||
import { Utils } from "../platform/misc/utils";
|
||||
import { SyncResponse } from "../platform/sync";
|
||||
import { UserId } from "../types/guid";
|
||||
@@ -1843,44 +1844,20 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
const requestUrl =
|
||||
apiUrl + Utils.normalizePath(pathParts[0]) + (pathParts.length > 1 ? `?${pathParts[1]}` : "");
|
||||
|
||||
const headers = new Headers({
|
||||
"Device-Type": this.deviceType,
|
||||
});
|
||||
if (this.customUserAgent != null) {
|
||||
headers.set("User-Agent", this.customUserAgent);
|
||||
}
|
||||
const [requestHeaders, requestBody] = await this.buildHeadersAndBody(
|
||||
authed,
|
||||
hasResponse,
|
||||
body,
|
||||
alterHeaders,
|
||||
);
|
||||
|
||||
const requestInit: RequestInit = {
|
||||
cache: "no-store",
|
||||
credentials: await this.getCredentials(),
|
||||
method: method,
|
||||
};
|
||||
|
||||
if (authed) {
|
||||
const authHeader = await this.getActiveBearerToken();
|
||||
headers.set("Authorization", "Bearer " + authHeader);
|
||||
}
|
||||
if (body != null) {
|
||||
if (typeof body === "string") {
|
||||
requestInit.body = body;
|
||||
headers.set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
|
||||
} else if (typeof body === "object") {
|
||||
if (body instanceof FormData) {
|
||||
requestInit.body = body;
|
||||
} else {
|
||||
headers.set("Content-Type", "application/json; charset=utf-8");
|
||||
requestInit.body = JSON.stringify(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasResponse) {
|
||||
headers.set("Accept", "application/json");
|
||||
}
|
||||
if (alterHeaders != null) {
|
||||
alterHeaders(headers);
|
||||
}
|
||||
|
||||
requestInit.headers = headers;
|
||||
requestInit.headers = requestHeaders;
|
||||
requestInit.body = requestBody;
|
||||
const response = await this.fetch(new Request(requestUrl, requestInit));
|
||||
|
||||
const responseType = response.headers.get("content-type");
|
||||
@@ -1897,6 +1874,51 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
private async buildHeadersAndBody(
|
||||
authed: boolean,
|
||||
hasResponse: boolean,
|
||||
body: any,
|
||||
alterHeaders: (headers: Headers) => void,
|
||||
): Promise<[Headers, any]> {
|
||||
let requestBody: any = null;
|
||||
const headers = new Headers({
|
||||
"Device-Type": this.deviceType,
|
||||
});
|
||||
|
||||
if (flagEnabled("prereleaseBuild")) {
|
||||
headers.set("Is-Prerelease", "true");
|
||||
}
|
||||
if (this.customUserAgent != null) {
|
||||
headers.set("User-Agent", this.customUserAgent);
|
||||
}
|
||||
if (hasResponse) {
|
||||
headers.set("Accept", "application/json");
|
||||
}
|
||||
if (alterHeaders != null) {
|
||||
alterHeaders(headers);
|
||||
}
|
||||
if (authed) {
|
||||
const authHeader = await this.getActiveBearerToken();
|
||||
headers.set("Authorization", "Bearer " + authHeader);
|
||||
}
|
||||
|
||||
if (body != null) {
|
||||
if (typeof body === "string") {
|
||||
requestBody = body;
|
||||
headers.set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
|
||||
} else if (typeof body === "object") {
|
||||
if (body instanceof FormData) {
|
||||
requestBody = body;
|
||||
} else {
|
||||
headers.set("Content-Type", "application/json; charset=utf-8");
|
||||
requestBody = JSON.stringify(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [headers, requestBody];
|
||||
}
|
||||
|
||||
private async handleError(
|
||||
response: Response,
|
||||
tokenError: boolean,
|
||||
|
||||
@@ -22,6 +22,7 @@ export type ObjectKey<State, Secret = State, Disclosed = Record<string, never>>
|
||||
classifier: Classifier<State, Disclosed, Secret>;
|
||||
format: "plain" | "classified";
|
||||
options: UserKeyDefinitionOptions<State>;
|
||||
initial?: State;
|
||||
};
|
||||
|
||||
export function isObjectKey(key: any): key is ObjectKey<unknown> {
|
||||
|
||||
@@ -254,17 +254,18 @@ export class UserStateSubject<
|
||||
withConstraints,
|
||||
map(([loadedState, constraints]) => {
|
||||
// bypass nulls
|
||||
if (!loadedState) {
|
||||
if (!loadedState && !this.objectKey?.initial) {
|
||||
return {
|
||||
constraints: {} as Constraints<State>,
|
||||
state: null,
|
||||
} satisfies Constrained<State>;
|
||||
}
|
||||
|
||||
const unconstrained = loadedState ?? structuredClone(this.objectKey.initial);
|
||||
const calibration = isDynamic(constraints)
|
||||
? constraints.calibrate(loadedState)
|
||||
? constraints.calibrate(unconstrained)
|
||||
: constraints;
|
||||
const adjusted = calibration.adjust(loadedState);
|
||||
const adjusted = calibration.adjust(unconstrained);
|
||||
|
||||
return {
|
||||
constraints: calibration.constraints,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
(blur)="onBlur()"
|
||||
[labelForId]="labelForId"
|
||||
[clearable]="false"
|
||||
(close)="onClose()"
|
||||
appendTo="body"
|
||||
>
|
||||
<ng-template ng-option-tmp let-item="item">
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
QueryList,
|
||||
Self,
|
||||
ViewChild,
|
||||
Output,
|
||||
EventEmitter,
|
||||
} from "@angular/core";
|
||||
import { ControlValueAccessor, NgControl, Validators } from "@angular/forms";
|
||||
import { NgSelectComponent } from "@ng-select/ng-select";
|
||||
@@ -31,6 +33,7 @@ export class SelectComponent<T> implements BitFormFieldControl, ControlValueAcce
|
||||
/** Optional: Options can be provided using an array input or using `bit-option` */
|
||||
@Input() items: Option<T>[] = [];
|
||||
@Input() placeholder = this.i18nService.t("selectPlaceholder");
|
||||
@Output() closed = new EventEmitter();
|
||||
|
||||
protected selectedValue: T;
|
||||
protected selectedOption: Option<T>;
|
||||
@@ -156,4 +159,9 @@ export class SelectComponent<T> implements BitFormFieldControl, ControlValueAcce
|
||||
private findSelectedOption(items: Option<T>[], value: T): Option<T> | undefined {
|
||||
return items.find((item) => item.value === value);
|
||||
}
|
||||
|
||||
/**Emits the closed event. */
|
||||
protected onClose() {
|
||||
this.closed.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<form class="box" [formGroup]="settings" class="tw-container">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "domainName" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="catchallDomain" type="text" />
|
||||
<input
|
||||
bitInput
|
||||
formControlName="catchallDomain"
|
||||
type="text"
|
||||
(change)="save('catchallDomain')"
|
||||
/>
|
||||
</bit-form-field>
|
||||
</form>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { BehaviorSubject, skip, Subject, takeUntil } from "rxjs";
|
||||
import { BehaviorSubject, map, skip, Subject, takeUntil, withLatestFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
@@ -12,6 +12,11 @@ import {
|
||||
|
||||
import { completeOnAccountSwitch } from "./util";
|
||||
|
||||
/** Splits an email into a username, subaddress, and domain named group.
|
||||
* Subaddress is optional.
|
||||
*/
|
||||
export const DOMAIN_PARSER = new RegExp("[^@]+@(?<domain>.+)");
|
||||
|
||||
/** Options group for catchall emails */
|
||||
@Component({
|
||||
selector: "tools-catchall-settings",
|
||||
@@ -60,7 +65,19 @@ export class CatchallSettingsComponent implements OnInit, OnDestroy {
|
||||
// the first emission is the current value; subsequent emissions are updates
|
||||
settings.pipe(skip(1), takeUntil(this.destroyed$)).subscribe(this.onUpdated);
|
||||
|
||||
this.settings.valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(settings);
|
||||
// now that outputs are set up, connect inputs
|
||||
this.saveSettings
|
||||
.pipe(
|
||||
withLatestFrom(this.settings.valueChanges),
|
||||
map(([, settings]) => settings),
|
||||
takeUntil(this.destroyed$),
|
||||
)
|
||||
.subscribe(settings);
|
||||
}
|
||||
|
||||
private saveSettings = new Subject<string>();
|
||||
save(site: string = "component api call") {
|
||||
this.saveSettings.next(site);
|
||||
}
|
||||
|
||||
private singleUserId$() {
|
||||
@@ -78,6 +95,7 @@ export class CatchallSettingsComponent implements OnInit, OnDestroy {
|
||||
|
||||
private readonly destroyed$ = new Subject<void>();
|
||||
ngOnDestroy(): void {
|
||||
this.destroyed$.next();
|
||||
this.destroyed$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
buttonType="main"
|
||||
(click)="generate('user request')"
|
||||
[appA11yTitle]="credentialTypeGenerateLabel$ | async"
|
||||
[disabled]="!(algorithm$ | async)"
|
||||
>
|
||||
{{ credentialTypeGenerateLabel$ | async }}
|
||||
</button>
|
||||
@@ -33,16 +34,19 @@
|
||||
[appA11yTitle]="credentialTypeCopyLabel$ | async"
|
||||
[appCopyClick]="value$ | async"
|
||||
[valueLabel]="credentialTypeLabel$ | async"
|
||||
[disabled]="!(algorithm$ | async)"
|
||||
></button>
|
||||
</div>
|
||||
</bit-card>
|
||||
<tools-password-settings
|
||||
#passwordSettings
|
||||
class="tw-mt-6"
|
||||
*ngIf="(showAlgorithm$ | async)?.id === 'password'"
|
||||
[userId]="userId$ | async"
|
||||
(onUpdated)="generate('password settings')"
|
||||
/>
|
||||
<tools-passphrase-settings
|
||||
#passphraseSettings
|
||||
class="tw-mt-6"
|
||||
*ngIf="(showAlgorithm$ | async)?.id === 'passphrase'"
|
||||
[userId]="userId$ | async"
|
||||
@@ -80,21 +84,25 @@
|
||||
</bit-form-field>
|
||||
</form>
|
||||
<tools-catchall-settings
|
||||
#catchallSettings
|
||||
*ngIf="(showAlgorithm$ | async)?.id === 'catchall'"
|
||||
[userId]="userId$ | async"
|
||||
(onUpdated)="generate('catchall settings')"
|
||||
/>
|
||||
<tools-forwarder-settings
|
||||
#forwarderSettings
|
||||
*ngIf="!!(forwarderId$ | async)"
|
||||
[forwarder]="forwarderId$ | async"
|
||||
[userId]="this.userId$ | async"
|
||||
/>
|
||||
<tools-subaddress-settings
|
||||
#subaddressSettings
|
||||
*ngIf="(showAlgorithm$ | async)?.id === 'subaddress'"
|
||||
[userId]="userId$ | async"
|
||||
(onUpdated)="generate('subaddress settings')"
|
||||
/>
|
||||
<tools-username-settings
|
||||
#usernameSettings
|
||||
*ngIf="(showAlgorithm$ | async)?.id === 'username'"
|
||||
[userId]="userId$ | async"
|
||||
(onUpdated)="generate('username settings')"
|
||||
|
||||
@@ -202,9 +202,8 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
});
|
||||
|
||||
// normalize cascade selections; introduce subjects to allow changes
|
||||
// from user selections and changes from preference updates to
|
||||
// update the template
|
||||
// these subjects normalize cascade selections to ensure the current
|
||||
// cascade is always well-known.
|
||||
type CascadeValue = { nav: string; algorithm?: CredentialAlgorithm };
|
||||
const activeRoot$ = new Subject<CascadeValue>();
|
||||
const activeIdentifier$ = new Subject<CascadeValue>();
|
||||
@@ -385,7 +384,7 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
|
||||
if (!a || a.onlyOnRequest) {
|
||||
this.value$.next("-");
|
||||
} else {
|
||||
this.generate("autogenerate");
|
||||
this.generate("autogenerate").catch((e: unknown) => this.logService.error(e));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -495,7 +494,7 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
|
||||
* @param requestor a label used to trace generation request
|
||||
* origin in the debugger.
|
||||
*/
|
||||
protected generate(requestor: string) {
|
||||
protected async generate(requestor: string) {
|
||||
this.generate$.next(requestor);
|
||||
}
|
||||
|
||||
@@ -510,6 +509,7 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
|
||||
|
||||
private readonly destroyed = new Subject<void>();
|
||||
ngOnDestroy() {
|
||||
this.destroyed.next();
|
||||
this.destroyed.complete();
|
||||
|
||||
// finalize subjects
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
<form class="box" [formGroup]="settings" class="tw-container">
|
||||
<bit-form-field *ngIf="displayDomain">
|
||||
<bit-label>{{ "forwarderDomainName" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="domain" type="text" placeholder="example.com" />
|
||||
<input
|
||||
bitInput
|
||||
formControlName="domain"
|
||||
type="text"
|
||||
placeholder="example.com"
|
||||
(change)="save('domain')"
|
||||
/>
|
||||
<bit-hint>{{ "forwarderDomainNameHint" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<bit-form-field *ngIf="displayToken">
|
||||
<bit-label>{{ "apiKey" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="token" type="password" />
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton
|
||||
bitSuffix
|
||||
bitPasswordInputToggle
|
||||
(change)="save('token')"
|
||||
></button>
|
||||
</bit-form-field>
|
||||
<bit-form-field *ngIf="displayBaseUrl" disableMargin>
|
||||
<bit-label>{{ "selfHostBaseUrl" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="baseUrl" type="text" />
|
||||
<input bitInput formControlName="baseUrl" type="text" (change)="save('baseUrl')" />
|
||||
</bit-form-field>
|
||||
</form>
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
skip,
|
||||
Subject,
|
||||
switchAll,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
withLatestFrom,
|
||||
} from "rxjs";
|
||||
@@ -33,7 +32,7 @@ import {
|
||||
toCredentialGeneratorConfiguration,
|
||||
} from "@bitwarden/generator-core";
|
||||
|
||||
import { completeOnAccountSwitch, toValidators } from "./util";
|
||||
import { completeOnAccountSwitch } from "./util";
|
||||
|
||||
const Controls = Object.freeze({
|
||||
domain: "domain",
|
||||
@@ -117,35 +116,17 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy
|
||||
this.settings.patchValue(settings as any, { emitEvent: false });
|
||||
});
|
||||
|
||||
// bind policy to the reactive form
|
||||
forwarder$
|
||||
.pipe(
|
||||
switchMap((forwarder) => {
|
||||
const constraints$ = this.generatorService
|
||||
.policy$(forwarder, { userId$: singleUserId$ })
|
||||
.pipe(map(({ constraints }) => [constraints, forwarder] as const));
|
||||
|
||||
return constraints$;
|
||||
}),
|
||||
takeUntil(this.destroyed$),
|
||||
)
|
||||
.subscribe(([constraints, forwarder]) => {
|
||||
for (const name in Controls) {
|
||||
const control = this.settings.get(name);
|
||||
if (forwarder.request.includes(name as any)) {
|
||||
control.enable({ emitEvent: false });
|
||||
control.setValidators(
|
||||
// the configuration's type erasure affects `toValidators` as well
|
||||
toValidators(name, forwarder, constraints),
|
||||
);
|
||||
} else {
|
||||
control.disable({ emitEvent: false });
|
||||
control.clearValidators();
|
||||
}
|
||||
// enable requested forwarder inputs
|
||||
forwarder$.pipe(takeUntil(this.destroyed$)).subscribe((forwarder) => {
|
||||
for (const name in Controls) {
|
||||
const control = this.settings.get(name);
|
||||
if (forwarder.request.includes(name as any)) {
|
||||
control.enable({ emitEvent: false });
|
||||
} else {
|
||||
control.disable({ emitEvent: false });
|
||||
}
|
||||
|
||||
this.settings.updateValueAndValidity({ emitEvent: false });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// the first emission is the current value; subsequent emissions are updates
|
||||
settings$$
|
||||
@@ -157,13 +138,18 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy
|
||||
.subscribe(this.onUpdated);
|
||||
|
||||
// now that outputs are set up, connect inputs
|
||||
this.settings.valueChanges
|
||||
.pipe(withLatestFrom(settings$$), takeUntil(this.destroyed$))
|
||||
.subscribe(([value, settings]) => {
|
||||
this.saveSettings
|
||||
.pipe(withLatestFrom(this.settings.valueChanges, settings$$), takeUntil(this.destroyed$))
|
||||
.subscribe(([, value, settings]) => {
|
||||
settings.next(value);
|
||||
});
|
||||
}
|
||||
|
||||
private saveSettings = new Subject<string>();
|
||||
save(site: string = "component api call") {
|
||||
this.saveSettings.next(site);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this.refresh$.complete();
|
||||
if ("forwarder" in changes) {
|
||||
@@ -192,6 +178,7 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy
|
||||
|
||||
private readonly destroyed$ = new Subject<void>();
|
||||
ngOnDestroy(): void {
|
||||
this.destroyed$.next();
|
||||
this.destroyed$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
|
||||
import { SafeInjectionToken } from "@bitwarden/angular/services/injection-tokens";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
@@ -79,6 +80,7 @@ const RANDOMIZER = new SafeInjectionToken<Randomizer>("Randomizer");
|
||||
I18nService,
|
||||
EncryptService,
|
||||
KeyService,
|
||||
AccountService,
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -7,7 +7,13 @@
|
||||
<bit-card>
|
||||
<bit-form-field disableMargin>
|
||||
<bit-label>{{ "numWords" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="numWords" id="num-words" type="number" />
|
||||
<input
|
||||
bitInput
|
||||
formControlName="numWords"
|
||||
id="num-words"
|
||||
type="number"
|
||||
(change)="save('numWords')"
|
||||
/>
|
||||
<bit-hint>{{ numWordsBoundariesHint$ | async }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</bit-card>
|
||||
@@ -16,14 +22,33 @@
|
||||
<bit-card>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "wordSeparator" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="wordSeparator" id="word-separator" type="text" />
|
||||
<input
|
||||
bitInput
|
||||
formControlName="wordSeparator"
|
||||
id="word-separator"
|
||||
type="text"
|
||||
[maxlength]="wordSeparatorMaxLength"
|
||||
(change)="save('wordSeparator')"
|
||||
/>
|
||||
</bit-form-field>
|
||||
<bit-form-control>
|
||||
<input bitCheckbox formControlName="capitalize" id="capitalize" type="checkbox" />
|
||||
<input
|
||||
bitCheckbox
|
||||
formControlName="capitalize"
|
||||
id="capitalize"
|
||||
type="checkbox"
|
||||
(change)="save('capitalize')"
|
||||
/>
|
||||
<bit-label>{{ "capitalize" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
<bit-form-control [disableMargin]="!policyInEffect">
|
||||
<input bitCheckbox formControlName="includeNumber" id="include-number" type="checkbox" />
|
||||
<input
|
||||
bitCheckbox
|
||||
formControlName="includeNumber"
|
||||
id="include-number"
|
||||
type="checkbox"
|
||||
(change)="save('includeNumber')"
|
||||
/>
|
||||
<bit-label>{{ "includeNumber" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
<p *ngIf="policyInEffect" bitTypography="helper">{{ "generatorPolicyInEffect" | i18n }}</p>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { coerceBooleanProperty } from "@angular/cdk/coercion";
|
||||
import { OnInit, Input, Output, EventEmitter, Component, OnDestroy } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { BehaviorSubject, skip, takeUntil, Subject, ReplaySubject } from "rxjs";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
skip,
|
||||
takeUntil,
|
||||
Subject,
|
||||
map,
|
||||
withLatestFrom,
|
||||
ReplaySubject,
|
||||
} from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -12,7 +20,7 @@ import {
|
||||
PassphraseGenerationOptions,
|
||||
} from "@bitwarden/generator-core";
|
||||
|
||||
import { completeOnAccountSwitch, toValidators } from "./util";
|
||||
import { completeOnAccountSwitch } from "./util";
|
||||
|
||||
const Controls = Object.freeze({
|
||||
numWords: "numWords",
|
||||
@@ -81,21 +89,12 @@ export class PassphraseSettingsComponent implements OnInit, OnDestroy {
|
||||
// the first emission is the current value; subsequent emissions are updates
|
||||
settings.pipe(skip(1), takeUntil(this.destroyed$)).subscribe(this.onUpdated);
|
||||
|
||||
// dynamic policy enforcement
|
||||
// explain policy & disable policy-overridden fields
|
||||
this.generatorService
|
||||
.policy$(Generators.passphrase, { userId$: singleUserId$ })
|
||||
.pipe(takeUntil(this.destroyed$))
|
||||
.subscribe(({ constraints }) => {
|
||||
this.settings
|
||||
.get(Controls.numWords)
|
||||
.setValidators(toValidators(Controls.numWords, Generators.passphrase, constraints));
|
||||
|
||||
this.settings
|
||||
.get(Controls.wordSeparator)
|
||||
.setValidators(toValidators(Controls.wordSeparator, Generators.passphrase, constraints));
|
||||
|
||||
this.settings.updateValueAndValidity({ emitEvent: false });
|
||||
|
||||
this.wordSeparatorMaxLength = constraints.wordSeparator.maxLength;
|
||||
this.policyInEffect = constraints.policyInEffect;
|
||||
|
||||
this.toggleEnabled(Controls.capitalize, !constraints.capitalize?.readonly);
|
||||
@@ -110,7 +109,21 @@ export class PassphraseSettingsComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
// now that outputs are set up, connect inputs
|
||||
this.settings.valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(settings);
|
||||
this.saveSettings
|
||||
.pipe(
|
||||
withLatestFrom(this.settings.valueChanges),
|
||||
map(([, settings]) => settings),
|
||||
takeUntil(this.destroyed$),
|
||||
)
|
||||
.subscribe(settings);
|
||||
}
|
||||
|
||||
/** attribute binding for wordSeparator[maxlength] */
|
||||
protected wordSeparatorMaxLength: number;
|
||||
|
||||
private saveSettings = new Subject<string>();
|
||||
save(site: string = "component api call") {
|
||||
this.saveSettings.next(site);
|
||||
}
|
||||
|
||||
/** display binding for enterprise policy notice */
|
||||
@@ -144,6 +157,7 @@ export class PassphraseSettingsComponent implements OnInit, OnDestroy {
|
||||
|
||||
private readonly destroyed$ = new Subject<void>();
|
||||
ngOnDestroy(): void {
|
||||
this.destroyed$.next();
|
||||
this.destroyed$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
buttonType="main"
|
||||
(click)="generate('user request')"
|
||||
[appA11yTitle]="credentialTypeGenerateLabel$ | async"
|
||||
[disabled]="!(algorithm$ | async)"
|
||||
>
|
||||
{{ credentialTypeGenerateLabel$ | async }}
|
||||
</button>
|
||||
@@ -31,10 +32,12 @@
|
||||
[appA11yTitle]="credentialTypeCopyLabel$ | async"
|
||||
[appCopyClick]="value$ | async"
|
||||
[valueLabel]="credentialTypeLabel$ | async"
|
||||
[disabled]="!(algorithm$ | async)"
|
||||
></button>
|
||||
</div>
|
||||
</bit-card>
|
||||
<tools-password-settings
|
||||
#passwordSettings
|
||||
class="tw-mt-6"
|
||||
*ngIf="(algorithm$ | async)?.id === 'password'"
|
||||
[userId]="this.userId$ | async"
|
||||
@@ -42,6 +45,7 @@
|
||||
(onUpdated)="generate('password settings')"
|
||||
/>
|
||||
<tools-passphrase-settings
|
||||
#passphraseSettings
|
||||
class="tw-mt-6"
|
||||
*ngIf="(algorithm$ | async)?.id === 'passphrase'"
|
||||
[userId]="this.userId$ | async"
|
||||
|
||||
@@ -22,11 +22,11 @@ import { Option } from "@bitwarden/components/src/select/option";
|
||||
import {
|
||||
CredentialGeneratorService,
|
||||
Generators,
|
||||
PasswordAlgorithm,
|
||||
GeneratedCredential,
|
||||
CredentialAlgorithm,
|
||||
isPasswordAlgorithm,
|
||||
AlgorithmInfo,
|
||||
isSameAlgorithm,
|
||||
} from "@bitwarden/generator-core";
|
||||
import { GeneratorHistoryService } from "@bitwarden/generator-history";
|
||||
|
||||
@@ -57,7 +57,7 @@ export class PasswordGeneratorComponent implements OnInit, OnDestroy {
|
||||
@Input({ transform: coerceBooleanProperty }) disableMargin = false;
|
||||
|
||||
/** tracks the currently selected credential type */
|
||||
protected credentialType$ = new BehaviorSubject<PasswordAlgorithm>(null);
|
||||
protected credentialType$ = new BehaviorSubject<CredentialAlgorithm>(null);
|
||||
|
||||
/** Emits the last generated value. */
|
||||
protected readonly value$ = new BehaviorSubject<string>("");
|
||||
@@ -72,14 +72,14 @@ export class PasswordGeneratorComponent implements OnInit, OnDestroy {
|
||||
* @param requestor a label used to trace generation request
|
||||
* origin in the debugger.
|
||||
*/
|
||||
protected generate(requestor: string) {
|
||||
protected async generate(requestor: string) {
|
||||
this.generate$.next(requestor);
|
||||
}
|
||||
|
||||
/** Tracks changes to the selected credential type
|
||||
* @param type the new credential type
|
||||
*/
|
||||
protected onCredentialTypeChanged(type: PasswordAlgorithm) {
|
||||
protected onCredentialTypeChanged(type: CredentialAlgorithm) {
|
||||
// break subscription cycle
|
||||
if (this.credentialType$.value !== type) {
|
||||
this.zone.run(() => {
|
||||
@@ -169,29 +169,34 @@ export class PasswordGeneratorComponent implements OnInit, OnDestroy {
|
||||
preferences.next(preference);
|
||||
});
|
||||
|
||||
// populate the form with the user's preferences to kick off interactivity
|
||||
preferences.pipe(takeUntil(this.destroyed)).subscribe(({ password }) => {
|
||||
// update navigation
|
||||
this.onCredentialTypeChanged(password.algorithm);
|
||||
|
||||
// load algorithm metadata
|
||||
const algorithm = this.generatorService.algorithm(password.algorithm);
|
||||
|
||||
// update subjects within the angular zone so that the
|
||||
// template bindings refresh immediately
|
||||
this.zone.run(() => {
|
||||
this.algorithm$.next(algorithm);
|
||||
});
|
||||
});
|
||||
|
||||
// generate on load unless the generator prohibits it
|
||||
this.algorithm$
|
||||
// update active algorithm
|
||||
preferences
|
||||
.pipe(
|
||||
distinctUntilChanged((prev, next) => prev.id === next.id),
|
||||
filter((a) => !a.onlyOnRequest),
|
||||
map(({ password }) => this.generatorService.algorithm(password.algorithm)),
|
||||
distinctUntilChanged((prev, next) => isSameAlgorithm(prev?.id, next?.id)),
|
||||
takeUntil(this.destroyed),
|
||||
)
|
||||
.subscribe(() => this.generate("autogenerate"));
|
||||
.subscribe((algorithm) => {
|
||||
// update navigation
|
||||
this.onCredentialTypeChanged(algorithm.id);
|
||||
|
||||
// update subjects within the angular zone so that the
|
||||
// template bindings refresh immediately
|
||||
this.zone.run(() => {
|
||||
this.algorithm$.next(algorithm);
|
||||
});
|
||||
});
|
||||
|
||||
// generate on load unless the generator prohibits it
|
||||
this.algorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => {
|
||||
this.zone.run(() => {
|
||||
if (!a || a.onlyOnRequest) {
|
||||
this.value$.next("-");
|
||||
} else {
|
||||
this.generate("autogenerate").catch((e: unknown) => this.logService.error(e));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private typeToGenerator$(type: CredentialAlgorithm) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<bit-card>
|
||||
<bit-form-field disableMargin>
|
||||
<bit-label>{{ "length" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="length" type="number" />
|
||||
<input bitInput formControlName="length" type="number" (change)="save('length')" />
|
||||
<bit-hint>{{ lengthBoundariesHint$ | async }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</bit-card>
|
||||
@@ -21,7 +21,12 @@
|
||||
attr.aria-description="{{ 'uppercaseDescription' | i18n }}"
|
||||
title="{{ 'uppercaseDescription' | i18n }}"
|
||||
>
|
||||
<input bitCheckbox type="checkbox" formControlName="uppercase" />
|
||||
<input
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
formControlName="uppercase"
|
||||
(change)="save('uppercase')"
|
||||
/>
|
||||
<bit-label>{{ "uppercaseLabel" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
<bit-form-control
|
||||
@@ -29,7 +34,12 @@
|
||||
attr.aria-description="{{ 'lowercaseDescription' | i18n }}"
|
||||
title="{{ 'lowercaseDescription' | i18n }}"
|
||||
>
|
||||
<input bitCheckbox type="checkbox" formControlName="lowercase" />
|
||||
<input
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
formControlName="lowercase"
|
||||
(change)="save('lowercase')"
|
||||
/>
|
||||
<bit-label>{{ "lowercaseLabel" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
<bit-form-control
|
||||
@@ -37,7 +47,7 @@
|
||||
attr.aria-description="{{ 'numbersDescription' | i18n }}"
|
||||
title="{{ 'numbersDescription' | i18n }}"
|
||||
>
|
||||
<input bitCheckbox type="checkbox" formControlName="number" />
|
||||
<input bitCheckbox type="checkbox" formControlName="number" (change)="save('number')" />
|
||||
<bit-label>{{ "numbersLabel" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
<bit-form-control
|
||||
@@ -45,22 +55,42 @@
|
||||
attr.aria-description="{{ 'specialCharactersDescription' | i18n }}"
|
||||
title="{{ 'specialCharactersDescription' | i18n }}"
|
||||
>
|
||||
<input bitCheckbox type="checkbox" formControlName="special" />
|
||||
<input
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
formControlName="special"
|
||||
(change)="save('special')"
|
||||
/>
|
||||
<bit-label>{{ "specialCharactersLabel" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
</div>
|
||||
<div class="tw-flex">
|
||||
<bit-form-field class="tw-w-full tw-basis-1/2 tw-mr-4">
|
||||
<bit-label>{{ "minNumbers" | i18n }}</bit-label>
|
||||
<input bitInput type="number" formControlName="minNumber" />
|
||||
<input
|
||||
bitInput
|
||||
type="number"
|
||||
formControlName="minNumber"
|
||||
(change)="save('minNumbers')"
|
||||
/>
|
||||
</bit-form-field>
|
||||
<bit-form-field class="tw-w-full tw-basis-1/2">
|
||||
<bit-label>{{ "minSpecial" | i18n }}</bit-label>
|
||||
<input bitInput type="number" formControlName="minSpecial" />
|
||||
<input
|
||||
bitInput
|
||||
type="number"
|
||||
formControlName="minSpecial"
|
||||
(change)="save('minSpecial')"
|
||||
/>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<bit-form-control [disableMargin]="!policyInEffect">
|
||||
<input bitCheckbox type="checkbox" formControlName="avoidAmbiguous" />
|
||||
<input
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
formControlName="avoidAmbiguous"
|
||||
(change)="save('avoidAmbiguous')"
|
||||
/>
|
||||
<bit-label>{{ "avoidAmbiguous" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
<p *ngIf="policyInEffect" bitTypography="helper">{{ "generatorPolicyInEffect" | i18n }}</p>
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { coerceBooleanProperty } from "@angular/cdk/coercion";
|
||||
import { OnInit, Input, Output, EventEmitter, Component, OnDestroy } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { BehaviorSubject, takeUntil, Subject, map, filter, tap, skip, ReplaySubject } from "rxjs";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
takeUntil,
|
||||
Subject,
|
||||
map,
|
||||
filter,
|
||||
tap,
|
||||
skip,
|
||||
ReplaySubject,
|
||||
withLatestFrom,
|
||||
} from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -12,7 +22,7 @@ import {
|
||||
PasswordGenerationOptions,
|
||||
} from "@bitwarden/generator-core";
|
||||
|
||||
import { completeOnAccountSwitch, toValidators } from "./util";
|
||||
import { completeOnAccountSwitch } from "./util";
|
||||
|
||||
const Controls = Object.freeze({
|
||||
length: "length",
|
||||
@@ -118,23 +128,11 @@ export class PasswordSettingsComponent implements OnInit, OnDestroy {
|
||||
this.settings.patchValue(s, { emitEvent: false });
|
||||
});
|
||||
|
||||
// bind policy to the template
|
||||
// explain policy & disable policy-overridden fields
|
||||
this.generatorService
|
||||
.policy$(Generators.password, { userId$: singleUserId$ })
|
||||
.pipe(takeUntil(this.destroyed$))
|
||||
.subscribe(({ constraints }) => {
|
||||
this.settings
|
||||
.get(Controls.length)
|
||||
.setValidators(toValidators(Controls.length, Generators.password, constraints));
|
||||
|
||||
this.minNumber.setValidators(
|
||||
toValidators(Controls.minNumber, Generators.password, constraints),
|
||||
);
|
||||
|
||||
this.minSpecial.setValidators(
|
||||
toValidators(Controls.minSpecial, Generators.password, constraints),
|
||||
);
|
||||
|
||||
this.policyInEffect = constraints.policyInEffect;
|
||||
|
||||
const toggles = [
|
||||
@@ -153,8 +151,8 @@ export class PasswordSettingsComponent implements OnInit, OnDestroy {
|
||||
|
||||
const boundariesHint = this.i18nService.t(
|
||||
"generatorBoundariesHint",
|
||||
constraints.length.min,
|
||||
constraints.length.max,
|
||||
constraints.length.min?.toString(),
|
||||
constraints.length.max?.toString(),
|
||||
);
|
||||
this.lengthBoundariesHint.next(boundariesHint);
|
||||
});
|
||||
@@ -201,9 +199,10 @@ export class PasswordSettingsComponent implements OnInit, OnDestroy {
|
||||
settings.pipe(skip(1), takeUntil(this.destroyed$)).subscribe(this.onUpdated);
|
||||
|
||||
// now that outputs are set up, connect inputs
|
||||
this.settings.valueChanges
|
||||
this.saveSettings
|
||||
.pipe(
|
||||
map((settings) => {
|
||||
withLatestFrom(this.settings.valueChanges),
|
||||
map(([, settings]) => {
|
||||
// interface is "avoid" while storage is "include"
|
||||
const s: any = { ...settings };
|
||||
s.ambiguous = s.avoidAmbiguous;
|
||||
@@ -215,6 +214,11 @@ export class PasswordSettingsComponent implements OnInit, OnDestroy {
|
||||
.subscribe(settings);
|
||||
}
|
||||
|
||||
private saveSettings = new Subject<string>();
|
||||
save(site: string = "component api call") {
|
||||
this.saveSettings.next(site);
|
||||
}
|
||||
|
||||
/** display binding for enterprise policy notice */
|
||||
protected policyInEffect: boolean;
|
||||
|
||||
@@ -246,6 +250,7 @@ export class PasswordSettingsComponent implements OnInit, OnDestroy {
|
||||
|
||||
private readonly destroyed$ = new Subject<void>();
|
||||
ngOnDestroy(): void {
|
||||
this.destroyed$.next();
|
||||
this.destroyed$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<form class="box" [formGroup]="settings" class="tw-container">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "email" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="subaddressEmail" type="text" />
|
||||
<input
|
||||
bitInput
|
||||
formControlName="subaddressEmail"
|
||||
type="text"
|
||||
(change)="save('subaddressEmail')"
|
||||
/>
|
||||
</bit-form-field>
|
||||
</form>
|
||||
|
||||
@@ -53,28 +53,25 @@ export class SubaddressSettingsComponent implements OnInit, OnDestroy {
|
||||
const singleUserId$ = this.singleUserId$();
|
||||
const settings = await this.generatorService.settings(Generators.subaddress, { singleUserId$ });
|
||||
|
||||
settings
|
||||
.pipe(
|
||||
withLatestFrom(this.accountService.activeAccount$),
|
||||
map(([settings, activeAccount]) => {
|
||||
// if the subaddress isn't specified, copy it from
|
||||
// the user's settings
|
||||
if ((settings.subaddressEmail ?? "").length < 1) {
|
||||
settings.subaddressEmail = activeAccount.email;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}),
|
||||
takeUntil(this.destroyed$),
|
||||
)
|
||||
.subscribe((s) => {
|
||||
this.settings.patchValue(s, { emitEvent: false });
|
||||
});
|
||||
settings.pipe(takeUntil(this.destroyed$)).subscribe((s) => {
|
||||
this.settings.patchValue(s, { emitEvent: false });
|
||||
});
|
||||
|
||||
// the first emission is the current value; subsequent emissions are updates
|
||||
settings.pipe(skip(1), takeUntil(this.destroyed$)).subscribe(this.onUpdated);
|
||||
|
||||
this.settings.valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(settings);
|
||||
this.saveSettings
|
||||
.pipe(
|
||||
withLatestFrom(this.settings.valueChanges),
|
||||
map(([, settings]) => settings),
|
||||
takeUntil(this.destroyed$),
|
||||
)
|
||||
.subscribe(settings);
|
||||
}
|
||||
|
||||
private saveSettings = new Subject<string>();
|
||||
save(site: string = "component api call") {
|
||||
this.saveSettings.next(site);
|
||||
}
|
||||
|
||||
private singleUserId$() {
|
||||
@@ -92,6 +89,7 @@ export class SubaddressSettingsComponent implements OnInit, OnDestroy {
|
||||
|
||||
private readonly destroyed$ = new Subject<void>();
|
||||
ngOnDestroy(): void {
|
||||
this.destroyed$.next();
|
||||
this.destroyed$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user