1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-05 23:53:21 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
github-actions[bot]
b0028bb41a Bump version to 2.10.0 (#252)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit 536f48b3c7)
2022-03-16 10:55:08 -06:00
Joseph Flinn
e581e1f6e4 Update hotfix release branch name to hotfix-rc (#247)
(cherry picked from commit ae05183aa3)
2022-03-09 12:53:46 -08:00
747 changed files with 16445 additions and 78233 deletions

View File

@@ -1,10 +1,9 @@
dist
build
build-cli
jslib
webpack.cli.js
webpack.main.js
webpack.renderer.js
**/node_modules
**/jest.config.js

View File

@@ -4,32 +4,8 @@
"browser": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"prettier"
],
"extends": ["./jslib/shared/eslintrc.json"],
"rules": {
"@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled
"@typescript-eslint/no-unused-vars": ["warn", { "args": "none" }],
"@typescript-eslint/explicit-member-accessibility": [
"error",
{
"accessibility": "no-public"
}
],
"@typescript-eslint/no-this-alias": [
"error",
{
"allowedNames": ["self"]
}
],
"no-console": "warn",
"import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package.
"import/order": [
"error",
{

15
.github/CODEOWNERS vendored
View File

@@ -1,15 +0,0 @@
# Please sort into logical groups with comment headers. Sort groups in order of specificity.
# For example, default owners should always be the first group.
# Sort lines alphabetically within these groups to avoid accidentally adding duplicates.
#
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# Default file owners.
* @bitwarden/team-admin-console-dev
# DevOps for Actions and other workflow changes.
.github/workflows @bitwarden/dept-devops
.github/secrets @bitwarden/dept-devops
# Multiple Owners
**/package.json

30
.github/renovate.json vendored
View File

@@ -1,30 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
":combinePatchMinorReleases",
":dependencyDashboard",
":maintainLockFilesWeekly",
":pinAllExceptPeerDependencies",
":prConcurrentLimit10",
":rebaseStalePrs",
":separateMajorReleases",
"group:monorepos",
"schedule:weekends"
],
"enabledManagers": ["github-actions", "npm"],
"commitMessagePrefix": "[deps]:",
"commitMessageTopic": "{{depName}}",
"packageRules": [
{
"groupName": "npm minor",
"matchManagers": ["npm"],
"matchUpdateTypes": ["minor", "patch"]
},
{
"matchFileNames": ["package.json"],
"description": "Admin Console owns general dependencies",
"reviewers": ["team:team-admin-console-dev"]
}
]
}

View File

@@ -2,16 +2,20 @@
name: Build
on:
pull_request: {}
workflow_dispatch: {}
push:
branches-ignore:
- 'l10n_master'
paths-ignore:
- '.github/workflows/**'
jobs:
cloc:
name: CLOC
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up CLOC
run: |
@@ -24,38 +28,36 @@ jobs:
setup:
name: Setup
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
outputs:
package_version: ${{ steps.retrieve-version.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Get Package Version
id: retrieve-version
run: |
PKG_VERSION=$(jq -r .version package.json)
echo "package_version=$PKG_VERSION" >> $GITHUB_OUTPUT
PKG_VERSION=$(jq -r .version src/package.json)
echo "::set-output name=package_version::$PKG_VERSION"
linux-cli:
name: Build Linux CLI
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
needs: setup
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
_PKG_FETCH_NODE_VERSION: 18.5.0
_PKG_FETCH_VERSION: 3.4
_PKG_FETCH_NODE_VERSION: 16.13.0
_PKG_FETCH_VERSION: 3.2
steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up Node
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '18'
node-version: '16'
- name: Update NPM
run: |
@@ -72,7 +74,7 @@ jobs:
- name: Keytar
run: |
keytarVersion=$(cat package.json | jq -r '.dependencies.keytar')
keytarVersion=$(cat src/package.json | jq -r '.dependencies.keytar')
keytarTar="keytar-v$keytarVersion-napi-v3-linux-x64.tar"
keytarTarGz="$keytarTar.gz"
@@ -89,16 +91,15 @@ jobs:
run: npm run dist:cli:lin
- name: Zip
run: zip -j dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip dist-cli/linux/bwdc keytar/linux/build/Release/keytar.node
run: |
zip -j ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip ./dist-cli/linux/bwdc ./keytar/linux/build/Release/keytar.node
- name: Create checksums
run: |
shasum -a 256 dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip | \
cut -d " " -f 1 > dist-cli/bwdc-linux-sha256-$_PACKAGE_VERSION.txt
sha256sum ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip | cut -d " " -f 1 > ./dist-cli/bwdc-linux-sha256-$_PACKAGE_VERSION.txt
- name: Version Test
run: |
sudo apt-get update
sudo apt install libsecret-1-0 dbus-x11 gnome-keyring
eval $(dbus-launch --sh-syntax)
@@ -119,14 +120,14 @@ jobs:
fi
- name: Upload Linux Zip to GitHub
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
path: ./dist-cli/bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
if-no-files-found: error
- name: Upload Linux checksum to GitHub
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
path: ./dist-cli/bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
@@ -135,22 +136,20 @@ jobs:
macos-cli:
name: Build Mac CLI
runs-on: macos-12
runs-on: macos-11
needs: setup
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
_PKG_FETCH_NODE_VERSION: 18.5.0
_PKG_FETCH_VERSION: 3.4
_PKG_FETCH_NODE_VERSION: 16.13.0
_PKG_FETCH_VERSION: 3.2
steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up Node
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '18'
node-version: '16'
- name: Update NPM
run: |
@@ -167,7 +166,7 @@ jobs:
- name: Keytar
run: |
keytarVersion=$(cat package.json | jq -r '.dependencies.keytar')
keytarVersion=$(cat src/package.json | jq -r '.dependencies.keytar')
keytarTar="keytar-v$keytarVersion-napi-v3-darwin-x64.tar"
keytarTarGz="$keytarTar.gz"
@@ -184,12 +183,12 @@ jobs:
run: npm run dist:cli:mac
- name: Zip
run: zip -j dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip dist-cli/macos/bwdc keytar/macos/build/Release/keytar.node
run: |
zip -j ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip ./dist-cli/macos/bwdc ./keytar/macos/build/Release/keytar.node
- name: Create checksums
run: |
shasum -a 256 dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip | \
cut -d " " -f 1 > dist-cli/bwdc-macos-sha256-$_PACKAGE_VERSION.txt
sha256sum ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip | cut -d " " -f 1 > ./dist-cli/bwdc-macos-sha256-$_PACKAGE_VERSION.txt
- name: Version Test
run: |
@@ -207,30 +206,31 @@ jobs:
fi
- name: Upload Mac Zip to GitHub
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
path: ./dist-cli/bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
if-no-files-found: error
- name: Upload Mac checksum to GitHub
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
path: ./dist-cli/bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
if-no-files-found: error
windows-cli:
name: Build Windows CLI
runs-on: windows-2022
runs-on: windows-2019
needs: setup
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
_WIN_PKG_FETCH_VERSION: 18.5.0
_WIN_PKG_VERSION: 3.4
_WIN_PKG_FETCH_VERSION: 16.13.0
_WIN_PKG_VERSION: 3.2
steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Setup Windows builder
run: |
@@ -238,11 +238,9 @@ jobs:
choco install reshack --no-progress
- name: Set up Node
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '18'
node-version: '16'
- name: Update NPM
run: |
@@ -263,7 +261,7 @@ jobs:
- name: Keytar
shell: pwsh
run: |
$keytarVersion = (Get-Content -Raw -Path ./package.json | ConvertFrom-Json).dependencies.keytar
$keytarVersion = (Get-Content -Raw -Path ./src/package.json | ConvertFrom-Json).dependencies.keytar
$keytarTar = "keytar-v${keytarVersion}-napi-v3-{0}-x64.tar"
$keytarTarGz = "${keytarTar}.gz"
$keytarUrl = "https://github.com/atom/node-keytar/releases/download/v${keytarVersion}/${keytarTarGz}"
@@ -332,16 +330,16 @@ jobs:
- name: Zip
shell: cmd
run: 7z a .\dist-cli\bwdc-windows-%_PACKAGE_VERSION%.zip .\dist-cli\windows\bwdc.exe .\keytar\windows\keytar.node
run: |
7z a ./dist-cli/bwdc-windows-%_PACKAGE_VERSION%.zip ./dist-cli/windows/bwdc.exe ./keytar/windows/keytar.node
- name: Version Test
shell: pwsh
run: |
Expand-Archive -Path "dist-cli\bwdc-windows-${{ env._PACKAGE_VERSION }}.zip" -DestinationPath "test\windows"
$testVersion = Invoke-Expression '& .\test\windows\bwdc.exe -v'
echo "version: ${env:_PACKAGE_VERSION}"
Expand-Archive -Path "./dist-cli/bwdc-windows-${env:_PACKAGE_VERSION}.zip" -DestinationPath "./test/windows"
$testVersion = Invoke-Expression '& ./test/windows/bwdc.exe -v'
echo "version: $env:_PACKAGE_VERSION"
echo "testVersion: $testVersion"
if ($testVersion -ne ${env:_PACKAGE_VERSION}) {
if($testVersion -ne $env:_PACKAGE_VERSION) {
Throw "Version test failed."
}
@@ -351,14 +349,14 @@ jobs:
-t sha256 | Out-File ./dist-cli/bwdc-windows-sha256-${env:_PACKAGE_VERSION}.txt
- name: Upload Windows Zip to GitHub
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
path: ./dist-cli/bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
if-no-files-found: error
- name: Upload Windows checksum to GitHub
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
path: ./dist-cli/bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
@@ -367,38 +365,48 @@ jobs:
windows-gui:
name: Build Windows GUI
runs-on: windows-2022
runs-on: windows-2019
needs: setup
env:
NODE_OPTIONS: --max_old_space_size=4096
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up .NET
uses: actions/setup-dotnet@a71d1eb2c86af85faa8c772c03fb365e377e45ea
with:
dotnet-version: "3.1.x"
- name: Set up Node
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '18'
node-version: '16'
- name: Update NPM
run: |
npm install -g node-gyp
node-gyp install $(node -v)
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
shell: pwsh
- name: Print environment
run: |
node --version
npm --version
dotnet --version
- name: Install AST
run: dotnet tool install --global AzureSignTool --version 4.0.1
uses: bitwarden/gh-actions/install-ast@f135c42c8596cb535c5bcb7523c0b2eef89709ac
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Install Node dependencies
run: npm install
# - name: Run linter
# run: npm run lint
- name: Build & Sign
run: npm run dist:win
env:
@@ -410,28 +418,28 @@ jobs:
SIGNING_CERT_NAME: ${{ secrets.SIGNING_CERT_NAME }}
- name: Upload Portable Executable to GitHub
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
path: ./dist/Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
if-no-files-found: error
- name: Upload Installer Executable to GitHub
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
if-no-files-found: error
- name: Upload Installer Executable Blockmap to GitHub
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
if-no-files-found: error
- name: Upload latest auto-update artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: latest.yml
path: ./dist/latest.yml
@@ -440,33 +448,33 @@ jobs:
linux-gui:
name: Build Linux GUI
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
needs: setup
env:
NODE_OPTIONS: --max_old_space_size=4096
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up Node
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '18'
node-version: '16'
- name: Update NPM
run: |
npm install -g node-gyp
node-gyp install $(node -v)
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
- name: Set up environment
run: |
sudo apt-get update
sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev
sudo apt-get -y install rpm
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: NPM Install
run: npm install
@@ -477,14 +485,14 @@ jobs:
run: npm run dist:lin
- name: Upload AppImage
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
if-no-files-found: error
- name: Upload latest auto-update artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: latest-linux.yml
path: ./dist/latest-linux.yml
@@ -493,27 +501,24 @@ jobs:
macos-gui:
name: Build MacOS GUI
runs-on: macos-12
runs-on: macos-11
needs: setup
env:
NODE_OPTIONS: --max_old_space_size=4096
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up Node
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '18'
node-version: '16'
- name: Update NPM
run: |
npm install -g node-gyp
node-gyp install $(node -v)
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
- name: Print environment
run: |
node --version
@@ -522,6 +527,9 @@ jobs:
echo "GitHub event: $GITHUB_EVENT"
shell: bash
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Decrypt secrets
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
@@ -563,7 +571,7 @@ jobs:
- name: Load package version
run: |
$rootPath = $env:GITHUB_WORKSPACE;
$packageVersion = (Get-Content -Raw -Path $rootPath\package.json | ConvertFrom-Json).version;
$packageVersion = (Get-Content -Raw -Path $rootPath\src\package.json | ConvertFrom-Json).version;
Write-Output "Setting package version to $packageVersion";
Write-Output "PACKAGE_VERSION=$packageVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append;
@@ -572,36 +580,44 @@ jobs:
- name: Install Node dependencies
run: npm install
# - name: Run linter
# run: npm run lint
- name: Build application
run: npm run dist:mac
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
CSC_FOR_PULL_REQUEST: true
- name: Rename Zip Artifact
run: |
cd dist
mv "Bitwarden Directory Connector-${{ env._PACKAGE_VERSION }}-mac.zip" \
"Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip"
- name: Upload .zip artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
if-no-files-found: error
- name: Upload .dmg artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
if-no-files-found: error
- name: Upload .dmg Blockmap artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
if-no-files-found: error
- name: Upload latest auto-update artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: latest-mac.yml
path: ./dist/latest-mac.yml
@@ -610,7 +626,7 @@ jobs:
check-failures:
name: Check for failures
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
needs:
- cloc
- setup
@@ -622,7 +638,7 @@ jobs:
- macos-gui
steps:
- name: Check if any job failed
if: ${{ (github.ref == 'refs/heads/main') || (github.ref == 'refs/heads/rc') }}
if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') }}
env:
CLOC_STATUS: ${{ needs.cloc.result }}
SETUP_STATUS: ${{ needs.setup.result }}
@@ -651,22 +667,22 @@ jobs:
exit 1
fi
- name: Login to Azure - CI subscription
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
- name: Login to Azure - Prod Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
if: failure()
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
if: failure()
with:
keyvault: "bitwarden-ci"
keyvault: "bitwarden-prod-kv"
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f # v2.0.0
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}

View File

@@ -7,10 +7,10 @@ on:
jobs:
enforce-label:
name: EnforceLabel
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Enforce Label
uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # 2.2.2
uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
with:
BANNED_LABELS: "hold"
BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged"

View File

@@ -17,13 +17,8 @@ on:
jobs:
setup:
name: Setup
runs-on: ubuntu-22.04
outputs:
release-version: ${{ steps.version.outputs.version }}
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Branch check
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: |
@@ -34,50 +29,49 @@ jobs:
exit 1
fi
- name: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@main
with:
release-type: ${{ github.event.inputs.release_type }}
project-type: ts
file: package.json
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
release:
name: Release
runs-on: ubuntu-22.04
needs: setup
steps:
- name: Create GitHub deployment
uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5
id: deployment
with:
token: '${{ secrets.GITHUB_TOKEN }}'
initial-status: 'in_progress'
environment: 'production'
description: 'Deployment ${{ needs.setup.outputs.release-version }} from branch ${{ github.ref_name }}'
task: release
- name: Retrieve Directory Connector release version
id: retrieve-version
run: |
PKG_VERSION=$(jq -r .version src/package.json)
echo "::set-output name=package_version::$PKG_VERSION"
- name: Check to make sure Mobile release version has been bumped
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
latest_ver=$(hub release -L 1 -f '%T')
latest_ver=${latest_ver:1}
echo "Latest version: $latest_ver"
ver=${{ steps.retrieve-version.outputs.package_version }}
echo "Version: $ver"
if [ "$latest_ver" = "$ver" ]; then
echo "Version has not been bumped!"
exit 1
fi
shell: bash
- name: Get branch name
id: branch
run: |
BRANCH_NAME=$(basename ${{ github.ref }})
echo "::set-output name=branch-name::$BRANCH_NAME"
- name: Download all artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@main
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ github.ref_name }}
- name: Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@main
with:
workflow: build.yml
workflow_conclusion: success
branch: main
branch: ${{ steps.branch.outputs.branch-name }}
- name: Create release
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0
env:
PKG_VERSION: ${{ needs.setup.outputs.release-version }}
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
with:
artifacts: "./bwdc-windows-${{ env.PKG_VERSION }}.zip,
./bwdc-macos-${{ env.PKG_VERSION }}.zip,
@@ -101,19 +95,3 @@ jobs:
body: "<insert release notes here>"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
- name: Update deployment status to Success
if: ${{ success() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'success'
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
- name: Update deployment status to Failure
if: ${{ failure() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'failure'
deployment-id: ${{ steps.deployment.outputs.deployment_id }}

View File

@@ -1,121 +1,55 @@
---
name: Version Bump
run-name: Version Bump - v${{ inputs.version_number }}
on:
workflow_dispatch:
inputs:
version_number:
description: "New version (example: '2024.1.0')"
description: "New Version"
required: true
type: string
cut_rc_branch:
description: "Cut RC branch?"
default: true
type: boolean
jobs:
bump_version:
name: "Bump Version to v${{ inputs.version_number }}"
runs-on: ubuntu-22.04
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
runs-on: ubuntu-20.04
steps:
- name: Login to Azure - CI Subscription
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key,
github-gpg-private-key-passphrase,
github-pat-bitwarden-devops-bot-repo-scope"
- name: Checkout Branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
repository: bitwarden/directory-connector
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@82a020f1f7f605c65dd2449b392a52c3fcfef7ef # v6.0.0
with:
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
git_user_signingkey: true
git_commit_gpgsign: true
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Create Version Branch
id: create-branch
run: |
NAME=version_bump_${{ github.ref_name }}_${{ inputs.version_number }}
git switch -c $NAME
echo "name=$NAME" >> $GITHUB_OUTPUT
git switch -c version_bump_${{ github.event.inputs.version_number }}
git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Verify input version
env:
NEW_VERSION: ${{ inputs.version_number }}
run: |
CURRENT_VERSION=$(cat package.json | jq -r '.version')
# Error if version has not changed.
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
echo "Version has not changed."
exit 1
fi
# Check if version is newer.
printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V
if [ $? -eq 0 ]; then
echo "Version check successful."
else
echo "Version check failed."
exit 1
fi
- name: Checkout Version Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
with:
ref: version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Package
uses: bitwarden/gh-actions/version-bump@main
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with:
version: ${{ inputs.version_number }}
file_path: "./package.json"
- name: Setup git
run: |
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
git config --local user.name "bitwarden-devops-bot"
- name: Check if version changed
id: version-changed
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "changes_to_commit=TRUE" >> $GITHUB_OUTPUT
else
echo "changes_to_commit=FALSE" >> $GITHUB_OUTPUT
echo "No changes to commit!";
fi
version: ${{ github.event.inputs.version_number }}
file_path: "./src/package.json"
- name: Commit files
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
run: git commit -m "Bumped version to ${{ inputs.version_number }}" -a
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
- name: Push changes
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
env:
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
run: git push -u origin $PR_BRANCH
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Create Version PR
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
id: create-pr
env:
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
TITLE: "Bump version to ${{ inputs.version_number }}"
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
BASE_BRANCH: master
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
run: |
PR_URL=$(gh pr create --title "$TITLE" \
--base "main" \
gh pr create --title "$TITLE" \
--base "$BASE" \
--head "$PR_BRANCH" \
--label "version update" \
--label "automated pr" \
@@ -126,44 +60,6 @@ jobs:
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [X] Other
## Objective
Automated version bump to ${{ inputs.version_number }}")
echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT
- name: Approve PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
run: gh pr review $PR_NUMBER --approve
- name: Merge PR
env:
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
run: gh pr merge $PR_NUMBER --squash --auto --delete-branch
cut_rc:
name: Cut RC branch
needs: bump_version
if: ${{ inputs.cut_rc_branch == true }}
runs-on: ubuntu-22.04
steps:
- name: Checkout Branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
- name: Check if RC branch exists
run: |
remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
if [[ "${remote_rc_branch_check}" -gt 0 ]]; then
echo "Remote RC branch exists."
echo "Please delete current RC branch before running again."
exit 1
fi
- name: Cut RC branch
run: |
git switch --quiet --create rc
git push --quiet --set-upstream origin rc
Automated version bump to ${{ github.event.inputs.version_number }}"

View File

@@ -8,4 +8,4 @@ on:
jobs:
call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@main
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master

47
.gitignore vendored
View File

@@ -1,40 +1,17 @@
# General
.DS_Store
Thumbs.db
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Node
.vs
.idea
node_modules
npm-debug.log
# Build directories
dist
build
.angular/cache
# Testing
coverage
junit.xml
# Misc
vwd.webinfo
dist/
dist-cli/
css/
*.crx
*.pem
*.zip
build-cli/
build/
yarn-error.log
.DS_Store
*.nupkg
*.provisionprofile
.swp
*.env

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "jslib"]
path = jslib
url = https://github.com/bitwarden/jslib.git
branch = master

1
.nvmrc
View File

@@ -1 +0,0 @@
v18

View File

@@ -3,6 +3,8 @@ build
build-cli
dist
jslib
# External libraries / auto synced locales
src/locales

View File

@@ -48,7 +48,7 @@ We provide detailed documentation and examples for using the Directory Connector
**Requirements**
- [Node.js](https://nodejs.org) v18 (LTS)
- [Node.js](https://nodejs.org) v16.13.1 (LTS)
- 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 (recommended) or by installing [`windows-build-tools`](https://github.com/felixrieseberg/windows-build-tools) through `npm`. See more at [Compiling native Addon modules](https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md#compiling-native-addon-modules).
**Run the app**

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,35 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "apps",
"cli": {
"analytics": false
},
"projects": {
"app": {
"projectType": "application",
"schematics": {
"@schematics/angular:application": {
"strict": true
}
},
"root": ".",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "tsconfig.json",
"assets": [],
"styles": [],
"scripts": []
}
}
}
}
}
}

View File

@@ -1,67 +0,0 @@
{
"extraMetadata": {
"name": "bitwarden-directory-connector"
},
"productName": "Bitwarden Directory Connector",
"appId": "com.bitwarden.directory-connector",
"copyright": "Copyright © 2015-2022 Bitwarden Inc.",
"directories": {
"buildResources": "resources",
"output": "dist",
"app": "build"
},
"afterSign": "scripts/notarize.js",
"mac": {
"artifactName": "Bitwarden-Connector-${version}-mac.${ext}",
"category": "public.app-category.productivity",
"gatekeeperAssess": false,
"hardenedRuntime": true,
"entitlements": "resources/entitlements.mac.plist",
"entitlementsInherit": "resources/entitlements.mac.plist",
"target": ["dmg", "zip"]
},
"win": {
"target": ["portable", "nsis"],
"sign": "scripts/sign.js"
},
"linux": {
"category": "Utility",
"synopsis": "Sync your user directory to your Bitwarden organization.",
"target": ["AppImage"]
},
"dmg": {
"artifactName": "Bitwarden-Connector-${version}.${ext}",
"icon": "dmg.icns",
"contents": [
{
"x": 150,
"y": 185,
"type": "file"
},
{
"x": 390,
"y": 180,
"type": "link",
"path": "/Applications"
}
],
"window": {
"width": 540,
"height": 380
}
},
"nsis": {
"oneClick": false,
"perMachine": true,
"allowToChangeInstallationDirectory": true,
"artifactName": "Bitwarden-Connector-Installer-${version}.${ext}",
"uninstallDisplayName": "${productName}",
"deleteAppDataOnUninstall": true
},
"portable": {
"artifactName": "Bitwarden-Connector-Portable-${version}.${ext}"
},
"appImage": {
"artifactName": "Bitwarden-Connector-${version}-${arch}.${ext}"
}
}

View File

@@ -1,27 +0,0 @@
const { pathsToModuleNameMapper } = require("ts-jest");
const { compilerOptions } = require("./tsconfig");
/** @type {import('jest').Config} */
module.exports = {
reporters: ["default", "jest-junit"],
collectCoverage: true,
coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage",
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
projects: [
"<rootDir>/jslib/angular/jest.config.js",
"<rootDir>/jslib/common/jest.config.js",
"<rootDir>/jslib/electron/jest.config.js",
"<rootDir>/jslib/node/jest.config.js",
],
// Workaround for a memory leak that crashes tests in CI:
// https://github.com/facebook/jest/issues/9430#issuecomment-1149882002
// Also anecdotally improves performance when run locally
maxWorkers: 3,
};

1
jslib Submodule

Submodule jslib added at 1cbc119ad8

9
jslib/.gitignore vendored
View File

@@ -1,9 +0,0 @@
.vs
.idea
node_modules
npm-debug.log
vwd.webinfo
*.crx
*.pem
dist
coverage

View File

@@ -1,16 +0,0 @@
const { pathsToModuleNameMapper } = require("ts-jest");
const { compilerOptions } = require("../shared/tsconfig.libs");
const sharedConfig = require("../shared/jest.config.angular");
/** @type {import('jest').Config} */
module.exports = {
...sharedConfig,
displayName: "libs/angular tests",
preset: "jest-preset-angular",
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
};

View File

@@ -1,28 +0,0 @@
import { webcrypto } from "crypto";
import "jest-preset-angular/setup-jest";
Object.defineProperty(window, "CSS", { value: null });
Object.defineProperty(window, "getComputedStyle", {
value: () => {
return {
display: "none",
appearance: ["-webkit-appearance"],
};
},
});
Object.defineProperty(document, "doctype", {
value: "<!DOCTYPE html>",
});
Object.defineProperty(document.body.style, "transform", {
value: () => {
return {
enumerable: true,
configurable: true,
};
},
});
Object.defineProperty(window, "crypto", {
value: webcrypto,
});

View File

@@ -1,35 +0,0 @@
<div
#callout
class="callout callout-{{ calloutStyle }}"
[ngClass]="{ clickable: clickable }"
[attr.role]="useAlertRole ? 'alert' : null"
>
<h3 class="callout-heading" *ngIf="title">
<i class="bwi {{ icon }}" *ngIf="icon" aria-hidden="true"></i>
{{ title }}
</h3>
<div class="enforced-policy-options" *ngIf="enforcedPolicyOptions">
{{ enforcedPolicyMessage }}
<ul>
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
{{ "policyInEffectMinComplexity" | i18n : getPasswordScoreAlertDisplay() }}
</li>
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
{{ "policyInEffectMinLength" | i18n : enforcedPolicyOptions?.minLength.toString() }}
</li>
<li *ngIf="enforcedPolicyOptions?.requireUpper">
{{ "policyInEffectUppercase" | i18n }}
</li>
<li *ngIf="enforcedPolicyOptions?.requireLower">
{{ "policyInEffectLowercase" | i18n }}
</li>
<li *ngIf="enforcedPolicyOptions?.requireNumbers">
{{ "policyInEffectNumbers" | i18n }}
</li>
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
{{ "policyInEffectSpecial" | i18n : "!@#$%^&*" }}
</li>
</ul>
</div>
<ng-content></ng-content>
</div>

View File

@@ -1,78 +0,0 @@
import { Component, Input, OnInit } from "@angular/core";
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
import { MasterPasswordPolicyOptions } from "@/jslib/common/src/models/domain/masterPasswordPolicyOptions";
@Component({
selector: "app-callout",
templateUrl: "callout.component.html",
})
export class CalloutComponent implements OnInit {
@Input() type = "info";
@Input() icon: string;
@Input() title: string;
@Input() clickable: boolean;
@Input() enforcedPolicyOptions: MasterPasswordPolicyOptions;
@Input() enforcedPolicyMessage: string;
@Input() useAlertRole = false;
calloutStyle: string;
constructor(private i18nService: I18nService) {}
ngOnInit() {
this.calloutStyle = this.type;
if (this.enforcedPolicyMessage === undefined) {
this.enforcedPolicyMessage = this.i18nService.t("masterPasswordPolicyInEffect");
}
if (this.type === "warning" || this.type === "danger") {
if (this.type === "danger") {
this.calloutStyle = "danger";
}
if (this.title === undefined) {
this.title = this.i18nService.t("warning");
}
if (this.icon === undefined) {
this.icon = "bwi-exclamation-triangle";
}
} else if (this.type === "error") {
this.calloutStyle = "danger";
if (this.title === undefined) {
this.title = this.i18nService.t("error");
}
if (this.icon === undefined) {
this.icon = "bwi-error";
}
} else if (this.type === "tip") {
this.calloutStyle = "success";
if (this.title === undefined) {
this.title = this.i18nService.t("tip");
}
if (this.icon === undefined) {
this.icon = "bwi-lightbulb";
}
}
}
getPasswordScoreAlertDisplay() {
if (this.enforcedPolicyOptions == null) {
return "";
}
let str: string;
switch (this.enforcedPolicyOptions.minComplexity) {
case 4:
str = this.i18nService.t("strong");
break;
case 3:
str = this.i18nService.t("good");
break;
default:
str = this.i18nService.t("weak");
break;
}
return str + " (" + this.enforcedPolicyOptions.minComplexity + ")";
}
}

View File

@@ -1,63 +0,0 @@
import { Directive, EventEmitter, Output } from "@angular/core";
import { EnvironmentService } from "@/jslib/common/src/abstractions/environment.service";
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service";
@Directive()
export class EnvironmentComponent {
@Output() onSaved = new EventEmitter();
iconsUrl: string;
identityUrl: string;
apiUrl: string;
webVaultUrl: string;
notificationsUrl: string;
baseUrl: string;
showCustom = false;
constructor(
protected platformUtilsService: PlatformUtilsService,
protected environmentService: EnvironmentService,
protected i18nService: I18nService
) {
const urls = this.environmentService.getUrls();
this.baseUrl = urls.base || "";
this.webVaultUrl = urls.webVault || "";
this.apiUrl = urls.api || "";
this.identityUrl = urls.identity || "";
this.iconsUrl = urls.icons || "";
this.notificationsUrl = urls.notifications || "";
}
async submit() {
const resUrls = await this.environmentService.setUrls({
base: this.baseUrl,
api: this.apiUrl,
identity: this.identityUrl,
webVault: this.webVaultUrl,
icons: this.iconsUrl,
notifications: this.notificationsUrl,
});
// re-set urls since service can change them, ex: prefixing https://
this.baseUrl = resUrls.base;
this.apiUrl = resUrls.api;
this.identityUrl = resUrls.identity;
this.webVaultUrl = resUrls.webVault;
this.iconsUrl = resUrls.icons;
this.notificationsUrl = resUrls.notifications;
this.platformUtilsService.showToast("success", null, this.i18nService.t("environmentSaved"));
this.saved();
}
toggleCustom() {
this.showCustom = !this.showCustom;
}
protected saved() {
this.onSaved.emit();
}
}

View File

@@ -1,11 +0,0 @@
<div class="icon" aria-hidden="true">
<img
[src]="image"
appFallbackSrc="{{ fallbackImage }}"
*ngIf="imageEnabled && image"
alt=""
decoding="async"
loading="lazy"
/>
<i class="bwi bwi-fw bwi-lg {{ icon }}" *ngIf="!imageEnabled || !image"></i>
</div>

View File

@@ -1,112 +0,0 @@
import { Component, Input, OnChanges } from "@angular/core";
import { EnvironmentService } from "@/jslib/common/src/abstractions/environment.service";
import { StateService } from "@/jslib/common/src/abstractions/state.service";
import { CipherType } from "@/jslib/common/src/enums/cipherType";
import { Utils } from "@/jslib/common/src/misc/utils";
import { CipherView } from "@/jslib/common/src/models/view/cipherView";
/**
* Provides a mapping from supported card brands to
* the filenames of icon that should be present in images/cards folder of clients.
*/
const cardIcons: Record<string, string> = {
Visa: "card-visa",
Mastercard: "card-mastercard",
Amex: "card-amex",
Discover: "card-discover",
"Diners Club": "card-diners-club",
JCB: "card-jcb",
Maestro: "card-maestro",
UnionPay: "card-union-pay",
};
@Component({
selector: "app-vault-icon",
templateUrl: "icon.component.html",
})
export class IconComponent implements OnChanges {
@Input() cipher: CipherView;
icon: string;
image: string;
fallbackImage: string;
imageEnabled: boolean;
private iconsUrl: string;
constructor(environmentService: EnvironmentService, private stateService: StateService) {
this.iconsUrl = environmentService.getIconsUrl();
}
async ngOnChanges() {
// Components may be re-used when using cdk-virtual-scroll. Which puts the component in a weird state,
// to avoid this we reset all state variables.
this.image = null;
this.fallbackImage = null;
this.imageEnabled = !(await this.stateService.getDisableFavicon());
this.load();
}
protected load() {
switch (this.cipher.type) {
case CipherType.Login:
this.icon = "bwi-globe";
this.setLoginIcon();
break;
case CipherType.SecureNote:
this.icon = "bwi-sticky-note";
break;
case CipherType.Card:
this.icon = "bwi-credit-card";
this.setCardIcon();
break;
case CipherType.Identity:
this.icon = "bwi-id-card";
break;
default:
break;
}
}
private setLoginIcon() {
if (this.cipher.login.uri) {
let hostnameUri = this.cipher.login.uri;
let isWebsite = false;
if (hostnameUri.indexOf("androidapp://") === 0) {
this.icon = "bwi-android";
this.image = null;
} else if (hostnameUri.indexOf("iosapp://") === 0) {
this.icon = "bwi-apple";
this.image = null;
} else if (
this.imageEnabled &&
hostnameUri.indexOf("://") === -1 &&
hostnameUri.indexOf(".") > -1
) {
hostnameUri = "http://" + hostnameUri;
isWebsite = true;
} else if (this.imageEnabled) {
isWebsite = hostnameUri.indexOf("http") === 0 && hostnameUri.indexOf(".") > -1;
}
if (this.imageEnabled && isWebsite) {
try {
this.image = this.iconsUrl + "/" + Utils.getHostname(hostnameUri) + "/icon.png";
this.fallbackImage = "images/bwi-globe.png";
} catch (e) {
// Ignore error since the fallback icon will be shown if image is null.
}
}
} else {
this.image = null;
}
}
private setCardIcon() {
const brand = this.cipher.card.brand;
if (this.imageEnabled && brand in cardIcons) {
this.icon = "credit-card-icon " + cardIcons[brand];
}
}
}

View File

@@ -1,79 +0,0 @@
import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from "@angular/cdk/a11y";
import {
AfterViewInit,
ChangeDetectorRef,
Component,
ComponentRef,
ElementRef,
OnDestroy,
Type,
ViewChild,
ViewContainerRef,
} from "@angular/core";
import { ModalService } from "../../services/modal.service";
import { ModalRef } from "./modal.ref";
@Component({
selector: "app-modal",
template: "<ng-template #modalContent></ng-template>",
})
export class DynamicModalComponent implements AfterViewInit, OnDestroy {
componentRef: ComponentRef<any>;
@ViewChild("modalContent", { read: ViewContainerRef, static: true })
modalContentRef: ViewContainerRef;
childComponentType: Type<any>;
setComponentParameters: (component: any) => void;
private focusTrap: ConfigurableFocusTrap;
constructor(
private modalService: ModalService,
private cd: ChangeDetectorRef,
private el: ElementRef<HTMLElement>,
private focusTrapFactory: ConfigurableFocusTrapFactory,
public modalRef: ModalRef
) {}
ngAfterViewInit() {
this.loadChildComponent(this.childComponentType);
if (this.setComponentParameters != null) {
this.setComponentParameters(this.componentRef.instance);
}
this.cd.detectChanges();
this.modalRef.created(this.el.nativeElement);
this.focusTrap = this.focusTrapFactory.create(
this.el.nativeElement.querySelector(".modal-dialog")
);
if (this.el.nativeElement.querySelector("[appAutoFocus]") == null) {
this.focusTrap.focusFirstTabbableElementWhenReady();
}
}
loadChildComponent(componentType: Type<any>) {
const componentFactory = this.modalService.resolveComponentFactory(componentType);
this.modalContentRef.clear();
this.componentRef = this.modalContentRef.createComponent(componentFactory);
}
ngOnDestroy() {
if (this.componentRef) {
this.componentRef.destroy();
}
this.focusTrap.destroy();
}
close() {
this.modalRef.close();
}
getFocus() {
const autoFocusEl = this.el.nativeElement.querySelector("[appAutoFocus]") as HTMLElement;
autoFocusEl?.focus();
}
}

View File

@@ -1,18 +0,0 @@
import { InjectFlags, InjectOptions, Injector, ProviderToken } from "@angular/core";
export class ModalInjector implements Injector {
constructor(private _parentInjector: Injector, private _additionalTokens: WeakMap<any, any>) {}
get<T>(
token: ProviderToken<T>,
notFoundValue: undefined,
options: InjectOptions & { optional?: false }
): T;
get<T>(token: ProviderToken<T>, notFoundValue: null, options: InjectOptions): T;
get<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions | InjectFlags): T;
get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
get(token: any, notFoundValue?: any): any;
get(token: any, notFoundValue?: any, flags?: any): any {
return this._additionalTokens.get(token) ?? this._parentInjector.get<any>(token, notFoundValue);
}
}

View File

@@ -1,50 +0,0 @@
import { Observable, Subject } from "rxjs";
import { first } from "rxjs/operators";
export class ModalRef {
onCreated: Observable<HTMLElement>; // Modal added to the DOM.
onClose: Observable<any>; // Initiated close.
onClosed: Observable<any>; // Modal was closed (Remove element from DOM)
onShow: Observable<void>; // Start showing modal
onShown: Observable<void>; // Modal is fully visible
private readonly _onCreated = new Subject<HTMLElement>();
private readonly _onClose = new Subject<any>();
private readonly _onClosed = new Subject<any>();
private readonly _onShow = new Subject<void>();
private readonly _onShown = new Subject<void>();
private lastResult: any;
constructor() {
this.onCreated = this._onCreated.asObservable();
this.onClose = this._onClose.asObservable();
this.onClosed = this._onClosed.asObservable();
this.onShow = this._onShow.asObservable();
this.onShown = this._onShow.asObservable();
}
show() {
this._onShow.next();
}
shown() {
this._onShown.next();
}
close(result?: any) {
this.lastResult = result;
this._onClose.next(result);
}
closed() {
this._onClosed.next(this.lastResult);
}
created(el: HTMLElement) {
this._onCreated.next(el);
}
onClosedPromise(): Promise<any> {
return this.onClosed.pipe(first()).toPromise();
}
}

View File

@@ -1,41 +0,0 @@
import { Directive } from "@angular/core";
import { CryptoService } from "@/jslib/common/src/abstractions/crypto.service";
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service";
import { ModalRef } from "./modal/modal.ref";
/**
* Used to verify the user's Master Password for the "Master Password Re-prompt" feature only.
* See UserVerificationComponent for any other situation where you need to verify the user's identity.
*/
@Directive()
export class PasswordRepromptComponent {
showPassword = false;
masterPassword = "";
constructor(
private modalRef: ModalRef,
private cryptoService: CryptoService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService
) {}
togglePassword() {
this.showPassword = !this.showPassword;
}
async submit() {
if (!(await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, null))) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidMasterPassword")
);
return;
}
this.modalRef.close(true);
}
}

View File

@@ -1,95 +0,0 @@
import { animate, state, style, transition, trigger } from "@angular/animations";
import { CommonModule } from "@angular/common";
import { Component, ModuleWithProviders, NgModule } from "@angular/core";
import {
DefaultNoComponentGlobalConfig,
GlobalConfig,
Toast as BaseToast,
ToastPackage,
ToastrService,
TOAST_CONFIG,
} from "ngx-toastr";
@Component({
selector: "[toast-component2]",
template: `
<button
*ngIf="options.closeButton"
(click)="remove()"
type="button"
class="toast-close-button"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
<div class="icon">
<i></i>
</div>
<div>
<div *ngIf="title" [class]="options.titleClass" [attr.aria-label]="title">
{{ title }} <ng-container *ngIf="duplicatesCount">[{{ duplicatesCount + 1 }}]</ng-container>
</div>
<div
*ngIf="message && options.enableHtml"
role="alertdialog"
aria-live="polite"
[class]="options.messageClass"
[innerHTML]="message"
></div>
<div
*ngIf="message && !options.enableHtml"
role="alertdialog"
aria-live="polite"
[class]="options.messageClass"
[attr.aria-label]="message"
>
{{ message }}
</div>
</div>
<div *ngIf="options.progressBar">
<div class="toast-progress" [style.width]="width + '%'"></div>
</div>
`,
animations: [
trigger("flyInOut", [
state("inactive", style({ opacity: 0 })),
state("active", style({ opacity: 1 })),
state("removed", style({ opacity: 0 })),
transition("inactive => active", animate("{{ easeTime }}ms {{ easing }}")),
transition("active => removed", animate("{{ easeTime }}ms {{ easing }}")),
]),
],
preserveWhitespaces: false,
})
export class BitwardenToast extends BaseToast {
constructor(protected toastrService: ToastrService, public toastPackage: ToastPackage) {
super(toastrService, toastPackage);
}
}
export const BitwardenToastGlobalConfig: GlobalConfig = {
...DefaultNoComponentGlobalConfig,
toastComponent: BitwardenToast,
};
@NgModule({
imports: [CommonModule],
declarations: [BitwardenToast],
exports: [BitwardenToast],
})
export class BitwardenToastModule {
static forRoot(config: Partial<GlobalConfig> = {}): ModuleWithProviders<BitwardenToastModule> {
return {
ngModule: BitwardenToastModule,
providers: [
{
provide: TOAST_CONFIG,
useValue: {
default: BitwardenToastGlobalConfig,
config: config,
},
},
],
};
}
}

View File

@@ -1,23 +0,0 @@
import { Directive, ElementRef, Input, Renderer2 } from "@angular/core";
@Directive({
selector: "[appA11yTitle]",
})
export class A11yTitleDirective {
@Input() set appA11yTitle(title: string) {
this.title = title;
}
private title: string;
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit() {
if (!this.el.nativeElement.hasAttribute("title")) {
this.renderer.setAttribute(this.el.nativeElement, "title", this.title);
}
if (!this.el.nativeElement.hasAttribute("aria-label")) {
this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title);
}
}
}

