1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-03-12 13:39:14 +01:00

Merge branch 'main' into autofill/pm-6546-blurring-of-autofilled-elements-causes-problems-in-blur-event-listeners

This commit is contained in:
Cesar Gonzalez 2024-03-07 13:25:14 -06:00 committed by GitHub
commit ab734d2281
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
76 changed files with 961 additions and 472 deletions

View File

@ -29,7 +29,7 @@ jobs:
secrets: "brew-bump-workflow-pat"
- name: Update Homebrew formula
uses: dawidd6/action-homebrew-bump-formula@75ed025ff3ad1d617862838b342b06d613a0ddf3 # v3.10.1
uses: dawidd6/action-homebrew-bump-formula@baf2b60c51fc1f8453c884b0c61052668a71bd1d # v3.11.0
with:
# Required, custom GitHub access token with the 'public_repo' and 'workflow' scopes
token: ${{ steps.retrieve-secrets.outputs.brew-bump-workflow-pat }}

View File

@ -371,7 +371,7 @@ jobs:
secrets: "crowdin-api-token"
- name: Upload Sources
uses: crowdin/github-action@198daeb2d30636c4608d6a6bb96c009dbefc02a2 # v1.18.0
uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}

View File

@ -297,7 +297,7 @@ jobs:
echo "BW Package Version: $_PACKAGE_VERSION"
- name: Get bw linux cli
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: bw-linux-${{ env._PACKAGE_VERSION }}.zip
path: apps/cli/dist/snap

View File

@ -167,7 +167,7 @@ jobs:
working-directory: ./
- name: Cache Native Module
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
id: cache
with:
path: |
@ -296,7 +296,7 @@ jobs:
working-directory: ./
- name: Cache Native Module
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
id: cache
with:
path: apps/desktop/desktop_native/*.node
@ -476,14 +476,14 @@ jobs:
- name: Cache Build
id: build-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Cache Safari
id: safari-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@ -576,7 +576,7 @@ jobs:
working-directory: ./
- name: Cache Native Module
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
id: cache
with:
path: apps/desktop/desktop_native/*.node
@ -637,14 +637,14 @@ jobs:
- name: Get Build Cache
id: build-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@ -737,7 +737,7 @@ jobs:
working-directory: ./
- name: Cache Native Module
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
id: cache
with:
path: apps/desktop/desktop_native/*.node
@ -753,7 +753,7 @@ jobs:
run: npm run build
- name: Download Browser artifact
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
path: ${{ github.workspace }}/browser-build-artifacts
@ -843,14 +843,14 @@ jobs:
- name: Get Build Cache
id: build-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@ -943,7 +943,7 @@ jobs:
working-directory: ./
- name: Cache Native Module
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
id: cache
with:
path: apps/desktop/desktop_native/*.node
@ -959,7 +959,7 @@ jobs:
run: npm run build
- name: Download Browser artifact
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
path: ${{ github.workspace }}/browser-build-artifacts
@ -1036,14 +1036,14 @@ jobs:
- name: Get Build Cache
id: build-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@ -1136,7 +1136,7 @@ jobs:
working-directory: ./
- name: Cache Native Module
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
id: cache
with:
path: apps/desktop/desktop_native/*.node
@ -1152,7 +1152,7 @@ jobs:
run: npm run build
- name: Download Browser artifact
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
path: ${{ github.workspace }}/browser-build-artifacts
@ -1211,7 +1211,7 @@ jobs:
secrets: "crowdin-api-token"
- name: Upload Sources
uses: crowdin/github-action@198daeb2d30636c4608d6a6bb96c009dbefc02a2 # v1.18.0
uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}

View File

@ -194,7 +194,7 @@ jobs:
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Download ${{ matrix.artifact_name }} artifact
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: web-${{ env._VERSION }}-${{ matrix.artifact_name }}.zip
path: apps/web
@ -270,7 +270,7 @@ jobs:
secrets: "crowdin-api-token"
- name: Upload Sources
uses: crowdin/github-action@198daeb2d30636c4608d6a6bb96c009dbefc02a2 # v1.18.0
uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}

View File

@ -33,7 +33,7 @@ jobs:
- name: Cache npm
id: npm-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-chromatic-${{ hashFiles('**/package-lock.json') }}
@ -46,7 +46,7 @@ jobs:
run: npm run build-storybook:ci
- name: Publish to Chromatic
uses: chromaui/action@76bda3648003815314bd50adaa553ee612a7f36c # v10.9.2
uses: chromaui/action@c9067691aca4a28d6fbb40d9eea6e144369fbcae # v10.9.5
with:
token: ${{ secrets.GITHUB_TOKEN }}
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

View File

@ -19,14 +19,14 @@ on:
description: "Branch or Tag name to deploy (examples: 'main', 'feature/sm', 'web-v2023.12.0')"
type: string
default: main
invert-default-sync-delete-destination-files-value:
description: "Invert the default sync-delete-destination-files value"
force-delete-destination:
description: "Delete remote files that are not found locally"
type: boolean
default: false
debug:
description: "Debug mode"
type: boolean
default: false
default: true
workflow_call:
inputs:
@ -38,8 +38,8 @@ on:
description: "Branch or Tag name to deploy (examples: 'main', 'feature/sm', 'web-v2023.12.0')"
type: string
default: main
invert-default-sync-delete-destination-files-value:
description: "Invert the default sync-delete-destination-files value"
force-delete-destination:
description: "Delete remote files that are not found locally"
type: boolean
default: false
debug:
@ -71,14 +71,6 @@ jobs:
echo "configuring the Web deploy for ${{ inputs.environment }}"
echo "environment=${{ inputs.environment }}" >> $GITHUB_OUTPUT
# Invert the default value for sync-delete-destination-files
if [ ${{ inputs.invert-default-sync-delete-destination-files-value }} ]; then
echo "sync-delete-destination-files=true" >> $GITHUB_OUTPUT
else
# This is the default value for USQA, EUQA, USPROD, and EUPROD
echo "sync-delete-destination-files=false" >> $GITHUB_OUTPUT
fi
case ${{ inputs.environment }} in
"USQA")
echo "azure-login-creds=AZURE_KV_US_QA_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT
@ -114,13 +106,6 @@ jobs:
echo "environment-artifact=web-*-cloud-usdev.zip" >> $GITHUB_OUTPUT
echo "environment-name=Web Vault - US Development Cloud" >> $GITHUB_OUTPUT
echo "environment-url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT
if [ ${{ inputs.invert-default-sync-delete-destination-files-value }} ]; then
echo "sync-delete-destination-files=false" >> $GITHUB_OUTPUT
else
# This is the default value for USDEV
echo "sync-delete-destination-files=true" >> $GITHUB_OUTPUT
fi
;;
esac
# Set the sync utility to use for deployment to the environment (az-sync or azcopy)
@ -285,7 +270,7 @@ jobs:
--source "./build" \
--container '$web' \
--connection-string "${{ steps.retrieve-secrets-az-sync.outputs.sa-bitwarden-web-vault-dev-key-temp }}" \
--delete-destination=${{ needs.setup.outputs.sync-delete-destination-files }}
--delete-destination=${{ inputs.force-delete-destination }}
- name: Sync to Azure Storage Account using azcopy
if: ${{ needs.setup.outputs.sync-utility == 'azcopy' }}
@ -297,7 +282,7 @@ jobs:
AZCOPY_TENANT_ID: ${{ steps.retrieve-secrets-azcopy.outputs.sp-bitwarden-web-vault-tenant }}
run: |
azcopy sync ./build 'https://${{ steps.retrieve-secrets-azcopy.outputs.sa-bitwarden-web-vault-name }}.blob.core.windows.net/$web/' \
--delete-destination=${{ needs.setup.outputs.sync-delete-destination-files }} --compare-hash="MD5"
--delete-destination=${{ inputs.force-delete-destination }} --compare-hash="MD5"
- name: Debug sync logs
if: ${{ inputs.debug }}
@ -309,7 +294,7 @@ jobs:
- name: Update deployment status to Success
if: success()
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
with:
token: '${{ secrets.GITHUB_TOKEN }}'
environment-url: ${{ env._ENVIRONMENT_URL }}
@ -318,7 +303,7 @@ jobs:
- name: Update deployment status to Failure
if: failure()
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
with:
token: '${{ secrets.GITHUB_TOKEN }}'
environment-url: ${{ env._ENVIRONMENT_URL }}

View File

