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

Copy jslib into Directory Connector [TI-6] (#262)

This commit is contained in:
Oscar Hinton
2022-04-28 16:41:07 +02:00
committed by GitHub
parent 2d02d54b56
commit 45d0192f82
694 changed files with 104863 additions and 14 deletions

View File

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

4
.gitmodules vendored
View File

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

View File

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

1
jslib

Submodule jslib deleted from 6bcadc4f40

9
jslib/.gitignore vendored Normal file
View File

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

3
jslib/.prettierrc.json Normal file
View File

@@ -0,0 +1,3 @@
{
"printWidth": 100
}

View File

@@ -0,0 +1,17 @@
const { pathsToModuleNameMapper } = require("ts-jest/utils");
const { compilerOptions } = require("./tsconfig");
module.exports = {
name: "angular",
displayName: "angular tests",
preset: "jest-preset-angular",
testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
collectCoverage: true,
coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage",
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
};

654
jslib/angular/package-lock.json generated Normal file
View File

@@ -0,0 +1,654 @@
{
"name": "@bitwarden/jslib-angular",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@bitwarden/jslib-angular",
"version": "0.0.0",
"license": "GPL-3.0",
"dependencies": {
"@angular/animations": "^12.2.13",
"@angular/cdk": "^12.2.13",
"@angular/common": "^12.2.13",
"@angular/compiler": "^12.2.13",
"@angular/core": "^12.2.13",
"@angular/forms": "^12.2.13",
"@angular/platform-browser": "^12.2.13",
"@angular/platform-browser-dynamic": "^12.2.13",
"@angular/router": "^12.2.13",
"@bitwarden/jslib-common": "file:../common",
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
"rxjs": "^7.4.0",
"tldjs": "^2.3.1",
"zone.js": "0.11.4"
},
"devDependencies": {
"@types/duo_web_sdk": "^2.7.1",
"rimraf": "^3.0.2",
"typescript": "4.3.5"
}
},
"../common": {
"name": "@bitwarden/jslib-common",
"version": "0.0.0",
"license": "GPL-3.0",
"dependencies": {
"@microsoft/signalr": "5.0.10",
"@microsoft/signalr-protocol-msgpack": "5.0.10",
"big-integer": "1.6.48",
"browser-hrtime": "^1.1.8",
"lunr": "^2.3.9",
"node-forge": "^1.2.1",
"papaparse": "^5.3.0",
"rxjs": "^7.4.0",
"tldjs": "^2.3.1",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@types/lunr": "^2.3.3",
"@types/node": "^16.11.12",
"@types/node-forge": "^1.0.1",
"@types/papaparse": "^5.2.5",
"@types/tldjs": "^2.3.0",
"@types/zxcvbn": "^4.4.1",
"rimraf": "^3.0.2",
"typescript": "4.3.5"
}
},
"node_modules/@angular/animations": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.14.tgz",
"integrity": "sha512-1BR5u32auVePvXNNP96DB2008V+Ku0OGqeZQl2h4XA9xzES/Zk5WllIJZXqRmWMRBVARfXsfb0RdMty9gcaVjA==",
"dependencies": {
"tslib": "^2.2.0"
},
"engines": {
"node": "^12.14.1 || >=14.0.0"
},
"peerDependencies": {
"@angular/core": "12.2.14"
}
},
"node_modules/@angular/cdk": {
"version": "12.2.13",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.13.tgz",
"integrity": "sha512-zSKRhECyFqhingIeyRInIyTvYErt4gWo+x5DQr0b7YLUbU8DZSwWnG4w76Ke2s4U8T7ry1jpJBHoX/e8YBpGMg==",
"dependencies": {
"tslib": "^2.2.0"
},
"optionalDependencies": {
"parse5": "^5.0.0"
},
"peerDependencies": {
"@angular/common": "^12.0.0 || ^13.0.0-0",
"@angular/core": "^12.0.0 || ^13.0.0-0",
"rxjs": "^6.5.3 || ^7.0.0"
}
},
"node_modules/@angular/common": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.14.tgz",
"integrity": "sha512-ffYUYdwZETmFJw0AcWY30WsaWBhJxj/zSmFXWjgEGEGZH56zwbbNwfMZOYZ1jz4haAVxGu+TdXsOl2yMGzN7jQ==",
"dependencies": {
"tslib": "^2.2.0"
},
"engines": {
"node": "^12.14.1 || >=14.0.0"
},
"peerDependencies": {
"@angular/core": "12.2.14",
"rxjs": "^6.5.3 || ^7.0.0"
}
},
"node_modules/@angular/compiler": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.14.tgz",
"integrity": "sha512-dwmZi+n66IUzRFlGWu9mjXq170ZEsaDvlNLZzaPgs6vZTa4Kt7PWvIF/Y7TMvnVv/uqNG6kOhfmOkf6rfz1Gjg==",
"dependencies": {
"tslib": "^2.2.0"
},
"engines": {
"node": "^12.14.1 || >=14.0.0"
}
},
"node_modules/@angular/core": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.14.tgz",
"integrity": "sha512-dlVk7yqUHL2R/eCmM8LsWuxhEBfzg0y1zHt0UqCuFwlCoiw+IG4HFy4OlZEUw9NUEZJSv0aDv3sWqxLkvK5vvg==",
"dependencies": {
"tslib": "^2.2.0"
},
"engines": {
"node": "^12.14.1 || >=14.0.0"
},
"peerDependencies": {
"rxjs": "^6.5.3 || ^7.0.0",
"zone.js": "~0.11.4"
}
},
"node_modules/@angular/forms": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.14.tgz",
"integrity": "sha512-/9/gSJUBXVRVdRnzgJnALAQZYJATuGDMkFC9ms9DEMG4PMAhe9x4if1lJjN6noz5RAom3qNuVBNWaYAPUxlcBQ==",
"dependencies": {
"tslib": "^2.2.0"
},
"engines": {
"node": "^12.14.1 || >=14.0.0"
},
"peerDependencies": {
"@angular/common": "12.2.14",
"@angular/core": "12.2.14",
"@angular/platform-browser": "12.2.14",
"rxjs": "^6.5.3 || ^7.0.0"
}
},
"node_modules/@angular/platform-browser": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.14.tgz",
"integrity": "sha512-fWcE2rnJ3ZCISa1oPfsIDV7FBZBoLFEdDuMXAiDYqDPKvF/E5U5nHrS+K4SlLAi094bMobtTOReNWl/Ienniyw==",
"dependencies": {
"tslib": "^2.2.0"
},
"engines": {
"node": "^12.14.1 || >=14.0.0"
},
"peerDependencies": {
"@angular/animations": "12.2.14",
"@angular/common": "12.2.14",
"@angular/core": "12.2.14"
},
"peerDependenciesMeta": {
"@angular/animations": {
"optional": true
}
}
},
"node_modules/@angular/platform-browser-dynamic": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.14.tgz",
"integrity": "sha512-0NPF7mS91Tct8rBmOLZPmnLSuS4kbLHXo6eTgrg80OC0vlzBiQwGDVW4X3KncCoX9CpevaGJCdSMc+uPNsFOUQ==",
"dependencies": {
"tslib": "^2.2.0"
},
"engines": {
"node": "^12.14.1 || >=14.0.0"
},
"peerDependencies": {
"@angular/common": "12.2.14",
"@angular/compiler": "12.2.14",
"@angular/core": "12.2.14",
"@angular/platform-browser": "12.2.14"
}
},
"node_modules/@angular/router": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.14.tgz",
"integrity": "sha512-yP5grSnqBvc4vNhtYdcxDgDYIebUKs5f0xyFkUJM5030UnQ0CV45tBsSxHMkQbPZucIfOuxpRy8xy5+4GizuwQ==",
"dependencies": {
"tslib": "^2.2.0"
},
"engines": {
"node": "^12.14.1 || >=14.0.0"
},
"peerDependencies": {
"@angular/common": "12.2.14",
"@angular/core": "12.2.14",
"@angular/platform-browser": "12.2.14",
"rxjs": "^6.5.3 || ^7.0.0"
}
},
"node_modules/@bitwarden/jslib-common": {
"resolved": "../common",
"link": true
},
"node_modules/@types/duo_web_sdk": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@types/duo_web_sdk/-/duo_web_sdk-2.7.1.tgz",
"integrity": "sha512-DePanZjFww36yGSxXwC8B3AsjrrDuPxEcufeh4gTqVsUMpCYByxjX4PERiYZdW0typzKSt9E4I14PPp+PrSIQA==",
"dev": true
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"node_modules/duo_web_sdk": {
"version": "2.7.0",
"resolved": "git+ssh://git@github.com/duosecurity/duo_web_sdk.git#378e855ce4a1de1d1b2f7fd60465e564b3e9fbda",
"license": "SEE LICENSE IN LICENSE"
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"node_modules/glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"dependencies": {
"wrappy": "1"
}
},
"node_modules/parse5": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
"optional": true
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rxjs": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz",
"integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==",
"dependencies": {
"tslib": "~2.1.0"
}
},
"node_modules/rxjs/node_modules/tslib": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A=="
},
"node_modules/tldjs": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tldjs/-/tldjs-2.3.1.tgz",
"integrity": "sha512-W/YVH/QczLUxVjnQhFC61Iq232NWu3TqDdO0S/MtXVz4xybejBov4ud+CIwN9aYqjOecEqIy0PscGkwpG9ZyTw==",
"hasInstallScript": true,
"dependencies": {
"punycode": "^1.4.1"
},
"engines": {
"node": ">= 4"
}
},
"node_modules/tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"node_modules/typescript": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"node_modules/zone.js": {
"version": "0.11.4",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.4.tgz",
"integrity": "sha512-DDh2Ab+A/B+9mJyajPjHFPWfYU1H+pdun4wnnk0OcQTNjem1XQSZ2CDW+rfZEUDjv5M19SBqAkjZi0x5wuB5Qw==",
"dependencies": {
"tslib": "^2.0.0"
}
}
},
"dependencies": {
"@angular/animations": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.14.tgz",
"integrity": "sha512-1BR5u32auVePvXNNP96DB2008V+Ku0OGqeZQl2h4XA9xzES/Zk5WllIJZXqRmWMRBVARfXsfb0RdMty9gcaVjA==",
"requires": {
"tslib": "^2.2.0"
}
},
"@angular/cdk": {
"version": "12.2.13",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.13.tgz",
"integrity": "sha512-zSKRhECyFqhingIeyRInIyTvYErt4gWo+x5DQr0b7YLUbU8DZSwWnG4w76Ke2s4U8T7ry1jpJBHoX/e8YBpGMg==",
"requires": {
"parse5": "^5.0.0",
"tslib": "^2.2.0"
}
},
"@angular/common": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.14.tgz",
"integrity": "sha512-ffYUYdwZETmFJw0AcWY30WsaWBhJxj/zSmFXWjgEGEGZH56zwbbNwfMZOYZ1jz4haAVxGu+TdXsOl2yMGzN7jQ==",
"requires": {
"tslib": "^2.2.0"
}
},
"@angular/compiler": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.14.tgz",
"integrity": "sha512-dwmZi+n66IUzRFlGWu9mjXq170ZEsaDvlNLZzaPgs6vZTa4Kt7PWvIF/Y7TMvnVv/uqNG6kOhfmOkf6rfz1Gjg==",
"requires": {
"tslib": "^2.2.0"
}
},
"@angular/core": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.14.tgz",
"integrity": "sha512-dlVk7yqUHL2R/eCmM8LsWuxhEBfzg0y1zHt0UqCuFwlCoiw+IG4HFy4OlZEUw9NUEZJSv0aDv3sWqxLkvK5vvg==",
"requires": {
"tslib": "^2.2.0"
}
},
"@angular/forms": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.14.tgz",
"integrity": "sha512-/9/gSJUBXVRVdRnzgJnALAQZYJATuGDMkFC9ms9DEMG4PMAhe9x4if1lJjN6noz5RAom3qNuVBNWaYAPUxlcBQ==",
"requires": {
"tslib": "^2.2.0"
}
},
"@angular/platform-browser": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.14.tgz",
"integrity": "sha512-fWcE2rnJ3ZCISa1oPfsIDV7FBZBoLFEdDuMXAiDYqDPKvF/E5U5nHrS+K4SlLAi094bMobtTOReNWl/Ienniyw==",
"requires": {
"tslib": "^2.2.0"
}
},
"@angular/platform-browser-dynamic": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.14.tgz",
"integrity": "sha512-0NPF7mS91Tct8rBmOLZPmnLSuS4kbLHXo6eTgrg80OC0vlzBiQwGDVW4X3KncCoX9CpevaGJCdSMc+uPNsFOUQ==",
"requires": {
"tslib": "^2.2.0"
}
},
"@angular/router": {
"version": "12.2.14",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.14.tgz",
"integrity": "sha512-yP5grSnqBvc4vNhtYdcxDgDYIebUKs5f0xyFkUJM5030UnQ0CV45tBsSxHMkQbPZucIfOuxpRy8xy5+4GizuwQ==",
"requires": {
"tslib": "^2.2.0"
}
},
"@bitwarden/jslib-common": {
"version": "file:../common",
"requires": {
"@microsoft/signalr": "5.0.10",
"@microsoft/signalr-protocol-msgpack": "5.0.10",
"@types/lunr": "^2.3.3",
"@types/node": "^16.11.12",
"@types/node-forge": "^1.0.1",
"@types/papaparse": "^5.2.5",
"@types/tldjs": "^2.3.0",
"@types/zxcvbn": "^4.4.1",
"big-integer": "1.6.48",
"browser-hrtime": "^1.1.8",
"lunr": "^2.3.9",
"node-forge": "^1.2.1",
"papaparse": "^5.3.0",
"rimraf": "^3.0.2",
"rxjs": "^7.4.0",
"tldjs": "^2.3.1",
"typescript": "4.3.5",
"zxcvbn": "^4.4.2"
}
},
"@types/duo_web_sdk": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@types/duo_web_sdk/-/duo_web_sdk-2.7.1.tgz",
"integrity": "sha512-DePanZjFww36yGSxXwC8B3AsjrrDuPxEcufeh4gTqVsUMpCYByxjX4PERiYZdW0typzKSt9E4I14PPp+PrSIQA==",
"dev": true
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"duo_web_sdk": {
"version": "git+ssh://git@github.com/duosecurity/duo_web_sdk.git#378e855ce4a1de1d1b2f7fd60465e564b3e9fbda",
"from": "duo_web_sdk@git+https://github.com/duosecurity/duo_web_sdk.git"
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"parse5": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
"optional": true
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"rxjs": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz",
"integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==",
"requires": {
"tslib": "~2.1.0"
},
"dependencies": {
"tslib": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A=="
}
}
},
"tldjs": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tldjs/-/tldjs-2.3.1.tgz",
"integrity": "sha512-W/YVH/QczLUxVjnQhFC61Iq232NWu3TqDdO0S/MtXVz4xybejBov4ud+CIwN9aYqjOecEqIy0PscGkwpG9ZyTw==",
"requires": {
"punycode": "^1.4.1"
}
},
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"typescript": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
"dev": true
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"zone.js": {
"version": "0.11.4",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.4.tgz",
"integrity": "sha512-DDh2Ab+A/B+9mJyajPjHFPWfYU1H+pdun4wnnk0OcQTNjem1XQSZ2CDW+rfZEUDjv5M19SBqAkjZi0x5wuB5Qw==",
"requires": {
"tslib": "^2.0.0"
}
}
}
}