View File

@@ -1,49 +0,0 @@
import { Directive, ElementRef, Input, OnChanges } from "@angular/core";
import { LogService } from "@/jslib/common/src/abstractions/log.service";
import { ErrorResponse } from "@/jslib/common/src/models/response/errorResponse";
import { ValidationService } from "../services/validation.service";
/**
* Provides error handling, in particular for any error returned by the server in an api call.
* Attach it to a <form> element and provide the name of the class property that will hold the api call promise.
* e.g. <form [appApiAction]="this.formPromise">
* Any errors/rejections that occur will be intercepted and displayed as error toasts.
*/
@Directive({
selector: "[appApiAction]",
})
export class ApiActionDirective implements OnChanges {
@Input() appApiAction: Promise<any>;
constructor(
private el: ElementRef,
private validationService: ValidationService,
private logService: LogService
) {}
ngOnChanges(changes: any) {
if (this.appApiAction == null || this.appApiAction.then == null) {
return;
}
this.el.nativeElement.loading = true;
this.appApiAction.then(
(response: any) => {
this.el.nativeElement.loading = false;
},
(e: any) => {
this.el.nativeElement.loading = false;
if ((e as ErrorResponse).captchaRequired) {
this.logService.error("Captcha required error response: " + e.getSingleMessage());
return;
}
this.logService?.error(`Received API exception: ${e}`);
this.validationService.showError(e);
}
);
}
}

View File

@@ -1,27 +0,0 @@
import { Directive, ElementRef, Input, NgZone } from "@angular/core";
import { take } from "rxjs/operators";
import { Utils } from "@/jslib/common/src/misc/utils";
@Directive({
selector: "[appAutofocus]",
})
export class AutofocusDirective {
@Input() set appAutofocus(condition: boolean | string) {
this.autofocus = condition === "" || condition === true;
}
private autofocus: boolean;
constructor(private el: ElementRef, private ngZone: NgZone) {}
ngOnInit() {
if (!Utils.isMobileBrowser && this.autofocus) {
if (this.ngZone.isStable) {
this.el.nativeElement.focus();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => this.el.nativeElement.focus());
}
}
}
}

View File

@@ -1,12 +0,0 @@
import { Directive, ElementRef, HostListener } from "@angular/core";
@Directive({
selector: "[appBlurClick]",
})
export class BlurClickDirective {
constructor(private el: ElementRef) {}
@HostListener("click") onClick() {
this.el.nativeElement.blur();
}
}

View File

@@ -1,59 +0,0 @@
import { Directive, ElementRef, HostListener, OnInit } from "@angular/core";
@Directive({
selector: "[appBoxRow]",
})
export class BoxRowDirective implements OnInit {
el: HTMLElement = null;
formEls: Element[];
constructor(elRef: ElementRef) {
this.el = elRef.nativeElement;
}
ngOnInit(): void {
this.formEls = Array.from(
this.el.querySelectorAll('input:not([type="hidden"]), select, textarea')
);
this.formEls.forEach((formEl) => {
formEl.addEventListener(
"focus",
() => {
this.el.classList.add("active");
},
false
);
formEl.addEventListener(
"blur",
() => {
this.el.classList.remove("active");
},
false
);
});
}
@HostListener("click", ["$event"]) onClick(event: Event) {
const target = event.target as HTMLElement;
if (
target !== this.el &&
!target.classList.contains("progress") &&
!target.classList.contains("progress-bar")
) {
return;
}
if (this.formEls.length > 0) {
const formEl = this.formEls[0] as HTMLElement;
if (formEl.tagName.toLowerCase() === "input") {
const inputEl = formEl as HTMLInputElement;
if (inputEl.type != null && inputEl.type.toLowerCase() === "checkbox") {
inputEl.click();
return;
}
}
formEl.focus();
}
}
}

View File

@@ -1,14 +0,0 @@
import { Directive, ElementRef, HostListener, Input } from "@angular/core";
@Directive({
selector: "[appFallbackSrc]",
})
export class FallbackSrcDirective {
@Input("appFallbackSrc") appFallbackSrc: string;
constructor(private el: ElementRef) {}
@HostListener("error") onError() {
this.el.nativeElement.src = this.appFallbackSrc;
}
}

View File

@@ -1,10 +0,0 @@
import { Directive, HostListener } from "@angular/core";
@Directive({
selector: "[appStopClick]",
})
export class StopClickDirective {
@HostListener("click", ["$event"]) onClick($event: MouseEvent) {
$event.preventDefault();
}
}

View File

@@ -1,10 +0,0 @@
import { Directive, HostListener } from "@angular/core";
@Directive({
selector: "[appStopProp]",
})
export class StopPropDirective {
@HostListener("click", ["$event"]) onClick($event: MouseEvent) {
$event.stopPropagation();
}
}

View File

@@ -1,14 +0,0 @@
import { Pipe, PipeTransform } from "@angular/core";
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
@Pipe({
name: "i18n",
})
export class I18nPipe implements PipeTransform {
constructor(private i18nService: I18nService) {}
transform(id: string, p1?: string, p2?: string, p3?: string): string {
return this.i18nService.t(id, p1, p2, p3);
}
}

View File