@ -155,7 +155,7 @@ jobs:
- name: Update deployment status to Success
if: ${{ success() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'success'
@ -163,7 +163,7 @@ jobs:
- name: Update deployment status to Failure
if: ${{ failure() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'failure'

View File

@ -118,7 +118,7 @@ jobs:
- name: Update deployment status to Success
if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'success'
@ -126,7 +126,7 @@ jobs:
- name: Update deployment status to Failure
if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'failure'

View File

@ -424,14 +424,14 @@ jobs:
- name: Cache Build
id: build-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Cache Safari
id: safari-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@ -555,14 +555,14 @@ jobs:
- name: Get Build Cache
id: build-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@ -659,7 +659,7 @@ jobs:
- name: Download artifact from hotfix-rc
if: github.ref == 'refs/heads/hotfix-rc'
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
with:
workflow: build-browser.yml
workflow_conclusion: success
@ -668,7 +668,7 @@ jobs:
- name: Download artifact from rc
if: github.ref == 'refs/heads/rc'
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
with:
workflow: build-browser.yml
workflow_conclusion: success
@ -677,7 +677,7 @@ jobs:
- name: Download artifacts from main
if: ${{ github.ref != 'refs/heads/rc' && github.ref != 'refs/heads/hotfix-rc' }}
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
with:
workflow: build-browser.yml
workflow_conclusion: success
@ -765,14 +765,14 @@ jobs:
- name: Get Build Cache
id: build-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/desktop/build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: apps/browser/dist/Safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@ -864,7 +864,7 @@ jobs:
- name: Download artifact from hotfix-rc
if: github.ref == 'refs/heads/hotfix-rc'
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
with:
workflow: build-browser.yml
workflow_conclusion: success
@ -873,7 +873,7 @@ jobs:
- name: Download artifact from rc
if: github.ref == 'refs/heads/rc'
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
with:
workflow: build-browser.yml
workflow_conclusion: success
@ -882,7 +882,7 @@ jobs:
- name: Download artifact from main
if: ${{ github.ref != 'refs/heads/rc' && github.ref != 'refs/heads/hotfix-rc' }}
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
with:
workflow: build-browser.yml
workflow_conclusion: success
@ -953,7 +953,7 @@ jobs:
cf-prod-account"
- name: Download all artifacts
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
path: apps/desktop/artifacts
@ -992,7 +992,7 @@ jobs:
- name: Update deployment status to Success
if: ${{ success() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'success'
@ -1000,7 +1000,7 @@ jobs:
- name: Update deployment status to Failure
if: ${{ failure() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'failure'

View File

@ -231,7 +231,7 @@ jobs:
- name: Update deployment status to Success
if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'success'
@ -239,7 +239,7 @@ jobs:
- name: Update deployment status to Failure
if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'failure'

View File

@ -268,7 +268,7 @@ jobs:
- name: Update deployment status to Success
if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
with:
token: '${{ secrets.GITHUB_TOKEN }}'
environment-url: http://vault.bitwarden.com
@ -277,7 +277,7 @@ jobs:
- name: Update deployment status to Failure
if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
with:
token: '${{ secrets.GITHUB_TOKEN }}'
environment-url: http://vault.bitwarden.com

View File

@ -63,7 +63,7 @@ jobs:
fail-on-error: true
- name: Upload to codecov.io
uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1
uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@ -4,6 +4,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { NOTIFICATION_BAR_LIFESPAN_MS } from "@bitwarden/common/autofill/constants";
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -19,7 +20,6 @@ import { openUnlockPopout } from "../../auth/popup/utils/auth-popout-window";
import { BrowserApi } from "../../platform/browser/browser-api";
import { BrowserStateService } from "../../platform/services/abstractions/browser-state.service";
import { openAddEditVaultItemPopout } from "../../vault/popup/utils/vault-popout-window";
import { NOTIFICATION_BAR_LIFESPAN_MS } from "../constants";
import { NotificationQueueMessageType } from "../enums/notification-queue-message-type.enum";
import { AutofillService } from "../services/abstractions/autofill.service";

View File

@ -2,6 +2,10 @@ import { mock, mockReset } from "jest-mock-extended";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import {
SHOW_AUTOFILL_BUTTON,
AutofillOverlayVisibility,
} from "@bitwarden/common/autofill/constants";
import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
@ -15,7 +19,6 @@ import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
import { BrowserApi } from "../../platform/browser/browser-api";
import BrowserPlatformUtilsService from "../../platform/services/browser-platform-utils.service";
import { BrowserStateService } from "../../platform/services/browser-state.service";
import { SHOW_AUTOFILL_BUTTON } from "../constants";
import { AutofillService } from "../services/abstractions/autofill.service";
import {
createAutofillPageDetailsMock,
@ -28,7 +31,6 @@ import { flushPromises, sendExtensionRuntimeMessage, sendPortMessage } from "../
import {
AutofillOverlayElement,
AutofillOverlayPort,
AutofillOverlayVisibility,
RedirectFocusDirection,
} from "../utils/autofill-overlay.enum";

View File

@ -3,7 +3,9 @@ import { firstValueFrom } from "rxjs";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { SHOW_AUTOFILL_BUTTON } from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -22,13 +24,8 @@ import {
openViewVaultItemPopout,
openAddEditVaultItemPopout,
} from "../../vault/popup/utils/vault-popout-window";
import { SHOW_AUTOFILL_BUTTON } from "../constants";
import { AutofillService, PageDetail } from "../services/abstractions/autofill.service";
import {
InlineMenuVisibilitySetting,
AutofillOverlayElement,
AutofillOverlayPort,
} from "../utils/autofill-overlay.enum";
import { AutofillOverlayElement, AutofillOverlayPort } from "../utils/autofill-overlay.enum";
import { LockedVaultPendingNotificationsData } from "./abstractions/notification.background";
import {

View File

@ -3,14 +3,6 @@ import { mock, MockProxy } from "jest-mock-extended";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
AUTOFILL_ID,
COPY_PASSWORD_ID,
@ -18,7 +10,14 @@ import {
COPY_VERIFICATION_CODE_ID,
GENERATE_PASSWORD_ID,
NOOP_COMMAND_SUFFIX,
} from "../constants";
} from "@bitwarden/common/autofill/constants";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
CopyToClipboardAction,

View File

@ -2,6 +2,20 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import {
AUTOFILL_CARD_ID,
AUTOFILL_ID,
AUTOFILL_IDENTITY_ID,
COPY_IDENTIFIER_ID,
COPY_PASSWORD_ID,
COPY_USERNAME_ID,
COPY_VERIFICATION_CODE_ID,
CREATE_CARD_ID,
CREATE_IDENTITY_ID,
CREATE_LOGIN_ID,
GENERATE_PASSWORD_ID,
NOOP_COMMAND_SUFFIX,
} from "@bitwarden/common/autofill/constants";
import { EventType } from "@bitwarden/common/enums";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
@ -38,20 +52,6 @@ import { LockedVaultPendingNotificationsData } from "../background/abstractions/
import { autofillServiceFactory } from "../background/service_factories/autofill-service.factory";
import { copyToClipboard, GeneratePasswordToClipboardCommand } from "../clipboard";
import { AutofillTabCommand } from "../commands/autofill-tab-command";
import {
AUTOFILL_CARD_ID,
AUTOFILL_ID,
AUTOFILL_IDENTITY_ID,
COPY_IDENTIFIER_ID,
COPY_PASSWORD_ID,
COPY_USERNAME_ID,
COPY_VERIFICATION_CODE_ID,
CREATE_CARD_ID,
CREATE_IDENTITY_ID,
CREATE_LOGIN_ID,
GENERATE_PASSWORD_ID,
NOOP_COMMAND_SUFFIX,
} from "../constants";
import { AutofillCipherTypeId } from "../types";
export type CopyToClipboardOptions = { text: string; tab: chrome.tabs.Tab };

View File

@ -1,5 +1,6 @@
import { mock, MockProxy } from "jest-mock-extended";
import { NOOP_COMMAND_SUFFIX } from "@bitwarden/common/autofill/constants";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { CipherType } from "@bitwarden/common/vault/enums";
@ -7,7 +8,6 @@ import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { BrowserStateService } from "../../platform/services/abstractions/browser-state.service";
import { NOOP_COMMAND_SUFFIX } from "../constants";
import { MainContextMenuHandler } from "./main-context-menu-handler";

View File

@ -1,3 +1,19 @@
import {
AUTOFILL_CARD_ID,
AUTOFILL_ID,
AUTOFILL_IDENTITY_ID,
COPY_IDENTIFIER_ID,
COPY_PASSWORD_ID,
COPY_USERNAME_ID,
COPY_VERIFICATION_CODE_ID,
CREATE_CARD_ID,
CREATE_IDENTITY_ID,
CREATE_LOGIN_ID,
GENERATE_PASSWORD_ID,
NOOP_COMMAND_SUFFIX,
ROOT_ID,
SEPARATOR_ID,
} from "@bitwarden/common/autofill/constants";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
@ -21,22 +37,6 @@ import {
StateServiceInitOptions,
} from "../../platform/background/service-factories/state-service.factory";
import { BrowserStateService } from "../../platform/services/abstractions/browser-state.service";
import {
AUTOFILL_CARD_ID,
AUTOFILL_ID,
AUTOFILL_IDENTITY_ID,
COPY_IDENTIFIER_ID,
COPY_PASSWORD_ID,
COPY_USERNAME_ID,
COPY_VERIFICATION_CODE_ID,
CREATE_CARD_ID,
CREATE_IDENTITY_ID,
CREATE_LOGIN_ID,
GENERATE_PASSWORD_ID,
NOOP_COMMAND_SUFFIX,
ROOT_ID,
SEPARATOR_ID,
} from "../constants";
import { InitContextMenuItems } from "./abstractions/main-context-menu-handler";

View File

@ -1,12 +1,13 @@
import { mock } from "jest-mock-extended";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
import AutofillPageDetails from "../models/autofill-page-details";
import AutofillScript from "../models/autofill-script";
import AutofillOverlayContentService from "../services/autofill-overlay-content.service";
import { flushPromises, sendExtensionRuntimeMessage } from "../spec/testing-utils";
import { AutofillOverlayVisibility, RedirectFocusDirection } from "../utils/autofill-overlay.enum";
import { RedirectFocusDirection } from "../utils/autofill-overlay.enum";
import { AutofillExtensionMessage } from "./abstractions/autofill-init";
import AutofillInit from "./autofill-init";

View File

@ -1,8 +1,8 @@
import { mock } from "jest-mock-extended";
import { EVENTS } from "@bitwarden/common/autofill/constants";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { EVENTS } from "../../constants";
import { createPortSpyMock } from "../../spec/autofill-mocks";
import {
flushPromises,

View File

@ -1,6 +1,6 @@
import { EVENTS } from "@bitwarden/common/autofill/constants";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { EVENTS } from "../../constants";
import { setElementStyles } from "../../utils";
import {
BackgroundPortMessageHandlers,

View File

@ -1,8 +1,8 @@
import "@webcomponents/custom-elements";
import "lit/polyfill-support.js";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EVENTS } from "@bitwarden/common/autofill/constants";
import { EVENTS } from "../../../constants";
import { buildSvgDomElement } from "../../../utils";
import { logoIcon, logoLockedIcon } from "../../../utils/svg-icons";
import {

View File

@ -1,9 +1,9 @@
import "@webcomponents/custom-elements";
import "lit/polyfill-support.js";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EVENTS } from "@bitwarden/common/autofill/constants";
import { OverlayCipherData } from "../../../background/abstractions/overlay.background";
import { EVENTS } from "../../../constants";
import { buildSvgDomElement } from "../../../utils";
import { globeIcon, lockIcon, plusIcon, viewCipherIcon } from "../../../utils/svg-icons";
import {

View File

@ -1,4 +1,5 @@
import { EVENTS } from "../../../constants";
import { EVENTS } from "@bitwarden/common/autofill/constants";
import { RedirectFocusDirection } from "../../../utils/autofill-overlay.enum";
import {
AutofillOverlayPageElementWindowMessage,

View File

@ -2,7 +2,9 @@ import { Component, OnInit } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
@ -12,10 +14,6 @@ import { DialogService } from "@bitwarden/components";
import { BrowserApi } from "../../../platform/browser/browser-api";
import { enableAccountSwitching } from "../../../platform/flags";
import { AutofillService } from "../../services/abstractions/autofill.service";
import {
AutofillOverlayVisibility,
InlineMenuVisibilitySetting,
} from "../../utils/autofill-overlay.enum";
@Component({
selector: "app-autofill",

View File

@ -1,17 +1,13 @@
import { mock } from "jest-mock-extended";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EVENTS, AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
import { EVENTS } from "../constants";
import AutofillField from "../models/autofill-field";
import { createAutofillFieldMock } from "../spec/autofill-mocks";
import { flushPromises } from "../spec/testing-utils";
import { ElementWithOpId, FormFieldElement } from "../types";
import {
AutofillOverlayElement,
AutofillOverlayVisibility,
RedirectFocusDirection,
} from "../utils/autofill-overlay.enum";
import { AutofillOverlayElement, RedirectFocusDirection } from "../utils/autofill-overlay.enum";
import { AutoFillConstants } from "./autofill-constants";
import AutofillOverlayContentService from "./autofill-overlay-content.service";

View File

@ -3,9 +3,9 @@ import "lit/polyfill-support.js";
import { FocusableElement, tabbable } from "tabbable";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EVENTS, AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
import { FocusedFieldData } from "../background/abstractions/overlay.background";
import { EVENTS } from "../constants";
import AutofillField from "../models/autofill-field";
import AutofillOverlayButtonIframe from "../overlay/iframe-content/autofill-overlay-button-iframe";
import AutofillOverlayListIframe from "../overlay/iframe-content/autofill-overlay-list-iframe";
@ -16,11 +16,7 @@ import {
sendExtensionMessage,
setElementStyles,
} from "../utils";
import {
AutofillOverlayElement,
RedirectFocusDirection,
AutofillOverlayVisibility,
} from "../utils/autofill-overlay.enum";
import { AutofillOverlayElement, RedirectFocusDirection } from "../utils/autofill-overlay.enum";
import {
AutofillOverlayContentService as AutofillOverlayContentServiceInterface,

View File

@ -1,6 +1,7 @@
import { mock, mockReset } from "jest-mock-extended";
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { EventType } from "@bitwarden/common/enums";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -37,7 +38,6 @@ import {
createGenerateFillScriptOptionsMock,
} from "../spec/autofill-mocks";
import { triggerTestFailure } from "../spec/testing-utils";
import { AutofillOverlayVisibility } from "../utils/autofill-overlay.enum";
import {
AutoFillOptions,

View File

@ -4,6 +4,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import { EventType } from "@bitwarden/common/enums";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@ -20,7 +21,6 @@ import { AutofillPort } from "../enums/autofill-port.enums";
import AutofillField from "../models/autofill-field";
import AutofillPageDetails from "../models/autofill-page-details";
import AutofillScript from "../models/autofill-script";
import { InlineMenuVisibilitySetting } from "../utils/autofill-overlay.enum";
import {
AutoFillOptions,

View File

@ -1,4 +1,5 @@
import { EVENTS } from "../constants";
import { EVENTS } from "@bitwarden/common/autofill/constants";
import AutofillScript, { FillScript, FillScriptActions } from "../models/autofill-script";
import { FillableFormFieldElement, FormElementWithAttribute, FormFieldElement } from "../types";

View File

@ -1,4 +1,5 @@
import { EVENTS, TYPE_CHECK } from "../constants";
import { EVENTS, TYPE_CHECK } from "@bitwarden/common/autofill/constants";
import AutofillScript, { AutofillInsertActions, FillScript } from "../models/autofill-script";
import { FormFieldElement } from "../types";
import {

View File

@ -14,19 +14,4 @@ const RedirectFocusDirection = {
Next: "next",
} as const;
const AutofillOverlayVisibility = {
Off: 0,
OnButtonClick: 1,
OnFieldFocus: 2,
} as const;
type InlineMenuVisibilitySetting =
(typeof AutofillOverlayVisibility)[keyof typeof AutofillOverlayVisibility];
export {
AutofillOverlayElement,
AutofillOverlayPort,
RedirectFocusDirection,
AutofillOverlayVisibility,
InlineMenuVisibilitySetting,
};
export { AutofillOverlayElement, AutofillOverlayPort, RedirectFocusDirection };

View File

@ -1,4 +1,5 @@
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -15,7 +16,6 @@ import {
} from "../auth/popup/utils/auth-popout-window";
import { LockedVaultPendingNotificationsData } from "../autofill/background/abstractions/notification.background";
import { AutofillService } from "../autofill/services/abstractions/autofill.service";
import { AutofillOverlayVisibility } from "../autofill/utils/autofill-overlay.enum";
import { BrowserApi } from "../platform/browser/browser-api";
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";

View File

@ -6,6 +6,7 @@ import { SettingsService } from "@bitwarden/common/abstractions/settings.service
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
import { ClearClipboardDelaySetting } from "@bitwarden/common/autofill/types";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
@ -13,7 +14,6 @@ import { ThemeType } from "@bitwarden/common/platform/enums";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { UriMatchType } from "@bitwarden/common/vault/enums";
import { ClearClipboardDelaySetting } from "../../../../../apps/browser/src/autofill/constants";
import { enableAccountSwitching } from "../../platform/flags";
@Component({

View File

@ -5,6 +5,7 @@ import { debounceTime, takeUntil } from "rxjs/operators";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -19,7 +20,6 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { PasswordRepromptService } from "@bitwarden/vault";
import { AutofillService } from "../../../../autofill/services/abstractions/autofill.service";
import { AutofillOverlayVisibility } from "../../../../autofill/utils/autofill-overlay.enum";
import { BrowserApi } from "../../../../platform/browser/browser-api";
import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils";
import { VaultFilterService } from "../../../services/vault-filter.service";
@ -123,15 +123,32 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
.pipe(debounceTime(500), takeUntil(this.destroy$))
.subscribe(() => this.searchVault());
// activate autofill on page load if policy is set
if (await this.getActivateAutofillOnPageLoadFromPolicy()) {
const autofillOnPageLoadOrgPolicy = await firstValueFrom(
this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$,
);
const autofillOnPageLoadPolicyToastHasDisplayed = await firstValueFrom(
this.autofillSettingsService.autofillOnPageLoadPolicyToastHasDisplayed$,
);
// If the org "autofill on page load" policy is set, set the user setting to match it
// @TODO override user setting instead of overwriting
if (autofillOnPageLoadOrgPolicy === true) {
await this.autofillSettingsService.setAutofillOnPageLoad(true);
await this.autofillSettingsService.setActivateAutofillOnPageLoadFromPolicy(false);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("autofillPageLoadPolicyActivated"),
);
if (!autofillOnPageLoadPolicyToastHasDisplayed) {
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("autofillPageLoadPolicyActivated"),
);
await this.autofillSettingsService.setAutofillOnPageLoadPolicyToastHasDisplayed(true);
}
}
// If the org policy is ever disabled after being enabled, reset the toast notification
if (!autofillOnPageLoadOrgPolicy && autofillOnPageLoadPolicyToastHasDisplayed) {
await this.autofillSettingsService.setAutofillOnPageLoadPolicyToastHasDisplayed(false);
}
}
@ -303,10 +320,6 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
this.router.navigate(["autofill"]);
}
private async getActivateAutofillOnPageLoadFromPolicy(): Promise<boolean> {
return await firstValueFrom(this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$);
}
async dismissCallout() {
await this.autofillSettingsService.setAutofillOnPageLoadCalloutIsDismissed(true);
this.showHowToAutofill = false;

View File

@ -31,5 +31,9 @@
"strictTemplates": true,
"preserveWhitespaces": true
},
"include": ["src", "../../libs/common/src/platform/services/**/*.worker.ts"]
"include": [
"src",
"../../libs/common/src/platform/services/**/*.worker.ts",
"../../libs/common/src/autofill/constants"
]
}

View File

@ -1607,21 +1607,31 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.52.0"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
dependencies = [
"windows-core",
"windows-targets 0.52.0",
"windows-targets 0.52.4",
]
[[package]]
name = "windows-core"
version = "0.52.0"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
dependencies = [
"windows-targets 0.52.0",
"windows-result",
"windows-targets 0.52.4",
]
[[package]]
name = "windows-result"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64"
dependencies = [
"windows-targets 0.52.4",
]
[[package]]
@ -1639,7 +1649,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
"windows-targets 0.52.4",
]
[[package]]
@ -1659,17 +1669,17 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
"windows_aarch64_gnullvm 0.52.4",
"windows_aarch64_msvc 0.52.4",
"windows_i686_gnu 0.52.4",
"windows_i686_msvc 0.52.4",
"windows_x86_64_gnu 0.52.4",
"windows_x86_64_gnullvm 0.52.4",
"windows_x86_64_msvc 0.52.4",
]
[[package]]
@ -1680,9 +1690,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
@ -1692,9 +1702,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
@ -1704,9 +1714,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
@ -1716,9 +1726,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
@ -1728,9 +1738,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
@ -1740,9 +1750,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
@ -1752,9 +1762,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "winnow"

View File

@ -33,7 +33,7 @@ napi-build = "=2.0.1"
[target.'cfg(windows)'.dependencies]
widestring = "=1.0.2"
windows = { version = "=0.52.0", features = [
windows = { version = "=0.54.0", features = [
"Foundation",
"Security_Credentials_UI",
"Security_Cryptography",

View File

@ -24,7 +24,7 @@
"**/node_modules/argon2/package.json",
"**/node_modules/argon2/lib/binding/napi-v3/argon2.node"
],
"electronVersion": "28.2.5",
"electronVersion": "28.2.6",
"generateUpdatesFilesForAllChannels": true,
"publish": {
"provider": "generic",

View File

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

View File

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

View File

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

View File

@ -1,33 +1,30 @@
<h1 class="tw-text-4xl !tw-text-alt2">{{ header }}</h1>
<div class="tw-pt-16">
<h2 class="tw-text-2xl tw-font-semibold">
Secure your business with a simpler, faster way to secure and manage secrets
{{ headline }}
</h2>
</div>
<ul class="tw-mt-12 tw-flex tw-flex-col tw-gap-10 tw-text-2xl tw-text-main">
<li>Unlimited secrets, users, and projects</li>
<li>Simple and transparent pricing</li>
<li>End-to-end encryption</li>
<li *ngFor="let primaryPoint of primaryPoints">
{{ primaryPoint }}
</li>
</ul>
<div class="tw-mt-12 tw-flex tw-flex-col">
<div class="tw-rounded-[32px] tw-bg-background">
<div class="tw-my-8 tw-mx-6">
<h2 class="tw-pl-5 tw-font-semibold">Limited time offer</h2>
<h2 class="tw-pl-5 tw-font-semibold">{{ calloutHeadline }}</h2>
<ul class="tw-space-y-4 tw-mt-4 tw-pl-10">
<li>
Sign up today and receive a complimentary 12-month subscription to Bitwarden Password
Manager
<li *ngFor="let callout of callouts">
{{ callout }}
</li>
<li>Experience complete security across your organization</li>
<li>Secure all your sensitive credentials, from passwords to machine secrets</li>
</ul>
</div>
</div>
</div>
<div class="tw-mt-12 tw-flex tw-flex-col tw-items-center tw-gap-5">
<app-review-blurb
header="Businesses trust Bitwarden to secure their secrets"
header="Businesses trust Bitwarden to secure their infrastructure"
quote="At this point, it would be almost impossible to leak our secrets. It's just one less thing we have to worry about."
source="Head of IT, Titanom Technologies"
source="Titanom Technologies"
></app-review-blurb>
</div>

View File

@ -8,6 +8,38 @@ import { Subject, takeUntil } from "rxjs";
})
export class SecretsManagerContentComponent implements OnInit, OnDestroy {
header: string;
headline =
"A simpler, faster way to secure and automate secrets across code and infrastructure deployments";
primaryPoints: string[];
calloutHeadline: string;
callouts: string[];
private paidPrimaryPoints = [
"Unlimited secrets, users, and projects",
"Simple and transparent pricing",
"Zero-knowledge, end-to-end encryption",
];
private paidCalloutHeadline = "Limited time offer";
private paidCallouts = [
"Sign up today and receive a complimentary 12-month subscription to Bitwarden Password Manager",
"Experience complete security across your organization",
"Secure all your sensitive credentials, from user applications to machine secrets",
];
private freePrimaryPoints = [
"Unlimited secrets",
"Simple and transparent pricing",
"Zero-knowledge, end-to-end encryption",
];
private freeCalloutHeadline = "Go beyond developer security!";
private freeCallouts = [
"Your Bitwarden account will also grant complimentary access to Bitwarden Password Manager",
"Extend end-to-end encryption to your personal passwords, addresses, credit cards and notes",
];
private destroy$ = new Subject<void>();
@ -23,13 +55,22 @@ export class SecretsManagerContentComponent implements OnInit, OnDestroy {
switch (queryParameters.org) {
case "enterprise":
this.header = "Secrets Manager for Enterprise";
this.primaryPoints = this.paidPrimaryPoints;
this.calloutHeadline = this.paidCalloutHeadline;
this.callouts = this.paidCallouts;
break;
case "free":
this.header = "Bitwarden Secrets Manager";
this.primaryPoints = this.freePrimaryPoints;
this.calloutHeadline = this.freeCalloutHeadline;
this.callouts = this.freeCallouts;
break;
case "teams":
case "teamsStarter":
this.header = "Secrets Manager for Teams";
this.primaryPoints = this.paidPrimaryPoints;
this.calloutHeadline = this.paidCalloutHeadline;
this.callouts = this.paidCallouts;
break;
}
});

View File

@ -41,14 +41,14 @@
</ul>
</div>
<div class="tw-mb-3 tw-flex">
<button type="button" bitButton buttonType="primary" (click)="navigateTo('vault')">
<button type="button" bitButton buttonType="primary" (click)="navigateToSecretsManager()">
{{ "getStarted" | i18n | titlecase }}
</button>
<button
type="button"
bitButton
buttonType="secondary"
(click)="navigateTo('members')"
(click)="navigateToMembers()"
class="tw-ml-3 tw-inline-flex tw-items-center tw-px-3"
>
{{ "inviteUsers" | i18n }}

View File

@ -78,7 +78,11 @@ export class SecretsManagerTrialFreeStepperComponent implements OnInit {
this.verticalStepper.next();
}
async navigateTo(organizationRoute: string): Promise<void> {
await this.router.navigate(["organizations", this.organizationId, organizationRoute]);
async navigateToMembers(): Promise<void> {
await this.router.navigate(["organizations", this.organizationId, "members"]);
}
async navigateToSecretsManager(): Promise<void> {
await this.router.navigate(["sm", this.organizationId]);
}
}

View File

@ -46,14 +46,14 @@
[orgLabel]="organizationTypeQueryParameter"
></app-trial-confirmation-details>
<div class="tw-mb-3 tw-flex">
<button type="button" bitButton buttonType="primary" (click)="navigateTo('vault')">
<button type="button" bitButton buttonType="primary" (click)="navigateToSecretsManager()">
{{ "getStarted" | i18n | titlecase }}
</button>
<button
type="button"
bitButton
buttonType="secondary"
(click)="navigateTo('members')"
(click)="navigateToMembers()"
class="tw-ml-3 tw-inline-flex tw-items-center tw-px-3"
>
{{ "inviteUsers" | i18n }}

View File

@ -27,12 +27,9 @@
<button type="button" bitLink (click)="emitToAddCipher()">
{{ "onboardingImportDataDetailsLink" | i18n }}
</button>
<span *ngIf="orgs == null || orgs.length === 0">
<span>
{{ "onboardingImportDataDetailsPartTwoNoOrgs" | i18n }}
</span>
<span *ngIf="orgs.length > 0">
{{ "onboardingImportDataDetailsPartTwoWithOrgs" | i18n }}
</span>
</p>
</app-onboarding-task>

View File

@ -32,9 +32,6 @@ export const ClearClipboardDelay = {
FiveMinutes: 300,
} as const;
export type ClearClipboardDelaySetting =
(typeof ClearClipboardDelay)[keyof typeof ClearClipboardDelay];
/* Context Menu item Ids */
export const AUTOFILL_CARD_ID = "autofill-card";
export const AUTOFILL_ID = "autofill";
@ -53,3 +50,9 @@ export const ROOT_ID = "root";
export const SEPARATOR_ID = "separator";
export const NOTIFICATION_BAR_LIFESPAN_MS = 150000; // 150 seconds
export const AutofillOverlayVisibility = {
Off: 0,
OnButtonClick: 1,
OnFieldFocus: 2,
} as const;

View File

@ -1,16 +1,7 @@
import { filter, switchMap, tap, firstValueFrom, map, Observable } from "rxjs";
import { map, Observable } from "rxjs";
import {
ClearClipboardDelaySetting,
ClearClipboardDelay,
} from "../../../../../apps/browser/src/autofill/constants";
import {
AutofillOverlayVisibility,
InlineMenuVisibilitySetting,
} from "../../../../../apps/browser/src/autofill/utils/autofill-overlay.enum";
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "../../admin-console/enums/index";
import { Policy } from "../../admin-console/models/domain/policy";
import { PolicyType } from "../../admin-console/enums";
import {
AUTOFILL_SETTINGS_DISK,
AUTOFILL_SETTINGS_DISK_LOCAL,
@ -19,6 +10,8 @@ import {
KeyDefinition,
StateProvider,
} from "../../platform/state";
import { ClearClipboardDelay, AutofillOverlayVisibility } from "../constants";
import { ClearClipboardDelaySetting, InlineMenuVisibilitySetting } from "../types";
const AUTOFILL_ON_PAGE_LOAD = new KeyDefinition(AUTOFILL_SETTINGS_DISK, "autofillOnPageLoad", {
deserializer: (value: boolean) => value ?? false,
@ -32,10 +25,6 @@ const AUTOFILL_ON_PAGE_LOAD_DEFAULT = new KeyDefinition(
},
);
const AUTO_COPY_TOTP = new KeyDefinition(AUTOFILL_SETTINGS_DISK, "autoCopyTotp", {
deserializer: (value: boolean) => value ?? false,
});
const AUTOFILL_ON_PAGE_LOAD_CALLOUT_DISMISSED = new KeyDefinition(
AUTOFILL_SETTINGS_DISK,
"autofillOnPageLoadCalloutIsDismissed",
@ -44,14 +33,18 @@ const AUTOFILL_ON_PAGE_LOAD_CALLOUT_DISMISSED = new KeyDefinition(
},
);
const ACTIVATE_AUTOFILL_ON_PAGE_LOAD_FROM_POLICY = new KeyDefinition(
AUTOFILL_SETTINGS_DISK_LOCAL,
"activateAutofillOnPageLoadFromPolicy",
const AUTOFILL_ON_PAGE_LOAD_POLICY_TOAST_HAS_DISPLAYED = new KeyDefinition(
AUTOFILL_SETTINGS_DISK,
"autofillOnPageLoadPolicyToastHasDisplayed",
{
deserializer: (value: boolean) => value ?? false,
},
);
const AUTO_COPY_TOTP = new KeyDefinition(AUTOFILL_SETTINGS_DISK, "autoCopyTotp", {
deserializer: (value: boolean) => value ?? false,
});
const INLINE_MENU_VISIBILITY = new KeyDefinition(
AUTOFILL_SETTINGS_DISK_LOCAL,
"inlineMenuVisibility",
@ -73,17 +66,17 @@ export abstract class AutofillSettingsServiceAbstraction {
setAutofillOnPageLoad: (newValue: boolean) => Promise<void>;
autofillOnPageLoadDefault$: Observable<boolean>;
setAutofillOnPageLoadDefault: (newValue: boolean) => Promise<void>;
autoCopyTotp$: Observable<boolean>;
setAutoCopyTotp: (newValue: boolean) => Promise<void>;
autofillOnPageLoadCalloutIsDismissed$: Observable<boolean>;
setAutofillOnPageLoadCalloutIsDismissed: (newValue: boolean) => Promise<void>;
activateAutofillOnPageLoadFromPolicy$: Observable<boolean>;
setActivateAutofillOnPageLoadFromPolicy: (newValue: boolean) => Promise<void>;
setAutofillOnPageLoadPolicyToastHasDisplayed: (newValue: boolean) => Promise<void>;
autofillOnPageLoadPolicyToastHasDisplayed$: Observable<boolean>;
autoCopyTotp$: Observable<boolean>;
setAutoCopyTotp: (newValue: boolean) => Promise<void>;
inlineMenuVisibility$: Observable<InlineMenuVisibilitySetting>;
setInlineMenuVisibility: (newValue: InlineMenuVisibilitySetting) => Promise<void>;
clearClipboardDelay$: Observable<ClearClipboardDelaySetting>;
setClearClipboardDelay: (newValue: ClearClipboardDelaySetting) => Promise<void>;
handleActivateAutofillPolicy: (policies: Observable<Policy[]>) => Observable<boolean[]>;
}
export class AutofillSettingsService implements AutofillSettingsServiceAbstraction {
@ -93,15 +86,17 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
private autofillOnPageLoadDefaultState: ActiveUserState<boolean>;
readonly autofillOnPageLoadDefault$: Observable<boolean>;
private autoCopyTotpState: ActiveUserState<boolean>;
readonly autoCopyTotp$: Observable<boolean>;
private autofillOnPageLoadCalloutIsDismissedState: ActiveUserState<boolean>;
readonly autofillOnPageLoadCalloutIsDismissed$: Observable<boolean>;
private activateAutofillOnPageLoadFromPolicyState: ActiveUserState<boolean>;
readonly activateAutofillOnPageLoadFromPolicy$: Observable<boolean>;
private autofillOnPageLoadPolicyToastHasDisplayedState: ActiveUserState<boolean>;
readonly autofillOnPageLoadPolicyToastHasDisplayed$: Observable<boolean>;
private autoCopyTotpState: ActiveUserState<boolean>;
readonly autoCopyTotp$: Observable<boolean>;
private inlineMenuVisibilityState: GlobalState<InlineMenuVisibilitySetting>;
readonly inlineMenuVisibility$: Observable<InlineMenuVisibilitySetting>;
@ -110,7 +105,7 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
constructor(
private stateProvider: StateProvider,
policyService: PolicyService,
private policyService: PolicyService,
) {
this.autofillOnPageLoadState = this.stateProvider.getActive(AUTOFILL_ON_PAGE_LOAD);
this.autofillOnPageLoad$ = this.autofillOnPageLoadState.state$.pipe(map((x) => x ?? false));
@ -122,20 +117,25 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
map((x) => x ?? true),
);
this.autoCopyTotpState = this.stateProvider.getActive(AUTO_COPY_TOTP);
this.autoCopyTotp$ = this.autoCopyTotpState.state$.pipe(map((x) => x ?? false));
this.autofillOnPageLoadCalloutIsDismissedState = this.stateProvider.getActive(
AUTOFILL_ON_PAGE_LOAD_CALLOUT_DISMISSED,
);
this.autofillOnPageLoadCalloutIsDismissed$ =
this.autofillOnPageLoadCalloutIsDismissedState.state$.pipe(map((x) => x ?? false));
this.activateAutofillOnPageLoadFromPolicyState = this.stateProvider.getActive(
ACTIVATE_AUTOFILL_ON_PAGE_LOAD_FROM_POLICY,
this.activateAutofillOnPageLoadFromPolicy$ = this.policyService.policyAppliesToActiveUser$(
PolicyType.ActivateAutofill,
);
this.activateAutofillOnPageLoadFromPolicy$ =
this.activateAutofillOnPageLoadFromPolicyState.state$.pipe(map((x) => x ?? false));
this.autofillOnPageLoadPolicyToastHasDisplayedState = this.stateProvider.getActive(
AUTOFILL_ON_PAGE_LOAD_POLICY_TOAST_HAS_DISPLAYED,
);
this.autofillOnPageLoadPolicyToastHasDisplayed$ = this.autofillOnPageLoadState.state$.pipe(
map((x) => x ?? false),
);
this.autoCopyTotpState = this.stateProvider.getActive(AUTO_COPY_TOTP);
this.autoCopyTotp$ = this.autoCopyTotpState.state$.pipe(map((x) => x ?? false));
this.inlineMenuVisibilityState = this.stateProvider.getGlobal(INLINE_MENU_VISIBILITY);
this.inlineMenuVisibility$ = this.inlineMenuVisibilityState.state$.pipe(
@ -146,8 +146,6 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
this.clearClipboardDelay$ = this.clearClipboardDelayState.state$.pipe(
map((x) => x ?? ClearClipboardDelay.Never),
);
policyService.policies$.pipe(this.handleActivateAutofillPolicy.bind(this)).subscribe();
}
async setAutofillOnPageLoad(newValue: boolean): Promise<void> {
@ -158,16 +156,16 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
await this.autofillOnPageLoadDefaultState.update(() => newValue);
}
async setAutoCopyTotp(newValue: boolean): Promise<void> {
await this.autoCopyTotpState.update(() => newValue);
}
async setAutofillOnPageLoadCalloutIsDismissed(newValue: boolean): Promise<void> {
await this.autofillOnPageLoadCalloutIsDismissedState.update(() => newValue);
}
async setActivateAutofillOnPageLoadFromPolicy(newValue: boolean): Promise<void> {
await this.activateAutofillOnPageLoadFromPolicyState.update(() => newValue);
async setAutofillOnPageLoadPolicyToastHasDisplayed(newValue: boolean): Promise<void> {
await this.autofillOnPageLoadPolicyToastHasDisplayedState.update(() => newValue);
}
async setAutoCopyTotp(newValue: boolean): Promise<void> {
await this.autoCopyTotpState.update(() => newValue);
}
async setInlineMenuVisibility(newValue: InlineMenuVisibilitySetting): Promise<void> {
@ -177,24 +175,4 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
async setClearClipboardDelay(newValue: ClearClipboardDelaySetting): Promise<void> {
await this.clearClipboardDelayState.update(() => newValue);
}
/**
* If the ActivateAutofill policy is enabled, save a flag indicating if we need to
* enable Autofill on page load.
*/
handleActivateAutofillPolicy(policies$: Observable<Policy[]>): Observable<boolean[]> {
return policies$.pipe(
map((policies) => policies.find((p) => p.type == PolicyType.ActivateAutofill && p.enabled)),
filter((p) => p != null),
switchMap(async (_) => [
await firstValueFrom(this.activateAutofillOnPageLoadFromPolicy$),
await firstValueFrom(this.autofillOnPageLoad$),
]),
tap(([activated, autofillEnabled]) => {
if (activated === undefined) {
void this.setActivateAutofillOnPageLoadFromPolicy(!autofillEnabled);
}
}),
);
}
}

View File

@ -0,0 +1,7 @@
import { ClearClipboardDelay, AutofillOverlayVisibility } from "../constants";
export type ClearClipboardDelaySetting =
(typeof ClearClipboardDelay)[keyof typeof ClearClipboardDelay];
export type InlineMenuVisibilitySetting =
(typeof AutofillOverlayVisibility)[keyof typeof AutofillOverlayVisibility];

View File

@ -1,7 +1,15 @@
import { InlineMenuVisibilitySetting } from "../../../../../apps/browser/src/autofill/utils/autofill-overlay.enum";
import { StateDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
const AutofillOverlayVisibility = {
Off: 0,
OnButtonClick: 1,
OnFieldFocus: 2,
} as const;
type InlineMenuVisibilitySetting =
(typeof AutofillOverlayVisibility)[keyof typeof AutofillOverlayVisibility];
type ExpectedAccountState = {
settings?: {
autoFillOnPageLoadDefault?: boolean;

View File

@ -1,7 +1,18 @@
import { ClearClipboardDelaySetting } from "../../../../../apps/browser/src/autofill/constants";
import { StateDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
const ClearClipboardDelay = {
Never: null as null,
TenSeconds: 10,
TwentySeconds: 20,
ThirtySeconds: 30,
OneMinute: 60,
TwoMinutes: 120,
FiveMinutes: 300,
} as const;
type ClearClipboardDelaySetting = (typeof ClearClipboardDelay)[keyof typeof ClearClipboardDelay];
type ExpectedAccountState = {
settings?: {
clearClipboard?: ClearClipboardDelaySetting;

View File

@ -5,6 +5,12 @@ import {
SUBADDRESS_SETTINGS,
PASSPHRASE_SETTINGS,
PASSWORD_SETTINGS,
SIMPLE_LOGIN_FORWARDER,
FORWARD_EMAIL_FORWARDER,
FIREFOX_RELAY_FORWARDER,
FASTMAIL_FORWARDER,
DUCK_DUCK_GO_FORWARDER,
ADDY_IO_FORWARDER,
} from "./key-definitions";
describe("Key definitions", () => {
@ -48,6 +54,54 @@ describe("Key definitions", () => {
});
});
describe("ADDY_IO_FORWARDER", () => {
it("should pass through deserialization", () => {
const value: any = {};
const result = ADDY_IO_FORWARDER.deserializer(value);
expect(result).toBe(value);
});
});
describe("DUCK_DUCK_GO_FORWARDER", () => {
it("should pass through deserialization", () => {
const value: any = {};
const result = DUCK_DUCK_GO_FORWARDER.deserializer(value);
expect(result).toBe(value);
});
});
describe("FASTMAIL_FORWARDER", () => {
it("should pass through deserialization", () => {
const value: any = {};
const result = FASTMAIL_FORWARDER.deserializer(value);
expect(result).toBe(value);
});
});
describe("FIREFOX_RELAY_FORWARDER", () => {
it("should pass through deserialization", () => {
const value: any = {};
const result = FIREFOX_RELAY_FORWARDER.deserializer(value);
expect(result).toBe(value);
});
});
describe("FORWARD_EMAIL_FORWARDER", () => {
it("should pass through deserialization", () => {
const value: any = {};
const result = FORWARD_EMAIL_FORWARDER.deserializer(value);
expect(result).toBe(value);
});
});
describe("SIMPLE_LOGIN_FORWARDER", () => {
it("should pass through deserialization", () => {
const value: any = {};
const result = SIMPLE_LOGIN_FORWARDER.deserializer(value);
expect(result).toBe(value);
});
});
describe("ENCRYPTED_HISTORY", () => {
it("should pass through deserialization", () => {
const value = {};

View File

@ -5,6 +5,12 @@ import { GeneratedPasswordHistory } from "./password/generated-password-history"
import { PasswordGenerationOptions } from "./password/password-generation-options";
import { CatchallGenerationOptions } from "./username/catchall-generator-options";
import { EffUsernameGenerationOptions } from "./username/eff-username-generator-options";
import {
ApiOptions,
EmailDomainOptions,
EmailPrefixOptions,
SelfHostedApiOptions,
} from "./username/options/forwarder-options";
import { SubaddressGenerationOptions } from "./username/subaddress-generator-options";
/** plaintext password generation options */
@ -52,6 +58,54 @@ export const SUBADDRESS_SETTINGS = new KeyDefinition<SubaddressGenerationOptions
},
);
export const ADDY_IO_FORWARDER = new KeyDefinition<SelfHostedApiOptions & EmailDomainOptions>(
GENERATOR_DISK,
"addyIoForwarder",
{
deserializer: (value) => value,
},
);
export const DUCK_DUCK_GO_FORWARDER = new KeyDefinition<ApiOptions>(
GENERATOR_DISK,
"duckDuckGoForwarder",
{
deserializer: (value) => value,
},
);
export const FASTMAIL_FORWARDER = new KeyDefinition<ApiOptions & EmailPrefixOptions>(
GENERATOR_DISK,
"fastmailForwarder",
{
deserializer: (value) => value,
},
);
export const FIREFOX_RELAY_FORWARDER = new KeyDefinition<ApiOptions>(
GENERATOR_DISK,
"firefoxRelayForwarder",
{
deserializer: (value) => value,
},
);
export const FORWARD_EMAIL_FORWARDER = new KeyDefinition<ApiOptions & EmailDomainOptions>(
GENERATOR_DISK,
"forwardEmailForwarder",
{
deserializer: (value) => value,
},
);
export const SIMPLE_LOGIN_FORWARDER = new KeyDefinition<SelfHostedApiOptions>(
GENERATOR_DISK,
"simpleLoginForwarder",
{
deserializer: (value) => value,
},
);
/** encrypted password generation history */
export const ENCRYPTED_HISTORY = new KeyDefinition<GeneratedPasswordHistory>(
GENERATOR_DISK,

View File

@ -0,0 +1,73 @@
import { mock } from "jest-mock-extended";
import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec";
import { CryptoService } from "../../../platform/abstractions/crypto.service";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { StateProvider } from "../../../platform/state";
import { UserId } from "../../../types/guid";
import { DefaultPolicyEvaluator } from "../default-policy-evaluator";
import { DUCK_DUCK_GO_FORWARDER } from "../key-definitions";
import { SecretState } from "../state/secret-state";
import { ForwarderGeneratorStrategy } from "./forwarder-generator-strategy";
import { ApiOptions } from "./options/forwarder-options";
class TestForwarder extends ForwarderGeneratorStrategy<ApiOptions> {
constructor(
encryptService: EncryptService,
keyService: CryptoService,
stateProvider: StateProvider,
) {
super(encryptService, keyService, stateProvider);
}
get key() {
// arbitrary.
return DUCK_DUCK_GO_FORWARDER;
}
}
const SomeUser = "some user" as UserId;
const AnotherUser = "another user" as UserId;
describe("ForwarderGeneratorStrategy", () => {
const encryptService = mock<EncryptService>();
const keyService = mock<CryptoService>();
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser));
describe("durableState", () => {
it("constructs a secret state", () => {
const strategy = new TestForwarder(encryptService, keyService, stateProvider);
const result = strategy.durableState(SomeUser);
expect(result).toBeInstanceOf(SecretState);
});
it("returns the same secret state for a single user", () => {
const strategy = new TestForwarder(encryptService, keyService, stateProvider);
const firstResult = strategy.durableState(SomeUser);
const secondResult = strategy.durableState(SomeUser);
expect(firstResult).toBe(secondResult);
});
it("returns a different secret state for a different user", () => {
const strategy = new TestForwarder(encryptService, keyService, stateProvider);
const firstResult = strategy.durableState(SomeUser);
const secondResult = strategy.durableState(AnotherUser);
expect(firstResult).not.toBe(secondResult);
});
});
it("evaluator returns the default policy evaluator", () => {
const strategy = new TestForwarder(null, null, null);
const result = strategy.evaluator(null);
expect(result).toBeInstanceOf(DefaultPolicyEvaluator);
});
});

View File

@ -0,0 +1,73 @@
import { PolicyType } from "../../../admin-console/enums";
import { Policy } from "../../../admin-console/models/domain/policy";
import { CryptoService } from "../../../platform/abstractions/crypto.service";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { KeyDefinition, StateProvider } from "../../../platform/state";
import { UserId } from "../../../types/guid";
import { GeneratorStrategy } from "../abstractions";
import { DefaultPolicyEvaluator } from "../default-policy-evaluator";
import { NoPolicy } from "../no-policy";
import { PaddedDataPacker } from "../state/padded-data-packer";
import { SecretClassifier } from "../state/secret-classifier";
import { SecretState } from "../state/secret-state";
import { UserKeyEncryptor } from "../state/user-key-encryptor";
import { ApiOptions } from "./options/forwarder-options";
const ONE_MINUTE = 60 * 1000;
const OPTIONS_FRAME_SIZE = 512;
/** An email forwarding service configurable through an API. */
export abstract class ForwarderGeneratorStrategy<
Options extends ApiOptions,
> extends GeneratorStrategy<Options, NoPolicy> {
/** Initializes the generator strategy
* @param encryptService protects sensitive forwarder options
* @param keyService looks up the user key when protecting data.
* @param stateProvider creates the durable state for options storage
*/
constructor(
private readonly encryptService: EncryptService,
private readonly keyService: CryptoService,
private stateProvider: StateProvider,
) {
super();
// Uses password generator since there aren't policies
// specific to usernames.
this.policy = PolicyType.PasswordGenerator;
this.cache_ms = ONE_MINUTE;
}
private durableStates = new Map<UserId, SecretState<Options, Record<string, never>>>();
/** {@link GeneratorStrategy.durableState} */
durableState = (userId: UserId) => {
let state = this.durableStates.get(userId);
if (!state) {
const encryptor = this.createEncryptor();
state = SecretState.from(userId, this.key, this.stateProvider, encryptor);
this.durableStates.set(userId, state);
}
return state;
};
private createEncryptor() {
// always exclude request properties
const classifier = SecretClassifier.allSecret<Options>().exclude("website");
// construct the encryptor
const packer = new PaddedDataPacker(OPTIONS_FRAME_SIZE);
return new UserKeyEncryptor(this.encryptService, this.keyService, classifier, packer);
}
/** Determine where forwarder configuration is stored */
protected abstract readonly key: KeyDefinition<Options>;
/** {@link GeneratorStrategy.evaluator} */
evaluator = (_policy: Policy) => {
return new DefaultPolicyEvaluator<Options>();
};
}

View File

@ -2,22 +2,30 @@
* include Request in test environment.
* @jest-environment ../../../../shared/test.environment.ts
*/
import { ADDY_IO_FORWARDER } from "../../key-definitions";
import { Forwarders } from "../options/constants";
import { AddyIoForwarder } from "./addy-io";
import { mockApiService, mockI18nService } from "./mocks.jest";
describe("Addy.io Forwarder", () => {
it("key returns the Addy IO forwarder key", () => {
const forwarder = new AddyIoForwarder(null, null, null, null, null);
expect(forwarder.key).toBe(ADDY_IO_FORWARDER);
});
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new AddyIoForwarder(apiService, i18nService);
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token,
domain: "example.com",
baseUrl: "https://api.example.com",
@ -34,11 +42,12 @@ describe("Addy.io Forwarder", () => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new AddyIoForwarder(apiService, i18nService);
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain,
baseUrl: "https://api.example.com",
@ -56,11 +65,12 @@ describe("Addy.io Forwarder", () => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new AddyIoForwarder(apiService, i18nService);
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
baseUrl,
@ -83,9 +93,10 @@ describe("Addy.io Forwarder", () => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new AddyIoForwarder(apiService, i18nService);
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
await forwarder.generate(website, {
await forwarder.generate({
website,
token: "token",
domain: "example.com",
baseUrl: "https://api.example.com",
@ -107,9 +118,10 @@ describe("Addy.io Forwarder", () => {
const apiService = mockApiService(status, { data: { email } });
const i18nService = mockI18nService();
const forwarder = new AddyIoForwarder(apiService, i18nService);
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
const result = await forwarder.generate(null, {
const result = await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
baseUrl: "https://api.example.com",
@ -124,11 +136,12 @@ describe("Addy.io Forwarder", () => {
const apiService = mockApiService(401, {});
const i18nService = mockI18nService();
const forwarder = new AddyIoForwarder(apiService, i18nService);
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
baseUrl: "https://api.example.com",
@ -148,11 +161,12 @@ describe("Addy.io Forwarder", () => {
const apiService = mockApiService(500, {});
const i18nService = mockI18nService();
const forwarder = new AddyIoForwarder(apiService, i18nService);
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
baseUrl: "https://api.example.com",
@ -181,11 +195,12 @@ describe("Addy.io Forwarder", () => {
const apiService = mockApiService(statusCode, {}, statusText);
const i18nService = mockI18nService();
const forwarder = new AddyIoForwarder(apiService, i18nService);
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
baseUrl: "https://api.example.com",

View File

@ -1,24 +1,41 @@
import { ApiService } from "../../../../abstractions/api.service";
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
import { I18nService } from "../../../../platform/abstractions/i18n.service";
import { StateProvider } from "../../../../platform/state";
import { ADDY_IO_FORWARDER } from "../../key-definitions";
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
import { Forwarders } from "../options/constants";
import { EmailDomainOptions, Forwarder, SelfHostedApiOptions } from "../options/forwarder-options";
import { EmailDomainOptions, SelfHostedApiOptions } from "../options/forwarder-options";
/** Generates a forwarding address for addy.io (formerly anon addy) */
export class AddyIoForwarder implements Forwarder {
export class AddyIoForwarder extends ForwarderGeneratorStrategy<
SelfHostedApiOptions & EmailDomainOptions
> {
/** Instantiates the forwarder
* @param apiService used for ajax requests to the forwarding service
* @param i18nService used to look up error strings
* @param encryptService protects sensitive forwarder options
* @param keyService looks up the user key when protecting data.
* @param stateProvider creates the durable state for options storage
*/
constructor(
private apiService: ApiService,
private i18nService: I18nService,
) {}
encryptService: EncryptService,
keyService: CryptoService,
stateProvider: StateProvider,
) {
super(encryptService, keyService, stateProvider);
}
/** {@link Forwarder.generate} */
async generate(
website: string | null,
options: SelfHostedApiOptions & EmailDomainOptions,
): Promise<string> {
/** {@link ForwarderGeneratorStrategy.key} */
get key() {
return ADDY_IO_FORWARDER;
}
/** {@link ForwarderGeneratorStrategy.generate} */
generate = async (options: SelfHostedApiOptions & EmailDomainOptions) => {
if (!options.token || options.token === "") {
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.AddyIo.name);
throw error;
@ -32,9 +49,11 @@ export class AddyIoForwarder implements Forwarder {
throw error;
}
const descriptionId =
website && website !== "" ? "forwarderGeneratedByWithWebsite" : "forwarderGeneratedBy";
const description = this.i18nService.t(descriptionId, website ?? "");
let descriptionId = "forwarderGeneratedByWithWebsite";
if (!options.website || options.website === "") {
descriptionId = "forwarderGeneratedBy";
}
const description = this.i18nService.t(descriptionId, options.website ?? "");
const url = options.baseUrl + "/api/v1/aliases";
const request = new Request(url, {
@ -70,5 +89,5 @@ export class AddyIoForwarder implements Forwarder {
const error = this.i18nService.t("forwarderUnknownError", Forwarders.AddyIo.name);
throw error;
}
}
};
}

View File

@ -2,22 +2,30 @@
* include Request in test environment.
* @jest-environment ../../../../shared/test.environment.ts
*/
import { DUCK_DUCK_GO_FORWARDER } from "../../key-definitions";
import { Forwarders } from "../options/constants";
import { DuckDuckGoForwarder } from "./duck-duck-go";
import { mockApiService, mockI18nService } from "./mocks.jest";
describe("DuckDuckGo Forwarder", () => {
it("key returns the Duck Duck Go forwarder key", () => {
const forwarder = new DuckDuckGoForwarder(null, null, null, null, null);
expect(forwarder.key).toBe(DUCK_DUCK_GO_FORWARDER);
});
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token,
}),
).rejects.toEqual("forwaderInvalidToken");
@ -40,9 +48,10 @@ describe("DuckDuckGo Forwarder", () => {
const apiService = mockApiService(status, { address });
const i18nService = mockI18nService();
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
const result = await forwarder.generate(null, {
const result = await forwarder.generate({
website: null,
token: "token",
});
@ -55,11 +64,12 @@ describe("DuckDuckGo Forwarder", () => {
const apiService = mockApiService(401, {});
const i18nService = mockI18nService();
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
}),
).rejects.toEqual("forwaderInvalidToken");
@ -76,11 +86,12 @@ describe("DuckDuckGo Forwarder", () => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
}),
).rejects.toEqual("forwarderUnknownError");
@ -99,11 +110,12 @@ describe("DuckDuckGo Forwarder", () => {
const apiService = mockApiService(statusCode, {});
const i18nService = mockI18nService();
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
}),
).rejects.toEqual("forwarderUnknownError");

View File

@ -1,21 +1,39 @@
import { ApiService } from "../../../../abstractions/api.service";
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
import { I18nService } from "../../../../platform/abstractions/i18n.service";
import { StateProvider } from "../../../../platform/state";
import { DUCK_DUCK_GO_FORWARDER } from "../../key-definitions";
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
import { Forwarders } from "../options/constants";
import { ApiOptions, Forwarder } from "../options/forwarder-options";
import { ApiOptions } from "../options/forwarder-options";
/** Generates a forwarding address for DuckDuckGo */
export class DuckDuckGoForwarder implements Forwarder {
export class DuckDuckGoForwarder extends ForwarderGeneratorStrategy<ApiOptions> {
/** Instantiates the forwarder
* @param apiService used for ajax requests to the forwarding service
* @param i18nService used to look up error strings
* @param encryptService protects sensitive forwarder options
* @param keyService looks up the user key when protecting data.
* @param stateProvider creates the durable state for options storage
*/
constructor(
private apiService: ApiService,
private i18nService: I18nService,
) {}
encryptService: EncryptService,
keyService: CryptoService,
stateProvider: StateProvider,
) {
super(encryptService, keyService, stateProvider);
}
/** {@link Forwarder.generate} */
async generate(_website: string | null, options: ApiOptions): Promise<string> {
/** {@link ForwarderGeneratorStrategy.key} */
get key() {
return DUCK_DUCK_GO_FORWARDER;
}
/** {@link ForwarderGeneratorStrategy.generate} */
generate = async (options: ApiOptions): Promise<string> => {
if (!options.token || options.token === "") {
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.DuckDuckGo.name);
throw error;
@ -48,5 +66,5 @@ export class DuckDuckGoForwarder implements Forwarder {
const error = this.i18nService.t("forwarderUnknownError", Forwarders.DuckDuckGo.name);
throw error;
}
}
};
}

View File

@ -3,6 +3,7 @@
* @jest-environment ../../../../shared/test.environment.ts
*/
import { ApiService } from "../../../../abstractions/api.service";
import { FASTMAIL_FORWARDER } from "../../key-definitions";
import { Forwarders } from "../options/constants";
import { FastmailForwarder } from "./fastmail";
@ -45,16 +46,23 @@ const AccountIdSuccess: MockResponse = Object.freeze({
// the tests
describe("Fastmail Forwarder", () => {
it("key returns the Fastmail forwarder key", () => {
const forwarder = new FastmailForwarder(null, null, null, null, null);
expect(forwarder.key).toBe(FASTMAIL_FORWARDER);
});
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
const apiService = mockApiService(AccountIdSuccess, EmptyResponse);
const i18nService = mockI18nService();
const forwarder = new FastmailForwarder(apiService, i18nService);
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token,
domain: "example.com",
prefix: "prefix",
@ -71,11 +79,12 @@ describe("Fastmail Forwarder", () => {
const apiService = mockApiService({ status, body: {} }, EmptyResponse);
const i18nService = mockI18nService();
const forwarder = new FastmailForwarder(apiService, i18nService);
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
prefix: "prefix",
@ -105,9 +114,10 @@ describe("Fastmail Forwarder", () => {
});
const i18nService = mockI18nService();
const forwarder = new FastmailForwarder(apiService, i18nService);
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
const result = await forwarder.generate(null, {
const result = await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
prefix: "prefix",
@ -138,11 +148,12 @@ describe("Fastmail Forwarder", () => {
});
const i18nService = mockI18nService();
const forwarder = new FastmailForwarder(apiService, i18nService);
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
prefix: "prefix",
@ -165,11 +176,12 @@ describe("Fastmail Forwarder", () => {
const apiService = mockApiService(AccountIdSuccess, { status, body: {} });
const i18nService = mockI18nService();
const forwarder = new FastmailForwarder(apiService, i18nService);
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
prefix: "prefix",
@ -206,11 +218,12 @@ describe("Fastmail Forwarder", () => {
});
const i18nService = mockI18nService();
const forwarder = new FastmailForwarder(apiService, i18nService);
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
prefix: "prefix",
@ -232,11 +245,12 @@ describe("Fastmail Forwarder", () => {
const apiService = mockApiService(AccountIdSuccess, { status: statusCode, body: {} });
const i18nService = mockI18nService();
const forwarder = new FastmailForwarder(apiService, i18nService);
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
prefix: "prefix",

View File

@ -1,24 +1,39 @@
import { ApiService } from "../../../../abstractions/api.service";
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
import { I18nService } from "../../../../platform/abstractions/i18n.service";
import { StateProvider } from "../../../../platform/state";
import { FASTMAIL_FORWARDER } from "../../key-definitions";
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
import { Forwarders } from "../options/constants";
import { EmailPrefixOptions, Forwarder, ApiOptions } from "../options/forwarder-options";
import { EmailPrefixOptions, ApiOptions } from "../options/forwarder-options";
/** Generates a forwarding address for Fastmail */
export class FastmailForwarder implements Forwarder {
export class FastmailForwarder extends ForwarderGeneratorStrategy<ApiOptions & EmailPrefixOptions> {
/** Instantiates the forwarder
* @param apiService used for ajax requests to the forwarding service
* @param i18nService used to look up error strings
* @param encryptService protects sensitive forwarder options
* @param keyService looks up the user key when protecting data.
* @param stateProvider creates the durable state for options storage
*/
constructor(
private apiService: ApiService,
private i18nService: I18nService,
) {}
encryptService: EncryptService,
keyService: CryptoService,
stateProvider: StateProvider,
) {
super(encryptService, keyService, stateProvider);
}
/** {@link Forwarder.generate} */
async generate(
website: string | null,
options: ApiOptions & EmailPrefixOptions,
): Promise<string> {
/** {@link ForwarderGeneratorStrategy.key} */
get key() {
return FASTMAIL_FORWARDER;
}
/** {@link ForwarderGeneratorStrategy.generate} */
generate = async (options: ApiOptions & EmailPrefixOptions) => {
if (!options.token || options.token === "") {
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.Fastmail.name);
throw error;
@ -41,7 +56,7 @@ export class FastmailForwarder implements Forwarder {
"new-masked-email": {
state: "enabled",
description: "",
forDomain: website,
forDomain: options.website,
emailPrefix: options.prefix,
},
},
@ -104,7 +119,7 @@ export class FastmailForwarder implements Forwarder {
const error = this.i18nService.t("forwarderUnknownError", Forwarders.Fastmail.name);
throw error;
}
};
private async getAccountId(options: ApiOptions): Promise<string> {
const requestInit: RequestInit = {

View File

@ -2,22 +2,30 @@
* include Request in test environment.
* @jest-environment ../../../../shared/test.environment.ts
*/
import { FIREFOX_RELAY_FORWARDER } from "../../key-definitions";
import { Forwarders } from "../options/constants";
import { FirefoxRelayForwarder } from "./firefox-relay";
import { mockApiService, mockI18nService } from "./mocks.jest";
describe("Firefox Relay Forwarder", () => {
it("key returns the Firefox Relay forwarder key", () => {
const forwarder = new FirefoxRelayForwarder(null, null, null, null, null);
expect(forwarder.key).toBe(FIREFOX_RELAY_FORWARDER);
});
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new FirefoxRelayForwarder(apiService, i18nService);
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token,
}),
).rejects.toEqual("forwaderInvalidToken");
@ -40,9 +48,10 @@ describe("Firefox Relay Forwarder", () => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new FirefoxRelayForwarder(apiService, i18nService);
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
await forwarder.generate(website, {
await forwarder.generate({
website,
token: "token",
});
@ -62,9 +71,10 @@ describe("Firefox Relay Forwarder", () => {
const apiService = mockApiService(status, { full_address });
const i18nService = mockI18nService();
const forwarder = new FirefoxRelayForwarder(apiService, i18nService);
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
const result = await forwarder.generate(null, {
const result = await forwarder.generate({
website: null,
token: "token",
});
@ -77,11 +87,12 @@ describe("Firefox Relay Forwarder", () => {
const apiService = mockApiService(401, {});
const i18nService = mockI18nService();
const forwarder = new FirefoxRelayForwarder(apiService, i18nService);
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
}),
).rejects.toEqual("forwaderInvalidToken");
@ -101,11 +112,12 @@ describe("Firefox Relay Forwarder", () => {
const apiService = mockApiService(statusCode, {});
const i18nService = mockI18nService();
const forwarder = new FirefoxRelayForwarder(apiService, i18nService);
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
}),
).rejects.toEqual("forwarderUnknownError");

View File

@ -1,21 +1,39 @@
import { ApiService } from "../../../../abstractions/api.service";
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
import { I18nService } from "../../../../platform/abstractions/i18n.service";
import { StateProvider } from "../../../../platform/state";
import { FIREFOX_RELAY_FORWARDER } from "../../key-definitions";
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
import { Forwarders } from "../options/constants";
import { Forwarder, ApiOptions } from "../options/forwarder-options";
import { ApiOptions } from "../options/forwarder-options";
/** Generates a forwarding address for Firefox Relay */
export class FirefoxRelayForwarder implements Forwarder {
export class FirefoxRelayForwarder extends ForwarderGeneratorStrategy<ApiOptions> {
/** Instantiates the forwarder
* @param apiService used for ajax requests to the forwarding service
* @param i18nService used to look up error strings
* @param encryptService protects sensitive forwarder options
* @param keyService looks up the user key when protecting data.
* @param stateProvider creates the durable state for options storage
*/
constructor(
private apiService: ApiService,
private i18nService: I18nService,
) {}
encryptService: EncryptService,
keyService: CryptoService,
stateProvider: StateProvider,
) {
super(encryptService, keyService, stateProvider);
}
/** {@link Forwarder.generate} */
async generate(website: string | null, options: ApiOptions): Promise<string> {
/** {@link ForwarderGeneratorStrategy.key} */
get key() {
return FIREFOX_RELAY_FORWARDER;
}
/** {@link ForwarderGeneratorStrategy.generate} */
generate = async (options: ApiOptions) => {
if (!options.token || options.token === "") {
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.FirefoxRelay.name);
throw error;
@ -23,9 +41,11 @@ export class FirefoxRelayForwarder implements Forwarder {
const url = "https://relay.firefox.com/api/v1/relayaddresses/";
const descriptionId =
website && website !== "" ? "forwarderGeneratedByWithWebsite" : "forwarderGeneratedBy";
const description = this.i18nService.t(descriptionId, website ?? "");
let descriptionId = "forwarderGeneratedByWithWebsite";
if (!options.website || options.website === "") {
descriptionId = "forwarderGeneratedBy";
}
const description = this.i18nService.t(descriptionId, options.website ?? "");
const request = new Request(url, {
redirect: "manual",
@ -37,7 +57,7 @@ export class FirefoxRelayForwarder implements Forwarder {
}),
body: JSON.stringify({
enabled: true,
generated_for: website,
generated_for: options.website,
description,
}),
});
@ -53,5 +73,5 @@ export class FirefoxRelayForwarder implements Forwarder {
const error = this.i18nService.t("forwarderUnknownError", Forwarders.FirefoxRelay.name);
throw error;
}
}
};
}

View File

@ -2,22 +2,30 @@
* include Request in test environment.
* @jest-environment ../../../../shared/test.environment.ts
*/
import { FORWARD_EMAIL_FORWARDER } from "../../key-definitions";
import { Forwarders } from "../options/constants";
import { ForwardEmailForwarder } from "./forward-email";
import { mockApiService, mockI18nService } from "./mocks.jest";
describe("ForwardEmail Forwarder", () => {
it("key returns the Forward Email forwarder key", () => {
const forwarder = new ForwardEmailForwarder(null, null, null, null, null);
expect(forwarder.key).toBe(FORWARD_EMAIL_FORWARDER);
});
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token,
domain: "example.com",
}),
@ -36,11 +44,12 @@ describe("ForwardEmail Forwarder", () => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain,
}),
@ -65,9 +74,10 @@ describe("ForwardEmail Forwarder", () => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
await forwarder.generate(website, {
await forwarder.generate({
website,
token: "token",
domain: "example.com",
});
@ -92,9 +102,10 @@ describe("ForwardEmail Forwarder", () => {
const apiService = mockApiService(status, response);
const i18nService = mockI18nService();
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
const result = await forwarder.generate(null, {
const result = await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
});
@ -108,11 +119,12 @@ describe("ForwardEmail Forwarder", () => {
const apiService = mockApiService(401, {});
const i18nService = mockI18nService();
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
}),
@ -132,11 +144,12 @@ describe("ForwardEmail Forwarder", () => {
const apiService = mockApiService(401, { message: "A message" });
const i18nService = mockI18nService();
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
}),
@ -158,11 +171,12 @@ describe("ForwardEmail Forwarder", () => {
const apiService = mockApiService(500, json);
const i18nService = mockI18nService();
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
}),
@ -191,11 +205,12 @@ describe("ForwardEmail Forwarder", () => {
const apiService = mockApiService(statusCode, { message });
const i18nService = mockI18nService();
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
}),
@ -225,11 +240,12 @@ describe("ForwardEmail Forwarder", () => {
const apiService = mockApiService(statusCode, { error });
const i18nService = mockI18nService();
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
domain: "example.com",
}),

View File

@ -1,25 +1,42 @@
import { ApiService } from "../../../../abstractions/api.service";
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
import { I18nService } from "../../../../platform/abstractions/i18n.service";
import { Utils } from "../../../../platform/misc/utils";
import { StateProvider } from "../../../../platform/state";
import { FORWARD_EMAIL_FORWARDER } from "../../key-definitions";
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
import { Forwarders } from "../options/constants";
import { EmailDomainOptions, Forwarder, ApiOptions } from "../options/forwarder-options";
import { EmailDomainOptions, ApiOptions } from "../options/forwarder-options";
/** Generates a forwarding address for Forward Email */
export class ForwardEmailForwarder implements Forwarder {
export class ForwardEmailForwarder extends ForwarderGeneratorStrategy<
ApiOptions & EmailDomainOptions
> {
/** Instantiates the forwarder
* @param apiService used for ajax requests to the forwarding service
* @param i18nService used to look up error strings
* @param encryptService protects sensitive forwarder options
* @param keyService looks up the user key when protecting data.
* @param stateProvider creates the durable state for options storage
*/
constructor(
private apiService: ApiService,
private i18nService: I18nService,
) {}
encryptService: EncryptService,
keyService: CryptoService,
stateProvider: StateProvider,
) {
super(encryptService, keyService, stateProvider);
}
/** {@link Forwarder.generate} */
async generate(
website: string | null,
options: ApiOptions & EmailDomainOptions,
): Promise<string> {
/** {@link ForwarderGeneratorStrategy.key} */
get key() {
return FORWARD_EMAIL_FORWARDER;
}
/** {@link ForwarderGeneratorStrategy.generate} */
generate = async (options: ApiOptions & EmailDomainOptions) => {
if (!options.token || options.token === "") {
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.ForwardEmail.name);
throw error;
@ -31,9 +48,11 @@ export class ForwardEmailForwarder implements Forwarder {
const url = `https://api.forwardemail.net/v1/domains/${options.domain}/aliases`;
const descriptionId =
website && website !== "" ? "forwarderGeneratedByWithWebsite" : "forwarderGeneratedBy";
const description = this.i18nService.t(descriptionId, website ?? "");
let descriptionId = "forwarderGeneratedByWithWebsite";
if (!options.website || options.website === "") {
descriptionId = "forwarderGeneratedBy";
}
const description = this.i18nService.t(descriptionId, options.website ?? "");
const request = new Request(url, {
redirect: "manual",
@ -44,7 +63,7 @@ export class ForwardEmailForwarder implements Forwarder {
"Content-Type": "application/json",
}),
body: JSON.stringify({
labels: website,
labels: options.website,
description,
}),
});
@ -75,5 +94,5 @@ export class ForwardEmailForwarder implements Forwarder {
const error = this.i18nService.t("forwarderUnknownError", Forwarders.ForwardEmail.name);
throw error;
}
}
};
}

View File

@ -2,22 +2,30 @@
* include Request in test environment.
* @jest-environment ../../../../shared/test.environment.ts
*/
import { SIMPLE_LOGIN_FORWARDER } from "../../key-definitions";
import { Forwarders } from "../options/constants";
import { mockApiService, mockI18nService } from "./mocks.jest";
import { SimpleLoginForwarder } from "./simple-login";
describe("SimpleLogin Forwarder", () => {
it("key returns the Simple Login forwarder key", () => {
const forwarder = new SimpleLoginForwarder(null, null, null, null, null);
expect(forwarder.key).toBe(SIMPLE_LOGIN_FORWARDER);
});
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token,
baseUrl: "https://api.example.com",
}),
@ -36,11 +44,12 @@ describe("SimpleLogin Forwarder", () => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
baseUrl,
}),
@ -62,9 +71,10 @@ describe("SimpleLogin Forwarder", () => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
await forwarder.generate(website, {
await forwarder.generate({
website,
token: "token",
baseUrl: "https://api.example.com",
});
@ -85,9 +95,10 @@ describe("SimpleLogin Forwarder", () => {
const apiService = mockApiService(status, { alias });
const i18nService = mockI18nService();
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
const result = await forwarder.generate(null, {
const result = await forwarder.generate({
website: null,
token: "token",
baseUrl: "https://api.example.com",
});
@ -101,11 +112,12 @@ describe("SimpleLogin Forwarder", () => {
const apiService = mockApiService(401, {});
const i18nService = mockI18nService();
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
baseUrl: "https://api.example.com",
}),
@ -126,11 +138,12 @@ describe("SimpleLogin Forwarder", () => {
const apiService = mockApiService(500, body);
const i18nService = mockI18nService();
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
baseUrl: "https://api.example.com",
}),
@ -159,11 +172,12 @@ describe("SimpleLogin Forwarder", () => {
const apiService = mockApiService(statusCode, { error });
const i18nService = mockI18nService();
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
await expect(
async () =>
await forwarder.generate(null, {
await forwarder.generate({
website: null,
token: "token",
baseUrl: "https://api.example.com",
}),

View File

@ -1,21 +1,39 @@
import { ApiService } from "../../../../abstractions/api.service";
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
import { I18nService } from "../../../../platform/abstractions/i18n.service";
import { StateProvider } from "../../../../platform/state";
import { SIMPLE_LOGIN_FORWARDER } from "../../key-definitions";
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
import { Forwarders } from "../options/constants";
import { Forwarder, SelfHostedApiOptions } from "../options/forwarder-options";
import { SelfHostedApiOptions } from "../options/forwarder-options";
/** Generates a forwarding address for Simple Login */
export class SimpleLoginForwarder implements Forwarder {
export class SimpleLoginForwarder extends ForwarderGeneratorStrategy<SelfHostedApiOptions> {
/** Instantiates the forwarder
* @param apiService used for ajax requests to the forwarding service
* @param i18nService used to look up error strings
* @param encryptService protects sensitive forwarder options
* @param keyService looks up the user key when protecting data.
* @param stateProvider creates the durable state for options storage
*/
constructor(
private apiService: ApiService,
private i18nService: I18nService,
) {}
encryptService: EncryptService,
keyService: CryptoService,
stateProvider: StateProvider,
) {
super(encryptService, keyService, stateProvider);
}
/** {@link Forwarder.generate} */
async generate(website: string, options: SelfHostedApiOptions): Promise<string> {
/** {@link ForwarderGeneratorStrategy.key} */
get key() {
return SIMPLE_LOGIN_FORWARDER;
}
/** {@link ForwarderGeneratorStrategy.generate} */
generate = async (options: SelfHostedApiOptions) => {
if (!options.token || options.token === "") {
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.SimpleLogin.name);
throw error;
@ -27,11 +45,11 @@ export class SimpleLoginForwarder implements Forwarder {
let url = options.baseUrl + "/api/alias/random/new";
let noteId = "forwarderGeneratedBy";
if (website && website !== "") {
url += "?hostname=" + website;
if (options.website && options.website !== "") {
url += "?hostname=" + options.website;
noteId = "forwarderGeneratedByWithWebsite";
}
const note = this.i18nService.t(noteId, website ?? "");
const note = this.i18nService.t(noteId, options.website ?? "");
const request = new Request(url, {
redirect: "manual",
@ -60,5 +78,5 @@ export class SimpleLoginForwarder implements Forwarder {
const error = this.i18nService.t("forwarderUnknownError", Forwarders.SimpleLogin.name);
throw error;
}
}
};
}

View File

@ -85,27 +85,33 @@ export const DefaultOptions: UsernameGeneratorOptions = Object.freeze({
forwarders: Object.freeze({
service: Forwarders.Fastmail.id,
fastMail: Object.freeze({
website: null,
domain: "",
prefix: "",
token: "",
}),
addyIo: Object.freeze({
website: null,
baseUrl: "https://app.addy.io",
domain: "",
token: "",
}),
forwardEmail: Object.freeze({
website: null,
token: "",
domain: "",
}),
simpleLogin: Object.freeze({
website: null,
baseUrl: "https://app.simplelogin.io",
token: "",
}),
duckDuckGo: Object.freeze({
website: null,
token: "",
}),
firefoxRelay: Object.freeze({
website: null,
token: "",
}),
}),

View File

@ -1,5 +1,3 @@
import { EncString } from "../../../../platform/models/domain/enc-string";
/** Identifiers for email forwarding services.
* @remarks These are used to select forwarder-specific options.
* The must be kept in sync with the forwarder implementations.
@ -24,26 +22,24 @@ export type ForwarderMetadata = {
validForSelfHosted: boolean;
};
/** An email forwarding service configurable through an API. */
export interface Forwarder {
/** Generate a forwarding email.
* @param website The website to generate a username for.
* @param options The options to use when generating the username.
*/
generate(website: string | null, options: ApiOptions): Promise<string>;
}
/** Options common to all forwarder APIs */
export type ApiOptions = {
/** bearer token that authenticates bitwarden to the forwarder.
* This is required to issue an API request.
*/
token?: string;
} & RequestOptions;
/** encrypted bearer token that authenticates bitwarden to the forwarder.
* This is used to store the token at rest and must be decoded before use.
/** Options that provide contextual information about the application state
* when a forwarder is invoked.
* @remarks these fields should always be omitted when saving options.
*/
export type RequestOptions = {
/** @param website The domain of the website the generated email is used
* within. This should be set to `null` when the request is not specific
* to any website.
*/
encryptedToken?: EncString;
website: string | null;
};
/** Api configuration for forwarders that support self-hosted installations. */

View File

@ -24,27 +24,33 @@ const TestOptions: UsernameGeneratorOptions = {
forwarders: {
service: Forwarders.Fastmail.id,
fastMail: {
website: null,
domain: "httpbin.com",
prefix: "foo",
token: "some-token",
},
addyIo: {
website: null,
baseUrl: "https://app.addy.io",
domain: "example.com",
token: "some-token",
},
forwardEmail: {
website: null,
token: "some-token",
domain: "example.com",
},
simpleLogin: {
website: null,
baseUrl: "https://app.simplelogin.io",
token: "some-token",
},
duckDuckGo: {
website: null,
token: "some-token",
},
firefoxRelay: {
website: null,
token: "some-token",
},
},

18
package-lock.json generated
View File

@ -94,7 +94,7 @@
"@storybook/jest": "0.2.3",
"@storybook/testing-library": "0.2.2",
"@types/argon2-browser": "1.18.1",
"@types/chrome": "0.0.243",
"@types/chrome": "0.0.262",
"@types/duo_web_sdk": "2.7.1",
"@types/firefox-webext-browser": "111.0.1",
"@types/inquirer": "8.2.10",
@ -128,7 +128,7 @@
"copy-webpack-plugin": "11.0.0",
"cross-env": "7.0.3",
"css-loader": "6.8.1",
"electron": "28.2.5",
"electron": "28.2.6",
"electron-builder": "24.9.1",
"electron-log": "5.0.1",
"electron-reload": "2.0.0-alpha.1",
@ -234,7 +234,7 @@
},
"apps/desktop": {
"name": "@bitwarden/desktop",
"version": "2024.2.1",
"version": "2024.2.2",
"hasInstallScript": true,
"license": "GPL-3.0"
},
@ -10867,9 +10867,9 @@
}
},
"node_modules/@types/chrome": {
"version": "0.0.243",
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.243.tgz",
"integrity": "sha512-4PHv0kxxxpZFHWPBiJJ9TWH8kbx0567j1b2djnhpJjpiSGNI7UKkz7dSEECBtQ0B3N5nQTMwSB/5IopkWGAbEA==",
"version": "0.0.262",
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.262.tgz",
"integrity": "sha512-TOoj3dqSYE13PD2fRuMQ6X6pggEvL9rRk/yOYOyWE6sfqRWxsJm4VoVm+wr9pkr4Sht/M5t7FFL4vXato8d1gA==",
"dev": true,
"dependencies": {
"@types/filesystem": "*",
@ -17807,9 +17807,9 @@
}
},
"node_modules/electron": {
"version": "28.2.5",
"resolved": "https://registry.npmjs.org/electron/-/electron-28.2.5.tgz",
"integrity": "sha512-qlvQkDNVAzN647NpiJJw7GYJqE0NwK4+1evkhrQ0Xv6Qgab1EtN50G4oDr4/x/+O5pGUG2P5d3isXu+37O3RDw==",
"version": "28.2.6",
"resolved": "https://registry.npmjs.org/electron/-/electron-28.2.6.tgz",
"integrity": "sha512-RuhbW+ifvh3DqnVlHCcCKhKIFOxTktq1GN1gkIkEZ8y5LEZfcjOkxB2s6Fd1S6MzsMZbiJti+ZJG5hXS4SDVLQ==",
"dev": true,
"hasInstallScript": true,
"dependencies": {

View File

@ -55,7 +55,7 @@
"@storybook/jest": "0.2.3",
"@storybook/testing-library": "0.2.2",
"@types/argon2-browser": "1.18.1",
"@types/chrome": "0.0.243",
"@types/chrome": "0.0.262",
"@types/duo_web_sdk": "2.7.1",
"@types/firefox-webext-browser": "111.0.1",
"@types/inquirer": "8.2.10",
@ -89,7 +89,7 @@
"copy-webpack-plugin": "11.0.0",
"cross-env": "7.0.3",
"css-loader": "6.8.1",
"electron": "28.2.5",
"electron": "28.2.6",
"electron-builder": "24.9.1",
"electron-log": "5.0.1",
"electron-reload": "2.0.0-alpha.1",