1
0
mirror of https://github.com/bitwarden/desktop.git synced 2024-09-27 03:53:00 +02:00

Compare commits

..

6 Commits

Author SHA1 Message Date
github-actions[bot]
68f09f7401 Bump version to 1.32.0 (#1415)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit e5feda0fa6)
2022-03-16 10:47:07 -06:00
addison
35aaa71363 Revert "Disable disk caching in stateService (#1406)"
This reverts commit 8461161ac5.
2022-03-15 09:38:04 -04:00
Thomas Rittson
8461161ac5 Disable disk caching in stateService (#1406)
* Disable disk caching in stateService

* Turn back on caching for the renderer

Co-authored-by: addison <addisonbeck1@gmail.com>
2022-03-15 09:26:51 -04:00
Matt Gibson
a9a43d5f21 Update jslib 2022-03-15 08:55:59 -04:00
Matt Gibson
762e3caed9 Add jit entitlement (#1395)
* Add arm64 package

* temporarily remove workflow ignore

* Don't delete universal dist when completing arm64

* Add allow-jit entitlement

* Revert "Don't delete universal dist when completing arm64"

This reverts commit b06d638345.

* Revert "temporarily remove workflow ignore"

This reverts commit 8007983547.

* Revert "Add arm64 package"

This reverts commit c8359dae4d.

Co-authored-by: Hinton <oscar@oscarhinton.com>
(cherry picked from commit 81457f6c05)
2022-03-11 11:03:22 -05:00
Joseph Flinn
21f96af441 Update hotfix release branch name to hotfix-rc (#1396)
(cherry picked from commit f845bcf6c1)
2022-03-09 12:52:47 -08:00
172 changed files with 3686 additions and 9492 deletions

View File

@ -4,6 +4,5 @@ jslib
webpack.main.js
webpack.renderer.js
src/scripts/duo.js
desktop_native
**/node_modules

View File

@ -8,8 +8,6 @@ on:
- 'gh-pages'
paths-ignore:
- '.github/workflows/**'
workflow_dispatch:
inputs: {}
jobs:
cloc:
@ -17,7 +15,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up cloc
run: |
@ -39,7 +37,7 @@ jobs:
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Get Package Version
id: retrieve-version
@ -83,28 +81,6 @@ jobs:
echo "::set-output name=hotfix_branch_exists::0"
fi
lint:
name: Lint
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Cache npm
id: npm-cache
uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed
with:
path: '~/.npm'
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install Node dependencies
run: npm ci
- name: Run linter
run: npm run lint
linux:
name: Linux Build
@ -114,15 +90,20 @@ jobs:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '16'
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: '~/.npm'
key: ${{ runner.os }}-${{ github.run_id }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
@ -149,60 +130,49 @@ jobs:
- name: Install Node dependencies
run: npm ci
- name: Cache Native Module
uses: actions/cache@48af2dc4a9e8278b89d7fa154b955c30c6aaab09 # v3.0.2
id: cache
with:
path: desktop_native/*.node
key: rust-${{ runner.os }}-${{ hashFiles('desktop_native/**/*') }}
- name: Build Native Module
if: steps.cache.outputs.cache-hit != 'true'
working-directory: './desktop_native'
run: |
npm ci
npm run build:cross-platform
- name: Run linter
run: npm run lint
- name: Build application
run: npm run dist:lin
- name: Upload .deb artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb
if-no-files-found: error
- name: Upload .rpm artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm
if-no-files-found: error
- name: Upload .freebsd artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd
if-no-files-found: error
- name: Upload .snap artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap
path: ./dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap
if-no-files-found: error
- name: Upload .AppImage artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
if-no-files-found: error
- name: Upload latest auto-update artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: latest-linux.yml
path: ./dist/latest-linux.yml
@ -217,15 +187,20 @@ jobs:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '16'
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: '%AppData%/npm-cache'
key: ${{ runner.os }}-${{ github.run_id }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
shell: pwsh
@ -242,27 +217,20 @@ jobs:
shell: pwsh
run: choco install checksum --no-progress
- name: Rust
shell: pwsh
run: |
rustup target install i686-pc-windows-msvc
rustup target install aarch64-pc-windows-msvc
- name: Print environment
run: |
node --version
npm --version
choco --version
rustup show
- name: Login to Azure
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with:
keyvault: "bitwarden-prod-kv"
secrets: "code-signing-vault-url,
@ -274,19 +242,8 @@ jobs:
- name: Install Node dependencies
run: npm ci
- name: Cache Native Module
uses: actions/cache@48af2dc4a9e8278b89d7fa154b955c30c6aaab09 # v3.0.2
id: cache
with:
path: desktop_native/*.node
key: rust-${{ runner.os }}-${{ hashFiles('desktop_native/**/*') }}
- name: Build Native Module
if: steps.cache.outputs.cache-hit != 'true'
working-directory: './desktop_native'
run: |
npm ci
npm run build:cross-platform
- name: Run linter
run: npm run lint
- name: Build & Sign (dev)
env:
@ -323,91 +280,91 @@ jobs:
choco pack ./dist/chocolatey/bitwarden.nuspec --version "$env:_PACKAGE_VERSION" --out ./dist/chocolatey
- name: Upload portable exe artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe
path: ./dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe
if-no-files-found: error
- name: Upload installer exe artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe
path: ./dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe
if-no-files-found: error
- name: Upload appx ia32 artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx
if-no-files-found: error
- name: Upload store appx ia32 artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx
if-no-files-found: error
- name: Upload NSIS ia32 artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z
path: ./dist/nsis-web/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z
if-no-files-found: error
- name: Upload appx x64 artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx
if-no-files-found: error
- name: Upload store appx x64 artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx
if-no-files-found: error
- name: Upload NSIS x64 artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z
path: ./dist/nsis-web/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z
if-no-files-found: error
- name: Upload appx ARM64 artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx
if-no-files-found: error
- name: Upload store appx ARM64 artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx
if-no-files-found: error
- name: Upload NSIS ARM64 artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z
path: ./dist/nsis-web/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z
if-no-files-found: error
- name: Upload nupkg artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg
path: ./dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg
if-no-files-found: error
- name: Upload latest auto-update artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: latest.yml
path: ./dist/nsis-web/latest.yml
@ -422,15 +379,20 @@ jobs:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '16'
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: '~/.npm'
key: ${{ runner.os }}-${{ github.run_id }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
@ -439,28 +401,23 @@ jobs:
npm install -g node-gyp
node-gyp install $(node -v)
- name: Rust
shell: pwsh
run: rustup target install aarch64-apple-darwin
- name: Print environment
run: |
node --version
npm --version
rustup show
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Cache Build
id: build-cache
uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Cache Safari
id: safari-cache
uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: dist-safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@ -532,27 +489,16 @@ jobs:
env:
BUILD_NUMBER: ${{ needs.setup.outputs.build_number }}
run: |
$package = Get-Content -Raw -Path $env:GITHUB_WORKSPACE\electron-builder.json | ConvertFrom-Json;
$package | Add-Member -MemberType NoteProperty -Name buildVersion -Value "$env:BUILD_NUMBER";
$package | ConvertTo-Json -Depth 32 | Set-Content $env:GITHUB_WORKSPACE\electron-builder.json;
- name: Cache Native Module
uses: actions/cache@48af2dc4a9e8278b89d7fa154b955c30c6aaab09 # v3.0.2
id: cache
with:
path: desktop_native/*.node
key: rust-${{ runner.os }}-${{ hashFiles('desktop_native/**/*') }}
- name: Build Native Module
if: steps.cache.outputs.cache-hit != 'true'
working-directory: './desktop_native'
run: |
npm ci
npm run build:cross-platform
$package = Get-Content -Raw -Path $env:GITHUB_WORKSPACE\package.json | ConvertFrom-Json;
$package.build | Add-Member -MemberType NoteProperty -Name buildVersion -Value "$env:BUILD_NUMBER";
$package | ConvertTo-Json -Depth 32 | Set-Content $env:GITHUB_WORKSPACE\package.json;
- name: Install Node dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Build application (dev)
run: npm run build
@ -561,39 +507,39 @@ jobs:
run: New-Item ./dist-safari -ItemType Directory -ea 0
- name: Checkout browser extension
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
repository: 'bitwarden/browser'
path: 'dist-safari/bitwarden'
path: 'dist-safari/browser'
ref: ${{ needs.setup.outputs.safari_ref }}
- name: Build Safari extension
run: |
npm install
npm run dist:safari
working-directory: dist-safari/bitwarden/apps/browser
shell: pwsh
run: ./scripts/safari-build.ps1 -skipcheckout -skipoutcopy
macos-package-github:
name: MacOS Package GitHub Release Assets
runs-on: macos-11
needs:
- setup
- macos-build
- lint
needs: [setup, macos-build]
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '16'
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: '~/.npm'
key: ${{ runner.os }}-${{ github.run_id }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
@ -602,28 +548,23 @@ jobs:
npm install -g node-gyp
node-gyp install $(node -v)
- name: Rust
shell: pwsh
run: rustup target install aarch64-apple-darwin
- name: Print environment
run: |
node --version
npm --version
rustup show
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Get Build Cache
id: build-cache
uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: dist-safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@ -695,23 +636,9 @@ jobs:
env:
BUILD_NUMBER: ${{ needs.setup.outputs.build_number }}
run: |
$package = Get-Content -Raw -Path $env:GITHUB_WORKSPACE\electron-builder.json | ConvertFrom-Json;
$package | Add-Member -MemberType NoteProperty -Name buildVersion -Value "$env:BUILD_NUMBER";
$package | ConvertTo-Json -Depth 32 | Set-Content $env:GITHUB_WORKSPACE\electron-builder.json;
- name: Cache Native Module
uses: actions/cache@48af2dc4a9e8278b89d7fa154b955c30c6aaab09 # v3.0.2
id: cache
with:
path: desktop_native/*.node
key: rust-${{ runner.os }}-${{ hashFiles('desktop_native/**/*') }}
- name: Build Native Module
if: steps.cache.outputs.cache-hit != 'true'
working-directory: './desktop_native'
run: |
npm ci
npm run build:cross-platform
$package = Get-Content -Raw -Path $env:GITHUB_WORKSPACE\package.json | ConvertFrom-Json;
$package.build | Add-Member -MemberType NoteProperty -Name buildVersion -Value "$env:BUILD_NUMBER";
$package | ConvertTo-Json -Depth 32 | Set-Content $env:GITHUB_WORKSPACE\package.json;
- name: NPM install
run: npm ci
@ -727,24 +654,20 @@ jobs:
- name: Checkout browser extension
if: steps.safari-cache.outputs.cache-hit != 'true'
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
repository: 'bitwarden/bitwarden'
path: 'dist-safari/bitwarden'
repository: 'bitwarden/browser'
path: 'dist-safari/browser'
ref: ${{ needs.setup.outputs.safari_ref }}
- name: Build Safari extension
if: steps.safari-cache.outputs.cache-hit != 'true'
run: |
npm install
npm run dist:safari
working-directory: dist-safari/bitwarden/apps/browser
shell: pwsh
run: ./scripts/safari-build.ps1 -skipcheckout -skipoutcopy
- name: Load Safari extension for .dmg
working-directory: dist-safari/bitwarden/apps/browser
run: |
mkdir PlugIns
cp -r dist/Safari/dmg/build/Release/safari.appex PlugIns/safari.appex
shell: pwsh
run: ./scripts/safari-build.ps1 -copyonly
- name: Build application (dist)
env:
@ -753,28 +676,28 @@ jobs:
run: npm run pack:mac
- name: Upload .zip artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip
if-no-files-found: error
- name: Upload .dmg artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg
if-no-files-found: error
- name: Upload .dmg blockmap artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap
path: ./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap
if-no-files-found: error
- name: Upload latest auto-update artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: latest-mac.yml
path: ./dist/latest-mac.yml
@ -784,23 +707,25 @@ jobs:
macos-package-mas:
name: MacOS Package Prod Release Asset
runs-on: macos-11
needs:
- setup
- macos-build
- lint
needs: [setup, macos-build]
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '16'
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: '~/.npm'
key: ${{ runner.os }}-${{ github.run_id }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
@ -809,28 +734,23 @@ jobs:
npm install -g node-gyp
node-gyp install $(node -v)
- name: Rust
shell: pwsh
run: rustup target install aarch64-apple-darwin
- name: Print environment
run: |
node --version
npm --version
rustup show
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Get Build Cache
id: build-cache
uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: dist-safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@ -902,23 +822,9 @@ jobs:
env:
BUILD_NUMBER: ${{ needs.setup.outputs.build_number }}
run: |
$package = Get-Content -Raw -Path $env:GITHUB_WORKSPACE\electron-builder.json | ConvertFrom-Json;
$package | Add-Member -MemberType NoteProperty -Name buildVersion -Value "$env:BUILD_NUMBER";
$package | ConvertTo-Json -Depth 32 | Set-Content $env:GITHUB_WORKSPACE\electron-builder.json;
- name: Cache Native Module
uses: actions/cache@48af2dc4a9e8278b89d7fa154b955c30c6aaab09 # v3.0.2
id: cache
with:
path: desktop_native/*.node
key: rust-${{ runner.os }}-${{ hashFiles('desktop_native/**/*') }}
- name: Build Native Module
if: steps.cache.outputs.cache-hit != 'true'
working-directory: './desktop_native'
run: |
npm ci
npm run build:cross-platform
$package = Get-Content -Raw -Path $env:GITHUB_WORKSPACE\package.json | ConvertFrom-Json;
$package.build | Add-Member -MemberType NoteProperty -Name buildVersion -Value "$env:BUILD_NUMBER";
$package | ConvertTo-Json -Depth 32 | Set-Content $env:GITHUB_WORKSPACE\package.json;
- name: NPM install
run: npm ci
@ -934,24 +840,20 @@ jobs:
- name: Checkout browser extension
if: steps.safari-cache.outputs.cache-hit != 'true'
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
repository: 'bitwarden/browser'
path: 'dist-safari/bitwarden'
path: 'dist-safari/browser'
ref: ${{ needs.setup.outputs.safari_ref }}
- name: Build Safari extension
if: steps.safari-cache.outputs.cache-hit != 'true'
run: |
npm install
npm run dist:safari
working-directory: dist-safari/bitwarden/apps/browser
shell: pwsh
run: ./scripts/safari-build.ps1 -skipcheckout -skipoutcopy
- name: Load Safari extension for App Store
working-directory: dist-safari/bitwarden/apps/browser
run: |
mkdir PlugIns
cp -r dist/Safari/mas/build/Release/safari.appex PlugIns/safari.appex
shell: pwsh
run: ./scripts/safari-build.ps1 -mas -copyonly
- name: Build application for App Store
run: npm run pack:mac:mas
@ -960,7 +862,7 @@ jobs:
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
- name: Upload .pkg artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg
path: ./dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg
@ -983,23 +885,25 @@ jobs:
name: MacOS Package Dev Release Asset
if: false # We need to look into how code signing works for dev
runs-on: macos-11
needs:
- setup
- macos-build
- lint
needs: [setup, macos-build]
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '16'
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: '~/.npm'
key: ${{ runner.os }}-${{ github.run_id }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
@ -1017,14 +921,14 @@ jobs:
- name: Get Build Cache
id: build-cache
uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: build
key: ${{ runner.os }}-${{ github.run_id }}-build
- name: Setup Safari Cache
id: safari-cache
uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: dist-safari
key: ${{ runner.os }}-${{ github.run_id }}-safari-extension
@ -1096,23 +1000,9 @@ jobs:
env:
BUILD_NUMBER: ${{ needs.setup.outputs.build_number }}
run: |
$package = Get-Content -Raw -Path $env:GITHUB_WORKSPACE\electron-builder.json | ConvertFrom-Json;
$package | Add-Member -MemberType NoteProperty -Name buildVersion -Value "$env:BUILD_NUMBER";
$package | ConvertTo-Json -Depth 32 | Set-Content $env:GITHUB_WORKSPACE\electron-builder.json;
- name: Cache Native Module
uses: actions/cache@48af2dc4a9e8278b89d7fa154b955c30c6aaab09 # v3.0.2
id: cache
with:
path: desktop_native/*.node
key: rust-${{ runner.os }}-${{ hashFiles('desktop_native/**/*') }}
- name: Build Native Module
if: steps.cache.outputs.cache-hit != 'true'
working-directory: './desktop_native'
run: |
npm ci
npm run build:cross-platform
$package = Get-Content -Raw -Path $env:GITHUB_WORKSPACE\package.json | ConvertFrom-Json;
$package.build | Add-Member -MemberType NoteProperty -Name buildVersion -Value "$env:BUILD_NUMBER";
$package | ConvertTo-Json -Depth 32 | Set-Content $env:GITHUB_WORKSPACE\package.json;
- name: NPM install
run: npm ci
@ -1128,24 +1018,20 @@ jobs:
- name: Checkout browser extension
if: steps.safari-cache.outputs.cache-hit != 'true'
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
repository: 'bitwarden/browser'
path: 'dist-safari/bitwarden'
path: 'dist-safari/browser'
ref: ${{ needs.setup.outputs.safari_ref }}
- name: Build Safari extension
if: steps.safari-cache.outputs.cache-hit != 'true'
run: |
npm install
npm run dist:safari
working-directory: dist-safari/bitwarden/apps/browser
shell: pwsh
run: ./scripts/safari-build.ps1 -skipcheckout -skipoutcopy
- name: Load Safari extension for App Store
working-directory: dist-safari/bitwarden/apps/browser
run: |
mkdir PlugIns
cp -r dist/Safari/masdev/build/Release/safari.appex PlugIns/safari.appex
shell: pwsh
run: ./scripts/safari-build.ps1 -masdev -copyonly
- name: Build dev application for App Store
run: npm run pack:mac:masdev
@ -1158,7 +1044,7 @@ jobs:
run: zip -r Bitwarden-${{ env.PACKAGE_VERSION }}-masdev-universal.zip Bitwarden.app
- name: Upload masdev artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip
path: ./dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip
@ -1178,22 +1064,22 @@ jobs:
_CROWDIN_PROJECT_ID: "299360"
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Login to Azure
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with:
keyvault: "bitwarden-prod-kv"
secrets: "crowdin-api-token"
- name: Upload Sources
uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
@ -1211,7 +1097,6 @@ jobs:
needs:
- cloc
- setup
- lint
- linux
- windows
- macos-build
@ -1224,7 +1109,6 @@ jobs:
env:
CLOC_STATUS: ${{ needs.cloc.result }}
SETUP_STATUS: ${{ needs.setup.result }}
LINT_STATUS: ${{ needs.lint.result }}
LINUX_STATUS: ${{ needs.linux.result }}
WINDOWS_STATUS: ${{ needs.windows.result }}
MACOS_BUILD_STATUS: ${{ needs.macos-build.result }}
@ -1236,8 +1120,6 @@ jobs:
exit 1
elif [ "$SETUP_STATUS" = "failure" ]; then
exit 1
elif [ "$LINT_STATUS" = "failure" ]; then
exit 1
elif [ "$LINUX_STATUS" = "failure" ]; then
exit 1
elif [ "$WINDOWS_STATUS" = "failure" ]; then
@ -1253,21 +1135,21 @@ jobs:
fi
- name: Login to Azure - Prod Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
if: failure()
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
if: failure()
with:
keyvault: "bitwarden-prod-kv"
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}

View File

@ -62,69 +62,48 @@ jobs:
BRANCH_NAME=$(basename ${{ github.ref }})
echo "::set-output name=branch-name::$BRANCH_NAME"
- name: Login to Azure
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
with:
keyvault: "bitwarden-prod-kv"
secrets: "aws-electron-access-id, aws-electron-access-key"
- name: Download all artifacts
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }}
path: ./artifacts
- name: Rename .pkg to .pkg.archive
env:
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
working-directory: ./artifacts
run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive
- name: Publish artifacts to S3
env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }}
AWS_DEFAULT_REGION: 'us-west-2'
run: |
aws s3 cp ./artifacts s3://public-s3-electron-artifacts/desktop/ \
--acl "public-read" \
--recursive \
--quiet
- name: Create release
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
env:
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
with:
artifacts: "artifacts/Bitwarden-${{ env.PKG_VERSION }}-amd64.deb,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-x64.freebsd,
artifacts/bitwarden_${{ env.PKG_VERSION }}_amd64.snap,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-x86_64.AppImage,
artifacts/Bitwarden-Portable-${{ env.PKG_VERSION }}.exe,
artifacts/Bitwarden-Installer-${{ env.PKG_VERSION }}.exe,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-ia32-store.appx,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-ia32.appx,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-ia32.nsis.7z,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-x64-store.appx,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-x64.appx,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-x64.nsis.7z,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-arm64-store.appx,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-arm64.appx,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-arm64.nsis.7z,
artifacts/bitwarden.${{ env.PKG_VERSION }}.nupkg,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-universal-mac.zip,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-universal.dmg,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-universal.dmg.blockmap,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive"
artifacts: "Bitwarden-${{ env.PKG_VERSION }}-amd64.deb,
Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm,
Bitwarden-${{ env.PKG_VERSION }}-x64.freebsd,
bitwarden_${{ env.PKG_VERSION }}_amd64.snap,
Bitwarden-${{ env.PKG_VERSION }}-x86_64.AppImage,
latest-linux.yml,
Bitwarden-Portable-${{ env.PKG_VERSION }}.exe,
Bitwarden-Installer-${{ env.PKG_VERSION }}.exe,
Bitwarden-${{ env.PKG_VERSION }}-ia32-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-ia32.appx,
Bitwarden-${{ env.PKG_VERSION }}-ia32.nsis.7z,
Bitwarden-${{ env.PKG_VERSION }}-x64-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-x64.appx,
Bitwarden-${{ env.PKG_VERSION }}-x64.nsis.7z,
Bitwarden-${{ env.PKG_VERSION }}-arm64-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-arm64.appx,
Bitwarden-${{ env.PKG_VERSION }}-arm64.nsis.7z,
bitwarden.${{ env.PKG_VERSION }}.nupkg,
latest.yml,
Bitwarden-${{ env.PKG_VERSION }}-universal-mac.zip,
Bitwarden-${{ env.PKG_VERSION }}-universal.dmg,
Bitwarden-${{ env.PKG_VERSION }}-universal.dmg.blockmap,
latest-mac.yml,
Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive"
commit: ${{ github.sha }}
tag: v${{ env.PKG_VERSION }}
name: Version ${{ env.PKG_VERSION }}
@ -163,7 +142,7 @@ jobs:
run: mkdir dist
- name: Download Snap artifact
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with:
workflow: build.yml
workflow_conclusion: success
@ -171,6 +150,9 @@ jobs:
artifacts: bitwarden_${{ env._PKG_VERSION }}_amd64.snap
path: ./dist
- name: Test
run: ls -alht dist
- name: Deploy to Snap Store
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: |
@ -197,7 +179,7 @@ jobs:
run: New-Item -ItemType directory -Path ./dist
- name: Download choco artifact
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with:
workflow: build.yml
workflow_conclusion: success

View File

@ -2,7 +2,6 @@
build
dist
dist-safari
desktop_native
jslib

View File

@ -10,7 +10,7 @@ Here is how you can get involved:
- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
- **Report a bug or submit a bugfix:** Use Github issues and pull requests
- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
- **Help other users:** Go to the [Ask the Bitwarden Community category](https://community.bitwarden.com/c/support/) on the Community Forums
- **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
- **Translate:** See the localization (l10n) section below
## Contributor Agreement
@ -31,6 +31,6 @@ We use a translation tool called [Crowdin](https://crowdin.com) to help manage o
If you are interested in helping translate the Bitwarden desktop app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-desktop
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/dwbit).
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/kspearrin).
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/

View File

@ -2,10 +2,6 @@
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-desktop/localized.svg)](https://crowdin.com/project/bitwarden-desktop)
[![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby)
> **Archived**
>
> This repository is archived, please go to https://github.com/bitwarden/clients for future development.
# Bitwarden Desktop Application
[![Platforms](https://imgur.com/SLv9paA.png "Windows, macOS, and Linux")](https://bitwarden.com/download/)
@ -16,23 +12,20 @@ The Bitwarden desktop app is written using Electron and Angular. The application
# Build/Run
## Requirements
**Requirements**
- [Node.js](https://nodejs.org) v16.13.1 (LTS) or greater
- NPM v8
- Windows:
- To compile the native node modules used in the app you will need the _Visual C++ toolset_, available through the standard Visual Studio installer. You will also need to install the _Microsoft Build Tools 2015_ and _Windows 10 SDK 17134_ as additional dependencies in the Visual Studio installer.
- Linux:
- The following packages `build-essential libsecret-1-dev libglib2.0-dev`
- Windows users: To compile the native node modules used in the app you will need the _Visual C++ toolset_, available through the standard Visual Studio installer. You will also need to install the _Microsoft Build Tools 2015_ and _Windows 10 SDK 17134_ as additional dependencies in the Visual Studio installer.
## Run the app
**Run the app**
```bash
npm ci
npm install
npm run electron
```
### Debug Native Messaging
**Debug Native Messaging**
Native Messaging (communication with the browser extension) works by having the browser start a lightweight proxy application baked into our desktop binary. To setup an environment which allows
for easy debugging you will need to build the application for distribution, i.e. `npm run dist:<platform>`, start the dist version and enable desktop integration. This will write some manifests

View File

@ -1,11 +1,39 @@
Bitwarden believes that working with security researchers across the globe is crucial to keeping our users safe. If you believe you've found a security issue in our product or service, we encourage you to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We welcome working with you to resolve the issue promptly. Thanks in advance!
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
users safe. If you believe you've found a security issue in our product or service, we encourage you to
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
# Disclosure Policy
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our service. Only interact with accounts you own or with explicit permission of the account holder.
- If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.
- If you would like to encrypt your report, please use the PGP key with long ID
`0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
# In-scope
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
code is available at https://github.com/bitwarden.
# Exclusions
The following bug classes are out-of scope:
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
or that we already know of. Note that some of our issue tracking is private.
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
upstream maintainer.
- Attacks requiring physical access to a user's device.
- Self-XSS
- Issues related to software or protocols not under Bitwarden's control
- Vulnerabilities in outdated versions of Bitwarden
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
While researching, we'd like to ask you to refrain from:
@ -14,8 +42,4 @@ While researching, we'd like to ask you to refrain from:
- Social engineering (including phishing) of Bitwarden staff or contractors
- Any physical attempts against Bitwarden property or data centers
# We want to help you!
If you have something that you feel is close to exploitation, or if you'd like some information regarding the internal API, or generally have any questions regarding the app that would help in your efforts, please email us at https://bitwarden.com/contact and ask for that information. As stated above, Bitwarden wants to help you find issues, and is more than willing to help.
Thank you for helping keep Bitwarden and our users safe!

View File

@ -1,6 +0,0 @@
target
index.node
**/node_modules
**/.DS_Store
npm-debug.log*
*.node

View File

@ -1,946 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytes"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-expr"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e068cb2806bbc15b439846dc16c5f89f8599f2c3e4d73d4449d38f9b2f0b6c5"
dependencies = [
"smallvec",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "convert_case"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8"
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "ctor"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "cxx"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce2295fe8865279f404147e9b2328e5af0ad11a2c016e58c13acfd48a07d8a55"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0aaaa055d4908326f1b4524b23ae53758019b806c0c4f382ea240982e9766b26"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a670224c6686471df12560a0b97a08145082e70bd38e2b0b5383b79e46c3da7"
[[package]]
name = "cxxbridge-macro"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b700096ca0dece28d9535fdb17ab784a8ae155d7f29d39c273643e6292c9620"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "desktop_native"
version = "0.0.0"
dependencies = [
"anyhow",
"core-foundation",
"gio",
"keytar",
"libsecret",
"napi",
"napi-build",
"napi-derive",
"scopeguard",
"security-framework",
"security-framework-sys",
"tokio",
"widestring",
"windows 0.32.0",
]
[[package]]
name = "futures-channel"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
[[package]]
name = "futures-executor"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
[[package]]
name = "futures-task"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
[[package]]
name = "futures-util"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "gio"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96efd8a1c00d890f6b45671916e165b5e43ccec61957d443aff6d7e44f62d348"
dependencies = [
"bitflags",
"futures-channel",
"futures-core",
"futures-io",
"gio-sys",
"glib",
"libc",
"once_cell",
"thiserror",
]
[[package]]
name = "gio-sys"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d0fa5052773f5a56b8ae47dab09d040f5d9ce1311f4f99006e16e9a08269296"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"winapi",
]
[[package]]
name = "glib"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa570813c504bdf7539a9400180c2dd4b789a819556fb86da7226d7d1b037b49"
dependencies = [
"bitflags",
"futures-channel",
"futures-core",
"futures-executor",
"futures-task",
"glib-macros",
"glib-sys",
"gobject-sys",
"libc",
"once_cell",
"smallvec",
"thiserror",
]
[[package]]
name = "glib-macros"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41bfd8d227dead0829ac142454e97531b93f576d0805d779c42bfd799c65c572"
dependencies = [
"anyhow",
"heck",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "glib-sys"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4366377bd56697de8aaee24e673c575d2694d72e7756324ded2b0428829a7b8"
dependencies = [
"libc",
"system-deps",
]
[[package]]
name = "gobject-sys"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df6859463843c20cf3837e3a9069b6ab2051aeeadf4c899d33344f4aea83189a"
dependencies = [
"glib-sys",
"libc",
"system-deps",
]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "keytar"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d361c55fba09829ac620b040f5425bf239b1030c3d6820a84acac8da867dca4d"
dependencies = [
"keytar-sys",
]
[[package]]
name = "keytar-sys"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe908c6896705a1cb516cd6a5d956c63f08d95ace81b93253a98cd93e1e6a65a"
dependencies = [
"cc",
"cxx",
"cxx-build",
"pkg-config",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
[[package]]
name = "libsecret"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4af5a2342942fa42d706a424e9f9914287fb8317132750fd73a241140ac38c1"
dependencies = [
"bitflags",
"gio",
"glib",
"libc",
"libsecret-sys",
"once_cell",
]
[[package]]
name = "libsecret-sys"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287d2a0fcd95e4d7b0ac6fc9f802691a790d7e522138713b0cacebc4e63cab91"
dependencies = [
"gio-sys",
"glib-sys",
"gobject-sys",
"libc",
"pkg-config",
"system-deps",
]
[[package]]
name = "link-cplusplus"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cae2cd7ba2f3f63938b9c724475dfb7b9861b545a90324476324ed21dbc8c8"
dependencies = [
"cc",
]
[[package]]
name = "lock_api"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "mio"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
]
[[package]]
name = "napi"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ec66e60f000c78dd7c6215b6fa260e0591e09805024332bc5b3f55acc12244"
dependencies = [
"ctor",
"lazy_static",
"napi-sys",
"tokio",
"windows 0.30.0",
]
[[package]]
name = "napi-build"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebd4419172727423cf30351406c54f6cc1b354a2cfb4f1dba3e6cd07f6d5522b"
[[package]]
name = "napi-derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74ac5287a5e94a8728fc82d16c5127acc5eb5b8ad6404ef5f82d6a4ce8d5bdd2"
dependencies = [
"convert_case",
"napi-derive-backend",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "napi-derive-backend"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427f4f04525635cdf22005d1be62d6d671bcb5550d694a1efb480a315422b4af"
dependencies = [
"convert_case",
"once_cell",
"proc-macro2",
"quote",
"regex",
"syn",
]
[[package]]
name = "napi-sys"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a385494dac3c52cbcacb393bb3b42669e7db8ab240c7ad5115f549eb061f2cc"
[[package]]
name = "ntapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
dependencies = [
"winapi",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "parking_lot"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
]
[[package]]
name = "pin-project-lite"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "proc-macro-crate"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
dependencies = [
"thiserror",
"toml",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scratch"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96311ef4a16462c757bb6a39152c40f58f31cd2602a40fceb937e2bc34e6cbab"
[[package]]
name = "security-framework"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]]
name = "smallvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "socket2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "system-deps"
version = "6.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a45a1c4c9015217e12347f2a411b57ce2c4fc543913b14b6fe40483328e709"
dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml",
"version-compare",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee"
dependencies = [
"bytes",
"libc",
"memchr",
"mio",
"num_cpus",
"once_cell",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"winapi",
]
[[package]]
name = "tokio-macros"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version-compare"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "widestring"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b749ebd2304aa012c5992d11a25d07b406bdbe5f79d371cb7a918ce501a19eb0"
dependencies = [
"windows_aarch64_msvc 0.30.0",
"windows_i686_gnu 0.30.0",
"windows_i686_msvc 0.30.0",
"windows_x86_64_gnu 0.30.0",
"windows_x86_64_msvc 0.30.0",
]
[[package]]
name = "windows"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec"
dependencies = [
"windows_aarch64_msvc 0.32.0",
"windows_i686_gnu 0.32.0",
"windows_i686_msvc 0.32.0",
"windows_x86_64_gnu 0.32.0",
"windows_x86_64_msvc 0.32.0",
]
[[package]]
name = "windows-sys"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6"
dependencies = [
"windows_aarch64_msvc 0.32.0",
"windows_i686_gnu 0.32.0",
"windows_i686_msvc 0.32.0",
"windows_x86_64_gnu 0.32.0",
"windows_x86_64_msvc 0.32.0",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca"
[[package]]
name = "windows_aarch64_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
[[package]]
name = "windows_i686_gnu"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8"
[[package]]
name = "windows_i686_gnu"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
[[package]]
name = "windows_i686_msvc"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6"
[[package]]
name = "windows_i686_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
[[package]]
name = "windows_x86_64_gnu"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"

View File

@ -1,43 +0,0 @@
[package]
edition = "2021"
exclude = ["index.node"]
license = "GPL-3.0"
name = "desktop_native"
version = "0.0.0"
[lib]
crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0"
napi = {version = "2", features = ["async"]}
napi-derive = "2"
scopeguard = "1.1.0"
tokio = {version = "1.17.0", features = ["full"]}
[build-dependencies]
napi-build = "1"
[target.'cfg(windows)'.dependencies]
widestring = "0.5.1"
windows = {version = "0.32.0", features = [
"alloc",
"Foundation",
"Storage_Streams",
"Win32_Foundation",
"Win32_Security_Credentials",
]}
[target.'cfg(windows)'.dev-dependencies]
keytar = "0.1.6"
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9.3"
security-framework = "2.6.1"
security-framework-sys = "2.6.1"
[target.'cfg(target_os = "linux")'.dependencies]
gio = "0.15.6"
libsecret = "0.1.4"

View File

@ -1,22 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const child_process = require("child_process");
const process = require("process");
let targets = [];
switch (process.platform) {
case "win32":
targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"];
break;
case "darwin":
targets = ["x86_64-apple-darwin", "aarch64-apple-darwin"];
break;
default:
targets = ['x86_64-unknown-linux-gnu'];
break;
}
targets.forEach(target => {
child_process.execSync(`npm run build -- --target ${target}`, {stdio: 'inherit'});
});

View File

@ -1,5 +0,0 @@
extern crate napi_build;
fn main() {
napi_build::setup();
}

View File

@ -1,15 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/* auto-generated by NAPI-RS */
export namespace passwords {
/** Fetch the stored password from the keychain. */
export function getPassword(service: string, account: string): Promise<string>
/** Fetch the stored password from the keychain that was stored with Keytar. */
export function getPasswordKeytar(service: string, account: string): Promise<string>
/** Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry. */
export function setPassword(service: string, account: string, password: string): Promise<void>
/** Delete the stored password from the keychain. */
export function deletePassword(service: string, account: string): Promise<void>
}

View File

@ -1,241 +0,0 @@
const { existsSync, readFileSync } = require('fs')
const { join } = require('path')
const { platform, arch } = process
let nativeBinding = null
let localFileExisted = false
let loadError = null
function isMusl() {
// For Node 10
if (!process.report || typeof process.report.getReport !== 'function') {
try {
return readFileSync('/usr/bin/ldd', 'utf8').includes('musl')
} catch (e) {
return true
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header
return !glibcVersionRuntime
}
}
switch (platform) {
case 'android':
switch (arch) {
case 'arm64':
localFileExisted = existsSync(join(__dirname, 'desktop_native.android-arm64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.android-arm64.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-android-arm64')
}
} catch (e) {
loadError = e
}
break
case 'arm':
localFileExisted = existsSync(join(__dirname, 'desktop_native.android-arm-eabi.node'))
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.android-arm-eabi.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-android-arm-eabi')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Android ${arch}`)
}
break
case 'win32':
switch (arch) {
case 'x64':
localFileExisted = existsSync(
join(__dirname, 'desktop_native.win32-x64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.win32-x64-msvc.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-win32-x64-msvc')
}
} catch (e) {
loadError = e
}
break
case 'ia32':
localFileExisted = existsSync(
join(__dirname, 'desktop_native.win32-ia32-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.win32-ia32-msvc.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-win32-ia32-msvc')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'desktop_native.win32-arm64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.win32-arm64-msvc.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-win32-arm64-msvc')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`)
}
break
case 'darwin':
switch (arch) {
case 'x64':
localFileExisted = existsSync(join(__dirname, 'desktop_native.darwin-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.darwin-x64.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-darwin-x64')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'desktop_native.darwin-arm64.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.darwin-arm64.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-darwin-arm64')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`)
}
break
case 'freebsd':
if (arch !== 'x64') {
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
}
localFileExisted = existsSync(join(__dirname, 'desktop_native.freebsd-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.freebsd-x64.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-freebsd-x64')
}
} catch (e) {
loadError = e
}
break
case 'linux':
switch (arch) {
case 'x64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'desktop_native.linux-x64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.linux-x64-musl.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-linux-x64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'desktop_native.linux-x64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.linux-x64-gnu.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-linux-x64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'desktop_native.linux-arm64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.linux-arm64-musl.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-linux-arm64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'desktop_native.linux-arm64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.linux-arm64-gnu.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-linux-arm64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm':
localFileExisted = existsSync(
join(__dirname, 'desktop_native.linux-arm-gnueabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.linux-arm-gnueabihf.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-linux-arm-gnueabihf')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`)
}
break
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
}
if (!nativeBinding) {
if (loadError) {
throw loadError
}
throw new Error(`Failed to load native binding`)
}
const { passwords } = nativeBinding
module.exports.passwords = passwords

View File

@ -1,41 +0,0 @@
{
"name": "@bitwarden/desktop_native",
"version": "0.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@bitwarden/desktop_native",
"version": "0.1.0",
"hasInstallScript": true,
"license": "GPL-3.0",
"devDependencies": {
"@napi-rs/cli": "^2.6.2"
}
},
"node_modules/@napi-rs/cli": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.6.2.tgz",
"integrity": "sha512-EmH+DQDEBUIoqMim0cc+X96ImtcIZLFjgW5WWORpzYnA9Ug7zNPO7jCLMhIQRd/p5AdWaXrT4SVXc/aip09rKQ==",
"dev": true,
"bin": {
"napi": "scripts/index.js"
},
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
}
},
"dependencies": {
"@napi-rs/cli": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.6.2.tgz",
"integrity": "sha512-EmH+DQDEBUIoqMim0cc+X96ImtcIZLFjgW5WWORpzYnA9Ug7zNPO7jCLMhIQRd/p5AdWaXrT4SVXc/aip09rKQ==",
"dev": true
}
}
}

View File

@ -1,32 +0,0 @@
{
"name": "@bitwarden/desktop_native",
"version": "0.1.0",
"description": "",
"main": "index.node",
"scripts": {
"build": "napi build --release --platform",
"build:debug": "napi build --platform",
"build:cross-platform": "node build.js",
"test": "cargo test"
},
"author": "",
"license": "GPL-3.0",
"devDependencies": {
"@napi-rs/cli": "^2.6.2"
},
"napi": {
"name": "desktop_native",
"triples": {
"defaults": true,
"additional": [
"x86_64-unknown-linux-musl",
"aarch64-unknown-linux-gnu",
"i686-pc-windows-msvc",
"armv7-unknown-linux-gnueabihf",
"aarch64-apple-darwin",
"aarch64-unknown-linux-musl",
"aarch64-pc-windows-msvc"
]
}
}
}

View File

@ -1,39 +0,0 @@
#[macro_use]
extern crate napi_derive;
mod password;
#[napi]
pub mod passwords {
/// Fetch the stored password from the keychain.
#[napi]
pub async fn get_password(service: String, account: String) -> napi::Result<String> {
super::password::get_password(&service, &account)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
/// Fetch the stored password from the keychain that was stored with Keytar.
#[napi]
pub async fn get_password_keytar(service: String, account: String) -> napi::Result<String> {
super::password::get_password_keytar(&service, &account)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
/// Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry.
#[napi]
pub async fn set_password(
service: String,
account: String,
password: String,
) -> napi::Result<()> {
super::password::set_password(&service, &account, &password)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
/// Delete the stored password from the keychain.
#[napi]
pub async fn delete_password(service: String, account: String) -> napi::Result<()> {
super::password::delete_password(&service, &account)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
}

View File

@ -1,59 +0,0 @@
use anyhow::Result;
use security_framework::passwords::{
delete_generic_password, get_generic_password, set_generic_password,
};
pub fn get_password(service: &str, account: &str) -> Result<String> {
let result = String::from_utf8(get_generic_password(&service, &account)?)?;
Ok(result)
}
pub fn get_password_keytar(service: &str, account: &str) -> Result<String> {
get_password(service, account)
}
pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
let result = set_generic_password(&service, &account, password.as_bytes())?;
Ok(result)
}
pub fn delete_password(service: &str, account: &str) -> Result<()> {
let result = delete_generic_password(&service, &account)?;
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({}););
set_password("BitwardenTest", "BitwardenTest", "Random").unwrap();
assert_eq!(
"Random",
get_password("BitwardenTest", "BitwardenTest").unwrap()
);
delete_password("BitwardenTest", "BitwardenTest").unwrap();
// Ensure password is deleted
match get_password("BitwardenTest", "BitwardenTest") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!(
"The specified item could not be found in the keychain.",
e.to_string()
),
}
}
#[test]
fn test_error_no_password() {
match get_password("Unknown", "Unknown") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!(
"The specified item could not be found in the keychain.",
e.to_string()
),
}
}
}

View File

@ -1,5 +0,0 @@
#[cfg_attr(target_os = "linux", path = "unix.rs")]
#[cfg_attr(target_os = "windows", path = "windows.rs")]
#[cfg_attr(target_os = "macos", path = "macos.rs")]
mod password;
pub use password::*;

View File

@ -1,91 +0,0 @@
use anyhow::{anyhow, Result};
use libsecret::{password_clear_sync, password_lookup_sync, password_store_sync, Schema};
use std::collections::HashMap;
pub fn get_password(service: &str, account: &str) -> Result<String> {
let res = password_lookup_sync(
Some(&get_schema()),
build_attributes(service, account),
gio::Cancellable::NONE,
)?;
match res {
Some(s) => Ok(String::from(s)),
None => Err(anyhow!("No password found")),
}
}
pub fn get_password_keytar(service: &str, account: &str) -> Result<String> {
get_password(service, account)
}
pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
let result = password_store_sync(
Some(&get_schema()),
build_attributes(service, account),
Some(&libsecret::COLLECTION_DEFAULT),
&format!("{}/{}", service, account),
password,
gio::Cancellable::NONE,
)?;
Ok(result)
}
pub fn delete_password(service: &str, account: &str) -> Result<()> {
let result = password_clear_sync(
Some(&get_schema()),
build_attributes(service, account),
gio::Cancellable::NONE,
)?;
Ok(result)
}
fn get_schema() -> Schema {
let mut attributes = std::collections::HashMap::new();
attributes.insert("service", libsecret::SchemaAttributeType::String);
attributes.insert("account", libsecret::SchemaAttributeType::String);
libsecret::Schema::new(
"org.freedesktop.Secret.Generic",
libsecret::SchemaFlags::NONE,
attributes,
)
}
fn build_attributes<'a>(service: &'a str, account: &'a str) -> HashMap<&'a str, &'a str> {
let mut attributes = HashMap::new();
attributes.insert("service", service);
attributes.insert("account", account);
attributes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({}););
set_password("BitwardenTest", "BitwardenTest", "Random").unwrap();
assert_eq!(
"Random",
get_password("BitwardenTest", "BitwardenTest").unwrap()
);
delete_password("BitwardenTest", "BitwardenTest").unwrap();
// Ensure password is deleted
match get_password("BitwardenTest", "BitwardenTest") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("No password found", e.to_string()),
}
}
#[test]
fn test_error_no_password() {
match get_password("BitwardenTest", "BitwardenTest") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("No password found", e.to_string()),
}
}
}

View File

@ -1,180 +0,0 @@
use anyhow::{anyhow, Result};
use widestring::{U16CString, U16String};
use windows::Win32::{
Foundation::{GetLastError, ERROR_NOT_FOUND, FILETIME, PWSTR, WIN32_ERROR},
Security::Credentials::{
CredDeleteW, CredFree, CredReadW, CredWriteW, CREDENTIALW, CRED_FLAGS,
CRED_PERSIST_ENTERPRISE, CRED_TYPE_GENERIC,
},
};
const CRED_FLAGS_NONE: u32 = 0;
pub fn get_password<'a>(service: &str, account: &str) -> Result<String> {
let target_name = U16CString::from_str(target_name(service, account))?;
let mut credential: *mut CREDENTIALW = std::ptr::null_mut();
let credential_ptr = &mut credential;
let result = unsafe {
CredReadW(
PWSTR(target_name.as_ptr()),
CRED_TYPE_GENERIC.0,
CRED_FLAGS_NONE,
credential_ptr,
)
};
scopeguard::defer!({
unsafe { CredFree(credential as *mut _) };
});
if !result.as_bool() {
return Err(anyhow!(convert_error(unsafe { GetLastError() })));
}
let password = unsafe {
U16String::from_ptr(
(*credential).CredentialBlob as *const u16,
(*credential).CredentialBlobSize as usize / 2,
)
.to_string_lossy()
};
Ok(String::from(password))
}
// Remove this after sufficient releases
pub fn get_password_keytar<'a>(service: &str, account: &str) -> Result<String> {
let target_name = U16CString::from_str(target_name(service, account))?;
let mut credential: *mut CREDENTIALW = std::ptr::null_mut();
let credential_ptr = &mut credential;
let result = unsafe {
CredReadW(
PWSTR(target_name.as_ptr()),
CRED_TYPE_GENERIC.0,
CRED_FLAGS_NONE,
credential_ptr,
)
};
scopeguard::defer!({
unsafe { CredFree(credential as *mut _) };
});
if !result.as_bool() {
return Err(anyhow!(unsafe { GetLastError() }.0.to_string()));
}
let password = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
(*credential).CredentialBlob,
(*credential).CredentialBlobSize as usize,
))
};
Ok(String::from(password))
}
pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
let target_name = U16CString::from_str(target_name(service, account))?;
let user_name = U16CString::from_str(account)?;
let last_written = FILETIME {
dwLowDateTime: 0,
dwHighDateTime: 0,
};
let credential = U16CString::from_str(password)?;
let credential_len = password.len() as u32 * 2;
let credential = CREDENTIALW {
Flags: CRED_FLAGS(CRED_FLAGS_NONE),
Type: CRED_TYPE_GENERIC,
TargetName: PWSTR(target_name.as_ptr()),
Comment: PWSTR::default(),
LastWritten: last_written,
CredentialBlobSize: credential_len,
CredentialBlob: credential.as_ptr() as *mut u8,
Persist: CRED_PERSIST_ENTERPRISE,
AttributeCount: 0,
Attributes: std::ptr::null_mut(),
TargetAlias: PWSTR::default(),
UserName: PWSTR(user_name.as_ptr()),
};
let result = unsafe { CredWriteW(&credential, 0) };
if !result.as_bool() {
return Err(anyhow!(unsafe { GetLastError() }.0.to_string()));
}
Ok(())
}
pub fn delete_password(service: &str, account: &str) -> Result<()> {
let target_name = U16CString::from_str(target_name(service, account))?;
unsafe {
CredDeleteW(
PWSTR(target_name.as_ptr()),
CRED_TYPE_GENERIC.0,
CRED_FLAGS_NONE,
)
.ok()?
};
Ok(())
}
fn target_name(service: &str, account: &str) -> String {
format!("{}/{}", service, account)
}
// Convert the internal WIN32 errors to descriptive messages
fn convert_error(code: WIN32_ERROR) -> String {
match code {
ERROR_NOT_FOUND => String::from("Password not found."),
_ => code.0.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({}););
set_password("BitwardenTest", "BitwardenTest", "Random").unwrap();
assert_eq!(
"Random",
get_password("BitwardenTest", "BitwardenTest").unwrap()
);
delete_password("BitwardenTest", "BitwardenTest").unwrap();
// Ensure password is deleted
match get_password("BitwardenTest", "BitwardenTest") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("Password not found.", e.to_string()),
}
}
#[test]
fn test_get_password_keytar() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({}););
keytar::set_password("BitwardenTest", "BitwardenTest", "HelloFromKeytar").unwrap();
assert_eq!(
"HelloFromKeytar",
get_password_keytar("BitwardenTest", "BitwardenTest").unwrap()
);
}
#[test]
fn test_error_no_password() {
match get_password("BitwardenTest", "BitwardenTest") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("Password not found.", e.to_string()),
}
}
}

View File

@ -1,121 +0,0 @@
{
"extraMetadata": { "name": "bitwarden" },
"productName": "Bitwarden",
"appId": "com.bitwarden.desktop",
"buildDependenciesFromSource": true,
"copyright": "Copyright © 2015-2022 Bitwarden Inc.",
"directories": { "buildResources": "resources", "output": "dist", "app": "build" },
"afterSign": "scripts/after-sign.js",
"asarUnpack": ["**/*.node"],
"files": ["**/*", "!**/node_modules/@bitwarden/desktop-native/**/*"],
"publish": {
"provider": "generic",
"url": "https://artifacts.bitwarden.com/desktop"
},
"mac": {
"electronUpdaterCompatibility": ">=0.0.1",
"category": "public.app-category.productivity",
"darkModeSupport": true,
"gatekeeperAssess": false,
"hardenedRuntime": true,
"entitlements": "resources/entitlements.mac.plist",
"entitlementsInherit": "resources/entitlements.mac.plist",
"extendInfo": {
"ITSAppUsesNonExemptEncryption": false,
"CFBundleLocalizations": [
"en",
"cs",
"da",
"de",
"es",
"et",
"fi",
"fr",
"hr",
"hu",
"id",
"it",
"ja",
"nb",
"nl",
"pl",
"pt-BR",
"pt-PT",
"ro",
"ru",
"sk",
"sv",
"tr",
"uk",
"vi",
"zh-Hans",
"zh-Hant"
],
"CFBundleDevelopmentRegion": "en"
},
"target": ["dmg", "zip"]
},
"win": {
"electronUpdaterCompatibility": ">=0.0.1",
"target": ["portable", "nsis-web", "appx"],
"sign": "./sign.js",
"extraResources": [
{ "from": "node_modules/regedit/vbs", "to": "regedit/vbs", "filter": ["**/*"] },
{ "from": "resources/native-messaging.bat", "to": "native-messaging.bat" }
]
},
"linux": {
"category": "Utility",
"synopsis": "A secure and free password manager for all of your devices.",
"target": ["deb", "freebsd", "rpm", "AppImage", "snap"],
"desktop": { "Name": "Bitwarden", "Type": "Application", "GenericName": "Password Manager" }
},
"dmg": {
"icon": "dmg.icns",
"contents": [
{ "x": 150, "y": 185, "type": "file" },
{ "x": 390, "y": 180, "type": "link", "path": "/Applications" }
],
"window": { "width": 540, "height": 380 }
},
"mas": {
"entitlements": "resources/entitlements.mas.plist",
"entitlementsInherit": "resources/entitlements.mas.inherit.plist",
"hardenedRuntime": false,
"extendInfo": { "LSMinimumSystemVersion": "10.14.0" }
},
"nsisWeb": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": false,
"artifactName": "${productName}-Installer-${version}.${ext}",
"uninstallDisplayName": "${productName}",
"deleteAppDataOnUninstall": true
},
"portable": { "artifactName": "${productName}-Portable-${version}.${ext}" },
"appx": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"backgroundColor": "#175DDC",
"applicationId": "bitwardendesktop",
"identityName": "8bitSolutionsLLC.bitwardendesktop",
"publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418",
"publisherDisplayName": "8bit Solutions LLC",
"languages": ["en-US"]
},
"deb": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"depends": ["libnotify4", "libxtst6", "libnss3", "libsecret-1-0", "libxss1"]
},
"appImage": {
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"rpm": { "artifactName": "${productName}-${version}-${arch}.${ext}" },
"freebsd": { "artifactName": "${productName}-${version}-${arch}.${ext}" },
"snap": {
"autoStart": true,
"confinement": "strict",
"plugs": ["default", "password-manager-service"],
"stagePackages": ["default"]
},
"protocols": [{ "name": "Bitwarden", "schemes": ["bitwarden"] }]
}

2
jslib

@ -1 +1 @@
Subproject commit 80c834b52a8b00f88250a47a9bbd40c269fc2cba
Subproject commit 212bd70986ae1cf28f82823cb3f45fae4a8d0ce0

4278
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,6 @@
"build:renderer:dev": "cross-env NODE_ENV=development webpack --config webpack.renderer.js",
"build:renderer:watch": "cross-env NODE_ENV=development webpack --config webpack.renderer.js --watch",
"electron": "npm run build:main:dev && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 ./build --watch\" \"npm run build:renderer:watch\"",
"electron:ignore": "npm run build:main:dev && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 --ignore-certificate-errors ./build --watch\" \"npm run build:renderer:watch\"",
"clean:dist": "rimraf ./dist/*",
"clean:l10n": "git push origin --delete l10n_master",
"pack:dir": "npm run clean:dist && electron-builder --dir -p never",
@ -58,11 +57,201 @@
"publish:mac": "npm run build && npm run clean:dist && electron-builder --mac -p always",
"publish:mac:mas": "npm run dist:mac:mas && npm run upload:mas",
"publish:win": "npm run build && npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
"publish:win:dev": "npm run build && npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p always",
"upload:mas": "xcrun altool --upload-app --type osx --file \"$(find ./dist/mas-universal/Bitwarden*.pkg)\" --username $APPLE_ID_USERNAME --password $APPLE_ID_PASSWORD",
"prettier": "prettier --write .",
"prepare": "husky install"
},
"build": {
"extraMetadata": {
"name": "bitwarden"
},
"productName": "Bitwarden",
"appId": "com.bitwarden.desktop",
"buildDependenciesFromSource": true,
"copyright": "Copyright © 2015-2022 Bitwarden Inc.",
"directories": {
"buildResources": "resources",
"output": "dist",
"app": "build"
},
"afterSign": "scripts/after-sign.js",
"asarUnpack": [
"**/*.node"
],
"mac": {
"electronUpdaterCompatibility": ">=0.0.1",
"category": "public.app-category.productivity",
"minimumSystemVersion": "10.14",
"darkModeSupport": true,
"gatekeeperAssess": false,
"hardenedRuntime": true,
"entitlements": "resources/entitlements.mac.plist",
"entitlementsInherit": "resources/entitlements.mac.plist",
"extendInfo": {
"ITSAppUsesNonExemptEncryption": false,
"CFBundleLocalizations": [
"en",
"cs",
"da",
"de",
"es",
"et",
"fi",
"fr",
"hr",
"hu",
"id",
"it",
"ja",
"nb",
"nl",
"pl",
"pt-BR",
"pt-PT",
"ro",
"ru",
"sk",
"sv",
"tr",
"uk",
"vi",
"zh-Hans",
"zh-Hant"
],
"CFBundleDevelopmentRegion": "en"
},
"target": [
"dmg",
"zip"
]
},
"win": {
"electronUpdaterCompatibility": ">=0.0.1",
"target": [
"portable",
"nsis-web",
"appx"
],
"sign": "./sign.js",
"extraResources": [
{
"from": "node_modules/regedit/vbs",
"to": "regedit/vbs",
"filter": [
"**/*"
]
},
{
"from": "resources/native-messaging.bat",
"to": "native-messaging.bat"
}
]
},
"linux": {
"category": "Utility",
"synopsis": "A secure and free password manager for all of your devices.",
"target": [
"deb",
"freebsd",
"rpm",
"AppImage",
"snap"
],
"desktop": {
"Name": "Bitwarden",
"Type": "Application",
"GenericName": "Password Manager"
}
},
"dmg": {
"icon": "dmg.icns",
"contents": [
{
"x": 150,
"y": 185,
"type": "file"
},
{
"x": 390,
"y": 180,
"type": "link",
"path": "/Applications"
}
],
"window": {
"width": 540,
"height": 380
}
},
"mas": {
"entitlements": "resources/entitlements.mas.plist",
"entitlementsInherit": "resources/entitlements.mas.inherit.plist",
"hardenedRuntime": false
},
"nsisWeb": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": false,
"artifactName": "${productName}-Installer-${version}.${ext}",
"uninstallDisplayName": "${productName}",
"deleteAppDataOnUninstall": true
},
"portable": {
"artifactName": "${productName}-Portable-${version}.${ext}"
},
"appx": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"backgroundColor": "#175DDC",
"applicationId": "bitwardendesktop",
"identityName": "8bitSolutionsLLC.bitwardendesktop",
"publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418",
"publisherDisplayName": "8bit Solutions LLC",
"languages": [
"en-US"
]
},
"deb": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"depends": [
"libnotify4",
"libxtst6",
"libnss3",
"libsecret-1-0",
"libxss1"
]
},
"appImage": {
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"rpm": {
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"freebsd": {
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"snap": {
"autoStart": true,
"confinement": "strict",
"plugs": [
"default",
"password-manager-service"
],
"stagePackages": [
"default"
],
"publish": [
"github"
]
},
"protocols": [
{
"name": "Bitwarden",
"schemes": [
"bitwarden"
]
}
]
},
"devDependencies": {
"@angular/compiler-cli": "^12.2.13",
"@ngtools/webpack": "^12.2.13",
@ -114,16 +303,12 @@
"@bitwarden/jslib-angular": "file:jslib/angular",
"@bitwarden/jslib-common": "file:jslib/common",
"@bitwarden/jslib-electron": "file:jslib/electron",
"@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4",
"forcefocus": "^1.1.0",
"keytar": "^7.9.0",
"ngx-toastr": "14.1.4",
"node-ipc": "^9.1.4",
"nord": "^0.2.1",
"regedit": "^3.0.3",
"rxjs": "^7.4.0",
"sweetalert2": "^10.16.6",
"zone.js": "0.11.4"
"sweetalert2": "^10.16.6"
},
"engines": {
"node": "~16",

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 510 B

BIN
resources/icons/48x48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1 +0,0 @@
data

View File

@ -1,19 +0,0 @@
version: "3"
services:
minio:
image: minio/minio
command: server /data --console-address ":9001"
ports:
- "9000:9000"
- "9001:9001"
# environment:
# MINIO_ROOT_USER: minioadmin
# MINIO_ROOT_PASSWORD: minioadmin
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
volumes:
- ./data:/data

View File

@ -25,7 +25,11 @@
</div>
<div class="box">
<div class="box-header">
<button type="button" (click)="toggleCustom()" [attr.aria-expanded]="showCustom">
<button
type="button"
(click)="toggleCustom()"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
>
<i class="bwi bwi-plus-square" [hidden]="showCustom" aria-hidden="true"></i>
<i class="bwi bwi-minus-square" [hidden]="!showCustom" aria-hidden="true"></i>
{{ "customEnvironment" | i18n }}
@ -83,7 +87,7 @@
</div>
</div>
<div class="modal-footer">
<button type="submit" class="primary" appA11yTitle="{{ 'save' | i18n }}">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{ 'save' | i18n }}">
<i class="bwi bwi-save-changes bwi-lg bwi-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>

View File

@ -21,11 +21,11 @@
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" routerLink="/login" class="btn block">{{ "cancel" | i18n }}</button>
<a routerLink="/login" class="btn block">{{ "cancel" | i18n }}</a>
</div>
</div>
</form>

View File

@ -30,12 +30,13 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword()"
>
<i
@ -43,7 +44,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</a>
</div>
</div>
</div>
@ -57,16 +58,17 @@
type="button"
class="btn block"
[ngClass]="{ 'primary font-weight-bold': hideInput }"
appBlurClick
(click)="unlockBiometric()"
>
{{ biometricText | i18n }}
</button>
</div>
<div class="buttons-row">
<button type="submit" class="btn primary block" *ngIf="!hideInput">
<button type="submit" class="btn primary block" appBlurClick *ngIf="!hideInput">
<i class="bwi bwi-unlock" aria-hidden="true"></i> <b>{{ "unlock" | i18n }}</b>
</button>
<button type="button" class="btn block" (click)="logOut()">
<button type="button" class="btn block" appBlurClick (click)="logOut()">
{{ "logOut" | i18n }}
</button>
</div>

View File

@ -1,7 +1,7 @@
<div id="login-page">
<div class="login-header">
<button
type="button"
<a
href="#"
appStopClick
(click)="settings()"
class="environment-urls-settings-icon"
@ -9,7 +9,7 @@
>
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
{{ "settings" | i18n }}
</button>
</a>
</div>
<form
id="login-page"
@ -48,12 +48,13 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword()"
>
<i
@ -61,7 +62,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</a>
</div>
</div>
</div>
@ -75,28 +76,24 @@
</div>
<div class="buttons with-rows">
<div class="buttons-row">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading"
><i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }}</b
>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" routerLink="/register" class="btn block">
<a routerLink="/register" class="btn block">
<i class="bwi bwi-pencil-square" aria-hidden="true"></i> {{ "createAccount" | i18n }}
</button>
</a>
</div>
<div class="buttons-row">
<button
type="button"
(click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')"
class="btn block"
>
<a (click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')" class="btn block">
<i class="bwi bwi-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</button>
</a>
</div>
</div>
<div class="sub-options">
<button type="button" routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</button>
<a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
</div>
</div>
</form>

View File

@ -48,13 +48,14 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="primary" (click)="manage()" *ngIf="isPremium">
<button type="button" class="primary" appBlurClick (click)="manage()" *ngIf="isPremium">
<b>{{ "premiumManage" | i18n }}</b>
</button>
<button
#purchaseBtn
type="button"
class="primary"
appBlurClick
(click)="purchase()"
*ngIf="!isPremium"
[disabled]="purchaseBtn.loading"
@ -66,6 +67,7 @@
<button
#refreshBtn
type="button"
appBlurClick
(click)="refresh()"
[disabled]="refreshBtn.loading"
appA11yTitle="{{ 'premiumRefresh' | i18n }}"

View File

@ -40,12 +40,13 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(false)"
>
<i
@ -53,7 +54,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</a>
</div>
</div>
<div class="progress">
@ -89,12 +90,13 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(true)"
>
<i
@ -102,7 +104,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
@ -138,11 +140,11 @@
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" routerLink="/login" class="btn block">{{ "cancel" | i18n }}</button>
<a routerLink="/login" class="btn block">{{ "cancel" | i18n }}</a>
</div>
</div>
</form>

View File

@ -7,6 +7,7 @@
type="submit"
class="btn primary block"
[disabled]="actionPromise"
appBlurClick
(click)="convert()"
>
<b [hidden]="continuing">{{ "removeMasterPassword" | i18n }}</b>
@ -16,6 +17,7 @@
type="button"
class="btn secondary block"
[disabled]="actionPromise"
appBlurClick
(click)="leave()"
>
<b [hidden]="leaving">{{ "leaveOrganization" | i18n }}</b>

View File

@ -56,12 +56,13 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(false)"
>
<i
@ -69,7 +70,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</a>
</div>
</div>
<div class="progress">
@ -107,12 +108,13 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(true)"
>
<i
@ -120,7 +122,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</a>
</div>
</div>
</div>

View File

@ -3,9 +3,7 @@
<div class="modal-content">
<div class="modal-body form">
<div class="box">
<div class="box-header">
{{ "settingsTitle" | i18n: currentUserEmail }}
</div>
<label class="settingsTitle">{{ "settingsTitle" | i18n: currentUserEmail }} </label>
<div class="box-content box-content-padded">
<h2>
<button

View File

@ -7,8 +7,8 @@
{{ "twoStepOptions" | i18n }}
</div>
<div class="box-content">
<button
type="button"
<a
href="#"
appStopClick
*ngFor="let p of providers"
class="box-content-row"
@ -17,11 +17,11 @@
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right" />
<span class="text">{{ p.name }}</span>
<span class="detail">{{ p.description }}</span>
</button>
<button type="button" appStopClick class="box-content-row" (click)="recover()">
</a>
<a href="#" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{ "recoveryCodeTitle" | i18n }}</span>
<span class="detail">{{ "recoveryCodeDesc" | i18n }}</span>
</button>
</a>
</div>
</div>
</div>

View File

@ -112,6 +112,7 @@
type="submit"
class="btn primary block"
[disabled]="form.loading"
appBlurClick
*ngIf="
selectedProviderType != null &&
selectedProviderType !== providerType.Duo &&
@ -123,21 +124,22 @@
>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" routerLink="/login" class="btn block">{{ "cancel" | i18n }}</button>
<a routerLink="/login" class="btn block">{{ "cancel" | i18n }}</a>
</div>
<div class="sub-options">
<button type="button" appStopClick (click)="anotherMethod()">
{{ "useAnotherTwoStepMethod" | i18n }}
</button>
<button
type="button"
<a href="#" appStopClick (click)="anotherMethod()" role="button">{{
"useAnotherTwoStepMethod" | i18n
}}</a>
<a
href="#"
appStopClick
(click)="sendEmail(true)"
[appApiAction]="emailPromise"
role="button"
*ngIf="selectedProviderType === providerType.Email"
>
{{ "sendVerificationCodeEmailAgain" | i18n }}
</button>
</a>
</div>
</div>
</form>

View File

@ -4,7 +4,6 @@ import { ActivatedRoute, Router } from "@angular/router";
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
import { ModalService } from "jslib-angular/services/modal.service";
import { ApiService } from "jslib-common/abstractions/api.service";
import { AppIdService } from "jslib-common/abstractions/appId.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@ -39,8 +38,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
stateService: StateService,
route: ActivatedRoute,
logService: LogService,
twoFactorService: TwoFactorService,
appIdService: AppIdService
twoFactorService: TwoFactorService
) {
super(
authService,
@ -53,8 +51,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
stateService,
route,
logService,
twoFactorService,
appIdService
twoFactorService
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);

View File

@ -36,12 +36,13 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(false)"
>
<i
@ -49,7 +50,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</a>
</div>
</div>
<div class="progress">
@ -82,12 +83,13 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(true)"
>
<i
@ -95,7 +97,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</a>
</div>
</div>
</div>
@ -112,11 +114,11 @@
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" (click)="logOut()" class="btn block">{{ "logOut" | i18n }}</button>
<a (click)="logOut()" class="btn block">{{ "logOut" | i18n }}</a>
</div>
</div>
</form>

View File

@ -35,17 +35,16 @@ import { SyncService } from "jslib-common/abstractions/sync.service";
import { SystemService } from "jslib-common/abstractions/system.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { AuthenticationStatus } from "jslib-common/enums/authenticationStatus";
import { CipherType } from "jslib-common/enums/cipherType";
import { MenuUpdateRequest } from "../main/menu/menu.updater";
import { MenuUpdateRequest } from "src/main/menu.updater";
import { PremiumComponent } from "./accounts/premium.component";
import { SettingsComponent } from "./accounts/settings.component";
import { ExportComponent } from "./vault/export.component";
import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
import { GeneratorComponent } from "./vault/generator.component";
import { PasswordGeneratorHistoryComponent } from "./vault/password-generator-history.component";
import { PasswordGeneratorComponent } from "./vault/password-generator.component";
const BroadcasterSubscriptionId = "AppComponent";
const IdleTimeout = 60000 * 10; // 10 minutes
@ -66,7 +65,7 @@ const systemTimeoutOptions = {
<ng-template #passwordHistory></ng-template>
<ng-template #appFolderAddEdit></ng-template>
<ng-template #exportVault></ng-template>
<ng-template #appGenerator></ng-template>
<ng-template #appPasswordGenerator></ng-template>
<app-header></app-header>
<div id="container">
<div class="loading" *ngIf="loading">
@ -85,8 +84,8 @@ export class AppComponent implements OnInit {
exportVaultModalRef: ViewContainerRef;
@ViewChild("appFolderAddEdit", { read: ViewContainerRef, static: true })
folderAddEditModalRef: ViewContainerRef;
@ViewChild("appGenerator", { read: ViewContainerRef, static: true })
generatorModalRef: ViewContainerRef;
@ViewChild("appPasswordGenerator", { read: ViewContainerRef, static: true })
passwordGeneratorModalRef: ViewContainerRef;
loading = false;
@ -318,10 +317,10 @@ export class AppComponent implements OnInit {
case "newFolder":
await this.addFolder();
break;
case "openGenerator":
// openGenerator has extended functionality if called in the vault
case "openPasswordGenerator":
// openPasswordGenerator has extended functionality if called in the vault
if (!this.router.url.includes("vault")) {
await this.openGenerator();
await this.openPasswordGenerator();
}
break;
case "convertAccountToKeyConnector":
@ -331,9 +330,7 @@ export class AppComponent implements OnInit {
if (message.userId != null) {
await this.stateService.setActiveUser(message.userId);
}
const locked =
(await this.authService.getAuthStatus(message.userId)) ===
AuthenticationStatus.Locked;
const locked = await this.vaultTimeoutService.isLocked(message.userId);
if (locked) {
this.messagingService.send("locked", { userId: message.userId });
} else {
@ -405,15 +402,15 @@ export class AppComponent implements OnInit {
});
}
async openGenerator() {
async openPasswordGenerator() {
if (this.modal != null) {
this.modal.close();
}
[this.modal] = await this.modalService.openViewRef(
GeneratorComponent,
this.generatorModalRef,
(comp) => (comp.comingFromAddEdit = false)
PasswordGeneratorComponent,
this.folderAddEditModalRef,
(comp) => (comp.showSelect = false)
);
this.modal.onClosed.subscribe(() => {
@ -439,8 +436,7 @@ export class AppComponent implements OnInit {
isAuthenticated: await this.stateService.getIsAuthenticated({
userId: userId,
}),
isLocked:
(await this.authService.getAuthStatus(userId)) === AuthenticationStatus.Locked,
isLocked: await this.vaultTimeoutService.isLocked(userId),
email: stateAccounts[i].profile.email,
userId: stateAccounts[i].profile.userId,
};
@ -595,7 +591,7 @@ export class AppComponent implements OnInit {
const keys = Object.keys(accounts);
if (keys.length > 0) {
for (const userId of keys) {
if ((await this.authService.getAuthStatus(userId)) === AuthenticationStatus.Unlocked) {
if (!(await this.vaultTimeoutService.isLocked(userId))) {
return;
}
}

View File

@ -63,7 +63,26 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { JslibModule } from "jslib-angular/jslib.module";
import { AvatarComponent } from "jslib-angular/components/avatar.component";
import { CalloutComponent } from "jslib-angular/components/callout.component";
import { ExportScopeCalloutComponent } from "jslib-angular/components/export-scope-callout.component";
import { IconComponent } from "jslib-angular/components/icon.component";
import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import { A11yTitleDirective } from "jslib-angular/directives/a11y-title.directive";
import { ApiActionDirective } from "jslib-angular/directives/api-action.directive";
import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive";
import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive";
import { BoxRowDirective } from "jslib-angular/directives/box-row.directive";
import { CipherListVirtualScroll } from "jslib-angular/directives/cipherListVirtualScroll.directive";
import { FallbackSrcDirective } from "jslib-angular/directives/fallback-src.directive";
import { InputVerbatimDirective } from "jslib-angular/directives/input-verbatim.directive";
import { SelectCopyDirective } from "jslib-angular/directives/select-copy.directive";
import { StopClickDirective } from "jslib-angular/directives/stop-click.directive";
import { StopPropDirective } from "jslib-angular/directives/stop-prop.directive";
import { TrueFalseValueDirective } from "jslib-angular/directives/true-false-value.directive";
import { ColorPasswordPipe } from "jslib-angular/pipes/color-password.pipe";
import { I18nPipe } from "jslib-angular/pipes/i18n.pipe";
import { SearchCiphersPipe } from "jslib-angular/pipes/search-ciphers.pipe";
import { EnvironmentComponent } from "./accounts/environment.component";
import { HintComponent } from "./accounts/hint.component";
@ -83,7 +102,7 @@ import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
import { PasswordRepromptComponent } from "./components/password-reprompt.component";
import { SetPinComponent } from "./components/set-pin.component";
import { UserVerificationComponent } from "./components/user-verification.component";
import { VerifyMasterPasswordComponent } from "./components/verify-master-password.component";
import { AccountSwitcherComponent } from "./layout/account-switcher.component";
import { HeaderComponent } from "./layout/header.component";
import { NavComponent } from "./layout/nav.component";
@ -91,7 +110,7 @@ import { SearchComponent } from "./layout/search/search.component";
import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
import { SendComponent } from "./send/send.component";
import { ServicesModule } from "./services/services.module";
import { ServicesModule } from "./services.module";
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component";
@ -99,9 +118,9 @@ import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from "./vault/collections.component";
import { ExportComponent } from "./vault/export.component";
import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
import { GeneratorComponent } from "./vault/generator.component";
import { GroupingsComponent } from "./vault/groupings.component";
import { PasswordGeneratorHistoryComponent } from "./vault/password-generator-history.component";
import { PasswordGeneratorComponent } from "./vault/password-generator.component";
import { PasswordHistoryComponent } from "./vault/password-history.component";
import { ShareComponent } from "./vault/share.component";
import { VaultComponent } from "./vault/vault.component";
@ -164,43 +183,59 @@ registerLocaleData(localeZhTw, "zh-TW");
@NgModule({
imports: [
A11yModule,
AppRoutingModule,
BrowserAnimationsModule,
BrowserModule,
DragDropModule,
FormsModule,
JslibModule,
OverlayModule,
ReactiveFormsModule,
ScrollingModule,
ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
ScrollingModule,
A11yModule,
OverlayModule,
],
declarations: [
AccountSwitcherComponent,
A11yTitleDirective,
AddEditComponent,
AddEditCustomFieldsComponent,
ApiActionDirective,
AppComponent,
AttachmentsComponent,
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
CalloutComponent,
CipherListVirtualScroll,
CiphersComponent,
CollectionsComponent,
ColorPasswordPipe,
EnvironmentComponent,
ExportComponent,
ExportScopeCalloutComponent,
FallbackSrcDirective,
FolderAddEditComponent,
GroupingsComponent,
HeaderComponent,
HintComponent,
I18nPipe,
IconComponent,
InputVerbatimDirective,
LockComponent,
LoginComponent,
NavComponent,
GeneratorComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordHistoryComponent,
PasswordRepromptComponent,
PremiumComponent,
RegisterComponent,
RemovePasswordComponent,
SearchComponent,
SearchCiphersPipe,
SelectCopyDirective,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
@ -209,14 +244,21 @@ registerLocaleData(localeZhTw, "zh-TW");
SettingsComponent,
ShareComponent,
SsoComponent,
StopClickDirective,
StopPropDirective,
TrueFalseValueDirective,
TwoFactorComponent,
TwoFactorOptionsComponent,
UpdateTempPasswordComponent,
UserVerificationComponent,
VaultComponent,
VaultTimeoutInputComponent,
VerifyMasterPasswordComponent,
ViewComponent,
ViewCustomFieldsComponent,
HeaderComponent,
AccountSwitcherComponent,
AvatarComponent,
SearchComponent,
],
providers: [DatePipe],
bootstrap: [AppComponent],

View File

@ -19,12 +19,13 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword()"
>
<i
@ -32,7 +33,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</a>
</div>
</div>
</div>
@ -42,7 +43,7 @@
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">

View File

@ -22,12 +22,12 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPin"
(click)="toggleVisibility()"
>
<i
@ -35,7 +35,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPin, 'bwi-eye-slash': showPin }"
></i>
</button>
</a>
</div>
</div>
</div>

View File

@ -2,16 +2,16 @@ import { animate, style, transition, trigger } from "@angular/animations";
import { Component } from "@angular/core";
import { NG_VALUE_ACCESSOR } from "@angular/forms";
import { UserVerificationComponent as BaseComponent } from "jslib-angular/components/user-verification.component";
import { VerifyMasterPasswordComponent as BaseComponent } from "jslib-angular/components/verify-master-password.component";
@Component({
selector: "app-user-verification",
templateUrl: "user-verification.component.html",
selector: "app-verify-master-password",
templateUrl: "verify-master-password.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: UserVerificationComponent,
useExisting: VerifyMasterPasswordComponent,
},
],
animations: [
@ -20,4 +20,4 @@ import { UserVerificationComponent as BaseComponent } from "jslib-angular/compon
]),
],
})
export class UserVerificationComponent extends BaseComponent {}
export class VerifyMasterPasswordComponent extends BaseComponent {}

View File

@ -53,6 +53,7 @@
<button
*ngFor="let a of accounts | keyvalue"
class="account"
[ngClass]="{ active: a.value.profile.authenticationStatus == 'active' }"
(click)="switch(a.key)"
appA11yTitle="{{ 'loggedInAsOn' | i18n: a.value.profile.email:a.value.serverUrl }}"
attr.aria-label="{{ 'switchAccount' | i18n }}"
@ -71,17 +72,17 @@
<span class="server" aria-hidden="true" *ngIf="a.value.serverUrl != 'bitwarden.com'">{{
a.value.serverUrl
}}</span>
<span class="status" aria-hidden="true">{{
(a.value.profile.authenticationStatus === authStatus.Unlocked ? "unlocked" : "locked")
| i18n
}}</span>
<span class="status" aria-hidden="true">{{ a.value.profile.authenticationStatus }}</span>
</div>
<i
class="bwi bwi-2x text-muted"
[ngClass]="
a.value.profile.authenticationStatus == authStatus.Unlocked ? 'bwi-unlock' : 'bwi-lock'
"
class="bwi bwi-unlock bwi-2x text-muted"
aria-hidden="true"
*ngIf="a.value.profile.authenticationStatus == 'unlocked'"
></i>
<i
class="bwi bwi-lock bwi-2x text-muted"
aria-hidden="true"
*ngIf="a.value.profile.authenticationStatus == 'locked'"
></i>
</button>
</div>

View File

@ -2,9 +2,9 @@ import { animate, state, style, transition, trigger } from "@angular/animations"
import { ConnectedPosition } from "@angular/cdk/overlay";
import { Component, OnInit } from "@angular/core";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { AuthenticationStatus } from "jslib-common/enums/authenticationStatus";
import { Utils } from "jslib-common/misc/utils";
import { Account } from "jslib-common/models/domain/account";
@ -53,7 +53,6 @@ export class AccountSwitcherComponent implements OnInit {
accounts: { [userId: string]: SwitcherAccount } = {};
activeAccountEmail: string;
serverUrl: string;
authStatus = AuthenticationStatus;
overlayPostition: ConnectedPosition[] = [
{
originX: "end",
@ -79,16 +78,22 @@ export class AccountSwitcherComponent implements OnInit {
constructor(
private stateService: StateService,
private authService: AuthService,
private vaultTimeoutService: VaultTimeoutService,
private messagingService: MessagingService
) {}
async ngOnInit(): Promise<void> {
this.stateService.accounts.subscribe(async (accounts: { [userId: string]: Account }) => {
this.stateService.accounts.subscribe(async (accounts) => {
for (const userId in accounts) {
accounts[userId].profile.authenticationStatus = await this.authService.getAuthStatus(
if (userId === (await this.stateService.getUserId())) {
accounts[userId].profile.authenticationStatus = AuthenticationStatus.Active;
} else {
accounts[userId].profile.authenticationStatus = (await this.vaultTimeoutService.isLocked(
userId
);
))
? AuthenticationStatus.Locked
: AuthenticationStatus.Unlocked;
}
}
this.accounts = await this.createSwitcherAccounts(accounts);

View File

@ -1,13 +1,5 @@
<ng-container *ngFor="let item of items">
<button
type="button"
[routerLink]="item.link"
class="btn primary"
routerLinkActive="active"
[title]="item.label"
#rla="routerLinkActive"
[attr.aria-pressed]="rla.isActive"
>
<a [routerLink]="item.link" class="btn primary" routerLinkActive="active" [title]="item.label">
<i class="bwi" [ngClass]="item.icon"></i>{{ item.label }}
</button>
</a>
</ng-container>

View File

@ -93,20 +93,21 @@
</div>
<div class="box">
<div class="box-header">
<button
type="button"
class="toggle"
appStopClick
(click)="toggleOptions()"
[attr.aria-expanded]="showOptions"
>
{{ "options" | i18n }}
<a
class="toggle"
href="#"
appStopClick
appBlurClick
role="button"
(click)="toggleOptions()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-angle-down': !showOptions, 'bwi-chevron-up': showOptions }"
></i>
</button>
</a>
</div>
</div>
<div [hidden]="!showOptions">
@ -156,12 +157,13 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePasswordVisible()"
[disabled]="disableSend"
>
@ -170,7 +172,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</a>
</div>
</div>
</div>
@ -251,6 +253,7 @@
</div>
<div class="footer">
<button
appBlurClick
type="submit"
class="primary btn-submit"
appA11yTitle="{{ 'save' | i18n }}"
@ -260,11 +263,12 @@
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span><i class="bwi bwi-save-changes bwi-lg bwi-fw" aria-hidden="true"></i></span>
</button>
<button type="button" (click)="cancel()" [disabled]="form.loading">
<button appBlurClick type="button" (click)="cancel()" [disabled]="form.loading">
{{ "cancel" | i18n }}
</button>
<div class="right">
<button
appBlurClick
type="button"
(click)="copyLinkToClipboard(link)"
appA11yTitle="{{ 'copySendLinkToClipboard' | i18n }}"
@ -274,6 +278,7 @@
</button>
<button
#deleteBtn
appBlurClick
type="button"
(click)="delete()"
class="danger"

View File

@ -5,41 +5,26 @@
<h2 class="sr-only">{{ "filters" | i18n }}</h2>
<ul>
<li [ngClass]="{ active: selectedAll }">
<button
type="button"
appStopClick
(click)="selectAll()"
[attr.aria-pressed]="selectedAll"
>
<a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="bwi bwi-fw bwi-filter" aria-hidden="true"></i>&nbsp;{{ "allSends" | i18n }}
</button>
</a>
</li>
</ul>
<h2>{{ "types" | i18n }}</h2>
<ul>
<li [ngClass]="{ active: selectedType === sendType.Text }">
<button
type="button"
appStopClick
(click)="selectType(sendType.Text)"
[attr.aria-pressed]="selectedType === sendType.Text"
>
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.Text)">
<i class="bwi bwi-fw bwi-file-text" aria-hidden="true"></i>&nbsp;{{
"sendTypeText" | i18n
}}
</button>
</a>
</li>
<li [ngClass]="{ active: selectedType === sendType.File }">
<button
type="button"
appStopClick
(click)="selectType(sendType.File)"
[attr.aria-pressed]="selectedType === sendType.File"
>
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.File)">
<i class="bwi bwi-fw bwi-file" aria-hidden="true"></i>&nbsp;{{
"sendTypeFile" | i18n
}}
</button>
</a>
</li>
</ul>
</div>
@ -51,21 +36,20 @@
<div id="items" class="items">
<div class="content">
<div class="list" *ngIf="filteredSends.length">
<button
<a
*ngFor="let s of filteredSends"
appStopClick
(click)="selectSend(s.id)"
title="{{ 'viewItem' | i18n }}"
(contextmenu)="viewSendMenu(s)"
[ngClass]="{ active: s.id === sendId }"
[attr.aria-pressed]="s.id === sendId"
class="flex-list-item"
>
<span class="item-icon" aria-hidden="true">
<div class="item-icon" aria-hidden="true">
<i class="bwi bwi-fw bwi-lg" [ngClass]="s.type == 0 ? 'bwi-file-text' : 'bwi-file'"></i>
</span>
<span class="item-content">
<span class="item-title">
</div>
<div class="item-content">
<div class="item-title">
{{ s.name }}
<span class="title-badges">
<ng-container *ngIf="s.disabled">
@ -114,10 +98,10 @@
<span class="sr-only">{{ "pendingDeletion" | i18n }}</span>
</ng-container>
</span>
</span>
</div>
<span class="item-details">{{ s.deletionDate | date }}</span>
</span>
</button>
</div>
</a>
</div>
<div class="no-items" *ngIf="!filteredSends.length">
<i class="bwi bwi-spinner bwi-spin bwi-3x" *ngIf="!loaded" aria-hidden="true"></i>
@ -128,7 +112,12 @@
</div>
</div>
<div class="footer">
<button (click)="addSend()" class="block primary" appA11yTitle="{{ 'addItem' | i18n }}">
<button
appBlurClick
(click)="addSend()"
class="block primary"
appA11yTitle="{{ 'addItem' | i18n }}"
>
<i class="bwi bwi-plus bwi-lg" aria-hidden="true"></i>
</button>
</div>

211
src/app/services.module.ts Normal file
View File

@ -0,0 +1,211 @@
import { APP_INITIALIZER, NgModule } from "@angular/core";
import { JslibServicesModule } from "jslib-angular/services/jslib-services.module";
import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
import { EventService as EventServiceAbstraction } from "jslib-common/abstractions/event.service";
import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "jslib-common/abstractions/stateMigration.service";
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from "jslib-common/abstractions/sync.service";
import { SystemService as SystemServiceAbstraction } from "jslib-common/abstractions/system.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "jslib-common/abstractions/twoFactor.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service";
import { ThemeType } from "jslib-common/enums/themeType";
import { StateFactory } from "jslib-common/factories/stateFactory";
import { GlobalState } from "jslib-common/models/domain/globalState";
import { ContainerService } from "jslib-common/services/container.service";
import { EventService } from "jslib-common/services/event.service";
import { StateMigrationService } from "jslib-common/services/stateMigration.service";
import { SystemService } from "jslib-common/services/system.service";
import { VaultTimeoutService } from "jslib-common/services/vaultTimeout.service";
import { ElectronCryptoService } from "jslib-electron/services/electronCrypto.service";
import { ElectronLogService } from "jslib-electron/services/electronLog.service";
import { ElectronPlatformUtilsService } from "jslib-electron/services/electronPlatformUtils.service";
import { ElectronRendererMessagingService } from "jslib-electron/services/electronRendererMessaging.service";
import { ElectronRendererSecureStorageService } from "jslib-electron/services/electronRendererSecureStorage.service";
import { ElectronRendererStorageService } from "jslib-electron/services/electronRendererStorage.service";
import { Account } from "../models/account";
import { I18nService } from "../services/i18n.service";
import { LoginGuardService } from "../services/loginGuard.service";
import { NativeMessagingService } from "../services/nativeMessaging.service";
import { PasswordRepromptService } from "../services/passwordReprompt.service";
import { StateService } from "../services/state.service";
import { SearchBarService } from "./layout/search/search-bar.service";
export function initFactory(
window: Window,
environmentService: EnvironmentServiceAbstraction,
syncService: SyncServiceAbstraction,
vaultTimeoutService: VaultTimeoutService,
i18nService: I18nService,
eventService: EventService,
twoFactorService: TwoFactorServiceAbstraction,
notificationsService: NotificationsServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
stateService: StateServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
nativeMessagingService: NativeMessagingService
): () => Promise<void> {
return async () => {
nativeMessagingService.init();
await stateService.init();
await environmentService.setUrlsFromStorage();
syncService.fullSync(true);
await vaultTimeoutService.init(true);
const locale = await stateService.getLocale();
await i18nService.init(locale);
eventService.init(true);
twoFactorService.init();
setTimeout(() => notificationsService.init(), 3000);
const htmlEl = window.document.documentElement;
htmlEl.classList.add("os_" + platformUtilsService.getDeviceString());
htmlEl.classList.add("locale_" + i18nService.translationLocale);
const theme = await platformUtilsService.getEffectiveTheme();
htmlEl.classList.add("theme_" + theme);
platformUtilsService.onDefaultSystemThemeChange(async (sysTheme) => {
const bwTheme = await stateService.getTheme();
if (bwTheme == null || bwTheme === ThemeType.System) {
htmlEl.classList.remove("theme_" + ThemeType.Light, "theme_" + ThemeType.Dark);
htmlEl.classList.add("theme_" + sysTheme);
}
});
let installAction = null;
const installedVersion = await stateService.getInstalledVersion();
const currentVersion = await platformUtilsService.getApplicationVersion();
if (installedVersion == null) {
installAction = "install";
} else if (installedVersion !== currentVersion) {
installAction = "update";
}
if (installAction != null) {
await stateService.setInstalledVersion(currentVersion);
}
const containerService = new ContainerService(cryptoService);
containerService.attachToGlobal(window);
};
}
@NgModule({
imports: [JslibServicesModule],
declarations: [],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initFactory,
deps: [
"WINDOW",
EnvironmentServiceAbstraction,
SyncServiceAbstraction,
VaultTimeoutServiceAbstraction,
I18nServiceAbstraction,
EventServiceAbstraction,
TwoFactorServiceAbstraction,
NotificationsServiceAbstraction,
PlatformUtilsServiceAbstraction,
StateServiceAbstraction,
CryptoServiceAbstraction,
NativeMessagingService,
],
multi: true,
},
{ provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] },
{
provide: PlatformUtilsServiceAbstraction,
useFactory: (
i18nService: I18nServiceAbstraction,
messagingService: MessagingServiceAbstraction,
stateService: StateServiceAbstraction
) => new ElectronPlatformUtilsService(i18nService, messagingService, true, stateService),
deps: [I18nServiceAbstraction, MessagingServiceAbstraction, StateServiceAbstraction],
},
{
provide: I18nServiceAbstraction,
useFactory: (window: Window) => new I18nService(window.navigator.language, "./locales"),
deps: ["WINDOW"],
},
{
provide: MessagingServiceAbstraction,
useClass: ElectronRendererMessagingService,
deps: [BroadcasterServiceAbstraction],
},
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService },
{ provide: "SECURE_STORAGE", useClass: ElectronRendererSecureStorageService },
{
provide: CryptoServiceAbstraction,
useClass: ElectronCryptoService,
deps: [
CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction,
LogServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: SystemServiceAbstraction,
useFactory: (
messagingService: MessagingServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
stateService: StateServiceAbstraction
) => new SystemService(messagingService, platformUtilsService, null, stateService),
deps: [MessagingServiceAbstraction, PlatformUtilsServiceAbstraction, StateServiceAbstraction],
},
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
NativeMessagingService,
SearchBarService,
{
provide: LoginGuardService,
useClass: LoginGuardService,
deps: [StateServiceAbstraction, PlatformUtilsServiceAbstraction, I18nServiceAbstraction],
},
{
provide: StateServiceAbstraction,
useFactory: (
storageService: StorageServiceAbstraction,
secureStorageService: StorageServiceAbstraction,
logService: LogServiceAbstraction,
stateMigrationService: StateMigrationServiceAbstraction
) =>
new StateService(
storageService,
secureStorageService,
logService,
stateMigrationService,
new StateFactory(GlobalState, Account)
),
deps: [
StorageServiceAbstraction,
"SECURE_STORAGE",
LogServiceAbstraction,
StateMigrationServiceAbstraction,
],
},
{
provide: StateMigrationServiceAbstraction,
useFactory: (
storageService: StorageServiceAbstraction,
secureStorageService: StorageServiceAbstraction
) =>
new StateMigrationService(
storageService,
secureStorageService,
new StateFactory(GlobalState, Account)
),
deps: [StorageServiceAbstraction, "SECURE_STORAGE"],
},
],
})
export class ServicesModule {}

View File

@ -1,81 +0,0 @@
import { Inject, Injectable } from "@angular/core";
import { WINDOW } from "jslib-angular/services/jslib-services.module";
import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
import { EventService as EventServiceAbstraction } from "jslib-common/abstractions/event.service";
import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { SyncService as SyncServiceAbstraction } from "jslib-common/abstractions/sync.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "jslib-common/abstractions/twoFactor.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service";
import { ThemeType } from "jslib-common/enums/themeType";
import { ContainerService } from "jslib-common/services/container.service";
import { EventService } from "jslib-common/services/event.service";
import { VaultTimeoutService } from "jslib-common/services/vaultTimeout.service";
import { I18nService } from "../../services/i18n.service";
import { NativeMessagingService } from "../../services/nativeMessaging.service";
@Injectable()
export class InitService {
constructor(
@Inject(WINDOW) private win: Window,
private environmentService: EnvironmentServiceAbstraction,
private syncService: SyncServiceAbstraction,
private vaultTimeoutService: VaultTimeoutServiceAbstraction,
private i18nService: I18nServiceAbstraction,
private eventService: EventServiceAbstraction,
private twoFactorService: TwoFactorServiceAbstraction,
private notificationsService: NotificationsServiceAbstraction,
private platformUtilsService: PlatformUtilsServiceAbstraction,
private stateService: StateServiceAbstraction,
private cryptoService: CryptoServiceAbstraction,
private nativeMessagingService: NativeMessagingService
) {}
init() {
return async () => {
this.nativeMessagingService.init();
await this.stateService.init();
await this.environmentService.setUrlsFromStorage();
this.syncService.fullSync(true);
(this.vaultTimeoutService as VaultTimeoutService).init(true);
const locale = await this.stateService.getLocale();
await (this.i18nService as I18nService).init(locale);
(this.eventService as EventService).init(true);
this.twoFactorService.init();
setTimeout(() => this.notificationsService.init(), 3000);
const htmlEl = this.win.document.documentElement;
htmlEl.classList.add("os_" + this.platformUtilsService.getDeviceString());
const theme = await this.platformUtilsService.getEffectiveTheme();
htmlEl.classList.add("theme_" + theme);
this.platformUtilsService.onDefaultSystemThemeChange(async (sysTheme) => {
const bwTheme = await this.stateService.getTheme();
if (bwTheme == null || bwTheme === ThemeType.System) {
htmlEl.classList.remove("theme_" + ThemeType.Light, "theme_" + ThemeType.Dark);
htmlEl.classList.add("theme_" + sysTheme);
}
});
let installAction = null;
const installedVersion = await this.stateService.getInstalledVersion();
const currentVersion = await this.platformUtilsService.getApplicationVersion();
if (installedVersion == null) {
installAction = "install";
} else if (installedVersion !== currentVersion) {
installAction = "update";
}
if (installAction != null) {
await this.stateService.setInstalledVersion(currentVersion);
}
const containerService = new ContainerService(this.cryptoService);
containerService.attachToGlobal(this.win);
};
}
}

View File

@ -1,135 +0,0 @@
import { APP_INITIALIZER, InjectionToken, NgModule } from "@angular/core";
import {
JslibServicesModule,
SECURE_STORAGE,
STATE_FACTORY,
STATE_SERVICE_USE_CACHE,
WINDOW,
CLIENT_TYPE,
LOCALES_DIRECTORY,
SYSTEM_LANGUAGE,
} from "jslib-angular/services/jslib-services.module";
import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import {
LogService,
LogService as LogServiceAbstraction,
} from "jslib-common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "jslib-common/abstractions/stateMigration.service";
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { SystemService as SystemServiceAbstraction } from "jslib-common/abstractions/system.service";
import { ClientType } from "jslib-common/enums/clientType";
import { StateFactory } from "jslib-common/factories/stateFactory";
import { GlobalState } from "jslib-common/models/domain/globalState";
import { SystemService } from "jslib-common/services/system.service";
import { ElectronCryptoService } from "jslib-electron/services/electronCrypto.service";
import { ElectronLogService } from "jslib-electron/services/electronLog.service";
import { ElectronPlatformUtilsService } from "jslib-electron/services/electronPlatformUtils.service";
import { ElectronRendererMessagingService } from "jslib-electron/services/electronRendererMessaging.service";
import { ElectronRendererSecureStorageService } from "jslib-electron/services/electronRendererSecureStorage.service";
import { ElectronRendererStorageService } from "jslib-electron/services/electronRendererStorage.service";
import { Account } from "../../models/account";
import { I18nService } from "../../services/i18n.service";
import { LoginGuardService } from "../../services/loginGuard.service";
import { NativeMessagingService } from "../../services/nativeMessaging.service";
import { PasswordRepromptService } from "../../services/passwordReprompt.service";
import { StateService } from "../../services/state.service";
import { SearchBarService } from "../layout/search/search-bar.service";
import { InitService } from "./init.service";
const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
@NgModule({
imports: [JslibServicesModule],
declarations: [],
providers: [
InitService,
NativeMessagingService,
SearchBarService,
LoginGuardService,
{
provide: APP_INITIALIZER,
useFactory: (initService: InitService) => initService.init(),
deps: [InitService],
multi: true,
},
{
provide: STATE_FACTORY,
useValue: new StateFactory(GlobalState, Account),
},
{
provide: CLIENT_TYPE,
useValue: ClientType.Desktop,
},
{
provide: RELOAD_CALLBACK,
useValue: null,
},
{ provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] },
{
provide: PlatformUtilsServiceAbstraction,
useClass: ElectronPlatformUtilsService,
deps: [
I18nServiceAbstraction,
MessagingServiceAbstraction,
CLIENT_TYPE,
StateServiceAbstraction,
],
},
{
provide: I18nServiceAbstraction,
useClass: I18nService,
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY],
},
{
provide: MessagingServiceAbstraction,
useClass: ElectronRendererMessagingService,
deps: [BroadcasterServiceAbstraction],
},
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService },
{ provide: SECURE_STORAGE, useClass: ElectronRendererSecureStorageService },
{
provide: CryptoServiceAbstraction,
useClass: ElectronCryptoService,
deps: [
CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction,
LogServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: SystemServiceAbstraction,
useClass: SystemService,
deps: [
MessagingServiceAbstraction,
PlatformUtilsServiceAbstraction,
RELOAD_CALLBACK,
StateServiceAbstraction,
],
},
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
{
provide: StateServiceAbstraction,
useClass: StateService,
deps: [
StorageServiceAbstraction,
SECURE_STORAGE,
LogService,
StateMigrationServiceAbstraction,
STATE_FACTORY,
STATE_SERVICE_USE_CACHE,
],
},
],
})
export class ServicesModule {}

View File

@ -10,14 +10,15 @@
*ngFor="let f of cipher.fields; let i = index; trackBy: trackByFunction"
[ngClass]="{ 'box-content-row-checkbox': f.type === fieldType.Boolean }"
>
<button
type="button"
<a
href="#"
appStopClick
(click)="removeField(f)"
appA11yTitle="{{ 'remove' | i18n }}"
role="button"
>
<i class="bwi bwi-minus-circle bwi-lg" aria-hidden="true"></i>
</button>
</a>
<label for="fieldName{{ i }}" class="sr-only">{{ "name" | i18n }}</label>
<label for="fieldValue{{ i }}" class="sr-only">{{ "value" | i18n }}</label>
<div class="row-main">
@ -77,12 +78,13 @@
class="action-buttons"
*ngIf="f.type === fieldType.Hidden && (cipher.viewPassword || f.newField)"
>
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="f.showValue"
(click)="toggleFieldValue(f)"
>
<i
@ -90,7 +92,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !f.showValue, 'bwi-eye-slash': f.showValue }"
></i>
</button>
</a>
</div>
<div class="drag-handle" appA11yTitle="{{ 'dragToSort' | i18n }}" cdkDragHandle>
<i class="bwi bwi-hamburger" aria-hidden="true"></i>
@ -99,10 +101,10 @@
</div>
<!-- Add new custom field -->
<div class="box-content-row" appBoxRow>
<button type="button" appStopClick (click)="addField()">
<a href="#" appStopClick (click)="addField()" role="button">
<i class="bwi bwi-plus-circle bwi-fw bwi-lg" aria-hidden="true"></i>
{{ "newCustomField" | i18n }}
</button>
</a>
<label for="addFieldType" class="sr-only">{{ "type" | i18n }}</label>
<select id="addFieldType" name="AddFieldType" [(ngModel)]="addFieldType" class="field-type">
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{ o.name }}</option>

View File

@ -27,8 +27,7 @@
</div>
<!-- Login -->
<div *ngIf="cipher.type === cipherType.Login">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<div class="box-content-row" appBoxRow>
<label for="loginUsername">{{ "username" | i18n }}</label>
<input
id="loginUsername"
@ -38,18 +37,6 @@
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'generateUsername' | i18n }}"
(click)="generateUsername()"
>
<i class="bwi bwi-lg bwi-generate" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="loginPassword">{{ "password" | i18n }}</label>
@ -68,6 +55,7 @@
type="button"
#checkPasswordBtn
class="row-btn btn"
appBlurClick
appA11yTitle="{{ 'checkPassword' | i18n }}"
(click)="checkPassword()"
[appApiAction]="checkPasswordPromise"
@ -84,12 +72,13 @@
aria-hidden="true"
></i>
</button>
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword()"
>
<i
@ -97,16 +86,18 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
<button
type="button"
</a>
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'generatePassword' | i18n }}"
(click)="generatePassword()"
>
<i class="bwi bwi-lg bwi-generate" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
@ -146,12 +137,13 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showCardNumber"
(click)="toggleCardNumber()"
>
<i
@ -159,7 +151,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showCardNumber, 'bwi-eye-slash': showCardNumber }"
></i>
</button>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
@ -199,12 +191,13 @@
/>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showCardCode"
(click)="toggleCardCode()"
>
<i
@ -212,7 +205,7 @@
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showCardCode, 'bwi-eye-slash': showCardCode }"
></i>
</button>
</a>
</div>
</div>
</div>
@ -395,14 +388,9 @@
appBoxRow
*ngFor="let u of cipher.login.uris; let i = index; trackBy: trackByFunction"
>
<button
type="button"
appStopClick
(click)="removeUri(u)"
appA11yTitle="{{ 'remove' | i18n }}"
>
<i class="bwi bwi-minus-circle bwi-lg" aria-hidden="true"></i>
</button>
<a href="#" appStopClick (click)="removeUri(u)" appA11yTitle="{{ 'remove' | i18n }}">
<i class="bwi bwi-minus-circle bwi-lg" aria-hidden="true" role="button"></i>
</a>
<div class="row-main">
<label for="loginUri{{ i }}">{{ "uriPosition" | i18n: i + 1 }}</label>
<input
@ -429,25 +417,31 @@
</select>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleOptions' | i18n }}"
(click)="toggleUriOptions(u)"
[attr.aria-expanded]="
!(u.showOptions === false || (u.showOptions == null && u.match == null))
"
>
<i class="bwi bwi-lg bwi-cog" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
</ng-container>
<button type="button" appStopClick (click)="addUri()" class="box-content-row">
<a
href="#"
appStopClick
appBlurClick
(click)="addUri()"
class="box-content-row"
role="button"
>
<i class="bwi bwi-plus-circle bwi-fw bwi-lg" aria-hidden="true"></i>
{{ "newUri" | i18n }}
</button>
</a>
</div>
</div>
<div class="box">
@ -465,13 +459,9 @@
<div class="box-content-row box-content-row-checkbox" appBoxRow *ngIf="canUseReprompt">
<label for="passwordPrompt"
>{{ "passwordPrompt" | i18n }}
<button
type="button"
appA11yTitle="{{ 'learnMore' | i18n }}"
(click)="openHelpReprompt()"
>
<a href="#" appA11yTitle="{{ 'learnMore' | i18n }}" (click)="openHelpReprompt()">
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</button>
</a>
</label>
<input
id="passwordPrompt"
@ -481,26 +471,30 @@
(change)="repromptChanged()"
/>
</div>
<button
type="button"
<a
class="box-content-row box-content-row-flex text-default"
href="#"
appStopClick
appBlurClick
(click)="attachments()"
*ngIf="editMode && !cloneMode"
role="button"
>
<div class="row-main">{{ "attachments" | i18n }}</div>
<i class="bwi bwi-angle-right row-sub-icon" aria-hidden="true"></i>
</button>
<button
type="button"
</a>
<a
class="box-content-row box-content-row-flex text-default"
href="#"
appStopClick
appBlurClick
(click)="editCollections()"
*ngIf="editMode && !cloneMode && cipher.organizationId"
role="button"
>
<div class="row-main">{{ "collections" | i18n }}</div>
<i class="bwi bwi-angle-right row-sub-icon" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
<div class="box">
@ -565,6 +559,7 @@
</div>
<div class="footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"
@ -577,11 +572,12 @@
aria-hidden="true"
></i>
</button>
<button type="button" (click)="cancel()">
<button appBlurClick type="button" (click)="cancel()">
{{ "cancel" | i18n }}
</button>
<div class="right">
<button
appBlurClick
type="button"
(click)="share()"
appA11yTitle="{{ 'moveToOrganization' | i18n }}"
@ -591,6 +587,7 @@
</button>
<button
#deleteBtn
appBlurClick
type="button"
(click)="delete()"
class="danger"

View File

@ -17,6 +17,7 @@
class="row-btn btn"
type="button"
appStopClick
appBlurClick
appA11yTitle="{{ 'delete' | i18n }}"
(click)="delete(a)"
#deleteBtn
@ -55,6 +56,7 @@
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"

View File

@ -6,15 +6,14 @@
*ngIf="ciphers.length"
>
<div class="list">
<button
type="button"
<a
*cdkVirtualFor="let c of ciphers; trackBy: trackByFn"
appStopClick
(click)="selectCipher(c)"
(contextmenu)="rightClickCipher(c)"
href="#"
title="{{ 'viewItem' | i18n }}"
[ngClass]="{ active: c.id === activeCipherId }"
[attr.aria-pressed]="c.id === activeCipherId"
class="flex-list-item virtual-scroll-item"
>
<app-vault-icon [cipher]="c"></app-vault-icon>
@ -40,7 +39,7 @@
</span>
<span *ngIf="c.subTitle" class="detail">{{ c.subTitle }}</span>
</div>
</button>
</a>
</div>
</cdk-virtual-scroll-viewport>
<div class="no-items" *ngIf="!ciphers.length">
@ -54,6 +53,7 @@
</div>
<div class="footer">
<button
appBlurClick
(click)="addCipher()"
(contextmenu)="addCipherOptions()"
class="block primary"

View File

@ -28,6 +28,7 @@
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"

View File

@ -21,8 +21,8 @@
<option *ngFor="let f of formatOptions" [value]="f.value">{{ f.name }}</option>
</select>
</div>
<app-user-verification ngDefaultControl formControlName="secret" name="secret">
</app-user-verification>
<app-verify-master-password ngDefaultControl formControlName="secret" name="secret">
</app-verify-master-password>
</div>
<div class="box-footer">
<p>{{ "confirmIdentity" | i18n }}</p>
@ -31,6 +31,7 @@
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'submit' | i18n }}"

View File

@ -22,6 +22,7 @@
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"
@ -42,6 +43,7 @@
<div class="right">
<button
#deleteBtn
appBlurClick
type="button"
(click)="delete()"
class="danger"

View File

@ -1,570 +0,0 @@
<div
class="modal fade"
role="dialog"
aria-modal="true"
attr.aria-label="{{ 'generatePassword' | i18n }}"
>
<div class="modal-dialog modal-md" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="modal-title">
{{ "generator" | i18n }}
</div>
<app-callout
type="info"
*ngIf="enforcedPasswordPolicyOptions?.inEffect() && type === 'password'"
>
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="generated-block" *ngIf="type === 'password'">
<div class="generated-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
<div class="action-buttons">
<button
type="button"
class="icon-btn primary"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy()"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
<button
type="button"
class="icon-btn primary"
appStopClick
appA11yTitle="{{ 'regeneratePassword' | i18n }}"
(click)="regenerate()"
>
<i class="bwi bwi-lg bwi-generate" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="generated-block" *ngIf="type === 'username'">
<div class="generated-wrapper" [innerHTML]="username | colorPassword" appSelectCopy></div>
<div class="action-buttons" #form [appApiAction]="usernameGeneratingPromise">
<button
type="button"
class="icon-btn primary"
appStopClick
appA11yTitle="{{ 'copyUsername' | i18n }}"
(click)="copy()"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
<button
type="button"
class="icon-btn primary"
appStopClick
appA11yTitle="{{ 'regenerateUsername' | i18n }}"
(click)="regenerate()"
[disabled]="form.loading"
>
<i
class="bwi bwi-lg bwi-generate"
[ngClass]="form.loading ? 'bwi-spin' : ''"
aria-hidden="true"
></i>
</button>
</div>
</div>
<div class="box">
<div class="box-content condensed">
<div
class="box-content-row box-content-row-radio"
role="radiogroup"
aria-labelledby="typeHeading"
>
<label id="typeHeading" class="radio-header">{{
"whatWouldYouLikeToGenerate" | i18n
}}</label>
<div
class="radio-group text-default"
appBoxRow
name="TypeOptions"
*ngFor="let o of typeOptions"
>
<input
type="radio"
class="radio"
[(ngModel)]="type"
name="Type"
id="type_{{ o.value }}"
[value]="o.value"
(change)="typeChanged()"
[checked]="type === o.value"
[disabled]="comingFromAddEdit && type !== o.value"
/>
<label class="unstyled" for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
</div>
</div>
<ng-container *ngIf="type === 'password'">
<div class="box">
<div class="box-header">
<button
type="button"
(click)="toggleOptions()"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-expanded]="showOptions"
>
<i class="bwi bwi-plus-square" aria-hidden="true" [hidden]="showOptions"></i>
<i class="bwi bwi-minus-square" aria-hidden="true" [hidden]="!showOptions"></i>
{{ "options" | i18n }}
</button>
</div>
<div class="box-content condensed" [hidden]="!showOptions">
<div
class="box-content-row box-content-row-radio"
role="radiogroup"
aria-labelledby="passwordTypeHeading"
>
<label id="passwordTypeHeading" class="radio-header">{{
"passwordType" | i18n
}}</label>
<div
class="radio-group text-default"
appBoxRow
name="PassTypeOptions"
*ngFor="let o of passTypeOptions"
>
<input
type="radio"
class="radio"
[(ngModel)]="passwordOptions.type"
name="PasswordType"
id="passwordType_{{ o.value }}"
[value]="o.value"
(change)="savePasswordOptions()"
[checked]="passwordOptions.type === o.value"
/>
<label class="unstyled" for="passwordType_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions" *ngIf="passwordOptions.type === 'passphrase'">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="num-words">{{ "numWords" | i18n }}</label>
<input
id="num-words"
type="number"
min="3"
max="20"
(blur)="savePasswordOptions()"
[(ngModel)]="passwordOptions.numWords"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input
id="word-separator"
type="text"
maxlength="1"
(input)="savePasswordOptions()"
[(ngModel)]="passwordOptions.wordSeparator"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{ "capitalize" | i18n }}</label>
<input
id="capitalize"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.capitalize"
[disabled]="enforcedPasswordPolicyOptions?.capitalize"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="include-number">{{ "includeNumber" | i18n }}</label>
<input
id="include-number"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.includeNumber"
[disabled]="enforcedPasswordPolicyOptions?.includeNumber"
/>
</div>
</div>
</div>
<ng-container *ngIf="passwordOptions.type === 'password'">
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-slider" appBoxRow>
<label for="length">{{ "length" | i18n }}</label>
<input
id="length"
type="number"
min="5"
max="128"
[(ngModel)]="passwordOptions.length"
(blur)="savePasswordOptions()"
/>
<input
id="lengthRange"
type="range"
min="5"
max="128"
step="1"
[(ngModel)]="passwordOptions.length"
(change)="sliderChanged()"
(input)="sliderInput()"
attr.aria-label="{{ 'length' | i18n }}"
tabindex="-1"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="uppercase">A-Z</label>
<input
id="uppercase"
type="checkbox"
(change)="savePasswordOptions()"
[disabled]="enforcedPasswordPolicyOptions?.useUppercase"
[(ngModel)]="passwordOptions.uppercase"
attr.aria-label="{{ 'uppercase' | i18n }}"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label>
<input
id="lowercase"
type="checkbox"
(change)="savePasswordOptions()"
[disabled]="enforcedPasswordPolicyOptions?.useLowercase"
[(ngModel)]="passwordOptions.lowercase"
attr.aria-label="{{ 'lowercase' | i18n }}"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="numbers">0-9</label>
<input
id="numbers"
type="checkbox"
(change)="savePasswordOptions()"
[disabled]="enforcedPasswordPolicyOptions?.useNumbers"
[(ngModel)]="passwordOptions.number"
attr.aria-label="{{ 'numbers' | i18n }}"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="special">!@#$%^&*</label>
<input
id="special"
type="checkbox"
(change)="savePasswordOptions()"
[disabled]="enforcedPasswordPolicyOptions?.useSpecial"
[(ngModel)]="passwordOptions.special"
attr.aria-label="{{ 'specialCharacters' | i18n }}"
/>
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-number">{{ "minNumbers" | i18n }}</label>
<input
id="min-number"
type="number"
min="0"
max="9"
(blur)="savePasswordOptions()"
[(ngModel)]="passwordOptions.minNumber"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-special">{{ "minSpecial" | i18n }}</label>
<input
id="min-special"
type="number"
min="0"
max="9"
(blur)="savePasswordOptions()"
[(ngModel)]="passwordOptions.minSpecial"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{ "ambiguous" | i18n }}</label>
<input
id="ambiguous"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="avoidAmbiguous"
/>
</div>
</div>
</div>
</ng-container>
</ng-container>
<ng-container *ngIf="type === 'username'">
<div class="box">
<div class="box-header">
<button
type="button"
(click)="toggleOptions()"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-expanded]="showOptions"
>
<i class="bwi bwi-plus-square" aria-hidden="true" [hidden]="showOptions"></i>
<i class="bwi bwi-minus-square" aria-hidden="true" [hidden]="!showOptions"></i>
{{ "options" | i18n }}
</button>
</div>
<div class="box-content condensed" [hidden]="!showOptions">
<div
class="box-content-row box-content-row-radio"
role="radiogroup"
aria-labelledby="usernameTypeHeading"
>
<label id="usernameTypeHeading" class="radio-header">
{{ "usernameType" | i18n }}
<a
href="#"
appStopClick
(click)="usernameTypesLearnMore()"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</label>
<div
class="radio-group align-start text-default"
appBoxRow
name="UsernameTypeOptions"
*ngFor="let o of usernameTypeOptions"
>
<input
type="radio"
class="radio"
[(ngModel)]="usernameOptions.type"
name="UsernameType"
id="usernameType_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.type === o.value"
/>
<label class="unstyled" for="usernameType_{{ o.value }}">
{{ o.name }}
<div class="small text-muted" *ngIf="o.desc">{{ o.desc }}</div>
</label>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="usernameOptions.type === 'forwarded'" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row" role="radiogroup" aria-labelledby="forwardTypeHeading">
<label id="forwardTypeHeading" class="radio-header">{{ "service" | i18n }}</label>
<div class="radio-group text-default" appBoxRow *ngFor="let o of forwardOptions">
<input
type="radio"
[(ngModel)]="usernameOptions.forwardedService"
name="ForwardType"
id="forwardtype_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.forwardedService === o.value"
/>
<label for="forwardtype_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<ng-container *ngIf="usernameOptions.forwardedService === 'simplelogin'">
<div class="box-content-row" appBoxRow>
<label for="simplelogin-apikey">{{ "apiKey" | i18n }}</label>
<input
id="simplelogin-apikey"
type="password"
name="SimpleLoginApiKey"
[(ngModel)]="usernameOptions.forwardedSimpleLoginApiKey"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="simplelogin-hostname">{{ "hostname" | i18n }}</label>
<input
id="simplelogin-hostname"
type="text"
name="SimpleLoginHostname"
[(ngModel)]="usernameOptions.forwardedSimpleLoginHostname"
(blur)="saveUsernameOptions()"
/>
</div>
</ng-container>
<ng-container *ngIf="usernameOptions.forwardedService === 'anonaddy'">
<div class="box-content-row" appBoxRow>
<label for="anonaddy-accessToken">{{ "apiAccessToken" | i18n }}</label>
<input
id="anonaddy-accessToken"
type="password"
name="AnonAddyAccessToken"
[(ngModel)]="usernameOptions.forwardedAnonAddyApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="anonaddy-domain">{{ "domainName" | i18n }}</label>
<input
id="anonaddy-domain"
type="text"
name="AnonAddyDomain"
[(ngModel)]="usernameOptions.forwardedAnonAddyDomain"
(blur)="saveUsernameOptions()"
/>
</div>
</ng-container>
<ng-container *ngIf="usernameOptions.forwardedService === 'firefoxrelay'">
<div class="box-content-row" appBoxRow>
<label for="firefox-apikey">{{ "apiAccessToken" | i18n }}</label>
<input
id="firefox-apikey"
type="password"
name="FirefoxApiKey"
[(ngModel)]="usernameOptions.forwardedFirefoxApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
</ng-container>
</div>
</div>
<div class="box" *ngIf="usernameOptions.type === 'subaddress'" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row" appBoxRow>
<label for="subaddress-email">{{ "emailAddress" | i18n }}</label>
<input
id="subaddress-email"
type="text"
name="SubaddressEmail"
[(ngModel)]="usernameOptions.subaddressEmail"
(blur)="saveUsernameOptions()"
/>
</div>
<div
class="box-content-row"
role="radiogroup"
aria-labelledby="subaddressTypeHeading"
*ngIf="subaddressOptions.length > 1"
>
<label id="subaddressTypeHeading" class="radio-header">{{ "type" | i18n }}</label>
<div class="radio-group text-default" appBoxRow *ngFor="let o of subaddressOptions">
<input
type="radio"
[(ngModel)]="usernameOptions.subaddressType"
name="SubaddressType"
id="subaddresstype_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.subaddressType === o.value"
/>
<label for="subaddresstype_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<div class="box-content-row" appBoxRow *ngIf="usernameWebsite">
<label for="subaddress-website">{{ "website" | i18n }}</label>
<input
id="subaddress-website"
type="text"
name="SubaddressWebsite"
[value]="usernameOptions.website"
disabled
readonly
/>
</div>
</div>
</div>
<div class="box" *ngIf="usernameOptions.type === 'catchall'" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row" appBoxRow>
<label for="catchall-domain">{{ "domainName" | i18n }}</label>
<input
id="catchall-domain"
type="text"
name="CatchallDomain"
[(ngModel)]="usernameOptions.catchallDomain"
(blur)="saveUsernameOptions()"
/>
</div>
<div
class="box-content-row"
role="radiogroup"
aria-labelledby="catchallTypeHeading"
*ngIf="catchallOptions.length > 1"
>
<label id="catchallTypeHeading" class="radio-header">{{ "type" | i18n }}</label>
<div class="radio-group text-default" appBoxRow *ngFor="let o of catchallOptions">
<input
type="radio"
[(ngModel)]="usernameOptions.catchallType"
name="CatchallType"
id="catchalltype_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.catchallType === o.value"
/>
<label for="catchalltype_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<div class="box-content-row" appBoxRow *ngIf="usernameWebsite">
<label for="catchall-website">{{ "website" | i18n }}</label>
<input
id="catchall-website"
type="text"
name="CatchallWebsite"
[value]="usernameOptions.website"
disabled
readonly
/>
</div>
</div>
</div>
<div class="box" *ngIf="usernameOptions.type === 'word'" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{ "capitalize" | i18n }}</label>
<input
id="capitalize"
type="checkbox"
(change)="saveUsernameOptions()"
[(ngModel)]="usernameOptions.wordCapitalize"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="include-number">{{ "includeNumber" | i18n }}</label>
<input
id="include-number"
type="checkbox"
(change)="saveUsernameOptions()"
[(ngModel)]="usernameOptions.wordIncludeNumber"
/>
</div>
</div>
</div>
</ng-container>
</div>
<div class="modal-footer">
<button
type="button"
class="primary"
*ngIf="comingFromAddEdit"
(click)="select()"
appA11yTitle="{{ 'select' | i18n }}"
>
<i class="bwi bwi-lg bwi-fw bwi-check" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">
{{ (comingFromAddEdit ? "cancel" : "close") | i18n }}
</button>
</div>
</div>
</div>
</div>

View File

@ -1,41 +0,0 @@
import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { GeneratorComponent as BaseGeneratorComponent } from "jslib-angular/components/generator.component";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { UsernameGenerationService } from "jslib-common/abstractions/usernameGeneration.service";
@Component({
selector: "app-generator",
templateUrl: "generator.component.html",
})
export class GeneratorComponent extends BaseGeneratorComponent {
constructor(
passwordGenerationService: PasswordGenerationService,
usernameGenerationService: UsernameGenerationService,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
route: ActivatedRoute,
logService: LogService
) {
super(
passwordGenerationService,
usernameGenerationService,
platformUtilsService,
stateService,
i18nService,
logService,
route,
window
);
}
usernameTypesLearnMore() {
this.platformUtilsService.launchUri("https://bitwarden.com/help/generator/#username-types");
}
}

View File

@ -3,81 +3,51 @@
<h2 class="sr-only">{{ "filters" | i18n }}</h2>
<ul>
<li [ngClass]="{ active: selectedAll }">
<button type="button" [attr.aria-pressed]="selectedAll" appStopClick (click)="selectAll()">
<a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="bwi bwi-fw bwi-filter" aria-hidden="true"></i>&nbsp;{{ "allItems" | i18n }}
</button>
</a>
</li>
<li [ngClass]="{ active: selectedFavorites }">
<button
type="button"
[attr.aria-pressed]="selectedFavorites"
appStopClick
(click)="selectFavorites()"
>
<a href="#" appStopClick appBlurClick (click)="selectFavorites()">
<i class="bwi bwi-fw bwi-star" aria-hidden="true"></i>&nbsp;{{ "favorites" | i18n }}
</button>
</a>
</li>
<li [ngClass]="{ active: selectedTrash }">
<button
type="button"
[attr.aria-pressed]="selectedTrash"
appStopClick
(click)="selectTrash()"
>
<a href="#" appStopClick appBlurClick (click)="selectTrash()">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>&nbsp;{{ "trash" | i18n }}
</button>
</a>
</li>
</ul>
<h2>{{ "types" | i18n }}</h2>
<ul>
<li [ngClass]="{ active: selectedType === cipherType.Login }">
<button
type="button"
[attr.aria-pressed]="selectedType === cipherType.Login"
appStopClick
(click)="selectType(cipherType.Login)"
>
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Login)">
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>&nbsp;{{ "typeLogin" | i18n }}
</button>
</a>
</li>
<li [ngClass]="{ active: selectedType === cipherType.Card }">
<button
type="button"
[attr.aria-pressed]="selectedType === cipherType.Card"
appStopClick
(click)="selectType(cipherType.Card)"
>
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Card)">
<i class="bwi bwi-fw bwi-credit-card" aria-hidden="true"></i>&nbsp;{{ "typeCard" | i18n }}
</button>
</a>
</li>
<li [ngClass]="{ active: selectedType === cipherType.Identity }">
<button
type="button"
[attr.aria-pressed]="selectedType === cipherType.Identity"
appStopClick
(click)="selectType(cipherType.Identity)"
>
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Identity)">
<i class="bwi bwi-fw bwi-id-card" aria-hidden="true"></i>&nbsp;{{ "typeIdentity" | i18n }}
</button>
</a>
</li>
<li [ngClass]="{ active: selectedType === cipherType.SecureNote }">
<button
type="button"
[attr.aria-pressed]="selectedType === cipherType.SecureNote"
appStopClick
(click)="selectType(cipherType.SecureNote)"
>
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.SecureNote)">
<i class="bwi bwi-fw bwi-sticky-note" aria-hidden="true"></i>&nbsp;{{
"typeSecureNote" | i18n
}}
</button>
</a>
</li>
</ul>
<p *ngIf="!loaded" class="text-muted">{{ "loading" | i18n }}</p>
<ng-container *ngIf="loaded">
<div class="heading">
<h2>{{ "folders" | i18n }}</h2>
<button (click)="addFolder()" appA11yTitle="{{ 'addFolder' | i18n }}">
<button appBlurClick (click)="addFolder()" appA11yTitle="{{ 'addFolder' | i18n }}">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</button>
</div>
@ -87,12 +57,7 @@
*ngFor="let f of folders"
[ngClass]="{ active: selectedFolder && f.node.id === selectedFolderId }"
>
<button
type="button"
[attr.aria-pressed]="selectedFolder && f.node.id === selectedFolderId"
appStopClick
(click)="selectFolder(f.node)"
>
<a href="#" appStopClick appBlurClick (click)="selectFolder(f.node)">
<i
*ngIf="f.children.length"
class="bwi bwi-fw"
@ -121,7 +86,7 @@
>
<i class="bwi bwi-pencil bwi-fw" aria-hidden="true"></i>
</span>
</button>
</a>
<ul class="bwi-ul" *ngIf="f.children.length && !isCollapsed(f.node)">
<ng-container
*ngTemplateOutlet="recursiveFolders; context: { $implicit: f.children }"
@ -142,13 +107,7 @@
*ngFor="let c of collections"
[ngClass]="{ active: c.node.id === selectedCollectionId }"
>
<button
*ngIf="c.children.length == 0"
type="button"
[attr.aria-pressed]="c.node.id === selectedCollectionId"
appStopClick
(click)="selectCollection(c.node)"
>
<a href="#" appStopClick appBlurClick (click)="selectCollection(c.node)">
<i
*ngIf="c.children.length"
class="bwi bwi-fw"
@ -167,7 +126,7 @@
aria-hidden="true"
></i>
&nbsp;{{ c.node.name }}
</button>
</a>
<ul class="bwi-ul" *ngIf="c.children.length && !isCollapsed(c.node)">
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: c.children }"

View File

@ -10,22 +10,23 @@
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<div
class="generated-wrapper monospaced"
class="password-wrapper monospaced"
appSelectCopy
[innerHTML]="h.password | colorPassword"
></div>
<span class="detail">{{ h.date | date: "medium" }}</span>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(h.password)"
role="button"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
<div class="box-content-row" *ngIf="!history.length">
@ -38,6 +39,7 @@
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
<div class="right">
<button
appBlurClick
type="button"
(click)="clear()"
class="danger"

View File

@ -0,0 +1,230 @@
<div
class="modal fade"
role="dialog"
aria-modal="true"
attr.aria-label="{{ 'generatePassword' | i18n }}"
>
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-body">
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="password-block">
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
</div>
<div class="box">
<div class="box-content condensed">
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="regenerate()">
<i class="bwi bwi-fw bwi-generate" aria-hidden="true"></i>
{{ "regeneratePassword" | i18n }}
</a>
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="copy()">
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i> {{ "copyPassword" | i18n }}
</a>
</div>
</div>
<div class="box">
<div class="box-header">
<button
type="button"
(click)="toggleOptions()"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
>
<i class="bwi bwi-plus-square" aria-hidden="true" [hidden]="showOptions"></i>
<i class="bwi bwi-minus-square" aria-hidden="true" [hidden]="!showOptions"></i>
{{ "options" | i18n }}
</button>
</div>
<div class="box-content condensed" [hidden]="!showOptions">
<div class="box-content-row box-content-row-radio">
<label class="sr-only radio-header">{{ "type" | i18n }}</label>
<div
class="radio-group text-default"
appBoxRow
name="PassTypeOptions"
*ngFor="let o of passTypeOptions"
>
<input
type="radio"
class="radio"
[(ngModel)]="options.type"
name="Type_{{ o.value }}"
id="type_{{ o.value }}"
[value]="o.value"
(change)="saveOptions()"
[checked]="options.type === o.value"
/>
<label class="unstyled" for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions" *ngIf="options.type === 'passphrase'">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="num-words">{{ "numWords" | i18n }}</label>
<input
id="num-words"
type="number"
min="3"
max="20"
(blur)="saveOptions()"
[(ngModel)]="options.numWords"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input
id="word-separator"
type="text"
maxlength="1"
(input)="saveOptions()"
[(ngModel)]="options.wordSeparator"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{ "capitalize" | i18n }}</label>
<input
id="capitalize"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.capitalize"
[disabled]="enforcedPolicyOptions?.capitalize"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="include-number">{{ "includeNumber" | i18n }}</label>
<input
id="include-number"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.includeNumber"
[disabled]="enforcedPolicyOptions?.includeNumber"
/>
</div>
</div>
</div>
<ng-container *ngIf="options.type === 'password'">
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-slider" appBoxRow>
<label for="length">{{ "length" | i18n }}</label>
<input
id="length"
type="number"
min="5"
max="128"
[(ngModel)]="options.length"
(blur)="saveOptions()"
/>
<input
id="lengthRange"
type="range"
min="5"
max="128"
step="1"
[(ngModel)]="options.length"
(change)="sliderChanged()"
(input)="sliderInput()"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="uppercase">A-Z</label>
<input
id="uppercase"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useUppercase"
[(ngModel)]="options.uppercase"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label>
<input
id="lowercase"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useLowercase"
[(ngModel)]="options.lowercase"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="numbers">0-9</label>
<input
id="numbers"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useNumbers"
[(ngModel)]="options.number"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="special">!@#$%^&*</label>
<input
id="special"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useSpecial"
[(ngModel)]="options.special"
/>
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-number">{{ "minNumbers" | i18n }}</label>
<input
id="min-number"
type="number"
min="0"
max="9"
(blur)="saveOptions()"
[(ngModel)]="options.minNumber"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-special">{{ "minSpecial" | i18n }}</label>
<input
id="min-special"
type="number"
min="0"
max="9"
(blur)="saveOptions()"
[(ngModel)]="options.minSpecial"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{ "ambiguous" | i18n }}</label>
<input
id="ambiguous"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="avoidAmbiguous"
/>
</div>
</div>
</div>
</ng-container>
</div>
<div class="modal-footer">
<button
type="button"
class="primary"
appBlurClick
*ngIf="showSelect"
(click)="select()"
appA11yTitle="{{ 'select' | i18n }}"
>
<i class="bwi bwi-lg bwi-fw bwi-check" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">
{{ (showSelect ? "cancel" : "close") | i18n }}
</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,20 @@
import { Component } from "@angular/core";
import { PasswordGeneratorComponent as BasePasswordGeneratorComponent } from "jslib-angular/components/password-generator.component";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Component({
selector: "app-password-generator",
templateUrl: "password-generator.component.html",
})
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
constructor(
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService
) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
}

View File

@ -15,15 +15,16 @@
<span class="detail">{{ h.lastUsedDate | date: "medium" }}</span>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(h.password)"
role="button"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
<div class="box-content-row" *ngIf="!history.length">

View File

@ -54,6 +54,7 @@
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"

View File

@ -37,8 +37,7 @@
(onCancelled)="cancelledAddEdit($event)"
(onShareCipher)="shareCipher($event)"
(onEditCollections)="cipherCollections($event)"
(onGeneratePassword)="openGenerator(true, true)"
(onGenerateUsername)="openGenerator(true, false)"
(onGeneratePassword)="openPasswordGenerator(true)"
>
</app-vault-add-edit>
<div
@ -66,7 +65,7 @@
>
</app-vault-groupings>
</div>
<ng-template #generator></ng-template>
<ng-template #passwordGenerator></ng-template>
<ng-template #attachments></ng-template>
<ng-template #collections></ng-template>
<ng-template #share></ng-template>

View File

@ -35,8 +35,8 @@ import { AttachmentsComponent } from "./attachments.component";
import { CiphersComponent } from "./ciphers.component";
import { CollectionsComponent } from "./collections.component";
import { FolderAddEditComponent } from "./folder-add-edit.component";
import { GeneratorComponent } from "./generator.component";
import { GroupingsComponent } from "./groupings.component";
import { PasswordGeneratorComponent } from "./password-generator.component";
import { PasswordHistoryComponent } from "./password-history.component";
import { ShareComponent } from "./share.component";
import { ViewComponent } from "./view.component";
@ -52,8 +52,8 @@ export class VaultComponent implements OnInit, OnDestroy {
@ViewChild(AddEditComponent) addEditComponent: AddEditComponent;
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
@ViewChild("generator", { read: ViewContainerRef, static: true })
generatorModalRef: ViewContainerRef;
@ViewChild("passwordGenerator", { read: ViewContainerRef, static: true })
passwordGeneratorModalRef: ViewContainerRef;
@ViewChild("attachments", { read: ViewContainerRef, static: true })
attachmentsModalRef: ViewContainerRef;
@ViewChild("passwordHistory", { read: ViewContainerRef, static: true })
@ -120,8 +120,8 @@ export class VaultComponent implements OnInit, OnDestroy {
(document.querySelector("#search") as HTMLInputElement).select();
detectChanges = false;
break;
case "openGenerator":
await this.openGenerator(false);
case "openPasswordGenerator":
await this.openPasswordGenerator(false);
break;
case "syncCompleted":
await this.load();
@ -599,38 +599,28 @@ export class VaultComponent implements OnInit, OnDestroy {
this.go();
}
async openGenerator(comingFromAddEdit: boolean, passwordType = true) {
async openPasswordGenerator(showSelect: boolean) {
if (this.modal != null) {
this.modal.close();
}
const cipher = this.addEditComponent?.cipher;
const loginType = cipher != null && cipher.type === CipherType.Login && cipher.login != null;
const [modal, childComponent] = await this.modalService.openViewRef(
GeneratorComponent,
this.generatorModalRef,
(comp) => {
comp.comingFromAddEdit = comingFromAddEdit;
if (comingFromAddEdit) {
comp.type = passwordType ? "password" : "username";
if (loginType && cipher.login.hasUris && cipher.login.uris[0].hostname != null) {
comp.usernameWebsite = cipher.login.uris[0].hostname;
}
}
}
PasswordGeneratorComponent,
this.passwordGeneratorModalRef,
(comp) => (comp.showSelect = showSelect)
);
this.modal = modal;
childComponent.onSelected.subscribe((value: string) => {
childComponent.onSelected.subscribe((password: string) => {
this.modal.close();
if (loginType) {
if (
this.addEditComponent != null &&
this.addEditComponent.cipher != null &&
this.addEditComponent.cipher.type === CipherType.Login &&
this.addEditComponent.cipher.login != null
) {
this.addEditComponent.markPasswordAsDirty();
if (passwordType) {
this.addEditComponent.cipher.login.password = value;
} else {
this.addEditComponent.cipher.login.username = value;
}
this.addEditComponent.cipher.login.password = password;
}
});

View File

@ -35,23 +35,24 @@
</div>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
*ngIf="field.type === fieldType.Hidden && cipher.viewPassword"
(click)="toggleFieldValue(field)"
role="button"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !field.showValue, 'bwi-eye-slash': field.showValue }"
></i>
</button>
<button
type="button"
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyValue' | i18n }}"
*ngIf="
@ -63,9 +64,10 @@
(click)="
copy(field.value, 'value', field.type === fieldType.Hidden ? 'H_Field' : 'Field')
"
role="button"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
</div>

View File

@ -22,15 +22,16 @@
{{ cipher.login.username }}
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyUsername' | i18n }}"
(click)="copy(cipher.login.username, 'username', 'Username')"
role="button"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.password">
@ -46,7 +47,7 @@
</div>
<div
*ngIf="showPassword"
class="monospaced generated-wrapper"
class="monospaced password-wrapper"
appSelectCopy
[innerHTML]="cipher.login.password | colorPassword"
></div>
@ -56,6 +57,7 @@
type="button"
#checkPasswordBtn
class="row-btn btn"
appBlurClick
appA11yTitle="{{ 'checkPassword' | i18n }}"
(click)="checkPassword()"
[appApiAction]="checkPasswordPromise"
@ -72,29 +74,30 @@
aria-hidden="true"
></i>
</button>
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword()"
role="button"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
<button
type="button"
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(cipher.login.password, 'password', 'Password')"
role="button"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
<div
@ -127,15 +130,16 @@
</svg>
</span>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')"
role="button"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
</div>
@ -148,37 +152,34 @@
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.number">
<div class="row-main">
<span class="row-label">{{ "number" | i18n }}</span>
<span *ngIf="!showCardNumber" class="monospaced">{{
cipher.card.maskedNumber | creditCardNumber: cipher.card.brand
}}</span>
<span *ngIf="showCardNumber" class="monospaced">{{
cipher.card.number | creditCardNumber: cipher.card.brand
}}</span>
<span *ngIf="!showCardNumber" class="monospaced">{{ cipher.card.maskedNumber }}</span>
<span *ngIf="showCardNumber" class="monospaced">{{ cipher.card.number }}</span>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showCardNumber"
(click)="toggleCardNumber()"
role="button"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showCardNumber, 'bwi-eye-slash': showCardNumber }"
></i>
</button>
<button
type="button"
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyNumber' | i18n }}"
(click)="copy(cipher.card.number, 'number', 'Card Number')"
role="button"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
<div class="box-content-row" *ngIf="cipher.card.brand">
@ -196,29 +197,30 @@
<span *ngIf="showCardCode" class="monospaced">{{ cipher.card.code }}</span>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showCardCode"
(click)="toggleCardCode()"
role="button"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showCardCode, 'bwi-eye-slash': showCardCode }"
></i>
</button>
<button
type="button"
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copySecurityCode' | i18n }}"
(click)="copy(cipher.card.code, 'securityCode', 'Security Code')"
role="button"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
</div>
@ -284,25 +286,27 @@
<span title="{{ u.uri }}">{{ u.hostOrUri }}</span>
</div>
<div class="action-buttons">
<button
type="button"
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'launch' | i18n }}"
*ngIf="u.canLaunch"
(click)="launch(u)"
role="button"
>
<i class="bwi bwi-lg bwi-share-square" aria-hidden="true"></i>
</button>
<button
type="button"
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyUri' | i18n }}"
(click)="copy(u.uri, u.isWebsite ? 'website' : 'uri', 'URI')"
role="button"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
</div>
@ -327,11 +331,12 @@
{{ "attachments" | i18n }}
</div>
<div class="box-content">
<button
type="button"
<a
class="box-content-row box-content-row-flex text-default"
*ngFor="let attachment of cipher.attachments"
href="#"
appStopClick
appBlurCLick
(click)="downloadAttachment(attachment)"
>
<span class="row-main">{{ attachment.fileName }}</span>
@ -346,7 +351,7 @@
*ngIf="attachment.downloading"
aria-hidden="true"
></i>
</button>
</a>
</div>
</div>
<div class="box">
@ -361,14 +366,15 @@
</div>
<div *ngIf="cipher.hasPasswordHistory">
<b class="font-weight-semibold">{{ "passwordHistory" | i18n }}:</b>
<button
type="button"
<a
href="#"
(click)="viewHistory()"
appStopClick
role="button"
appA11yTitle="{{ 'passwordHistory' | i18n }}, {{ cipher.passwordHistory.length }}"
>
<span aria-hidden="true">{{ cipher.passwordHistory.length }}</span>
</button>
</a>
</div>
</div>
</div>
@ -376,6 +382,7 @@
</div>
<div class="footer" *ngIf="cipher">
<button
appBlurClick
class="primary"
(click)="edit()"
appA11yTitle="{{ 'edit' | i18n }}"
@ -384,6 +391,7 @@
<i class="bwi bwi-pencil bwi-fw bwi-lg" aria-hidden="true"></i>
</button>
<button
appBlurClick
class="primary"
(click)="restore()"
appA11yTitle="{{ 'restore' | i18n }}"
@ -392,6 +400,7 @@
<i class="bwi bwi-undo bwi-fw bwi-lg" aria-hidden="true"></i>
</button>
<button
appBlurClick
class="primary"
*ngIf="!cipher?.organizationId && !cipher.isDeleted"
(click)="clone()"
@ -401,6 +410,7 @@
</button>
<div class="right">
<button
appBlurClick
type="button"
(click)="delete()"
class="danger"

View File

@ -369,12 +369,6 @@
"overwritePasswordConfirmation": {
"message": "Is u seker u wil oor die huidige wagwoord skryf?"
},
"overwriteUsername": {
"message": "Overwrite Username"
},
"overwriteUsernameConfirmation": {
"message": "Are you sure you want to overwrite the current username?"
},
"noneFolder": {
"message": "Geen Vouer",
"description": "This is the folder for uncategorized items"
@ -1194,12 +1188,7 @@
"message": "Hierdie wagwoord is in geen bekende databreuke gevind nie. Dit behoort veilig vir gebruik te wees."
},
"baseDomain": {
"message": "Basisdomein",
"description": "Domain name. Ex. website.com"
},
"domainName": {
"message": "Domain Name",
"description": "Domain name. Ex. website.com"
"message": "Basisdomein"
},
"host": {
"message": "Gasheer",
@ -1839,47 +1828,5 @@
"example": "name@example.com"
}
}
},
"generator": {
"message": "Generator"
},
"whatWouldYouLikeToGenerate": {
"message": "What would you like to generate?"
},
"passwordType": {
"message": "Password Type"
},
"regenerateUsername": {
"message": "Regenerate Username"
},
"generateUsername": {
"message": "Generate Username"
},
"usernameType": {
"message": "Username Type"
},
"plusAddressedEmail": {
"message": "Plus Addressed Email"
},
"plusAddressedEmailDesc": {
"message": "Use your email provider's sub-addressing capabilities."
},
"catchallEmail": {
"message": "Catch-all Email"
},
"catchallEmailDesc": {
"message": "Use your domain's configured catch-all inbox."
},
"random": {
"message": "Random"
},
"randomWord": {
"message": "Random Word"
},
"websiteName": {
"message": "Website Name"
},
"service": {
"message": "Service"
}
}

View File

@ -369,12 +369,6 @@
"overwritePasswordConfirmation": {
"message": "Hazırkı parolun üzərinə yazmaq istədiyinizə əminsiniz?"
},
"overwriteUsername": {
"message": "İstifadəçi adının üzərinə yaz"
},
"overwriteUsernameConfirmation": {
"message": "Hazırkı istifadəçi adının üzərinə yazmaq istədiyinizə əminsiniz?"
},
"noneFolder": {
"message": "Qovluq yoxdur",
"description": "This is the folder for uncategorized items"
@ -1194,12 +1188,7 @@
"message": "Bu parol, məlumat pozuntularında qeydə alınmayıb. Rahatlıqla istifadə edə bilərsiniz."
},
"baseDomain": {
"message": "Baza domeni",
"description": "Domain name. Ex. website.com"
},
"domainName": {
"message": "Domen adı",
"description": "Domain name. Ex. website.com"
"message": "Baza domeni"
},
"host": {
"message": "Host",
@ -1839,47 +1828,5 @@
"example": "name@example.com"
}
}
},
"generator": {
"message": "Yaradıcı"
},
"whatWouldYouLikeToGenerate": {
"message": "Nə yaratmaq istəyirsiniz?"
},
"passwordType": {
"message": "Parol növü"
},
"regenerateUsername": {
"message": "İstifadəçi adını yenidən yarat"
},
"generateUsername": {
"message": "İstifadəçi adı yarat"
},
"usernameType": {
"message": "İstifadəçi adı növü"
},
"plusAddressedEmail": {
"message": "Plyus ünvanlı e-poçt"
},
"plusAddressedEmailDesc": {
"message": "E-poçt təchizatçınızın alt ünvan özəlliklərini istifadə et."
},
"catchallEmail": {
"message": "Catch-all E-poçt"
},
"catchallEmailDesc": {
"message": "Domeninizin konfiqurasiya edilmiş hamısını yaxalama gələn qutusunu istifadə edin."
},
"random": {
"message": "Təsadüfi"
},
"randomWord": {
"message": "Təsadüfi söz"
},
"websiteName": {
"message": "Veb sayt adı"
},
"service": {
"message": "Xidmət"
}
}

View File

@ -369,12 +369,6 @@
"overwritePasswordConfirmation": {
"message": "Вы ўпэўнены, што хочаце перазапісаць бягучы пароль?"
},
"overwriteUsername": {
"message": "Overwrite Username"
},
"overwriteUsernameConfirmation": {
"message": "Are you sure you want to overwrite the current username?"
},
"noneFolder": {
"message": "Без папкі",
"description": "This is the folder for uncategorized items"
@ -1194,12 +1188,7 @@
"message": "Гэты пароль не быў знойдзены ў вядомых базах уцечак. Можна працягваць яго выкарыстоўваць."
},
"baseDomain": {
"message": "Асноўны дамен",
"description": "Domain name. Ex. website.com"
},
"domainName": {
"message": "Domain Name",
"description": "Domain name. Ex. website.com"
"message": "Асноўны дамен"
},
"host": {
"message": "Хост",
@ -1839,47 +1828,5 @@
"example": "name@example.com"
}
}
},
"generator": {
"message": "Generator"
},
"whatWouldYouLikeToGenerate": {
"message": "What would you like to generate?"
},
"passwordType": {
"message": "Password Type"
},
"regenerateUsername": {
"message": "Regenerate Username"
},
"generateUsername": {
"message": "Generate Username"
},
"usernameType": {
"message": "Username Type"
},
"plusAddressedEmail": {
"message": "Plus Addressed Email"
},
"plusAddressedEmailDesc": {
"message": "Use your email provider's sub-addressing capabilities."
},
"catchallEmail": {
"message": "Catch-all Email"
},
"catchallEmailDesc": {
"message": "Use your domain's configured catch-all inbox."
},
"random": {
"message": "Random"
},
"randomWord": {
"message": "Random Word"
},
"websiteName": {
"message": "Website Name"
},
"service": {
"message": "Service"
}
}

View File

@ -369,12 +369,6 @@
"overwritePasswordConfirmation": {
"message": "Сигурни ли сте, че искате да обновите текущата парола?"
},
"overwriteUsername": {
"message": "Замяна на потребителското име"
},
"overwriteUsernameConfirmation": {
"message": "Наостина ли искате да замените текущото потребителско име?"
},
"noneFolder": {
"message": "Няма папка",
"description": "This is the folder for uncategorized items"
@ -1194,12 +1188,7 @@
"message": "Паролата не е била разкрита в известните пробиви. Засега ползването ѝ изглежда безопасно."
},
"baseDomain": {
"message": "Основен домейн",
"description": "Domain name. Ex. website.com"
},
"domainName": {
"message": "Име на домейн",
"description": "Domain name. Ex. website.com"
"message": "Основен домейн"
},
"host": {
"message": "Сървър",
@ -1839,47 +1828,5 @@
"example": "name@example.com"
}
}
},
"generator": {
"message": "Генератор"
},
"whatWouldYouLikeToGenerate": {
"message": "Какво бихте искали да генерирате?"
},
"passwordType": {
"message": "Тип парола"
},
"regenerateUsername": {
"message": "Повторно генериране на потр. име"
},
"generateUsername": {
"message": "Генериране на потр. име"
},
"usernameType": {
"message": "Тип потребителско име"
},
"plusAddressedEmail": {
"message": "Plus Addressed Email"
},
"plusAddressedEmailDesc": {
"message": "Използвайте възможностите за под-адресиране на е-поща на своя доставчик."
},
"catchallEmail": {
"message": "Catch-all Email"
},
"catchallEmailDesc": {
"message": "Use your domain's configured catch-all inbox."
},
"random": {
"message": "Произволно"
},
"randomWord": {
"message": "Произволна дума"
},
"websiteName": {
"message": "Име на уеб сайт"
},
"service": {
"message": "Услуга"
}
}

View File

@ -369,12 +369,6 @@
"overwritePasswordConfirmation": {
"message": "আপনি কি নিশ্চিত যে আপনি বর্তমান পাসওয়ার্ডটি ওভাররাইট করতে চান?"
},
"overwriteUsername": {
"message": "Overwrite Username"
},
"overwriteUsernameConfirmation": {
"message": "Are you sure you want to overwrite the current username?"
},
"noneFolder": {
"message": "কোন ফোল্ডার নেই",
"description": "This is the folder for uncategorized items"
@ -1194,12 +1188,7 @@
"message": "এই পাসওয়ার্ডটি কোনও পরিচিত তথ্য লঙ্ঘনে পাওয়া যায় নি। এটি ব্যবহার করা নিরাপদ হওয়া উচিত।"
},
"baseDomain": {
"message": "ভিত্তি ডোমেইন",
"description": "Domain name. Ex. website.com"
},
"domainName": {
"message": "Domain Name",
"description": "Domain name. Ex. website.com"
"message": "ভিত্তি ডোমেইন"
},
"host": {
"message": "নিয়ন্ত্রণকর্তা",
@ -1839,47 +1828,5 @@
"example": "name@example.com"
}
}
},
"generator": {
"message": "Generator"
},
"whatWouldYouLikeToGenerate": {
"message": "What would you like to generate?"
},
"passwordType": {
"message": "Password Type"
},
"regenerateUsername": {
"message": "Regenerate Username"
},
"generateUsername": {
"message": "Generate Username"
},
"usernameType": {
"message": "Username Type"
},
"plusAddressedEmail": {
"message": "Plus Addressed Email"
},
"plusAddressedEmailDesc": {
"message": "Use your email provider's sub-addressing capabilities."
},
"catchallEmail": {
"message": "Catch-all Email"
},
"catchallEmailDesc": {
"message": "Use your domain's configured catch-all inbox."
},
"random": {
"message": "Random"
},
"randomWord": {
"message": "Random Word"
},
"websiteName": {
"message": "Website Name"
},
"service": {
"message": "Service"
}
}

View File

@ -369,12 +369,6 @@
"overwritePasswordConfirmation": {
"message": "Da li ste sigurni da želite da zamijenite trenutnu lozinku?"
},
"overwriteUsername": {
"message": "Overwrite Username"
},
"overwriteUsernameConfirmation": {
"message": "Are you sure you want to overwrite the current username?"
},
"noneFolder": {
"message": "Nema foldera",
"description": "This is the folder for uncategorized items"
@ -461,112 +455,112 @@
"message": "Maksimalna veličina datoteke je 500 MB."
},
"updateKey": {
"message": "Ovu funkciju ne možete koristiti dok ne ažurirate ključ za šifrovanje."
"message": "You cannot use this feature until you update your encryption key."
},
"editedFolder": {
"message": "Uređen folder"
"message": "Edited folder"
},
"addedFolder": {
"message": "Folder dodan"
"message": "Added folder"
},
"deleteFolderConfirmation": {
"message": "Sigurno želiš izbrisati ovaj Folder?"
"message": "Are you sure you want to delete this folder?"
},
"deletedFolder": {
"message": "Folder izbrisan"
"message": "Deleted folder"
},
"loginOrCreateNewAccount": {
"message": "Prijavite se ili napravite novi račun da biste pristupili svom sigurnom trezoru."
"message": "Log in or create a new account to access your secure vault."
},
"createAccount": {
"message": "Napravi račun"
"message": "Create Account"
},
"logIn": {
"message": "Prijavite se"
"message": "Log In"
},
"submit": {
"message": "Potvrdi"
"message": "Submit"
},
"masterPass": {
"message": "Master/glavna šifra"
"message": "Master Password"
},
"masterPassDesc": {
"message": "Glavna lozinka je lozinka koju koristite za pristup Vašem trezoru. Veoma je važno da ne zaboravite glavnu lozinku. Ne postoji način da povratite lozinku u slučaju da je zaboravite."
"message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it."
},
"masterPassHintDesc": {
"message": "Nagoveštaj glavne lozinke može Vam pomoći da zapamtite lozinku ako je zaboravite."
"message": "A master password hint can help you remember your password if you forget it."
},
"reTypeMasterPass": {
"message": "Ponovo unesite glavnu lozinku"
"message": "Re-type Master Password"
},
"masterPassHint": {
"message": "Nagovještaj za glavnu lozinku (opcionalno)"
"message": "Master Password Hint (optional)"
},
"settings": {
"message": "Postavke"
"message": "Settings"
},
"passwordHint": {
"message": "Nagovještaj lozinke"
"message": "Password Hint"
},
"enterEmailToGetHint": {
"message": "Unesite E-Mail adresu Vašeg računa da biste dobili nagovještaj o mogućoj glavnoj lozinki."
"message": "Enter your account email address to receive your master password hint."
},
"getMasterPasswordHint": {
"message": "Dobijte nagovještaj glavne lozinke"
"message": "Get master password hint"
},
"emailRequired": {
"message": "Potrebna je email adresa."
"message": "Email address is required."
},
"invalidEmail": {
"message": "Neispravna email adresa."
"message": "Invalid email address."
},
"masterPassRequired": {
"message": "Potrebna je glavna lozinka."
"message": "Master password is required."
},
"masterPassLength": {
"message": "Glavna lozinka mora imati najmanje 8 znakova."
"message": "Master password must be at least 8 characters long."
},
"masterPassDoesntMatch": {
"message": "Potvrda glavne lozinke se ne podudara."
"message": "Master password confirmation does not match."
},
"newAccountCreated": {
"message": "Tvoj novi račun je kreiran! Sada se možeš prijaviti."
"message": "Your new account has been created! You may now log in."
},
"masterPassSent": {
"message": "Poslali smo vam e-mail sa podsjetnikom za glavnu lozinku."
"message": "We've sent you an email with your master password hint."
},
"unexpectedError": {
"message": "Neočekivana greška se dogodila."
"message": "An unexpected error has occurred."
},
"itemInformation": {
"message": "Informacije o stavki"
"message": "Item Information"
},
"noItemsInList": {
"message": "Nema podataka za prikazati."
"message": "There are no items to list."
},
"sendVerificationCode": {
"message": "Pošalji verifikacijski kod na E-Mail"
"message": "Send a verification code to your email"
},
"sendCode": {
"message": "Pošalji kod"
"message": "Send Code"
},
"codeSent": {
"message": "Kod poslan"
"message": "Code Sent"
},
"verificationCode": {
"message": "Verifikacioni kod"
"message": "Verification Code"
},
"confirmIdentity": {
"message": "Potvrdite lozinku za nastavak."
"message": "Confirm your identity to continue."
},
"verificationCodeRequired": {
"message": "Verifikacijski kod je neophodan."
"message": "Verification code is required."
},
"invalidVerificationCode": {
"message": "Neispravan verifikacijski kod"
"message": "Invalid verification code"
},
"continue": {
"message": "Nastavi"
"message": "Continue"
},
"enterVerificationCodeApp": {
"message": "Unesite 6-ocifreni verifikacioni kod iz Vaše aplikacije za potvrdu autentičnosti."
@ -716,87 +710,87 @@
"message": "Da li ste sigurni da želite da se odjavite?"
},
"logOut": {
"message": "Odjavi se"
"message": "Log Out"
},
"addNewLogin": {
"message": "Dodaj novu prijavu"
"message": "Add New Login"
},
"addNewItem": {
"message": "Dodaj novu stavku"
"message": "Add New Item"
},
"addNewFolder": {
"message": "Dodajte novi folder"
"message": "Add New Folder"
},
"view": {
"message": "Prikaz"
"message": "View"
},
"account": {
"message": "Račun"
"message": "Account"
},
"loading": {
"message": "Učitavanje..."
"message": "Loading..."
},
"lockVault": {
"message": "Zaključaj trezor"
"message": "Lock Vault"
},
"passwordGenerator": {
"message": "Generator lozinki"
"message": "Password Generator"
},
"contactUs": {
"message": "Kontaktirajte nas"
"message": "Contact Us"
},
"getHelp": {
"message": "Potraži pomoć"
"message": "Get Help"
},
"fileBugReport": {
"message": "Podnesite izvještaj o greški"
"message": "File a Bug Report"
},
"blog": {
"message": "Blog"
},
"followUs": {
"message": "Pratite nas"
"message": "Follow Us"
},
"syncVault": {
"message": "Sinhronizujte trezor sada"
"message": "Sync Vault"
},
"changeMasterPass": {
"message": "Promijenite glavnu lozinku"
"message": "Change Master Password"
},
"changeMasterPasswordConfirmation": {
"message": "Možete da promjenite svoju glavnu lozinku na bitwarden.com web trezoru. Da li želite da posjetite web stranicu sada?"
"message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?"
},
"fingerprintPhrase": {
"message": "Jedinstvena fraza",
"message": "Fingerprint Phrase",
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
},
"yourAccountsFingerprint": {
"message": "Jedinstvena fraza tvog računa",
"message": "Your account's fingerprint phrase",
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
},
"goToWebVault": {
"message": "Idi na web trezor"
"message": "Go To Web Vault"
},
"getMobileApp": {
"message": "Preuzmi mobilnu aplikaciju"
"message": "Get Mobile App"
},
"getBrowserExtension": {
"message": "Preuzmi proširenje za preglednik"
"message": "Get Browser Extension"
},
"syncingComplete": {
"message": "Sinhronizacija je završena"
"message": "Syncing complete"
},
"syncingFailed": {
"message": "Sinhronizacija nije uspjela"
"message": "Syncing failed"
},
"yourVaultIsLocked": {
"message": "Vaš trezor je zaključan. Potvrdite glavnu lozinku da nastavite."
"message": "Your vault is locked. Verify your identity to continue."
},
"unlock": {
"message": "Otključaj"
"message": "Unlock"
},
"loggedInAsOn": {
"message": "Prijavljen kao $EMAIL$ na $HOSTNAME$.",
"message": "Logged in as $EMAIL$ on $HOSTNAME$.",
"placeholders": {
"email": {
"content": "$1",
@ -809,81 +803,81 @@
}
},
"invalidMasterPassword": {
"message": "Neispravna glavna lozinka"
"message": "Invalid master password"
},
"twoStepLoginConfirmation": {
"message": "Prijava u dva koraka čini Vaš račun sigurnijim tako što zahtjeva da verifikujete svoje podatke pomoću drugog uređaja, kao što su sigurnosni ključ, aplikacija za autentifikaciju, SMS, telefonski poziv ili E-Mail. Prijavljivanje u dva koraka može se omogućiti na bitwarden.com web trezoru. Da li želite da posjetite web stranicu sada?"
"message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?"
},
"twoStepLogin": {
"message": "Prijava u dva koraka"
"message": "Two-step Login"
},
"vaultTimeout": {
"message": "Vremensko ograničenje trezora"
"message": "Vault Timeout"
},
"vaultTimeoutDesc": {
"message": "Odaberi kada će za koliko vremena će isteći aktivnost trezora i biti izvršena odabrana radnja."
"message": "Choose when your vault will timeout and perform the selected action."
},
"immediately": {
"message": "Odmah"
"message": "Immediately"
},
"tenSeconds": {
"message": "10 sekundi"
"message": "10 seconds"
},
"twentySeconds": {
"message": "20 sekundi"
"message": "20 seconds"
},
"thirtySeconds": {
"message": "30 sekundi"
"message": "30 seconds"
},
"oneMinute": {
"message": "1 minuta"
"message": "1 minute"
},
"twoMinutes": {
"message": "2 minute"
"message": "2 minutes"
},
"fiveMinutes": {
"message": "5 minuta"
"message": "5 minutes"
},
"fifteenMinutes": {
"message": "15 minuta"
"message": "15 minutes"
},
"thirtyMinutes": {
"message": "30 minuta"
"message": "30 minutes"
},
"oneHour": {
"message": "1 sat"
"message": "1 hour"
},
"fourHours": {
"message": "4 sata"
"message": "4 hours"
},
"onIdle": {
"message": "Na sistemskoj pripravnosti"
"message": "On System Idle"
},
"onSleep": {
"message": "Na sistemskom mirovanju"
"message": "On System Sleep"
},
"onLocked": {
"message": "Na sistemskom zaključavanju"
"message": "On System Lock"
},
"onRestart": {
"message": "Kod ponovnog pokretanja"
"message": "On Restart"
},
"never": {
"message": "Nikad"
"message": "Never"
},
"security": {
"message": "Sigurnost"
"message": "Security"
},
"clearClipboard": {
"message": "Očisti međumemoriju",
"message": "Clear Clipboard",
"description": "Clipboard is the operating system thing where you copy/paste data to on your device."
},
"clearClipboardDesc": {
"message": "Automatski očistiti kopirane vrijednosti iz vaše međumemorije.",
"message": "Automatically clear copied values from your clipboard.",
"description": "Clipboard is the operating system thing where you copy/paste data to on your device."
},
"disableFavicon": {
"message": "Onemogućite ikone web lokacije"
"message": "Disable Website Icons"
},
"disableFaviconDesc": {
"message": "Website Icons provide a recognizable image next to each login item in your vault."
@ -922,61 +916,61 @@
"message": "Start To Tray Icon"
},
"startToTrayDesc": {
"message": "Kada se aplikacija prvi put pokrene, prikaži samo ikonu u sistemskoj traci."
"message": "When the application is first started, only show an icon in the system tray."
},
"startToMenuBar": {
"message": "Pokreni u traci menija"
"message": "Start to menu bar"
},
"startToMenuBarDesc": {
"message": "Kada se aplikacija prvi put pokrene, prikaži samo ikonu u sistemskoj traci."
"message": "When the application is first started, only show an icon in the menu bar."
},
"openAtLogin": {
"message": "Automatsko pokretanje prilikom prijavljivanja"
"message": "Start automatically on login"
},
"openAtLoginDesc": {
"message": "Automatski pokreni Bitwarden desktop aplikaciju kod prijave."
"message": "Start the Bitwarden Desktop application automatically on login."
},
"alwaysShowDock": {
"message": "Uvijek prikaži u Dock-u"
"message": "Always show in the Dock"
},
"alwaysShowDockDesc": {
"message": "Prikaži Bitwareden ikonu u Docku čak i kada je minimiziran u traku s izbornicima."
"message": "Show the Bitwarden icon in the Dock even when minimized to the menu bar."
},
"confirmTrayTitle": {
"message": "Potvrdi onemogućavanje trake"
"message": "Confirm disable tray"
},
"confirmTrayDesc": {
"message": "Ako onemogućite ovo podešavanje sva ostala podešavanja vezana za sistemsku traku će također biti onemogućena."
"message": "Disabling this setting will also disable all other tray related settings."
},
"language": {
"message": "Jezik"
"message": "Language"
},
"languageDesc": {
"message": "Promijeni jezik aplikacije. Potrebno je ponovno pokretanje."
"message": "Change the language used by the application. Restart is required."
},
"theme": {
"message": "Tema"
"message": "Theme"
},
"themeDesc": {
"message": "Promjeni boju teme u aplikaciji."
"message": "Change the application's color theme."
},
"dark": {
"message": "Tamno",
"message": "Dark",
"description": "Dark color"
},
"light": {
"message": "Svijetlo",
"message": "Light",
"description": "Light color"
},
"copy": {
"message": "Kopiraj",
"message": "Copy",
"description": "Copy to clipboard"
},
"checkForUpdates": {
"message": "Provjeri ima li ažuriranja"
"message": "Check For Updates"
},
"version": {
"message": "Verzija $VERSION_NUM$",
"message": "Version $VERSION_NUM$",
"placeholders": {
"version_num": {
"content": "$1",
@ -985,10 +979,10 @@
}
},
"restartToUpdate": {
"message": "Ponovo pokreni za ažuriranje"
"message": "Restart To Update"
},
"restartToUpdateDesc": {
"message": "Verzija $VERSION_NUM$ je spremna za instalaciju. Moraš ponovno pokrenuti Bitwarden za dovršetak instalacije. Želiš li ponovo pokrenuti i ažurirati?",
"message": "Version $VERSION_NUM$ is ready to install. You must restart the application to complete the installation. Do you want to restart and update now?",
"placeholders": {
"version_num": {
"content": "$1",
@ -997,87 +991,87 @@
}
},
"updateAvailable": {
"message": "Dostupna su ažuriranja"
"message": "Update Available"
},
"updateAvailableDesc": {
"message": "Pronađeno je ažuriranje. Želiš li ga sada preuzeti?"
"message": "An update was found. Do you want to download it now?"
},
"restart": {
"message": "Ponovno pokreni"
"message": "Restart"
},
"later": {
"message": "Kasnije"
"message": "Later"
},
"noUpdatesAvailable": {
"message": "Trenutno nema ažuriranja. Već koristiš najnoviju verziju."
"message": "No updates are currently available. You are using the latest version."
},
"updateError": {
"message": "Greška pri ažuriranju"
"message": "Update Error"
},
"unknown": {
"message": "Nepoznato"
"message": "Unknown"
},
"copyUsername": {
"message": "Kopiraj korisničko ime"
"message": "Copy Username"
},
"copyNumber": {
"message": "Kopiraj broj",
"message": "Copy Number",
"description": "Copy credit card number"
},
"copySecurityCode": {
"message": "Kopirajte sigurnosni kod",
"message": "Copy Security Code",
"description": "Copy credit card security code (CVV)"
},
"premiumMembership": {
"message": "Premium članstvo"
"message": "Premium Membership"
},
"premiumManage": {
"message": "Upravljaj članstvom"
"message": "Manage Membership"
},
"premiumManageAlert": {
"message": "Svojim članstvom možeš upravljati na bitwarden.com web trezoru. Želiš li sada posjetiti web stranicu?"
"message": "You can manage your membership on the bitwarden.com web vault. Do you want to visit the website now?"
},
"premiumRefresh": {
"message": "Osvježi status članstva"
"message": "Refresh Membership"
},
"premiumNotCurrentMember": {
"message": "Trenutno nisi premium član."
"message": "You are not currently a premium member."
},
"premiumSignUpAndGet": {
"message": "Prijavi se za premium članstvo, čime dobijaš:"
"message": "Sign up for a premium membership and get:"
},
"premiumSignUpStorage": {
"message": "1 GB šifriranog prostora za pohranu podataka."
"message": "1 GB encrypted storage for file attachments."
},
"premiumSignUpTwoStep": {
"message": "Dodatne mogućnosti za prijavu u dva koraka kao što su YubiKey, FIDO U2F i Duo."
"message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo."
},
"premiumSignUpReports": {
"message": "Higijenu lozinki, zdravlje računa i izvještaje o krađi podataka radi zaštite svojeg trezora."
"message": "Password hygiene, account health, and data breach reports to keep your vault safe."
},
"premiumSignUpTotp": {
"message": "Generator TOTP kontrolnog koda (2FA) za prijave u tvom trezoru."
"message": "TOTP verification code (2FA) generator for logins in your vault."
},
"premiumSignUpSupport": {
"message": "Prioritetnu korisničku podršku."
"message": "Priority customer support."
},
"premiumSignUpFuture": {
"message": "Sve buduće premium značajke. Uskoro više!"
"message": "All future premium features. More coming soon!"
},
"premiumPurchase": {
"message": "Kupi premium članstvo"
"message": "Purchase Premium"
},
"premiumPurchaseAlert": {
"message": "Svojim članstvom možeš upravljati na bitwarden.com web trezoru. Želiš li sada posjetiti web stranicu?"
"message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?"
},
"premiumCurrentMember": {
"message": "Ti si premium član!"
"message": "You are a premium member!"
},
"premiumCurrentMemberThanks": {
"message": "Hvala ti što podupireš Bitwarden."
"message": "Thank you for supporting Bitwarden."
},
"premiumPrice": {
"message": "Sve za samo $PRICE$ /godišnje!",
"message": "All for just $PRICE$ /year!",
"placeholders": {
"price": {
"content": "$1",
@ -1086,13 +1080,13 @@
}
},
"refreshComplete": {
"message": "Osvježavanje završeno"
"message": "Refresh complete"
},
"passwordHistory": {
"message": "Historija uređivanja lozinke"
"message": "Password History"
},
"clear": {
"message": "Obriši",
"message": "Clear",
"description": "To clear something out. example: To clear browser history."
},
"noPasswordsInList": {
@ -1194,12 +1188,7 @@
"message": "This password was not found in any known data breaches. It should be safe to use."
},
"baseDomain": {
"message": "Base domain",
"description": "Domain name. Ex. website.com"
},
"domainName": {
"message": "Domain Name",
"description": "Domain name. Ex. website.com"
"message": "Base domain"
},
"host": {
"message": "Host",
@ -1278,34 +1267,34 @@
"message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account."
},
"noOrganizationsList": {
"message": "Ne pripadaš niti jednoj organizaciji. Organizacije omogućuju sigurno dijeljenje stavki s drugim korisnicima."
"message": "You do not belong to any organizations. Organizations allow you to securely share items with other users."
},
"noCollectionsInList": {
"message": "Nema kolekcija za prikazati."
"message": "There are no collections to list."
},
"ownership": {
"message": "Vlasništvo"
"message": "Ownership"
},
"whoOwnsThisItem": {
"message": "Ko je vlasnik ove stavke?"
"message": "Who owns this item?"
},
"strong": {
"message": "Jaka",
"message": "Strong",
"description": "ex. A strong password. Scale: Weak -> Good -> Strong"
},
"good": {
"message": "Dobra",
"message": "Good",
"description": "ex. A good password. Scale: Weak -> Good -> Strong"
},
"weak": {
"message": "Slaba",
"message": "Weak",
"description": "ex. A weak password. Scale: Weak -> Good -> Strong"
},
"weakMasterPassword": {
"message": "Slaba glavna lozinka"
"message": "Weak Master Password"
},
"weakMasterPasswordDesc": {
"message": "Odabrana glavna lozinka je slaba. Trebaš koristiti jaču glavnu lozinku (ili frazu) kako bi tvoj Bitwarden račun bio pravilno zaštićen. Sigurno želiš koristiti ovakvu, slabu glavnu lozinku?"
"message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?"
},
"pin": {
"message": "PIN",
@ -1839,47 +1828,5 @@
"example": "name@example.com"
}
}
},
"generator": {
"message": "Generator"
},
"whatWouldYouLikeToGenerate": {
"message": "What would you like to generate?"
},
"passwordType": {
"message": "Password Type"
},
"regenerateUsername": {
"message": "Regenerate Username"
},
"generateUsername": {
"message": "Generate Username"
},
"usernameType": {
"message": "Username Type"
},
"plusAddressedEmail": {
"message": "Plus Addressed Email"
},
"plusAddressedEmailDesc": {
"message": "Use your email provider's sub-addressing capabilities."
},
"catchallEmail": {
"message": "Catch-all Email"
},
"catchallEmailDesc": {
"message": "Use your domain's configured catch-all inbox."
},
"random": {
"message": "Random"
},
"randomWord": {
"message": "Random Word"
},
"websiteName": {
"message": "Website Name"
},
"service": {
"message": "Service"
}
}

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