@@ -1,41 +0,0 @@
import { Pipe, PipeTransform } from "@angular/core";
import { CipherView } from "@/jslib/common/src/models/view/cipherView";
@Pipe({
name: "searchCiphers",
})
export class SearchCiphersPipe implements PipeTransform {
transform(ciphers: CipherView[], searchText: string, deleted = false): CipherView[] {
if (ciphers == null || ciphers.length === 0) {
return [];
}
if (searchText == null || searchText.length < 2) {
return ciphers.filter((c) => {
return deleted !== c.isDeleted;
});
}
searchText = searchText.trim().toLowerCase();
return ciphers.filter((c) => {
if (deleted !== c.isDeleted) {
return false;
}
if (c.name != null && c.name.toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (searchText.length >= 8 && c.id.startsWith(searchText)) {
return true;
}
if (c.subTitle != null && c.subTitle.toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (c.login && c.login.uri != null && c.login.uri.toLowerCase().indexOf(searchText) > -1) {
return true;
}
return false;
});
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 262 KiB

View File

@@ -1,250 +0,0 @@
$icomoon-font-family: "bwi-font" !default;
$icomoon-font-path: "/jslib/angular/src/scss/bwicons/fonts/" !default;
// New font sheet? Update the font-face information below
@font-face {
font-family: "#{$icomoon-font-family}";
src: url($icomoon-font-path + "bwi-font.svg") format("svg"),
url($icomoon-font-path + "bwi-font.ttf") format("truetype"),
url($icomoon-font-path + "bwi-font.woff") format("woff"),
url($icomoon-font-path + "bwi-font.woff2") format("woff2");
font-weight: normal;
font-style: normal;
font-display: block;
}
// Base Class
.bwi {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: "#{$icomoon-font-family}" !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
display: inline-block;
/* Better Font Rendering */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
// Fixed Width Icons
.bwi-fw {
width: calc(18em / 14);
text-align: center;
}
// Sizing Changes
.bwi-sm {
font-size: 0.875em;
}
.bwi-lg {
font-size: calc(4em / 3);
line-height: calc(3em / 4);
vertical-align: -15%;
}
.bwi-2x {
font-size: 2em;
}
.bwi-3x {
font-size: 3em;
}
.bwi-4x {
font-size: 4em;
}
// Spin Animations
.bwi-spin {
animation: bwi-spin 2s infinite linear;
}
@keyframes bwi-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
// List Icons
.bwi-ul {
padding-left: 0;
margin-left: calc(30em / 14);
list-style-type: none;
> li {
position: relative;
}
}
.bwi-li {
position: absolute;
left: calc(-30em / 14);
width: calc(30em / 14);
top: calc(2em / 14);
text-align: center;
&.bwi-lg {
left: calc(-30em / 14) + calc(4em / 14);
}
}
// Rotation
.bwi-rotate-270 {
transform: rotate(270deg);
}
// For new icons - add their glyph name and value to the map below
$icons: (
"save-changes": "\e988",
"browser": "\e985",
"mobile": "\e986",
"cli": "\e987",
"providers": "\e983",
"vault": "\e984",
"folder-closed-f": "\e982",
"rocket": "\e9ee",
"ellipsis-h": "\e9ef",
"ellipsis-v": "\e9f0",
"safari": "\e974",
"opera": "\e975",
"firefox": "\e976",
"edge": "\e977",
"chrome": "\e978",
"star-f": "\e979",
"arrow-circle-up": "\e97a",
"arrow-circle-right": "\e97b",
"arrow-circle-left": "\e97c",
"arrow-circle-down": "\e97d",
"undo": "\e97e",
"bolt": "\e97f",
"puzzle": "\e980",
"rss": "\e973",
"dbl-angle-left": "\e970",
"dbl-angle-right": "\e971",
"hamburger": "\e972",
"bw-folder-open-f": "\e93e",
"desktop": "\e96a",
"angle-left": "\e96b",
"user": "\e900",
"user-f": "\e901",
"key": "\e902",
"share-square": "\e903",
"hashtag": "\e904",
"clone": "\e905",
"list-alt": "\e906",
"id-card": "\e907",
"credit-card": "\e908",
"globe": "\e909",
"sticky-note": "\e90a",
"folder": "\e90b",
"lock": "\e90c",
"lock-f": "\e90d",
"generate": "\e90e",
"generate-f": "\e90f",
"cog": "\e910",
"cog-f": "\e911",
"check-circle": "\e912",
"eye": "\e913",
"pencil-square": "\e914",
"bookmark": "\e915",
"files": "\e916",
"trash": "\e917",
"plus": "\e918",
"star": "\e919",
"list": "\e91a",
"angle-right": "\e91b",
"external-link": "\e91c",
"refresh": "\e91d",
"search": "\e91f",
"filter": "\e920",
"plus-circle": "\e921",
"user-circle": "\e922",
"question-circle": "\e923",
"cogs": "\e924",
"minus-circle": "\e925",
"send": "\e926",
"send-f": "\e927",
"download": "\e928",
"pencil": "\e929",
"sign-out": "\e92a",
"share": "\e92b",
"clock": "\e92c",
"angle-down": "\e92d",
"caret-down": "\e92e",
"square": "\e92f",
"collection": "\e930",
"bank": "\e931",
"shield": "\e932",
"stop": "\e933",
"plus-square": "\e934",
"save": "\e935",
"sign-in": "\e936",
"spinner": "\e937",
"dollar": "\e939",
"check": "\e93a",
"check-square": "\e93b",
"minus-square": "\e93c",
"close": "\e93d",
"share-arrow": "\e96c",
"paperclip": "\e93f",
"bitcoin": "\e940",
"cut": "\e941",
"frown": "\e942",
"folder-open": "\e943",
"bug": "\e946",
"chain-broken": "\e947",
"dashboard": "\e948",
"envelope": "\e949",
"exclamation-circle": "\e94a",
"exclamation-triangle": "\e94b",
"caret-right": "\e94c",
"file-pdf": "\e94e",
"file-text": "\e94f",
"info-circle": "\e952",
"lightbulb": "\e953",
"link": "\e954",
"linux": "\e956",
"long-arrow-right": "\e957",
"money": "\e958",
"play": "\e959",
"reddit": "\e95a",
"refresh-tab": "\e95b",
"sitemap": "\e95c",
"sliders": "\e95d",
"tag": "\e95e",
"thumb-tack": "\e95f",
"thumbs-up": "\e960",
"unlock": "\e962",
"users": "\e963",
"wrench": "\e965",
"ban": "\e967",
"camera": "\e968",
"chevron-up": "\e969",
"eye-slash": "\e96d",
"file": "\e96e",
"paste": "\e96f",
"github": "\e950",
"facebook": "\e94d",
"paypal": "\e938",
"google": "\e951",
"linkedin": "\e955",
"discourse": "\e91e",
"twitter": "\e961",
"youtube": "\e966",
"windows": "\e964",
"apple": "\e945",
"android": "\e944",
"error": "\e981",
"numbered-list": "\e989",
);
@each $name, $glyph in $icons {
.bwi-#{$name}:before {
content: $glyph;
}
}

View File

@@ -1,44 +0,0 @@
$card-icons-base: "~@bitwarden/jslib-angular/src/images/cards/";
$card-icons: (
"visa": $card-icons-base + "visa-light.png",
"amex": $card-icons-base + "amex-light.png",
"diners-club": $card-icons-base + "diners_club-light.png",
"discover": $card-icons-base + "discover-light.png",
"jcb": $card-icons-base + "jcb-light.png",
"maestro": $card-icons-base + "maestro-light.png",
"mastercard": $card-icons-base + "mastercard-light.png",
"union-pay": $card-icons-base + "union_pay-light.png",
);
$card-icons-dark: (
"visa": $card-icons-base + "visa-dark.png",
"amex": $card-icons-base + "amex-dark.png",
"diners-club": $card-icons-base + "diners_club-dark.png",
"discover": $card-icons-base + "discover-dark.png",
"jcb": $card-icons-base + "jcb-dark.png",
"maestro": $card-icons-base + "maestro-dark.png",
"mastercard": $card-icons-base + "mastercard-dark.png",
"union-pay": $card-icons-base + "union_pay-dark.png",
);
.credit-card-icon {
display: block; // Resolves the parent container being slighly to big
height: 19px;
width: 24px;
background-size: contain;
background-repeat: no-repeat;
}
@each $name, $url in $card-icons {
.card-#{$name} {
background-image: url("#{$url}");
}
}
@each $theme in $dark-icon-themes {
@each $name, $url in $card-icons-dark {
.#{$theme} .card-#{$name} {
background-image: url("#{$url}");
}
}
}

View File

@@ -1,89 +0,0 @@
@font-face {
font-family: "Open Sans";
font-style: italic;
font-weight: 300;
font-display: auto;
src: url(webfonts/Open_Sans-italic-300.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: "Open Sans";
font-style: italic;
font-weight: 400;
font-display: auto;
src: url(webfonts/Open_Sans-italic-400.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: "Open Sans";
font-style: italic;
font-weight: 600;
font-display: auto;
src: url(webfonts/Open_Sans-italic-600.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: "Open Sans";
font-style: italic;
font-weight: 700;
font-display: auto;
src: url(webfonts/Open_Sans-italic-700.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: "Open Sans";
font-style: italic;
font-weight: 800;
font-display: auto;
src: url(webfonts/Open_Sans-italic-800.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: "Open Sans";
font-style: normal;
font-weight: 300;
font-display: auto;
src: url(webfonts/Open_Sans-normal-300.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: "Open Sans";
font-style: normal;
font-weight: 400;
font-display: auto;
src: url(webfonts/Open_Sans-normal-400.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: "Open Sans";
font-style: normal;
font-weight: 600;
font-display: auto;
src: url(webfonts/Open_Sans-normal-600.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: "Open Sans";
font-style: normal;
font-weight: 700;
font-display: auto;
src: url(webfonts/Open_Sans-normal-700.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: "Open Sans";
font-style: normal;
font-weight: 800;
font-display: auto;
src: url(webfonts/Open_Sans-normal-800.woff) format("woff");
unicode-range: U+0-10FFFF;
}

View File

@@ -1,45 +0,0 @@
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
import { KeyConnectorService } from "@/jslib/common/src/abstractions/keyConnector.service";
import { MessagingService } from "@/jslib/common/src/abstractions/messaging.service";
import { StateService } from "@/jslib/common/src/abstractions/state.service";
import { VaultTimeoutService } from "@/jslib/common/src/abstractions/vaultTimeout.service";
@Injectable()
export class AuthGuardService implements CanActivate {
constructor(
private vaultTimeoutService: VaultTimeoutService,
private router: Router,
private messagingService: MessagingService,
private keyConnectorService: KeyConnectorService,
private stateService: StateService
) {}
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
const isAuthed = await this.stateService.getIsAuthenticated();
if (!isAuthed) {
this.messagingService.send("authBlocked");
return false;
}
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
if (routerState != null) {
this.messagingService.send("lockedUrl", { url: routerState.url });
}
this.router.navigate(["lock"], { queryParams: { promptBiometric: true } });
return false;
}
if (
!routerState.url.includes("remove-password") &&
(await this.keyConnectorService.getConvertAccountRequired())
) {
this.router.navigate(["/remove-password"]);
return false;
}
return true;
}
}

View File

@@ -1,6 +0,0 @@
import { Injectable } from "@angular/core";
import { BroadcasterService as BaseBroadcasterService } from "@/jslib/common/src/services/broadcaster.service";
@Injectable()
export class BroadcasterService extends BaseBroadcasterService {}

View File

@@ -1,492 +0,0 @@
import { Injector, LOCALE_ID, NgModule } from "@angular/core";
import { ApiService as ApiServiceAbstraction } from "@/jslib/common/src/abstractions/api.service";
import { AppIdService as AppIdServiceAbstraction } from "@/jslib/common/src/abstractions/appId.service";
import { AuditService as AuditServiceAbstraction } from "@/jslib/common/src/abstractions/audit.service";
import { AuthService as AuthServiceAbstraction } from "@/jslib/common/src/abstractions/auth.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from "@/jslib/common/src/abstractions/broadcaster.service";
import { CipherService as CipherServiceAbstraction } from "@/jslib/common/src/abstractions/cipher.service";
import { CollectionService as CollectionServiceAbstraction } from "@/jslib/common/src/abstractions/collection.service";
import { CryptoService as CryptoServiceAbstraction } from "@/jslib/common/src/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@/jslib/common/src/abstractions/cryptoFunction.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from "@/jslib/common/src/abstractions/environment.service";
import { EventService as EventServiceAbstraction } from "@/jslib/common/src/abstractions/event.service";
import { ExportService as ExportServiceAbstraction } from "@/jslib/common/src/abstractions/export.service";
import { FileUploadService as FileUploadServiceAbstraction } from "@/jslib/common/src/abstractions/fileUpload.service";
import { FolderService as FolderServiceAbstraction } from "@/jslib/common/src/abstractions/folder.service";
import { I18nService as I18nServiceAbstraction } from "@/jslib/common/src/abstractions/i18n.service";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@/jslib/common/src/abstractions/keyConnector.service";
import { LogService } from "@/jslib/common/src/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "@/jslib/common/src/abstractions/messaging.service";
import { NotificationsService as NotificationsServiceAbstraction } from "@/jslib/common/src/abstractions/notifications.service";
import { OrganizationService as OrganizationServiceAbstraction } from "@/jslib/common/src/abstractions/organization.service";
import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@/jslib/common/src/abstractions/passwordGeneration.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@/jslib/common/src/abstractions/passwordReprompt.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@/jslib/common/src/abstractions/platformUtils.service";
import { PolicyService as PolicyServiceAbstraction } from "@/jslib/common/src/abstractions/policy.service";
import { ProviderService as ProviderServiceAbstraction } from "@/jslib/common/src/abstractions/provider.service";
import { SearchService as SearchServiceAbstraction } from "@/jslib/common/src/abstractions/search.service";
import { SendService as SendServiceAbstraction } from "@/jslib/common/src/abstractions/send.service";
import { SettingsService as SettingsServiceAbstraction } from "@/jslib/common/src/abstractions/settings.service";
import { StateService as StateServiceAbstraction } from "@/jslib/common/src/abstractions/state.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "@/jslib/common/src/abstractions/stateMigration.service";
import { StorageService as StorageServiceAbstraction } from "@/jslib/common/src/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from "@/jslib/common/src/abstractions/sync.service";
import { TokenService as TokenServiceAbstraction } from "@/jslib/common/src/abstractions/token.service";
import { TotpService as TotpServiceAbstraction } from "@/jslib/common/src/abstractions/totp.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@/jslib/common/src/abstractions/twoFactor.service";
import { UserVerificationService as UserVerificationServiceAbstraction } from "@/jslib/common/src/abstractions/userVerification.service";
import { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "@/jslib/common/src/abstractions/usernameGeneration.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@/jslib/common/src/abstractions/vaultTimeout.service";
import { StateFactory } from "@/jslib/common/src/factories/stateFactory";
import { Account } from "@/jslib/common/src/models/domain/account";
import { GlobalState } from "@/jslib/common/src/models/domain/globalState";
import { ApiService } from "@/jslib/common/src/services/api.service";
import { AppIdService } from "@/jslib/common/src/services/appId.service";
import { AuditService } from "@/jslib/common/src/services/audit.service";
import { AuthService } from "@/jslib/common/src/services/auth.service";
import { CipherService } from "@/jslib/common/src/services/cipher.service";
import { CollectionService } from "@/jslib/common/src/services/collection.service";
import { ConsoleLogService } from "@/jslib/common/src/services/consoleLog.service";
import { CryptoService } from "@/jslib/common/src/services/crypto.service";
import { EnvironmentService } from "@/jslib/common/src/services/environment.service";
import { EventService } from "@/jslib/common/src/services/event.service";
import { ExportService } from "@/jslib/common/src/services/export.service";
import { FileUploadService } from "@/jslib/common/src/services/fileUpload.service";
import { FolderService } from "@/jslib/common/src/services/folder.service";
import { KeyConnectorService } from "@/jslib/common/src/services/keyConnector.service";
import { NotificationsService } from "@/jslib/common/src/services/notifications.service";
import { OrganizationService } from "@/jslib/common/src/services/organization.service";
import { PasswordGenerationService } from "@/jslib/common/src/services/passwordGeneration.service";
import { PolicyService } from "@/jslib/common/src/services/policy.service";
import { ProviderService } from "@/jslib/common/src/services/provider.service";
import { SearchService } from "@/jslib/common/src/services/search.service";
import { SendService } from "@/jslib/common/src/services/send.service";
import { SettingsService } from "@/jslib/common/src/services/settings.service";
import { StateService } from "@/jslib/common/src/services/state.service";
import { StateMigrationService } from "@/jslib/common/src/services/stateMigration.service";
import { SyncService } from "@/jslib/common/src/services/sync.service";
import { TokenService } from "@/jslib/common/src/services/token.service";
import { TotpService } from "@/jslib/common/src/services/totp.service";
import { TwoFactorService } from "@/jslib/common/src/services/twoFactor.service";
import { UserVerificationService } from "@/jslib/common/src/services/userVerification.service";
import { UsernameGenerationService } from "@/jslib/common/src/services/usernameGeneration.service";
import { VaultTimeoutService } from "@/jslib/common/src/services/vaultTimeout.service";
import { WebCryptoFunctionService } from "@/jslib/common/src/services/webCryptoFunction.service";
import { AuthGuardService } from "./auth-guard.service";
import { BroadcasterService } from "./broadcaster.service";
import { LockGuardService } from "./lock-guard.service";
import { ModalService } from "./modal.service";
import { PasswordRepromptService } from "./passwordReprompt.service";
import { UnauthGuardService } from "./unauth-guard.service";
import { ValidationService } from "./validation.service";
@NgModule({
declarations: [],
providers: [
{ provide: "WINDOW", useValue: window },
{
provide: LOCALE_ID,
useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale,
deps: [I18nServiceAbstraction],
},
ValidationService,
AuthGuardService,
UnauthGuardService,
LockGuardService,
ModalService,
{
provide: AppIdServiceAbstraction,
useClass: AppIdService,
deps: [StorageServiceAbstraction],
},
{
provide: AuditServiceAbstraction,
useClass: AuditService,
deps: [CryptoFunctionServiceAbstraction, ApiServiceAbstraction],
},
{
provide: AuthServiceAbstraction,
useClass: AuthService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
TokenServiceAbstraction,
AppIdServiceAbstraction,
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
LogService,
KeyConnectorServiceAbstraction,
EnvironmentServiceAbstraction,
StateServiceAbstraction,
TwoFactorServiceAbstraction,
I18nServiceAbstraction,
],
},
{
provide: CipherServiceAbstraction,
useFactory: (
cryptoService: CryptoServiceAbstraction,
settingsService: SettingsServiceAbstraction,
apiService: ApiServiceAbstraction,
fileUploadService: FileUploadServiceAbstraction,
i18nService: I18nServiceAbstraction,
injector: Injector,
logService: LogService,
stateService: StateServiceAbstraction
) =>
new CipherService(
cryptoService,
settingsService,
apiService,
fileUploadService,
i18nService,
() => injector.get(SearchServiceAbstraction),
logService,
stateService
),
deps: [
CryptoServiceAbstraction,
SettingsServiceAbstraction,
ApiServiceAbstraction,
FileUploadServiceAbstraction,
I18nServiceAbstraction,
Injector, // TODO: Get rid of this circular dependency!
LogService,
StateServiceAbstraction,
],
},
{
provide: FolderServiceAbstraction,
useClass: FolderService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
I18nServiceAbstraction,
CipherServiceAbstraction,
StateServiceAbstraction,
],
},
{ provide: LogService, useFactory: () => new ConsoleLogService(false) },
{
provide: CollectionServiceAbstraction,
useClass: CollectionService,
deps: [CryptoServiceAbstraction, I18nServiceAbstraction, StateServiceAbstraction],
},
{
provide: EnvironmentServiceAbstraction,
useClass: EnvironmentService,
deps: [StateServiceAbstraction],
},
{
provide: TotpServiceAbstraction,
useClass: TotpService,
deps: [CryptoFunctionServiceAbstraction, LogService, StateServiceAbstraction],
},
{ provide: TokenServiceAbstraction, useClass: TokenService, deps: [StateServiceAbstraction] },
{
provide: CryptoServiceAbstraction,
useClass: CryptoService,
deps: [
CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction,
LogService,
StateServiceAbstraction,
],
},
{
provide: PasswordGenerationServiceAbstraction,
useClass: PasswordGenerationService,
deps: [CryptoServiceAbstraction, PolicyServiceAbstraction, StateServiceAbstraction],
},
{
provide: UsernameGenerationServiceAbstraction,
useClass: UsernameGenerationService,
deps: [CryptoServiceAbstraction, StateServiceAbstraction],
},
{
provide: ApiServiceAbstraction,
useFactory: (
tokenService: TokenServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
environmentService: EnvironmentServiceAbstraction,
messagingService: MessagingServiceAbstraction,
appIdService: AppIdServiceAbstraction
) =>
new ApiService(
tokenService,
platformUtilsService,
environmentService,
appIdService,
async (expired: boolean) => messagingService.send("logout", { expired: expired })
),
deps: [
TokenServiceAbstraction,
PlatformUtilsServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
AppIdServiceAbstraction,
],
},
{
provide: FileUploadServiceAbstraction,
useClass: FileUploadService,
deps: [LogService, ApiServiceAbstraction],
},
{
provide: SyncServiceAbstraction,
useFactory: (
apiService: ApiServiceAbstraction,
settingsService: SettingsServiceAbstraction,
folderService: FolderServiceAbstraction,
cipherService: CipherServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
collectionService: CollectionServiceAbstraction,
messagingService: MessagingServiceAbstraction,
policyService: PolicyServiceAbstraction,
sendService: SendServiceAbstraction,
logService: LogService,
keyConnectorService: KeyConnectorServiceAbstraction,
stateService: StateServiceAbstraction,
organizationService: OrganizationServiceAbstraction,
providerService: ProviderServiceAbstraction
) =>
new SyncService(
apiService,
settingsService,
folderService,
cipherService,
cryptoService,
collectionService,
messagingService,
policyService,
sendService,
logService,
keyConnectorService,
stateService,
organizationService,
providerService,
async (expired: boolean) => messagingService.send("logout", { expired: expired })
),
deps: [
ApiServiceAbstraction,
SettingsServiceAbstraction,
FolderServiceAbstraction,
CipherServiceAbstraction,
CryptoServiceAbstraction,
CollectionServiceAbstraction,
MessagingServiceAbstraction,
PolicyServiceAbstraction,
SendServiceAbstraction,
LogService,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
OrganizationServiceAbstraction,
ProviderServiceAbstraction,
],
},
{ provide: BroadcasterServiceAbstraction, useClass: BroadcasterService },
{
provide: SettingsServiceAbstraction,
useClass: SettingsService,
deps: [StateServiceAbstraction],
},
{
provide: VaultTimeoutServiceAbstraction,
useFactory: (
cipherService: CipherServiceAbstraction,
folderService: FolderServiceAbstraction,
collectionService: CollectionServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
messagingService: MessagingServiceAbstraction,
searchService: SearchServiceAbstraction,
tokenService: TokenServiceAbstraction,
policyService: PolicyServiceAbstraction,
keyConnectorService: KeyConnectorServiceAbstraction,
stateService: StateServiceAbstraction
) =>
new VaultTimeoutService(
cipherService,
folderService,
collectionService,
cryptoService,
platformUtilsService,
messagingService,
searchService,
tokenService,
policyService,
keyConnectorService,
stateService,
null,
async (userId?: string) =>
messagingService.send("logout", { expired: false, userId: userId })
),
deps: [
CipherServiceAbstraction,
FolderServiceAbstraction,
CollectionServiceAbstraction,
CryptoServiceAbstraction,
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
SearchServiceAbstraction,
TokenServiceAbstraction,
PolicyServiceAbstraction,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: StateServiceAbstraction,
useFactory: (
storageService: StorageServiceAbstraction,
secureStorageService: StorageServiceAbstraction,
logService: LogService,
stateMigrationService: StateMigrationServiceAbstraction
) =>
new StateService(
storageService,
secureStorageService,
logService,
stateMigrationService,
new StateFactory(GlobalState, Account)
),
deps: [
StorageServiceAbstraction,
"SECURE_STORAGE",
LogService,
StateMigrationServiceAbstraction,
],
},
{
provide: StateMigrationServiceAbstraction,
useFactory: (
storageService: StorageServiceAbstraction,
secureStorageService: StorageServiceAbstraction
) =>
new StateMigrationService(
storageService,
secureStorageService,
new StateFactory(GlobalState, Account)
),
deps: [StorageServiceAbstraction, "SECURE_STORAGE"],
},
{
provide: ExportServiceAbstraction,
useClass: ExportService,
deps: [
FolderServiceAbstraction,
CipherServiceAbstraction,
ApiServiceAbstraction,
CryptoServiceAbstraction,
],
},
{
provide: SearchServiceAbstraction,
useClass: SearchService,
deps: [CipherServiceAbstraction, LogService, I18nServiceAbstraction],
},
{
provide: NotificationsServiceAbstraction,
useFactory: (
syncService: SyncServiceAbstraction,
appIdService: AppIdServiceAbstraction,
apiService: ApiServiceAbstraction,
vaultTimeoutService: VaultTimeoutServiceAbstraction,
environmentService: EnvironmentServiceAbstraction,
messagingService: MessagingServiceAbstraction,
logService: LogService,
stateService: StateServiceAbstraction
) =>
new NotificationsService(
syncService,
appIdService,
apiService,
vaultTimeoutService,
environmentService,
async () => messagingService.send("logout", { expired: true }),
logService,
stateService
),
deps: [
SyncServiceAbstraction,
AppIdServiceAbstraction,
ApiServiceAbstraction,
VaultTimeoutServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
LogService,
StateServiceAbstraction,
],
},
{
provide: CryptoFunctionServiceAbstraction,
useClass: WebCryptoFunctionService,
deps: ["WINDOW"],
},
{
provide: EventServiceAbstraction,
useClass: EventService,
deps: [
ApiServiceAbstraction,
CipherServiceAbstraction,
StateServiceAbstraction,
LogService,
OrganizationServiceAbstraction,
],
},
{
provide: PolicyServiceAbstraction,
useClass: PolicyService,
deps: [StateServiceAbstraction, OrganizationServiceAbstraction, ApiServiceAbstraction],
},
{
provide: SendServiceAbstraction,
useClass: SendService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
FileUploadServiceAbstraction,
I18nServiceAbstraction,
CryptoFunctionServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: KeyConnectorServiceAbstraction,
useClass: KeyConnectorService,
deps: [
StateServiceAbstraction,
CryptoServiceAbstraction,
ApiServiceAbstraction,
TokenServiceAbstraction,
LogService,
OrganizationServiceAbstraction,
CryptoFunctionServiceAbstraction,
],
},
{
provide: UserVerificationServiceAbstraction,
useClass: UserVerificationService,
deps: [CryptoServiceAbstraction, I18nServiceAbstraction, ApiServiceAbstraction],
},
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
{
provide: OrganizationServiceAbstraction,
useClass: OrganizationService,
deps: [StateServiceAbstraction],
},
{
provide: ProviderServiceAbstraction,
useClass: ProviderService,
deps: [StateServiceAbstraction],
},
{
provide: TwoFactorServiceAbstraction,
useClass: TwoFactorService,
deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction],
},
],
})
export class JslibServicesModule {}

View File

@@ -1,29 +0,0 @@
import { Injectable } from "@angular/core";
import { CanActivate, Router } from "@angular/router";
import { StateService } from "@/jslib/common/src/abstractions/state.service";
import { VaultTimeoutService } from "@/jslib/common/src/abstractions/vaultTimeout.service";
@Injectable()
export class LockGuardService implements CanActivate {
protected homepage = "vault";
protected loginpage = "login";
constructor(
private vaultTimeoutService: VaultTimeoutService,
private router: Router,
private stateService: StateService
) {}
async canActivate() {
if (await this.vaultTimeoutService.isLocked()) {
return true;
}
const redirectUrl = (await this.stateService.getIsAuthenticated())
? [this.homepage]
: [this.loginpage];
this.router.navigate(redirectUrl);
return false;
}
}

View File

@@ -1,180 +0,0 @@
import {
ApplicationRef,
ComponentFactory,
ComponentFactoryResolver,
ComponentRef,
EmbeddedViewRef,
Injectable,
Injector,
Type,
ViewContainerRef,
} from "@angular/core";
import { first } from "rxjs/operators";
import { DynamicModalComponent } from "../components/modal/dynamic-modal.component";
import { ModalInjector } from "../components/modal/modal-injector";
import { ModalRef } from "../components/modal/modal.ref";
export class ModalConfig<D = any> {
data?: D;
allowMultipleModals = false;
}
@Injectable()
export class ModalService {
protected modalList: ComponentRef<DynamicModalComponent>[] = [];
// Lazy loaded modules are not available in componentFactoryResolver,
// therefore modules needs to manually initialize their resolvers.
private factoryResolvers: Map<Type<any>, ComponentFactoryResolver> = new Map();
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private applicationRef: ApplicationRef,
private injector: Injector
) {
document.addEventListener("keyup", (event) => {
if (event.key === "Escape" && this.modalCount > 0) {
this.topModal.instance.close();
}
});
}
get modalCount() {
return this.modalList.length;
}
private get topModal() {
return this.modalList[this.modalCount - 1];
}
async openViewRef<T>(
componentType: Type<T>,
viewContainerRef: ViewContainerRef,
setComponentParameters: (component: T) => void = null
): Promise<[ModalRef, T]> {
const [modalRef, modalComponentRef] = this.openInternal(componentType, null, false);
modalComponentRef.instance.setComponentParameters = setComponentParameters;
viewContainerRef.insert(modalComponentRef.hostView);
await modalRef.onCreated.pipe(first()).toPromise();
return [modalRef, modalComponentRef.instance.componentRef.instance];
}
open(componentType: Type<any>, config?: ModalConfig) {
if (!(config?.allowMultipleModals ?? false) && this.modalCount > 0) {
return;
}
// eslint-disable-next-line
const [modalRef, _] = this.openInternal(componentType, config, true);
return modalRef;
}
registerComponentFactoryResolver<T>(
componentType: Type<T>,
componentFactoryResolver: ComponentFactoryResolver
): void {
this.factoryResolvers.set(componentType, componentFactoryResolver);
}
resolveComponentFactory<T>(componentType: Type<T>): ComponentFactory<T> {
if (this.factoryResolvers.has(componentType)) {
return this.factoryResolvers.get(componentType).resolveComponentFactory(componentType);
}
return this.componentFactoryResolver.resolveComponentFactory(componentType);
}
protected openInternal(
componentType: Type<any>,
config?: ModalConfig,
attachToDom?: boolean
): [ModalRef, ComponentRef<DynamicModalComponent>] {
const [modalRef, componentRef] = this.createModalComponent(config);
componentRef.instance.childComponentType = componentType;
if (attachToDom) {
this.applicationRef.attachView(componentRef.hostView);
const domElem = (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
document.body.appendChild(domElem);
}
modalRef.onClosed.pipe(first()).subscribe(() => {
if (attachToDom) {
this.applicationRef.detachView(componentRef.hostView);
}
componentRef.destroy();
this.modalList.pop();
if (this.modalCount > 0) {
this.topModal.instance.getFocus();
}
});
this.setupHandlers(modalRef);
this.modalList.push(componentRef);
return [modalRef, componentRef];
}
protected setupHandlers(modalRef: ModalRef) {
let backdrop: HTMLElement = null;
// Add backdrop, setup [data-dismiss] handler.
modalRef.onCreated.pipe(first()).subscribe((el) => {
document.body.classList.add("modal-open");
const modalEl: HTMLElement = el.querySelector(".modal");
const dialogEl = modalEl.querySelector(".modal-dialog") as HTMLElement;
backdrop = document.createElement("div");
backdrop.className = "modal-backdrop fade";
backdrop.style.zIndex = `${this.modalCount}040`;
modalEl.prepend(backdrop);
dialogEl.addEventListener("click", (e: Event) => {
e.stopPropagation();
});
dialogEl.style.zIndex = `${this.modalCount}050`;
const modals = Array.from(
el.querySelectorAll('.modal-backdrop, .modal *[data-dismiss="modal"]')
);
for (const closeElement of modals) {
closeElement.addEventListener("click", () => {
modalRef.close();
});
}
});
// onClose is used in Web to hook into bootstrap. On other projects we pipe it directly to closed.
modalRef.onClose.pipe(first()).subscribe(() => {
modalRef.closed();
if (this.modalCount === 0) {
document.body.classList.remove("modal-open");
}
});
}
protected createModalComponent(
config: ModalConfig
): [ModalRef, ComponentRef<DynamicModalComponent>] {
const modalRef = new ModalRef();
const map = new WeakMap();
map.set(ModalConfig, config);
map.set(ModalRef, modalRef);
const componentFactory =
this.componentFactoryResolver.resolveComponentFactory(DynamicModalComponent);
const componentRef = componentFactory.create(new ModalInjector(this.injector, map));
return [modalRef, componentRef];
}
}

View File

@@ -1,45 +0,0 @@
import { Injectable } from "@angular/core";
import { KeyConnectorService } from "@/jslib/common/src/abstractions/keyConnector.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@/jslib/common/src/abstractions/passwordReprompt.service";
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
import { ModalService } from "./modal.service";
/**
* Used to verify the user's Master Password for the "Master Password Re-prompt" feature only.
* See UserVerificationService for any other situation where you need to verify the user's identity.
*/
@Injectable()
export class PasswordRepromptService implements PasswordRepromptServiceAbstraction {
protected component = PasswordRepromptComponent;
constructor(
private modalService: ModalService,
private keyConnectorService: KeyConnectorService
) {}
protectedFields() {
return ["TOTP", "Password", "H_Field", "Card Number", "Security Code"];
}
async showPasswordPrompt() {
if (!(await this.enabled())) {
return true;
}
const ref = this.modalService.open(this.component, { allowMultipleModals: true });
if (ref == null) {
return false;
}
const result = await ref.onClosedPromise();
return result === true;
}
async enabled() {
return !(await this.keyConnectorService.getUsesKeyConnector());
}
}

View File

@@ -1,29 +0,0 @@
import { Injectable } from "@angular/core";
import { CanActivate, Router } from "@angular/router";
import { StateService } from "@/jslib/common/src/abstractions/state.service";
import { VaultTimeoutService } from "@/jslib/common/src/abstractions/vaultTimeout.service";
@Injectable()
export class UnauthGuardService implements CanActivate {
protected homepage = "vault";
constructor(
private vaultTimeoutService: VaultTimeoutService,
private router: Router,
private stateService: StateService
) {}
async canActivate() {
const isAuthed = await this.stateService.getIsAuthenticated();
if (isAuthed) {
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
this.router.navigate(["lock"]);
} else {
this.router.navigate([this.homepage]);
}
return false;
}
return true;
}
}

View File

@@ -1,38 +0,0 @@
import { Injectable } from "@angular/core";
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service";
import { ErrorResponse } from "@/jslib/common/src/models/response/errorResponse";
@Injectable()
export class ValidationService {
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService
) {}
showError(data: any): string[] {
const defaultErrorMessage = this.i18nService.t("unexpectedError");
let errors: string[] = [];
if (data != null && typeof data === "string") {
errors.push(data);
} else if (data == null || typeof data !== "object") {
errors.push(defaultErrorMessage);
} else if (data.validationErrors != null) {
errors = errors.concat((data as ErrorResponse).getAllMessages());
} else {
errors.push(data.message ? data.message : defaultErrorMessage);
}
if (errors.length === 1) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errors[0]);
} else if (errors.length > 1) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errors, {
timeout: 5000 * errors.length,
});
}
return errors;
}
}

View File

@@ -1,28 +0,0 @@
import { webcrypto } from "crypto";
import "jest-preset-angular/setup-jest";
Object.defineProperty(window, "CSS", { value: null });
Object.defineProperty(window, "getComputedStyle", {
value: () => {
return {
display: "none",
appearance: ["-webkit-appearance"],
};
},
});
Object.defineProperty(document, "doctype", {
value: "<!DOCTYPE html>",
});
Object.defineProperty(document.body.style, "transform", {
value: () => {
return {
enumerable: true,
configurable: true,
};
},
});
Object.defineProperty(window, "crypto", {
value: webcrypto,
});

View File

@@ -1,15 +0,0 @@
import type { CustomMatchers } from "./test.setup";
// This declares the types for our custom matchers so that they're recognised by Typescript
// This file must also be included in the TS compilation (via the tsconfig.json "include" property) to be recognised by
// vscode
/* eslint-disable */
declare global {
namespace jest {
interface Expect extends CustomMatchers {}
interface Matchers<R> extends CustomMatchers<R> {}
interface InverseAsymmetricMatchers extends CustomMatchers {}
}
}
/* eslint-enable */

View File

@@ -1,17 +0,0 @@
const { pathsToModuleNameMapper } = require("ts-jest");
const { compilerOptions } = require("../shared/tsconfig.libs");
const sharedConfig = require("../shared/jest.config.ts");
/** @type {import('jest').Config} */
module.exports = {
...sharedConfig,
displayName: "libs/common tests",
preset: "ts-jest",
testEnvironment: "jsdom",
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
};

View File

@@ -1,83 +0,0 @@
import Substitute, { Arg } from "@fluffy-spoon/substitute";
import { CryptoService } from "@/jslib/common/src/abstractions/crypto.service";
import { AttachmentData } from "@/jslib/common/src/models/data/attachmentData";
import { Attachment } from "@/jslib/common/src/models/domain/attachment";
import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey";
import { ContainerService } from "@/jslib/common/src/services/container.service";
import { makeStaticByteArray, mockEnc } from "../utils";
describe("Attachment", () => {
let data: AttachmentData;
beforeEach(() => {
data = {
id: "id",
url: "url",
fileName: "fileName",
key: "key",
size: "1100",
sizeName: "1.1 KB",
};
});
it("Convert from empty", () => {
const data = new AttachmentData();
const attachment = new Attachment(data);
expect(attachment).toEqual({
id: null,
url: null,
size: undefined,
sizeName: null,
key: null,
fileName: null,
});
});
it("Convert", () => {
const attachment = new Attachment(data);
expect(attachment).toEqual({
size: "1100",
id: "id",
url: "url",
sizeName: "1.1 KB",
fileName: { encryptedString: "fileName", encryptionType: 0 },
key: { encryptedString: "key", encryptionType: 0 },
});
});
it("toAttachmentData", () => {
const attachment = new Attachment(data);
expect(attachment.toAttachmentData()).toEqual(data);
});
it("Decrypt", async () => {
const attachment = new Attachment();
attachment.id = "id";
attachment.url = "url";
attachment.size = "1100";
attachment.sizeName = "1.1 KB";
attachment.key = mockEnc("key");
attachment.fileName = mockEnc("fileName");
const cryptoService = Substitute.for<CryptoService>();
cryptoService.getOrgKey(null).resolves(null);
cryptoService.decryptToBytes(Arg.any(), Arg.any()).resolves(makeStaticByteArray(32));
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
const view = await attachment.decrypt(null);
expect(view).toEqual({
id: "id",
url: "url",
size: "1100",
sizeName: "1.1 KB",
fileName: "fileName",
key: expect.any(SymmetricCryptoKey),
});
});
});

View File

@@ -1,73 +0,0 @@
import { CardData } from "@/jslib/common/src/models/data/cardData";
import { Card } from "@/jslib/common/src/models/domain/card";
import { mockEnc } from "../utils";
describe("Card", () => {
let data: CardData;
beforeEach(() => {
data = {
cardholderName: "encHolder",
brand: "encBrand",
number: "encNumber",
expMonth: "encMonth",
expYear: "encYear",
code: "encCode",
};
});
it("Convert from empty", () => {
const data = new CardData();
const card = new Card(data);
expect(card).toEqual({
cardholderName: null,
brand: null,
number: null,
expMonth: null,
expYear: null,
code: null,
});
});
it("Convert", () => {
const card = new Card(data);
expect(card).toEqual({
cardholderName: { encryptedString: "encHolder", encryptionType: 0 },
brand: { encryptedString: "encBrand", encryptionType: 0 },
number: { encryptedString: "encNumber", encryptionType: 0 },
expMonth: { encryptedString: "encMonth", encryptionType: 0 },
expYear: { encryptedString: "encYear", encryptionType: 0 },
code: { encryptedString: "encCode", encryptionType: 0 },
});
});
it("toCardData", () => {
const card = new Card(data);
expect(card.toCardData()).toEqual(data);
});
it("Decrypt", async () => {
const card = new Card();
card.cardholderName = mockEnc("cardHolder");
card.brand = mockEnc("brand");
card.number = mockEnc("number");
card.expMonth = mockEnc("expMonth");
card.expYear = mockEnc("expYear");
card.code = mockEnc("code");
const view = await card.decrypt(null);
expect(view).toEqual({
_brand: "brand",
_number: "number",
_subTitle: null,
cardholderName: "cardHolder",
code: "code",
expMonth: "expMonth",
expYear: "expYear",
});
});
});

View File

@@ -1,599 +0,0 @@
import Substitute, { Arg } from "@fluffy-spoon/substitute";
import { CipherRepromptType } from "@/jslib/common/src/enums/cipherRepromptType";
import { CipherType } from "@/jslib/common/src/enums/cipherType";
import { FieldType } from "@/jslib/common/src/enums/fieldType";
import { SecureNoteType } from "@/jslib/common/src/enums/secureNoteType";
import { UriMatchType } from "@/jslib/common/src/enums/uriMatchType";
import { CipherData } from "@/jslib/common/src/models/data/cipherData";
import { Card } from "@/jslib/common/src/models/domain/card";
import { Cipher } from "@/jslib/common/src/models/domain/cipher";
import { Identity } from "@/jslib/common/src/models/domain/identity";
import { Login } from "@/jslib/common/src/models/domain/login";
import { SecureNote } from "@/jslib/common/src/models/domain/secureNote";
import { CardView } from "@/jslib/common/src/models/view/cardView";
import { IdentityView } from "@/jslib/common/src/models/view/identityView";
import { LoginView } from "@/jslib/common/src/models/view/loginView";
import { mockEnc } from "../utils";
describe("Cipher DTO", () => {
it("Convert from empty CipherData", () => {
const data = new CipherData();
const cipher = new Cipher(data);
expect(cipher).toEqual({
id: null,
userId: null,
organizationId: null,
folderId: null,
name: null,
notes: null,
type: undefined,
favorite: undefined,
organizationUseTotp: undefined,
edit: undefined,
viewPassword: true,
revisionDate: null,
collectionIds: undefined,
localData: null,
deletedDate: null,
reprompt: undefined,
attachments: null,
fields: null,
passwordHistory: null,
});
});
describe("LoginCipher", () => {
let cipherData: CipherData;
beforeEach(() => {
cipherData = {
id: "id",
organizationId: "orgId",
folderId: "folderId",
userId: "userId",
edit: true,
viewPassword: true,
organizationUseTotp: true,
favorite: false,
revisionDate: "2022-01-31T12:00:00.000Z",
type: CipherType.Login,
name: "EncryptedString",
notes: "EncryptedString",
deletedDate: null,
reprompt: CipherRepromptType.None,
login: {
uris: [{ uri: "EncryptedString", match: UriMatchType.Domain }],
username: "EncryptedString",
password: "EncryptedString",
passwordRevisionDate: "2022-01-31T12:00:00.000Z",
totp: "EncryptedString",
autofillOnPageLoad: false,
},
passwordHistory: [
{ password: "EncryptedString", lastUsedDate: "2022-01-31T12:00:00.000Z" },
],
attachments: [
{
id: "a1",
url: "url",
size: "1100",
sizeName: "1.1 KB",
fileName: "file",
key: "EncKey",
},
{
id: "a2",
url: "url",
size: "1100",
sizeName: "1.1 KB",
fileName: "file",
key: "EncKey",
},
],
fields: [
{
name: "EncryptedString",
value: "EncryptedString",
type: FieldType.Text,
linkedId: null,
},
{
name: "EncryptedString",
value: "EncryptedString",
type: FieldType.Hidden,
linkedId: null,
},
],
};
});
it("Convert", () => {
const cipher = new Cipher(cipherData);
expect(cipher).toEqual({
id: "id",
userId: "userId",
organizationId: "orgId",
folderId: "folderId",
name: { encryptedString: "EncryptedString", encryptionType: 0 },
notes: { encryptedString: "EncryptedString", encryptionType: 0 },
type: 1,
favorite: false,
organizationUseTotp: true,
edit: true,
viewPassword: true,
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
collectionIds: undefined,
localData: null,
deletedDate: null,
reprompt: 0,
login: {
passwordRevisionDate: new Date("2022-01-31T12:00:00.000Z"),
autofillOnPageLoad: false,
username: { encryptedString: "EncryptedString", encryptionType: 0 },
password: { encryptedString: "EncryptedString", encryptionType: 0 },
totp: { encryptedString: "EncryptedString", encryptionType: 0 },
uris: [{ match: 0, uri: { encryptedString: "EncryptedString", encryptionType: 0 } }],
},
attachments: [
{
fileName: { encryptedString: "file", encryptionType: 0 },
id: "a1",
key: { encryptedString: "EncKey", encryptionType: 0 },
size: "1100",
sizeName: "1.1 KB",
url: "url",
},
{
fileName: { encryptedString: "file", encryptionType: 0 },
id: "a2",
key: { encryptedString: "EncKey", encryptionType: 0 },
size: "1100",
sizeName: "1.1 KB",
url: "url",
},
],
fields: [
{
linkedId: null,
name: { encryptedString: "EncryptedString", encryptionType: 0 },
type: 0,
value: { encryptedString: "EncryptedString", encryptionType: 0 },
},
{
linkedId: null,
name: { encryptedString: "EncryptedString", encryptionType: 0 },
type: 1,
value: { encryptedString: "EncryptedString", encryptionType: 0 },
},
],
passwordHistory: [
{
lastUsedDate: new Date("2022-01-31T12:00:00.000Z"),
password: { encryptedString: "EncryptedString", encryptionType: 0 },
},
],
});
});
it("toCipherData", () => {
const cipher = new Cipher(cipherData);
expect(cipher.toCipherData("userId")).toEqual(cipherData);
});
it("Decrypt", async () => {
const cipher = new Cipher();
cipher.id = "id";
cipher.organizationId = "orgId";
cipher.folderId = "folderId";
cipher.edit = true;
cipher.viewPassword = true;
cipher.organizationUseTotp = true;
cipher.favorite = false;
cipher.revisionDate = new Date("2022-01-31T12:00:00.000Z");
cipher.type = CipherType.Login;
cipher.name = mockEnc("EncryptedString");
cipher.notes = mockEnc("EncryptedString");
cipher.deletedDate = null;
cipher.reprompt = CipherRepromptType.None;
const loginView = new LoginView();
loginView.username = "username";
loginView.password = "password";
const login = Substitute.for<Login>();
login.decrypt(Arg.any(), Arg.any()).resolves(loginView);
cipher.login = login;
const cipherView = await cipher.decrypt();
expect(cipherView).toMatchObject({
id: "id",
organizationId: "orgId",
folderId: "folderId",
name: "EncryptedString",
notes: "EncryptedString",
type: 1,
favorite: false,
organizationUseTotp: true,
edit: true,
viewPassword: true,
login: loginView,
attachments: null,
fields: null,
passwordHistory: null,
collectionIds: undefined,
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
deletedDate: null,
reprompt: 0,
localData: undefined,
});
});
});
describe("SecureNoteCipher", () => {
let cipherData: CipherData;
beforeEach(() => {
cipherData = {
id: "id",
organizationId: "orgId",
folderId: "folderId",
userId: "userId",
edit: true,
viewPassword: true,
organizationUseTotp: true,
favorite: false,
revisionDate: "2022-01-31T12:00:00.000Z",
type: CipherType.SecureNote,
name: "EncryptedString",
notes: "EncryptedString",
deletedDate: null,
reprompt: CipherRepromptType.None,
secureNote: {
type: SecureNoteType.Generic,
},
};
});
it("Convert", () => {
const cipher = new Cipher(cipherData);
expect(cipher).toEqual({
id: "id",
userId: "userId",
organizationId: "orgId",
folderId: "folderId",
name: { encryptedString: "EncryptedString", encryptionType: 0 },
notes: { encryptedString: "EncryptedString", encryptionType: 0 },
type: 2,
favorite: false,
organizationUseTotp: true,
edit: true,
viewPassword: true,
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
collectionIds: undefined,
localData: null,
deletedDate: null,
reprompt: 0,
secureNote: { type: SecureNoteType.Generic },
attachments: null,
fields: null,
passwordHistory: null,
});
});
it("toCipherData", () => {
const cipher = new Cipher(cipherData);
expect(cipher.toCipherData("userId")).toEqual(cipherData);
});
it("Decrypt", async () => {
const cipher = new Cipher();
cipher.id = "id";
cipher.organizationId = "orgId";
cipher.folderId = "folderId";
cipher.edit = true;
cipher.viewPassword = true;
cipher.organizationUseTotp = true;
cipher.favorite = false;
cipher.revisionDate = new Date("2022-01-31T12:00:00.000Z");
cipher.type = CipherType.SecureNote;
cipher.name = mockEnc("EncryptedString");
cipher.notes = mockEnc("EncryptedString");
cipher.deletedDate = null;
cipher.reprompt = CipherRepromptType.None;
cipher.secureNote = new SecureNote();
cipher.secureNote.type = SecureNoteType.Generic;
const cipherView = await cipher.decrypt();
expect(cipherView).toMatchObject({
id: "id",
organizationId: "orgId",
folderId: "folderId",
name: "EncryptedString",
notes: "EncryptedString",
type: 2,
favorite: false,
organizationUseTotp: true,
edit: true,
viewPassword: true,
secureNote: { type: 0 },
attachments: null,
fields: null,
passwordHistory: null,
collectionIds: undefined,
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
deletedDate: null,
reprompt: 0,
localData: undefined,
});
});
});
describe("CardCipher", () => {
let cipherData: CipherData;
beforeEach(() => {
cipherData = {
id: "id",
organizationId: "orgId",
folderId: "folderId",
userId: "userId",
edit: true,
viewPassword: true,
organizationUseTotp: true,
favorite: false,
revisionDate: "2022-01-31T12:00:00.000Z",
type: CipherType.Card,
name: "EncryptedString",
notes: "EncryptedString",
deletedDate: null,
reprompt: CipherRepromptType.None,
card: {
cardholderName: "EncryptedString",
brand: "EncryptedString",
number: "EncryptedString",
expMonth: "EncryptedString",
expYear: "EncryptedString",
code: "EncryptedString",
},
};
});
it("Convert", () => {
const cipher = new Cipher(cipherData);
expect(cipher).toEqual({
id: "id",
userId: "userId",
organizationId: "orgId",
folderId: "folderId",
name: { encryptedString: "EncryptedString", encryptionType: 0 },
notes: { encryptedString: "EncryptedString", encryptionType: 0 },
type: 3,
favorite: false,
organizationUseTotp: true,
edit: true,
viewPassword: true,
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
collectionIds: undefined,
localData: null,
deletedDate: null,
reprompt: 0,
card: {
cardholderName: { encryptedString: "EncryptedString", encryptionType: 0 },
brand: { encryptedString: "EncryptedString", encryptionType: 0 },
number: { encryptedString: "EncryptedString", encryptionType: 0 },
expMonth: { encryptedString: "EncryptedString", encryptionType: 0 },
expYear: { encryptedString: "EncryptedString", encryptionType: 0 },
code: { encryptedString: "EncryptedString", encryptionType: 0 },
},
attachments: null,
fields: null,
passwordHistory: null,
});
});
it("toCipherData", () => {
const cipher = new Cipher(cipherData);
expect(cipher.toCipherData("userId")).toEqual(cipherData);
});
it("Decrypt", async () => {
const cipher = new Cipher();
cipher.id = "id";
cipher.organizationId = "orgId";
cipher.folderId = "folderId";
cipher.edit = true;
cipher.viewPassword = true;
cipher.organizationUseTotp = true;
cipher.favorite = false;
cipher.revisionDate = new Date("2022-01-31T12:00:00.000Z");
cipher.type = CipherType.Card;
cipher.name = mockEnc("EncryptedString");
cipher.notes = mockEnc("EncryptedString");
cipher.deletedDate = null;
cipher.reprompt = CipherRepromptType.None;
const cardView = new CardView();
cardView.cardholderName = "cardholderName";
cardView.number = "4111111111111111";
const card = Substitute.for<Card>();
card.decrypt(Arg.any(), Arg.any()).resolves(cardView);
cipher.card = card;
const cipherView = await cipher.decrypt();
expect(cipherView).toMatchObject({
id: "id",
organizationId: "orgId",
folderId: "folderId",
name: "EncryptedString",
notes: "EncryptedString",
type: 3,
favorite: false,
organizationUseTotp: true,
edit: true,
viewPassword: true,
card: cardView,
attachments: null,
fields: null,
passwordHistory: null,
collectionIds: undefined,
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
deletedDate: null,
reprompt: 0,
localData: undefined,
});
});
});
describe("IdentityCipher", () => {
let cipherData: CipherData;
beforeEach(() => {
cipherData = {
id: "id",
organizationId: "orgId",
folderId: "folderId",
userId: "userId",
edit: true,
viewPassword: true,
organizationUseTotp: true,
favorite: false,
revisionDate: "2022-01-31T12:00:00.000Z",
type: CipherType.Identity,
name: "EncryptedString",
notes: "EncryptedString",
deletedDate: null,
reprompt: CipherRepromptType.None,
identity: {
title: "EncryptedString",
firstName: "EncryptedString",
middleName: "EncryptedString",
lastName: "EncryptedString",
address1: "EncryptedString",
address2: "EncryptedString",
address3: "EncryptedString",
city: "EncryptedString",
state: "EncryptedString",
postalCode: "EncryptedString",
country: "EncryptedString",
company: "EncryptedString",
email: "EncryptedString",
phone: "EncryptedString",
ssn: "EncryptedString",
username: "EncryptedString",
passportNumber: "EncryptedString",
licenseNumber: "EncryptedString",
},
};
});
it("Convert", () => {
const cipher = new Cipher(cipherData);
expect(cipher).toEqual({
id: "id",
userId: "userId",
organizationId: "orgId",
folderId: "folderId",
name: { encryptedString: "EncryptedString", encryptionType: 0 },
notes: { encryptedString: "EncryptedString", encryptionType: 0 },
type: 4,
favorite: false,
organizationUseTotp: true,
edit: true,
viewPassword: true,
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
collectionIds: undefined,
localData: null,
deletedDate: null,
reprompt: 0,
identity: {
title: { encryptedString: "EncryptedString", encryptionType: 0 },
firstName: { encryptedString: "EncryptedString", encryptionType: 0 },
middleName: { encryptedString: "EncryptedString", encryptionType: 0 },
lastName: { encryptedString: "EncryptedString", encryptionType: 0 },
address1: { encryptedString: "EncryptedString", encryptionType: 0 },
address2: { encryptedString: "EncryptedString", encryptionType: 0 },
address3: { encryptedString: "EncryptedString", encryptionType: 0 },
city: { encryptedString: "EncryptedString", encryptionType: 0 },
state: { encryptedString: "EncryptedString", encryptionType: 0 },
postalCode: { encryptedString: "EncryptedString", encryptionType: 0 },
country: { encryptedString: "EncryptedString", encryptionType: 0 },
company: { encryptedString: "EncryptedString", encryptionType: 0 },
email: { encryptedString: "EncryptedString", encryptionType: 0 },
phone: { encryptedString: "EncryptedString", encryptionType: 0 },
ssn: { encryptedString: "EncryptedString", encryptionType: 0 },
username: { encryptedString: "EncryptedString", encryptionType: 0 },
passportNumber: { encryptedString: "EncryptedString", encryptionType: 0 },
licenseNumber: { encryptedString: "EncryptedString", encryptionType: 0 },
},
attachments: null,
fields: null,
passwordHistory: null,
});
});
it("toCipherData", () => {
const cipher = new Cipher(cipherData);
expect(cipher.toCipherData("userId")).toEqual(cipherData);
});
it("Decrypt", async () => {
const cipher = new Cipher();
cipher.id = "id";
cipher.organizationId = "orgId";
cipher.folderId = "folderId";
cipher.edit = true;
cipher.viewPassword = true;
cipher.organizationUseTotp = true;
cipher.favorite = false;
cipher.revisionDate = new Date("2022-01-31T12:00:00.000Z");
cipher.type = CipherType.Identity;
cipher.name = mockEnc("EncryptedString");
cipher.notes = mockEnc("EncryptedString");
cipher.deletedDate = null;
cipher.reprompt = CipherRepromptType.None;
const identityView = new IdentityView();
identityView.firstName = "firstName";
identityView.lastName = "lastName";
const identity = Substitute.for<Identity>();
identity.decrypt(Arg.any(), Arg.any()).resolves(identityView);
cipher.identity = identity;
const cipherView = await cipher.decrypt();
expect(cipherView).toMatchObject({
id: "id",
organizationId: "orgId",
folderId: "folderId",
name: "EncryptedString",
notes: "EncryptedString",
type: 4,
favorite: false,
organizationUseTotp: true,
edit: true,
viewPassword: true,
identity: identityView,
attachments: null,
fields: null,
passwordHistory: null,
collectionIds: undefined,
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
deletedDate: null,
reprompt: 0,
localData: undefined,
});
});
});
});

View File

@@ -1,66 +0,0 @@
import { CollectionData } from "@/jslib/common/src/models/data/collectionData";
import { Collection } from "@/jslib/common/src/models/domain/collection";
import { mockEnc } from "../utils";
describe("Collection", () => {
let data: CollectionData;
beforeEach(() => {
data = {
id: "id",
organizationId: "orgId",
name: "encName",
externalId: "extId",
readOnly: true,
};
});
it("Convert from empty", () => {
const data = new CollectionData({} as any);
const card = new Collection(data);
expect(card).toEqual({
externalId: null,
hidePasswords: null,
id: null,
name: null,
organizationId: null,
readOnly: null,
});
});
it("Convert", () => {
const collection = new Collection(data);
expect(collection).toEqual({
id: "id",
organizationId: "orgId",
name: { encryptedString: "encName", encryptionType: 0 },
externalId: "extId",
readOnly: true,
hidePasswords: null,
});
});
it("Decrypt", async () => {
const collection = new Collection();
collection.id = "id";
collection.organizationId = "orgId";
collection.name = mockEnc("encName");
collection.externalId = "extId";
collection.readOnly = false;
collection.hidePasswords = false;
const view = await collection.decrypt();
expect(view).toEqual({
externalId: "extId",
hidePasswords: false,
id: "id",
name: "encName",
organizationId: "orgId",
readOnly: false,
});
});
});

View File

@@ -1,195 +0,0 @@
import Substitute, { Arg } from "@fluffy-spoon/substitute";
import { CryptoService } from "@/jslib/common/src/abstractions/crypto.service";
import { EncryptionType } from "@/jslib/common/src/enums/encryptionType";
import { EncString } from "@/jslib/common/src/models/domain/encString";
import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey";
import { ContainerService } from "@/jslib/common/src/services/container.service";
describe("EncString", () => {
afterEach(() => {
(window as any).bitwardenContainerService = undefined;
});
describe("Rsa2048_OaepSha256_B64", () => {
it("constructor", () => {
const encString = new EncString(EncryptionType.Rsa2048_OaepSha256_B64, "data");
expect(encString).toEqual({
data: "data",
encryptedString: "3.data",
encryptionType: 3,
});
});
describe("parse existing", () => {
it("valid", () => {
const encString = new EncString("3.data");
expect(encString).toEqual({
data: "data",
encryptedString: "3.data",
encryptionType: 3,
});
});
it("invalid", () => {
const encString = new EncString("3.data|test");
expect(encString).toEqual({
encryptedString: "3.data|test",
encryptionType: 3,
});
});
});
describe("decrypt", () => {
const encString = new EncString(EncryptionType.Rsa2048_OaepSha256_B64, "data");
const cryptoService = Substitute.for<CryptoService>();
cryptoService.getOrgKey(null).resolves(null);
cryptoService.decryptToUtf8(encString, Arg.any()).resolves("decrypted");
beforeEach(() => {
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
});
it("decrypts correctly", async () => {
const decrypted = await encString.decrypt(null);
expect(decrypted).toBe("decrypted");
});
it("result should be cached", async () => {
const decrypted = await encString.decrypt(null);
cryptoService.received(1).decryptToUtf8(Arg.any(), Arg.any());
expect(decrypted).toBe("decrypted");
});
});
});
describe("AesCbc256_B64", () => {
it("constructor", () => {
const encString = new EncString(EncryptionType.AesCbc256_B64, "data", "iv");
expect(encString).toEqual({
data: "data",
encryptedString: "0.iv|data",
encryptionType: 0,
iv: "iv",
});
});
describe("parse existing", () => {
it("valid", () => {
const encString = new EncString("0.iv|data");
expect(encString).toEqual({
data: "data",
encryptedString: "0.iv|data",
encryptionType: 0,
iv: "iv",
});
});
it("invalid", () => {
const encString = new EncString("0.iv|data|mac");
expect(encString).toEqual({
encryptedString: "0.iv|data|mac",
encryptionType: 0,
});
});
});
});
describe("AesCbc256_HmacSha256_B64", () => {
it("constructor", () => {
const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "data", "iv", "mac");
expect(encString).toEqual({
data: "data",
encryptedString: "2.iv|data|mac",
encryptionType: 2,
iv: "iv",
mac: "mac",
});
});
it("valid", () => {
const encString = new EncString("2.iv|data|mac");
expect(encString).toEqual({
data: "data",
encryptedString: "2.iv|data|mac",
encryptionType: 2,
iv: "iv",
mac: "mac",
});
});
it("invalid", () => {
const encString = new EncString("2.iv|data");
expect(encString).toEqual({
encryptedString: "2.iv|data",
encryptionType: 2,
});
});
});
it("Exit early if null", () => {
const encString = new EncString(null);
expect(encString).toEqual({
encryptedString: null,
});
});
describe("decrypt", () => {
it("throws exception when bitwarden container not initialized", async () => {
const encString = new EncString(null);
expect.assertions(1);
try {
await encString.decrypt(null);
} catch (e) {
expect(e.message).toEqual("global bitwardenContainerService not initialized.");
}
});
it("handles value it can't decrypt", async () => {
const encString = new EncString(null);
const cryptoService = Substitute.for<CryptoService>();
cryptoService.getOrgKey(null).resolves(null);
cryptoService.decryptToUtf8(encString, Arg.any()).throws("error");
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
const decrypted = await encString.decrypt(null);
expect(decrypted).toBe("[error: cannot decrypt]");
expect(encString).toEqual({
decryptedValue: "[error: cannot decrypt]",
encryptedString: null,
});
});
it("passes along key", async () => {
const encString = new EncString(null);
const key = Substitute.for<SymmetricCryptoKey>();
const cryptoService = Substitute.for<CryptoService>();
cryptoService.getOrgKey(null).resolves(null);
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
await encString.decrypt(null, key);
cryptoService.received().decryptToUtf8(encString, key);
});
});
});

View File

@@ -1,64 +0,0 @@
import { FieldType } from "@/jslib/common/src/enums/fieldType";
import { FieldData } from "@/jslib/common/src/models/data/fieldData";
import { Field } from "@/jslib/common/src/models/domain/field";
import { mockEnc } from "../utils";
describe("Field", () => {
let data: FieldData;
beforeEach(() => {
data = {
type: FieldType.Text,
name: "encName",
value: "encValue",
linkedId: null,
};
});
it("Convert from empty", () => {
const data = new FieldData();
const field = new Field(data);
expect(field).toEqual({
type: undefined,
name: null,
value: null,
linkedId: undefined,
});
});
it("Convert", () => {
const field = new Field(data);
expect(field).toEqual({
type: FieldType.Text,
name: { encryptedString: "encName", encryptionType: 0 },
value: { encryptedString: "encValue", encryptionType: 0 },
linkedId: null,
});
});
it("toFieldData", () => {
const field = new Field(data);
expect(field.toFieldData()).toEqual(data);
});
it("Decrypt", async () => {
const field = new Field();
field.type = FieldType.Text;
field.name = mockEnc("encName");
field.value = mockEnc("encValue");
const view = await field.decrypt(null);
expect(view).toEqual({
type: 0,
name: "encName",
value: "encValue",
newField: false,
showCount: false,
showValue: false,
});
});
});

View File

@@ -1,42 +0,0 @@
import { FolderData } from "@/jslib/common/src/models/data/folderData";
import { Folder } from "@/jslib/common/src/models/domain/folder";
import { mockEnc } from "../utils";
describe("Folder", () => {
let data: FolderData;
beforeEach(() => {
data = {
id: "id",
userId: "userId",
name: "encName",
revisionDate: "2022-01-31T12:00:00.000Z",
};
});
it("Convert", () => {
const field = new Folder(data);
expect(field).toEqual({
id: "id",
name: { encryptedString: "encName", encryptionType: 0 },
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
});
});
it("Decrypt", async () => {
const folder = new Folder();
folder.id = "id";
folder.name = mockEnc("encName");
folder.revisionDate = new Date("2022-01-31T12:00:00.000Z");
const view = await folder.decrypt();
expect(view).toEqual({
id: "id",
name: "encName",
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
});
});
});

View File

@@ -1,134 +0,0 @@
import { IdentityData } from "@/jslib/common/src/models/data/identityData";
import { Identity } from "@/jslib/common/src/models/domain/identity";
import { mockEnc } from "../utils";
describe("Identity", () => {
let data: IdentityData;
beforeEach(() => {
data = {
title: "enctitle",
firstName: "encfirstName",
middleName: "encmiddleName",
lastName: "enclastName",
address1: "encaddress1",
address2: "encaddress2",
address3: "encaddress3",
city: "enccity",
state: "encstate",
postalCode: "encpostalCode",
country: "enccountry",
company: "enccompany",
email: "encemail",
phone: "encphone",
ssn: "encssn",
username: "encusername",
passportNumber: "encpassportNumber",
licenseNumber: "enclicenseNumber",
};
});
it("Convert from empty", () => {
const data = new IdentityData();
const identity = new Identity(data);
expect(identity).toEqual({
address1: null,
address2: null,
address3: null,
city: null,
company: null,
country: null,
email: null,
firstName: null,
lastName: null,
licenseNumber: null,
middleName: null,
passportNumber: null,
phone: null,
postalCode: null,
ssn: null,
state: null,
title: null,
username: null,
});
});
it("Convert", () => {
const identity = new Identity(data);
expect(identity).toEqual({
title: { encryptedString: "enctitle", encryptionType: 0 },
firstName: { encryptedString: "encfirstName", encryptionType: 0 },
middleName: { encryptedString: "encmiddleName", encryptionType: 0 },
lastName: { encryptedString: "enclastName", encryptionType: 0 },
address1: { encryptedString: "encaddress1", encryptionType: 0 },
address2: { encryptedString: "encaddress2", encryptionType: 0 },
address3: { encryptedString: "encaddress3", encryptionType: 0 },
city: { encryptedString: "enccity", encryptionType: 0 },
state: { encryptedString: "encstate", encryptionType: 0 },
postalCode: { encryptedString: "encpostalCode", encryptionType: 0 },
country: { encryptedString: "enccountry", encryptionType: 0 },
company: { encryptedString: "enccompany", encryptionType: 0 },
email: { encryptedString: "encemail", encryptionType: 0 },
phone: { encryptedString: "encphone", encryptionType: 0 },
ssn: { encryptedString: "encssn", encryptionType: 0 },
username: { encryptedString: "encusername", encryptionType: 0 },
passportNumber: { encryptedString: "encpassportNumber", encryptionType: 0 },
licenseNumber: { encryptedString: "enclicenseNumber", encryptionType: 0 },
});
});
it("toIdentityData", () => {
const identity = new Identity(data);
expect(identity.toIdentityData()).toEqual(data);
});
it("Decrypt", async () => {
const identity = new Identity();
identity.title = mockEnc("mockTitle");
identity.firstName = mockEnc("mockFirstName");
identity.middleName = mockEnc("mockMiddleName");
identity.lastName = mockEnc("mockLastName");
identity.address1 = mockEnc("mockAddress1");
identity.address2 = mockEnc("mockAddress2");
identity.address3 = mockEnc("mockAddress3");
identity.city = mockEnc("mockCity");
identity.state = mockEnc("mockState");
identity.postalCode = mockEnc("mockPostalCode");
identity.country = mockEnc("mockCountry");
identity.company = mockEnc("mockCompany");
identity.email = mockEnc("mockEmail");
identity.phone = mockEnc("mockPhone");
identity.ssn = mockEnc("mockSsn");
identity.username = mockEnc("mockUsername");
identity.passportNumber = mockEnc("mockPassportNumber");
identity.licenseNumber = mockEnc("mockLicenseNumber");
const view = await identity.decrypt(null);
expect(view).toEqual({
_firstName: "mockFirstName",
_lastName: "mockLastName",
_subTitle: null,
address1: "mockAddress1",
address2: "mockAddress2",
address3: "mockAddress3",
city: "mockCity",
company: "mockCompany",
country: "mockCountry",
email: "mockEmail",
licenseNumber: "mockLicenseNumber",
middleName: "mockMiddleName",
passportNumber: "mockPassportNumber",
phone: "mockPhone",
postalCode: "mockPostalCode",
ssn: "mockSsn",
state: "mockState",
title: "mockTitle",
username: "mockUsername",
});
});
});

View File

@@ -1,101 +0,0 @@
import Substitute, { Arg } from "@fluffy-spoon/substitute";
import { UriMatchType } from "@/jslib/common/src/enums/uriMatchType";
import { LoginData } from "@/jslib/common/src/models/data/loginData";
import { Login } from "@/jslib/common/src/models/domain/login";
import { LoginUri } from "@/jslib/common/src/models/domain/loginUri";
import { LoginUriView } from "@/jslib/common/src/models/view/loginUriView";
import { mockEnc } from "../utils";
describe("Login DTO", () => {
it("Convert from empty LoginData", () => {
const data = new LoginData();
const login = new Login(data);
expect(login).toEqual({
passwordRevisionDate: null,
autofillOnPageLoad: undefined,
username: null,
password: null,
totp: null,
});
});
it("Convert from full LoginData", () => {
const data: LoginData = {
uris: [{ uri: "uri", match: UriMatchType.Domain }],
username: "username",
password: "password",
passwordRevisionDate: "2022-01-31T12:00:00.000Z",
totp: "123",
autofillOnPageLoad: false,
};
const login = new Login(data);
expect(login).toEqual({
passwordRevisionDate: new Date("2022-01-31T12:00:00.000Z"),
autofillOnPageLoad: false,
username: { encryptedString: "username", encryptionType: 0 },
password: { encryptedString: "password", encryptionType: 0 },
totp: { encryptedString: "123", encryptionType: 0 },
uris: [{ match: 0, uri: { encryptedString: "uri", encryptionType: 0 } }],
});
});
it("Initialize without LoginData", () => {
const login = new Login();
expect(login).toEqual({});
});
it("Decrypts correctly", async () => {
const loginUri = Substitute.for<LoginUri>();
const loginUriView = new LoginUriView();
loginUriView.uri = "decrypted uri";
loginUri.decrypt(Arg.any()).resolves(loginUriView);
const login = new Login();
login.uris = [loginUri];
login.username = mockEnc("encrypted username");
login.password = mockEnc("encrypted password");
login.passwordRevisionDate = new Date("2022-01-31T12:00:00.000Z");
login.totp = mockEnc("encrypted totp");
login.autofillOnPageLoad = true;
const loginView = await login.decrypt(null);
expect(loginView).toEqual({
username: "encrypted username",
password: "encrypted password",
passwordRevisionDate: new Date("2022-01-31T12:00:00.000Z"),
totp: "encrypted totp",
uris: [
{
match: null,
_uri: "decrypted uri",
_domain: null,
_hostname: null,
_host: null,
_canLaunch: null,
},
],
autofillOnPageLoad: true,
});
});
it("Converts from LoginData and back", () => {
const data: LoginData = {
uris: [{ uri: "uri", match: UriMatchType.Domain }],
username: "username",
password: "password",
passwordRevisionDate: "2022-01-31T12:00:00.000Z",
totp: "123",
autofillOnPageLoad: false,
};
const login = new Login(data);
const loginData = login.toLoginData();
expect(loginData).toEqual(data);
});
});

View File

@@ -1,57 +0,0 @@
import { UriMatchType } from "@/jslib/common/src/enums/uriMatchType";
import { LoginUriData } from "@/jslib/common/src/models/data/loginUriData";
import { LoginUri } from "@/jslib/common/src/models/domain/loginUri";
import { mockEnc } from "../utils";
describe("LoginUri", () => {
let data: LoginUriData;
beforeEach(() => {
data = {
uri: "encUri",
match: UriMatchType.Domain,
};
});
it("Convert from empty", () => {
const data = new LoginUriData();
const loginUri = new LoginUri(data);
expect(loginUri).toEqual({
match: null,
uri: null,
});
});
it("Convert", () => {
const loginUri = new LoginUri(data);
expect(loginUri).toEqual({
match: 0,
uri: { encryptedString: "encUri", encryptionType: 0 },
});
});
it("toLoginUriData", () => {
const loginUri = new LoginUri(data);
expect(loginUri.toLoginUriData()).toEqual(data);
});
it("Decrypt", async () => {
const loginUri = new LoginUri();
loginUri.match = UriMatchType.Exact;
loginUri.uri = mockEnc("uri");
const view = await loginUri.decrypt(null);
expect(view).toEqual({
_canLaunch: null,
_domain: null,
_host: null,
_hostname: null,
_uri: "uri",
match: 3,
});
});
});

View File

@@ -1,51 +0,0 @@
import { PasswordHistoryData } from "@/jslib/common/src/models/data/passwordHistoryData";
import { Password } from "@/jslib/common/src/models/domain/password";
import { mockEnc } from "../utils";
describe("Password", () => {
let data: PasswordHistoryData;
beforeEach(() => {
data = {
password: "encPassword",
lastUsedDate: "2022-01-31T12:00:00.000Z",
};
});
it("Convert from empty", () => {
const data = new PasswordHistoryData();
const password = new Password(data);
expect(password).toMatchObject({
password: null,
});
});
it("Convert", () => {
const password = new Password(data);
expect(password).toEqual({
password: { encryptedString: "encPassword", encryptionType: 0 },
lastUsedDate: new Date("2022-01-31T12:00:00.000Z"),
});
});
it("toPasswordHistoryData", () => {
const password = new Password(data);
expect(password.toPasswordHistoryData()).toEqual(data);
});
it("Decrypt", async () => {
const password = new Password();
password.password = mockEnc("password");
password.lastUsedDate = new Date("2022-01-31T12:00:00.000Z");
const view = await password.decrypt(null);
expect(view).toEqual({
password: "password",
lastUsedDate: new Date("2022-01-31T12:00:00.000Z"),
});
});
});

View File

@@ -1,46 +0,0 @@
import { SecureNoteType } from "@/jslib/common/src/enums/secureNoteType";
import { SecureNoteData } from "@/jslib/common/src/models/data/secureNoteData";
import { SecureNote } from "@/jslib/common/src/models/domain/secureNote";
describe("SecureNote", () => {
let data: SecureNoteData;
beforeEach(() => {
data = {
type: SecureNoteType.Generic,
};
});
it("Convert from empty", () => {
const data = new SecureNoteData();
const secureNote = new SecureNote(data);
expect(secureNote).toEqual({
type: undefined,
});
});
it("Convert", () => {
const secureNote = new SecureNote(data);
expect(secureNote).toEqual({
type: 0,
});
});
it("toSecureNoteData", () => {
const secureNote = new SecureNote(data);
expect(secureNote.toSecureNoteData()).toEqual(data);
});
it("Decrypt", async () => {
const secureNote = new SecureNote();
secureNote.type = SecureNoteType.Generic;
const view = await secureNote.decrypt(null);
expect(view).toEqual({
type: 0,
});
});
});

View File

@@ -1,144 +0,0 @@
import Substitute, { Arg, SubstituteOf } from "@fluffy-spoon/substitute";
import { CryptoService } from "@/jslib/common/src/abstractions/crypto.service";
import { SendType } from "@/jslib/common/src/enums/sendType";
import { SendData } from "@/jslib/common/src/models/data/sendData";
import { EncString } from "@/jslib/common/src/models/domain/encString";
import { Send } from "@/jslib/common/src/models/domain/send";
import { SendText } from "@/jslib/common/src/models/domain/sendText";
import { ContainerService } from "@/jslib/common/src/services/container.service";
import { makeStaticByteArray, mockEnc } from "../utils";
describe("Send", () => {
let data: SendData;
beforeEach(() => {
data = {
id: "id",
accessId: "accessId",
userId: "userId",
type: SendType.Text,
name: "encName",
notes: "encNotes",
text: {
text: "encText",
hidden: true,
},
file: null,
key: "encKey",
maxAccessCount: null,
accessCount: 10,
revisionDate: "2022-01-31T12:00:00.000Z",
expirationDate: "2022-01-31T12:00:00.000Z",
deletionDate: "2022-01-31T12:00:00.000Z",
password: "password",
disabled: false,
hideEmail: true,
};
});
it("Convert from empty", () => {
const data = new SendData();
const send = new Send(data);
expect(send).toEqual({
id: null,
accessId: null,
userId: null,
type: undefined,
name: null,
notes: null,
text: undefined,
file: undefined,
key: null,
maxAccessCount: undefined,
accessCount: undefined,
revisionDate: null,
expirationDate: null,
deletionDate: null,
password: undefined,
disabled: undefined,
hideEmail: undefined,
});
});
it("Convert", () => {
const send = new Send(data);
expect(send).toEqual({
id: "id",
accessId: "accessId",
userId: "userId",
type: SendType.Text,
name: { encryptedString: "encName", encryptionType: 0 },
notes: { encryptedString: "encNotes", encryptionType: 0 },
text: {
text: { encryptedString: "encText", encryptionType: 0 },
hidden: true,
},
key: { encryptedString: "encKey", encryptionType: 0 },
maxAccessCount: null,
accessCount: 10,
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
expirationDate: new Date("2022-01-31T12:00:00.000Z"),
deletionDate: new Date("2022-01-31T12:00:00.000Z"),
password: "password",
disabled: false,
hideEmail: true,
});
});
it("Decrypt", async () => {
const text = Substitute.for<SendText>();
text.decrypt(Arg.any()).resolves("textView" as any);
const send = new Send();
send.id = "id";
send.accessId = "accessId";
send.userId = "userId";
send.type = SendType.Text;
send.name = mockEnc("name");
send.notes = mockEnc("notes");
send.text = text;
send.key = mockEnc("key");
send.accessCount = 10;
send.revisionDate = new Date("2022-01-31T12:00:00.000Z");
send.expirationDate = new Date("2022-01-31T12:00:00.000Z");
send.deletionDate = new Date("2022-01-31T12:00:00.000Z");
send.password = "password";
send.disabled = false;
send.hideEmail = true;
const cryptoService = Substitute.for<CryptoService>();
cryptoService.decryptToBytes(send.key, null).resolves(makeStaticByteArray(32));
cryptoService.makeSendKey(Arg.any()).resolves("cryptoKey" as any);
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
const view = await send.decrypt();
text.received(1).decrypt("cryptoKey" as any);
(send.name as SubstituteOf<EncString>).received(1).decrypt(null, "cryptoKey" as any);
expect(view).toMatchObject({
id: "id",
accessId: "accessId",
name: "name",
notes: "notes",
type: 0,
key: expect.anything(),
cryptoKey: "cryptoKey",
file: expect.anything(),
text: "textView",
maxAccessCount: undefined,
accessCount: 10,
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
expirationDate: new Date("2022-01-31T12:00:00.000Z"),
deletionDate: new Date("2022-01-31T12:00:00.000Z"),
password: "password",
disabled: false,
hideEmail: true,
});
});
});

View File

@@ -1,84 +0,0 @@
import Substitute, { Arg } from "@fluffy-spoon/substitute";
import { SendType } from "@/jslib/common/src/enums/sendType";
import { SendAccess } from "@/jslib/common/src/models/domain/sendAccess";
import { SendText } from "@/jslib/common/src/models/domain/sendText";
import { SendAccessResponse } from "@/jslib/common/src/models/response/sendAccessResponse";
import { mockEnc } from "../utils";
describe("SendAccess", () => {
let request: SendAccessResponse;
beforeEach(() => {
request = {
id: "id",
type: SendType.Text,
name: "encName",
file: null,
text: {
text: "encText",
hidden: true,
},
expirationDate: new Date("2022-01-31T12:00:00.000Z"),
creatorIdentifier: "creatorIdentifier",
} as SendAccessResponse;
});
it("Convert from empty", () => {
const request = new SendAccessResponse({});
const sendAccess = new SendAccess(request);
expect(sendAccess).toEqual({
id: null,
type: undefined,
name: null,
creatorIdentifier: null,
expirationDate: null,
});
});
it("Convert", () => {
const sendAccess = new SendAccess(request);
expect(sendAccess).toEqual({
id: "id",
type: 0,
name: { encryptedString: "encName", encryptionType: 0 },
text: {
hidden: true,
text: { encryptedString: "encText", encryptionType: 0 },
},
expirationDate: new Date("2022-01-31T12:00:00.000Z"),
creatorIdentifier: "creatorIdentifier",
});
});
it("Decrypt", async () => {
const sendAccess = new SendAccess();
sendAccess.id = "id";
sendAccess.type = SendType.Text;
sendAccess.name = mockEnc("name");
const text = Substitute.for<SendText>();
text.decrypt(Arg.any()).resolves({} as any);
sendAccess.text = text;
sendAccess.expirationDate = new Date("2022-01-31T12:00:00.000Z");
sendAccess.creatorIdentifier = "creatorIdentifier";
const view = await sendAccess.decrypt(null);
text.received(1).decrypt(Arg.any());
expect(view).toEqual({
id: "id",
type: 0,
name: "name",
text: {},
file: expect.anything(),
expirationDate: new Date("2022-01-31T12:00:00.000Z"),
creatorIdentifier: "creatorIdentifier",
});
});
});

View File

@@ -1,57 +0,0 @@
import { SendFileData } from "@/jslib/common/src/models/data/sendFileData";
import { SendFile } from "@/jslib/common/src/models/domain/sendFile";
import { mockEnc } from "../utils";
describe("SendFile", () => {
let data: SendFileData;
beforeEach(() => {
data = {
id: "id",
size: "1100",
sizeName: "1.1 KB",
fileName: "encFileName",
};
});
it("Convert from empty", () => {
const data = new SendFileData();
const sendFile = new SendFile(data);
expect(sendFile).toEqual({
fileName: null,
id: null,
size: undefined,
sizeName: null,
});
});
it("Convert", () => {
const sendFile = new SendFile(data);
expect(sendFile).toEqual({
id: "id",
size: "1100",
sizeName: "1.1 KB",
fileName: { encryptedString: "encFileName", encryptionType: 0 },
});
});
it("Decrypt", async () => {
const sendFile = new SendFile();
sendFile.id = "id";
sendFile.size = "1100";
sendFile.sizeName = "1.1 KB";
sendFile.fileName = mockEnc("fileName");
const view = await sendFile.decrypt(null);
expect(view).toEqual({
fileName: "fileName",
id: "id",
size: "1100",
sizeName: "1.1 KB",
});
});
});

View File

@@ -1,47 +0,0 @@
import { SendTextData } from "@/jslib/common/src/models/data/sendTextData";
import { SendText } from "@/jslib/common/src/models/domain/sendText";
import { mockEnc } from "../utils";
describe("SendText", () => {
let data: SendTextData;
beforeEach(() => {
data = {
text: "encText",
hidden: false,
};
});
it("Convert from empty", () => {
const data = new SendTextData();
const secureNote = new SendText(data);
expect(secureNote).toEqual({
hidden: undefined,
text: null,
});
});
it("Convert", () => {
const secureNote = new SendText(data);
expect(secureNote).toEqual({
hidden: false,
text: { encryptedString: "encText", encryptionType: 0 },
});
});
it("Decrypt", async () => {
const secureNote = new SendText();
secureNote.text = mockEnc("text");
secureNote.hidden = true;
const view = await secureNote.decrypt(null);
expect(view).toEqual({
text: "text",
hidden: true,
});
});
});

View File

@@ -1,69 +0,0 @@
import { EncryptionType } from "@/jslib/common/src/enums/encryptionType";
import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey";
import { makeStaticByteArray } from "../utils";
describe("SymmetricCryptoKey", () => {
it("errors if no key", () => {
const t = () => {
new SymmetricCryptoKey(null);
};
expect(t).toThrowError("Must provide key");
});
describe("guesses encKey from key length", () => {
it("AesCbc256_B64", () => {
const key = makeStaticByteArray(32);
const cryptoKey = new SymmetricCryptoKey(key);
expect(cryptoKey).toEqual({
encKey: key,
encKeyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
encType: 0,
key: key,
keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
macKey: null,
});
});
it("AesCbc128_HmacSha256_B64", () => {
const key = makeStaticByteArray(32);
const cryptoKey = new SymmetricCryptoKey(key, EncryptionType.AesCbc128_HmacSha256_B64);
expect(cryptoKey).toEqual({
encKey: key.slice(0, 16),
encKeyB64: "AAECAwQFBgcICQoLDA0ODw==",
encType: 1,
key: key,
keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
macKey: key.slice(16, 32),
macKeyB64: "EBESExQVFhcYGRobHB0eHw==",
});
});
it("AesCbc256_HmacSha256_B64", () => {
const key = makeStaticByteArray(64);
const cryptoKey = new SymmetricCryptoKey(key);
expect(cryptoKey).toEqual({
encKey: key.slice(0, 32),
encKeyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
encType: 2,
key: key,
keyB64:
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==",
macKey: key.slice(32, 64),
macKeyB64: "ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=",
});
});
it("unknown length", () => {
const t = () => {
new SymmetricCryptoKey(makeStaticByteArray(30));
};
expect(t).toThrowError("Unable to determine encType.");
});
});
});

View File

@@ -1,31 +0,0 @@
import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { CryptoService } from "@/jslib/common/src/abstractions/crypto.service";
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
import { BitwardenJsonImporter } from "@/jslib/common/src/importers/bitwardenJsonImporter";
import { data as passwordProtectedData } from "./testData/bitwardenJson/passwordProtected.json";
describe("bitwarden json importer", () => {
let sut: BitwardenJsonImporter;
let cryptoService: SubstituteOf<CryptoService>;
let i18nService: SubstituteOf<I18nService>;
beforeEach(() => {
cryptoService = Substitute.for<CryptoService>();
i18nService = Substitute.for<I18nService>();
sut = new BitwardenJsonImporter(cryptoService, i18nService);
});
it("should fail if password is needed", async () => {
expect((await sut.parse(passwordProtectedData)).success).toBe(false);
});
it("should return password needed error message", async () => {
const expected = "Password required error message";
i18nService.t("importPasswordRequired").returns(expected);
expect((await sut.parse(passwordProtectedData)).errorMessage).toEqual(expected);
});
});

View File

@@ -1,113 +0,0 @@
import Substitute, { Arg, SubstituteOf } from "@fluffy-spoon/substitute";
import { CryptoService } from "@/jslib/common/src/abstractions/crypto.service";
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
import { KdfType } from "@/jslib/common/src/enums/kdfType";
import { BitwardenPasswordProtectedImporter } from "@/jslib/common/src/importers/bitwardenPasswordProtectedImporter";
import { Utils } from "@/jslib/common/src/misc/utils";
import { ImportResult } from "@/jslib/common/src/models/domain/importResult";
import { data as emptyDecryptedData } from "./testData/bitwardenJson/empty.json";
describe("BitwardenPasswordProtectedImporter", () => {
let importer: BitwardenPasswordProtectedImporter;
let cryptoService: SubstituteOf<CryptoService>;
let i18nService: SubstituteOf<I18nService>;
const password = Utils.newGuid();
const result = new ImportResult();
let jDoc: {
encrypted?: boolean;
passwordProtected?: boolean;
salt?: string;
kdfIterations?: any;
kdfType?: any;
encKeyValidation_DO_NOT_EDIT?: string;
data?: string;
};
beforeEach(() => {
cryptoService = Substitute.for<CryptoService>();
i18nService = Substitute.for<I18nService>();
jDoc = {
encrypted: true,
passwordProtected: true,
salt: "c2FsdA==",
kdfIterations: 100000,
kdfType: KdfType.PBKDF2_SHA256,
encKeyValidation_DO_NOT_EDIT: Utils.newGuid(),
data: Utils.newGuid(),
};
result.success = true;
importer = new BitwardenPasswordProtectedImporter(cryptoService, i18nService, password);
});
describe("Required Json Data", () => {
it("succeeds with default jdoc", async () => {
cryptoService.decryptToUtf8(Arg.any(), Arg.any()).resolves(emptyDecryptedData);
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(true);
});
it("fails if encrypted === false", async () => {
jDoc.encrypted = false;
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false);
});
it("fails if encrypted === null", async () => {
jDoc.encrypted = null;
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false);
});
it("fails if passwordProtected === false", async () => {
jDoc.passwordProtected = false;
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false);
});
it("fails if passwordProtected === null", async () => {
jDoc.passwordProtected = null;
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false);
});
it("fails if salt === null", async () => {
jDoc.salt = null;
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false);
});
it("fails if kdfIterations === null", async () => {
jDoc.kdfIterations = null;
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false);
});
it("fails if kdfIterations is not a number", async () => {
jDoc.kdfIterations = "not a number";
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false);
});
it("fails if kdfType === null", async () => {
jDoc.kdfType = null;
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false);
});
it("fails if kdfType is not a string", async () => {
jDoc.kdfType = "not a valid kdf type";
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false);
});
it("fails if kdfType is not a known kdfType", async () => {
jDoc.kdfType = -1;
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false);
});
it("fails if encKeyValidation_DO_NOT_EDIT === null", async () => {
jDoc.encKeyValidation_DO_NOT_EDIT = null;
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false);
});
it("fails if data === null", async () => {
jDoc.data = null;
expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false);
});
});
});