View File

@@ -0,0 +1,41 @@
{
"name": "@bitwarden/jslib-angular",
"version": "0.0.0",
"description": "Common code used across Bitwarden JavaScript projects.",
"keywords": [
"bitwarden"
],
"author": "Bitwarden Inc.",
"homepage": "https://bitwarden.com",
"repository": {
"type": "git",
"url": "https://github.com/bitwarden/jslib"
},
"license": "GPL-3.0",
"scripts": {
"clean": "rimraf dist/**/*",
"build": "npm run clean && tsc",
"build:watch": "npm run clean && tsc -watch"
},
"devDependencies": {
"@types/duo_web_sdk": "^2.7.1",
"rimraf": "^3.0.2",
"typescript": "4.3.5"
},
"dependencies": {
"@angular/animations": "^12.2.13",
"@angular/cdk": "^12.2.13",
"@angular/common": "^12.2.13",
"@angular/compiler": "^12.2.13",
"@angular/core": "^12.2.13",
"@angular/forms": "^12.2.13",
"@angular/platform-browser": "^12.2.13",
"@angular/platform-browser-dynamic": "^12.2.13",
"@angular/router": "^12.2.13",
"@bitwarden/jslib-common": "file:../common",
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
"rxjs": "^7.4.0",
"tldjs": "^2.3.1",
"zone.js": "0.11.4"
}
}

