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:
@@ -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": [
|
||||||
{
|
{
|
||||||
|
|||||||
6
.github/workflows/build-web.yml
vendored
6
.github/workflows/build-web.yml
vendored
@@ -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
|
||||||
|
|||||||
4
.github/workflows/release-browser.yml
vendored
4
.github/workflows/release-browser.yml
vendored
@@ -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
|
||||||
|
|||||||
16
.github/workflows/release-cli.yml
vendored
16
.github/workflows/release-cli.yml
vendored
@@ -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
|
||||||
|
|||||||
20
.github/workflows/release-desktop-beta.yml
vendored
20
.github/workflows/release-desktop-beta.yml
vendored
@@ -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
|
||||||
|
|||||||
102
.github/workflows/release-desktop.yml
vendored
102
.github/workflows/release-desktop.yml
vendored
@@ -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
|
||||||
|
|||||||
8
.github/workflows/release-web.yml
vendored
8
.github/workflows/release-web.yml
vendored
@@ -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
|
||||||
|
|||||||
123
.github/workflows/staged-rollout-desktop.yml
vendored
Normal file
123
.github/workflows/staged-rollout-desktop.yml
vendored
Normal 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 hasn’t 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
|
||||||
30
.github/workflows/version-auto-bump.yml
vendored
30
.github/workflows/version-auto-bump.yml
vendored
@@ -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:
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -172,14 +172,10 @@ 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) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cipher.reprompt !== CipherRepromptType.None) {
|
if (cipher == null || cipher.reprompt !== CipherRepromptType.None) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const totpCode = await this.doAutoFill({
|
const totpCode = await this.doAutoFill({
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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."
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
"**/organizations/policies/*",
|
"**/organizations/policies/*",
|
||||||
"@bitwarden/web-vault/*",
|
"@bitwarden/web-vault/*",
|
||||||
"src/**/*"
|
"src/**/*"
|
||||||
]
|
],
|
||||||
|
"paths": ["@fluffy-spoon/substitute"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,14 +121,29 @@ 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 (this.onSuccessfulLogin != null) {
|
|
||||||
this.onSuccessfulLogin();
|
if (loginResponse.requiresTwoFactor) {
|
||||||
}
|
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
|
||||||
if (this.onSuccessfulLoginNavigate != null) {
|
this.onSuccessfulLoginTwoFactorNavigate();
|
||||||
this.onSuccessfulLoginNavigate();
|
} else {
|
||||||
|
this.router.navigate([this.twoFactorRoute]);
|
||||||
|
}
|
||||||
|
} else if (loginResponse.forcePasswordReset) {
|
||||||
|
if (this.onSuccessfulLoginForceResetNavigate != null) {
|
||||||
|
this.onSuccessfulLoginForceResetNavigate();
|
||||||
|
} else {
|
||||||
|
this.router.navigate([this.forcePasswordResetRoute]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate([this.successRoute]);
|
if (this.onSuccessfulLogin != null) {
|
||||||
|
this.onSuccessfulLogin();
|
||||||
|
}
|
||||||
|
if (this.onSuccessfulLoginNavigate != null) {
|
||||||
|
this.onSuccessfulLoginNavigate();
|
||||||
|
} else {
|
||||||
|
this.router.navigate([this.successRoute]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logService.error(error);
|
this.logService.error(error);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(
|
||||||
minComplexity: 4,
|
of({
|
||||||
minLength: 10,
|
minComplexity: 4,
|
||||||
requireLower: null,
|
minLength: 10,
|
||||||
requireNumbers: null,
|
requireLower: null,
|
||||||
requireSpecial: null,
|
requireNumbers: null,
|
||||||
requireUpper: null,
|
requireSpecial: null,
|
||||||
} as MasterPasswordPolicyOptions);
|
requireUpper: null,
|
||||||
|
} 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);
|
||||||
|
|||||||
@@ -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 === "") {
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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,53 +111,63 @@ 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(
|
||||||
this.organizationId = params.organizationId;
|
concatMap(async ([params, qParams, policies]) => {
|
||||||
const organization = await this.organizationService.get(this.organizationId);
|
this.organizationId = params.organizationId;
|
||||||
this.accessEvents = organization.useEvents;
|
const organization = await this.organizationService.get(this.organizationId);
|
||||||
this.accessGroups = organization.useGroups;
|
this.accessEvents = organization.useEvents;
|
||||||
this.canResetPassword = organization.canManageUsersPassword;
|
this.accessGroups = organization.useGroups;
|
||||||
this.orgUseResetPassword = organization.useResetPassword;
|
this.canResetPassword = organization.canManageUsersPassword;
|
||||||
this.callingUserType = organization.type;
|
this.orgUseResetPassword = organization.useResetPassword;
|
||||||
this.orgHasKeys = organization.hasPublicAndPrivateKeys;
|
this.callingUserType = organization.type;
|
||||||
|
this.orgHasKeys = organization.hasPublicAndPrivateKeys;
|
||||||
|
|
||||||
// Backfill pub/priv key if necessary
|
// Backfill pub/priv key if necessary
|
||||||
if (this.canResetPassword && !this.orgHasKeys) {
|
if (this.canResetPassword && !this.orgHasKeys) {
|
||||||
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(
|
||||||
if (response != null) {
|
this.organizationId,
|
||||||
this.orgHasKeys = response.publicKey != null && response.privateKey != null;
|
request
|
||||||
await this.syncService.fullSync(true); // Replace oganizations with new data
|
);
|
||||||
} else {
|
if (response != null) {
|
||||||
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
|
this.orgHasKeys = response.publicKey != null && response.privateKey != null;
|
||||||
}
|
await this.syncService.fullSync(true); // Replace oganizations with new data
|
||||||
}
|
} else {
|
||||||
|
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
|
||||||
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;
|
|
||||||
if (qParams.viewEvents != null) {
|
|
||||||
const user = this.users.filter((u) => u.id === qParams.viewEvents);
|
|
||||||
if (user.length > 0 && user[0].status === OrganizationUserStatusType.Confirmed) {
|
|
||||||
this.events(user[0]);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
const resetPasswordPolicy = policies
|
||||||
});
|
.filter((policy) => policy.type === PolicyType.ResetPassword)
|
||||||
|
.find((p) => p.organizationId === this.organizationId);
|
||||||
|
this.orgResetPasswordPolicyEnabled = resetPasswordPolicy?.enabled;
|
||||||
|
|
||||||
|
await this.load();
|
||||||
|
|
||||||
|
this.searchText = qParams.search;
|
||||||
|
if (qParams.viewEvents != null) {
|
||||||
|
const user = this.users.filter((u) => u.id === qParams.viewEvents);
|
||||||
|
if (user.length > 0 && user[0].status === OrganizationUserStatusType.Confirmed) {
|
||||||
|
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>> {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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() {
|
||||||
await this.init();
|
this.policyService
|
||||||
|
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
|
||||||
|
.pipe(
|
||||||
|
concatMap(async (policyAppliesToActiveUser) => {
|
||||||
|
this.personalOwnershipPolicyAppliesToActiveUser = policyAppliesToActiveUser;
|
||||||
|
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();
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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(
|
||||||
await this.load();
|
concatMap(async () => {
|
||||||
});
|
await this.load();
|
||||||
|
}),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.activeAccountSubscription.unsubscribe();
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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$))
|
||||||
|
.subscribe(([policyAppliesToActiveUser, policies]) => {
|
||||||
|
if (policyAppliesToActiveUser) {
|
||||||
|
const vaultTimeoutPolicy = policies.find(
|
||||||
|
(policy) => policy.type === PolicyType.MaximumVaultTimeout && policy.enabled
|
||||||
|
);
|
||||||
|
|
||||||
this.vaultTimeoutPolicy = vaultTimeoutPolicy[0];
|
this.vaultTimeoutPolicy = vaultTimeoutPolicy;
|
||||||
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
|
this.applyVaultTimeoutPolicy();
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>[]> {
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
357
libs/common/spec/services/policy.service.spec.ts
Normal file
357
libs/common/spec/services/policy.service.spec.ts
Normal 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
3
libs/common/src/abstractions/validation.service.ts
Normal file
3
libs/common/src/abstractions/validation.service.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export abstract class ValidationService {
|
||||||
|
showError: (data: any) => string[];
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user