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:
@@ -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/**/*"] }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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: |
|
||||
We’ve 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.
|
||||
We’ve 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
|
||||
|
||||
16
.github/workflows/build-web-ee.yml
vendored
16
.github/workflows/build-web-ee.yml
vendored
@@ -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'
|
||||
41
.github/workflows/build-web.yml
vendored
41
.github/workflows/build-web.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/release-browser.yml
vendored
4
.github/workflows/release-browser.yml
vendored
@@ -102,7 +102,7 @@ jobs:
|
||||
|
||||
- name: Download latest Release build artifacts
|
||||
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
|
||||
|
||||
16
.github/workflows/release-cli.yml
vendored
16
.github/workflows/release-cli.yml
vendored
@@ -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
|
||||
|
||||
20
.github/workflows/release-desktop-beta.yml
vendored
20
.github/workflows/release-desktop-beta.yml
vendored
@@ -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
|
||||
|
||||
102
.github/workflows/release-desktop.yml
vendored
102
.github/workflows/release-desktop.yml
vendored
@@ -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
|
||||
|
||||
8
.github/workflows/release-web.yml
vendored
8
.github/workflows/release-web.yml
vendored
@@ -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
|
||||
|
||||
123
.github/workflows/staged-rollout-desktop.yml
vendored
Normal file
123
.github/workflows/staged-rollout-desktop.yml
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
name: Staged Rollout Desktop
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
rollout_percentage:
|
||||
description: 'Staged Rollout Percentage'
|
||||
required: true
|
||||
default: '10'
|
||||
type: string
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
rollout:
|
||||
name: Update Rollout Percentage
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
release-version: ${{ steps.version.outputs.version }}
|
||||
release-channel: ${{ steps.release-channel.outputs.channel }}
|
||||
steps:
|
||||
- name: Login to Azure
|
||||
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
|
||||
with:
|
||||
keyvault: "bitwarden-prod-kv"
|
||||
secrets: "aws-electron-access-id,
|
||||
aws-electron-access-key,
|
||||
aws-electron-bucket-name,
|
||||
r2-electron-access-id,
|
||||
r2-electron-access-key,
|
||||
r2-electron-bucket-name,
|
||||
cf-prod-account"
|
||||
|
||||
- name: Download channel update info files from S3
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }}
|
||||
AWS_DEFAULT_REGION: 'us-west-2'
|
||||
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }}
|
||||
run: |
|
||||
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest.yml . \
|
||||
--quiet
|
||||
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-linux.yml . \
|
||||
--quiet
|
||||
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-mac.yml . \
|
||||
--quiet
|
||||
|
||||
- name: Download channel update info files from R2
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }}
|
||||
AWS_DEFAULT_REGION: 'us-east-1'
|
||||
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }}
|
||||
CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }}
|
||||
run: |
|
||||
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest.yml . \
|
||||
--quiet \
|
||||
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
|
||||
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-linux.yml . \
|
||||
--quiet \
|
||||
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
|
||||
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-mac.yml . \
|
||||
--quiet \
|
||||
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
|
||||
|
||||
- name: Check new rollout percentage
|
||||
env:
|
||||
NEW_PCT: ${{ github.event.inputs.rollout_percentage }}
|
||||
run: |
|
||||
CURRENT_PCT=$(sed -r -n "s/stagingPercentage:\s([0-9]+)/\1/p" latest.yml)
|
||||
echo "Current percentage: ${CURRENT_PCT}"
|
||||
echo "New percentage: ${NEW_PCT}"
|
||||
echo
|
||||
if [ "$NEW_PCT" -le "$CURRENT_PCT" ]; then
|
||||
echo "New percentage (${NEW_PCT}) must be higher than current percentage (${CURRENT_PCT})!"
|
||||
echo
|
||||
echo "If you want to pull a staged release because it hasn’t gone well, you must increment the version \
|
||||
number higher than your broken release. Because some of your users will be on the broken 1.0.1, \
|
||||
releasing a new 1.0.1 would result in them staying on a broken version.”
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Set staged rollout percentage
|
||||
env:
|
||||
ROLLOUT_PCT: ${{ github.event.inputs.rollout_percentage }}
|
||||
run: |
|
||||
sed -i -r "/stagingPercentage/s/[0-9]+/${ROLLOUT_PCT}/" latest.yml
|
||||
sed -i -r "/stagingPercentage/s/[0-9]+/${ROLLOUT_PCT}/" latest-linux.yml
|
||||
sed -i -r "/stagingPercentage/s/[0-9]+/${ROLLOUT_PCT}/" latest-mac.yml
|
||||
|
||||
- name: Publish channel update info files to S3
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }}
|
||||
AWS_DEFAULT_REGION: 'us-west-2'
|
||||
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }}
|
||||
run: |
|
||||
aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \
|
||||
--include "latest*.yml" \
|
||||
--acl "public-read" \
|
||||
--quiet
|
||||
|
||||
- name: Publish channel update info files to R2
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }}
|
||||
AWS_DEFAULT_REGION: 'us-east-1'
|
||||
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }}
|
||||
CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }}
|
||||
run: |
|
||||
aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \
|
||||
--include "latest*.yml" \
|
||||
--quiet \
|
||||
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
|
||||
30
.github/workflows/version-auto-bump.yml
vendored
30
.github/workflows/version-auto-bump.yml
vendored
@@ -12,7 +12,7 @@ defaults:
|
||||
jobs:
|
||||
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
1
.gitignore
vendored
@@ -40,3 +40,4 @@ coverage
|
||||
# Storybook
|
||||
documentation.json
|
||||
.eslintcache
|
||||
storybook-static
|
||||
|
||||
@@ -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": "Выпадкова"
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "văzut ultima dată la $DATE$",
|
||||
"message": "văzut ultima dată pe: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -2017,7 +2017,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on $DATE$",
|
||||
"message": "last seen on: $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { AutofillService as AbstractAutoFillService } from "../../services/abstractions/autofill.service";
|
||||
import AutofillService from "../../services/autofill.service";
|
||||
|
||||
import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory";
|
||||
import { EventServiceInitOptions, eventServiceFactory } from "./event-service.factory";
|
||||
import { CachedServices, factory, FactoryOptions } from "./factory-options";
|
||||
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
|
||||
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
|
||||
import { totpServiceFactory, TotpServiceInitOptions } from "./totp-service.factory";
|
||||
|
||||
type AutoFillServiceOptions = FactoryOptions;
|
||||
|
||||
export type AutoFillServiceInitOptions = AutoFillServiceOptions &
|
||||
CipherServiceInitOptions &
|
||||
StateServiceInitOptions &
|
||||
TotpServiceInitOptions &
|
||||
EventServiceInitOptions &
|
||||
LogServiceInitOptions;
|
||||
|
||||
export function autofillServiceFactory(
|
||||
cache: { autofillService?: AbstractAutoFillService } & CachedServices,
|
||||
opts: AutoFillServiceInitOptions
|
||||
): Promise<AbstractAutoFillService> {
|
||||
return factory(
|
||||
cache,
|
||||
"autofillService",
|
||||
opts,
|
||||
async () =>
|
||||
new AutofillService(
|
||||
await cipherServiceFactory(cache, opts),
|
||||
await stateServiceFactory(cache, opts),
|
||||
await totpServiceFactory(cache, opts),
|
||||
await eventServiceFactory(cache, opts),
|
||||
await logServiceFactory(cache, opts)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export function cipherServiceFactory(
|
||||
await apiServiceFactory(cache, opts),
|
||||
await fileUploadServiceFactory(cache, opts),
|
||||
await i18nServiceFactory(cache, opts),
|
||||
opts.cipherServiceOptions.searchServiceFactory === undefined
|
||||
opts.cipherServiceOptions?.searchServiceFactory === undefined
|
||||
? () => cache.searchService
|
||||
: opts.cipherServiceOptions.searchServiceFactory,
|
||||
await logServiceFactory(cache, opts),
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { EventService as AbstractEventService } from "@bitwarden/common/abstractions/event.service";
|
||||
import { EventService } from "@bitwarden/common/services/event.service";
|
||||
|
||||
import { apiServiceFactory, ApiServiceInitOptions } from "./api-service.factory";
|
||||
import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory";
|
||||
import { FactoryOptions, CachedServices, factory } from "./factory-options";
|
||||
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
|
||||
import {
|
||||
organizationServiceFactory,
|
||||
OrganizationServiceInitOptions,
|
||||
} from "./organization-service.factory";
|
||||
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
|
||||
|
||||
type EventServiceOptions = FactoryOptions;
|
||||
|
||||
export type EventServiceInitOptions = EventServiceOptions &
|
||||
ApiServiceInitOptions &
|
||||
CipherServiceInitOptions &
|
||||
StateServiceInitOptions &
|
||||
LogServiceInitOptions &
|
||||
OrganizationServiceInitOptions;
|
||||
|
||||
export function eventServiceFactory(
|
||||
cache: { eventService?: AbstractEventService } & CachedServices,
|
||||
opts: EventServiceInitOptions
|
||||
): Promise<AbstractEventService> {
|
||||
return factory(
|
||||
cache,
|
||||
"eventService",
|
||||
opts,
|
||||
async () =>
|
||||
new EventService(
|
||||
await apiServiceFactory(cache, opts),
|
||||
await cipherServiceFactory(cache, opts),
|
||||
await stateServiceFactory(cache, opts),
|
||||
await logServiceFactory(cache, opts),
|
||||
await organizationServiceFactory(cache, opts)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -28,7 +28,8 @@ export function platformUtilsServiceFactory(
|
||||
new BrowserPlatformUtilsService(
|
||||
await messagingServiceFactory(cache, opts),
|
||||
opts.platformUtilsServiceOptions.clipboardWriteCallback,
|
||||
opts.platformUtilsServiceOptions.biometricCallback
|
||||
opts.platformUtilsServiceOptions.biometricCallback,
|
||||
opts.platformUtilsServiceOptions.win
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { TotpService as AbstractTotpService } from "@bitwarden/common/abstractions/totp.service";
|
||||
import { TotpService } from "@bitwarden/common/services/totp.service";
|
||||
|
||||
import {
|
||||
cryptoFunctionServiceFactory,
|
||||
CryptoFunctionServiceInitOptions,
|
||||
} from "./crypto-function-service.factory";
|
||||
import { CachedServices, factory, FactoryOptions } from "./factory-options";
|
||||
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
|
||||
|
||||
type TotpServiceOptions = FactoryOptions;
|
||||
|
||||
export type TotpServiceInitOptions = TotpServiceOptions &
|
||||
CryptoFunctionServiceInitOptions &
|
||||
LogServiceInitOptions;
|
||||
|
||||
export function totpServiceFactory(
|
||||
cache: { totpService?: AbstractTotpService } & CachedServices,
|
||||
opts: TotpServiceInitOptions
|
||||
): Promise<AbstractTotpService> {
|
||||
return factory(
|
||||
cache,
|
||||
"totpService",
|
||||
opts,
|
||||
async () =>
|
||||
new TotpService(
|
||||
await cryptoFunctionServiceFactory(cache, opts),
|
||||
await logServiceFactory(cache, opts)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,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")) {
|
||||
|
||||
132
apps/browser/src/globals.d.ts
vendored
132
apps/browser/src/globals.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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": "Выпадкова"
|
||||
|
||||
@@ -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(
|
||||
|
||||
4
apps/desktop/src/package-lock.json
generated
4
apps/desktop/src/package-lock.json
generated
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -154,6 +154,10 @@
|
||||
margin-left: auto;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.filter-button {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
12
apps/web/config/ee.json
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 === "") {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user