View File

@@ -0,0 +1,28 @@
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

@@ -0,0 +1,35 @@
<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

@@ -0,0 +1,78 @@
import { Component, Input, OnInit } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MasterPasswordPolicyOptions } from "jslib-common/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

@@ -0,0 +1,63 @@
import { Directive, EventEmitter, Output } from "@angular/core";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/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

@@ -0,0 +1,11 @@
<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

@@ -0,0 +1,112 @@
import { Component, Input, OnChanges } from "@angular/core";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { CipherType } from "jslib-common/enums/cipherType";
import { Utils } from "jslib-common/misc/utils";
import { CipherView } from "jslib-common/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

@@ -0,0 +1,79 @@
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

@@ -0,0 +1,10 @@
import { InjectFlags, InjectionToken, Injector, Type } from "@angular/core";
export class ModalInjector implements Injector {
constructor(private _parentInjector: Injector, private _additionalTokens: WeakMap<any, any>) {}
get<T>(token: Type<T> | InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
get(token: any, notFoundValue?: any, flags?: any) {
return this._additionalTokens.get(token) ?? this._parentInjector.get<any>(token, notFoundValue);
}
}

View File

@@ -0,0 +1,50 @@
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

@@ -0,0 +1,41 @@
import { Directive } from "@angular/core";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/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

@@ -0,0 +1,95 @@
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

@@ -0,0 +1,46 @@
<ng-container *ngIf="!usesKeyConnector">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
</ng-container>
<ng-container *ngIf="usesKeyConnector">
<div class="form-group">
<label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
<button
type="button"
class="btn btn-outline-secondary"
(click)="requestOTP()"
[disabled]="disableRequestOTP"
>
{{ "sendCode" | i18n }}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
{{ "codeSent" | i18n }}
</span>
</div>
<div class="form-group">
<label for="verificationCode">{{ "verificationCode" | i18n }}</label>
<input
id="verificationCode"
type="input"
name="verificationCode"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
</div>
</ng-container>

View File

@@ -0,0 +1,96 @@
import { animate, style, transition, trigger } from "@angular/animations";
import { Component, OnInit } from "@angular/core";
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from "@angular/forms";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
import { VerificationType } from "jslib-common/enums/verificationType";
import { Verification } from "jslib-common/types/verification";
/**
* Used for general-purpose user verification throughout the app.
* Collects the user's master password, or if they are using Key Connector, prompts for an OTP via email.
* This is exposed to the parent component via the ControlValueAccessor interface (e.g. bind it to a FormControl).
* Use UserVerificationService to verify the user's input.
*/
@Component({
selector: "app-user-verification",
templateUrl: "user-verification.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: UserVerificationComponent,
},
],
animations: [
trigger("sent", [
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
]),
],
})
export class UserVerificationComponent implements ControlValueAccessor, OnInit {
usesKeyConnector = false;
disableRequestOTP = false;
sentCode = false;
secret = new FormControl("");
private onChange: (value: Verification) => void;
constructor(
private keyConnectorService: KeyConnectorService,
private userVerificationService: UserVerificationService
) {}
async ngOnInit() {
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.processChanges(this.secret.value);
this.secret.valueChanges.subscribe((secret: string) => this.processChanges(secret));
}
async requestOTP() {
if (this.usesKeyConnector) {
this.disableRequestOTP = true;
try {
await this.userVerificationService.requestOTP();
this.sentCode = true;
} finally {
this.disableRequestOTP = false;
}
}
}
writeValue(obj: any): void {
this.secret.setValue(obj);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
// Not implemented
}
setDisabledState?(isDisabled: boolean): void {
this.disableRequestOTP = isDisabled;
if (isDisabled) {
this.secret.disable();
} else {
this.secret.enable();
}
}
private processChanges(secret: string) {
if (this.onChange == null) {
return;
}
this.onChange({
type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword,
secret: secret,
});
}
}

View File

@@ -0,0 +1,23 @@
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

@@ -0,0 +1,49 @@
import { Directive, ElementRef, Input, OnChanges } from "@angular/core";
import { LogService } from "jslib-common/abstractions/log.service";
import { ErrorResponse } from "jslib-common/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