View File

@@ -1,367 +0,0 @@
import { CipherType } from "@/jslib/common/src/enums/cipherType";
import { DashlaneCsvImporter as Importer } from "@/jslib/common/src/importers/dashlaneImporters/dashlaneCsvImporter";
import { credentialsData } from "./testData/dashlaneCsv/credentials.csv";
import { identityData } from "./testData/dashlaneCsv/id.csv";
import { multiplePersonalInfoData } from "./testData/dashlaneCsv/multiplePersonalInfo.csv";
import { paymentsData } from "./testData/dashlaneCsv/payments.csv";
import { personalInfoData } from "./testData/dashlaneCsv/personalInfo.csv";
import { secureNoteData } from "./testData/dashlaneCsv/securenotes.csv";
describe("Dashlane CSV Importer", () => {
let importer: Importer;
beforeEach(() => {
importer = new Importer();
});
it("should parse login records", async () => {
const result = await importer.parse(credentialsData);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("example.com");
expect(cipher.login.username).toEqual("jdoe");
expect(cipher.login.password).toEqual("somePassword");
expect(cipher.login.totp).toEqual("someTOTPSeed");
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual("https://www.example.com");
expect(cipher.notes).toEqual("some note for example.com");
});
it("should parse an item and create a folder", async () => {
const result = await importer.parse(credentialsData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.folders.length).toBe(1);
expect(result.folders[0].name).toBe("Entertainment");
expect(result.folderRelationships[0]).toEqual([0, 0]);
});
it("should parse payment records", async () => {
const result = await importer.parse(paymentsData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(2);
// Account
const cipher = result.ciphers.shift();
expect(cipher.type).toBe(CipherType.Card);
expect(cipher.name).toBe("John's savings account");
expect(cipher.card.brand).toBeNull();
expect(cipher.card.cardholderName).toBe("John Doe");
expect(cipher.card.number).toBe("accountNumber");
expect(cipher.card.code).toBeNull();
expect(cipher.card.expMonth).toBeNull();
expect(cipher.card.expYear).toBeNull();
expect(cipher.fields.length).toBe(4);
expect(cipher.fields[0].name).toBe("type");
expect(cipher.fields[0].value).toBe("bank");
expect(cipher.fields[1].name).toBe("routing_number");
expect(cipher.fields[1].value).toBe("routingNumber");
expect(cipher.fields[2].name).toBe("country");
expect(cipher.fields[2].value).toBe("US");
expect(cipher.fields[3].name).toBe("issuing_bank");
expect(cipher.fields[3].value).toBe("US-ALLY");
// CreditCard
const cipher2 = result.ciphers.shift();
expect(cipher2.type).toBe(CipherType.Card);
expect(cipher2.name).toBe("John Doe");
expect(cipher2.card.brand).toBe("Visa");
expect(cipher2.card.cardholderName).toBe("John Doe");
expect(cipher2.card.number).toBe("41111111111111111");
expect(cipher2.card.code).toBe("123");
expect(cipher2.card.expMonth).toBe("01");
expect(cipher2.card.expYear).toBe("23");
expect(cipher2.fields.length).toBe(2);
expect(cipher2.fields[0].name).toBe("type");
expect(cipher2.fields[0].value).toBe("credit_card");
expect(cipher2.fields[1].name).toBe("country");
expect(cipher2.fields[1].value).toBe("US");
});
it("should parse ids records", async () => {
const result = await importer.parse(identityData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
// Type card
const cipher = result.ciphers.shift();
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("John Doe card");
expect(cipher.identity.fullName).toBe("John Doe");
expect(cipher.identity.firstName).toBe("John");
expect(cipher.identity.middleName).toBeNull();
expect(cipher.identity.lastName).toBe("Doe");
expect(cipher.identity.licenseNumber).toBe("123123123");
expect(cipher.fields.length).toBe(3);
expect(cipher.fields[0].name).toEqual("type");
expect(cipher.fields[0].value).toEqual("card");
expect(cipher.fields[1].name).toEqual("issue_date");
expect(cipher.fields[1].value).toEqual("2022-1-30");
expect(cipher.fields[2].name).toEqual("expiration_date");
expect(cipher.fields[2].value).toEqual("2032-1-30");
// Type passport
const cipher2 = result.ciphers.shift();
expect(cipher2.type).toBe(CipherType.Identity);
expect(cipher2.name).toBe("John Doe passport");
expect(cipher2.identity.fullName).toBe("John Doe");
expect(cipher2.identity.firstName).toBe("John");
expect(cipher2.identity.middleName).toBeNull();
expect(cipher2.identity.lastName).toBe("Doe");
expect(cipher2.identity.passportNumber).toBe("123123123");
expect(cipher2.fields.length).toBe(4);
expect(cipher2.fields[0].name).toEqual("type");
expect(cipher2.fields[0].value).toEqual("passport");
expect(cipher2.fields[1].name).toEqual("issue_date");
expect(cipher2.fields[1].value).toEqual("2022-1-30");
expect(cipher2.fields[2].name).toEqual("expiration_date");
expect(cipher2.fields[2].value).toEqual("2032-1-30");
expect(cipher2.fields[3].name).toEqual("place_of_issue");
expect(cipher2.fields[3].value).toEqual("somewhere in Germany");
// Type license
const cipher3 = result.ciphers.shift();
expect(cipher3.type).toBe(CipherType.Identity);
expect(cipher3.name).toBe("John Doe license");
expect(cipher3.identity.fullName).toBe("John Doe");
expect(cipher3.identity.firstName).toBe("John");
expect(cipher3.identity.middleName).toBeNull();
expect(cipher3.identity.lastName).toBe("Doe");
expect(cipher3.identity.licenseNumber).toBe("1234556");
expect(cipher3.identity.state).toBe("DC");
expect(cipher3.fields.length).toBe(3);
expect(cipher3.fields[0].name).toEqual("type");
expect(cipher3.fields[0].value).toEqual("license");
expect(cipher3.fields[1].name).toEqual("issue_date");
expect(cipher3.fields[1].value).toEqual("2022-8-10");
expect(cipher3.fields[2].name).toEqual("expiration_date");
expect(cipher3.fields[2].value).toEqual("2022-10-10");
// Type social_security
const cipher4 = result.ciphers.shift();
expect(cipher4.type).toBe(CipherType.Identity);
expect(cipher4.name).toBe("John Doe social_security");
expect(cipher4.identity.fullName).toBe("John Doe");
expect(cipher4.identity.firstName).toBe("John");
expect(cipher4.identity.middleName).toBeNull();
expect(cipher4.identity.lastName).toBe("Doe");
expect(cipher4.identity.ssn).toBe("123123123");
expect(cipher4.fields.length).toBe(1);
expect(cipher4.fields[0].name).toEqual("type");
expect(cipher4.fields[0].value).toEqual("social_security");
// Type tax_number
const cipher5 = result.ciphers.shift();
expect(cipher5.type).toBe(CipherType.Identity);
expect(cipher5.name).toBe("tax_number");
expect(cipher5.identity.licenseNumber).toBe("123123123");
expect(cipher5.fields.length).toBe(1);
expect(cipher5.fields[0].name).toEqual("type");
expect(cipher5.fields[0].value).toEqual("tax_number");
});
it("should parse secureNote records", async () => {
const result = await importer.parse(secureNoteData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers.shift();
expect(cipher.type).toBe(CipherType.SecureNote);
expect(cipher.name).toBe("01");
expect(cipher.notes).toBe("test");
});
it("should parse personal information records (multiple identities)", async () => {
const result = await importer.parse(multiplePersonalInfoData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(6);
// name
const cipher = result.ciphers.shift();
expect(cipher.type).toBe(CipherType.SecureNote);
expect(cipher.name).toBe("MR John Doe");
expect(cipher.fields.length).toBe(7);
expect(cipher.fields[0].name).toEqual("type");
expect(cipher.fields[0].value).toEqual("name");
expect(cipher.fields[1].name).toEqual("title");
expect(cipher.fields[1].value).toEqual("MR");
expect(cipher.fields[2].name).toEqual("first_name");
expect(cipher.fields[2].value).toEqual("John");
expect(cipher.fields[3].name).toEqual("last_name");
expect(cipher.fields[3].value).toEqual("Doe");
expect(cipher.fields[4].name).toEqual("login");
expect(cipher.fields[4].value).toEqual("jdoe");
expect(cipher.fields[5].name).toEqual("date_of_birth");
expect(cipher.fields[5].value).toEqual("2022-01-30");
expect(cipher.fields[6].name).toEqual("place_of_birth");
expect(cipher.fields[6].value).toEqual("world");
// email
const cipher2 = result.ciphers.shift();
expect(cipher2.type).toBe(CipherType.SecureNote);
expect(cipher2.name).toBe("Johns email");
expect(cipher2.fields.length).toBe(4);
expect(cipher2.fields[0].name).toEqual("type");
expect(cipher2.fields[0].value).toEqual("email");
expect(cipher2.fields[1].name).toEqual("email");
expect(cipher2.fields[1].value).toEqual("jdoe@example.com");
expect(cipher2.fields[2].name).toEqual("email_type");
expect(cipher2.fields[2].value).toEqual("personal");
expect(cipher2.fields[3].name).toEqual("item_name");
expect(cipher2.fields[3].value).toEqual("Johns email");
// number
const cipher3 = result.ciphers.shift();
expect(cipher3.type).toBe(CipherType.SecureNote);
expect(cipher3.name).toBe("John's number");
expect(cipher3.fields.length).toBe(3);
expect(cipher3.fields[0].name).toEqual("type");
expect(cipher3.fields[0].value).toEqual("number");
expect(cipher3.fields[1].name).toEqual("item_name");
expect(cipher3.fields[1].value).toEqual("John's number");
expect(cipher3.fields[2].name).toEqual("phone_number");
expect(cipher3.fields[2].value).toEqual("+49123123123");
// address
const cipher4 = result.ciphers.shift();
expect(cipher4.type).toBe(CipherType.SecureNote);
expect(cipher4.name).toBe("John's home address");
expect(cipher4.fields.length).toBe(12);
expect(cipher4.fields[0].name).toEqual("type");
expect(cipher4.fields[0].value).toEqual("address");
expect(cipher4.fields[1].name).toEqual("item_name");
expect(cipher4.fields[1].value).toEqual("John's home address");
expect(cipher4.fields[2].name).toEqual("address");
expect(cipher4.fields[2].value).toEqual("1 some street");
expect(cipher4.fields[3].name).toEqual("country");
expect(cipher4.fields[3].value).toEqual("de");
expect(cipher4.fields[4].name).toEqual("state");
expect(cipher4.fields[4].value).toEqual("DE-0-NW");
expect(cipher4.fields[5].name).toEqual("city");
expect(cipher4.fields[5].value).toEqual("some city");
expect(cipher4.fields[6].name).toEqual("zip");
expect(cipher4.fields[6].value).toEqual("123123");
expect(cipher4.fields[7].name).toEqual("address_recipient");
expect(cipher4.fields[7].value).toEqual("John");
expect(cipher4.fields[8].name).toEqual("address_building");
expect(cipher4.fields[8].value).toEqual("1");
expect(cipher4.fields[9].name).toEqual("address_apartment");
expect(cipher4.fields[9].value).toEqual("1");
expect(cipher4.fields[10].name).toEqual("address_floor");
expect(cipher4.fields[10].value).toEqual("1");
expect(cipher4.fields[11].name).toEqual("address_door_code");
expect(cipher4.fields[11].value).toEqual("123");
// website
const cipher5 = result.ciphers.shift();
expect(cipher5.type).toBe(CipherType.SecureNote);
expect(cipher5.name).toBe("Website");
expect(cipher5.fields.length).toBe(3);
expect(cipher5.fields[0].name).toEqual("type");
expect(cipher5.fields[0].value).toEqual("website");
expect(cipher5.fields[1].name).toEqual("item_name");
expect(cipher5.fields[1].value).toEqual("Website");
expect(cipher5.fields[2].name).toEqual("url");
expect(cipher5.fields[2].value).toEqual("website.com");
// 2nd name/identity
const cipher6 = result.ciphers.shift();
expect(cipher6.type).toBe(CipherType.SecureNote);
expect(cipher6.name).toBe("Mrs Jane Doe");
expect(cipher6.fields.length).toBe(7);
expect(cipher6.fields[0].name).toEqual("type");
expect(cipher6.fields[0].value).toEqual("name");
expect(cipher6.fields[1].name).toEqual("title");
expect(cipher6.fields[1].value).toEqual("Mrs");
expect(cipher6.fields[2].name).toEqual("first_name");
expect(cipher6.fields[2].value).toEqual("Jane");
expect(cipher6.fields[3].name).toEqual("last_name");
expect(cipher6.fields[3].value).toEqual("Doe");
expect(cipher6.fields[4].name).toEqual("login");
expect(cipher6.fields[4].value).toEqual("jdoe");
expect(cipher6.fields[5].name).toEqual("date_of_birth");
expect(cipher6.fields[5].value).toEqual("2022-01-30");
expect(cipher6.fields[6].name).toEqual("place_of_birth");
expect(cipher6.fields[6].value).toEqual("earth");
});
it("should combine personal information records to one identity if only one identity present", async () => {
const result = await importer.parse(personalInfoData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("MR John Doe");
expect(cipher.identity.fullName).toBe("MR John Doe");
expect(cipher.identity.title).toBe("MR");
expect(cipher.identity.firstName).toBe("John");
expect(cipher.identity.middleName).toBeNull();
expect(cipher.identity.lastName).toBe("Doe");
expect(cipher.identity.username).toBe("jdoe");
expect(cipher.identity.email).toBe("jdoe@example.com");
expect(cipher.identity.phone).toBe("+49123123123");
expect(cipher.fields.length).toBe(9);
expect(cipher.fields[0].name).toBe("date_of_birth");
expect(cipher.fields[0].value).toBe("2022-01-30");
expect(cipher.fields[1].name).toBe("place_of_birth");
expect(cipher.fields[1].value).toBe("world");
expect(cipher.fields[2].name).toBe("email_type");
expect(cipher.fields[2].value).toBe("personal");
expect(cipher.fields[3].name).toBe("address_recipient");
expect(cipher.fields[3].value).toBe("John");
expect(cipher.fields[4].name).toBe("address_building");
expect(cipher.fields[4].value).toBe("1");
expect(cipher.fields[5].name).toBe("address_apartment");
expect(cipher.fields[5].value).toBe("1");
expect(cipher.fields[6].name).toBe("address_floor");
expect(cipher.fields[6].value).toBe("1");
expect(cipher.fields[7].name).toBe("address_door_code");
expect(cipher.fields[7].value).toBe("123");
expect(cipher.fields[8].name).toBe("url");
expect(cipher.fields[8].value).toBe("website.com");
});
});

View File

@@ -1,74 +0,0 @@
import { FirefoxCsvImporter as Importer } from "@/jslib/common/src/importers/firefoxCsvImporter";
import { CipherView } from "@/jslib/common/src/models/view/cipherView";
import { LoginUriView } from "@/jslib/common/src/models/view/loginUriView";
import { LoginView } from "@/jslib/common/src/models/view/loginView";
import { data as firefoxAccountsData } from "./testData/firefoxCsv/firefoxAccountsData.csv";
import { data as simplePasswordData } from "./testData/firefoxCsv/simplePasswordData.csv";
const CipherData = [
{
title: "should parse password",
csv: simplePasswordData,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "example.com",
login: Object.assign(new LoginView(), {
username: "foo",
password: "bar",
uris: [
Object.assign(new LoginUriView(), {
uri: "https://example.com",
}),
],
}),
notes: null,
type: 1,
}),
},
{
title: 'should skip "chrome://FirefoxAccounts"',
csv: firefoxAccountsData,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "example.com",
login: Object.assign(new LoginView(), {
username: "foo",
password: "bar",
uris: [
Object.assign(new LoginUriView(), {
uri: "https://example.com",
}),
],
}),
notes: null,
type: 1,
}),
},
];
describe("Firefox CSV Importer", () => {
CipherData.forEach((data) => {
it(data.title, async () => {
const importer = new Importer();
const result = await importer.parse(data.csv);
expect(result != null).toBe(true);
expect(result.ciphers.length).toBeGreaterThan(0);
const cipher = result.ciphers.shift();
let property: keyof typeof data.expected;
for (property in data.expected) {
// eslint-disable-next-line
if (data.expected.hasOwnProperty(property)) {
// eslint-disable-next-line
expect(cipher.hasOwnProperty(property)).toBe(true);
expect(cipher[property]).toEqual(data.expected[property]);
}
}
});
});
});

View File

@@ -1,77 +0,0 @@
import { FSecureFskImporter as Importer } from "@/jslib/common/src/importers/fsecureFskImporter";
const TestDataWithStyleSetToWebsite: string = JSON.stringify({
data: {
"8d58b5cf252dd06fbd98f5289e918ab1": {
color: "#00baff",
reatedDate: 1609302913,
creditCvv: "",
creditExpiry: "",
creditNumber: "",
favorite: 0,
modifiedDate: 1609302913,
notes: "note",
password: "word",
passwordList: [],
passwordModifiedDate: 1609302913,
rev: 1,
service: "My first pass",
style: "website",
type: 1,
url: "https://bitwarden.com",
username: "pass",
},
},
});
const TestDataWithStyleSetToGlobe: string = JSON.stringify({
data: {
"8d58b5cf252dd06fbd98f5289e918ab1": {
color: "#00baff",
reatedDate: 1609302913,
creditCvv: "",
creditExpiry: "",
creditNumber: "",
favorite: 0,
modifiedDate: 1609302913,
notes: "note",
password: "word",
passwordList: [],
passwordModifiedDate: 1609302913,
rev: 1,
service: "My first pass",
style: "globe",
type: 1,
url: "https://bitwarden.com",
username: "pass",
},
},
});
describe("FSecure FSK Importer", () => {
it("should parse data with style set to website", async () => {
const importer = new Importer();
const result = await importer.parse(TestDataWithStyleSetToWebsite);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.login.username).toEqual("pass");
expect(cipher.login.password).toEqual("word");
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual("https://bitwarden.com");
});
it("should parse data with style set to globe", async () => {
const importer = new Importer();
const result = await importer.parse(TestDataWithStyleSetToGlobe);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.login.username).toEqual("pass");
expect(cipher.login.password).toEqual("word");
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual("https://bitwarden.com");
});
});

