1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-21 11:53:34 +00:00

Merge branch 'feature/org-admin-refresh' into EC-86-groups-table

This commit is contained in:
Shane Melton
2022-10-17 17:10:01 -07:00
371 changed files with 8445 additions and 2317 deletions

View File

@@ -73,6 +73,10 @@
{
"message": "Calling `svgIcon` directly is not allowed",
"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"],
@@ -96,6 +100,42 @@
}
]
}
],
"no-restricted-imports": [
"error",
{ "patterns": ["src/**/*"], "paths": ["@fluffy-spoon/substitute"] }
]
}
},
"overrides": [
{
"files": ["libs/common/src/**/*.ts"],
"rules": {
"no-restricted-imports": ["error", { "patterns": ["@bitwarden/common/*", "src/**/*"] }]
}
},
{
"files": ["libs/components/src/**/*.ts"],
"rules": {
"no-restricted-imports": ["error", { "patterns": ["@bitwarden/components/*", "src/**/*"] }]
}
},
{
"files": ["libs/angular/src/**/*.ts"],
"rules": {
"no-restricted-imports": ["error", { "patterns": ["@bitwarden/angular/*", "src/**/*"] }]
}
},
{
"files": ["libs/node/src/**/*.ts"],
"rules": {
"no-restricted-imports": ["error", { "patterns": ["@bitwarden/node/*", "src/**/*"] }]
}
},
{
"files": ["libs/electron/src/**/*.ts"],
"rules": {
"no-restricted-imports": ["error", { "patterns": ["@bitwarden/electron/*", "src/**/*"] }]
}
}
]
}

View File