@@ -0,0 +1,27 @@
import { Directive, ElementRef, Input, NgZone } from "@angular/core";
import { take } from "rxjs/operators";
import { Utils } from "jslib-common/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

@@ -0,0 +1,12 @@
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

@@ -0,0 +1,59 @@
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

@@ -0,0 +1,14 @@
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

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

View File

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

View File

@@ -0,0 +1,14 @@
import { Pipe, PipeTransform } from "@angular/core";
import { I18nService } from "jslib-common/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

@@ -0,0 +1,41 @@
import { Pipe, PipeTransform } from "@angular/core";
import { CipherView } from "jslib-common/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

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,250 @@
$icomoon-font-family: "bwi-font" !default;
$icomoon-font-path: "~@bitwarden/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

@@ -0,0 +1,44 @@
$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

@@ -0,0 +1,89 @@
@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

@@ -0,0 +1,45 @@
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
@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

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

View File

@@ -0,0 +1,492 @@
import { Injector, LOCALE_ID, NgModule } from "@angular/core";
import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.service";
import { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service";
import { AuditService as AuditServiceAbstraction } from "jslib-common/abstractions/audit.service";
import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service";
import { CipherService as CipherServiceAbstraction } from "jslib-common/abstractions/cipher.service";
import { CollectionService as CollectionServiceAbstraction } from "jslib-common/abstractions/collection.service";
import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
import { EventService as EventServiceAbstraction } from "jslib-common/abstractions/event.service";
import { ExportService as ExportServiceAbstraction } from "jslib-common/abstractions/export.service";
import { FileUploadService as FileUploadServiceAbstraction } from "jslib-common/abstractions/fileUpload.service";
import { FolderService as FolderServiceAbstraction } from "jslib-common/abstractions/folder.service";
import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
import { OrganizationService as OrganizationServiceAbstraction } from "jslib-common/abstractions/organization.service";
import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "jslib-common/abstractions/passwordGeneration.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService as PolicyServiceAbstraction } from "jslib-common/abstractions/policy.service";
import { ProviderService as ProviderServiceAbstraction } from "jslib-common/abstractions/provider.service";
import { SearchService as SearchServiceAbstraction } from "jslib-common/abstractions/search.service";
import { SendService as SendServiceAbstraction } from "jslib-common/abstractions/send.service";
import { SettingsService as SettingsServiceAbstraction } from "jslib-common/abstractions/settings.service";
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "jslib-common/abstractions/stateMigration.service";
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from "jslib-common/abstractions/sync.service";
import { TokenService as TokenServiceAbstraction } from "jslib-common/abstractions/token.service";
import { TotpService as TotpServiceAbstraction } from "jslib-common/abstractions/totp.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "jslib-common/abstractions/twoFactor.service";
import { UserVerificationService as UserVerificationServiceAbstraction } from "jslib-common/abstractions/userVerification.service";
import { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "jslib-common/abstractions/usernameGeneration.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service";
import { StateFactory } from "jslib-common/factories/stateFactory";
import { Account } from "jslib-common/models/domain/account";
import { GlobalState } from "jslib-common/models/domain/globalState";
import { ApiService } from "jslib-common/services/api.service";
import { AppIdService } from "jslib-common/services/appId.service";
import { AuditService } from "jslib-common/services/audit.service";
import { AuthService } from "jslib-common/services/auth.service";
import { CipherService } from "jslib-common/services/cipher.service";
import { CollectionService } from "jslib-common/services/collection.service";
import { ConsoleLogService } from "jslib-common/services/consoleLog.service";
import { CryptoService } from "jslib-common/services/crypto.service";
import { EnvironmentService } from "jslib-common/services/environment.service";
import { EventService } from "jslib-common/services/event.service";
import { ExportService } from "jslib-common/services/export.service";
import { FileUploadService } from "jslib-common/services/fileUpload.service";
import { FolderService } from "jslib-common/services/folder.service";
import { KeyConnectorService } from "jslib-common/services/keyConnector.service";
import { NotificationsService } from "jslib-common/services/notifications.service";
import { OrganizationService } from "jslib-common/services/organization.service";
import { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service";
import { PolicyService } from "jslib-common/services/policy.service";
import { ProviderService } from "jslib-common/services/provider.service";
import { SearchService } from "jslib-common/services/search.service";
import { SendService } from "jslib-common/services/send.service";
import { SettingsService } from "jslib-common/services/settings.service";
import { StateService } from "jslib-common/services/state.service";
import { StateMigrationService } from "jslib-common/services/stateMigration.service";
import { SyncService } from "jslib-common/services/sync.service";
import { TokenService } from "jslib-common/services/token.service";
import { TotpService } from "jslib-common/services/totp.service";
import { TwoFactorService } from "jslib-common/services/twoFactor.service";
import { UserVerificationService } from "jslib-common/services/userVerification.service";
import { UsernameGenerationService } from "jslib-common/services/usernameGeneration.service";
import { VaultTimeoutService } from "jslib-common/services/vaultTimeout.service";
import { WebCryptoFunctionService } from "jslib-common/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

@@ -0,0 +1,29 @@
import { Injectable } from "@angular/core";
import { CanActivate, Router } from "@angular/router";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/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

@@ -0,0 +1,180 @@
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

@@ -0,0 +1,45 @@
import { Injectable } from "@angular/core";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/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

@@ -0,0 +1,29 @@
import { Injectable } from "@angular/core";
import { CanActivate, Router } from "@angular/router";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/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

@@ -0,0 +1,38 @@
import { Injectable } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ErrorResponse } from "jslib-common/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

@@ -0,0 +1,11 @@
{
"extends": "../shared/tsconfig",
"compilerOptions": {
"paths": {
"jslib-common/*": ["../common/src/*"],
"jslib-angular/*": ["./src/"]
}
},
"include": ["src", "spec"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,3 @@
{
"extends": "./tsconfig.json"
}

View File

@@ -0,0 +1,18 @@
const { pathsToModuleNameMapper } = require("ts-jest/utils");
const { compilerOptions } = require("./tsconfig");
module.exports = {
name: "common",
displayName: "common jslib tests",
preset: "ts-jest",
testEnvironment: "jsdom",
testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
collectCoverage: true,
coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage",
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
};

956
jslib/common/package-lock.json generated Normal file
View File

@@ -0,0 +1,956 @@
{
"name": "@bitwarden/jslib-common",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@bitwarden/jslib-common",
"version": "0.0.0",
"license": "GPL-3.0",
"dependencies": {
"@microsoft/signalr": "5.0.10",
"@microsoft/signalr-protocol-msgpack": "5.0.10",
"big-integer": "1.6.48",
"browser-hrtime": "^1.1.8",
"lunr": "^2.3.9",
"node-forge": "^1.2.1",
"papaparse": "^5.3.0",
"rxjs": "^7.4.0",
"tldjs": "^2.3.1",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@types/lunr": "^2.3.3",
"@types/node": "^16.11.12",
"@types/node-forge": "^1.0.1",
"@types/papaparse": "^5.2.5",
"@types/tldjs": "^2.3.0",
"@types/zxcvbn": "^4.4.1",
"rimraf": "^3.0.2",
"typescript": "4.3.5"
}
},
"node_modules/@microsoft/signalr": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-5.0.10.tgz",
"integrity": "sha512-7jg6s/cmULyeVvt5/bTB4N9T30HvAF1S06hL+nPcQMODXcclRo34Zcli/dfTLR8lCX31/cVEOmVgxXBOVRQ+Dw==",
"dependencies": {
"abort-controller": "^3.0.0",
"eventsource": "^1.0.7",
"fetch-cookie": "^0.7.3",
"node-fetch": "^2.6.0",
"ws": "^6.0.0"
}
},
"node_modules/@microsoft/signalr-protocol-msgpack": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/@microsoft/signalr-protocol-msgpack/-/signalr-protocol-msgpack-5.0.10.tgz",
"integrity": "sha512-HqZiNLyjYP1ONeLgYUjFBUsnhxSp5CW4AW8InsLI7lyAXZl2drUhkiBxf3xK9UsTErO1+9r5sdaYdSmUY8nx9A==",
"dependencies": {
"@microsoft/signalr": ">=5.0.10",
"msgpack5": "^4.5.0"
}
},
"node_modules/@types/lunr": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@types/lunr/-/lunr-2.3.4.tgz",
"integrity": "sha512-j4x4XJwZvorEUbA519VdQ5b9AOU9TSvfi8tvxMAfP8XzNLtFex7A8vFQwqOx3WACbV0KMXbACV3cZl4/gynQ7g==",
"dev": true
},
"node_modules/@types/node": {
"version": "16.11.26",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.26.tgz",
"integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==",
"dev": true
},
"node_modules/@types/node-forge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.0.1.tgz",
"integrity": "sha512-96ELNKv9tQJ19afdBUiM5iDw7OYEc53iUc51gAPR2aGaqRsO1DBROjqgZRjZa1tkPj7TnEOR0EnyAX6iryGkzA==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/papaparse": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.2.tgz",
"integrity": "sha512-BNbCHJkTE4RwmAFkCxEalET4mDvGr/1ld7ZtQ4i/laWI/iiVt+GL07stdvufle4KfywyvloqqpIiJscXNCrKxA==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/tldjs": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@types/tldjs/-/tldjs-2.3.1.tgz",
"integrity": "sha512-BQR04zLE0ve2eNrqxXw/Qp/f6LxvNrj/4A8ZgdQi3SzbBqxFhleI7N4DS/mSjDnODrUaEGgoWg4grAZR1kVj8w==",
"dev": true
},
"node_modules/@types/zxcvbn": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@types/zxcvbn/-/zxcvbn-4.4.1.tgz",
"integrity": "sha512-3NoqvZC2W5gAC5DZbTpCeJ251vGQmgcWIHQJGq2J240HY6ErQ9aWKkwfoKJlHLx+A83WPNTZ9+3cd2ILxbvr1w==",
"dev": true
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/big-integer": {
"version": "1.6.48",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==",
"engines": {
"node": ">=0.6"
}
},
"node_modules/bl": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
"integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==",
"dependencies": {
"readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1"
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/browser-hrtime": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/browser-hrtime/-/browser-hrtime-1.1.8.tgz",
"integrity": "sha512-kzXheikaJsBtzUBlyVtPIY5r0soQePzjwVwT4IlDpU2RvfB5Py52gpU98M77rgqMCheoSSZvrcrdj3t6cZ3suA=="
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/es6-denodeify": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/es6-denodeify/-/es6-denodeify-0.1.5.tgz",
"integrity": "sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8="
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/eventsource": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
"dependencies": {
"original": "^1.0.0"
},
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/fetch-cookie": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.7.3.tgz",
"integrity": "sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA==",
"dependencies": {
"es6-denodeify": "^0.1.1",
"tough-cookie": "^2.3.3"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"node_modules/glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"node_modules/lunr": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/msgpack5": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.5.1.tgz",
"integrity": "sha512-zC1vkcliryc4JGlL6OfpHumSYUHWFGimSI+OgfRCjTFLmKA2/foR9rMTOhWiqfOrfxJOctrpWPvrppf8XynJxw==",
"dependencies": {
"bl": "^2.0.1",
"inherits": "^2.0.3",
"readable-stream": "^2.3.6",
"safe-buffer": "^5.1.2"
}
},
"node_modules/node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-forge": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz",
"integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==",
"engines": {
"node": ">= 6.13.0"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"dependencies": {
"wrappy": "1"
}
},
"node_modules/original": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
"integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
"dependencies": {
"url-parse": "^1.4.3"
}
},
"node_modules/papaparse": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz",
"integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA=="
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"node_modules/psl": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
},
"node_modules/punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
},
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"node_modules/readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/readable-stream/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rxjs": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
"integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/string_decoder/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/tldjs": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tldjs/-/tldjs-2.3.1.tgz",
"integrity": "sha512-W/YVH/QczLUxVjnQhFC61Iq232NWu3TqDdO0S/MtXVz4xybejBov4ud+CIwN9aYqjOecEqIy0PscGkwpG9ZyTw==",
"hasInstallScript": true,
"dependencies": {
"punycode": "^1.4.1"
},
"engines": {
"node": ">= 4"
}
},
"node_modules/tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"dependencies": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/tough-cookie/node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"engines": {
"node": ">=6"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"node_modules/tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"node_modules/typescript": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"node_modules/ws": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
"dependencies": {
"async-limiter": "~1.0.0"
}
},
"node_modules/zxcvbn": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz",
"integrity": "sha1-KOwXzwl0PtyrBW3dixsGJizHPDA="
}
},
"dependencies": {
"@microsoft/signalr": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-5.0.10.tgz",
"integrity": "sha512-7jg6s/cmULyeVvt5/bTB4N9T30HvAF1S06hL+nPcQMODXcclRo34Zcli/dfTLR8lCX31/cVEOmVgxXBOVRQ+Dw==",
"requires": {
"abort-controller": "^3.0.0",
"eventsource": "^1.0.7",
"fetch-cookie": "^0.7.3",
"node-fetch": "^2.6.0",
"ws": "^6.0.0"
}
},
"@microsoft/signalr-protocol-msgpack": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/@microsoft/signalr-protocol-msgpack/-/signalr-protocol-msgpack-5.0.10.tgz",
"integrity": "sha512-HqZiNLyjYP1ONeLgYUjFBUsnhxSp5CW4AW8InsLI7lyAXZl2drUhkiBxf3xK9UsTErO1+9r5sdaYdSmUY8nx9A==",
"requires": {
"@microsoft/signalr": ">=5.0.10",
"msgpack5": "^4.5.0"
}
},
"@types/lunr": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@types/lunr/-/lunr-2.3.4.tgz",
"integrity": "sha512-j4x4XJwZvorEUbA519VdQ5b9AOU9TSvfi8tvxMAfP8XzNLtFex7A8vFQwqOx3WACbV0KMXbACV3cZl4/gynQ7g==",
"dev": true
},
"@types/node": {
"version": "16.11.26",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.26.tgz",
"integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==",
"dev": true
},
"@types/node-forge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.0.1.tgz",
"integrity": "sha512-96ELNKv9tQJ19afdBUiM5iDw7OYEc53iUc51gAPR2aGaqRsO1DBROjqgZRjZa1tkPj7TnEOR0EnyAX6iryGkzA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/papaparse": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.2.tgz",
"integrity": "sha512-BNbCHJkTE4RwmAFkCxEalET4mDvGr/1ld7ZtQ4i/laWI/iiVt+GL07stdvufle4KfywyvloqqpIiJscXNCrKxA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/tldjs": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@types/tldjs/-/tldjs-2.3.1.tgz",
"integrity": "sha512-BQR04zLE0ve2eNrqxXw/Qp/f6LxvNrj/4A8ZgdQi3SzbBqxFhleI7N4DS/mSjDnODrUaEGgoWg4grAZR1kVj8w==",
"dev": true
},
"@types/zxcvbn": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@types/zxcvbn/-/zxcvbn-4.4.1.tgz",
"integrity": "sha512-3NoqvZC2W5gAC5DZbTpCeJ251vGQmgcWIHQJGq2J240HY6ErQ9aWKkwfoKJlHLx+A83WPNTZ9+3cd2ILxbvr1w==",
"dev": true
},
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"requires": {
"event-target-shim": "^5.0.0"
}
},
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"big-integer": {
"version": "1.6.48",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w=="
},
"bl": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
"integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==",
"requires": {
"readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1"
}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"browser-hrtime": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/browser-hrtime/-/browser-hrtime-1.1.8.tgz",
"integrity": "sha512-kzXheikaJsBtzUBlyVtPIY5r0soQePzjwVwT4IlDpU2RvfB5Py52gpU98M77rgqMCheoSSZvrcrdj3t6cZ3suA=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"es6-denodeify": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/es6-denodeify/-/es6-denodeify-0.1.5.tgz",
"integrity": "sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8="
},
"event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
"eventsource": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
"requires": {
"original": "^1.0.0"
}
},
"fetch-cookie": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.7.3.tgz",
"integrity": "sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA==",
"requires": {
"es6-denodeify": "^0.1.1",
"tough-cookie": "^2.3.3"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"lunr": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="
},
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"msgpack5": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.5.1.tgz",
"integrity": "sha512-zC1vkcliryc4JGlL6OfpHumSYUHWFGimSI+OgfRCjTFLmKA2/foR9rMTOhWiqfOrfxJOctrpWPvrppf8XynJxw==",
"requires": {
"bl": "^2.0.1",
"inherits": "^2.0.3",
"readable-stream": "^2.3.6",
"safe-buffer": "^5.1.2"
}
},
"node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"requires": {
"whatwg-url": "^5.0.0"
}
},
"node-forge": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz",
"integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w=="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"original": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
"integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
"requires": {
"url-parse": "^1.4.3"
}
},
"papaparse": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz",
"integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA=="
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"psl": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
},
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
},
"querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"rxjs": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
"integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
"requires": {
"tslib": "^2.1.0"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
},
"tldjs": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tldjs/-/tldjs-2.3.1.tgz",
"integrity": "sha512-W/YVH/QczLUxVjnQhFC61Iq232NWu3TqDdO0S/MtXVz4xybejBov4ud+CIwN9aYqjOecEqIy0PscGkwpG9ZyTw==",
"requires": {
"punycode": "^1.4.1"
}
},
"tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"requires": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
},
"dependencies": {
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
}
}
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"typescript": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
"dev": true
},
"url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"ws": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
"requires": {
"async-limiter": "~1.0.0"
}
},
"zxcvbn": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz",
"integrity": "sha1-KOwXzwl0PtyrBW3dixsGJizHPDA="
}
}
}