View File

@@ -1,189 +0,0 @@
import { KeePass2XmlImporter as Importer } from "@/jslib/common/src/importers/keepass2XmlImporter";
const TestData = `<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<KeePassFile>
<Meta>
<Generator>KeePass</Generator>
<DatabaseName />
<DatabaseNameChanged>2016-12-31T21:33:52Z</DatabaseNameChanged>
<DatabaseDescription />
<DatabaseDescriptionChanged>2016-12-31T21:33:52Z</DatabaseDescriptionChanged>
<DefaultUserName />
<DefaultUserNameChanged>2016-12-31T21:33:52Z</DefaultUserNameChanged>
<MaintenanceHistoryDays>365</MaintenanceHistoryDays>
<Color />
<MasterKeyChanged>2016-12-31T21:33:59Z</MasterKeyChanged>
<MasterKeyChangeRec>-1</MasterKeyChangeRec>
<MasterKeyChangeForce>-1</MasterKeyChangeForce>
<MemoryProtection>
<ProtectTitle>False</ProtectTitle>
<ProtectUserName>False</ProtectUserName>
<ProtectPassword>True</ProtectPassword>
<ProtectURL>False</ProtectURL>
<ProtectNotes>False</ProtectNotes>
</MemoryProtection>
<RecycleBinEnabled>True</RecycleBinEnabled>
<RecycleBinUUID>AAAAAAAAAAAAAAAAAAAAAA==</RecycleBinUUID>
<RecycleBinChanged>2016-12-31T21:33:52Z</RecycleBinChanged>
<EntryTemplatesGroup>AAAAAAAAAAAAAAAAAAAAAA==</EntryTemplatesGroup>
<EntryTemplatesGroupChanged>2016-12-31T21:33:52Z</EntryTemplatesGroupChanged>
<HistoryMaxItems>10</HistoryMaxItems>
<HistoryMaxSize>6291456</HistoryMaxSize>
<LastSelectedGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastSelectedGroup>
<LastTopVisibleGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleGroup>
<Binaries />
<CustomData />
</Meta>
<Root>
<Group>
<UUID>KvS57lVwl13AfGFLwkvq4Q==</UUID>
<Name>Root</Name>
<Notes />
<IconID>48</IconID>
<Times>
<CreationTime>2016-12-31T21:33:52Z</CreationTime>
<LastModificationTime>2016-12-31T21:33:52Z</LastModificationTime>
<LastAccessTime>2017-01-01T22:58:00Z</LastAccessTime>
<ExpiryTime>2016-12-31T21:33:52Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>1</UsageCount>
<LocationChanged>2016-12-31T21:33:52Z</LocationChanged>
</Times>
<IsExpanded>True</IsExpanded>
<DefaultAutoTypeSequence />
<EnableAutoType>null</EnableAutoType>
<EnableSearching>null</EnableSearching>
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
<Group>
<UUID>P0ParXgGMBW6caOL2YrhqQ==</UUID>
<Name>Folder2</Name>
<Notes>a note about the folder</Notes>
<IconID>48</IconID>
<Times>
<CreationTime>2016-12-31T21:43:30Z</CreationTime>
<LastModificationTime>2016-12-31T21:43:43Z</LastModificationTime>
<LastAccessTime>2017-01-01T22:58:00Z</LastAccessTime>
<ExpiryTime>2016-12-31T21:43:30Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>1</UsageCount>
<LocationChanged>2016-12-31T21:43:43Z</LocationChanged>
</Times>
<IsExpanded>True</IsExpanded>
<DefaultAutoTypeSequence />
<EnableAutoType>null</EnableAutoType>
<EnableSearching>null</EnableSearching>
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
<Entry>
<UUID>fAa543oYlgnJKkhKag5HLw==</UUID>
<IconID>1</IconID>
<ForegroundColor />
<BackgroundColor />
<OverrideURL />
<Tags />
<Times>
<CreationTime>2016-12-31T21:34:13Z</CreationTime>
<LastModificationTime>2016-12-31T21:40:23Z</LastModificationTime>
<LastAccessTime>2016-12-31T21:40:23Z</LastAccessTime>
<ExpiryTime>2016-12-31T21:34:13Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2016-12-31T21:43:48Z</LocationChanged>
</Times>
<String>
<Key>att2</Key>
<Value>att2value</Value>
</String>
<String>
<Key>attr1</Key>
<Value>att1value
line1
line2</Value>
</String>
<String>
<Key>Notes</Key>
<Value>This is a note!!!
line1
line2</Value>
</String>
<String>
<Key>Password</Key>
<Value ProtectInMemory="True">googpass</Value>
</String>
<String>
<Key>Title</Key>
<Value>Google</Value>
</String>
<String>
<Key>URL</Key>
<Value>google.com</Value>
</String>
<String>
<Key>UserName</Key>
<Value>googleuser</Value>
</String>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
</AutoType>
<History>
<Entry>
<UUID>fAa543oYlgnJKkhKag5HLw==</UUID>
<IconID>0</IconID>
<ForegroundColor />
<BackgroundColor />
<OverrideURL />
<Tags />
<Times>
<CreationTime>2016-12-31T21:34:13Z</CreationTime>
<LastModificationTime>2016-12-31T21:34:40Z</LastModificationTime>
<LastAccessTime>2016-12-31T21:34:40Z</LastAccessTime>
<ExpiryTime>2016-12-31T21:34:13Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2016-12-31T21:34:40Z</LocationChanged>
</Times>
<String>
<Key>Notes</Key>
<Value>This is a note!!!
line1
line2</Value>
</String>
<String>
<Key>Password</Key>
<Value ProtectInMemory="True">googpass</Value>
</String>
<String>
<Key>Title</Key>
<Value>Google</Value>
</String>
<String>
<Key>URL</Key>
<Value>google.com</Value>
</String>
<String>
<Key>UserName</Key>
<Value>googleuser</Value>
</String>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
</AutoType>
</Entry>
</History>
</Entry>
</Group>
</Group>
<DeletedObjects />
</Root>
</KeePassFile>`;
describe("KeePass2 Xml Importer", () => {
it("should parse XML data", async () => {
const importer = new Importer();
const result = await importer.parse(TestData);
expect(result != null).toBe(true);
});
});

