1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 06:13:38 +00:00

Merge branch 'master' into feature/org-admin-refresh

This commit is contained in:
Shane Melton
2022-10-11 13:28:19 -07:00
140 changed files with 3451 additions and 683 deletions

View File

@@ -73,6 +73,10 @@
{ {
"message": "Calling `svgIcon` directly is not allowed", "message": "Calling `svgIcon` directly is not allowed",
"selector": "CallExpression[callee.name='svgIcon']" "selector": "CallExpression[callee.name='svgIcon']"
},
{
"message": "Accessing FormGroup using `get` is not allowed, use `.value` instead",
"selector": "ChainExpression[expression.object.callee.property.name='get'][expression.property.name='value']"
} }
], ],
"curly": ["error", "all"], "curly": ["error", "all"],
@@ -97,7 +101,10 @@
] ]
} }
], ],
"no-restricted-imports": ["error", { "patterns": ["src/**/*"] }] "no-restricted-imports": [
"error",
{ "patterns": ["src/**/*"], "paths": ["@fluffy-spoon/substitute"] }
]
}, },
"overrides": [ "overrides": [
{ {

View File

@@ -370,8 +370,8 @@ jobs:
- cloc - cloc
- setup - setup
- build-artifacts - build-artifacts
- build-containers
- build-commercial-selfhost-image - build-commercial-selfhost-image
- build-containers
- crowdin-push - crowdin-push
steps: steps:
- name: Check if any job failed - name: Check if any job failed
@@ -381,7 +381,7 @@ jobs:
SETUP_STATUS: ${{ needs.setup.result }} SETUP_STATUS: ${{ needs.setup.result }}
ARTIFACT_STATUS: ${{ needs.build-artifacts.result }} ARTIFACT_STATUS: ${{ needs.build-artifacts.result }}
BUILD_SELFHOST_STATUS: ${{ needs.build-commercial-selfhost-image.result }} BUILD_SELFHOST_STATUS: ${{ needs.build-commercial-selfhost-image.result }}
BUILD_QA_STATUS: ${{ needs.build-qa.result }} BUILD_CONTAINERS_STATUS: ${{ needs.build-containers.result }}
CROWDIN_PUSH_STATUS: ${{ needs.crowdin-push.result }} CROWDIN_PUSH_STATUS: ${{ needs.crowdin-push.result }}
run: | run: |
if [ "$CLOC_STATUS" = "failure" ]; then if [ "$CLOC_STATUS" = "failure" ]; then
@@ -392,7 +392,7 @@ jobs:
exit 1 exit 1
elif [ "$BUILD_SELFHOST_STATUS" = "failure" ]; then elif [ "$BUILD_SELFHOST_STATUS" = "failure" ]; then
exit 1 exit 1
elif [ "$BUILD_QA_STATUS" = "failure" ]; then elif [ "$BUILD_CONTAINERS_STATUS" = "failure" ]; then
exit 1 exit 1
elif [ "$CROWDIN_PUSH_STATUS" = "failure" ]; then elif [ "$CROWDIN_PUSH_STATUS" = "failure" ]; then
exit 1 exit 1

View File

@@ -102,7 +102,7 @@ jobs:
- name: Download latest Release build artifacts - name: Download latest Release build artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-browser.yml workflow: build-browser.yml
workflow_conclusion: success workflow_conclusion: success
@@ -115,7 +115,7 @@ jobs:
- name: Download latest master build artifacts - name: Download latest master build artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-browser.yml workflow: build-browser.yml
workflow_conclusion: success workflow_conclusion: success

View File

@@ -76,7 +76,7 @@ jobs:
- name: Download all Release artifacts - name: Download all Release artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-cli.yml workflow: build-cli.yml
path: apps/cli path: apps/cli
@@ -85,7 +85,7 @@ jobs:
- name: Download all artifacts - name: Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-cli.yml workflow: build-cli.yml
path: apps/cli path: apps/cli
@@ -167,7 +167,7 @@ jobs:
- name: Download artifacts - name: Download artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-cli.yml workflow: build-cli.yml
path: apps/cli path: apps/cli
@@ -177,7 +177,7 @@ jobs:
- name: Download artifacts - name: Download artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-cli.yml workflow: build-cli.yml
path: apps/cli path: apps/cli
@@ -232,7 +232,7 @@ jobs:
- name: Download artifacts - name: Download artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-cli.yml workflow: build-cli.yml
path: apps/cli/dist path: apps/cli/dist
@@ -242,7 +242,7 @@ jobs:
- name: Download artifacts - name: Download artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-cli.yml workflow: build-cli.yml
path: apps/cli/dist path: apps/cli/dist
@@ -289,7 +289,7 @@ jobs:
- name: Download artifacts - name: Download artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-cli.yml workflow: build-cli.yml
path: apps/cli/build path: apps/cli/build
@@ -299,7 +299,7 @@ jobs:
- name: Download artifacts - name: Download artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-cli.yml workflow: build-cli.yml
path: apps/cli/build path: apps/cli/build

View File

@@ -944,7 +944,11 @@ jobs:
SECRETS: | SECRETS: |
aws-electron-access-id, aws-electron-access-id,
aws-electron-access-key, aws-electron-access-key,
aws-electron-bucket-name aws-electron-bucket-name,
r2-electron-access-id,
r2-electron-access-key,
r2-electron-bucket-name,
cf-prod-account
run: | run: |
for i in ${SECRETS//,/ } for i in ${SECRETS//,/ }
do do
@@ -977,6 +981,20 @@ jobs:
--recursive \ --recursive \
--quiet --quiet
- name: Publish artifacts to R2
env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }}
AWS_DEFAULT_REGION: 'us-east-1'
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }}
CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }}
working-directory: apps/desktop/artifacts
run: |
aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \
--recursive \
--quiet \
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
- name: Update deployment status to Success - name: Update deployment status to Success
if: ${{ success() }} if: ${{ success() }}
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86 uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86

View File

@@ -13,13 +13,18 @@ on:
- Initial Release - Initial Release
- Redeploy - Redeploy
- Dry Run - Dry Run
rollout_percentage:
description: 'Staged Rollout Percentage'
required: true
default: '10'
type: string
snap_publish: snap_publish:
description: 'Publish to snap store' description: 'Publish to Snap store'
required: true required: true
default: true default: true
type: boolean type: boolean
choco_publish: choco_publish:
description: 'Publish to chocolatey store' description: 'Publish to Chocolatey store'
required: true required: true
default: true default: true
type: boolean type: boolean
@@ -93,23 +98,20 @@ jobs:
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
env: uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
KEYVAULT: bitwarden-prod-kv with:
SECRETS: | keyvault: "bitwarden-prod-kv"
aws-electron-access-id, secrets: "aws-electron-access-id,
aws-electron-access-key, aws-electron-access-key,
aws-electron-bucket-name aws-electron-bucket-name,
run: | r2-electron-access-id,
for i in ${SECRETS//,/ } r2-electron-access-key,
do r2-electron-bucket-name,
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv) cf-prod-account"
echo "::add-mask::$VALUE"
echo "::set-output name=$i::$VALUE"
done
- name: Download all artifacts - name: Download all artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-desktop.yml workflow: build-desktop.yml
workflow_conclusion: success workflow_conclusion: success
@@ -118,7 +120,7 @@ jobs:
- name: Download all artifacts - name: Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-desktop.yml workflow: build-desktop.yml
workflow_conclusion: success workflow_conclusion: success
@@ -131,6 +133,15 @@ jobs:
working-directory: apps/desktop/artifacts working-directory: apps/desktop/artifacts
run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive
- name: Set staged rollout percentage
env:
RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }}
ROLLOUT_PCT: ${{ github.event.inputs.rollout_percentage }}
run: |
echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}.yml
echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-linux.yml
echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-mac.yml
- name: Publish artifacts to S3 - name: Publish artifacts to S3
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env: env:
@@ -145,8 +156,23 @@ jobs:
--recursive \ --recursive \
--quiet --quiet
- name: Create release - name: Publish artifacts to R2
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5 if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }}
AWS_DEFAULT_REGION: 'us-east-1'
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }}
CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }}
working-directory: apps/desktop/artifacts
run: |
aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \
--recursive \
--quiet \
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
- name: Create Release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09
if: ${{ steps.release-channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }} if: ${{ steps.release-channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }}
env: env:
PKG_VERSION: ${{ steps.version.outputs.version }} PKG_VERSION: ${{ steps.version.outputs.version }}
@@ -217,17 +243,10 @@ jobs:
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
env: uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
KEYVAULT: bitwarden-prod-kv with:
SECRETS: | keyvault: "bitwarden-prod-kv"
snapcraft-store-token secrets: "snapcraft-store-token"
run: |
for i in ${SECRETS//,/ }
do
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
echo "::add-mask::$VALUE"
echo "::set-output name=$i::$VALUE"
done
- name: Install Snap - name: Install Snap
uses: samuelmeuli/action-snapcraft@10d7d0a84d9d86098b19f872257df314b0bd8e2d # v1.2.0 uses: samuelmeuli/action-snapcraft@10d7d0a84d9d86098b19f872257df314b0bd8e2d # v1.2.0
@@ -240,7 +259,7 @@ jobs:
- name: Download Snap artifact - name: Download Snap artifact
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-desktop.yml workflow: build-desktop.yml
workflow_conclusion: success workflow_conclusion: success
@@ -250,7 +269,7 @@ jobs:
- name: Download Snap artifact - name: Download Snap artifact
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-desktop.yml workflow: build-desktop.yml
workflow_conclusion: success workflow_conclusion: success
@@ -274,7 +293,7 @@ jobs:
_PKG_VERSION: ${{ needs.setup.outputs.release-version }} _PKG_VERSION: ${{ needs.setup.outputs.release-version }}
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Print Environment - name: Print Environment
run: | run: |
@@ -288,17 +307,10 @@ jobs:
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
env: uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
KEYVAULT: bitwarden-prod-kv with:
SECRETS: | keyvault: "bitwarden-prod-kv"
cli-choco-api-key secrets: "cli-choco-api-key"
run: |
for i in ${SECRETS//,/ }
do
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
echo "::add-mask::$VALUE"
echo "::set-output name=$i::$VALUE"
done
- name: Setup Chocolatey - name: Setup Chocolatey
shell: pwsh shell: pwsh
@@ -313,7 +325,7 @@ jobs:
- name: Download choco artifact - name: Download choco artifact
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-desktop.yml workflow: build-desktop.yml
workflow_conclusion: success workflow_conclusion: success
@@ -323,7 +335,7 @@ jobs:
- name: Download choco artifact - name: Download choco artifact
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-desktop.yml workflow: build-desktop.yml
workflow_conclusion: success workflow_conclusion: success

View File

@@ -154,7 +154,7 @@ jobs:
- name: Download latest cloud asset - name: Download latest cloud asset
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-web.yml workflow: build-web.yml
path: apps/web path: apps/web
@@ -164,7 +164,7 @@ jobs:
- name: Download latest cloud asset - name: Download latest cloud asset
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-web.yml workflow: build-web.yml
path: apps/web path: apps/web
@@ -240,7 +240,7 @@ jobs:
- name: Download latest build artifacts - name: Download latest build artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-web.yml workflow: build-web.yml
path: apps/web/artifacts path: apps/web/artifacts
@@ -251,7 +251,7 @@ jobs:
- name: Download latest build artifacts - name: Download latest build artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-web.yml workflow: build-web.yml
path: apps/web/artifacts path: apps/web/artifacts

View File

@@ -0,0 +1,123 @@
---
name: Staged Rollout Desktop
on:
workflow_dispatch:
inputs:
rollout_percentage:
description: 'Staged Rollout Percentage'
required: true
default: '10'
type: string
defaults:
run:
shell: bash
jobs:
rollout:
name: Update Rollout Percentage
runs-on: ubuntu-22.04
outputs:
release-version: ${{ steps.version.outputs.version }}
release-channel: ${{ steps.release-channel.outputs.channel }}
steps:
- name: Login to Azure
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
with:
keyvault: "bitwarden-prod-kv"
secrets: "aws-electron-access-id,
aws-electron-access-key,
aws-electron-bucket-name,
r2-electron-access-id,
r2-electron-access-key,
r2-electron-bucket-name,
cf-prod-account"
- name: Download channel update info files from S3
env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }}
AWS_DEFAULT_REGION: 'us-west-2'
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }}
run: |
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest.yml . \
--quiet
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-linux.yml . \
--quiet
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-mac.yml . \
--quiet
- name: Download channel update info files from R2
env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }}
AWS_DEFAULT_REGION: 'us-east-1'
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }}
CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }}
run: |
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest.yml . \
--quiet \
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-linux.yml . \
--quiet \
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-mac.yml . \
--quiet \
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
- name: Check new rollout percentage
env:
NEW_PCT: ${{ github.event.inputs.rollout_percentage }}
run: |
CURRENT_PCT=$(sed -r -n "s/stagingPercentage:\s([0-9]+)/\1/p" latest.yml)
echo "Current percentage: ${CURRENT_PCT}"
echo "New percentage: ${NEW_PCT}"
echo
if [ "$NEW_PCT" -le "$CURRENT_PCT" ]; then
echo "New percentage (${NEW_PCT}) must be higher than current percentage (${CURRENT_PCT})!"
echo
echo "If you want to pull a staged release because it hasnt gone well, you must increment the version \
number higher than your broken release. Because some of your users will be on the broken 1.0.1, \
releasing a new 1.0.1 would result in them staying on a broken version.”
exit 1
fi
- name: Set staged rollout percentage
env:
ROLLOUT_PCT: ${{ github.event.inputs.rollout_percentage }}
run: |
sed -i -r "/stagingPercentage/s/[0-9]+/${ROLLOUT_PCT}/" latest.yml
sed -i -r "/stagingPercentage/s/[0-9]+/${ROLLOUT_PCT}/" latest-linux.yml
sed -i -r "/stagingPercentage/s/[0-9]+/${ROLLOUT_PCT}/" latest-mac.yml
- name: Publish channel update info files to S3
env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }}
AWS_DEFAULT_REGION: 'us-west-2'
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }}
run: |
aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \
--include "latest*.yml" \
--acl "public-read" \
--quiet
- name: Publish channel update info files to R2
env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }}
AWS_DEFAULT_REGION: 'us-east-1'
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }}
CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }}
run: |
aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \
--include "latest*.yml" \
--quiet \
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com

View File