42
jslib/common/package.json Normal file
View File

@@ -0,0 +1,42 @@
{
"name": "@bitwarden/jslib-common",
"version": "0.0.0",
"description": "Common code used across Bitwarden JavaScript projects.",
"keywords": [
"bitwarden"
],
"author": "Bitwarden Inc.",
"homepage": "https://bitwarden.com",
"repository": {
"type": "git",
"url": "https://github.com/bitwarden/jslib"
},
"license": "GPL-3.0",
"scripts": {
"clean": "rimraf dist/**/*",
"build": "npm run clean && tsc",
"build:watch": "npm run clean && tsc -watch"
},
"devDependencies": {
"@types/lunr": "^2.3.3",
"@types/node": "^16.11.12",
"@types/node-forge": "^1.0.1",
"@types/papaparse": "^5.2.5",
"@types/tldjs": "^2.3.0",
"@types/zxcvbn": "^4.4.1",
"rimraf": "^3.0.2",
"typescript": "4.3.5"
},
"dependencies": {
"@microsoft/signalr": "5.0.10",
"@microsoft/signalr-protocol-msgpack": "5.0.10",
"big-integer": "1.6.48",
"browser-hrtime": "^1.1.8",
"lunr": "^2.3.9",
"node-forge": "^1.2.1",
"papaparse": "^5.3.0",
"rxjs": "^7.4.0",
"tldjs": "^2.3.1",
"zxcvbn": "^4.4.2"
}
}