View File

@@ -1,108 +0,0 @@
import { KeeperJsonImporter as Importer } from "@/jslib/common/src/importers/keeperImporters/keeperJsonImporter";
import { Utils } from "@/jslib/common/src/misc/utils";
import { testData as TestData } from "./testData/keeperJson/testData";
describe("Keeper Json Importer", () => {
const testDataJson = JSON.stringify(TestData);
let importer: Importer;
beforeEach(() => {
importer = new Importer();
});
it("should parse login data", async () => {
const result = await importer.parse(testDataJson);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("Bank Account 1");
expect(cipher.login.username).toEqual("customer1234");
expect(cipher.login.password).toEqual("4813fJDHF4239fdk");
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual("https://chase.com");
expect(cipher.notes).toEqual("These are some notes.");
const cipher2 = result.ciphers.shift();
expect(cipher2.name).toEqual("Bank Account 2");
expect(cipher2.login.username).toEqual("mybankusername");
expect(cipher2.login.password).toEqual("w4k4k193f$^&@#*%2");
expect(cipher2.login.uris.length).toEqual(1);
const uriView2 = cipher2.login.uris.shift();
expect(uriView2.uri).toEqual("https://amex.com");
expect(cipher2.notes).toEqual("Some great information here.");
const cipher3 = result.ciphers.shift();
expect(cipher3.name).toEqual("Some Account");
expect(cipher3.login.username).toEqual("someUserName");
expect(cipher3.login.password).toEqual("w4k4k1wergf$^&@#*%2");
expect(cipher3.notes).toBeNull();
expect(cipher3.fields).toBeNull();
expect(cipher3.login.uris.length).toEqual(1);
const uriView3 = cipher3.login.uris.shift();
expect(uriView3.uri).toEqual("https://example.com");
});
it("should import TOTP when present", async () => {
const result = await importer.parse(testDataJson);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.login.totp).toBeNull();
// 2nd Cipher
const cipher2 = result.ciphers.shift();
expect(cipher2.login.totp).toEqual(
"otpauth://totp/Amazon:me@company.com?secret=JBSWY3DPEHPK3PXP&issuer=Amazon&algorithm=SHA1&digits=6&period=30"
);
});
it("should parse custom fields", async () => {
const result = await importer.parse(testDataJson);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.fields.length).toBe(1);
expect(cipher.fields[0].name).toEqual("Account Number");
expect(cipher.fields[0].value).toEqual("123-456-789");
// 2nd Cipher
const cipher2 = result.ciphers.shift();
expect(cipher2.fields.length).toBe(2);
expect(cipher2.fields[0].name).toEqual("Security Group");
expect(cipher2.fields[0].value).toEqual("Public");
expect(cipher2.fields[1].name).toEqual("IP Address");
expect(cipher2.fields[1].value).toEqual("12.45.67.8");
});
it("should create folders and assigned ciphers to them", async () => {
const result = await importer.parse(testDataJson);
expect(result != null).toBe(true);
const folders = result.folders;
expect(folders.length).toBe(2);
expect(folders[0].name).toBe("Optional Private Folder 1");
expect(folders[1].name).toBe("My Customer 1");
expect(result.folderRelationships[0]).toEqual([0, 0]);
expect(result.folderRelationships[1]).toEqual([1, 0]);
expect(result.folderRelationships[2]).toEqual([1, 1]);
});
it("should create collections if part of an organization", async () => {
importer.organizationId = Utils.newGuid();
const result = await importer.parse(testDataJson);
expect(result != null).toBe(true);
const collections = result.collections;
expect(collections.length).toBe(2);
expect(collections[0].name).toBe("Optional Private Folder 1");
expect(collections[1].name).toBe("My Customer 1");
expect(result.collectionRelationships[0]).toEqual([0, 0]);
expect(result.collectionRelationships[1]).toEqual([1, 0]);
expect(result.collectionRelationships[2]).toEqual([1, 1]);
});
});

View File