@@ -12,7 +12,7 @@ defaults:
jobs: jobs:
setup: setup:
name: "Setup" name: "Setup"
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
outputs: outputs:
version_number: ${{ steps.version.outputs.new-version }} version_number: ${{ steps.version.outputs.new-version }}
if: contains(github.event.release.tag, 'desktop') if: contains(github.event.release.tag, 'desktop')
@@ -20,22 +20,23 @@ jobs:
- name: Checkout Branch - name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Get version to bump - name: Calculate bumped version
id: version id: version
env: env:
RELEASE_TAG: ${{ github.event.release.tag }} RELEASE_TAG: ${{ github.event.release.tag_name }}
run: | run: |
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/')
CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/')
echo "Current Patch: $CURR_PATCH"
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/desktop-v([0-9]{4}\.[0-9]\.)([0-9])/\1/') NEW_PATCH=$((CURR_PATCH++))
CURR_VER=$(echo $RELEASE_TAG | sed -r 's/desktop-v([0-9]{4}\.[0-9]\.)([0-9])/\2/') NEW_VER=$CURR_MAJOR.$NEW_PATCH
echo $CURR_VER echo "New Version: $NEW_VER"
((CURR_VER++))
NEW_VER=$CURR_MAJOR$CURR_VER
echo "::set-output name=new-version::$NEW_VER" echo "::set-output name=new-version::$NEW_VER"
trigger_version_bump: trigger_version_bump:
name: "Trigger desktop version bump workflow" name: "Trigger desktop version bump workflow"
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
needs: needs:
- setup - setup
steps: steps:
@@ -46,13 +47,10 @@ jobs:
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
env: uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
KEYVAULT: bitwarden-prod-kv with:
SECRET: "github-pat-bitwarden-devops-bot-repo-scope" keyvault: "bitwarden-prod-kv"
run: | secrets: "github-pat-bitwarden-devops-bot-repo-scope"
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $SECRET --query value --output tsv)
echo "::add-mask::$VALUE"
echo "::set-output name=$SECRET::$VALUE"
- name: Call GitHub API to trigger workflow bump - name: Call GitHub API to trigger workflow bump
env: env:

View File

@@ -64,7 +64,7 @@ export default class CommandsBackground {
} }
private async generatePasswordToClipboard() { private async generatePasswordToClipboard() {
const options = (await this.passwordGenerationService.getOptions())[0]; const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
const password = await this.passwordGenerationService.generatePassword(options); const password = await this.passwordGenerationService.generatePassword(options);
this.platformUtilsService.copyToClipboard(password, { window: window }); this.platformUtilsService.copyToClipboard(password, { window: window });
this.passwordGenerationService.addHistory(password); this.passwordGenerationService.addHistory(password);

View File

@@ -66,7 +66,7 @@ export default class ContextMenusBackground {
} }
private async generatePasswordToClipboard() { private async generatePasswordToClipboard() {
const options = (await this.passwordGenerationService.getOptions())[0]; const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
const password = await this.passwordGenerationService.generatePassword(options); const password = await this.passwordGenerationService.generatePassword(options);
this.platformUtilsService.copyToClipboard(password, { window: window }); this.platformUtilsService.copyToClipboard(password, { window: window });
this.passwordGenerationService.addHistory(password); this.passwordGenerationService.addHistory(password);

View File

@@ -247,7 +247,8 @@ export default class MainBackground {
return promise.then((result) => result.response === "unlocked"); return promise.then((result) => result.response === "unlocked");
} }
} },
window
); );
this.i18nService = new I18nService(BrowserApi.getUILanguage(window)); this.i18nService = new I18nService(BrowserApi.getUILanguage(window));
this.encryptService = new EncryptService(this.cryptoFunctionService, this.logService, true); this.encryptService = new EncryptService(this.cryptoFunctionService, this.logService, true);

View File

@@ -446,6 +446,8 @@ export default class NotificationBackground {
} }
private async allowPersonalOwnership(): Promise<boolean> { private async allowPersonalOwnership(): Promise<boolean> {
return !(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)); return !(await firstValueFrom(
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
));
} }
} }

View File

@@ -0,0 +1,37 @@
import { AutofillService as AbstractAutoFillService } from "../../services/abstractions/autofill.service";
import AutofillService from "../../services/autofill.service";
import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory";
import { EventServiceInitOptions, eventServiceFactory } from "./event-service.factory";
import { CachedServices, factory, FactoryOptions } from "./factory-options";
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
import { totpServiceFactory, TotpServiceInitOptions } from "./totp-service.factory";
type AutoFillServiceOptions = FactoryOptions;
export type AutoFillServiceInitOptions = AutoFillServiceOptions &
CipherServiceInitOptions &
StateServiceInitOptions &
TotpServiceInitOptions &
EventServiceInitOptions &
LogServiceInitOptions;
export function autofillServiceFactory(
cache: { autofillService?: AbstractAutoFillService } & CachedServices,
opts: AutoFillServiceInitOptions
): Promise<AbstractAutoFillService> {
return factory(
cache,
"autofillService",
opts,
async () =>
new AutofillService(
await cipherServiceFactory(cache, opts),
await stateServiceFactory(cache, opts),
await totpServiceFactory(cache, opts),
await eventServiceFactory(cache, opts),
await logServiceFactory(cache, opts)
)
);
}

View File

@@ -44,7 +44,7 @@ export function cipherServiceFactory(
await apiServiceFactory(cache, opts), await apiServiceFactory(cache, opts),
await fileUploadServiceFactory(cache, opts), await fileUploadServiceFactory(cache, opts),
await i18nServiceFactory(cache, opts), await i18nServiceFactory(cache, opts),
opts.cipherServiceOptions.searchServiceFactory === undefined opts.cipherServiceOptions?.searchServiceFactory === undefined
? () => cache.searchService ? () => cache.searchService
: opts.cipherServiceOptions.searchServiceFactory, : opts.cipherServiceOptions.searchServiceFactory,
await logServiceFactory(cache, opts), await logServiceFactory(cache, opts),

View File

@@ -0,0 +1,40 @@
import { EventService as AbstractEventService } from "@bitwarden/common/abstractions/event.service";
import { EventService } from "@bitwarden/common/services/event.service";
import { apiServiceFactory, ApiServiceInitOptions } from "./api-service.factory";
import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory";
import { FactoryOptions, CachedServices, factory } from "./factory-options";
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
import {
organizationServiceFactory,
OrganizationServiceInitOptions,
} from "./organization-service.factory";
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
type EventServiceOptions = FactoryOptions;
export type EventServiceInitOptions = EventServiceOptions &
ApiServiceInitOptions &
CipherServiceInitOptions &
StateServiceInitOptions &
LogServiceInitOptions &
OrganizationServiceInitOptions;
export function eventServiceFactory(
cache: { eventService?: AbstractEventService } & CachedServices,
opts: EventServiceInitOptions
): Promise<AbstractEventService> {
return factory(
cache,
"eventService",
opts,
async () =>
new EventService(
await apiServiceFactory(cache, opts),
await cipherServiceFactory(cache, opts),
await stateServiceFactory(cache, opts),
await logServiceFactory(cache, opts),
await organizationServiceFactory(cache, opts)
)
);
}

View File

@@ -28,7 +28,8 @@ export function platformUtilsServiceFactory(
new BrowserPlatformUtilsService( new BrowserPlatformUtilsService(
await messagingServiceFactory(cache, opts), await messagingServiceFactory(cache, opts),
opts.platformUtilsServiceOptions.clipboardWriteCallback, opts.platformUtilsServiceOptions.clipboardWriteCallback,
opts.platformUtilsServiceOptions.biometricCallback opts.platformUtilsServiceOptions.biometricCallback,
opts.platformUtilsServiceOptions.win
) )
); );
} }

View File

@@ -0,0 +1,31 @@
import { TotpService as AbstractTotpService } from "@bitwarden/common/abstractions/totp.service";
import { TotpService } from "@bitwarden/common/services/totp.service";
import {
cryptoFunctionServiceFactory,
CryptoFunctionServiceInitOptions,
} from "./crypto-function-service.factory";
import { CachedServices, factory, FactoryOptions } from "./factory-options";
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
type TotpServiceOptions = FactoryOptions;
export type TotpServiceInitOptions = TotpServiceOptions &
CryptoFunctionServiceInitOptions &
LogServiceInitOptions;
export function totpServiceFactory(
cache: { totpService?: AbstractTotpService } & CachedServices,
opts: TotpServiceInitOptions
): Promise<AbstractTotpService> {
return factory(
cache,
"totpService",
opts,
async () =>
new TotpService(
await cryptoFunctionServiceFactory(cache, opts),
await logServiceFactory(cache, opts)
)
);
}

View File