View File

@@ -0,0 +1,83 @@
import Substitute, { Arg } from "@fluffy-spoon/substitute";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { AttachmentData } from "jslib-common/models/data/attachmentData";
import { Attachment } from "jslib-common/models/domain/attachment";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { ContainerService } from "jslib-common/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

@@ -0,0 +1,73 @@
import { CardData } from "jslib-common/models/data/cardData";
import { Card } from "jslib-common/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

@@ -0,0 +1,599 @@
import Substitute, { Arg } from "@fluffy-spoon/substitute";
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
import { CipherType } from "jslib-common/enums/cipherType";
import { FieldType } from "jslib-common/enums/fieldType";
import { SecureNoteType } from "jslib-common/enums/secureNoteType";
import { UriMatchType } from "jslib-common/enums/uriMatchType";
import { CipherData } from "jslib-common/models/data/cipherData";
import { Card } from "jslib-common/models/domain/card";
import { Cipher } from "jslib-common/models/domain/cipher";
import { Identity } from "jslib-common/models/domain/identity";
import { Login } from "jslib-common/models/domain/login";
import { SecureNote } from "jslib-common/models/domain/secureNote";
import { CardView } from "jslib-common/models/view/cardView";
import { IdentityView } from "jslib-common/models/view/identityView";
import { LoginView } from "jslib-common/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

@@ -0,0 +1,66 @@
import { CollectionData } from "jslib-common/models/data/collectionData";
import { Collection } from "jslib-common/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

@@ -0,0 +1,195 @@
import Substitute, { Arg } from "@fluffy-spoon/substitute";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EncryptionType } from "jslib-common/enums/encryptionType";
import { EncString } from "jslib-common/models/domain/encString";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { ContainerService } from "jslib-common/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