@@ -1,202 +0,0 @@
import { CipherType } from "@/jslib/common/src/enums/cipherType";
import { FieldType } from "@/jslib/common/src/enums/fieldType";
import { LastPassCsvImporter as Importer } from "@/jslib/common/src/importers/lastpassCsvImporter";
import { ImportResult } from "@/jslib/common/src/models/domain/importResult";
import { CipherView } from "@/jslib/common/src/models/view/cipherView";
import { FieldView } from "@/jslib/common/src/models/view/fieldView";
function baseExcept(result: ImportResult) {
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
}
function expectLogin(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Login);
expect(cipher.name).toBe("example.com");
expect(cipher.notes).toBe("super secure notes");
expect(cipher.login.uri).toBe("http://example.com");
expect(cipher.login.username).toBe("someUser");
expect(cipher.login.password).toBe("myPassword");
expect(cipher.login.totp).toBe("Y64VEVMBTSXCYIWRSHRNDZW62MPGVU2G");
}
const CipherData = [
{
title: "should parse expiration date",
csv: `url,username,password,extra,name,grouping,fav
http://sn,,,"NoteType:Credit Card
Name on Card:John Doe
Type:
Number:1234567812345678
Security Code:123
Start Date:October,2017
Expiration Date:June,2020
Notes:some text
",Credit-card,,0`,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "Credit-card",
notes: "some text\n",
type: 3,
card: {
cardholderName: "John Doe",
number: "1234567812345678",
code: "123",
expYear: "2020",
expMonth: "6",
},
fields: [
Object.assign(new FieldView(), {
name: "Start Date",
value: "October,2017",
type: FieldType.Text,
}),
],
}),
},
{
title: "should parse blank card note",
csv: `url,username,password,extra,name,grouping,fav
http://sn,,,"NoteType:Credit Card
Name on Card:
Type:
Number:
Security Code:
Start Date:,
Expiration Date:,
Notes:",empty,,0`,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "empty",
notes: null,
type: 3,
card: {
expMonth: undefined,
},
fields: [
Object.assign(new FieldView(), {
name: "Start Date",
value: ",",
type: FieldType.Text,
}),
],
}),
},
{
title: "should parse card expiration date w/ no exp year",
csv: `url,username,password,extra,name,grouping,fav
http://sn,,,"NoteType:Credit Card
Name on Card:John Doe
Type:Visa
Number:1234567887654321
Security Code:321
Start Date:,
Expiration Date:January,
Notes:",noyear,,0`,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "noyear",
notes: null,
type: 3,
card: {
cardholderName: "John Doe",
number: "1234567887654321",
code: "321",
expMonth: "1",
},
fields: [
Object.assign(new FieldView(), {
name: "Type",
value: "Visa",
type: FieldType.Text,
}),
Object.assign(new FieldView(), {
name: "Start Date",
value: ",",
type: FieldType.Text,
}),
],
}),
},
{
title: "should parse card expiration date w/ no month",
csv: `url,username,password,extra,name,grouping,fav
http://sn,,,"NoteType:Credit Card
Name on Card:John Doe
Type:Mastercard
Number:8765432112345678
Security Code:987
Start Date:,
Expiration Date:,2020
Notes:",nomonth,,0`,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "nomonth",
notes: null,
type: 3,
card: {
cardholderName: "John Doe",
number: "8765432112345678",
code: "987",
expYear: "2020",
expMonth: undefined,
},
fields: [
Object.assign(new FieldView(), {
name: "Type",
value: "Mastercard",
type: FieldType.Text,
}),
Object.assign(new FieldView(), {
name: "Start Date",
value: ",",
type: FieldType.Text,
}),
],
}),
},
];
describe("Lastpass CSV Importer", () => {
CipherData.forEach((data) => {
it(data.title, async () => {
const importer = new Importer();
const result = await importer.parse(data.csv);
expect(result != null).toBe(true);
expect(result.ciphers.length).toBeGreaterThan(0);
const cipher = result.ciphers.shift();
let property: keyof typeof data.expected;
for (property in data.expected) {
// eslint-disable-next-line
if (data.expected.hasOwnProperty(property)) {
// eslint-disable-next-line
expect(cipher.hasOwnProperty(property)).toBe(true);
expect(cipher[property]).toEqual(data.expected[property]);
}
}
});
});
it("should parse login with totp", async () => {
const input = `url,username,password,totp,extra,name,grouping,fav
http://example.com,someUser,myPassword,Y64VEVMBTSXCYIWRSHRNDZW62MPGVU2G,super secure notes,example.com,,0`;
const importer = new Importer();
const result = await importer.parse(input);
baseExcept(result);
const cipher = result.ciphers[0];
expectLogin(cipher);
});
});

View File

@@ -1,633 +0,0 @@
import { CipherType } from "@/jslib/common/src/enums/cipherType";
import { MykiCsvImporter as Importer } from "@/jslib/common/src/importers/mykiCsvImporter";
import { CipherView } from "@/jslib/common/src/models/view/cipherView";
import { userAccountData } from "./testData/mykiCsv/UserAccount.csv";
import { userCreditCardData } from "./testData/mykiCsv/UserCreditCard.csv";
import { userIdCardData } from "./testData/mykiCsv/UserIdCard.csv";
import { userIdentityData } from "./testData/mykiCsv/UserIdentity.csv";
import { userNoteData } from "./testData/mykiCsv/UserNote.csv";
import { userTwoFaData } from "./testData/mykiCsv/UserTwofa.csv";
function expectDriversLicense(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Joe User's nickname");
expect(cipher.notes).toBe("Additional information");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.licenseNumber).toBe("123456");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Driver's License");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("02/02/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("02/02/2024");
}
function expectPassport(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Passport ID card");
expect(cipher.notes).toBe("Additional information field");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.passportNumber).toBe("1234567");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Passport");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2028");
}
function expectSocialSecurity(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Social Security ID card");
expect(cipher.notes).toBe("Additional information field text");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.ssn).toBe("123455678");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Social Security");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2028");
}
function expectIdCard(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("ID card type ID card");
expect(cipher.notes).toBe("Additional Information field text");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.licenseNumber).toBe("1234566");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("ID Card");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2028");
}
function expectTaxNumber(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Tax number ID card");
expect(cipher.notes).toBe("Additinoal information text field");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.licenseNumber).toBe("12345678");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Tax Number");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2028");
}
function expectBankAccount(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Bank account ID card");
expect(cipher.notes).toBe("Additional text information here");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.licenseNumber).toBe("12344556677");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Bank Account");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2028");
}
function expectInsuranceCard(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Insurance card ID card");
expect(cipher.notes).toBe("Additional information text goes here");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.licenseNumber).toBe("123456677");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Insurance Card");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2022");
}
function expectHealthCard(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Health card Id card");
expect(cipher.notes).toBe("More info");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.licenseNumber).toBe("1234670");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Health Card");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2028");
}
function expectMembershipCard(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Membership ID card");
expect(cipher.notes).toBe("Add'l info");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.licenseNumber).toBe("12345709");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Membership");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2028");
}
function expectDatabase(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Database ID card");
expect(cipher.notes).toBe("Addin't info");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.licenseNumber).toBe("12345089u");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Database");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2028");
}
function expectOutdoorLicense(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Outdoor license ID card");
expect(cipher.notes).toBe("Additional info");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.licenseNumber).toBe("123890090");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Outdoor License");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2028");
}
function expectRewardProgram(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Reward program Id card");
expect(cipher.notes).toBe("1234890");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.licenseNumber).toBe("12345890b");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Reward Program");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2028");
}
function expectSoftwareLicense(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Software license ID card");
expect(cipher.notes).toBe(
"It seems like the fields don't change, which makes it pretty useless that they have so many ID card types."
);
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.licenseNumber).toBe("1234567c");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Software License");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2028");
}
function expectTourVisa(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Tour visa ID card");
expect(cipher.notes).toBe("Additional Informaion text");
expect(cipher.identity.fullName).toBe("Joe M User");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.licenseNumber).toBe("123456lkhj");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(5);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toEqual("tags");
expect(cipher.fields[1].value).toEqual("someTag");
expect(cipher.fields[2].name).toEqual("idType");
expect(cipher.fields[2].value).toEqual("Tour Visa");
expect(cipher.fields[3].name).toEqual("idIssuanceDate");
expect(cipher.fields[3].value).toEqual("03/07/2022");
expect(cipher.fields[4].name).toEqual("idExpirationDate");
expect(cipher.fields[4].value).toEqual("03/07/2028");
}
describe("Myki CSV Importer", () => {
let importer: Importer;
beforeEach(() => {
importer = new Importer();
});
it("should parse userAccount records", async () => {
const result = await importer.parse(userAccountData);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("PasswordNickname");
expect(cipher.login.username).toEqual("user.name@email.com");
expect(cipher.login.password).toEqual("abc123");
expect(cipher.login.totp).toEqual("someTOTPSeed");
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual("http://www.google.com");
expect(cipher.notes).toEqual("This is the additional information text.");
expect(cipher.fields.length).toBe(2);
expect(cipher.fields[0].name).toBe("status");
expect(cipher.fields[0].value).toBe("active");
expect(cipher.fields[1].name).toBe("tags");
expect(cipher.fields[1].value).toBe("someTag");
});
it("should parse userTwoFa records", async () => {
const result = await importer.parse(userTwoFaData);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("2FA nickname");
expect(cipher.login.username).toBeNull();
expect(cipher.login.password).toBeNull();
expect(cipher.login.totp).toBe("someTOTPSeed");
expect(cipher.notes).toEqual("Additional information field content.");
expect(cipher.fields.length).toBe(2);
expect(cipher.fields[0].name).toBe("status");
expect(cipher.fields[0].value).toBe("active");
expect(cipher.fields[1].name).toBe("tags");
expect(cipher.fields[1].value).toBe("someTag");
});
it("should parse creditCard records", async () => {
const result = await importer.parse(userCreditCardData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers.shift();
expect(cipher.type).toBe(CipherType.Card);
expect(cipher.name).toBe("Visa test card");
expect(cipher.card.brand).toBe("Visa");
expect(cipher.card.cardholderName).toBe("Joe User");
expect(cipher.card.number).toBe("4111111111111111");
expect(cipher.card.code).toBe("222");
expect(cipher.card.expMonth).toBe("04");
expect(cipher.card.expYear).toBe("24");
expect(cipher.notes).toBe("This is the additional information field");
expect(cipher.fields.length).toBe(2);
expect(cipher.fields[0].name).toBe("status");
expect(cipher.fields[0].value).toBe("active");
expect(cipher.fields[1].name).toBe("tags");
expect(cipher.fields[1].value).toBe("someTag");
});
it("should parse identity records", async () => {
const result = await importer.parse(userIdentityData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("Joe User's nickname");
expect(cipher.identity.fullName).toBe("Mr Joe M User");
expect(cipher.identity.title).toBe("Mr");
expect(cipher.identity.firstName).toBe("Joe");
expect(cipher.identity.middleName).toBe("M");
expect(cipher.identity.lastName).toBe("User");
expect(cipher.identity.email).toBe("joe.user@email.com");
expect(cipher.identity.address1).toBe("1 Example House");
expect(cipher.identity.address2).toBe("Suite 300");
expect(cipher.identity.city).toBe("Portland");
expect(cipher.identity.postalCode).toBe("04101");
expect(cipher.identity.country).toBe("United States");
expect(cipher.fields.length).toBe(4);
expect(cipher.fields[0].name).toEqual("status");
expect(cipher.fields[0].value).toEqual("active");
expect(cipher.fields[1].name).toBe("tags");
expect(cipher.fields[1].value).toBe("someTag");
expect(cipher.fields[2].name).toEqual("gender");
expect(cipher.fields[2].value).toEqual("Male");
expect(cipher.fields[3].name).toEqual("number");
expect(cipher.fields[3].value).toEqual("2223334444");
});
it("should parse secureNote records", async () => {
const result = await importer.parse(userNoteData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers.shift();
expect(cipher.type).toBe(CipherType.SecureNote);
expect(cipher.name).toBe("The title of a secure note");
expect(cipher.notes).toBe("The content of a secure note. Lorem ipsum, etc.");
expect(cipher.fields.length).toBe(1);
expect(cipher.fields[0].name).toBe("status");
expect(cipher.fields[0].value).toBe("active");
});
it("should parse idCard records", async () => {
const result = await importer.parse(userIdCardData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(14);
// Driver's license
const cipher = result.ciphers.shift();
expectDriversLicense(cipher);
// Passport
const cipher2 = result.ciphers.shift();
expectPassport(cipher2);
// Social Security
const cipher3 = result.ciphers.shift();
expectSocialSecurity(cipher3);
// Id Card
const cipher4 = result.ciphers.shift();
expectIdCard(cipher4);
// Tax Number
const cipher5 = result.ciphers.shift();
expectTaxNumber(cipher5);
// Bank Account
const cipher6 = result.ciphers.shift();
expectBankAccount(cipher6);
// Insurance card
const cipher7 = result.ciphers.shift();
expectInsuranceCard(cipher7);
// Health card
const cipher8 = result.ciphers.shift();
expectHealthCard(cipher8);
// Membership card
const cipher9 = result.ciphers.shift();
expectMembershipCard(cipher9);
// Database card
const cipher10 = result.ciphers.shift();
expectDatabase(cipher10);
// Outdoor license
const cipher11 = result.ciphers.shift();
expectOutdoorLicense(cipher11);
// Reward program
const cipher12 = result.ciphers.shift();
expectRewardProgram(cipher12);
// Software license
const cipher13 = result.ciphers.shift();
expectSoftwareLicense(cipher13);
// Tour visa
const cipher14 = result.ciphers.shift();
expectTourVisa(cipher14);
});
});

View File

@@ -1,181 +0,0 @@
import { CipherType } from "@/jslib/common/src/enums/cipherType";
import { SecureNoteType } from "@/jslib/common/src/enums/secureNoteType";
import { NordPassCsvImporter as Importer } from "@/jslib/common/src/importers/nordpassCsvImporter";
import { CipherView } from "@/jslib/common/src/models/view/cipherView";
import { IdentityView } from "@/jslib/common/src/models/view/identityView";
import { data as creditCardData } from "./testData/nordpassCsv/nordpass.card.csv";
import { data as identityData } from "./testData/nordpassCsv/nordpass.identity.csv";
import { data as loginData } from "./testData/nordpassCsv/nordpass.login.csv";
import { data as secureNoteData } from "./testData/nordpassCsv/nordpass.secureNote.csv";
const namesTestData = [
{
title: "Given #fullName should set firstName",
fullName: "MyFirstName",
expected: Object.assign(new IdentityView(), {
firstName: "MyFirstName",
middleName: null,
lastName: null,
}),
},
{
title: "Given #fullName should set first- and lastName",
fullName: "MyFirstName MyLastName",
expected: Object.assign(new IdentityView(), {
firstName: "MyFirstName",
middleName: null,
lastName: "MyLastName",
}),
},
{
title: "Given #fullName should set first-, middle and lastName",
fullName: "MyFirstName MyMiddleName MyLastName",
expected: Object.assign(new IdentityView(), {
firstName: "MyFirstName",
middleName: "MyMiddleName",
lastName: "MyLastName",
}),
},
{
title: "Given #fullName should set first-, middle and lastName with Jr",
fullName: "MyFirstName MyMiddleName MyLastName Jr",
expected: Object.assign(new IdentityView(), {
firstName: "MyFirstName",
middleName: "MyMiddleName",
lastName: "MyLastName Jr",
}),
},
{
title: "Given #fullName should set first-, middle and lastName with Jr and III",
fullName: "MyFirstName MyMiddleName MyLastName Jr III",
expected: Object.assign(new IdentityView(), {
firstName: "MyFirstName",
middleName: "MyMiddleName",
lastName: "MyLastName Jr III",
}),
},
];
function expectLogin(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Login);
expect(cipher.name).toBe("SomeVaultItemName");
expect(cipher.notes).toBe("Some note for the VaultItem");
expect(cipher.login.uri).toBe("https://example.com");
expect(cipher.login.username).toBe("hello@bitwarden.com");
expect(cipher.login.password).toBe("someStrongPassword");
}
function expectCreditCard(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Card);
expect(cipher.name).toBe("SomeVisa");
expect(cipher.card.brand).toBe("Visa");
expect(cipher.card.cardholderName).toBe("SomeHolder");
expect(cipher.card.number).toBe("4024007103939509");
expect(cipher.card.code).toBe("123");
expect(cipher.card.expMonth).toBe("1");
expect(cipher.card.expYear).toBe("22");
}
function expectIdentity(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe("SomeTitle");
expect(cipher.identity.fullName).toBe("MyFirstName MyMiddleName MyLastName");
expect(cipher.identity.firstName).toBe("MyFirstName");
expect(cipher.identity.middleName).toBe("MyMiddleName");
expect(cipher.identity.lastName).toBe("MyLastName");
expect(cipher.identity.email).toBe("hello@bitwarden.com");
expect(cipher.identity.phone).toBe("123456789");
expect(cipher.identity.address1).toBe("Test street 123");
expect(cipher.identity.address2).toBe("additional addressinfo");
expect(cipher.identity.postalCode).toBe("123456");
expect(cipher.identity.city).toBe("Cologne");
expect(cipher.identity.state).toBe("North-Rhine-Westphalia");
expect(cipher.identity.country).toBe("GERMANY");
expect(cipher.notes).toBe("SomeNoteToMyIdentity");
}
function expectSecureNote(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.SecureNote);
expect(cipher.name).toBe("MySuperSecureNoteTitle");
expect(cipher.secureNote.type).toBe(SecureNoteType.Generic);
expect(cipher.notes).toBe("MySuperSecureNote");
}
describe("NordPass CSV Importer", () => {
let importer: Importer;
beforeEach(() => {
importer = new Importer();
});
it("should parse login records", async () => {
const result = await importer.parse(loginData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectLogin(cipher);
});
it("should parse credit card records", async () => {
const result = await importer.parse(creditCardData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectCreditCard(cipher);
});
it("should parse identity records", async () => {
const result = await importer.parse(
identityData.replace("#fullName", "MyFirstName MyMiddleName MyLastName")
);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectIdentity(cipher);
});
namesTestData.forEach((data) => {
it(data.title.replace("#fullName", data.fullName), async () => {
const result = await importer.parse(identityData.replace("#fullName", data.fullName));
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expect(cipher.identity.firstName).toBe(data.expected.firstName);
expect(cipher.identity.middleName).toBe(data.expected.middleName);
expect(cipher.identity.lastName).toBe(data.expected.lastName);
});
});
it("should parse secureNote records", async () => {
const result = await importer.parse(secureNoteData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectSecureNote(cipher);
});
it("should parse an item and create a folder", async () => {
const result = await importer.parse(secureNoteData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.folders.length).toBe(1);
const folder = result.folders[0];
expect(folder.name).toBe("notesFolder");
});
});

View File

@@ -1,527 +0,0 @@
import { FieldType } from "@/jslib/common/src/enums/fieldType";
import { OnePassword1PifImporter as Importer } from "@/jslib/common/src/importers/onepasswordImporters/onepassword1PifImporter";
const TestData: string =
"***aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee***\n" +
JSON.stringify({
uuid: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
updatedAt: 1486071244,
securityLevel: "SL5",
contentsHash: "aaaaaaaa",
title: "Imported Entry",
location: "https://www.google.com",
secureContents: {
fields: [
{
value: "user@test.net",
id: "email-input",
name: "email",
type: "T",
designation: "username",
},
{
value: "myservicepassword",
id: "password-input",
name: "password",
type: "P",
designation: "password",
},
],
sections: [
{
fields: [
{
k: "concealed",
n: "AAAAAAAAAAAABBBBBBBBBBBCCCCCCCCC",
v: "console-password-123",
t: "console password",
},
],
title: "Admin Console",
name: "admin_console",
},
],
passwordHistory: [
{
value: "old-password",
time: 1447791421,
},
],
},
URLs: [
{
label: "website",
url: "https://www.google.com",
},
],
txTimestamp: 1508941334,
createdAt: 1390426636,
typeName: "webforms.WebForm",
});
const WindowsOpVaultTestData = JSON.stringify({
category: "001",
created: 1544823719,
hmac: "NtyBmTTPOb88HV3JUKPx1xl/vcMhac9kvCfe/NtszY0=",
k: "**REMOVED LONG LINE FOR LINTER** -Kyle",
tx: 1553395669,
updated: 1553395669,
uuid: "528AB076FB5F4FBF960884B8E01619AC",
overview: {
title: "Google",
URLs: [
{
u: "google.com",
},
],
url: "google.com",
ps: 26,
ainfo: "googluser",
},
details: {
passwordHistory: [
{
value: "oldpass1",
time: 1553394449,
},
{
value: "oldpass2",
time: 1553394457,
},
{
value: "oldpass3",
time: 1553394458,
},
{
value: "oldpass4",
time: 1553394459,
},
{
value: "oldpass5",
time: 1553394460,
},
{
value: "oldpass6",
time: 1553394461,
},
],
fields: [
{
type: "T",
id: "username",
name: "username",
value: "googluser",
designation: "username",
},
{
type: "P",
id: "password",
name: "password",
value: "12345678901",
designation: "password",
},
],
notesPlain: "This is a note\r\n\r\nline1\r\nline2",
sections: [
{
title: "test",
name: "1214FD88CD30405D9EED14BEB4D61B60",
fields: [
{
k: "string",
n: "6CC3BD77482D4559A4B8BB2D360F821B",
v: "fgfg",
t: "fgggf",
},
{
k: "concealed",
n: "5CFE7BCAA1DF4578BBF7EB508959BFF3",
v: "dfgdfgfdg",
t: "pwfield",
},
],
},
],
},
});
const IdentityTestData = JSON.stringify({
uuid: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
updatedAt: 1553365894,
securityLevel: "SL5",
contentsHash: "eeeeeeee",
title: "Test Identity",
secureContents: {
lastname: "Fritzenberger",
zip: "223344",
birthdate_dd: "11",
homephone: "+49 333 222 111",
company: "Web Inc.",
firstname: "Frank",
birthdate_mm: "3",
country: "de",
sex: "male",
sections: [
{
fields: [
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
n: "firstname",
v: "Frank",
a: {
guarded: "yes",
},
t: "first name",
},
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
n: "initial",
v: "MD",
a: {
guarded: "yes",
},
t: "initial",
},
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
n: "lastname",
v: "Fritzenberger",
a: {
guarded: "yes",
},
t: "last name",
},
{
k: "menu",
v: "male",
n: "sex",
a: {
guarded: "yes",
},
t: "sex",
},
{
k: "date",
v: 1552305660,
n: "birthdate",
a: {
guarded: "yes",
},
t: "birth date",
},
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
n: "occupation",
v: "Engineer",
a: {
guarded: "yes",
},
t: "occupation",
},
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
n: "company",
v: "Web Inc.",
a: {
guarded: "yes",
},
t: "company",
},
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
n: "department",
v: "IT",
a: {
guarded: "yes",
},
t: "department",
},
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
n: "jobtitle",
v: "Developer",
a: {
guarded: "yes",
},
t: "job title",
},
],
title: "Identification",
name: "name",
},
{
fields: [
{
k: "address",
inputTraits: {
autocapitalization: "Sentences",
},
n: "address",
v: {
street: "Mainstreet 1",
city: "Berlin",
country: "de",
zip: "223344",
},
a: {
guarded: "yes",
},
t: "address",
},
{
k: "phone",
v: "+49 001 222 333 44",
n: "defphone",
a: {
guarded: "yes",
},
t: "default phone",
},
{
k: "phone",
v: "+49 333 222 111",
n: "homephone",
a: {
guarded: "yes",
},
t: "home",
},
{
k: "phone",
n: "cellphone",
a: {
guarded: "yes",
},
t: "mobile",
},
{
k: "phone",
n: "busphone",
a: {
guarded: "yes",
},
t: "business",
},
],
title: "Address",
name: "address",
},
{
fields: [
{
k: "string",
n: "username",
a: {
guarded: "yes",
},
t: "username",
},
{
k: "string",
n: "reminderq",
t: "reminder question",
},
{
k: "string",
n: "remindera",
t: "reminder answer",
},
{
k: "string",
inputTraits: {
keyboard: "EmailAddress",
},
n: "email",
v: "test@web.de",
a: {
guarded: "yes",
},
t: "email",
},
{
k: "string",
n: "website",
inputTraits: {
keyboard: "URL",
},
t: "website",
},
{
k: "string",
n: "icq",
t: "ICQ",
},
{
k: "string",
n: "skype",
t: "skype",
},
{
k: "string",
n: "aim",
t: "AOL/AIM",
},
{
k: "string",
n: "yahoo",
t: "Yahoo",
},
{
k: "string",
n: "msn",
t: "MSN",
},
{
k: "string",
n: "forumsig",
t: "forum signature",
},
],
title: "Internet Details",
name: "internet",
},
{
title: "Related Items",
name: "linked items",
},
],
initial: "MD",
address1: "Mainstreet 1",
city: "Berlin",
jobtitle: "Developer",
occupation: "Engineer",
department: "IT",
email: "test@web.de",
birthdate_yy: "2019",
homephone_local: "+49 333 222 111",
defphone_local: "+49 001 222 333 44",
defphone: "+49 001 222 333 44",
},
txTimestamp: 1553365894,
createdAt: 1553364679,
typeName: "identities.Identity",
});
describe("1Password 1Pif Importer", () => {
it("should parse data", async () => {
const importer = new Importer();
const result = await importer.parse(TestData);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.login.username).toEqual("user@test.net");
expect(cipher.login.password).toEqual("myservicepassword");
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual("https://www.google.com");
});
it('should create concealed field as "hidden" type', async () => {
const importer = new Importer();
const result = await importer.parse(TestData);
expect(result != null).toBe(true);
const ciphers = result.ciphers;
expect(ciphers.length).toEqual(1);
const cipher = ciphers.shift();
const fields = cipher.fields;
expect(fields.length).toEqual(1);
const field = fields.shift();
expect(field.name).toEqual("console password");
expect(field.value).toEqual("console-password-123");
expect(field.type).toEqual(FieldType.Hidden);
});
it("should create identity records", async () => {
const importer = new Importer();
const result = await importer.parse(IdentityTestData);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("Test Identity");
const identity = cipher.identity;
expect(identity.firstName).toEqual("Frank");
expect(identity.middleName).toEqual("MD");
expect(identity.lastName).toEqual("Fritzenberger");
expect(identity.company).toEqual("Web Inc.");
expect(identity.address1).toEqual("Mainstreet 1");
expect(identity.country).toEqual("DE");
expect(identity.city).toEqual("Berlin");
expect(identity.postalCode).toEqual("223344");
expect(identity.phone).toEqual("+49 001 222 333 44");
expect(identity.email).toEqual("test@web.de");
// remaining fields as custom fields
expect(cipher.fields.length).toEqual(6);
const fields = cipher.fields;
expect(fields[0].name).toEqual("sex");
expect(fields[0].value).toEqual("male");
expect(fields[1].name).toEqual("birth date");
expect(fields[1].value).toEqual("Mon, 11 Mar 2019 12:01:00 GMT");
expect(fields[2].name).toEqual("occupation");
expect(fields[2].value).toEqual("Engineer");
expect(fields[3].name).toEqual("department");
expect(fields[3].value).toEqual("IT");
expect(fields[4].name).toEqual("job title");
expect(fields[4].value).toEqual("Developer");
expect(fields[5].name).toEqual("home");
expect(fields[5].value).toEqual("+49 333 222 111");
});
it("should create password history", async () => {
const importer = new Importer();
const result = await importer.parse(TestData);
const cipher = result.ciphers.shift();
expect(cipher.passwordHistory.length).toEqual(1);
const ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("old-password");
expect(ph.lastUsedDate.toISOString()).toEqual("2015-11-17T20:17:01.000Z");
});
it("should create password history from windows opvault 1pif format", async () => {
const importer = new Importer();
const result = await importer.parse(WindowsOpVaultTestData);
const cipher = result.ciphers.shift();
expect(cipher.passwordHistory.length).toEqual(5);
let ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("oldpass6");
expect(ph.lastUsedDate.toISOString()).toEqual("2019-03-24T02:27:41.000Z");
ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("oldpass5");
expect(ph.lastUsedDate.toISOString()).toEqual("2019-03-24T02:27:40.000Z");
ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("oldpass4");
expect(ph.lastUsedDate.toISOString()).toEqual("2019-03-24T02:27:39.000Z");
ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("oldpass3");
expect(ph.lastUsedDate.toISOString()).toEqual("2019-03-24T02:27:38.000Z");
ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("oldpass2");
expect(ph.lastUsedDate.toISOString()).toEqual("2019-03-24T02:27:37.000Z");
});
});

View File