@@ -1,27 +1,15 @@
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus";
import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { GlobalState } from "@bitwarden/common/models/domain/globalState"; import { GlobalState } from "@bitwarden/common/models/domain/globalState";
import { AuthService } from "@bitwarden/common/services/auth.service";
import { CipherService } from "@bitwarden/common/services/cipher.service";
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
import { EncryptService } from "@bitwarden/common/services/encrypt.service";
import { NoopEventService } from "@bitwarden/common/services/noopEvent.service";
import { SearchService } from "@bitwarden/common/services/search.service";
import { SettingsService } from "@bitwarden/common/services/settings.service";
import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service";
import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service";
import { authServiceFactory } from "../background/service_factories/auth-service.factory";
import { autofillServiceFactory } from "../background/service_factories/autofill-service.factory";
import { CachedServices } from "../background/service_factories/factory-options";
import { logServiceFactory } from "../background/service_factories/log-service.factory";
import { BrowserApi } from "../browser/browserApi";
import { AutoFillActiveTabCommand } from "../commands/autoFillActiveTabCommand"; import { AutoFillActiveTabCommand } from "../commands/autoFillActiveTabCommand";
import { Account } from "../models/account"; import { Account } from "../models/account";
import { StateService as AbstractStateService } from "../services/abstractions/state.service";
import AutofillService from "../services/autofill.service";
import { BrowserCryptoService } from "../services/browserCrypto.service";
import BrowserLocalStorageService from "../services/browserLocalStorage.service";
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
import I18nService from "../services/i18n.service";
import { KeyGenerationService } from "../services/keyGeneration.service";
import { LocalBackedSessionStorageService } from "../services/localBackedSessionStorage.service";
import { StateService } from "../services/state.service";
export const onCommandListener = async (command: string, tab: chrome.tabs.Tab) => { export const onCommandListener = async (command: string, tab: chrome.tabs.Tab) => {
switch (command) { switch (command) {
@@ -32,100 +20,44 @@ export const onCommandListener = async (command: string, tab: chrome.tabs.Tab) =
}; };
const doAutoFillLogin = async (tab: chrome.tabs.Tab): Promise<void> => { const doAutoFillLogin = async (tab: chrome.tabs.Tab): Promise<void> => {
const logService = new ConsoleLogService(false); const cachedServices: CachedServices = {};
const opts = {
const cryptoFunctionService = new WebCryptoFunctionService(self); cryptoFunctionServiceOptions: {
win: self,
const storageService = new BrowserLocalStorageService(); },
encryptServiceOptions: {
const secureStorageService = new BrowserLocalStorageService(); logMacFailures: true,
},
const memoryStorageService = new LocalBackedSessionStorageService( logServiceOptions: {
new EncryptService(cryptoFunctionService, logService, false), isDev: false,
new KeyGenerationService(cryptoFunctionService) },
); platformUtilsServiceOptions: {
clipboardWriteCallback: () => Promise.resolve(),
const stateFactory = new StateFactory(GlobalState, Account); biometricCallback: () => Promise.resolve(false),
win: self,
const stateMigrationService = new StateMigrationService( },
storageService, stateServiceOptions: {
secureStorageService, stateFactory: new StateFactory(GlobalState, Account),
stateFactory },
); stateMigrationServiceOptions: {
stateFactory: new StateFactory(GlobalState, Account),
const stateService: AbstractStateService = new StateService( },
storageService, apiServiceOptions: {
secureStorageService, logoutCallback: () => Promise.resolve(),
memoryStorageService, // AbstractStorageService },
logService, keyConnectorServiceOptions: {
stateMigrationService, logoutCallback: () => Promise.resolve(),
stateFactory },
); i18nServiceOptions: {
systemLanguage: BrowserApi.getUILanguage(self),
await stateService.init(); },
cipherServiceOptions: {
const platformUtils = new BrowserPlatformUtilsService( searchServiceFactory: null as () => SearchService, // No dependence on search service
null, // MessagingService },
null, // clipboardWriteCallback };
null // biometricCallback const logService = await logServiceFactory(cachedServices, opts);
); const authService = await authServiceFactory(cachedServices, opts);
const autofillService = await autofillServiceFactory(cachedServices, opts);
const cryptoService = new BrowserCryptoService(
cryptoFunctionService,
null, // AbstractEncryptService
platformUtils,
logService,
stateService
);
const settingsService = new SettingsService(stateService);
const i18nService = new I18nService(chrome.i18n.getUILanguage());
await i18nService.init();
// Don't love this pt.1
let searchService: SearchService = null;
const cipherService = new CipherService(
cryptoService,
settingsService,
null, // ApiService
null, // FileUploadService,
i18nService,
() => searchService, // Don't love this pt.2
logService,
stateService
);
// Don't love this pt.3
searchService = new SearchService(cipherService, logService, i18nService);
// TODO: Remove this before we encourage anyone to start using this
const eventService = new NoopEventService();
const autofillService = new AutofillService(
cipherService,
stateService,
null, // TotpService
eventService,
logService
);
const authService = new AuthService(
cryptoService, // CryptoService
null, // ApiService
null, // TokenService
null, // AppIdService
platformUtils,
null, // MessagingService
logService,
null, // KeyConnectorService
null, // EnvironmentService
stateService,
null, // TwoFactorService
i18nService
);
const authStatus = await authService.getAuthStatus(); const authStatus = await authService.getAuthStatus();
if (authStatus < AuthenticationStatus.Unlocked) { if (authStatus < AuthenticationStatus.Unlocked) {

View File

@@ -12,7 +12,6 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service"; import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus";
@@ -28,8 +27,6 @@ export class LockComponent extends BaseLockComponent {
biometricError: string; biometricError: string;
pendingBiometric = false; pendingBiometric = false;
authenicatedUrl = "/tabs/current";
unAuthenicatedUrl = "/update-temp-password";
constructor( constructor(
router: Router, router: Router,
@@ -45,8 +42,7 @@ export class LockComponent extends BaseLockComponent {
logService: LogService, logService: LogService,
keyConnectorService: KeyConnectorService, keyConnectorService: KeyConnectorService,
ngZone: NgZone, ngZone: NgZone,
private authService: AuthService, private authService: AuthService
private syncService: SyncService
) { ) {
super( super(
router, router,
@@ -63,17 +59,12 @@ export class LockComponent extends BaseLockComponent {
keyConnectorService, keyConnectorService,
ngZone ngZone
); );
this.successRoute = "/tabs/current";
this.isInitialLockScreen = (window as any).previousPopupUrl == null; this.isInitialLockScreen = (window as any).previousPopupUrl == null;
} }
async ngOnInit() { async ngOnInit() {
await super.ngOnInit(); await super.ngOnInit();
await this.syncService.fullSync(true);
const forcePasswordReset = await this.stateService.getForcePasswordReset();
this.successRoute = forcePasswordReset === true ? this.unAuthenicatedUrl : this.authenicatedUrl;
const disableAutoBiometricsPrompt = const disableAutoBiometricsPrompt =
(await this.stateService.getDisableAutoBiometricsPrompt()) ?? true; (await this.stateService.getDisableAutoBiometricsPrompt()) ?? true;

View File

@@ -172,15 +172,11 @@ export default class AutofillService implements AutofillServiceInterface {
} else { } else {
cipher = await this.cipherService.getLastUsedForUrl(tab.url, true); cipher = await this.cipherService.getLastUsedForUrl(tab.url, true);
} }
}
if (cipher == null) { if (cipher == null || cipher.reprompt !== CipherRepromptType.None) {
return null; return null;
} }
}
if (cipher.reprompt !== CipherRepromptType.None) {
return;
}
const totpCode = await this.doAutoFill({ const totpCode = await this.doAutoFill({
tab: tab, tab: tab,

View File

@@ -16,7 +16,7 @@ describe("Browser Utils Service", () => {
let browserPlatformUtilsService: BrowserPlatformUtilsService; let browserPlatformUtilsService: BrowserPlatformUtilsService;
beforeEach(() => { beforeEach(() => {
(window as any).matchMedia = jest.fn().mockReturnValueOnce({}); (window as any).matchMedia = jest.fn().mockReturnValueOnce({});
browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null); browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null, self);
}); });
afterEach(() => { afterEach(() => {

View File

@@ -19,7 +19,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
constructor( constructor(
private messagingService: MessagingService, private messagingService: MessagingService,
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void, private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
private biometricCallback: () => Promise<boolean> private biometricCallback: () => Promise<boolean>,
private win: Window & typeof globalThis
) {} ) {}
getDevice(): DeviceType { getDevice(): DeviceType {
@@ -33,8 +34,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
) { ) {
this.deviceCache = DeviceType.FirefoxExtension; this.deviceCache = DeviceType.FirefoxExtension;
} else if ( } else if (
(self.opr && self.opr.addons) || (!!this.win.opr && !!opr.addons) ||
self.opera || !!this.win.opera ||
navigator.userAgent.indexOf(" OPR/") >= 0 navigator.userAgent.indexOf(" OPR/") >= 0
) { ) {
this.deviceCache = DeviceType.OperaExtension; this.deviceCache = DeviceType.OperaExtension;
@@ -42,7 +43,7 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
this.deviceCache = DeviceType.EdgeExtension; this.deviceCache = DeviceType.EdgeExtension;
} else if (navigator.userAgent.indexOf(" Vivaldi/") !== -1) { } else if (navigator.userAgent.indexOf(" Vivaldi/") !== -1) {
this.deviceCache = DeviceType.VivaldiExtension; this.deviceCache = DeviceType.VivaldiExtension;
} else if (window.chrome && navigator.userAgent.indexOf(" Chrome/") !== -1) { } else if (this.win.chrome && navigator.userAgent.indexOf(" Chrome/") !== -1) {
this.deviceCache = DeviceType.ChromeExtension; this.deviceCache = DeviceType.ChromeExtension;
} else if (navigator.userAgent.indexOf(" Safari/") !== -1) { } else if (navigator.userAgent.indexOf(" Safari/") !== -1) {
this.deviceCache = DeviceType.SafariExtension; this.deviceCache = DeviceType.SafariExtension;
@@ -178,8 +179,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
} }
copyToClipboard(text: string, options?: any): void { copyToClipboard(text: string, options?: any): void {
let win = window; let win = this.win;
let doc = window.document; let doc = this.win.document;
if (options && (options.window || options.win)) { if (options && (options.window || options.win)) {
win = options.window || options.win; win = options.window || options.win;
doc = win.document; doc = win.document;
@@ -238,8 +239,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
} }
async readFromClipboard(options?: any): Promise<string> { async readFromClipboard(options?: any): Promise<string> {
let win = window; let win = this.win;
let doc = window.document; let doc = this.win.document;
if (options && (options.window || options.win)) { if (options && (options.window || options.win)) {
win = options.window || options.win; win = options.window || options.win;
doc = win.document; doc = win.document;
@@ -335,7 +336,7 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
} }
sidebarViewName(): string { sidebarViewName(): string {
if (window.chrome.sidebarAction && this.isFirefox()) { if (this.win.chrome.sidebarAction && this.isFirefox()) {
return "sidebar"; return "sidebar";
} else if (this.isOpera() && typeof opr !== "undefined" && opr.sidebarAction) { } else if (this.isOpera() && typeof opr !== "undefined" && opr.sidebarAction) {
return "sidebar_panel"; return "sidebar_panel";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { Utils } from "@bitwarden/common/misc/utils"; import { Utils } from "@bitwarden/common/misc/utils";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";

View File

@@ -1,5 +1,6 @@
import * as program from "commander"; import * as program from "commander";
import * as inquirer from "inquirer"; import * as inquirer from "inquirer";
import { firstValueFrom } from "rxjs";
import { ExportFormat, ExportService } from "@bitwarden/common/abstractions/export.service"; import { ExportFormat, ExportService } from "@bitwarden/common/abstractions/export.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
@@ -15,7 +16,9 @@ export class ExportCommand {
async run(options: program.OptionValues): Promise<Response> { async run(options: program.OptionValues): Promise<Response> {
if ( if (
options.organizationid == null && options.organizationid == null &&
(await this.policyService.policyAppliesToUser(PolicyType.DisablePersonalVaultExport)) (await firstValueFrom(
this.policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport)
))
) { ) {
return Response.badRequest( return Response.badRequest(
"One or more organization policies prevents you from exporting your personal vault." "One or more organization policies prevents you from exporting your personal vault."

View File

@@ -13,7 +13,6 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service"; import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
@@ -25,8 +24,6 @@ const BroadcasterSubscriptionId = "LockComponent";
}) })
export class LockComponent extends BaseLockComponent { export class LockComponent extends BaseLockComponent {
private deferFocus: boolean = null; private deferFocus: boolean = null;
authenicatedUrl = "vault";
unAuthenicatedUrl = "update-temp-password";
constructor( constructor(
router: Router, router: Router,
@@ -43,8 +40,7 @@ export class LockComponent extends BaseLockComponent {
private broadcasterService: BroadcasterService, private broadcasterService: BroadcasterService,
ngZone: NgZone, ngZone: NgZone,
logService: LogService, logService: LogService,
keyConnectorService: KeyConnectorService, keyConnectorService: KeyConnectorService
private syncService: SyncService
) { ) {
super( super(
router, router,
@@ -67,11 +63,6 @@ export class LockComponent extends BaseLockComponent {
await super.ngOnInit(); await super.ngOnInit();
const autoPromptBiometric = !(await this.stateService.getNoAutoPromptBiometrics()); const autoPromptBiometric = !(await this.stateService.getNoAutoPromptBiometrics());
await this.syncService.fullSync(true);
const forcePasswordReset = await this.stateService.getForcePasswordReset();
this.successRoute = forcePasswordReset === true ? this.unAuthenicatedUrl : this.authenicatedUrl;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil // eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.route.queryParams.subscribe((params) => { this.route.queryParams.subscribe((params) => {
if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) { if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) {

View File

@@ -1,6 +1,7 @@
import { NO_ERRORS_SCHEMA } from "@angular/core"; import { NO_ERRORS_SCHEMA } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing"; import { ComponentFixture, TestBed } from "@angular/core/testing";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
// eslint-disable-next-line no-restricted-imports
import { Substitute } from "@fluffy-spoon/substitute"; import { Substitute } from "@fluffy-spoon/substitute";
import { mock, MockProxy } from "jest-mock-extended"; import { mock, MockProxy } from "jest-mock-extended";

View File

@@ -182,12 +182,25 @@ export class NativeMessageHandlerService {
this.ddgSharedSecret = SymmetricCryptoKey.fromJSON({ keyB64: storedKey }); this.ddgSharedSecret = SymmetricCryptoKey.fromJSON({ keyB64: storedKey });
} }
return JSON.parse( try {
await this.cryptoService.decryptToUtf8( let decryptedResult = await this.cryptoService.decryptToUtf8(
message.encryptedCommand as EncString, message.encryptedCommand as EncString,
this.ddgSharedSecret this.ddgSharedSecret
)
); );
decryptedResult = this.trimNullCharsFromMessage(decryptedResult);
return JSON.parse(decryptedResult);
} catch {
this.sendResponse({
messageId: message.messageId,
version: NativeMessagingVersion.Latest,
payload: {
error: "cannot-decrypt",
},
});
return;
}
} }
private async sendEncryptedResponse( private async sendEncryptedResponse(
@@ -218,4 +231,23 @@ export class NativeMessageHandlerService {
private sendResponse(response: EncryptedMessageResponse | UnencryptedMessageResponse) { private sendResponse(response: EncryptedMessageResponse | UnencryptedMessageResponse) {
ipcRenderer.send("nativeMessagingReply", response); ipcRenderer.send("nativeMessagingReply", response);
} }
// Trim all null bytes padded at the end of messages. This happens with C encryption libraries.
private trimNullCharsFromMessage(message: string): string {
const charNull = 0;
const charRightCurlyBrace = 125;
const charRightBracket = 93;
for (let i = message.length - 1; i >= 0; i--) {
if (message.charCodeAt(i) === charNull) {
message = message.substring(0, message.length - 1);
} else if (
message.charCodeAt(i) === charRightCurlyBrace ||
message.charCodeAt(i) === charRightBracket
) {
break;
}
}
return message;
}
} }

View File

@@ -14,7 +14,8 @@
"**/organizations/policies/*", "**/organizations/policies/*",
"@bitwarden/web-vault/*", "@bitwarden/web-vault/*",
"src/**/*" "src/**/*"
] ],
"paths": ["@fluffy-spoon/substitute"]
} }
] ]
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@bitwarden/web-vault", "name": "@bitwarden/web-vault",
"version": "2022.9.2", "version": "2022.10.0",
"scripts": { "scripts": {
"build:oss": "webpack", "build:oss": "webpack",
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",

View File

@@ -37,9 +37,11 @@ export class LoginWithDeviceComponent
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>; onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLogin: () => Promise<any>; onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>; onSuccessfulLoginNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
protected twoFactorRoute = "2fa"; protected twoFactorRoute = "2fa";
protected successRoute = "vault"; protected successRoute = "vault";
protected forcePasswordResetRoute = "update-temp-password";
private authRequestKeyPair: [publicKey: ArrayBuffer, privateKey: ArrayBuffer]; private authRequestKeyPair: [publicKey: ArrayBuffer, privateKey: ArrayBuffer];
constructor( constructor(
@@ -119,7 +121,21 @@ export class LoginWithDeviceComponent
} }
const credentials = await this.buildLoginCredntials(requestId, response); const credentials = await this.buildLoginCredntials(requestId, response);
await this.authService.logIn(credentials); const loginResponse = await this.authService.logIn(credentials);
if (loginResponse.requiresTwoFactor) {
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
this.onSuccessfulLoginTwoFactorNavigate();
} else {
this.router.navigate([this.twoFactorRoute]);
}
} else if (loginResponse.forcePasswordReset) {
if (this.onSuccessfulLoginForceResetNavigate != null) {
this.onSuccessfulLoginForceResetNavigate();
} else {
this.router.navigate([this.forcePasswordResetRoute]);
}
} else {
if (this.onSuccessfulLogin != null) { if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin(); this.onSuccessfulLogin();
} }
@@ -128,6 +144,7 @@ export class LoginWithDeviceComponent
} else { } else {
this.router.navigate([this.successRoute]); this.router.navigate([this.successRoute]);
} }
}
} catch (error) { } catch (error) {
this.logService.error(error); this.logService.error(error);
} }

View File

@@ -1,6 +1,7 @@
import { Component, NgZone } from "@angular/core"; import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms"; import { FormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/components/login.component"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/components/login.component";
@@ -29,13 +30,14 @@ import { RouterService, StateService } from "../../core";
selector: "app-login", selector: "app-login",
templateUrl: "login.component.html", templateUrl: "login.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class LoginComponent extends BaseLoginComponent implements OnInit, OnDestroy {
export class LoginComponent extends BaseLoginComponent {
showResetPasswordAutoEnrollWarning = false; showResetPasswordAutoEnrollWarning = false;
enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions; enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions;
policies: ListResponse<PolicyResponse>; policies: ListResponse<PolicyResponse>;
showPasswordless = false; showPasswordless = false;
private destroy$ = new Subject<void>();
constructor( constructor(
authService: AuthService, authService: AuthService,
router: Router, router: Router,
@@ -128,14 +130,23 @@ export class LoginComponent extends BaseLoginComponent {
this.showResetPasswordAutoEnrollWarning = this.showResetPasswordAutoEnrollWarning =
resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled; resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled;
this.enforcedPasswordPolicyOptions = this.policyService
await this.policyService.getMasterPasswordPolicyOptions(policyList); .masterPasswordPolicyOptions$(policyList)
.pipe(takeUntil(this.destroy$))
.subscribe((enforcedPasswordPolicyOptions) => {
this.enforcedPasswordPolicyOptions = enforcedPasswordPolicyOptions;
});
} }
} }
} }
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
async goAfterLogIn() { async goAfterLogIn() {
const masterPassword = this.formGroup.get("masterPassword")?.value; const masterPassword = this.formGroup.value.masterPassword;
// Check master password against policy // Check master password against policy
if (this.enforcedPasswordPolicyOptions != null) { if (this.enforcedPasswordPolicyOptions != null) {
@@ -170,7 +181,7 @@ export class LoginComponent extends BaseLoginComponent {
} }
async submit() { async submit() {
const rememberEmail = this.formGroup.get("rememberEmail")?.value; const rememberEmail = this.formGroup.value.rememberEmail;
await this.stateService.setRememberEmail(rememberEmail); await this.stateService.setRememberEmail(rememberEmail);
if (!rememberEmail) { if (!rememberEmail) {
@@ -192,7 +203,7 @@ export class LoginComponent extends BaseLoginComponent {
} }
private getPasswordStrengthUserInput() { private getPasswordStrengthUserInput() {
const email = this.formGroup.get("email")?.value; const email = this.formGroup.value.email;
let userInput: string[] = []; let userInput: string[] = [];
const atPosition = email.indexOf("@"); const atPosition = email.indexOf("@");
if (atPosition > -1) { if (atPosition > -1) {

View File

@@ -73,7 +73,7 @@ export class RegisterFormComponent extends BaseRegisterComponent {
this.enforcedPolicyOptions != null && this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword( !this.policyService.evaluateMasterPassword(
this.passwordStrengthResult.score, this.passwordStrengthResult.score,
this.formGroup.get("masterPassword")?.value, this.formGroup.value.masterPassword,
this.enforcedPolicyOptions this.enforcedPolicyOptions
) )
) { ) {

View File

@@ -1,6 +1,7 @@
import { Component } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { UntypedFormBuilder } from "@angular/forms"; import { UntypedFormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component"; import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component";
@@ -27,14 +28,14 @@ import { RouterService } from "../core";
selector: "app-register", selector: "app-register",
templateUrl: "register.component.html", templateUrl: "register.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class RegisterComponent extends BaseRegisterComponent implements OnInit, OnDestroy {
export class RegisterComponent extends BaseRegisterComponent {
email = ""; email = "";
showCreateOrgMessage = false; showCreateOrgMessage = false;
layout = ""; layout = "";
enforcedPolicyOptions: MasterPasswordPolicyOptions; enforcedPolicyOptions: MasterPasswordPolicyOptions;
private policies: Policy[]; private policies: Policy[];
private destroy$ = new Subject<void>();
constructor( constructor(
formValidationErrorService: FormValidationErrorsService, formValidationErrorService: FormValidationErrorsService,
@@ -130,11 +131,19 @@ export class RegisterComponent extends BaseRegisterComponent {
} }
if (this.policies != null) { if (this.policies != null) {
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions( this.policyService
this.policies .masterPasswordPolicyOptions$(this.policies)
); .pipe(takeUntil(this.destroy$))
.subscribe((enforcedPasswordPolicyOptions) => {
this.enforcedPolicyOptions = enforcedPasswordPolicyOptions;
});
} }
await super.ngOnInit(); await super.ngOnInit();
} }
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
} }

View File

@@ -57,8 +57,8 @@ export class BillingComponent extends OrganizationPlansComponent {
async ngOnInit() { async ngOnInit() {
const additionalSeats = this.product == ProductType.Families ? 0 : 1; const additionalSeats = this.product == ProductType.Families ? 0 : 1;
this.formGroup.patchValue({ this.formGroup.patchValue({
name: this.orgInfoForm.get("name")?.value, name: this.orgInfoForm.value.name,
billingEmail: this.orgInfoForm.get("email")?.value, billingEmail: this.orgInfoForm.value.email,
additionalSeats: additionalSeats, additionalSeats: additionalSeats,
plan: this.plan, plan: this.plan,
product: this.product, product: this.product,

View File

@@ -5,8 +5,9 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testin
import { FormBuilder, UntypedFormBuilder } from "@angular/forms"; import { FormBuilder, UntypedFormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { RouterTestingModule } from "@angular/router/testing"; import { RouterTestingModule } from "@angular/router/testing";
// eslint-disable-next-line no-restricted-imports
import { Substitute } from "@fluffy-spoon/substitute"; import { Substitute } from "@fluffy-spoon/substitute";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject, of } from "rxjs";
import { I18nPipe } from "@bitwarden/angular/pipes/i18n.pipe"; import { I18nPipe } from "@bitwarden/angular/pipes/i18n.pipe";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -46,7 +47,7 @@ describe("TrialInitiationComponent", () => {
}; };
policyServiceMock = { policyServiceMock = {
getMasterPasswordPolicyOptions: jest.fn(), masterPasswordPolicyOptions$: jest.fn(),
}; };
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -144,14 +145,16 @@ describe("TrialInitiationComponent", () => {
}, },
], ],
}); });
policyServiceMock.getMasterPasswordPolicyOptions.mockReturnValueOnce({ policyServiceMock.masterPasswordPolicyOptions$.mockReturnValue(
of({
minComplexity: 4, minComplexity: 4,
minLength: 10, minLength: 10,
requireLower: null, requireLower: null,
requireNumbers: null, requireNumbers: null,
requireSpecial: null, requireSpecial: null,
requireUpper: null, requireUpper: null,
} as MasterPasswordPolicyOptions); } as MasterPasswordPolicyOptions)
);
// Need to recreate component with new service mocks // Need to recreate component with new service mocks
fixture = TestBed.createComponent(TrialInitiationComponent); fixture = TestBed.createComponent(TrialInitiationComponent);

View File

@@ -1,9 +1,9 @@
import { StepperSelectionEvent } from "@angular/cdk/stepper"; import { StepperSelectionEvent } from "@angular/cdk/stepper";
import { TitleCasePipe } from "@angular/common"; import { TitleCasePipe } from "@angular/common";
import { Component, OnInit, ViewChild } from "@angular/core"; import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { UntypedFormBuilder, Validators } from "@angular/forms"; import { UntypedFormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs"; import { first, Subject, takeUntil } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
@@ -24,8 +24,7 @@ import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.co
selector: "app-trial", selector: "app-trial",
templateUrl: "trial-initiation.component.html", templateUrl: "trial-initiation.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class TrialInitiationComponent implements OnInit, OnDestroy {
export class TrialInitiationComponent implements OnInit {
email = ""; email = "";
org = ""; org = "";
orgInfoSubLabel = ""; orgInfoSubLabel = "";
@@ -63,6 +62,8 @@ export class TrialInitiationComponent implements OnInit {
} }
} }
private destroy$ = new Subject<void>();
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
protected router: Router, protected router: Router,
@@ -140,12 +141,20 @@ export class TrialInitiationComponent implements OnInit {
} }
if (this.policies != null) { if (this.policies != null) {
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions( this.policyService
this.policies .masterPasswordPolicyOptions$(this.policies)
); .pipe(takeUntil(this.destroy$))
.subscribe((enforcedPasswordPolicyOptions) => {
this.enforcedPolicyOptions = enforcedPasswordPolicyOptions;
});
} }
} }
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
stepSelectionChange(event: StepperSelectionEvent) { stepSelectionChange(event: StepperSelectionEvent) {
// Set org info sub label // Set org info sub label
if (event.selectedIndex === 1 && this.orgInfoFormGroup.controls.name.value === "") { if (event.selectedIndex === 1 && this.orgInfoFormGroup.controls.name.value === "") {

View File

@@ -3,7 +3,6 @@ import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ValidationService } from "@bitwarden/angular/services/validation.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -11,6 +10,7 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { StateService } from "@bitwarden/common/abstractions/state.service"; import { StateService } from "@bitwarden/common/abstractions/state.service";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType";
import { ProviderUserStatusType } from "@bitwarden/common/enums/providerUserStatusType"; import { ProviderUserStatusType } from "@bitwarden/common/enums/providerUserStatusType";

View File

@@ -1,16 +1,32 @@
import { Injectable } from "@angular/core"; import { Injectable, OnDestroy, OnInit } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { DeviceType } from "@bitwarden/common/enums/deviceType"; import { DeviceType } from "@bitwarden/common/enums/deviceType";
import { EventType } from "@bitwarden/common/enums/eventType"; import { EventType } from "@bitwarden/common/enums/eventType";
import { PolicyType } from "@bitwarden/common/enums/policyType"; import { PolicyType } from "@bitwarden/common/enums/policyType";
import { Policy } from "@bitwarden/common/models/domain/policy";
import { EventResponse } from "@bitwarden/common/models/response/eventResponse"; import { EventResponse } from "@bitwarden/common/models/response/eventResponse";
@Injectable() @Injectable()
export class EventService { export class EventService implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
private policies: Policy[];
constructor(private i18nService: I18nService, private policyService: PolicyService) {} constructor(private i18nService: I18nService, private policyService: PolicyService) {}
ngOnInit(): void {
this.policyService.policies$.pipe(takeUntil(this.destroy$)).subscribe((policies) => {
this.policies = policies;
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
getDefaultDateFilters() { getDefaultDateFilters() {
const d = new Date(); const d = new Date();
const end = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59); const end = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59);
@@ -326,8 +342,7 @@ export class EventService {
case EventType.Policy_Updated: { case EventType.Policy_Updated: {
msg = this.i18nService.t("modifiedPolicyId", this.formatPolicyId(ev)); msg = this.i18nService.t("modifiedPolicyId", this.formatPolicyId(ev));
const policies = await this.policyService.getAll(); const policy = this.policies.filter((p) => p.id === ev.policyId)[0];
const policy = policies.filter((p) => p.id === ev.policyId)[0];
let p1 = this.getShortId(ev.policyId); let p1 = this.getShortId(ev.policyId);
if (policy != null) { if (policy != null) {
p1 = PolicyType[policy.type]; p1 = PolicyType[policy.type];

View File

@@ -1,11 +1,10 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { first } from "rxjs/operators"; import { combineLatest, concatMap, Subject, takeUntil } from "rxjs";
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ValidationService } from "@bitwarden/angular/services/validation.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -13,11 +12,11 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { StateService } from "@bitwarden/common/abstractions/state.service"; import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType";
import { PolicyType } from "@bitwarden/common/enums/policyType"; import { PolicyType } from "@bitwarden/common/enums/policyType";
@@ -43,10 +42,9 @@ import { UserGroupsComponent } from "./user-groups.component";
selector: "app-org-people", selector: "app-org-people",
templateUrl: "people.component.html", templateUrl: "people.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class PeopleComponent export class PeopleComponent
extends BasePeopleComponent<OrganizationUserUserDetailsResponse> extends BasePeopleComponent<OrganizationUserUserDetailsResponse>
implements OnInit implements OnInit, OnDestroy
{ {
@ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef; @ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
@ViewChild("groupsTemplate", { read: ViewContainerRef, static: true }) @ViewChild("groupsTemplate", { read: ViewContainerRef, static: true })
@@ -77,6 +75,8 @@ export class PeopleComponent
orgResetPasswordPolicyEnabled = false; orgResetPasswordPolicyEnabled = false;
callingUserType: OrganizationUserType = null; callingUserType: OrganizationUserType = null;
private destroy$ = new Subject<void>();
constructor( constructor(
apiService: ApiService, apiService: ApiService,
private route: ActivatedRoute, private route: ActivatedRoute,
@@ -84,10 +84,8 @@ export class PeopleComponent
modalService: ModalService, modalService: ModalService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
cryptoService: CryptoService, cryptoService: CryptoService,
private router: Router,
searchService: SearchService, searchService: SearchService,
validationService: ValidationService, validationService: ValidationService,
private policyApiService: PolicyApiServiceAbstraction,
private policyService: PolicyService, private policyService: PolicyService,
logService: LogService, logService: LogService,
searchPipe: SearchPipe, searchPipe: SearchPipe,
@@ -113,8 +111,9 @@ export class PeopleComponent
} }
async ngOnInit() { async ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe combineLatest([this.route.params, this.route.queryParams, this.policyService.policies$])
this.route.parent.params.subscribe(async (params) => { .pipe(
concatMap(async ([params, qParams, policies]) => {
this.organizationId = params.organizationId; this.organizationId = params.organizationId;
const organization = await this.organizationService.get(this.organizationId); const organization = await this.organizationService.get(this.organizationId);
this.accessEvents = organization.useEvents; this.accessEvents = organization.useEvents;
@@ -129,7 +128,10 @@ export class PeopleComponent
const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId); const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId);
const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey); const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey);
const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString); const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
const response = await this.organizationApiService.updateKeys(this.organizationId, request); const response = await this.organizationApiService.updateKeys(
this.organizationId,
request
);
if (response != null) { if (response != null) {
this.orgHasKeys = response.publicKey != null && response.privateKey != null; this.orgHasKeys = response.publicKey != null && response.privateKey != null;
await this.syncService.fullSync(true); // Replace oganizations with new data await this.syncService.fullSync(true); // Replace oganizations with new data
@@ -138,10 +140,13 @@ export class PeopleComponent
} }
} }
const resetPasswordPolicy = policies
.filter((policy) => policy.type === PolicyType.ResetPassword)
.find((p) => p.organizationId === this.organizationId);
this.orgResetPasswordPolicyEnabled = resetPasswordPolicy?.enabled;
await this.load(); await this.load();
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
this.searchText = qParams.search; this.searchText = qParams.search;
if (qParams.viewEvents != null) { if (qParams.viewEvents != null) {
const user = this.users.filter((u) => u.id === qParams.viewEvents); const user = this.users.filter((u) => u.id === qParams.viewEvents);
@@ -149,17 +154,20 @@ export class PeopleComponent
this.events(user[0]); this.events(user[0]);
} }
} }
}); }),
}); takeUntil(this.destroy$)
)
.subscribe();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
} }
async load() { async load() {
const resetPasswordPolicy = await this.policyApiService.getPolicyForOrganization(
PolicyType.ResetPassword,
this.organizationId
);
this.orgResetPasswordPolicyEnabled = resetPasswordPolicy?.enabled;
super.load(); super.load();
await super.load();
} }
getUsers(): Promise<ListResponse<OrganizationUserUserDetailsResponse>> { getUsers(): Promise<ListResponse<OrganizationUserUserDetailsResponse>> {

View File

@@ -1,4 +1,13 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from "@angular/core"; import {
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
ViewChild,
} from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import zxcvbn from "zxcvbn"; import zxcvbn from "zxcvbn";
import { PasswordStrengthComponent } from "@bitwarden/angular/shared/components/password-strength/password-strength.component"; import { PasswordStrengthComponent } from "@bitwarden/angular/shared/components/password-strength/password-strength.component";
@@ -18,7 +27,7 @@ import { OrganizationUserResetPasswordRequest } from "@bitwarden/common/models/r
selector: "app-reset-password", selector: "app-reset-password",
templateUrl: "reset-password.component.html", templateUrl: "reset-password.component.html",
}) })
export class ResetPasswordComponent implements OnInit { export class ResetPasswordComponent implements OnInit, OnDestroy {
@Input() name: string; @Input() name: string;
@Input() email: string; @Input() email: string;
@Input() id: string; @Input() id: string;
@@ -32,6 +41,8 @@ export class ResetPasswordComponent implements OnInit {
passwordStrengthResult: zxcvbn.ZXCVBNResult; passwordStrengthResult: zxcvbn.ZXCVBNResult;
formPromise: Promise<any>; formPromise: Promise<any>;
private destroy$ = new Subject<void>();
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,
private i18nService: I18nService, private i18nService: I18nService,
@@ -43,8 +54,18 @@ export class ResetPasswordComponent implements OnInit {
) {} ) {}
async ngOnInit() { async ngOnInit() {
// Get Enforced Policy Options this.policyService
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); .masterPasswordPolicyOptions$()
.pipe(takeUntil(this.destroy$))
.subscribe(
(enforcedPasswordPolicyOptions) =>
(this.enforcedPolicyOptions = enforcedPasswordPolicyOptions)
);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
} }
get loggedOutWarningName() { get loggedOutWarningName() {
@@ -52,7 +73,7 @@ export class ResetPasswordComponent implements OnInit {
} }
async generatePassword() { async generatePassword() {
const options = (await this.passwordGenerationService.getOptions())[0]; const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
this.newPassword = await this.passwordGenerationService.generatePassword(options); this.newPassword = await this.passwordGenerationService.generatePassword(options);
this.passwordStrengthComponent.updatePasswordStrength(this.newPassword); this.passwordStrengthComponent.updatePasswordStrength(this.newPassword);
} }

View File

@@ -4,12 +4,12 @@ import { Observable, Subject } from "rxjs";
import { first, map, takeUntil } from "rxjs/operators"; import { first, map, takeUntil } from "rxjs/operators";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ValidationService } from "@bitwarden/angular/services/validation.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
import { PlanSponsorshipType } from "@bitwarden/common/enums/planSponsorshipType"; import { PlanSponsorshipType } from "@bitwarden/common/enums/planSponsorshipType";
import { PlanType } from "@bitwarden/common/enums/planType"; import { PlanType } from "@bitwarden/common/enums/planType";
import { ProductType } from "@bitwarden/common/enums/productType"; import { ProductType } from "@bitwarden/common/enums/productType";

View File

@@ -47,7 +47,6 @@ export class OrganizationImportComponent extends ImportComponent {
this.organizationId = params.organizationId; this.organizationId = params.organizationId;
this.successNavigate = ["organizations", this.organizationId, "vault"]; this.successNavigate = ["organizations", this.organizationId, "vault"];
await super.ngOnInit(); await super.ngOnInit();
this.importBlockedByPolicy = false;
}); });
const organization = await this.organizationService.get(this.organizationId); const organization = await this.organizationService.get(this.organizationId);
this.organizationName = organization.name; this.organizationName = organization.name;

View File

@@ -1,4 +1,5 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { takeUntil } from "rxjs";
import { ChangePasswordComponent } from "@bitwarden/angular/components/change-password.component"; import { ChangePasswordComponent } from "@bitwarden/angular/components/change-password.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -21,7 +22,11 @@ import { PolicyResponse } from "@bitwarden/common/models/response/policyResponse
selector: "emergency-access-takeover", selector: "emergency-access-takeover",
templateUrl: "emergency-access-takeover.component.html", templateUrl: "emergency-access-takeover.component.html",
}) })
export class EmergencyAccessTakeoverComponent extends ChangePasswordComponent implements OnInit { // eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class EmergencyAccessTakeoverComponent
extends ChangePasswordComponent
implements OnInit, OnDestroy
{
@Output() onDone = new EventEmitter(); @Output() onDone = new EventEmitter();
@Input() emergencyAccessId: string; @Input() emergencyAccessId: string;
@Input() name: string; @Input() name: string;
@@ -59,12 +64,19 @@ export class EmergencyAccessTakeoverComponent extends ChangePasswordComponent im
const policies = response.data.map( const policies = response.data.map(
(policyResponse: PolicyResponse) => new Policy(new PolicyData(policyResponse)) (policyResponse: PolicyResponse) => new Policy(new PolicyData(policyResponse))
); );
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(
policies this.policyService
); .masterPasswordPolicyOptions$(policies)
.pipe(takeUntil(this.destroy$))
.subscribe((enforcedPolicyOptions) => (this.enforcedPolicyOptions = enforcedPolicyOptions));
} }
} }
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
ngOnDestroy(): void {
super.ngOnDestroy();
}
async submit() { async submit() {
if (!(await this.strongPassword())) { if (!(await this.strongPassword())) {
return; return;

View File

@@ -1,6 +1,15 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from "@angular/core"; import {
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
ViewChild,
} from "@angular/core";
import { UntypedFormBuilder, Validators } from "@angular/forms"; import { UntypedFormBuilder, Validators } from "@angular/forms";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
@@ -35,7 +44,7 @@ interface OnSuccessArgs {
selector: "app-organization-plans", selector: "app-organization-plans",
templateUrl: "organization-plans.component.html", templateUrl: "organization-plans.component.html",
}) })
export class OrganizationPlansComponent implements OnInit { export class OrganizationPlansComponent implements OnInit, OnDestroy {
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent; @ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
@ViewChild(TaxInfoComponent) taxComponent: TaxInfoComponent; @ViewChild(TaxInfoComponent) taxComponent: TaxInfoComponent;
@@ -73,6 +82,8 @@ export class OrganizationPlansComponent implements OnInit {
plans: PlanResponse[]; plans: PlanResponse[];
private destroy$ = new Subject<void>();
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,
private i18nService: I18nService, private i18nService: I18nService,
@@ -114,9 +125,21 @@ export class OrganizationPlansComponent implements OnInit {
this.formGroup.controls.billingEmail.addValidators(Validators.required); this.formGroup.controls.billingEmail.addValidators(Validators.required);
} }
this.policyService
.policyAppliesToActiveUser$(PolicyType.SingleOrg)
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.singleOrgPolicyBlock = policyAppliesToActiveUser;
});
this.loading = false; this.loading = false;
} }
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
get createOrganization() { get createOrganization() {
return this.organizationId == null; return this.organizationId == null;
} }
@@ -288,8 +311,6 @@ export class OrganizationPlansComponent implements OnInit {
} }
async submit() { async submit() {
this.singleOrgPolicyBlock = await this.userHasBlockingSingleOrgPolicy();
if (this.singleOrgPolicyBlock) { if (this.singleOrgPolicyBlock) {
return; return;
} }
@@ -353,10 +374,6 @@ export class OrganizationPlansComponent implements OnInit {
} }
} }
private async userHasBlockingSingleOrgPolicy() {
return this.policyService.policyAppliesToUser(PolicyType.SingleOrg);
}
private async updateOrganization(orgId: string) { private async updateOrganization(orgId: string) {
const request = new OrganizationUpgradeRequest(); const request = new OrganizationUpgradeRequest();
request.businessName = this.formGroup.controls.businessOwned.value request.businessName = this.formGroup.controls.businessOwned.value

View File

@@ -1,4 +1,5 @@
import { Component, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/core"; import { Component, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
@@ -25,8 +26,7 @@ import { TwoFactorYubiKeyComponent } from "./two-factor-yubikey.component";
selector: "app-two-factor-setup", selector: "app-two-factor-setup",
templateUrl: "two-factor-setup.component.html", templateUrl: "two-factor-setup.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class TwoFactorSetupComponent implements OnInit, OnDestroy {
export class TwoFactorSetupComponent implements OnInit {
@ViewChild("recoveryTemplate", { read: ViewContainerRef, static: true }) @ViewChild("recoveryTemplate", { read: ViewContainerRef, static: true })
recoveryModalRef: ViewContainerRef; recoveryModalRef: ViewContainerRef;
@ViewChild("authenticatorTemplate", { read: ViewContainerRef, static: true }) @ViewChild("authenticatorTemplate", { read: ViewContainerRef, static: true })
@@ -49,6 +49,9 @@ export class TwoFactorSetupComponent implements OnInit {
modal: ModalRef; modal: ModalRef;
formPromise: Promise<any>; formPromise: Promise<any>;
private destroy$ = new Subject<void>();
private twoFactorAuthPolicyAppliesToActiveUser: boolean;
constructor( constructor(
protected apiService: ApiService, protected apiService: ApiService,
protected modalService: ModalService, protected modalService: ModalService,
@@ -93,9 +96,22 @@ export class TwoFactorSetupComponent implements OnInit {
} }
this.providers.sort((a: any, b: any) => a.sort - b.sort); this.providers.sort((a: any, b: any) => a.sort - b.sort);
this.policyService
.policyAppliesToActiveUser$(PolicyType.TwoFactorAuthentication)
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.twoFactorAuthPolicyAppliesToActiveUser = policyAppliesToActiveUser;
});
await this.load(); await this.load();
} }
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
async load() { async load() {
this.loading = true; this.loading = true;
const providerList = await this.getTwoFactorProviders(); const providerList = await this.getTwoFactorProviders();
@@ -203,9 +219,7 @@ export class TwoFactorSetupComponent implements OnInit {
private async evaluatePolicies() { private async evaluatePolicies() {
if (this.organizationId == null && this.providers.filter((p) => p.enabled).length === 1) { if (this.organizationId == null && this.providers.filter((p) => p.enabled).length === 1) {
this.showPolicyWarning = await this.policyService.policyAppliesToUser( this.showPolicyWarning = this.twoFactorAuthPolicyAppliesToActiveUser;
PolicyType.TwoFactorAuthentication
);
} else { } else {
this.showPolicyWarning = false; this.showPolicyWarning = false;
} }

View File

@@ -13,6 +13,7 @@ import {
CalloutModule, CalloutModule,
FormFieldModule, FormFieldModule,
IconModule, IconModule,
AsyncActionsModule,
MenuModule, MenuModule,
TableModule, TableModule,
TabsModule, TabsModule,
@@ -51,6 +52,7 @@ import "./locales";
], ],
exports: [ exports: [
CommonModule, CommonModule,
AsyncActionsModule,
DragDropModule, DragDropModule,
FormsModule, FormsModule,
InfiniteScrollModule, InfiniteScrollModule,

View File

@@ -1,7 +1,7 @@
<div class="page-header"> <div class="page-header">
<h1>{{ "importData" | i18n }}</h1> <h1>{{ "importData" | i18n }}</h1>
</div> </div>
<app-callout type="info" *ngIf="importBlockedByPolicy"> <app-callout type="info" *ngIf="importBlockedByPolicy$ | async">
{{ "personalOwnershipPolicyInEffectImports" | i18n }} {{ "personalOwnershipPolicyInEffectImports" | i18n }}
</app-callout> </app-callout>
<form #form (ngSubmit)="submit()" ngNativeValidate> <form #form (ngSubmit)="submit()" ngNativeValidate>
@@ -14,7 +14,7 @@
name="Format" name="Format"
[(ngModel)]="format" [(ngModel)]="format"
class="form-control" class="form-control"
[disabled]="importBlockedByPolicy" [disabled]="importBlockedByPolicy$ | async"
required required
> >
<option *ngFor="let o of featuredImportOptions" [ngValue]="o.id">{{ o.name }}</option> <option *ngFor="let o of featuredImportOptions" [ngValue]="o.id">{{ o.name }}</option>
@@ -296,7 +296,7 @@
id="file" id="file"
class="form-control-file" class="form-control-file"
name="file" name="file"
[disabled]="importBlockedByPolicy" [disabled]="importBlockedByPolicy$ | async"
/> />
</div> </div>
</div> </div>
@@ -308,14 +308,14 @@
class="form-control" class="form-control"
name="FileContents" name="FileContents"
[(ngModel)]="fileContents" [(ngModel)]="fileContents"
[disabled]="importBlockedByPolicy" [disabled]="importBlockedByPolicy$ | async"
></textarea> ></textarea>
</div> </div>
<button <button
type="submit" type="submit"
class="btn btn-primary btn-submit" class="btn btn-primary btn-submit"
[disabled]="loading || importBlockedByPolicy" [disabled]="loading || importBlockedByPolicy$ | async"
[ngClass]="{ manual: importBlockedByPolicy }" [ngClass]="{ manual: importBlockedByPolicy$ | async }"
> >
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "importData" | i18n }}</span> <span>{{ "importData" | i18n }}</span>

View File

@@ -1,6 +1,7 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import * as JSZip from "jszip"; import * as JSZip from "jszip";
import { firstValueFrom, Subject } from "rxjs";
import Swal, { SweetAlertIcon } from "sweetalert2"; import Swal, { SweetAlertIcon } from "sweetalert2";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
@@ -19,18 +20,22 @@ import { FilePasswordPromptComponent } from "./file-password-prompt.component";
selector: "app-import", selector: "app-import",
templateUrl: "import.component.html", templateUrl: "import.component.html",
}) })
export class ImportComponent implements OnInit { export class ImportComponent implements OnInit, OnDestroy {
featuredImportOptions: ImportOption[]; featuredImportOptions: ImportOption[];
importOptions: ImportOption[]; importOptions: ImportOption[];
format: ImportType = null; format: ImportType = null;
fileContents: string; fileContents: string;
formPromise: Promise<ImportError>; formPromise: Promise<ImportError>;
loading = false; loading = false;
importBlockedByPolicy = false; importBlockedByPolicy$ = this.policyService.policyAppliesToActiveUser$(
PolicyType.PersonalOwnership
);
protected organizationId: string = null; protected organizationId: string = null;
protected successNavigate: any[] = ["vault"]; protected successNavigate: any[] = ["vault"];
private destroy$ = new Subject<void>();
constructor( constructor(
protected i18nService: I18nService, protected i18nService: I18nService,
protected importService: ImportService, protected importService: ImportService,
@@ -43,14 +48,15 @@ export class ImportComponent implements OnInit {
async ngOnInit() { async ngOnInit() {
this.setImportOptions(); this.setImportOptions();
}
this.importBlockedByPolicy = await this.policyService.policyAppliesToUser( ngOnDestroy(): void {
PolicyType.PersonalOwnership this.destroy$.next();
); this.destroy$.complete();
} }
async submit() { async submit() {
if (this.importBlockedByPolicy) { if (await firstValueFrom(this.importBlockedByPolicy$)) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
null, null,

View File

@@ -1,4 +1,4 @@
import { Component } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/components/add-edit.component"; import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/components/add-edit.component";
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service";
@@ -24,7 +24,7 @@ import { LoginUriView } from "@bitwarden/common/models/view/loginUriView";
selector: "app-vault-add-edit", selector: "app-vault-add-edit",
templateUrl: "add-edit.component.html", templateUrl: "add-edit.component.html",
}) })
export class AddEditComponent extends BaseAddEditComponent { export class AddEditComponent extends BaseAddEditComponent implements OnInit, OnDestroy {
canAccessPremium: boolean; canAccessPremium: boolean;
totpCode: string; totpCode: string;
totpCodeFormatted: string; totpCodeFormatted: string;
@@ -95,6 +95,10 @@ export class AddEditComponent extends BaseAddEditComponent {
} }
} }
ngOnDestroy() {
super.ngOnDestroy();
}
toggleFavorite() { toggleFavorite() {
this.cipher.favorite = !this.cipher.favorite; this.cipher.favorite = !this.cipher.favorite;
} }
@@ -133,7 +137,7 @@ export class AddEditComponent extends BaseAddEditComponent {
async generatePassword(): Promise<boolean> { async generatePassword(): Promise<boolean> {
const confirmed = await super.generatePassword(); const confirmed = await super.generatePassword();
if (confirmed) { if (confirmed) {
const options = (await this.passwordGenerationService.getOptions())[0]; const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
this.cipher.login.password = await this.passwordGenerationService.generatePassword(options); this.cipher.login.password = await this.passwordGenerationService.generatePassword(options);
} }
return confirmed; return confirmed;

View File

@@ -1,4 +1,5 @@
import { Component, Inject } from "@angular/core"; import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import { map, Subject, takeUntil } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -21,11 +22,13 @@ import { OrganizationFilter } from "../shared/models/vault-filter.type";
selector: "app-organization-options", selector: "app-organization-options",
templateUrl: "organization-options.component.html", templateUrl: "organization-options.component.html",
}) })
export class OrganizationOptionsComponent { export class OrganizationOptionsComponent implements OnInit, OnDestroy {
actionPromise: Promise<void | boolean>; actionPromise: Promise<void | boolean>;
policies: Policy[]; policies: Policy[];
loaded = false; loaded = false;
private destroy$ = new Subject<void>();
constructor( constructor(
@Inject(OptionsInput) private organization: OrganizationFilter, @Inject(OptionsInput) private organization: OrganizationFilter,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
@@ -39,12 +42,20 @@ export class OrganizationOptionsComponent {
) {} ) {}
async ngOnInit() { async ngOnInit() {
await this.load(); this.policyService.policies$
.pipe(
map((policies) => policies.filter((policy) => policy.type === PolicyType.ResetPassword)),
takeUntil(this.destroy$)
)
.subscribe((policies) => {
this.policies = policies;
this.loaded = true;
});
} }
async load() { ngOnDestroy() {
this.policies = await this.policyService.getAll(PolicyType.ResetPassword); this.destroy$.next();
this.loaded = true; this.destroy$.complete();
} }
allowEnrollmentChanges(org: Organization): boolean { allowEnrollmentChanges(org: Organization): boolean {
@@ -84,7 +95,6 @@ export class OrganizationOptionsComponent {
}); });
await this.actionPromise; await this.actionPromise;
this.platformUtilsService.showToast("success", null, "Unlinked SSO"); this.platformUtilsService.showToast("success", null, "Unlinked SSO");
await this.load();
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
@@ -106,7 +116,6 @@ export class OrganizationOptionsComponent {
this.actionPromise = this.organizationApiService.leave(org.id); this.actionPromise = this.organizationApiService.leave(org.id);
await this.actionPromise; await this.actionPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization")); this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization"));
await this.load();
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }

View File

@@ -5454,6 +5454,18 @@
"numberOfUsers": { "numberOfUsers": {
"message": "Number of users" "message": "Number of users"
}, },
"multiSelectPlaceholder": {
"message": "-- Type to Filter --"
},
"multiSelectLoading": {
"message": "Retrieving options..."
},
"multiSelectNotFound": {
"message": "No items found"
},
"multiSelectClearAll": {
"message": "Clear all"
},
"from": { "from": {
"message": "From" "message": "From"
}, },

View File

@@ -1,9 +1,9 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ValidationService } from "@bitwarden/angular/services/validation.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { ProviderService } from "@bitwarden/common/abstractions/provider.service"; import { ProviderService } from "@bitwarden/common/abstractions/provider.service";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
import { Organization } from "@bitwarden/common/models/domain/organization"; import { Organization } from "@bitwarden/common/models/domain/organization";
import { Provider } from "@bitwarden/common/models/domain/provider"; import { Provider } from "@bitwarden/common/models/domain/provider";

View File

@@ -3,7 +3,6 @@ import { ActivatedRoute } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ValidationService } from "@bitwarden/angular/services/validation.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
@@ -12,6 +11,7 @@ import { OrganizationService } from "@bitwarden/common/abstractions/organization
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { ProviderService } from "@bitwarden/common/abstractions/provider.service"; import { ProviderService } from "@bitwarden/common/abstractions/provider.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
import { PlanType } from "@bitwarden/common/enums/planType"; import { PlanType } from "@bitwarden/common/enums/planType";
import { ProviderUserType } from "@bitwarden/common/enums/providerUserType"; import { ProviderUserType } from "@bitwarden/common/enums/providerUserType";
import { Organization } from "@bitwarden/common/models/domain/organization"; import { Organization } from "@bitwarden/common/models/domain/organization";

View File

@@ -5,7 +5,6 @@ import { first } from "rxjs/operators";
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ValidationService } from "@bitwarden/angular/services/validation.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -14,6 +13,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { ProviderService } from "@bitwarden/common/abstractions/provider.service"; import { ProviderService } from "@bitwarden/common/abstractions/provider.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { StateService } from "@bitwarden/common/abstractions/state.service"; import { StateService } from "@bitwarden/common/abstractions/state.service";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
import { ProviderUserStatusType } from "@bitwarden/common/enums/providerUserStatusType"; import { ProviderUserStatusType } from "@bitwarden/common/enums/providerUserStatusType";
import { ProviderUserType } from "@bitwarden/common/enums/providerUserType"; import { ProviderUserType } from "@bitwarden/common/enums/providerUserType";
import { ProviderUserBulkRequest } from "@bitwarden/common/models/request/provider/providerUserBulkRequest"; import { ProviderUserBulkRequest } from "@bitwarden/common/models/request/provider/providerUserBulkRequest";

View File

@@ -2,12 +2,12 @@ import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { ValidationService } from "@bitwarden/angular/services/validation.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
import { ProviderSetupRequest } from "@bitwarden/common/models/request/provider/providerSetupRequest"; import { ProviderSetupRequest } from "@bitwarden/common/models/request/provider/providerSetupRequest";
@Component({ @Component({

View File

@@ -1,5 +1,5 @@
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { Observable } from "rxjs"; import { Observable, Subject, takeUntil, concatMap } from "rxjs";
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
@@ -33,7 +33,7 @@ import { LoginView } from "@bitwarden/common/models/view/loginView";
import { SecureNoteView } from "@bitwarden/common/models/view/secureNoteView"; import { SecureNoteView } from "@bitwarden/common/models/view/secureNoteView";
@Directive() @Directive()
export class AddEditComponent implements OnInit { export class AddEditComponent implements OnInit, OnDestroy {
@Input() cloneMode = false; @Input() cloneMode = false;
@Input() folderId: string = null; @Input() folderId: string = null;
@Input() cipherId: string; @Input() cipherId: string;
@@ -75,7 +75,9 @@ export class AddEditComponent implements OnInit {
reprompt = false; reprompt = false;
canUseReprompt = true; canUseReprompt = true;
protected destroy$ = new Subject<void>();
protected writeableCollections: CollectionView[]; protected writeableCollections: CollectionView[];
private personalOwnershipPolicyAppliesToActiveUser: boolean;
private previousCipherId: string; private previousCipherId: string;
constructor( constructor(
@@ -152,14 +154,28 @@ export class AddEditComponent implements OnInit {
} }
async ngOnInit() { async ngOnInit() {
this.policyService
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
.pipe(
concatMap(async (policyAppliesToActiveUser) => {
this.personalOwnershipPolicyAppliesToActiveUser = policyAppliesToActiveUser;
await this.init(); await this.init();
}),
takeUntil(this.destroy$)
)
.subscribe();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
} }
async init() { async init() {
if (this.ownershipOptions.length) { if (this.ownershipOptions.length) {
this.ownershipOptions = []; this.ownershipOptions = [];
} }
if (await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)) { if (this.personalOwnershipPolicyAppliesToActiveUser) {
this.allowPersonal = false; this.allowPersonal = false;
} else { } else {
const myEmail = await this.stateService.getEmail(); const myEmail = await this.stateService.getEmail();

View File

@@ -1,4 +1,5 @@
import { Directive, OnInit } from "@angular/core"; import { Directive, OnDestroy, OnInit } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -15,7 +16,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCry
import { PasswordColorText } from "../shared/components/password-strength/password-strength.component"; import { PasswordColorText } from "../shared/components/password-strength/password-strength.component";
@Directive() @Directive()
export class ChangePasswordComponent implements OnInit { export class ChangePasswordComponent implements OnInit, OnDestroy {
masterPassword: string; masterPassword: string;
masterPasswordRetype: string; masterPasswordRetype: string;
formPromise: Promise<any>; formPromise: Promise<any>;
@@ -28,6 +29,8 @@ export class ChangePasswordComponent implements OnInit {
protected kdf: KdfType; protected kdf: KdfType;
protected kdfIterations: number; protected kdfIterations: number;
protected destroy$ = new Subject<void>();
constructor( constructor(
protected i18nService: I18nService, protected i18nService: I18nService,
protected cryptoService: CryptoService, protected cryptoService: CryptoService,
@@ -40,7 +43,18 @@ export class ChangePasswordComponent implements OnInit {
async ngOnInit() { async ngOnInit() {
this.email = await this.stateService.getEmail(); this.email = await this.stateService.getEmail();
this.enforcedPolicyOptions ??= await this.policyService.getMasterPasswordPolicyOptions(); this.policyService
.masterPasswordPolicyOptions$()
.pipe(takeUntil(this.destroy$))
.subscribe(
(enforcedPasswordPolicyOptions) =>
(this.enforcedPolicyOptions ??= enforcedPasswordPolicyOptions)
);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
} }
async submit() { async submit() {

View File

@@ -55,6 +55,13 @@ export class ExportComponent implements OnInit, OnDestroy {
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.policyService
.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport)
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.disabledByPolicy = policyAppliesToActiveUser;
});
await this.checkExportDisabled(); await this.checkExportDisabled();
merge( merge(
@@ -71,9 +78,6 @@ export class ExportComponent implements OnInit, OnDestroy {
} }
async checkExportDisabled() { async checkExportDisabled() {
this.disabledByPolicy = await this.policyService.policyAppliesToUser(
PolicyType.DisablePersonalVaultExport
);
if (this.disabledByPolicy) { if (this.disabledByPolicy) {
this.exportForm.disable(); this.exportForm.disable();
} }
@@ -117,6 +121,7 @@ export class ExportComponent implements OnInit, OnDestroy {
await this.userVerificationService.verifyUser(secret); await this.userVerificationService.verifyUser(secret);
} catch (e) { } catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message); this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
return;
} }
this.doExport(); this.doExport();

View File

@@ -1,7 +1,7 @@
import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core"; import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { Subscription } from "rxjs"; import { Subject } from "rxjs";
import { take } from "rxjs/operators"; import { concatMap, take, takeUntil } from "rxjs/operators";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
@@ -41,7 +41,7 @@ export class LockComponent implements OnInit, OnDestroy {
private invalidPinAttempts = 0; private invalidPinAttempts = 0;
private pinSet: [boolean, boolean]; private pinSet: [boolean, boolean];
private activeAccountSubscription: Subscription; private destroy$ = new Subject<void>();
constructor( constructor(
protected router: Router, protected router: Router,
@@ -60,14 +60,19 @@ export class LockComponent implements OnInit, OnDestroy {
) {} ) {}
async ngOnInit() { async ngOnInit() {
// eslint-disable-next-line rxjs/no-async-subscribe this.stateService.activeAccount$
this.activeAccountSubscription = this.stateService.activeAccount$.subscribe(async () => { .pipe(
concatMap(async () => {
await this.load(); await this.load();
}); }),
takeUntil(this.destroy$)
)
.subscribe();
} }
ngOnDestroy() { ngOnDestroy() {
this.activeAccountSubscription.unsubscribe(); this.destroy$.next();
this.destroy$.complete();
} }
async submit() { async submit() {

View File

@@ -65,7 +65,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
} }
async ngOnInit() { async ngOnInit() {
let email = this.formGroup.get("email")?.value; let email = this.formGroup.value.email;
if (email == null || email === "") { if (email == null || email === "") {
email = await this.stateService.getRememberedEmail(); email = await this.stateService.getRememberedEmail();
this.formGroup.get("email")?.setValue(email); this.formGroup.get("email")?.setValue(email);
@@ -81,9 +81,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
} }
async submit(showToast = true) { async submit(showToast = true) {
const email = this.formGroup.get("email")?.value; const data = this.formGroup.value;
const masterPassword = this.formGroup.get("masterPassword")?.value;
const rememberEmail = this.formGroup.get("rememberEmail")?.value;
await this.setupCaptcha(); await this.setupCaptcha();
@@ -103,15 +101,15 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
try { try {
const credentials = new PasswordLogInCredentials( const credentials = new PasswordLogInCredentials(
email, data.email,
masterPassword, data.masterPassword,
this.captchaToken, this.captchaToken,
null null
); );
this.formPromise = this.authService.logIn(credentials); this.formPromise = this.authService.logIn(credentials);
const response = await this.formPromise; const response = await this.formPromise;
if (rememberEmail || this.alwaysRememberEmail) { if (data.rememberEmail || this.alwaysRememberEmail) {
await this.stateService.setRememberedEmail(email); await this.stateService.setRememberedEmail(data.email);
} else { } else {
await this.stateService.setRememberedEmail(null); await this.stateService.setRememberedEmail(null);
} }
@@ -216,7 +214,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
} }
protected focusInput() { protected focusInput() {
const email = this.formGroup.get("email")?.value; const email = this.formGroup.value.email;
document.getElementById(email == null || email === "" ? "email" : "masterPassword").focus(); document.getElementById(email == null || email === "" ? "email" : "masterPassword").focus();
} }
} }

View File

@@ -96,11 +96,11 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
} }
async submit(showToast = true) { async submit(showToast = true) {
let email = this.formGroup.get("email")?.value; let email = this.formGroup.value.email;
email = email.trim().toLowerCase(); email = email.trim().toLowerCase();
let name = this.formGroup.get("name")?.value; let name = this.formGroup.value.name;
name = name === "" ? null : name; // Why do we do this? name = name === "" ? null : name; // Why do we do this?
const masterPassword = this.formGroup.get("masterPassword")?.value; const masterPassword = this.formGroup.value.masterPassword;
try { try {
if (!this.accountCreated) { if (!this.accountCreated) {
const registerResponse = await this.registerAccount( const registerResponse = await this.registerAccount(
@@ -125,7 +125,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
if (loginResponse.captchaRequired) { if (loginResponse.captchaRequired) {
return; return;
} }
this.createdAccount.emit(this.formGroup.get("email")?.value); this.createdAccount.emit(this.formGroup.value.email);
} else { } else {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"success", "success",
@@ -232,7 +232,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
masterPassword: string, masterPassword: string,
name: string name: string
): Promise<RegisterRequest> { ): Promise<RegisterRequest> {
const hint = this.formGroup.get("hint")?.value; const hint = this.formGroup.value.hint;
const kdf = DEFAULT_KDF_TYPE; const kdf = DEFAULT_KDF_TYPE;
const kdfIterations = DEFAULT_KDF_ITERATIONS; const kdfIterations = DEFAULT_KDF_ITERATIONS;
const key = await this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations); const key = await this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);

View File

@@ -1,5 +1,6 @@
import { DatePipe } from "@angular/common"; import { DatePipe } from "@angular/common";
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -18,7 +19,7 @@ import { SendTextView } from "@bitwarden/common/models/view/sendTextView";
import { SendView } from "@bitwarden/common/models/view/sendView"; import { SendView } from "@bitwarden/common/models/view/sendView";
@Directive() @Directive()
export class AddEditComponent implements OnInit { export class AddEditComponent implements OnInit, OnDestroy {
@Input() sendId: string; @Input() sendId: string;
@Input() type: SendType; @Input() type: SendType;
@@ -45,6 +46,7 @@ export class AddEditComponent implements OnInit {
showOptions = false; showOptions = false;
private sendLinkBaseUrl: string; private sendLinkBaseUrl: string;
private destroy$ = new Subject<void>();
constructor( constructor(
protected i18nService: I18nService, protected i18nService: I18nService,
@@ -80,9 +82,28 @@ export class AddEditComponent implements OnInit {
} }
async ngOnInit() { async ngOnInit() {
this.policyService
.policyAppliesToActiveUser$(PolicyType.DisableSend)
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.disableSend = policyAppliesToActiveUser;
});
this.policyService
.policyAppliesToActiveUser$(PolicyType.SendOptions, (p) => p.data.disableHideEmail)
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.disableHideEmail = policyAppliesToActiveUser;
});
await this.load(); await this.load();
} }
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
get editMode(): boolean { get editMode(): boolean {
return this.sendId != null; return this.sendId != null;
} }
@@ -97,12 +118,6 @@ export class AddEditComponent implements OnInit {
} }
async load() { async load() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
this.disableHideEmail = await this.policyService.policyAppliesToUser(
PolicyType.SendOptions,
(p) => p.data.disableHideEmail
);
this.canAccessPremium = await this.stateService.getCanAccessPremium(); this.canAccessPremium = await this.stateService.getCanAccessPremium();
this.emailVerified = await this.stateService.getEmailVerified(); this.emailVerified = await this.stateService.getEmailVerified();
if (!this.canAccessPremium || !this.emailVerified) { if (!this.canAccessPremium || !this.emailVerified) {

View File

@@ -1,4 +1,5 @@
import { Directive, NgZone, OnInit } from "@angular/core"; import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -12,7 +13,7 @@ import { SendType } from "@bitwarden/common/enums/sendType";
import { SendView } from "@bitwarden/common/models/view/sendView"; import { SendView } from "@bitwarden/common/models/view/sendView";
@Directive() @Directive()
export class SendComponent implements OnInit { export class SendComponent implements OnInit, OnDestroy {
disableSend = false; disableSend = false;
sendType = SendType; sendType = SendType;
loaded = false; loaded = false;
@@ -36,6 +37,7 @@ export class SendComponent implements OnInit {
onSuccessfulLoad: () => Promise<any>; onSuccessfulLoad: () => Promise<any>;
private searchTimeout: any; private searchTimeout: any;
private destroy$ = new Subject<void>();
constructor( constructor(
protected sendService: SendService, protected sendService: SendService,
@@ -49,7 +51,17 @@ export class SendComponent implements OnInit {
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend); this.policyService
.policyAppliesToActiveUser$(PolicyType.DisableSend)
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.disableSend = policyAppliesToActiveUser;
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
} }
async load(filter: (send: SendView) => boolean = null) { async load(filter: (send: SendView) => boolean = null) {

View File

@@ -1,11 +1,12 @@
import { Directive, Input, OnInit } from "@angular/core"; import { Directive, Input, OnDestroy, OnInit } from "@angular/core";
import { import {
AbstractControl, AbstractControl,
ControlValueAccessor, ControlValueAccessor,
UntypedFormBuilder, FormBuilder,
ValidationErrors, ValidationErrors,
Validator, Validator,
} from "@angular/forms"; } from "@angular/forms";
import { combineLatestWith, Subject, takeUntil } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
@@ -13,7 +14,9 @@ import { PolicyType } from "@bitwarden/common/enums/policyType";
import { Policy } from "@bitwarden/common/models/domain/policy"; import { Policy } from "@bitwarden/common/models/domain/policy";
@Directive() @Directive()
export class VaultTimeoutInputComponent implements ControlValueAccessor, Validator, OnInit { export class VaultTimeoutInputComponent
implements ControlValueAccessor, Validator, OnInit, OnDestroy
{
get showCustom() { get showCustom() {
return this.form.get("vaultTimeout").value === VaultTimeoutInputComponent.CUSTOM_VALUE; return this.form.get("vaultTimeout").value === VaultTimeoutInputComponent.CUSTOM_VALUE;
} }
@@ -42,29 +45,28 @@ export class VaultTimeoutInputComponent implements ControlValueAccessor, Validat
private onChange: (vaultTimeout: number) => void; private onChange: (vaultTimeout: number) => void;
private validatorChange: () => void; private validatorChange: () => void;
private destroy$ = new Subject<void>();
constructor( constructor(
private formBuilder: UntypedFormBuilder, private formBuilder: FormBuilder,
private policyService: PolicyService, private policyService: PolicyService,
private i18nService: I18nService private i18nService: I18nService
) {} ) {}
async ngOnInit() { async ngOnInit() {
if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) { this.policyService
const vaultTimeoutPolicy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout); .policyAppliesToActiveUser$(PolicyType.MaximumVaultTimeout)
.pipe(combineLatestWith(this.policyService.policies$), takeUntil(this.destroy$))
this.vaultTimeoutPolicy = vaultTimeoutPolicy[0]; .subscribe(([policyAppliesToActiveUser, policies]) => {
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60); if (policyAppliesToActiveUser) {
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60; const vaultTimeoutPolicy = policies.find(
(policy) => policy.type === PolicyType.MaximumVaultTimeout && policy.enabled
this.vaultTimeouts = this.vaultTimeouts.filter(
(t) =>
t.value <= this.vaultTimeoutPolicy.data.minutes &&
(t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) &&
t.value != null
); );
this.validatorChange();
this.vaultTimeoutPolicy = vaultTimeoutPolicy;
this.applyVaultTimeoutPolicy();
} }
});
// eslint-disable-next-line rxjs/no-async-subscribe // eslint-disable-next-line rxjs/no-async-subscribe
this.form.valueChanges.subscribe(async (value) => { this.form.valueChanges.subscribe(async (value) => {
@@ -87,6 +89,11 @@ export class VaultTimeoutInputComponent implements ControlValueAccessor, Validat
}); });
} }
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
ngOnChanges() { ngOnChanges() {
this.vaultTimeouts.push({ this.vaultTimeouts.push({
name: this.i18nService.t("custom"), name: this.i18nService.t("custom"),
@@ -152,6 +159,19 @@ export class VaultTimeoutInputComponent implements ControlValueAccessor, Validat
} }
private customTimeInMinutes() { private customTimeInMinutes() {
return this.form.get("custom.hours")?.value * 60 + this.form.get("custom.minutes")?.value; return this.form.value.custom.hours * 60 + this.form.value.custom.minutes;
}
private applyVaultTimeoutPolicy() {
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60;
this.vaultTimeouts = this.vaultTimeouts.filter(
(t) =>
t.value <= this.vaultTimeoutPolicy.data.minutes &&
(t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) &&
t.value != null
);
this.validatorChange();
} }
} }

View File

@@ -60,7 +60,6 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
} }
async setupSubmitActions(): Promise<boolean> { async setupSubmitActions(): Promise<boolean> {
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
this.email = await this.stateService.getEmail(); this.email = await this.stateService.getEmail();
this.kdf = await this.stateService.getKdfType(); this.kdf = await this.stateService.getKdfType();
this.kdfIterations = await this.stateService.getKdfIterations(); this.kdfIterations = await this.stateService.getKdfIterations();

View File

@@ -1,10 +1,9 @@
import { Directive, ElementRef, Input, OnChanges } from "@angular/core"; import { Directive, ElementRef, Input, OnChanges } from "@angular/core";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
import { ErrorResponse } from "@bitwarden/common/models/response/errorResponse"; import { ErrorResponse } from "@bitwarden/common/models/response/errorResponse";
import { ValidationService } from "../services/validation.service";
/** /**
* Provides error handling, in particular for any error returned by the server in an api call. * Provides error handling, in particular for any error returned by the server in an api call.
* Attach it to a <form> element and provide the name of the class property that will hold the api call promise. * Attach it to a <form> element and provide the name of the class property that will hold the api call promise.

View File

@@ -55,6 +55,7 @@ import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/comm
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification-api.service.abstraction"; import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "@bitwarden/common/abstractions/usernameGeneration.service"; import { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "@bitwarden/common/abstractions/usernameGeneration.service";
import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/abstractions/validation.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service"; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { StateFactory } from "@bitwarden/common/factories/stateFactory";
@@ -102,6 +103,7 @@ import { TwoFactorService } from "@bitwarden/common/services/twoFactor.service";
import { UserVerificationApiService } from "@bitwarden/common/services/userVerification/userVerification-api.service"; import { UserVerificationApiService } from "@bitwarden/common/services/userVerification/userVerification-api.service";
import { UserVerificationService } from "@bitwarden/common/services/userVerification/userVerification.service"; import { UserVerificationService } from "@bitwarden/common/services/userVerification/userVerification.service";
import { UsernameGenerationService } from "@bitwarden/common/services/usernameGeneration.service"; import { UsernameGenerationService } from "@bitwarden/common/services/usernameGeneration.service";
import { ValidationService } from "@bitwarden/common/services/validation.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout/vaultTimeout.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service";
import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service"; import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service";
@@ -127,12 +129,10 @@ import { ModalService } from "./modal.service";
import { PasswordRepromptService } from "./passwordReprompt.service"; import { PasswordRepromptService } from "./passwordReprompt.service";
import { ThemingService } from "./theming/theming.service"; import { ThemingService } from "./theming/theming.service";
import { AbstractThemingService } from "./theming/theming.service.abstraction"; import { AbstractThemingService } from "./theming/theming.service.abstraction";
import { ValidationService } from "./validation.service";
@NgModule({ @NgModule({
declarations: [], declarations: [],
providers: [ providers: [
ValidationService,
AuthGuard, AuthGuard,
UnauthGuard, UnauthGuard,
LockGuard, LockGuard,
@@ -561,6 +561,11 @@ import { ValidationService } from "./validation.service";
useClass: AnonymousHubService, useClass: AnonymousHubService,
deps: [EnvironmentServiceAbstraction, AuthServiceAbstraction, LogService], deps: [EnvironmentServiceAbstraction, AuthServiceAbstraction, LogService],
}, },
{
provide: ValidationServiceAbstraction,
useClass: ValidationService,
deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction],
},
], ],
}) })
export class JslibServicesModule {} export class JslibServicesModule {}

View File

@@ -49,6 +49,11 @@ export class ModalService {
return this.modalList[this.modalCount - 1]; return this.modalList[this.modalCount - 1];
} }
/**
* @deprecated Use `dialogService.open` (in web) or `modalService.open` (in desktop/browser) instead.
* If replacing an existing call to this method, also remove any `@ViewChild` and `<ng-template>` associated with the
* existing usage.
*/
async openViewRef<T>( async openViewRef<T>(
componentType: Type<T>, componentType: Type<T>,
viewContainerRef: ViewContainerRef, viewContainerRef: ViewContainerRef,

View File

@@ -1,7 +1,6 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { firstValueFrom, mergeMap, Observable, from } from "rxjs"; import { firstValueFrom, from, mergeMap, Observable } from "rxjs";
import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "@bitwarden/angular/abstractions/deprecated-vault-filter.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service"; import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
@@ -15,6 +14,7 @@ import { TreeNode } from "@bitwarden/common/models/domain/treeNode";
import { CollectionView } from "@bitwarden/common/models/view/collectionView"; import { CollectionView } from "@bitwarden/common/models/view/collectionView";
import { FolderView } from "@bitwarden/common/models/view/folderView"; import { FolderView } from "@bitwarden/common/models/view/folderView";
import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "../../../abstractions/deprecated-vault-filter.service";
import { DynamicTreeNode } from "../models/dynamic-tree-node.model"; import { DynamicTreeNode } from "../models/dynamic-tree-node.model";
const NestingDelimiter = "/"; const NestingDelimiter = "/";
@@ -84,11 +84,15 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
} }
async checkForSingleOrganizationPolicy(): Promise<boolean> { async checkForSingleOrganizationPolicy(): Promise<boolean> {
return await this.policyService.policyAppliesToUser(PolicyType.SingleOrg); return await firstValueFrom(
this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg)
);
} }
async checkForPersonalOwnershipPolicy(): Promise<boolean> { async checkForPersonalOwnershipPolicy(): Promise<boolean> {
return await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership); return await firstValueFrom(
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
);
} }
protected async getAllFoldersNested(folders: FolderView[]): Promise<TreeNode<FolderView>[]> { protected async getAllFoldersNested(folders: FolderView[]): Promise<TreeNode<FolderView>[]> {

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Substitute, Arg, SubstituteOf } from "@fluffy-spoon/substitute"; import { Substitute, Arg, SubstituteOf } from "@fluffy-spoon/substitute";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Substitute, Arg } from "@fluffy-spoon/substitute"; import { Substitute, Arg } from "@fluffy-spoon/substitute";
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Substitute, Arg } from "@fluffy-spoon/substitute"; import { Substitute, Arg } from "@fluffy-spoon/substitute";
import { mock, MockProxy } from "jest-mock-extended"; import { mock, MockProxy } from "jest-mock-extended";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Substitute, Arg } from "@fluffy-spoon/substitute"; import { Substitute, Arg } from "@fluffy-spoon/substitute";
import { UriMatchType } from "@bitwarden/common/enums/uriMatchType"; import { UriMatchType } from "@bitwarden/common/enums/uriMatchType";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Substitute, Arg, SubstituteOf } from "@fluffy-spoon/substitute"; import { Substitute, Arg, SubstituteOf } from "@fluffy-spoon/substitute";
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Substitute, Arg } from "@fluffy-spoon/substitute"; import { Substitute, Arg } from "@fluffy-spoon/substitute";
import { SendType } from "@bitwarden/common/enums/sendType"; import { SendType } from "@bitwarden/common/enums/sendType";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { BehaviorSubject, firstValueFrom } from "rxjs"; import { BehaviorSubject, firstValueFrom } from "rxjs";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";

View File

@@ -0,0 +1,357 @@
// eslint-disable-next-line no-restricted-imports
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
import { PolicyType } from "@bitwarden/common/enums/policyType";
import { PermissionsApi } from "@bitwarden/common/models/api/permissionsApi";
import { OrganizationData } from "@bitwarden/common/models/data/organizationData";
import { PolicyData } from "@bitwarden/common/models/data/policyData";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { Policy } from "@bitwarden/common/models/domain/policy";
import { ResetPasswordPolicyOptions } from "@bitwarden/common/models/domain/resetPasswordPolicyOptions";
import { ListResponse } from "@bitwarden/common/models/response/listResponse";
import { PolicyResponse } from "@bitwarden/common/models/response/policyResponse";
import { ContainerService } from "@bitwarden/common/services/container.service";
import { EncryptService } from "@bitwarden/common/services/encrypt.service";
import { PolicyService } from "@bitwarden/common/services/policy/policy.service";
import { StateService } from "@bitwarden/common/services/state.service";
describe("PolicyService", () => {
let policyService: PolicyService;
let cryptoService: SubstituteOf<CryptoService>;
let stateService: SubstituteOf<StateService>;
let organizationService: SubstituteOf<OrganizationService>;
let encryptService: SubstituteOf<EncryptService>;
let activeAccount: BehaviorSubject<string>;
let activeAccountUnlocked: BehaviorSubject<boolean>;
beforeEach(() => {
stateService = Substitute.for();
organizationService = Substitute.for();
organizationService
.getAll("user")
.resolves([
new Organization(
organizationData(
"test-organization",
true,
true,
OrganizationUserStatusType.Accepted,
false
)
),
]);
organizationService.getAll(undefined).resolves([]);
organizationService.getAll(null).resolves([]);
activeAccount = new BehaviorSubject("123");
activeAccountUnlocked = new BehaviorSubject(true);
stateService.getEncryptedPolicies().resolves({
"1": policyData("1", "test-organization", PolicyType.MaximumVaultTimeout, true, {
minutes: 14,
}),
});
stateService.activeAccount$.returns(activeAccount);
stateService.activeAccountUnlocked$.returns(activeAccountUnlocked);
stateService.getUserId().resolves("user");
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
policyService = new PolicyService(stateService, organizationService);
});
afterEach(() => {
activeAccount.complete();
activeAccountUnlocked.complete();
});
it("upsert", async () => {
await policyService.upsert(policyData("99", "test-organization", PolicyType.DisableSend, true));
expect(await firstValueFrom(policyService.policies$)).toEqual([
{
id: "1",
organizationId: "test-organization",
type: PolicyType.MaximumVaultTimeout,
enabled: true,
data: { minutes: 14 },
},
{
id: "99",
organizationId: "test-organization",
type: PolicyType.DisableSend,
enabled: true,
},
]);
});
it("replace", async () => {
await policyService.replace({
"2": policyData("2", "test-organization", PolicyType.DisableSend, true),
});
expect(await firstValueFrom(policyService.policies$)).toEqual([
{
id: "2",
organizationId: "test-organization",
type: PolicyType.DisableSend,
enabled: true,
},
]);
});
it("locking should clear", async () => {
activeAccountUnlocked.next(false);
// Sleep for 100ms to avoid timing issues
await new Promise((r) => setTimeout(r, 100));
expect((await firstValueFrom(policyService.policies$)).length).toBe(0);
});
describe("clear", () => {
it("null userId", async () => {
await policyService.clear();
stateService.received(1).setEncryptedPolicies(Arg.any(), Arg.any());
expect((await firstValueFrom(policyService.policies$)).length).toBe(0);
});
it("matching userId", async () => {
await policyService.clear("user");
stateService.received(1).setEncryptedPolicies(Arg.any(), Arg.any());
expect((await firstValueFrom(policyService.policies$)).length).toBe(0);
});
it("mismatching userId", async () => {
await policyService.clear("12");
stateService.received(1).setEncryptedPolicies(Arg.any(), Arg.any());
expect((await firstValueFrom(policyService.policies$)).length).toBe(1);
});
});
describe("masterPasswordPolicyOptions", () => {
it("returns default policy options", async () => {
const data: any = {
minComplexity: 5,
minLength: 20,
requireUpper: true,
};
const model = [
new Policy(policyData("1", "test-organization-3", PolicyType.MasterPassword, true, data)),
];
const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(model));
expect(result).toEqual({
minComplexity: 5,
minLength: 20,
requireLower: false,
requireNumbers: false,
requireSpecial: false,
requireUpper: true,
});
});
it("returns null", async () => {
const data: any = {};
const model = [
new Policy(
policyData("3", "test-organization-3", PolicyType.DisablePersonalVaultExport, true, data)
),
new Policy(
policyData("4", "test-organization-3", PolicyType.MaximumVaultTimeout, true, data)
),
];
const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(model));
expect(result).toEqual(null);
});
it("returns specified policy options", async () => {
const data: any = {
minLength: 14,
};
const model = [
new Policy(
policyData("3", "test-organization-3", PolicyType.DisablePersonalVaultExport, true, data)
),
new Policy(policyData("4", "test-organization-3", PolicyType.MasterPassword, true, data)),
];
const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(model));
expect(result).toEqual({
minComplexity: 0,
minLength: 14,
requireLower: false,
requireNumbers: false,
requireSpecial: false,
requireUpper: false,
});
});
});
describe("evaluateMasterPassword", () => {
it("false", async () => {
const enforcedPolicyOptions = new MasterPasswordPolicyOptions();
enforcedPolicyOptions.minLength = 14;
const result = policyService.evaluateMasterPassword(10, "password", enforcedPolicyOptions);
expect(result).toEqual(false);
});
it("true", async () => {
const enforcedPolicyOptions = new MasterPasswordPolicyOptions();
const result = policyService.evaluateMasterPassword(0, "password", enforcedPolicyOptions);
expect(result).toEqual(true);
});
});
describe("getResetPasswordPolicyOptions", () => {
it("default", async () => {
const result = policyService.getResetPasswordPolicyOptions(null, null);
expect(result).toEqual([new ResetPasswordPolicyOptions(), false]);
});
it("returns autoEnrollEnabled true", async () => {
const data: any = {
autoEnrollEnabled: true,
};
const policies = [
new Policy(policyData("5", "test-organization-3", PolicyType.ResetPassword, true, data)),
];
const result = policyService.getResetPasswordPolicyOptions(policies, "test-organization-3");
expect(result).toEqual([{ autoEnrollEnabled: true }, true]);
});
});
describe("mapPoliciesFromToken", () => {
it("null", async () => {
const result = policyService.mapPoliciesFromToken(null);
expect(result).toEqual(null);
});
it("null data", async () => {
const model = new ListResponse(null, PolicyResponse);
model.data = null;
const result = policyService.mapPoliciesFromToken(model);
expect(result).toEqual(null);
});
it("empty array", async () => {
const model = new ListResponse(null, PolicyResponse);
const result = policyService.mapPoliciesFromToken(model);
expect(result).toEqual([]);
});
it("success", async () => {
const policyResponse: any = {
Data: [
{
Id: "1",
OrganizationId: "organization-1",
Type: PolicyType.DisablePersonalVaultExport,
Enabled: true,
Data: { requireUpper: true },
},
{
Id: "2",
OrganizationId: "organization-2",
Type: PolicyType.DisableSend,
Enabled: false,
Data: { minComplexity: 5, minLength: 20 },
},
],
};
const model = new ListResponse(policyResponse, PolicyResponse);
const result = policyService.mapPoliciesFromToken(model);
expect(result).toEqual([
new Policy(
policyData("1", "organization-1", PolicyType.DisablePersonalVaultExport, true, {
requireUpper: true,
})
),
new Policy(
policyData("2", "organization-2", PolicyType.DisableSend, false, {
minComplexity: 5,
minLength: 20,
})
),
]);
});
});
describe("policyAppliesToActiveUser", () => {
it("MasterPassword does not apply", async () => {
const result = await firstValueFrom(
policyService.policyAppliesToActiveUser$(PolicyType.MasterPassword)
);
expect(result).toEqual(false);
});
it("MaximumVaultTimeout applies", async () => {
const result = await firstValueFrom(
policyService.policyAppliesToActiveUser$(PolicyType.MaximumVaultTimeout)
);
expect(result).toEqual(true);
});
it("DisablePersonalVaultExport does not apply", async () => {
const result = await firstValueFrom(
policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport)
);
expect(result).toEqual(false);
});
});
function policyData(
id: string,
organizationId: string,
type: PolicyType,
enabled: boolean,
data?: any
) {
const policyData = new PolicyData({} as any);
policyData.id = id;
policyData.organizationId = organizationId;
policyData.type = type;
policyData.enabled = enabled;
policyData.data = data;
return policyData;
}
function organizationData(
id: string,
enabled: boolean,
usePolicies: boolean,
status: OrganizationUserStatusType,
managePolicies: boolean
) {
const organizationData = new OrganizationData({} as any);
organizationData.id = id;
organizationData.enabled = enabled;
organizationData.usePolicies = usePolicies;
organizationData.status = status;
organizationData.permissions = new PermissionsApi({ managePolicies: managePolicies } as any);
return organizationData;
}
});

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { BehaviorSubject, firstValueFrom } from "rxjs"; import { BehaviorSubject, firstValueFrom } from "rxjs";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Substitute, Arg } from "@fluffy-spoon/substitute"; import { Substitute, Arg } from "@fluffy-spoon/substitute";
import { EncString } from "@bitwarden/common/models/domain/encString"; import { EncString } from "@bitwarden/common/models/domain/encString";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { Substitute } from "@fluffy-spoon/substitute"; import { Substitute } from "@fluffy-spoon/substitute";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";

View File

@@ -1,6 +1,5 @@
import { PolicyType } from "../../enums/policyType"; import { PolicyType } from "../../enums/policyType";
import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions"; import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions";
import { Policy } from "../../models/domain/policy";
import { PolicyRequest } from "../../models/request/policyRequest"; import { PolicyRequest } from "../../models/request/policyRequest";
import { ListResponse } from "../../models/response/listResponse"; import { ListResponse } from "../../models/response/listResponse";
import { PolicyResponse } from "../../models/response/policyResponse"; import { PolicyResponse } from "../../models/response/policyResponse";
@@ -18,7 +17,6 @@ export class PolicyApiServiceAbstraction {
organizationId: string, organizationId: string,
userId: string userId: string
) => Promise<ListResponse<PolicyResponse>>; ) => Promise<ListResponse<PolicyResponse>>;
getPolicyForOrganization: (policyType: PolicyType, organizationId: string) => Promise<Policy>;
getMasterPasswordPoliciesForInvitedUsers: (orgId: string) => Promise<MasterPasswordPolicyOptions>; getMasterPasswordPoliciesForInvitedUsers: (orgId: string) => Promise<MasterPasswordPolicyOptions>;
putPolicy: (organizationId: string, type: PolicyType, request: PolicyRequest) => Promise<any>; putPolicy: (organizationId: string, type: PolicyType, request: PolicyRequest) => Promise<any>;
} }

View File

@@ -1,3 +1,5 @@
import { Observable } from "rxjs";
import { PolicyType } from "../../enums/policyType"; import { PolicyType } from "../../enums/policyType";
import { PolicyData } from "../../models/data/policyData"; import { PolicyData } from "../../models/data/policyData";
import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions"; import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions";
@@ -7,9 +9,17 @@ import { ListResponse } from "../../models/response/listResponse";
import { PolicyResponse } from "../../models/response/policyResponse"; import { PolicyResponse } from "../../models/response/policyResponse";
export abstract class PolicyService { export abstract class PolicyService {
getAll: (type?: PolicyType, userId?: string) => Promise<Policy[]>; policies$: Observable<Policy[]>;
masterPasswordPolicyOptions$: (policies?: Policy[]) => Observable<MasterPasswordPolicyOptions>;
policyAppliesToActiveUser$: (
policyType: PolicyType,
policyFilter?: (policy: Policy) => boolean
) => Observable<boolean>;
getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise<MasterPasswordPolicyOptions>; /**
* @deprecated Do not call this, use the policies$ observable collection
*/
getAll: (type?: PolicyType, userId?: string) => Promise<Policy[]>;
evaluateMasterPassword: ( evaluateMasterPassword: (
passwordStrength: number, passwordStrength: number,
newPassword: string, newPassword: string,
@@ -29,6 +39,6 @@ export abstract class PolicyService {
export abstract class InternalPolicyService extends PolicyService { export abstract class InternalPolicyService extends PolicyService {
upsert: (policy: PolicyData) => Promise<any>; upsert: (policy: PolicyData) => Promise<any>;
replace: (policies: { [id: string]: PolicyData }) => Promise<any>; replace: (policies: { [id: string]: PolicyData }) => Promise<void>;
clear: (userId?: string) => Promise<any>; clear: (userId?: string) => Promise<any>;
} }

View File

@@ -103,7 +103,13 @@ export abstract class StateService<T extends Account = Account> {
) => Promise<void>; ) => Promise<void>;
getDecryptedPinProtected: (options?: StorageOptions) => Promise<EncString>; getDecryptedPinProtected: (options?: StorageOptions) => Promise<EncString>;
setDecryptedPinProtected: (value: EncString, options?: StorageOptions) => Promise<void>; setDecryptedPinProtected: (value: EncString, options?: StorageOptions) => Promise<void>;
/**
* @deprecated Do not call this, use PolicyService
*/
getDecryptedPolicies: (options?: StorageOptions) => Promise<Policy[]>; getDecryptedPolicies: (options?: StorageOptions) => Promise<Policy[]>;
/**
* @deprecated Do not call this, use PolicyService
*/
setDecryptedPolicies: (value: Policy[], options?: StorageOptions) => Promise<void>; setDecryptedPolicies: (value: Policy[], options?: StorageOptions) => Promise<void>;
getDecryptedPrivateKey: (options?: StorageOptions) => Promise<ArrayBuffer>; getDecryptedPrivateKey: (options?: StorageOptions) => Promise<ArrayBuffer>;
setDecryptedPrivateKey: (value: ArrayBuffer, options?: StorageOptions) => Promise<void>; setDecryptedPrivateKey: (value: ArrayBuffer, options?: StorageOptions) => Promise<void>;
@@ -214,7 +220,13 @@ export abstract class StateService<T extends Account = Account> {
) => Promise<void>; ) => Promise<void>;
getEncryptedPinProtected: (options?: StorageOptions) => Promise<string>; getEncryptedPinProtected: (options?: StorageOptions) => Promise<string>;
setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>; setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>;
/**
* @deprecated Do not call this directly, use PolicyService
*/
getEncryptedPolicies: (options?: StorageOptions) => Promise<{ [id: string]: PolicyData }>; getEncryptedPolicies: (options?: StorageOptions) => Promise<{ [id: string]: PolicyData }>;
/**
* @deprecated Do not call this directly, use PolicyService
*/
setEncryptedPolicies: ( setEncryptedPolicies: (
value: { [id: string]: PolicyData }, value: { [id: string]: PolicyData },
options?: StorageOptions options?: StorageOptions

View File

@@ -0,0 +1,3 @@
export abstract class ValidationService {
showError: (data: any) => string[];
}

View File

@@ -339,6 +339,12 @@ export class Utils {
return str == null || typeof str !== "string" || str == ""; return str == null || typeof str !== "string" || str == "";
} }
static isPromise(obj: any): obj is Promise<unknown> {
return (
obj != undefined && typeof obj["then"] === "function" && typeof obj["catch"] === "function"
);
}
static nameOf<T>(name: string & keyof T) { static nameOf<T>(name: string & keyof T) {
return name; return name;
} }

View File

@@ -1,24 +0,0 @@
import { EventService } from "../abstractions/event.service";
import { EventType } from "../enums/eventType";
/**
* If you want to use this, don't.
* If you think you should use that after the warning, don't.
*/
export class NoopEventService implements EventService {
constructor() {
if (chrome.runtime.getManifest().manifest_version !== 3) {
throw new Error("You are not allowed to use this when not in manifest_version 3");
}
}
collect(eventType: EventType, cipherId?: string, uploadImmediately?: boolean) {
return Promise.resolve();
}
uploadEvents(userId?: string) {
return Promise.resolve();
}
clearEvents(userId?: string) {
return Promise.resolve();
}
}

View File

@@ -1,3 +1,4 @@
import { firstValueFrom, map } from "rxjs";
import * as zxcvbn from "zxcvbn"; import * as zxcvbn from "zxcvbn";
import { CryptoService } from "../abstractions/crypto.service"; import { CryptoService } from "../abstractions/crypto.service";
@@ -258,7 +259,11 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
const policies: Policy[] = const policies: Policy[] =
this.policyService == null this.policyService == null
? null ? null
: await this.policyService.getAll(PolicyType.PasswordGenerator); : await firstValueFrom(
this.policyService.policies$.pipe(
map((p) => p.filter((policy) => policy.type === PolicyType.PasswordGenerator))
)
);
let enforcedOptions: PasswordGeneratorPolicyOptions = null; let enforcedOptions: PasswordGeneratorPolicyOptions = null;
if (policies == null || policies.length === 0) { if (policies == null || policies.length === 0) {

View File

@@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
import { ApiService } from "../../abstractions/api.service"; import { ApiService } from "../../abstractions/api.service";
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
import { PolicyApiServiceAbstraction } from "../../abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "../../abstractions/policy/policy-api.service.abstraction";
@@ -6,7 +8,6 @@ import { StateService } from "../../abstractions/state.service";
import { PolicyType } from "../../enums/policyType"; import { PolicyType } from "../../enums/policyType";
import { PolicyData } from "../../models/data/policyData"; import { PolicyData } from "../../models/data/policyData";
import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions"; import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions";
import { Policy } from "../../models/domain/policy";
import { PolicyRequest } from "../../models/request/policyRequest"; import { PolicyRequest } from "../../models/request/policyRequest";
import { ListResponse } from "../../models/response/listResponse"; import { ListResponse } from "../../models/response/listResponse";
import { PolicyResponse } from "../../models/response/policyResponse"; import { PolicyResponse } from "../../models/response/policyResponse";
@@ -79,30 +80,13 @@ export class PolicyApiService implements PolicyApiServiceAbstraction {
return new ListResponse(r, PolicyResponse); return new ListResponse(r, PolicyResponse);
} }
async getPolicyForOrganization(policyType: PolicyType, organizationId: string): Promise<Policy> {
const org = await this.organizationService.get(organizationId);
if (org?.isProviderUser) {
const orgPolicies = await this.getPolicies(organizationId);
const policy = orgPolicies.data.find((p) => p.organizationId === organizationId);
if (policy == null) {
return null;
}
return new Policy(new PolicyData(policy));
}
const policies = await this.policyService.getAll(policyType);
return policies.find((p) => p.organizationId === organizationId);
}
async getMasterPasswordPoliciesForInvitedUsers( async getMasterPasswordPoliciesForInvitedUsers(
orgId: string orgId: string
): Promise<MasterPasswordPolicyOptions> { ): Promise<MasterPasswordPolicyOptions> {
const userId = await this.stateService.getUserId(); const userId = await this.stateService.getUserId();
const response = await this.getPoliciesByInvitedUser(orgId, userId); const response = await this.getPoliciesByInvitedUser(orgId, userId);
const policies = await this.policyService.mapPoliciesFromToken(response); const policies = await this.policyService.mapPoliciesFromToken(response);
return this.policyService.getMasterPasswordPolicyOptions(policies); return await firstValueFrom(this.policyService.masterPasswordPolicyOptions$(policies));
} }
async putPolicy(organizationId: string, type: PolicyType, request: PolicyRequest): Promise<any> { async putPolicy(organizationId: string, type: PolicyType, request: PolicyRequest): Promise<any> {

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