@@ -0,0 +1,64 @@
import { FieldType } from "jslib-common/enums/fieldType";
import { FieldData } from "jslib-common/models/data/fieldData";
import { Field } from "jslib-common/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

@@ -0,0 +1,42 @@
import { FolderData } from "jslib-common/models/data/folderData";
import { Folder } from "jslib-common/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

@@ -0,0 +1,134 @@
import { IdentityData } from "jslib-common/models/data/identityData";
import { Identity } from "jslib-common/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

@@ -0,0 +1,101 @@
import Substitute, { Arg } from "@fluffy-spoon/substitute";
import { UriMatchType } from "jslib-common/enums/uriMatchType";
import { LoginData } from "jslib-common/models/data/loginData";
import { Login } from "jslib-common/models/domain/login";
import { LoginUri } from "jslib-common/models/domain/loginUri";
import { LoginUriView } from "jslib-common/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

@@ -0,0 +1,57 @@
import { UriMatchType } from "jslib-common/enums/uriMatchType";
import { LoginUriData } from "jslib-common/models/data/loginUriData";
import { LoginUri } from "jslib-common/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

@@ -0,0 +1,51 @@
import { PasswordHistoryData } from "jslib-common/models/data/passwordHistoryData";
import { Password } from "jslib-common/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

@@ -0,0 +1,46 @@
import { SecureNoteType } from "jslib-common/enums/secureNoteType";
import { SecureNoteData } from "jslib-common/models/data/secureNoteData";
import { SecureNote } from "jslib-common/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

@@ -0,0 +1,144 @@
import Substitute, { Arg, SubstituteOf } from "@fluffy-spoon/substitute";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { SendType } from "jslib-common/enums/sendType";
import { SendData } from "jslib-common/models/data/sendData";
import { EncString } from "jslib-common/models/domain/encString";
import { Send } from "jslib-common/models/domain/send";
import { SendText } from "jslib-common/models/domain/sendText";
import { ContainerService } from "jslib-common/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

@@ -0,0 +1,84 @@
import Substitute, { Arg } from "@fluffy-spoon/substitute";
import { SendType } from "jslib-common/enums/sendType";
import { SendAccess } from "jslib-common/models/domain/sendAccess";
import { SendText } from "jslib-common/models/domain/sendText";
import { SendAccessResponse } from "jslib-common/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

@@ -0,0 +1,57 @@
import { SendFileData } from "jslib-common/models/data/sendFileData";
import { SendFile } from "jslib-common/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

@@ -0,0 +1,47 @@
import { SendTextData } from "jslib-common/models/data/sendTextData";
import { SendText } from "jslib-common/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

@@ -0,0 +1,69 @@
import { EncryptionType } from "jslib-common/enums/encryptionType";
import { SymmetricCryptoKey } from "jslib-common/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

@@ -0,0 +1,31 @@
import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { BitwardenJsonImporter } from "jslib-common/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

@@ -0,0 +1,113 @@
import Substitute, { Arg, SubstituteOf } from "@fluffy-spoon/substitute";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KdfType } from "jslib-common/enums/kdfType";
import { BitwardenPasswordProtectedImporter } from "jslib-common/importers/bitwardenPasswordProtectedImporter";
import { Utils } from "jslib-common/misc/utils";
import { ImportResult } from "jslib-common/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

@@ -0,0 +1,367 @@
import { CipherType } from "jslib-common/enums/cipherType";
import { DashlaneCsvImporter as Importer } from "jslib-common/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

@@ -0,0 +1,74 @@
import { FirefoxCsvImporter as Importer } from "jslib-common/importers/firefoxCsvImporter";
import { CipherView } from "jslib-common/models/view/cipherView";
import { LoginUriView } from "jslib-common/models/view/loginUriView";
import { LoginView } from "jslib-common/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

@@ -0,0 +1,77 @@
import { FSecureFskImporter as Importer } from "jslib-common/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

@@ -0,0 +1,189 @@
import { KeePass2XmlImporter as Importer } from "jslib-common/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

@@ -0,0 +1,108 @@
import { KeeperJsonImporter as Importer } from "jslib-common/importers/keeperImporters/keeperJsonImporter";
import { Utils } from "jslib-common/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

@@ -0,0 +1,202 @@
import { CipherType } from "jslib-common/enums/cipherType";
import { FieldType } from "jslib-common/enums/fieldType";
import { LastPassCsvImporter as Importer } from "jslib-common/importers/lastpassCsvImporter";
import { ImportResult } from "jslib-common/models/domain/importResult";
import { CipherView } from "jslib-common/models/view/cipherView";
import { FieldView } from "jslib-common/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

@@ -0,0 +1,633 @@
import { CipherType } from "jslib-common/enums/cipherType";
import { MykiCsvImporter as Importer } from "jslib-common/importers/mykiCsvImporter";
import { CipherView } from "jslib-common/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

@@ -0,0 +1,181 @@
import { CipherType } from "jslib-common/enums/cipherType";
import { SecureNoteType } from "jslib-common/enums/secureNoteType";
import { NordPassCsvImporter as Importer } from "jslib-common/importers/nordpassCsvImporter";
import { CipherView } from "jslib-common/models/view/cipherView";
import { IdentityView } from "jslib-common/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

@@ -0,0 +1,527 @@
import { FieldType } from "jslib-common/enums/fieldType";
import { OnePassword1PifImporter as Importer } from "jslib-common/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

@@ -0,0 +1,689 @@
import { CipherType } from "jslib-common/enums/cipherType";
import { FieldType } from "jslib-common/enums/fieldType";
import { SecureNoteType } from "jslib-common/enums/secureNoteType";
import { OnePassword1PuxImporter as Importer } from "jslib-common/importers/onepasswordImporters/onepassword1PuxImporter";
import { Utils } from "jslib-common/misc/utils";
import { FieldView } from "jslib-common/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

@@ -0,0 +1,74 @@
import { CipherType } from "jslib-common/enums/cipherType";
import { OnePasswordMacCsvImporter as Importer } from "jslib-common/importers/onepasswordImporters/onepasswordMacCsvImporter";
import { CipherView } from "jslib-common/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]);
});
});

View File

@@ -0,0 +1,87 @@
import { CipherType } from "jslib-common/enums/cipherType";
import { FieldType } from "jslib-common/enums/fieldType";
import { OnePasswordWinCsvImporter as Importer } from "jslib-common/importers/onepasswordImporters/onepasswordWinCsvImporter";
import { CipherView } from "jslib-common/models/view/cipherView";
import { FieldView } from "jslib-common/models/view/fieldView";
import { data as creditCardData } from "./testData/onePasswordCsv/creditCard.windows.csv";
import { data as identityData } from "./testData/onePasswordCsv/identity.windows.csv";
import { data as multiTypeData } from "./testData/onePasswordCsv/multipleItems.windows.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.fields).toEqual(
expect.arrayContaining([
Object.assign(new FieldView(), {
type: FieldType.Text,
name: "address",
value: "address city state zip us",
}),
])
);
}
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: "1970",
})
);
}
describe("1Password windows CSV Importer", () => {
let importer: Importer;
beforeEach(() => {
importer = new Importer();
});
it("should parse identity records", async () => {
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 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 types", async () => {
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]);
});
});