@@ -1,689 +0,0 @@
import { CipherType } from "@/jslib/common/src/enums/cipherType";
import { FieldType } from "@/jslib/common/src/enums/fieldType";
import { SecureNoteType } from "@/jslib/common/src/enums/secureNoteType";
import { OnePassword1PuxImporter as Importer } from "@/jslib/common/src/importers/onepasswordImporters/onepassword1PuxImporter";
import { Utils } from "@/jslib/common/src/misc/utils";
import { FieldView } from "@/jslib/common/src/models/view/fieldView";
import { APICredentialsData } from "./testData/onePassword1Pux/APICredentials";
import { BankAccountData } from "./testData/onePassword1Pux/BankAccount";
import { CreditCardData } from "./testData/onePassword1Pux/CreditCard";
import { DatabaseData } from "./testData/onePassword1Pux/Database";
import { DriversLicenseData } from "./testData/onePassword1Pux/DriversLicense";
import { EmailAccountData } from "./testData/onePassword1Pux/EmailAccount";
import { EmailFieldData } from "./testData/onePassword1Pux/Emailfield";
import { EmailFieldOnIdentityData } from "./testData/onePassword1Pux/EmailfieldOnIdentity";
import { EmailFieldOnIdentityPrefilledData } from "./testData/onePassword1Pux/EmailfieldOnIdentity_Prefilled";
import { IdentityData } from "./testData/onePassword1Pux/IdentityData";
import { LoginData } from "./testData/onePassword1Pux/LoginData";
import { MedicalRecordData } from "./testData/onePassword1Pux/MedicalRecord";
import { MembershipData } from "./testData/onePassword1Pux/Membership";
import { OnePuxExampleFile } from "./testData/onePassword1Pux/Onepux_example";
import { OutdoorLicenseData } from "./testData/onePassword1Pux/OutdoorLicense";
import { PassportData } from "./testData/onePassword1Pux/Passport";
import { PasswordData } from "./testData/onePassword1Pux/Password";
import { RewardsProgramData } from "./testData/onePassword1Pux/RewardsProgram";
import { SSNData } from "./testData/onePassword1Pux/SSN";
import { SanitizedExport } from "./testData/onePassword1Pux/SanitizedExport";
import { SecureNoteData } from "./testData/onePassword1Pux/SecureNote";
import { ServerData } from "./testData/onePassword1Pux/Server";
import { SoftwareLicenseData } from "./testData/onePassword1Pux/SoftwareLicense";
import { WirelessRouterData } from "./testData/onePassword1Pux/WirelessRouter";
function validateCustomField(fields: FieldView[], fieldName: string, expectedValue: any) {
expect(fields).toBeDefined();
const customField = fields.find((f) => f.name === fieldName);
expect(customField).toBeDefined();
expect(customField.value).toEqual(expectedValue);
}
describe("1Password 1Pux Importer", () => {
const OnePuxExampleFileJson = JSON.stringify(OnePuxExampleFile);
const LoginDataJson = JSON.stringify(LoginData);
const CreditCardDataJson = JSON.stringify(CreditCardData);
const IdentityDataJson = JSON.stringify(IdentityData);
const SecureNoteDataJson = JSON.stringify(SecureNoteData);
const SanitizedExportJson = JSON.stringify(SanitizedExport);
it("should parse login data", async () => {
const importer = new Importer();
const result = await importer.parse(LoginDataJson);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.Login);
expect(cipher.name).toEqual("eToro");
expect(cipher.login.username).toEqual("username123123123@gmail.com");
expect(cipher.login.password).toEqual("password!");
expect(cipher.login.uris.length).toEqual(1);
expect(cipher.login.uri).toEqual("https://www.fakesite.com");
expect(cipher.login.totp).toEqual("otpseed777");
// remaining fields as custom fields
expect(cipher.fields.length).toEqual(3);
validateCustomField(cipher.fields, "terms", "false");
validateCustomField(cipher.fields, "policies", "true");
validateCustomField(cipher.fields, "cyqyggt2otns6tbbqtsl6w2ceu", "username123123");
});
it("should parse notes", async () => {
const importer = new Importer();
const result = await importer.parse(OnePuxExampleFileJson);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.notes).toEqual("This is a note. *bold*! _italic_!");
});
it("should set favourite if favIndex equals 1", async () => {
const importer = new Importer();
const result = await importer.parse(OnePuxExampleFileJson);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.favorite).toBe(true);
});
it("should handle custom boolean fields", async () => {
const importer = new Importer();
const result = await importer.parse(LoginDataJson);
expect(result != null).toBe(true);
const ciphers = result.ciphers;
expect(ciphers.length).toEqual(1);
const cipher = ciphers.shift();
expect(cipher.fields[0].name).toEqual("terms");
expect(cipher.fields[0].value).toEqual("false");
expect(cipher.fields[0].type).toBe(FieldType.Boolean);
expect(cipher.fields[1].name).toEqual("policies");
expect(cipher.fields[1].value).toEqual("true");
expect(cipher.fields[1].type).toBe(FieldType.Boolean);
});
it("should add fields of type email as custom fields", async () => {
const importer = new Importer();
const EmailFieldDataJson = JSON.stringify(EmailFieldData);
const result = await importer.parse(EmailFieldDataJson);
expect(result != null).toBe(true);
const ciphers = result.ciphers;
expect(ciphers.length).toEqual(1);
const cipher = ciphers.shift();
expect(cipher.fields[0].name).toEqual("reg_email");
expect(cipher.fields[0].value).toEqual("kriddler@nullvalue.test");
expect(cipher.fields[0].type).toBe(FieldType.Text);
expect(cipher.fields[1].name).toEqual("provider");
expect(cipher.fields[1].value).toEqual("myEmailProvider");
expect(cipher.fields[1].type).toBe(FieldType.Text);
});
it('should create concealed field as "hidden" type', async () => {
const importer = new Importer();
const result = await importer.parse(OnePuxExampleFileJson);
expect(result != null).toBe(true);
const ciphers = result.ciphers;
expect(ciphers.length).toEqual(1);
const cipher = ciphers.shift();
const fields = cipher.fields;
expect(fields.length).toEqual(1);
const field = fields.shift();
expect(field.name).toEqual("PIN");
expect(field.value).toEqual("12345");
expect(field.type).toEqual(FieldType.Hidden);
});
it("should create password history", async () => {
const importer = new Importer();
const result = await importer.parse(OnePuxExampleFileJson);
const cipher = result.ciphers.shift();
expect(cipher.passwordHistory.length).toEqual(1);
const ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("12345password");
expect(ph.lastUsedDate.toISOString()).toEqual("2016-03-18T17:32:35.000Z");
});
it("should create credit card records", async () => {
const importer = new Importer();
const result = await importer.parse(CreditCardDataJson);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("Parent's Credit Card");
expect(cipher.notes).toEqual("My parents' credit card.");
const card = cipher.card;
expect(card.cardholderName).toEqual("Fred Engels");
expect(card.number).toEqual("6011111111111117");
expect(card.code).toEqual("1312");
expect(card.brand).toEqual("Discover");
expect(card.expMonth).toEqual("12");
expect(card.expYear).toEqual("2099");
// remaining fields as custom fields
expect(cipher.fields.length).toEqual(12);
validateCustomField(cipher.fields, "txbzvwzpck7ejhfres3733rbpm", "card");
validateCustomField(cipher.fields, "cashLimit", "$500");
validateCustomField(cipher.fields, "creditLimit", "$1312");
validateCustomField(cipher.fields, "validFrom", "200101");
validateCustomField(cipher.fields, "bank", "Some bank");
validateCustomField(cipher.fields, "phoneLocal", "123456");
validateCustomField(cipher.fields, "phoneTollFree", "0800123456");
validateCustomField(cipher.fields, "phoneIntl", "+49123456");
validateCustomField(cipher.fields, "website", "somebank.com");
validateCustomField(cipher.fields, "pin", "1234");
validateCustomField(cipher.fields, "interest", "1%");
validateCustomField(cipher.fields, "issuenumber", "123456");
});
it("should create identity records", async () => {
const importer = new Importer();
const result = await importer.parse(IdentityDataJson);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("George Engels");
const identity = cipher.identity;
expect(identity.firstName).toEqual("George");
expect(identity.middleName).toEqual("S");
expect(identity.lastName).toEqual("Engels");
expect(identity.company).toEqual("Acme Inc.");
expect(identity.address1).toEqual("1312 Main St.");
expect(identity.country).toEqual("US");
expect(identity.state).toEqual("California");
expect(identity.city).toEqual("Atlantis");
expect(identity.postalCode).toEqual("90210");
expect(identity.phone).toEqual("4565555555");
expect(identity.email).toEqual("gengels@nullvalue.test");
expect(identity.username).toEqual("gengels");
// remaining fields as custom fields
expect(cipher.fields.length).toEqual(17);
validateCustomField(cipher.fields, "sex", "male");
validateCustomField(cipher.fields, "birthdate", "Thu, 01 Jan 1981 12:01:00 GMT");
validateCustomField(cipher.fields, "occupation", "Steel Worker");
validateCustomField(cipher.fields, "department", "QA");
validateCustomField(cipher.fields, "jobtitle", "Quality Assurance Manager");
validateCustomField(cipher.fields, "homephone", "4575555555");
validateCustomField(cipher.fields, "cellphone", "4585555555");
validateCustomField(cipher.fields, "busphone", "4595555555");
validateCustomField(cipher.fields, "reminderq", "Who's a super cool guy?");
validateCustomField(cipher.fields, "remindera", "Me, buddy.");
validateCustomField(cipher.fields, "website", "cv.gengels.nullvalue.test");
validateCustomField(cipher.fields, "icq", "12345678");
validateCustomField(cipher.fields, "skype", "skypeisbad1619");
validateCustomField(cipher.fields, "aim", "aollol@lololol.aol.com");
validateCustomField(cipher.fields, "yahoo", "sk8rboi13@yah00.com");
validateCustomField(cipher.fields, "msn", "msnothankyou@msn&m&m.com");
validateCustomField(cipher.fields, "forumsig", "super cool guy");
});
it("emails fields on identity types should be added to the identity email field", async () => {
const importer = new Importer();
const EmailFieldOnIdentityDataJson = JSON.stringify(EmailFieldOnIdentityData);
const result = await importer.parse(EmailFieldOnIdentityDataJson);
expect(result != null).toBe(true);
const ciphers = result.ciphers;
expect(ciphers.length).toEqual(1);
const cipher = ciphers.shift();
const identity = cipher.identity;
expect(identity.email).toEqual("gengels@nullvalue.test");
expect(cipher.fields[0].name).toEqual("provider");
expect(cipher.fields[0].value).toEqual("myEmailProvider");
expect(cipher.fields[0].type).toBe(FieldType.Text);
});
it("emails fields on identity types should be added to custom fields if identity.email has been filled", async () => {
const importer = new Importer();
const EmailFieldOnIdentityPrefilledDataJson = JSON.stringify(EmailFieldOnIdentityPrefilledData);
const result = await importer.parse(EmailFieldOnIdentityPrefilledDataJson);
expect(result != null).toBe(true);
const ciphers = result.ciphers;
expect(ciphers.length).toEqual(1);
const cipher = ciphers.shift();
const identity = cipher.identity;
expect(identity.email).toEqual("gengels@nullvalue.test");
expect(cipher.fields[0].name).toEqual("2nd_email");
expect(cipher.fields[0].value).toEqual("kriddler@nullvalue.test");
expect(cipher.fields[0].type).toBe(FieldType.Text);
expect(cipher.fields[1].name).toEqual("provider");
expect(cipher.fields[1].value).toEqual("myEmailProvider");
expect(cipher.fields[1].type).toBe(FieldType.Text);
});
it("should parse category 005 - Password (Legacy)", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(PasswordData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.Login);
expect(cipher.name).toEqual("SuperSecret Password");
expect(cipher.notes).toEqual("SuperSecret Password Notes");
expect(cipher.login.password).toEqual("GBq[AGb]4*Si3tjwuab^");
expect(cipher.login.uri).toEqual("https://n0t.y0ur.n0rm4l.w3bs1t3");
});
it("should parse category 100 - SoftwareLicense", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(SoftwareLicenseData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.SecureNote);
expect(cipher.name).toEqual("Limux Product Key");
expect(cipher.notes).toEqual("My Software License");
expect(cipher.fields.length).toEqual(13);
validateCustomField(cipher.fields, "product_version", "5.10.1000");
validateCustomField(cipher.fields, "reg_code", "265453-13457355-847327");
validateCustomField(cipher.fields, "reg_name", "Kay Riddler");
validateCustomField(cipher.fields, "reg_email", "kriddler@nullvalue.test");
validateCustomField(cipher.fields, "company", "Riddles and Jigsaw Puzzles GmbH");
validateCustomField(
cipher.fields,
"download_link",
"https://limuxcompany.nullvalue.test/5.10.1000/isos"
);
validateCustomField(cipher.fields, "publisher_name", "Limux Software and Hardware");
validateCustomField(cipher.fields, "publisher_website", "https://limuxcompany.nullvalue.test/");
validateCustomField(cipher.fields, "retail_price", "$999");
validateCustomField(cipher.fields, "support_email", "support@nullvalue.test");
validateCustomField(cipher.fields, "order_date", "Thu, 01 Apr 2021 12:01:00 GMT");
validateCustomField(cipher.fields, "order_number", "594839");
validateCustomField(cipher.fields, "order_total", "$1086.59");
});
it("should parse category 101 - BankAccount", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(BankAccountData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.Card);
expect(cipher.name).toEqual("Bank Account");
expect(cipher.notes).toEqual("My Bank Account");
expect(cipher.card.cardholderName).toEqual("Cool Guy");
expect(cipher.fields.length).toEqual(9);
validateCustomField(cipher.fields, "bankName", "Super Credit Union");
validateCustomField(cipher.fields, "accountType", "checking");
validateCustomField(cipher.fields, "routingNo", "111000999");
validateCustomField(cipher.fields, "accountNo", "192837465918273645");
validateCustomField(cipher.fields, "swift", "123456");
validateCustomField(cipher.fields, "iban", "DE12 123456");
validateCustomField(cipher.fields, "telephonePin", "5555");
validateCustomField(cipher.fields, "branchPhone", "9399399933");
validateCustomField(cipher.fields, "branchAddress", "1 Fifth Avenue");
});
it("should parse category 102 - Database", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(DatabaseData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.Login);
expect(cipher.name).toEqual("Database");
expect(cipher.notes).toEqual("My Database");
const login = cipher.login;
expect(login.username).toEqual("cooldbuser");
expect(login.password).toEqual("^+kTjhLaN7wVPAhGU)*J");
expect(cipher.fields.length).toEqual(7);
validateCustomField(cipher.fields, "database_type", "postgresql");
validateCustomField(cipher.fields, "hostname", "my.secret.db.server");
validateCustomField(cipher.fields, "port", "1337");
validateCustomField(cipher.fields, "database", "user_database");
validateCustomField(cipher.fields, "sid", "ASDIUFU-283234");
validateCustomField(cipher.fields, "alias", "cdbu");
validateCustomField(cipher.fields, "options", "ssh");
});
it("should parse category 103 - Drivers license", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(DriversLicenseData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("Michael Scarn");
expect(cipher.subTitle).toEqual("Michael Scarn");
expect(cipher.notes).toEqual("My Driver's License");
const identity = cipher.identity;
expect(identity.firstName).toEqual("Michael");
expect(identity.middleName).toBeNull();
expect(identity.lastName).toEqual("Scarn");
expect(identity.address1).toEqual("2120 Mifflin Rd.");
expect(identity.state).toEqual("Pennsylvania");
expect(identity.country).toEqual("United States");
expect(identity.licenseNumber).toEqual("12345678901");
expect(cipher.fields.length).toEqual(6);
validateCustomField(cipher.fields, "birthdate", "Sun, 01 Jan 1978 12:01:00 GMT");
validateCustomField(cipher.fields, "sex", "male");
validateCustomField(cipher.fields, "height", "5'11\"");
validateCustomField(cipher.fields, "class", "C");
validateCustomField(cipher.fields, "conditions", "B");
validateCustomField(cipher.fields, "expiry_date", "203012");
});
it("should parse category 104 - Outdoor License", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(OutdoorLicenseData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.Identity);
expect(cipher.name).toEqual("Harvest License");
expect(cipher.subTitle).toEqual("Cash Bandit");
expect(cipher.notes).toEqual("My Outdoor License");
const identity = cipher.identity;
expect(identity.firstName).toEqual("Cash");
expect(identity.middleName).toBeNull();
expect(identity.lastName).toEqual("Bandit");
expect(identity.state).toEqual("Washington");
expect(identity.country).toEqual("United States of America");
expect(cipher.fields.length).toEqual(4);
validateCustomField(cipher.fields, "valid_from", "Thu, 01 Apr 2021 12:01:00 GMT");
validateCustomField(cipher.fields, "expires", "Fri, 01 Apr 2044 12:01:00 GMT");
validateCustomField(cipher.fields, "game", "Bananas,blueberries,corn");
validateCustomField(cipher.fields, "quota", "100/each");
});
it("should parse category 105 - Membership", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(MembershipData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.Identity);
expect(cipher.name).toEqual("Library Card");
const identity = cipher.identity;
expect(identity.firstName).toEqual("George");
expect(identity.middleName).toBeNull();
expect(identity.lastName).toEqual("Engels");
expect(identity.company).toEqual("National Public Library");
expect(identity.phone).toEqual("9995555555");
expect(cipher.fields.length).toEqual(5);
validateCustomField(cipher.fields, "website", "https://npl.nullvalue.gov.test");
validateCustomField(cipher.fields, "member_since", "199901");
validateCustomField(cipher.fields, "expiry_date", "203412");
validateCustomField(cipher.fields, "membership_no", "64783862");
validateCustomField(cipher.fields, "pin", "19191");
});
it("should parse category 106 - Passport", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(PassportData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.Identity);
expect(cipher.name).toEqual("Mr. Globewide");
const identity = cipher.identity;
expect(identity.firstName).toEqual("David");
expect(identity.middleName).toBeNull();
expect(identity.lastName).toEqual("Global");
expect(identity.passportNumber).toEqual("76436847");
expect(cipher.fields.length).toEqual(8);
validateCustomField(cipher.fields, "type", "US Passport");
validateCustomField(cipher.fields, "sex", "female");
validateCustomField(cipher.fields, "nationality", "International");
validateCustomField(cipher.fields, "issuing_authority", "Department of State");
validateCustomField(cipher.fields, "birthdate", "Fri, 01 Apr 1983 12:01:00 GMT");
validateCustomField(cipher.fields, "birthplace", "A cave somewhere in Maine");
validateCustomField(cipher.fields, "issue_date", "Wed, 01 Jan 2020 12:01:00 GMT");
validateCustomField(cipher.fields, "expiry_date", "Sat, 01 Jan 2050 12:01:00 GMT");
});
it("should parse category 107 - RewardsProgram", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(RewardsProgramData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.Identity);
expect(cipher.name).toEqual("Retail Reward Thing");
const identity = cipher.identity;
expect(identity.firstName).toEqual("Chef");
expect(identity.middleName).toBeNull();
expect(identity.lastName).toEqual("Coldroom");
expect(identity.company).toEqual("Super Cool Store Co.");
expect(cipher.fields.length).toEqual(7);
validateCustomField(cipher.fields, "membership_no", "member-29813569");
validateCustomField(cipher.fields, "pin", "99913");
validateCustomField(cipher.fields, "additional_no", "additional member id");
validateCustomField(cipher.fields, "member_since", "202101");
validateCustomField(cipher.fields, "customer_service_phone", "123456");
validateCustomField(cipher.fields, "reservations_phone", "123456");
validateCustomField(cipher.fields, "website", "supercoolstore.com");
});
it("should parse category 108 - SSN", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(SSNData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("SSN");
const identity = cipher.identity;
expect(identity.firstName).toEqual("Jack");
expect(identity.middleName).toBeNull();
expect(identity.lastName).toEqual("Judd");
expect(identity.ssn).toEqual("131-216-1900");
});
it("should parse category 109 - WirelessRouter", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(WirelessRouterData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.Login);
expect(cipher.name).toEqual("Wireless Router");
expect(cipher.notes).toEqual("My Wifi Router Config");
expect(cipher.login.password).toEqual("BqatGTVQ9TCN72tLbjrsHqkb");
expect(cipher.fields.length).toEqual(7);
validateCustomField(cipher.fields, "name", "pixel 2Xl");
validateCustomField(cipher.fields, "server", "127.0.0.1");
validateCustomField(cipher.fields, "airport_id", "some airportId");
validateCustomField(cipher.fields, "network_name", "some network name");
validateCustomField(cipher.fields, "wireless_security", "WPA");
validateCustomField(cipher.fields, "wireless_password", "wifipassword");
validateCustomField(cipher.fields, "disk_password", "diskpassword");
});
it("should parse category 110 - Server", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(ServerData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.Login);
expect(cipher.name).toEqual("Super Cool Server");
expect(cipher.notes).toEqual("My Server");
expect(cipher.login.username).toEqual("frankly-notsure");
expect(cipher.login.password).toEqual("*&YHJI87yjy78u");
expect(cipher.login.uri).toEqual("https://coolserver.nullvalue.test");
expect(cipher.fields.length).toEqual(7);
validateCustomField(
cipher.fields,
"admin_console_url",
"https://coolserver.nullvalue.test/admin"
);
validateCustomField(cipher.fields, "admin_console_username", "frankly-idontknowwhatimdoing");
validateCustomField(cipher.fields, "admin_console_password", "^%RY&^YUiju8iUYHJI(U");
validateCustomField(cipher.fields, "name", "Private Hosting Provider Inc.");
validateCustomField(cipher.fields, "website", "https://phpi.nullvalue.test");
validateCustomField(
cipher.fields,
"support_contact_url",
"https://phpi.nullvalue.test/support"
);
validateCustomField(cipher.fields, "support_contact_phone", "8882569382");
});
it("should parse category 111 - EmailAccount", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(EmailAccountData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.SecureNote);
expect(cipher.name).toEqual("Email Config");
expect(cipher.notes).toEqual("My Email Config");
expect(cipher.fields.length).toEqual(17);
validateCustomField(cipher.fields, "pop_type", "either");
validateCustomField(cipher.fields, "pop_username", "someuser@nullvalue.test");
validateCustomField(cipher.fields, "pop_server", "mailserver.nullvalue.test");
validateCustomField(cipher.fields, "pop_port", "587");
validateCustomField(cipher.fields, "pop_password", "u1jsf<UI*&YU&^T");
validateCustomField(cipher.fields, "pop_security", "TLS");
validateCustomField(cipher.fields, "pop_authentication", "kerberos_v5");
validateCustomField(cipher.fields, "smtp_server", "mailserver.nullvalue.test");
validateCustomField(cipher.fields, "smtp_port", "589");
validateCustomField(cipher.fields, "smtp_username", "someuser@nullvalue.test");
validateCustomField(cipher.fields, "smtp_password", "(*1674%^UIUJ*UI(IUI8u98uyy");
validateCustomField(cipher.fields, "smtp_security", "TLS");
validateCustomField(cipher.fields, "smtp_authentication", "password");
validateCustomField(cipher.fields, "provider", "Telum");
validateCustomField(cipher.fields, "provider_website", "https://telum.nullvalue.test");
validateCustomField(cipher.fields, "phone_local", "2346666666");
validateCustomField(cipher.fields, "phone_tollfree", "18005557777");
});
it("should parse category 112 - API Credentials", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(APICredentialsData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.Login);
expect(cipher.name).toEqual("API Credential");
expect(cipher.notes).toEqual("My API Credential");
expect(cipher.login.username).toEqual("apiuser@nullvalue.test");
expect(cipher.login.password).toEqual("apiapiapiapiapiapiappy");
expect(cipher.login.uri).toEqual("http://not.your.everyday.hostname");
expect(cipher.fields.length).toEqual(4);
validateCustomField(cipher.fields, "type", "jwt");
validateCustomField(cipher.fields, "filename", "filename.jwt");
validateCustomField(cipher.fields, "validFrom", "Mon, 04 Apr 2011 12:01:00 GMT");
validateCustomField(cipher.fields, "expires", "Tue, 01 Apr 2031 12:01:00 GMT");
});
it("should create secure notes", async () => {
const importer = new Importer();
const result = await importer.parse(SecureNoteDataJson);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("Secure Note #1");
expect(cipher.notes).toEqual(
"This is my secure note. \n\nLorem ipsum expecto patronum. \nThe quick brown fox jumped over the lazy dog."
);
expect(cipher.secureNote.type).toEqual(SecureNoteType.Generic);
});
it("should parse category 113 - Medical Record", async () => {
const importer = new Importer();
const jsonString = JSON.stringify(MedicalRecordData);
const result = await importer.parse(jsonString);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.type).toEqual(CipherType.SecureNote);
expect(cipher.name).toEqual("Some Health Record");
expect(cipher.notes).toEqual("Some notes about my medical history");
expect(cipher.secureNote.type).toEqual(SecureNoteType.Generic);
expect(cipher.fields.length).toEqual(8);
validateCustomField(cipher.fields, "date", "Sat, 01 Jan 2022 12:01:00 GMT");
validateCustomField(cipher.fields, "location", "some hospital/clinic");
validateCustomField(cipher.fields, "healthcareprofessional", "Some Doctor");
validateCustomField(cipher.fields, "patient", "Me");
validateCustomField(cipher.fields, "reason", "unwell");
validateCustomField(cipher.fields, "medication", "Insuline");
validateCustomField(cipher.fields, "dosage", "1");
validateCustomField(cipher.fields, "notes", "multiple times a day");
});
it("should create folders", async () => {
const importer = new Importer();
const result = await importer.parse(SanitizedExportJson);
expect(result != null).toBe(true);
const folders = result.folders;
expect(folders.length).toBe(5);
expect(folders[0].name).toBe("Movies");
expect(folders[1].name).toBe("Finance");
expect(folders[2].name).toBe("Travel");
expect(folders[3].name).toBe("Education");
expect(folders[4].name).toBe("Starter Kit");
// Check that ciphers have a folder assigned to them
expect(result.ciphers.filter((c) => c.folderId === folders[0].id).length).toBeGreaterThan(0);
expect(result.ciphers.filter((c) => c.folderId === folders[1].id).length).toBeGreaterThan(0);
expect(result.ciphers.filter((c) => c.folderId === folders[2].id).length).toBeGreaterThan(0);
expect(result.ciphers.filter((c) => c.folderId === folders[3].id).length).toBeGreaterThan(0);
expect(result.ciphers.filter((c) => c.folderId === folders[4].id).length).toBeGreaterThan(0);
});
it("should create collections if part of an organization", async () => {
const importer = new Importer();
importer.organizationId = Utils.newGuid();
const result = await importer.parse(SanitizedExportJson);
expect(result != null).toBe(true);
const collections = result.collections;
expect(collections.length).toBe(5);
expect(collections[0].name).toBe("Movies");
expect(collections[1].name).toBe("Finance");
expect(collections[2].name).toBe("Travel");
expect(collections[3].name).toBe("Education");
expect(collections[4].name).toBe("Starter Kit");
});
});

View File

@@ -1,74 +0,0 @@
import { CipherType } from "@/jslib/common/src/enums/cipherType";
import { OnePasswordMacCsvImporter as Importer } from "@/jslib/common/src/importers/onepasswordImporters/onepasswordMacCsvImporter";
import { CipherView } from "@/jslib/common/src/models/view/cipherView";
import { data as creditCardData } from "./testData/onePasswordCsv/creditCard.mac.csv";
import { data as identityData } from "./testData/onePasswordCsv/identity.mac.csv";
import { data as multiTypeData } from "./testData/onePasswordCsv/multipleItems.mac.csv";
function expectIdentity(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.identity).toEqual(
expect.objectContaining({
firstName: "first name",
middleName: "mi",
lastName: "last name",
username: "userNam3",
company: "bitwarden",
phone: "8005555555",
email: "email@bitwarden.com",
})
);
expect(cipher.notes).toContain("address\ncity state zip\nUnited States");
}
function expectCreditCard(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Card);
expect(cipher.card).toEqual(
expect.objectContaining({
number: "4111111111111111",
code: "111",
cardholderName: "test",
expMonth: "1",
expYear: "2030",
})
);
}
describe("1Password mac CSV Importer", () => {
it("should parse identity records", async () => {
const importer = new Importer();
const result = await importer.parse(identityData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectIdentity(cipher);
});
it("should parse credit card records", async () => {
const importer = new Importer();
const result = await importer.parse(creditCardData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectCreditCard(cipher);
});
it("should parse csv's with multiple record type", async () => {
const importer = new Importer();
const result = await importer.parse(multiTypeData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(4);
expectIdentity(result.ciphers[1]);
expectCreditCard(result.ciphers[2]);
});
});

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