@@ -45,14 +45,14 @@ jobs:
Please contact us using our [Contact page](https://bitwarden.com/contact). You can include a link to this issue in the message content.
Alternatively, you can also search for an answer in our [help documentation](https://bitwarden.com/help/) or get help from other Bitwarden users on our [community forums](https://community.bitwarden.com/c/support/). The issue here will be closed.
Alternatively, you can also search for an answer in our [help documentation](https://bitwarden.com/help/) or get help from other Bitwarden users on our [community forums](https://community.bitwarden.com/c/support/). The issue here will now be closed.
# Resolved
- if: github.event.label.name == 'resolved'
name: Resolved
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with:
comment: |
Weve closed this issue, as it appears the original problem has been resolved. If this happens again or continues to be an problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis.
Weve closed this issue, as it appears the original problem has been resolved. If this happens again or continues to be a problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis.
# Stale
- if: github.event.label.name == 'stale'
name: Stale

View File

@@ -1,16 +0,0 @@
---
name: Build Web for EE
on:
workflow_dispatch:
jobs:
stub:
name: stub
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Stub
run: print 'This is only a stub'

View File

@@ -80,6 +80,8 @@ jobs:
npm_command: "dist:bit:selfhost"
- name: "cloud-QA"
npm_command: "build:bit:qa"
- name: "ee"
npm_command: "build:bit:ee"
steps:
- name: Checkout repo
@@ -234,12 +236,20 @@ jobs:
- name: Log out of Docker
run: docker logout
build-qa:
name: Build Docker images for QA environment
build-containers:
name: Build Docker images for bitwardenqa
runs-on: ubuntu-22.04
needs:
- setup
- build-artifacts
strategy:
fail-fast: false
matrix:
include:
- artifact_name: cloud-QA
image_name: web
- artifact_name: ee
image_name: web-ee
env:
_VERSION: ${{ needs.setup.outputs.version }}
steps:
@@ -254,17 +264,17 @@ jobs:
- name: Log into container registry
run: az acr login -n bitwardenqa
- name: Download cloud-QA artifact
- name: Download ${{ matrix.artifact_name }} artifact
uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741
with:
name: web-${{ env._VERSION }}-cloud-QA.zip
name: web-${{ env._VERSION }}-${{ matrix.artifact_name }}.zip
path: apps/web/build
- name: Build Docker image
working-directory: apps/web
run: |
docker --version
docker build -t bitwardenqa.azurecr.io/web .
docker build -t bitwardenqa.azurecr.io/${{ matrix.image_name }} .
- name: Get image tag
id: image-tag
@@ -286,25 +296,30 @@ jobs:
- name: Tag image
env:
IMAGE_TAG: ${{ steps.image-tag.outputs.value }}
run: docker tag bitwardenqa.azurecr.io/web "bitwardenqa.azurecr.io/web:$IMAGE_TAG"
IMAGE_NAME: ${{ matrix.image_name }}
run: docker tag bitwardenqa.azurecr.io/$IMAGE_NAME "bitwardenqa.azurecr.io/$IMAGE_NAME:$IMAGE_TAG"
- name: Tag dev
if: github.ref == 'refs/heads/master'
run: docker tag bitwardenqa.azurecr.io/web bitwardenqa.azurecr.io/web:dev
env:
IMAGE_NAME: ${{ matrix.image_name }}
run: docker tag bitwardenqa.azurecr.io/$IMAGE_NAME "bitwardenqa.azurecr.io/$IMAGE_NAME:dev"
- name: Push image
env:
IMAGE_TAG: ${{ steps.image-tag.outputs.value }}
run: docker push "bitwardenqa.azurecr.io/web:$IMAGE_TAG"
IMAGE_NAME: ${{ matrix.image_name }}
run: docker push "bitwardenqa.azurecr.io/$IMAGE_NAME:$IMAGE_TAG"
- name: Push dev images
if: github.ref == 'refs/heads/master'
run: docker push bitwardenqa.azurecr.io/web:dev
env:
IMAGE_NAME: ${{ matrix.image_name }}
run: docker push "bitwardenqa.azurecr.io/$IMAGE_NAME:dev"
- name: Log out of Docker
run: docker logout
crowdin-push:
name: Crowdin Push
if: github.ref == 'refs/heads/master'
@@ -356,7 +371,7 @@ jobs:
- setup
- build-artifacts
- build-commercial-selfhost-image
- build-qa
- build-containers
- crowdin-push
steps:
- name: Check if any job failed
@@ -366,7 +381,7 @@ jobs:
SETUP_STATUS: ${{ needs.setup.result }}
ARTIFACT_STATUS: ${{ needs.build-artifacts.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 }}
run: |
if [ "$CLOC_STATUS" = "failure" ]; then
@@ -377,7 +392,7 @@ jobs:
exit 1
elif [ "$BUILD_SELFHOST_STATUS" = "failure" ]; then
exit 1
elif [ "$BUILD_QA_STATUS" = "failure" ]; then
elif [ "$BUILD_CONTAINERS_STATUS" = "failure" ]; then
exit 1
elif [ "$CROWDIN_PUSH_STATUS" = "failure" ]; then
exit 1

View File

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

View File

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

View File

@@ -944,7 +944,11 @@ jobs:
SECRETS: |
aws-electron-access-id,
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: |
for i in ${SECRETS//,/ }
do
@@ -977,6 +981,20 @@ jobs:
--recursive \
--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
if: ${{ success() }}
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86

View File

@@ -13,13 +13,18 @@ on:
- Initial Release
- Redeploy
- Dry Run
rollout_percentage:
description: 'Staged Rollout Percentage'
required: true
default: '10'
type: string
snap_publish:
description: 'Publish to snap store'
description: 'Publish to Snap store'
required: true
default: true
type: boolean
choco_publish:
description: 'Publish to chocolatey store'
description: 'Publish to Chocolatey store'
required: true
default: true
type: boolean
@@ -93,23 +98,20 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
env:
KEYVAULT: bitwarden-prod-kv
SECRETS: |
aws-electron-access-id,
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
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
aws-electron-bucket-name,
r2-electron-access-id,
r2-electron-access-key,
r2-electron-bucket-name,
cf-prod-account"
- name: Download all artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with:
workflow: build-desktop.yml
workflow_conclusion: success
@@ -118,7 +120,7 @@ jobs:
- name: Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with:
workflow: build-desktop.yml
workflow_conclusion: success
@@ -131,6 +133,15 @@ jobs:
working-directory: apps/desktop/artifacts
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
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
@@ -145,8 +156,23 @@ jobs:
--recursive \
--quiet
- name: Create release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
- name: Publish artifacts to R2
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' }}
env:
PKG_VERSION: ${{ steps.version.outputs.version }}
@@ -217,17 +243,10 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
env:
KEYVAULT: bitwarden-prod-kv
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
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
with:
keyvault: "bitwarden-prod-kv"
secrets: "snapcraft-store-token"
- name: Install Snap
uses: samuelmeuli/action-snapcraft@10d7d0a84d9d86098b19f872257df314b0bd8e2d # v1.2.0
@@ -240,7 +259,7 @@ jobs:
- name: Download Snap artifact
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with:
workflow: build-desktop.yml
workflow_conclusion: success
@@ -250,7 +269,7 @@ jobs:
- name: Download Snap artifact
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with:
workflow: build-desktop.yml
workflow_conclusion: success
@@ -274,7 +293,7 @@ jobs:
_PKG_VERSION: ${{ needs.setup.outputs.release-version }}
steps:
- name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Print Environment
run: |
@@ -288,17 +307,10 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
env:
KEYVAULT: bitwarden-prod-kv
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
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
with:
keyvault: "bitwarden-prod-kv"
secrets: "cli-choco-api-key"
- name: Setup Chocolatey
shell: pwsh
@@ -313,7 +325,7 @@ jobs:
- name: Download choco artifact
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with:
workflow: build-desktop.yml
workflow_conclusion: success
@@ -323,7 +335,7 @@ jobs:
- name: Download choco artifact
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with:
workflow: build-desktop.yml
workflow_conclusion: success

View File

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

View File

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

View File

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

1
.gitignore vendored
View File

@@ -40,3 +40,4 @@ coverage
# Storybook
documentation.json
.eslintcache
storybook-static

View File

@@ -38,7 +38,7 @@
"message": "Асноўны пароль"
},
"masterPassDesc": {
"message": "Асноўны пароль — ключ да вашага бяспечнага сховішча. Ён вельмі важны, таму не забывайце яго. Аднавіць асноўны пароль немагчыма."
"message": "Асноўны пароль — гэта ключ, які выкарыстоўваецца для доступу да вашага сховішча. Ён вельмі важны, таму не забывайце яго. Аднавіць асноўны пароль немагчыма ў выпадку, калі вы яго забылі."
},
"masterPassHintDesc": {
"message": "Падказка да асноўнага пароля можа дапамагчы вам успомніць яго, калі вы яго забылі."
@@ -107,7 +107,7 @@
"message": "Увайсці ў сховішча"
},
"autoFillInfo": {
"message": "Няма ўліковых даных, даступных для аўтазапаўнення ў бягучую ўкладку браўзера."
"message": "Для бягучай укладкі браўзера адсутнічаюць лагіны даступныя для аўтазапаўнення."
},
"addLogin": {
"message": "Дадаць лагін"
@@ -137,7 +137,7 @@
"message": "Код адпраўлены"
},
"verificationCode": {
"message": "Код праверкі"
"message": "Праверачны код"
},
"confirmIdentity": {
"message": "Пацвердзіце сваю асобу для працягу."
@@ -190,13 +190,13 @@
"message": "Папкі"
},
"noFolders": {
"message": "Няма элементаў для паказу."
"message": "У спісе адсутнічаюць папкі."
},
"helpFeedback": {
"message": "Даведка і зваротная сувязь"
},
"sync": {
"message": "Сінхранізаваць"
"message": "Сінхранізацыя"
},
"syncVaultNow": {
"message": "Сінхранізаваць сховішча зараз"
@@ -227,7 +227,7 @@
"message": "Генерыраваць пароль"
},
"regeneratePassword": {
"message": "Стварыць новы пароль"
"message": "Паўторна генерыраваць пароль"
},
"options": {
"message": "Параметры"
@@ -276,10 +276,10 @@
"message": "Рэдагаваць"
},
"view": {
"message": "Выгляд"
"message": "Прагляд"
},
"noItemsInList": {
"message": "Няма элементаў для паказу."
"message": "У спісе адсутнічаюць элементы."
},
"itemInformation": {
"message": "Звесткі аб элеменце"
@@ -312,7 +312,7 @@
"message": "Выдаліць элемент"
},
"viewItem": {
"message": "Прагляд элемента"
"message": "Прагледзець элемент"
},
"launch": {
"message": "Запусціць"
@@ -333,7 +333,7 @@
"message": "Ацаніць пашырэнне"
},
"rateExtensionDesc": {
"message": "Падумайце аб тым, каб дапамагчы нам, напісаўшы добрым водгукам!"
"message": "Падумайце пра тое, каб дапамагчы нам добрым водгукам!"
},
"browserNotSupportClipboard": {
"message": "Ваш вэб-браўзер не падтрымлівае капіяванне даных у буфер абмену. Скапіюйце іх уручную."
@@ -342,7 +342,7 @@
"message": "Праверыць асобу"
},
"yourVaultIsLocked": {
"message": "Ваша сховішча заблакіравана. Каб працягнуць, увядзіце асноўны пароль."
"message": "Ваша сховішча заблакіравана. Каб працягнуць, пацвердзіце сваю асобу."
},
"unlock": {
"message": "Разблакіраваць"
@@ -364,7 +364,7 @@
"message": "Памылковы асноўны пароль"
},
"vaultTimeout": {
"message": "Тайм-аўт сховішча"
"message": "Час чакання сховішча"
},
"lockNow": {
"message": "Заблакіраваць зараз"
@@ -436,13 +436,13 @@
"message": "Пацвярджэнне асноўнага пароля не супадае."
},
"newAccountCreated": {
"message": "Ваш уліковы запіс створаны! Вы можаце ўвайсці."
"message": "Ваш уліковы запіс створаны! Цяпер вы можаце ўвайсці ў яго."
},
"masterPassSent": {
"message": "Мы адправілі вам на электронную пошту падказку для асноўнага пароля."
"message": "Мы адправілі вам на электронную пошту падказку да асноўнага пароля."
},
"verificationCodeRequired": {
"message": "Патрабуецца код праверкі."
"message": "Патрабуецца праверачны код."
},
"invalidVerificationCode": {
"message": "Памылковы праверачны код"
@@ -464,7 +464,7 @@
"message": "Вы выйшлі"
},
"loginExpired": {
"message": "Скончыўся тэрмін дзеяння вашага сеансу."
"message": "Тэрмін дзеяння вашага сеансу завяршыўся."
},
"logOutConfirmation": {
"message": "Вы ўпэўнены, што хочаце выйсці?"
@@ -488,10 +488,10 @@
"message": "Змяніць асноўны пароль"
},
"changeMasterPasswordConfirmation": {
"message": "Вы можаце змяніць свой асноўны пароль на bitwarden.com. Перайсці на сайт зараз?"
"message": "Вы можаце змяніць свой асноўны пароль у вэб-сховішчы на bitwarden.com. Перайсці на вэб-сайт зараз?"
},
"twoStepLoginConfirmation": {
"message": "Двухэтапны ўваход робіць ваш уліковы запіс больш бяспечным, патрабуючы пацвярджэння ўваходу на іншай прыладзе, напрыклад, ключом бяспекі, праграмай для праверкі бяспекі, SMS, тэлефонным выклікам або электроннай поштай. Двухэтапны ўваход уключаецца на bitwarden.com. Перайсці на сайт зараз?"
"message": "Двухэтапны ўваход робіць ваш уліковы запіс больш бяспечным, патрабуючы пацвярджэнне ўваходу на іншай прыладзе з выкарыстаннем ключа бяспекі, праграмы аўтэнтыфікацыі, SMS, тэлефоннага званка або электроннай пошты. Двухэтапны ўваход уключаецца на bitwarden.com. Перайсці на вэб-сайт, каб зрабіць гэта?"
},
"editedFolder": {
"message": "Папка адрэдагавана"
@@ -506,13 +506,13 @@
"message": "Уводзіны ў карыстанне праграмай"
},
"gettingStartedTutorialVideo": {
"message": "Праглядзіце невялікі навучальны матэрыял, каб даведацца. як атрымаць максімальную аддачу ад пашырэння браўзера."
"message": "Азнаёмцеся з нашым кароткім дапаможнікам, каб даведацца як атрымаць максімальную перавагу ад пашырэння для браўзера."
},
"syncingComplete": {
"message": "Сінхранізацыя завершана"
},
"syncingFailed": {
"message": "Памылка сінхранізацыі"
"message": "Збой сінхранізацыі"
},
"passwordCopied": {
"message": "Пароль скапіяваны"
@@ -543,7 +543,7 @@
"message": "Вы ўпэўнены, што хочаце адправіць гэты элемент у сметніцу?"
},
"deletedItem": {
"message": "Выдалены элемент"
"message": "Элемент адпраўлены ў сметніцу"
},
"overwritePassword": {
"message": "Перазапісаць пароль"
@@ -600,7 +600,7 @@
"message": "Ці павінен Bitwarden запомніць гэты пароль?"
},
"notificationAddSave": {
"message": "Так, захаваць зараз"
"message": "Захаваць"
},
"enableChangedPasswordNotification": {
"message": "Пытацца пра абнаўленні існуючага лагіна"
@@ -612,13 +612,13 @@
"message": "Хочаце абнавіць гэты пароль у Bitwarden?"
},
"notificationChangeSave": {
"message": "Так, абнавіць зараз"
"message": "Абнавіць"
},
"enableContextMenuItem": {
"message": "Паказваць параметры кантэкстнага меню"
},
"contextMenuItemDesc": {
"message": "Выкарыстоўваць падвоены націск для доступу да генератара пароля і супастаўлення лагінаў для вэб-сайтаў. "
"message": "Выкарыстоўваць падвоены націск для доступу да генератара пароляў і супастаўлення лагінаў для вэб-сайтаў. "
},
"defaultUriMatchDetection": {
"message": "Прадвызначанае выяўленне супадзення URI",
@@ -652,17 +652,17 @@
"message": "Фармат файла"
},
"warning": {
"message": "УВАГА",
"message": "ПАПЯРЭДЖАННЕ",
"description": "WARNING (should stay in capitalized letters if the language permits)"
},
"confirmVaultExport": {
"message": "Пацвердзіць экспартаванне сховішча"
},
"exportWarningDesc": {
"message": "Экспартуемы файл утрымлівае даныя вашага сховішча ў незашыфраваным фармаце. Яго не варта захоўваць ці адпраўляць па небяспечным каналам (напрыклад, па электроннай пошце). Выдаліце яго адразу пасля выкарыстання."
"message": "Пры экспартаванні файл утрымлівае даныя вашага сховішча ў незашыфраваным фармаце. Яго не варта захоўваць або адпраўляць па неабароненых каналах (напрыклад, па электроннай пошце). Выдаліце яго адразу пасля выкарыстання."
},
"encExportKeyWarningDesc": {
"message": адчас экспартавання даныя шыфруюцца з дапамогай ключа шыфравання ўліковага запісу. Калі вы калі-небудзь зменіце ключ шыфравання ўліковага запісу, вам неабходна будзе экспартаваць даныя паўторна, паколькі вы не зможаце расшыфраваць гэты файл экспартавання."
"message": ры экспартаванні даныя шыфруюцца з дапамогай ключа шыфравання ўліковага запісу. Калі вы калі-небудзь зменіце ключ шыфравання ўліковага запісу, вам неабходна будзе экспартаваць даныя паўторна, паколькі вы не зможаце расшыфраваць гэты файл экспартавання."
},
"encExportAccountWarningDesc": {
"message": "Ключы шыфравання з'яўляюцца ўнікальнымі для кожнага ўліковага запісу Bitwarden, таму нельга імпартаваць зашыфраванае сховішча ў іншы ўліковы запіс."
@@ -708,7 +708,7 @@
"message": "Ключ аўтэнтыфікацыі (TOTP)"
},
"verificationCodeTotp": {
"message": "Код праверкі (TOTP)"
"message": "Праверачны код (TOTP)"
},
"copyVerificationCode": {
"message": "Скапіяваць праверачны код"
@@ -747,7 +747,7 @@
"message": "Функцыя недаступна"
},
"updateKey": {
"message": "Вы не можаце выкарыстоўваць гэту функцыю, пакуль не абнавіце свой ключ шыфравання."
"message": "Вы не зможаце выкарыстоўваць гэту функцыю, пакуль не абнавіце свой ключ шыфравання."
},
"premiumMembership": {
"message": "Прэміяльны статус"
@@ -756,13 +756,13 @@
"message": "Кіраваць статусам"
},
"premiumManageAlert": {
"message": "Вы можаце кіраваць сваім статусам на bitwarden.com. Перайсці на сайт зараз?"
"message": "Вы можаце кіраваць сваім статусам на bitwarden.com. Перайсці на вэб-сайт зараз?"
},
"premiumRefresh": {
"message": "Абнавіць статус"
},
"premiumNotCurrentMember": {
"message": "На дадзены момант у вас не прэміяльны статус."
"message": "Зараз у вас няма прэміяльнага статусу."
},
"premiumSignUpAndGet": {
"message": "Падпішыцеся на прэміяльны статус і атрымайце:"
@@ -777,7 +777,7 @@
"message": "Гігіена пароляў, здароўе ўліковага запісу і справаздачы аб уцечках даных для забеспячэння бяспекі вашага сховішча."
},
"ppremiumSignUpTotp": {
"message": "TOTP-генератар кодаў (2ФА) для ўліковых даных вашага сховішча."
"message": "Генератар праверачных кодаў TOTP (2ФА) для ўваходу ў ваша сховішча."
},
"ppremiumSignUpSupport": {
"message": "Прыярытэтная падтрымка."
@@ -786,16 +786,16 @@
"message": "Усе будучыя функцыі прэміяльнага статусу. Іх будзе больш!"
},
"premiumPurchase": {
"message": "Купіць прэміяльны статус"
"message": "Купіць прэміум"
},
"premiumPurchaseAlert": {
"message": "Вы можаце купіць прэміяльны статус на bitwarden.com. Перайсці на сайт зараз?"
"message": "Вы можаце купіць прэміяльны статус на bitwarden.com. Перайсці на вэб-сайт зараз?"
},
"premiumCurrentMember": {
"message": "У вас прэміяльны статус!"
},
"premiumCurrentMemberThanks": {
"message": "Дзякуем вам за падтрымку Bitwarden."
"message": "Дзякуй за падтрымку Bitwarden."
},
"premiumPrice": {
"message": "Усяго за $PRICE$ у год!",
@@ -837,7 +837,7 @@
}
},
"verificationCodeEmailSent": {
"message": "Адпраўлены ліст для пацвярджэння $EMAIL$.",
"message": "Праверачны ліст адпраўлены на $EMAIL$.",
"placeholders": {
"email": {
"content": "$1",
@@ -849,7 +849,7 @@
"message": "Запомніць мяне"
},
"sendVerificationCodeEmailAgain": {
"message": "Адправіць код пацвярджэння зноў"
"message": "Адправіць праверачны код яшчэ раз"
},
"useAnotherTwoStepMethod": {
"message": "Выкарыстоўваць іншы метад двухэтапнага ўваходу"
@@ -873,10 +873,10 @@
"message": "Уваход недаступны"
},
"noTwoStepProviders": {
"message": "У гэтага ўліковага запісу ўключаны двухэтапны ўваход, аднак ні адзін з наладжаных варыянтаў не падтрымліваецца гэтым вэб-браўзерам."
"message": "У гэтага ўліковага запісу ўключаны двухэтапны ўваход, аднак ніводны з наладжаных пастаўшчыкоў двухэтапнай праверкі не падтрымліваецца гэтым браўзерам."
},
"noTwoStepProviders2": {
"message": "Выкарыстоўвайце актуальны вэб-браўзер (напрыклад, Chrome) і/або дадайце дадатковыя варыянты праверкі сапраўднасці, якія падтрымліваюцца ў вэб-браўзерах (напрыклад, праграма для праверкі сапраўднасці)."
"message": "Выкарыстоўвайце актуальны браўзер (напрыклад, Chrome) і/або дадайце іншых пастаўшчыкоў, якія маюць лепшую падтрымку разнастайных браўзераў (напрыклад, праграма аўтэнтыфікацыі)."
},
"twoStepOptions": {
"message": "Параметры двухэтапнага ўваходу"
@@ -891,40 +891,40 @@
"message": "Праграма аўтэнтыфікацыі"
},
"authenticatorAppDesc": {
"message": "Выкарыстоўвайце праграму для праверкі сапраўднасці (напрыклад, Authy або Google Authenticator) для стварэння кодаў праверкі на аснове часу.",
"message": "Выкарыстоўвайце праграму праграму аўтэнтыфікацыі (напрыклад, Authy або Google Authenticator) для генерацыі праверачных кодаў на падставе часу.",
"description": "'Authy' and 'Google Authenticator' are product names and should not be translated."
},
"yubiKeyTitle": {
"message": "Ключ бяспекі YubiKey OTP"
},
"yubiKeyDesc": {
"message": "Выкарыстоўвайце YubiKey для доступу да вашага ўліковага запісу. Працуе з прыладамі YubiKey серый 4, 5 і NEO."
"message": "Выкарыстоўвайце YubiKey для доступу да вашага ўліковага запісу. Працуе з ключамі бяспекі YubiKey 4, 4 Nano, 4C і NEO."
},
"duoDesc": {
"message": ацвярдзіце з дапамогай Duo Security, выкарыстоўваючы праграму Duo Mobile, SMS, тэлефонны выклік або ключ бяспекі.",
"message": раверка з дапамогай Duo Security, выкарыстоўваючы праграму Duo Mobile, SMS, тэлефонны выклік або ключ бяспекі U2F.",
"description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated."
},
"duoOrganizationDesc": {
"message": ацвярдзіце з дапамогай Duo Security для вашай арганізацыі, выкарыстоўваючы праграму Duo Mobile, SMS, тэлефонны выклік або ключ бяспекі.",
"message": раверка з дапамогай Duo Security для вашай арганізацыі, выкарыстоўваючы праграму Duo Mobile, SMS, тэлефонны выклік або ключ бяспекі U2F.",
"description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated."
},
"webAuthnTitle": {
"message": "FIDO2 WebAuthn"
},
"webAuthnDesc": {
"message": "Выкарыстоўвайце любы ключ з падтрымкай WebAuthn для доступу да свайго ўліковага запісу."
"message": "Выкарыстоўвайце любы ключ бяспекі WebAuthn, каб атрымаць доступ да вашага ўліковага запісу."
},
"emailTitle": {
"message": "Электронная пошта"
},
"emailDesc": {
"message": "Коды пацвярджэння будуць адпраўлены вам па электроннай пошце."
"message": "Праверачныя коды будуць адпраўляцца вам па электронную пошту."
},
"selfHostedEnvironment": {
"message": "Асяроддзе ўласнага хостынгу"
},
"selfHostedEnvironmentFooter": {
"message": "Увядзіце асноўны URL-адрас на вашым серверы."
"message": "Увядзіце асноўны URL-адрас вашага лакальнага размяшчэння ўсталяванага Bitwarden."
},
"customEnvironment": {
"message": "Карыстальніцкае асяроддзе"
@@ -960,7 +960,7 @@
"message": "Калі выяўлена форма ўваходу, то будзе выканана яе аўтазапаўненне падчас загрузкі вэб-старонкі."
},
"experimentalFeature": {
"message": "Гэта эксперыментальная функцыя. Выкарыстоўвайце на свой страх і рызыку."
"message": "У дадзены час гэта функцыя з'яўляецца эксперыментальнай. Рызыку падчас яе выкарыстанні вы прымаеце на сябе."
},
"defaultAutoFillOnPageLoad": {
"message": "Прадвызначаная налада аўтазапаўнення для элементаў уваходу"
@@ -1034,13 +1034,13 @@
"message": "Націск за межамі ўсплывальнага акна для прагляду праверачнага кода прывядзе да яго закрыцця. Адкрыць гэта ўсплывальнае акно ў новым акне, якое не закрыецца?"
},
"popupU2fCloseMessage": {
"message": "Гэты браўзар не можа апрацоўваць запыты U2F у гэтым усплывальным акне. Вы хочаце адкрыць гэта ўсплывальнае акно ў новым акне, каб мець магчымасць увайсці ў сістэму, выкарыстоўваючы U2F?"
"message": "Дадзены браўзер не можа апрацоўваць запыты U2F у гэтым усплывальным акне. Адкрыць гэта ўсплывальнае акно ў новым акне, каб мець магчымасць увайсці ў сістэму, выкарыстоўваючы U2F?"
},
"enableFavicon": {
"message": "Паказваць значкі вэб-сайтаў"
},
"faviconDesc": {
"message": "Паказваць распазнавальны відарыс побач з кожным з кожным лагінам."
"message": "Паказваць распазнавальны відарыс побач з кожным лагінам."
},
"enableBadgeCounter": {
"message": "Паказваць лічыльнік на значку"
@@ -1190,7 +1190,7 @@
"message": "Уліковыя даныя"
},
"typeSecureNote": {
"message": "Бяспечныя нататкі"
"message": "Абароненая нататка"
},
"typeCard": {
"message": "Картка"
@@ -1211,7 +1211,7 @@
"message": "Абраныя"
},
"popOutNewWindow": {
"message": "Перайсці ў новае акно"
"message": "Адкрыць у новым акне"
},
"refresh": {
"message": "Абнавiць"
@@ -1226,7 +1226,7 @@
"message": "Лагіны"
},
"secureNotes": {
"message": "Бяспечныя нататкі"
"message": "Абароненыя нататкі"
},
"clear": {
"message": "Ачысціць",
@@ -1236,7 +1236,7 @@
"message": "Праверце, ці не скампраметаваны пароль."
},
"passwordExposed": {
"message": "Гэты пароль быў скампраметаваны $VALUE$ раз(-ы/-оў). Вы павінны змяніць яго.",
"message": "Гэты пароль быў скампраметаваны наступную колькасць разоў: $VALUE$. Вы павінны змяніць яго.",
"placeholders": {
"value": {
"content": "$1",
@@ -1281,7 +1281,7 @@
"message": "Пераключыць параметры"
},
"toggleCurrentUris": {
"message": "Уключыць бягучыя URI укладак",
"message": "Пераключыць бягучыя URI",
"description": "Toggle the display of the URIs of the currently open tabs in the browser."
},
"currentUri": {
@@ -1299,7 +1299,7 @@
"message": "Усе элементы"
},
"noPasswordsInList": {
"message": "Няма пароляў для паказу."
"message": "У спісе адсутнічаюць паролі."
},
"remove": {
"message": "Выдаліць"
@@ -1322,7 +1322,7 @@
"message": "Вы не з'яўляецеся членам якой-небудзь арганізацыі. Арганізацыі дазваляюць бяспечна абменьвацца элементамі з іншымі карыстальнікамі."
},
"noCollectionsInList": {
"message": "Няма калекцый для паказу."
"message": "У спісе адсутнічаюць калекцыі."
},
"ownership": {
"message": "Уладальнік"
@@ -1343,7 +1343,7 @@
"description": "ex. A weak password. Scale: Weak -> Good -> Strong"
},
"weakMasterPassword": {
"message": "Слабы асноўны пароль"
"message": "Ненадзейны асноўны пароль"
},
"weakMasterPasswordDesc": {
"message": "Асноўны пароль, які вы выбралі з'яўляецца ненадзейным. Для належнай абароны ўліковага запісу Bitwarden, вы павінны выкарыстоўваць надзейны асноўны пароль (або парольную фразу). Вы ўпэўнены, што хочаце выкарыстоўваць гэты асноўны пароль?"
@@ -1356,7 +1356,7 @@
"message": "Разблакіраваць PIN-кодам"
},
"setYourPinCode": {
"message": "Задайце PIN-код для разблакіроўкі Bitwarden. Налады PIN-кода будуць скінуты, калі вы калі-небудзь цалкам выйдзеце з праграмы."
"message": "Прызначце PIN-код для разблакіроўкі Bitwarden. Налады PIN-кода будуць скінуты, калі вы калі-небудзь цалкам выйдзеце з праграмы."
},
"pinRequired": {
"message": "Патрабуецца PIN-код."
@@ -1389,7 +1389,7 @@
"message": "Адна або больш палітык арганізацыі ўплывае на налады генератара."
},
"vaultTimeoutAction": {
"message": "Дзеянне пры тайм-аўце"
"message": "Дзеянне пасля заканчэння часу чакання сховішча"
},
"lock": {
"message": "Заблакіраваць",
@@ -1409,7 +1409,7 @@
"message": "Вы ўпэўнены, што хочаце назаўсёды выдаліць гэты элемент?"
},
"permanentlyDeletedItem": {
"message": "Выдаленны назаўсёды элемент"
"message": "Элемент выдалены назаўсёды"
},
"restoreItem": {
"message": "Аднавіць элемент"
@@ -1424,7 +1424,7 @@
"message": "Выхад з сістэмы скасуе ўсе магчымасці доступу да сховішча і запатрабуе аўтэнтыфікацыю праз інтэрнэт пасля завяршэння часу чакання. Вы ўпэўнены, што хочаце выкарыстоўваць гэты параметр?"
},
"vaultTimeoutLogOutConfirmationTitle": {
"message": "Пацвярджэнне дзеяння для тайм-аута"
"message": "Пацвярджэнне дзеяння часу чакання"
},
"autoFillAndSave": {
"message": "Аўтазапоўніць і захаваць"
@@ -1436,7 +1436,7 @@
"message": "Аўтазапоўнены элемент"
},
"setMasterPassword": {
"message": "Задаць асноўны пароль"
"message": "Прызначыць асноўны пароль"
},
"masterPasswordPolicyInEffect": {
"message": "Адна або больш палітык арганізацыі патрабуе, каб ваш асноўны пароль адпавядаў наступным патрабаванням:"
@@ -1478,7 +1478,7 @@
}
},
"masterPasswordPolicyRequirementsNotMet": {
"message": "Ваш новы асноўны пароль не адпавядае патрабаванням палітыкі арганізацыі."
"message": "Ваш новы асноўны пароль не адпавядае патрабаванням палітыкі."
},
"acceptPolicies": {
"message": "Ставячы гэты сцяжок, вы пагаджаецеся з наступным:"
@@ -1502,7 +1502,7 @@
"message": "Праверка сінхранізацыі на камп'ютары"
},
"desktopIntegrationVerificationText": {
"message": "Праверце, ці паказвае праграма на камп'ютары гэты адбітак: "
"message": "Праверце, ці паказвае праграма на камп'ютары гэты адбітак пальца: "
},
"desktopIntegrationDisabledTitle": {
"message": "Інтэграцыя з браўзерам не ўключана"
@@ -1517,7 +1517,7 @@
"message": "Для разблакіроўкі па біяметрыі неабходна запусціць праграму Bitwarden на камп'ютары."
},
"errorEnableBiometricTitle": {
"message": "Не атрымалася ўключыць біяметрыю"
"message": "Немагчыма ўключыць біяметрыю"
},
"errorEnableBiometricDesc": {
"message": "Дзеянне было скасавана праграмай на камп'ютары"
@@ -1556,7 +1556,7 @@
"message": "Памылка пры запыце дазволу"
},
"nativeMessaginPermissionSidebarDesc": {
"message": "Гэта дзеянне немагчыма выканаць у бакавой панэлі. Паспрабуйце паўтарыць гэта дзеянне ва ўсплывальным або асобным акне."
"message": "Гэта дзеянне немагчыма выканаць у бакавой панэлі. Паспрабуйце паўтарыць яго ва ўсплывальным або асобным акне."
},
"personalOwnershipSubmitError": {
"message": "У адпаведнасці з палітыкай прадпрыемства вам забаронена захоўваць элементы ў асабістым сховішчы. Змяніце параметры ўласнасці на арганізацыю і выберыце з даступных калекцый."
@@ -1584,7 +1584,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"searchSends": {
"message": "Пошук Send'аў",
"message": "Пошук у Send'ах",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"addSend": {
@@ -1632,7 +1632,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendLink": {
"message": "Спасылка Send",
"message": "Спасылка на Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
@@ -1654,7 +1654,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendTypeHeader": {
"message": "Які гэта тып Send?",
"message": "Які гэта тып Send'a?",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNameDesc": {
@@ -1770,7 +1770,7 @@
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'"
},
"sendFirefoxCustomDatePopoutMessage3": {
"message": "для адкрыцця ў асобным акне.",
"message": "для адкрыцця ў новым акне.",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'"
},
"expirationDateIsInvalid": {
@@ -1801,28 +1801,28 @@
"message": "Пацвярджэнне асноўнага пароля"
},
"passwordConfirmationDesc": {
"message": "Гэта дзеянне абаронена. Для працягу, калі ласка, паўторна ўвядзіце свой пароль, каб пацвердзіць вашу асобу."
"message": "Гэта дзеянне абаронена. Для працягу, калі ласка, паўторна ўвядзіце свой асноўны пароль, каб пацвердзіць вашу асобу."
},
"emailVerificationRequired": {
"message": "Патрабуецца праверка электроннай пошты"
},
"emailVerificationRequiredDesc": {
"message": "Вы павінны праверыць свой адрас электроннай пошты, каб выкарыстоўваць гэту функцыю. Вы можаце гэта зрабіць з дапамогай вэб-сховішча."
"message": "Вы павінны праверыць свой адрас электроннай пошты, каб выкарыстоўваць гэту функцыю. Зрабіць гэта можна ў вэб-сховішчы."
},
"updatedMasterPassword": {
"message": "Галоўны пароль абноўлены"
"message": "Асноўны пароль абноўлены"
},
"updateMasterPassword": {
"message": "Абнавіць галоўны пароль"
"message": "Абнавіць асноўны пароль"
},
"updateMasterPasswordWarning": {
"message": "Ваш галоўны пароль не так даўно быў зменены адміністратарам вашай арганізацыі. Для таго, каб атрымаць доступ да вашага сховішча, вы павінны абнавіць яго зараз. У выніку бягучы сеанс будзе завершаны і вам неабходна будзе паўторна ўвайсці. Сеансы на іншых прыладах могуць заставацца актыўнымі на працягу адной гадзіны."
"message": "Ваш асноўны пароль нядаўна быў зменены адміністратарам арганізацыі. Для таго, каб атрымаць доступ да вашага сховішча, вы павінны абнавіць яго зараз. Гэта прывядзе да завяршэння бягучага сеанса і вам неабходна будзе ўвайсці паўторна. Сеансы на іншых прыладах могуць заставацца актыўнымі на працягу адной гадзіны."
},
"resetPasswordPolicyAutoEnroll": {
"message": "Аўтаматычная рэгістрацыя"
},
"resetPasswordAutoEnrollInviteWarning": {
"message": "Гэта арганізацыя мае карпаратыўную палітыку, якая будзе аўтаматычна зарэгіструе ваша скіданне пароля. Рэгістрацыя дазволіць адміністратарам арганізацыі змяняць ваш асноўны пароль."
"message": "Гэта арганізацыя мае палітыку прадпрыемства, якая аўтаматычна зарэгіструе ваша скіданне пароля. Рэгістрацыя дазволіць адміністратарам арганізацыі змяняць ваш асноўны пароль."
},
"selectFolder": {
"message": "Выбраць папку..."
@@ -1837,7 +1837,7 @@
"message": "Хвіліны"
},
"vaultTimeoutPolicyInEffect": {
"message": "Палітыка вашай арганізацыі ўплывае на час чакання сховішча. Максімальны дазволены час чакання сховішча $HOURS$ гадз. і $MINUTES$ хв.",
"message": "Палітыка вашай арганізацыі ўплывае на час чакання сховішча. Максімальны дазволены час чакання сховішча складае $HOURS$ гадз. і $MINUTES$ хв.",
"placeholders": {
"hours": {
"content": "$1",
@@ -1889,10 +1889,10 @@
"message": "Вы пакінулі арганізацыю."
},
"toggleCharacterCount": {
"message": "Пераключыць колькасць сімвалаў"
"message": "Пераключыць лічыльнік сімвалаў"
},
"sessionTimeout": {
"message": "Час вашага сеанса завяршыўся. Аўтарызуйцеся паўторна."
"message": "Час чакання вашага сеанса завяршыўся. Калі ласка, увайдзіце паўторна."
},
"exportingPersonalVaultTitle": {
"message": "Экспартаванне асабістага сховішча"
@@ -1910,7 +1910,7 @@
"message": "Памылка"
},
"regenerateUsername": {
"message": "Паўторна згенерыраваць імя карыстастальніка"
"message": "Паўторна генерыраваць імя карыстальніка"
},
"generateUsername": {
"message": "Генерыраваць імя карыстальніка"
@@ -1923,13 +1923,13 @@
"description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com"
},
"plusAddressedEmailDesc": {
"message": "Выкарыстоўваць магчымасці пададрасацыі вашага пастаўшчыка паслуг электроннай пошты."
"message": "Выкарыстоўвайце магчымасці пададрасацыі вашага пастаўшчыка паслуг электроннай пошты."
},
"catchallEmail": {
"message": "Адрас для ўсёй пошты дамена"
},
"catchallEmailDesc": {
"message": "Выкарыстоўвайце сваю сканфігураваную скрыню для ўсё пошты дамена."
"message": "Выкарыстоўвайце сваю сканфігураваную скрыню для ўсёй пошты дамена."
},
"random": {
"message": "Выпадкова"

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "văzut ultima dată la $DATE$",
"message": "văzut ultima dată pe: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2002,13 +2002,13 @@
"message": "Serverversion"
},
"selfHosted": {
"message": "Self-Hosted"
"message": "Lokalt installerad"
},
"thirdParty": {
"message": "Tredje part"
},
"thirdPartyServerMessage": {
"message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.",
"message": "Ansluten till implementering på tredjepartsserver, $SERVERNAME$. Verifiera buggar med den officiella servern eller rapportera dem till tredjepartsservern.",
"placeholders": {
"servername": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
"message": "last seen on $DATE$",
"message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,10 +5,9 @@ import { NotificationsService } from "@bitwarden/common/abstractions/notificatio
import { SystemService } from "@bitwarden/common/abstractions/system.service";
import { Utils } from "@bitwarden/common/misc/utils";
import { BrowserEnvironmentService } from "src/services/browser-environment.service";
import { BrowserApi } from "../browser/browserApi";
import { AutofillService } from "../services/abstractions/autofill.service";
import { BrowserEnvironmentService } from "../services/browser-environment.service";
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
import MainBackground from "./main.background";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import AddLoginRuntimeMessage from "src/background/models/addLoginRuntimeMessage";
import ChangePasswordRuntimeMessage from "src/background/models/changePasswordRuntimeMessage";
import AddLoginRuntimeMessage from "../background/models/addLoginRuntimeMessage";
import ChangePasswordRuntimeMessage from "../background/models/changePasswordRuntimeMessage";
document.addEventListener("DOMContentLoaded", (event) => {
if (window.location.hostname.endsWith("vault.bitwarden.com")) {

View File

@@ -1,4 +1,134 @@
declare function escape(s: string): string;
declare function unescape(s: string): string;
declare let opr: any;
/**
* @link https://dev.opera.com/extensions/addons-api/
*/
type OperaAddons = {
/**
* @link https://dev.opera.com/extensions/addons-api/#method-installextension
*/
installExtension: (
id: string,
success_callback: () => void,
error_callback: (errorMessage: string) => void
) => void;
};
type OperaEvent<T> = {
addListener: (callback: (state: T) => void) => void;
};
/**
* @link https://dev.opera.com/extensions/sidebar-action-api/#type-colorarray
*/
type ColorArray = [number, number, number, number];
/**
* @link https://dev.opera.com/extensions/sidebar-action-api/#type-imagedatatype
*/
type ImageDataType = ImageData;
/**
* @link https://dev.opera.com/extensions/sidebar-action-api/
*/
type OperaSidebarAction = {
/**
* @link https://dev.opera.com/extensions/sidebar-action-api/#method-settitle
*/
setTitle: (details: { title: string; tabId?: number }) => void;
/**
* @link https://dev.opera.com/extensions/sidebar-action-api/#method-gettitle
*/
getTitle: (details: { tabId?: number }, callback: (result: string) => void) => void;
/**
* @link https://dev.opera.com/extensions/sidebar-action-api/#method-seticon
*/
setIcon: (
details: {
imageData?: ImageDataType | Record<number, ImageDataType>;
path?: string | Record<number, string>;
tabId?: number;
},
callback?: () => void
) => void;
/**
* @link https://dev.opera.com/extensions/sidebar-action-api/#method-setpanel
*/
setPanel: (details: { tabId?: number; panel: string }) => void;
/**
* @link https://dev.opera.com/extensions/sidebar-action-api/#method-getpanel
*/
getPanel: (details: { tabId?: number }, callback: (result: string) => void) => void;
/**
* *Not supported on mac*
*
* @link https://dev.opera.com/extensions/sidebar-action-api/#method-setbadgetext
*/
setBadgeText: (details: { text: string; tabId?: number }) => void;
/**
* *Not supported on mac*
*
* @link https://dev.opera.com/extensions/sidebar-action-api/#method-getbadgetext
*/
getBadgeText: (details: { tabId?: number }, callback: (result: string) => void) => void;
/**
* *Not supported on mac*
*
* @link https://dev.opera.com/extensions/sidebar-action-api/#method-setbadgebackgroundcolor
*/
setBadgeBackgroundColor: (details: { color: ColorArray | string; tabId?: number }) => void;
/**
* *Not supported on mac*
*
* @link https://dev.opera.com/extensions/sidebar-action-api/#method-getbadgebackgroundcolor
*/
getBadgeBackgroundColor: (
details: { tabId?: number },
callback: (result: ColorArray) => void
) => void;
/**
* *Not supported on mac*
*
* @link https://dev.opera.com/extensions/sidebar-action-api/#events-onfocus
*/
onFocus: OperaEvent<Window>;
/**
* *Not supported on mac*
*
* @link https://dev.opera.com/extensions/sidebar-action-api/#events-onblur
*/
onBlur: OperaEvent<Window>;
};
type Opera = {
addons: OperaAddons;
sidebarAction: OperaSidebarAction;
};
declare namespace chrome {
/**
* This is for firefoxes sidebar action and it is based on the opera one but with a few less methods
*
* @link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/sidebarAction
*/
let sidebarAction:
| Omit<
OperaSidebarAction,
| "setBadgeText"
| "getBadgeText"
| "setBadgeBackgroundColor"
| "getBadgeBackgroundColor"
| "onFocus"
| "onBlur"
>
| undefined;
}
interface Window {
opr: Opera | undefined;
opera: unknown;
}
declare let opr: Opera | undefined;
declare let opera: unknown | undefined;
declare let safari: any;

View File

@@ -1,27 +1,15 @@
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus";
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
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 { 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) => {
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 logService = new ConsoleLogService(false);
const cryptoFunctionService = new WebCryptoFunctionService(self);
const storageService = new BrowserLocalStorageService();
const secureStorageService = new BrowserLocalStorageService();
const memoryStorageService = new LocalBackedSessionStorageService(
new EncryptService(cryptoFunctionService, logService, false),
new KeyGenerationService(cryptoFunctionService)
);
const stateFactory = new StateFactory(GlobalState, Account);
const stateMigrationService = new StateMigrationService(
storageService,
secureStorageService,
stateFactory
);
const stateService: AbstractStateService = new StateService(
storageService,
secureStorageService,
memoryStorageService, // AbstractStorageService
logService,
stateMigrationService,
stateFactory
);
await stateService.init();
const platformUtils = new BrowserPlatformUtilsService(
null, // MessagingService
null, // clipboardWriteCallback
null // biometricCallback
);
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 cachedServices: CachedServices = {};
const opts = {
cryptoFunctionServiceOptions: {
win: self,
},
encryptServiceOptions: {
logMacFailures: true,
},
logServiceOptions: {
isDev: false,
},
platformUtilsServiceOptions: {
clipboardWriteCallback: () => Promise.resolve(),
biometricCallback: () => Promise.resolve(false),
win: self,
},
stateServiceOptions: {
stateFactory: new StateFactory(GlobalState, Account),
},
stateMigrationServiceOptions: {
stateFactory: new StateFactory(GlobalState, Account),
},
apiServiceOptions: {
logoutCallback: () => Promise.resolve(),
},
keyConnectorServiceOptions: {
logoutCallback: () => Promise.resolve(),
},
i18nServiceOptions: {
systemLanguage: BrowserApi.getUILanguage(self),
},
cipherServiceOptions: {
searchServiceFactory: null as () => SearchService, // No dependence on search service
},
};
const logService = await logServiceFactory(cachedServices, opts);
const authService = await authServiceFactory(cachedServices, opts);
const autofillService = await autofillServiceFactory(cachedServices, opts);
const authStatus = await authService.getAuthStatus();
if (authStatus < AuthenticationStatus.Unlocked) {

View File

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

View File

@@ -146,6 +146,10 @@ p.lead {
border: 0 !important;
}
:not(:focus) > .exists-only-on-parent-focus {
display: none;
}
.password-wrapper {
overflow-wrap: break-word;
white-space: pre-wrap;

View File

@@ -19,9 +19,8 @@ import { CipherView } from "@bitwarden/common/models/view/cipherView";
import { CollectionView } from "@bitwarden/common/models/view/collectionView";
import { FolderView } from "@bitwarden/common/models/view/folderView";
import { BrowserComponentState } from "src/models/browserComponentState";
import { BrowserApi } from "../../browser/browserApi";
import { BrowserComponentState } from "../../models/browserComponentState";
import { StateService } from "../../services/abstractions/state.service";
import { VaultFilterService } from "../../services/vaultFilter.service";
import { PopupUtilsService } from "../services/popup-utils.service";

View File

@@ -16,9 +16,8 @@ import { CipherView } from "@bitwarden/common/models/view/cipherView";
import { CollectionView } from "@bitwarden/common/models/view/collectionView";
import { FolderView } from "@bitwarden/common/models/view/folderView";
import { BrowserGroupingsComponentState } from "src/models/browserGroupingsComponentState";
import { BrowserApi } from "../../browser/browserApi";
import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState";
import { StateService } from "../../services/abstractions/state.service";
import { VaultFilterService } from "../../services/vaultFilter.service";
import { PopupUtilsService } from "../services/popup-utils.service";

View File

@@ -156,7 +156,7 @@
>
<span class="totp-code">{{ totpCodeFormatted }}</span>
</div>
<span class="totp-countdown">
<span class="totp-countdown" aria-hidden="true">
<span class="totp-sec">{{ totpSec }}</span>
<svg>
<g>
@@ -176,10 +176,17 @@
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'copyVerificationCode' | i18n }}"
title="{{ 'copyVerificationCode' | i18n }}"
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
<span class="sr-only">{{ "copyValue" | i18n }}</span>
<span
class="sr-only exists-only-on-parent-focus"
aria-live="polite"
aria-atomic="true"
>{{ totpSec }}</span
>
</button>
</div>
</div>

View File

@@ -3,10 +3,10 @@ import { Jsonify } from "type-fest";
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
import { StorageOptions } from "@bitwarden/common/models/domain/storageOptions";
import { Account } from "src/models/account";
import { BrowserComponentState } from "src/models/browserComponentState";
import { BrowserGroupingsComponentState } from "src/models/browserGroupingsComponentState";
import { BrowserSendComponentState } from "src/models/browserSendComponentState";
import { Account } from "../../models/account";
import { BrowserComponentState } from "../../models/browserComponentState";
import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState";
import { BrowserSendComponentState } from "../../models/browserSendComponentState";
export abstract class StateService extends BaseStateServiceAbstraction<Account> {
abstract getFromSessionMemory<T>(key: string, deserializer?: (obj: Jsonify<T>) => T): Promise<T>;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -147,12 +147,12 @@ Bitwarden — гэта праграмнае забеспячэнне з адкр
<value>Хутка і аўтаматычна запаўняйце свае ўліковыя даныя на любым вэб-сайце</value>
</data>
<data name="ScreenshotMenu" xml:space="preserve">
<value>У вас ёсць зручны доступ да сховішча з кантэкстнага меню</value>
<value>Вы таксама зручна можаце атрымаць доступ да сховішча праз кантэкстнае меню</value>
</data>
<data name="ScreenshotPassword" xml:space="preserve">
<value>Аўтаматычна генерыруйце надзейныя, выпадковыя і бяспечныя паролі</value>
</data>
<data name="ScreenshotEdit" xml:space="preserve">
<value>Вашыя звесткі надзейна захоўваюцца, дзякуючы шыфраванню AES-256</value>
<value>Ваша інфармацыя надзейна абаронена алгарытмам шыфравання AES-256</value>
</data>
</root>

View File

@@ -6,7 +6,7 @@ import { StateService } from "@bitwarden/common/abstractions/state.service";
import { Utils } from "@bitwarden/common/misc/utils";
import { Response } from "@bitwarden/node/cli/models/response";
import { CliUtils } from "src/utils";
import { CliUtils } from "../utils";
export class DeleteCommand {
constructor(

View File

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

View File

@@ -3,7 +3,7 @@ import { Response } from "@bitwarden/node/cli/models/response";
import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse";
import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse";
import { CliUtils } from "src/utils";
import { CliUtils } from "../utils";
export class SyncCommand {
constructor(private syncService: SyncService) {}

View File

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

View File

@@ -13,7 +13,6 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.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 { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
@@ -25,8 +24,6 @@ const BroadcasterSubscriptionId = "LockComponent";
})
export class LockComponent extends BaseLockComponent {
private deferFocus: boolean = null;
authenicatedUrl = "vault";
unAuthenicatedUrl = "update-temp-password";
constructor(
router: Router,
@@ -43,8 +40,7 @@ export class LockComponent extends BaseLockComponent {
private broadcasterService: BroadcasterService,
ngZone: NgZone,
logService: LogService,
keyConnectorService: KeyConnectorService,
private syncService: SyncService
keyConnectorService: KeyConnectorService
) {
super(
router,
@@ -67,11 +63,6 @@ export class LockComponent extends BaseLockComponent {
await super.ngOnInit();
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
this.route.queryParams.subscribe((params) => {
if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) {

View File

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

View File

@@ -1,6 +1,7 @@
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "@bitwarden/angular/abstractions/deprecated-vault-filter.service";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { VaultFilterService } from "@bitwarden/angular/vault/vault-filter/services/vault-filter.service";
@@ -22,6 +23,11 @@ import { VaultFilterComponent } from "./vault-filter.component";
TypeFilterComponent,
],
exports: [VaultFilterComponent],
providers: [VaultFilterService],
providers: [
{
provide: DeprecatedVaultFilterServiceAbstraction,
useClass: VaultFilterService,
},
],
})
export class VaultFilterModule {}

View File

@@ -116,7 +116,7 @@
>
<span class="totp-code">{{ totpCodeFormatted }}</span>
</div>
<span class="totp-countdown">
<span class="totp-countdown" aria-hidden="true">
<span class="totp-sec">{{ totpSec }}</span>
<svg>
<g>
@@ -136,10 +136,17 @@
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'copyValue' | i18n }}"
title="{{ 'copyValue' | i18n }}"
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
<span class="sr-only">{{ "copyValue" | i18n }}</span>
<span
class="sr-only exists-only-on-parent-focus"
aria-live="polite"
aria-atomic="true"
>{{ totpSec }}</span
>
</button>
</div>
</div>

View File

@@ -67,7 +67,7 @@
"message": "Далучэнні"
},
"viewItem": {
"message": "Прагляд элемента"
"message": "Прагледзець элемент"
},
"name": {
"message": "Назва"
@@ -473,7 +473,7 @@
"message": "Максімальны памер файла 500 МБ."
},
"updateKey": {
"message": "Вы не можаце выкарыстоўваць гэту функцыю, пакуль не абнавіце свой ключ шыфравання."
"message": "Вы не зможаце выкарыстоўваць гэту функцыю, пакуль не абнавіце свой ключ шыфравання."
},
"editedFolder": {
"message": "Папка адрэдагавана"
@@ -802,7 +802,7 @@
"message": "Сінхранізацыя завершана"
},
"syncingFailed": {
"message": "Памылка сінхранізацыі"
"message": "Збой сінхранізацыі"
},
"yourVaultIsLocked": {
"message": "Ваша сховішча заблакіравана. Каб працягнуць, пацвердзіце сваю асобу."
@@ -1050,13 +1050,13 @@
"message": "Кіраваць статусам"
},
"premiumManageAlert": {
"message": "Вы можаце кіраваць сваім статусам на bitwarden.com. Перайсці на вэб-сайт зараз зараз?"
"message": "Вы можаце кіраваць сваім статусам на bitwarden.com. Перайсці на вэб-сайт зараз?"
},
"premiumRefresh": {
"message": "Абнавіць статус удзельніка"
"message": "Абнавіць статус"
},
"premiumNotCurrentMember": {
"message": "На дадзены момант у вас не прэміяльны статус."
"message": "Зараз у вас няма прэміяльнага статусу."
},
"premiumSignUpAndGet": {
"message": "Падпішыцеся на прэміяльны статус і атрымайце:"
@@ -1876,7 +1876,7 @@
"message": "Вы сапраўды хочаце пакінуць гэту арганізацыю?"
},
"leftOrganization": {
"message": "Вы пакінулі арганізацыю."
"message": "Вы выйшлі з арганізацыі."
},
"ssoKeyConnectorError": {
"message": "Памылка Key Connector: пераканайцеся, што Key Connector даступны і карэктна працуе."
@@ -1961,7 +1961,7 @@
"message": "Адрас для ўсёй пошты дамена"
},
"catchallEmailDesc": {
"message": "Выкарыстоўвайце сваю сканфігураваную скрыню для ўсё пошты дамена."
"message": "Выкарыстоўвайце сваю сканфігураваную скрыню для ўсёй пошты дамена."
},
"random": {
"message": "Выпадкова"

View File

@@ -6,7 +6,7 @@ import { StateService } from "@bitwarden/common/abstractions/state.service";
import { biometrics } from "@bitwarden/desktop-native";
import { WindowMain } from "@bitwarden/electron/window.main";
import { BiometricMain } from "src/main/biometric/biometric.main";
import { BiometricMain } from "./biometric.main";
export default class BiometricWindowsMain implements BiometricMain {
constructor(

View File

@@ -1,12 +1,12 @@
{
"name": "@bitwarden/desktop",
"version": "2022.9.1",
"version": "2022.9.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@bitwarden/desktop",
"version": "2022.9.1",
"version": "2022.9.2",
"license": "GPL-3.0",
"dependencies": {
"@bitwarden/desktop-native": "file:../desktop_native"

View File

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

View File

@@ -154,6 +154,10 @@
margin-left: auto;
margin-right: 5px;
}
.filter-button {
white-space: nowrap;
}
}
.nav {

View File

@@ -112,6 +112,10 @@ p.lead {
border: 0 !important;
}
:not(:focus) > .exists-only-on-parent-focus {
display: none;
}
.totp {
.totp-code {
font-family: $font-family-monospace;

View File

@@ -10,18 +10,18 @@ import { CipherView } from "@bitwarden/common/models/view/cipherView";
import { LoginUriView } from "@bitwarden/common/models/view/loginUriView";
import { LoginView } from "@bitwarden/common/models/view/loginView";
import { DecryptedCommandData } from "src/models/nativeMessaging/decryptedCommandData";
import { CredentialCreatePayload } from "src/models/nativeMessaging/encryptedMessagePayloads/credentialCreatePayload";
import { CredentialRetrievePayload } from "src/models/nativeMessaging/encryptedMessagePayloads/credentialRetrievePayload";
import { CredentialUpdatePayload } from "src/models/nativeMessaging/encryptedMessagePayloads/credentialUpdatePayload";
import { PasswordGeneratePayload } from "src/models/nativeMessaging/encryptedMessagePayloads/passwordGeneratePayload";
import { AccountStatusResponse } from "src/models/nativeMessaging/encryptedMessageResponses/accountStatusResponse";
import { CipherResponse } from "src/models/nativeMessaging/encryptedMessageResponses/cipherResponse";
import { EncyptedMessageResponse } from "src/models/nativeMessaging/encryptedMessageResponses/encryptedMessageResponse";
import { FailureStatusResponse } from "src/models/nativeMessaging/encryptedMessageResponses/failureStatusResponse";
import { GenerateResponse } from "src/models/nativeMessaging/encryptedMessageResponses/generateResponse";
import { SuccessStatusResponse } from "src/models/nativeMessaging/encryptedMessageResponses/successStatusResponse";
import { UserStatusErrorResponse } from "src/models/nativeMessaging/encryptedMessageResponses/userStatusErrorResponse";
import { DecryptedCommandData } from "../models/nativeMessaging/decryptedCommandData";
import { CredentialCreatePayload } from "../models/nativeMessaging/encryptedMessagePayloads/credentialCreatePayload";
import { CredentialRetrievePayload } from "../models/nativeMessaging/encryptedMessagePayloads/credentialRetrievePayload";
import { CredentialUpdatePayload } from "../models/nativeMessaging/encryptedMessagePayloads/credentialUpdatePayload";
import { PasswordGeneratePayload } from "../models/nativeMessaging/encryptedMessagePayloads/passwordGeneratePayload";
import { AccountStatusResponse } from "../models/nativeMessaging/encryptedMessageResponses/accountStatusResponse";
import { CipherResponse } from "../models/nativeMessaging/encryptedMessageResponses/cipherResponse";
import { EncyptedMessageResponse } from "../models/nativeMessaging/encryptedMessageResponses/encryptedMessageResponse";
import { FailureStatusResponse } from "../models/nativeMessaging/encryptedMessageResponses/failureStatusResponse";
import { GenerateResponse } from "../models/nativeMessaging/encryptedMessageResponses/generateResponse";
import { SuccessStatusResponse } from "../models/nativeMessaging/encryptedMessageResponses/successStatusResponse";
import { UserStatusErrorResponse } from "../models/nativeMessaging/encryptedMessageResponses/userStatusErrorResponse";
import { StateService } from "./state.service";

View File

@@ -12,12 +12,12 @@ import { EncString } from "@bitwarden/common/models/domain/encString";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
import { StateService } from "@bitwarden/common/services/state.service";
import { DecryptedCommandData } from "src/models/nativeMessaging/decryptedCommandData";
import { EncryptedMessage } from "src/models/nativeMessaging/encryptedMessage";
import { EncryptedMessageResponse } from "src/models/nativeMessaging/encryptedMessageResponse";
import { Message } from "src/models/nativeMessaging/message";
import { UnencryptedMessage } from "src/models/nativeMessaging/unencryptedMessage";
import { UnencryptedMessageResponse } from "src/models/nativeMessaging/unencryptedMessageResponse";
import { DecryptedCommandData } from "../models/nativeMessaging/decryptedCommandData";
import { EncryptedMessage } from "../models/nativeMessaging/encryptedMessage";
import { EncryptedMessageResponse } from "../models/nativeMessaging/encryptedMessageResponse";
import { Message } from "../models/nativeMessaging/message";
import { UnencryptedMessage } from "../models/nativeMessaging/unencryptedMessage";
import { UnencryptedMessageResponse } from "../models/nativeMessaging/unencryptedMessageResponse";
import { EncryptedMessageHandlerService } from "./encryptedMessageHandlerService";
@@ -182,12 +182,25 @@ export class NativeMessageHandlerService {
this.ddgSharedSecret = SymmetricCryptoKey.fromJSON({ keyB64: storedKey });
}
return JSON.parse(
await this.cryptoService.decryptToUtf8(
try {
let decryptedResult = await this.cryptoService.decryptToUtf8(
message.encryptedCommand as EncString,
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(
@@ -218,4 +231,23 @@ export class NativeMessageHandlerService {
private sendResponse(response: EncryptedMessageResponse | UnencryptedMessageResponse) {
ipcRenderer.send("nativeMessagingReply", response);
}
// Trim all null bytes padded at the end of messages. This happens with C encryption libraries.
private trimNullCharsFromMessage(message: string): string {
const charNull = 0;
const charRightCurlyBrace = 125;
const charRightBracket = 93;
for (let i = message.length - 1; i >= 0; i--) {
if (message.charCodeAt(i) === charNull) {
message = message.substring(0, message.length - 1);
} else if (
message.charCodeAt(i) === charRightCurlyBrace ||
message.charCodeAt(i) === charRightBracket
) {
break;
}
}
return message;
}
}

View File

@@ -14,9 +14,9 @@ import { Utils } from "@bitwarden/common/misc/utils";
import { EncString } from "@bitwarden/common/models/domain/encString";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
import { LegacyMessage } from "src/models/nativeMessaging/legacyMessage";
import { LegacyMessageWrapper } from "src/models/nativeMessaging/legacyMessageWrapper";
import { Message } from "src/models/nativeMessaging/message";
import { LegacyMessage } from "../models/nativeMessaging/legacyMessage";
import { LegacyMessageWrapper } from "../models/nativeMessaging/legacyMessageWrapper";
import { Message } from "../models/nativeMessaging/message";
import { NativeMessageHandlerService } from "./nativeMessageHandler.service";

View File

@@ -11,8 +11,11 @@
"**/reports/*",
"**/app/shared/*",
"**/organizations/settings/*",
"**/organizations/policies/*"
]
"**/organizations/policies/*",
"@bitwarden/web-vault/*",
"src/**/*"
],
"paths": ["@fluffy-spoon/substitute"]
}
]
}

12
apps/web/config/ee.json Normal file
View File

@@ -0,0 +1,12 @@
{
"dev": {
"proxyApi": "http://localhost:4001",
"proxyIdentity": "http://localhost:33657",
"proxyEvents": "http://localhost:46274",
"proxyNotifications": "http://localhost:61841",
"port": 8081
},
"flags": {
"showTrial": false
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@bitwarden/web-vault",
"version": "2022.9.2",
"version": "2022.10.0",
"scripts": {
"build:oss": "webpack",
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",
@@ -15,6 +15,7 @@
"build:bit:selfhost:watch": "cross-env ENV=selfhosted npm run build:bit:watch",
"build:oss:selfhost:prod": "cross-env ENV=selfhosted NODE_ENV=production npm run build:oss",
"build:bit:selfhost:prod": "cross-env ENV=selfhosted NODE_ENV=production npm run build:bit",
"build:bit:ee": "cross-env NODE_ENV=production ENV=ee npm run build:bit",
"clean:l10n": "git push origin --delete l10n_master",
"dist:bit:cloud": "npm run build:bit:cloud",
"dist:oss:selfhost": "npm run build:oss:selfhost:prod",

View File

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

View File

@@ -79,18 +79,14 @@
bitButton
buttonType="primary"
type="submit"
class="tw-inline-block tw-w-1/2"
[block]="true"
[loading]="form.loading"
[disabled]="form.loading"
>
<span> <i class="bwi bwi-sign-in"></i> {{ "logIn" | i18n }} </span>
</button>
<a
bitButton
buttonType="secondary"
routerLink="/register"
class="tw-inline-block tw-w-1/2"
>
<a bitButton buttonType="secondary" routerLink="/register" [block]="true">
<i class="bwi bwi-pencil-square"></i>
{{ "createAccount" | i18n }}
</a>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,6 @@ import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
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 { CryptoService } from "@bitwarden/common/abstractions/crypto.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 { SearchService } from "@bitwarden/common/abstractions/search.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 { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType";
import { ProviderUserStatusType } from "@bitwarden/common/enums/providerUserStatusType";

View File

@@ -1,16 +1,32 @@
import { Injectable } from "@angular/core";
import { Injectable, OnDestroy, OnInit } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { DeviceType } from "@bitwarden/common/enums/deviceType";
import { EventType } from "@bitwarden/common/enums/eventType";
import { PolicyType } from "@bitwarden/common/enums/policyType";
import { Policy } from "@bitwarden/common/models/domain/policy";
import { EventResponse } from "@bitwarden/common/models/response/eventResponse";
@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) {}
ngOnInit(): void {
this.policyService.policies$.pipe(takeUntil(this.destroy$)).subscribe((policies) => {
this.policies = policies;
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
getDefaultDateFilters() {
const d = new Date();
const end = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59);
@@ -326,8 +342,7 @@ export class EventService {
case EventType.Policy_Updated: {
msg = this.i18nService.t("modifiedPolicyId", this.formatPolicyId(ev));
const policies = await this.policyService.getAll();
const policy = policies.filter((p) => p.id === ev.policyId)[0];
const policy = this.policies.filter((p) => p.id === ev.policyId)[0];
let p1 = this.getShortId(ev.policyId);
if (policy != null) {
p1 = PolicyType[policy.type];
@@ -464,16 +479,14 @@ export class EventService {
private formatGroupId(ev: EventResponse) {
const shortId = this.getShortId(ev.groupId);
const a = this.makeAnchor(shortId);
a.setAttribute(
"href",
"#/organizations/" + ev.organizationId + "/manage/groups?search=" + shortId
);
a.setAttribute("href", "#/organizations/" + ev.organizationId + "/groups?search=" + shortId);
return a.outerHTML;
}
private formatCollectionId(ev: EventResponse) {
const shortId = this.getShortId(ev.collectionId);
const a = this.makeAnchor(shortId);
// TODO: Update view/edit collection link after EC-14 is completed
a.setAttribute(
"href",
"#/organizations/" + ev.organizationId + "/manage/collections?search=" + shortId
@@ -488,7 +501,7 @@ export class EventService {
"href",
"#/organizations/" +
ev.organizationId +
"/manage/people?search=" +
"/members?search=" +
shortId +
"&viewEvents=" +
ev.organizationUserId

View File

@@ -1,5 +1,6 @@
import { Component, OnInit } from "@angular/core";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { concatMap, Subject, takeUntil } from "rxjs";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
import { BillingHistoryResponse } from "@bitwarden/common/models/response/billingHistoryResponse";
@@ -8,25 +9,35 @@ import { BillingHistoryResponse } from "@bitwarden/common/models/response/billin
selector: "app-org-billing-history-view",
templateUrl: "organization-billing-history-view.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class OrgBillingHistoryViewComponent implements OnInit {
export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy {
loading = false;
firstLoaded = false;
billing: BillingHistoryResponse;
organizationId: string;
private destroy$ = new Subject<void>();
constructor(
private organizationApiService: OrganizationApiServiceAbstraction,
private route: ActivatedRoute
) {}
async ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
await this.load();
this.firstLoaded = true;
});
this.route.params
.pipe(
concatMap(async (params) => {
this.organizationId = params.organizationId;
await this.load();
this.firstLoaded = true;
}),
takeUntil(this.destroy$)
)
.subscribe();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
async load() {

View File

@@ -1,5 +1,6 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { concatMap, Subject, takeUntil } from "rxjs";
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
import { ModalService } from "@bitwarden/angular/services/modal.service";
@@ -26,17 +27,13 @@ import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
selector: "app-org-subscription",
templateUrl: "organization-subscription.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class OrganizationSubscriptionComponent implements OnInit {
export class OrganizationSubscriptionComponent implements OnInit, OnDestroy {
@ViewChild("setupBillingSyncTemplate", { read: ViewContainerRef, static: true })
setupBillingSyncModalRef: ViewContainerRef;
loading = false;
firstLoaded = false;
organizationId: string;
adjustSeatsAdd = true;
showAdjustSeats = false;
showAdjustSeatAutoscale = false;
adjustStorageAdd = true;
showAdjustStorage = false;
showUpdateLicense = false;
@@ -58,6 +55,8 @@ export class OrganizationSubscriptionComponent implements OnInit {
billingSyncKeyViewContainerRef: ViewContainerRef;
billingSyncKeyRef: [ModalRef, BillingSyncKeyComponent];
private destroy$ = new Subject<void>();
constructor(
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
@@ -73,19 +72,27 @@ export class OrganizationSubscriptionComponent implements OnInit {
}
async ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
await this.load();
this.firstLoaded = true;
});
this.route.params
.pipe(
concatMap(async (params) => {
this.organizationId = params.organizationId;
await this.load();
this.firstLoaded = true;
}),
takeUntil(this.destroy$)
)
.subscribe();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
async load() {
if (this.loading) {
return;
}
this.loading = true;
this.userOrg = this.organizationService.get(this.organizationId);
if (this.userOrg.canManageBilling) {
@@ -172,7 +179,7 @@ export class OrganizationSubscriptionComponent implements OnInit {
this.showChangePlan = !this.showChangePlan;
}
closeChangePlan(changed: boolean) {
closeChangePlan() {
this.showChangePlan = false;
}
@@ -189,10 +196,14 @@ export class OrganizationSubscriptionComponent implements OnInit {
comp.hasBillingToken = this.hasBillingSyncToken;
}
);
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
ref.onClosed.subscribe(async () => {
await this.load();
});
ref.onClosed
.pipe(
concatMap(async () => {
await this.load();
}),
takeUntil(this.destroy$)
)
.subscribe();
}
closeDownloadLicense() {

View File

@@ -1,54 +1,57 @@
<div class="page-header d-flex">
<div class="tw-mb-4">
<h1>{{ "eventLogs" | i18n }}</h1>
<div class="ml-auto d-flex">
<div class="form-inline">
<label class="sr-only" for="start">{{ "startDate" | i18n }}</label>
<div class="tw-mt-4 tw-flex tw-items-center">
<bit-form-field>
<bit-label>{{ "from" | i18n }}</bit-label>
<input
bitInput
type="datetime-local"
class="form-control form-control-sm"
id="start"
placeholder="{{ 'startDate' | i18n }}"
[(ngModel)]="start"
placeholder="YYYY-MM-DDTHH:MM"
(change)="dirtyDates = true"
/>
<span class="mx-2">-</span>
<label class="sr-only" for="end">{{ "endDate" | i18n }}</label>
</bit-form-field>
<span class="tw-mx-2">-</span>
<bit-form-field>
<bit-label>{{ "to" | i18n }}</bit-label>
<input
bitInput
type="datetime-local"
class="form-control form-control-sm"
id="end"
placeholder="{{ 'endDate' | i18n }}"
[(ngModel)]="end"
placeholder="YYYY-MM-DDTHH:MM"
(change)="dirtyDates = true"
/>
</div>
<form #refreshForm [appApiAction]="refreshPromise" class="d-inline">
</bit-form-field>
<form #refreshForm [appApiAction]="refreshPromise">
<button
class="tw-mx-3 tw-mt-1"
type="button"
class="btn btn-sm btn-outline-primary ml-3"
bitButton
buttonType="primary"
(click)="loadEvents(true)"
[disabled]="loaded && refreshForm.loading"
>
<i
class="bwi bwi-refresh bwi-fw"
aria-hidden="true"
[ngClass]="{ 'bwi-spin': loaded && refreshForm.loading }"
></i>
{{ "refresh" | i18n }}
{{ "update" | i18n }}
</button>
</form>
<form #exportForm [appApiAction]="exportPromise" class="d-inline">
<form #exportForm [appApiAction]="exportPromise">
<button
type="button"
class="btn btn-sm btn-outline-primary btn-submit manual ml-3"
class="tw-mt-1"
bitButton
[ngClass]="{ loading: exportForm.loading }"
(click)="exportEvents()"
[disabled]="(loaded && exportForm.loading) || dirtyDates"
>
<i class="bwi bwi-spinner bwi-spin" aria-hidden="true"></i>
<span>{{ "export" | i18n }}</span>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-sign-in': !exportForm.loading,
'bwi-spinner bwi-spin': exportForm.loading
}"
></i>
</button>
</form>
</div>
@@ -63,45 +66,44 @@
</ng-container>
<ng-container *ngIf="loaded">
<p *ngIf="!events || !events.length">{{ "noEventsInList" | i18n }}</p>
<table class="table table-hover" *ngIf="events && events.length">
<thead>
<bit-table *ngIf="events && events.length">
<ng-container header>
<tr>
<th class="border-top-0" width="210">{{ "timestamp" | i18n }}</th>
<th class="border-top-0" width="40">
<span class="sr-only">{{ "device" | i18n }}</span>
</th>
<th class="border-top-0" width="150">{{ "user" | i18n }}</th>
<th class="border-top-0">{{ "event" | i18n }}</th>
<th bitCell style="width: 210px">{{ "timestamp" | i18n }}</th>
<th bitCell style="width: 100px">{{ "client" | i18n }}</th>
<th bitCell style="width: 150px">{{ "member" | i18n }}</th>
<th bitCell>{{ "event" | i18n }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let e of events">
<td>{{ e.date | date: "medium" }}</td>
<td>
<i
class="text-muted bwi bwi-lg {{ e.appIcon }}"
title="{{ e.appName }}, {{ e.ip }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ e.appName }}, {{ e.ip }}</span>
</ng-container>
<ng-container body>
<tr bitRow *ngFor="let e of events">
<td bitCell>{{ e.date | date: "medium" }}</td>
<td bitCell>
<span title="{{ e.appName }}, {{ e.ip }}">{{ e.appName }}</span>
</td>
<td>
<td bitCell>
<span title="{{ e.userEmail }}">{{ e.userName }}</span>
</td>
<td [innerHTML]="e.message"></td>
<td bitCell [innerHTML]="e.message"></td>
</tr>
</tbody>
</table>
</ng-container>
</bit-table>
<button
#moreBtn
[appApiAction]="morePromise"
type="button"
class="btn btn-block btn-link btn-submit"
bitButton
buttonType="primary"
(click)="loadEvents(false)"
[disabled]="loaded && moreBtn.loading"
*ngIf="continuationToken"
>
<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"
*ngIf="moreBtn.loading"
></i>
<span>{{ "loadMore" | i18n }}</span>
</button>
</ng-container>

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