View File

@@ -0,0 +1,74 @@
import { SafariCsvImporter as Importer } from "jslib-common/importers/safariCsvImporter";
import { CipherView } from "jslib-common/models/view/cipherView";
import { LoginUriView } from "jslib-common/models/view/loginUriView";
import { LoginView } from "jslib-common/models/view/loginView";
import { data as oldSimplePasswordData } from "./testData/safariCsv/oldSimplePasswordData.csv";
import { data as simplePasswordData } from "./testData/safariCsv/simplePasswordData.csv";
const CipherData = [
{
title: "should parse URLs in new CSV format",
csv: simplePasswordData,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "example.com (example_user)",
login: Object.assign(new LoginView(), {
username: "example_user",
password: "example_p@ssword",
uris: [
Object.assign(new LoginUriView(), {
uri: "https://example.com",
}),
],
totp: "otpauth://totp/test?secret=examplesecret",
}),
notes: "Example note\nMore notes on new line",
type: 1,
}),
},
{
title: "should parse URLs in old CSV format",
csv: oldSimplePasswordData,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "example.com (example_user)",
login: Object.assign(new LoginView(), {
username: "example_user",
password: "example_p@ssword",
uris: [
Object.assign(new LoginUriView(), {
uri: "https://example.com",
}),
],
}),
type: 1,
}),
},
];
describe("Safari 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

@@ -0,0 +1 @@
export const data = '{"encrypted":false,"folders":[],"items":[]}';

View File

@@ -0,0 +1,9 @@
export const data = `{
"encrypted": true,
"passwordProtected": true,
"salt": "Oy0xcgVRzxQ+9NpB5GLehw==",
"kdfIterations": 100000,
"kdfType": 0,
"encKeyValidation_DO_NOT_EDIT": "2.sZs4Jc1HW9rhABzRRYR/gQ==|8kTDaDxafulnybpWoqVX8RAybhVRTr+dffNjms271Y7amQmIE1VSMwLbk+b2vxZb|IqOo6oXQtmv/Xb/GHDi42XG9c9ILePYtP5qq584VWcg=",
"data": "2.D0AXAf7G/XIwq6EC7A0Suw==|4w+m0wHRo25y1T1Syh5wdAUyF8voqEy54waMEsbnK0Nzee959w54ru5D1NntvxZL4HFqkQLyR6jCFkn5g40f+MGJgihS/wvf4NcJJfLiiFo6MEDOQNBkxw7ZBGuHiKfVuBO5u36JgzQtZ8lyFaduGxFszuF5c+URiE9PDh9jY0//poVgHKwuLZuYFIW+f7h6T+shUWK0ya11lcHn/B/CA2xiI+YiKdNZreJrwN0yslpJ/f+MrOzagvftRjt0GNkwveCtwcYUw/zFvqvibUpKeHcRiXs8SaGoHJ5RTm69FbJ7C5tnLwoVT89Af156uvRAXV7yAC4oPcbU/3TGb6hqYosvi1QNyaqG3M9gxS6+AK0C4yWuNbMLDEr+MWiw0SWLVMKQEkCZ4oM+oTCx52otW3+2V9I8Pv3KmmhkvVvE4wBdweOJeRX53Tf5ySkmpIhCfzj6JMmxO+nmTXIhWnJChr4hPVh+ixv1GQK5thIPTCMXmAtXoTIFUx1KWjS6LjOdi2hKQueVI+XZjf0qnY2vTMxRg0ZsLBA2znQTx+DSEqumORb5T/lV73pWZiCNePSAE2msOm7tep+lm4O/VCViCfXjITAY196syhOK0XnhxJvPALchZY8sYRAfuw6hHoDiVr+JUieRoI7eUrhXBp+D6Py9TL/dS/rHe+C2Zhx+xwx2NfGt+xEp8ZAOOCxgZ0UTeSA/abm0Oz7tJIK1n26acQrgbr7rMeBymAX+5L5OWlwI1hGgEBfj6W0rrbSXf3VMfaFXZ5UsXi1VhzQmU3LyWENoDeImXFQj6zMbUSfcVwLsG5Fg8Ee/kO/wJPfG5BO51+/vFqQj6AkaMEcwg5xNrObHYfQ/DMhIn7YDM2zdzbNTdhnobGkz6YRKFPCgFe3EmIEPEpeh9S3eKE9C7MQsrR8jVSiseR/FipJLsN+W7iOwzeXdwxUFlC/0a98bTKvdrbMgNi6ZVXykHY/t2UyEGpxZGTHoZwhX01kiQrwzC4/+v/676ldxPluO9GY7MtrLveCDsiyBz15u43IGHayDEBNT0rqrOKLYmfzwCWoahRLZQrSmepe/FXqgPqRfyWc/Ro+w3sT9dXUkx3B5xxWgSyABowPV48yBUSJuefhKTpqgzkU+LzhNnWHjnxJzzQ2/|IhlRjnyhIoDM85qHX/bY2zaIU5YaRO/iFVTQDd3uFDo="
}`;

View File

@@ -0,0 +1,2 @@
export const credentialsData = `username,username2,username3,title,password,note,url,category,otpSecret
jdoe,,,example.com,somePassword,some note for example.com,https://www.example.com,Entertainment,someTOTPSeed`;

View File

@@ -0,0 +1,6 @@
export const identityData = `type,number,name,issue_date,expiration_date,place_of_issue,state
card,123123123,John Doe,2022-1-30,2032-1-30,,
passport,123123123,John Doe,2022-1-30,2032-1-30,somewhere in Germany,
license,1234556,John Doe,2022-8-10,2022-10-10,,DC
social_security,123123123,John Doe,,,,
tax_number,123123123,,,,,`;

View File

@@ -0,0 +1,7 @@
export const multiplePersonalInfoData = `type,title,first_name,middle_name,last_name,login,date_of_birth,place_of_birth,email,email_type,item_name,phone_number,address,country,state,city,zip,address_recipient,address_building,address_apartment,address_floor,address_door_code,job_title,url
name,MR,John,,Doe,jdoe,2022-01-30,world,,,,,,,,,,,,,,,,
email,,,,,,,,jdoe@example.com,personal,Johns email,,,,,,,,,,,,,
number,,,,,,,,,,John's number,+49123123123,,,,,,,,,,,,
address,,,,,,,,,,John's home address,,1 some street,de,DE-0-NW,some city,123123,John,1,1,1,123,,
website,,,,,,,,,,Website,,,,,,,,,,,,,website.com
name,Mrs,Jane,,Doe,jdoe,2022-01-30,earth,,,,,,,,,,,,,,,,`;

View File

@@ -0,0 +1,3 @@
export const paymentsData = `type,account_name,account_holder,cc_number,code,expiration_month,expiration_year,routing_number,account_number,country,issuing_bank
bank,John's savings account,John Doe,,,,,routingNumber,accountNumber,US,US-ALLY
credit_card,John Doe,,41111111111111111,123,01,2023,,,US,`;

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