mirror of
https://github.com/bitwarden/directory-connector
synced 2026-02-11 22:13:34 +00:00
Compare commits
467 Commits
DEVOPS-986
...
dev-clarit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
623382f9e1 | ||
|
|
1aad9e1cbe | ||
|
|
3059934d4c | ||
|
|
42cf13df08 | ||
|
|
1a9f0a2ca7 | ||
|
|
30b3595de3 | ||
|
|
28f0ff4b24 | ||
|
|
14fc69c810 | ||
|
|
1ad0aea61f | ||
|
|
f41156969c | ||
|
|
39b151b1e0 | ||
|
|
483f26fa6f | ||
|
|
8849385d1b | ||
|
|
a7aff97360 | ||
|
|
7381857296 | ||
|
|
ba17d5b438 | ||
|
|
b5d31e693b | ||
|
|
2854a2eba1 | ||
|
|
4485ecab3c | ||
|
|
9e3b2d2d95 | ||
|
|
b2997358dc | ||
|
|
db258f0191 | ||
|
|
19d7884933 | ||
|
|
21ce02f431 | ||
|
|
1af8fc1067 | ||
|
|
6c2f54bad5 | ||
|
|
bb9a6a61ee | ||
|
|
f0a19b6267 | ||
|
|
220d6c02c7 | ||
|
|
321db6e771 | ||
|
|
554e14d7a8 | ||
|
|
f195e27938 | ||
|
|
d1ac1e667e | ||
|
|
b9867b131f | ||
|
|
bb165441ee | ||
|
|
b8964aa382 | ||
|
|
db5268ccd1 | ||
|
|
9a719c9e4e | ||
|
|
2f49f4d5f1 | ||
|
|
5426f251a7 | ||
|
|
a0c30350d4 | ||
|
|
6f3d8f73e1 | ||
|
|
d7be5486c7 | ||
|
|
ce43f651ab | ||
|
|
eda713bcc9 | ||
|
|
b53e145e62 | ||
|
|
2ad35be82e | ||
|
|
bdfc8ae5eb | ||
|
|
7d218eac2f | ||
|
|
ccbb24d504 | ||
|
|
dd1f36e3d6 | ||
|
|
0780f9a931 | ||
|
|
62c8a64298 | ||
|
|
0d3bbc1db8 | ||
|
|
99655a0abf | ||
|
|
2883ff6068 | ||
|
|
f5abaf114a | ||
|
|
5792578946 | ||
|
|
6b3b29a1a0 | ||
|
|
02809be178 | ||
|
|
6abfdd8a88 | ||
|
|
b95f57c4e7 | ||
|
|
9ecfc29ae4 | ||
|
|
e32f29b8e7 | ||
|
|
e333db372d | ||
|
|
a44eb28be8 | ||
|
|
ab436551de | ||
|
|
10e17adfb2 | ||
|
|
c7db8376ec | ||
|
|
bc996d680f | ||
|
|
fe01b49df1 | ||
|
|
daeb96713f | ||
|
|
f6791dabef | ||
|
|
a3a5ed8531 | ||
|
|
d3d62c30aa | ||
|
|
f81155b6b3 | ||
|
|
57a3ef04cc | ||
|
|
4e21b28276 | ||
|
|
1c2a0c677b | ||
|
|
5666f09e89 | ||
|
|
b13895bdd6 | ||
|
|
29fc4ad61e | ||
|
|
f722196149 | ||
|
|
a4ec6df118 | ||
|
|
01e60bf090 | ||
|
|
7c27202dab | ||
|
|
77ea7a395d | ||
|
|
a259de8b26 | ||
|
|
06dbc14136 | ||
|
|
e74546e8c3 | ||
|
|
5ac0cc408e | ||
|
|
9044f94f43 | ||
|
|
1b2c854569 | ||
|
|
e5b3e58a02 | ||
|
|
32b29d2d34 | ||
|
|
a68744524c | ||
|
|
cee7700895 | ||
|
|
b2c60aab1e | ||
|
|
ab76a7eac4 | ||
|
|
d662c05b3e | ||
|
|
ec2c40a565 | ||
|
|
8dc2be7fab | ||
|
|
2879d9c38c | ||
|
|
71ca0772a9 | ||
|
|
6ff39dd207 | ||
|
|
489effb852 | ||
|
|
acb5bc4d25 | ||
|
|
cac411fb29 | ||
|
|
94881d0db0 | ||
|
|
a7c3c40570 | ||
|
|
88af7d6b12 | ||
|
|
3716e5ca57 | ||
|
|
3cc4f90688 | ||
|
|
afa6ced621 | ||
|
|
68efd0a86e | ||
|
|
7fb8732e1e | ||
|
|
48acb783fe | ||
|
|
3df63b8ddf | ||
|
|
ed40b17a80 | ||
|
|
460de6a075 | ||
|
|
4784d45d23 | ||
|
|
60d9a35239 | ||
|
|
5ffd761326 | ||
|
|
55fe14b744 | ||
|
|
c0cbf7651a | ||
|
|
926202f80a | ||
|
|
3013e5f06f | ||
|
|
6789a14527 | ||
|
|
66c38dc18f | ||
|
|
763497e160 | ||
|
|
c28a93bdbe | ||
|
|
3715df42d7 | ||
|
|
a643175a99 | ||
|
|
0c1d20aaa6 | ||
|
|
c51e37e77d | ||
|
|
eec7420826 | ||
|
|
284206b735 | ||
|
|
51042857c9 | ||
|
|
a462ae7457 | ||
|
|
5dfd60d25c | ||
|
|
19937fcbe9 | ||
|
|
46405ad75c | ||
|
|
04ee7533e4 | ||
|
|
02aa653a48 | ||
|
|
921466677e | ||
|
|
e8f0d17944 | ||
|
|
2cc2292ed6 | ||
|
|
c96e0bb147 | ||
|
|
6964a7db49 | ||
|
|
32e3327974 | ||
|
|
3a46e1781e | ||
|
|
dc64f7191e | ||
|
|
570bcf1581 | ||
|
|
fc06bf401a | ||
|
|
61d7c996c1 | ||
|
|
71a19fecaa | ||
|
|
ae37cea276 | ||
|
|
09f1f6981c | ||
|
|
ceff0559f2 | ||
|
|
4d55bf0527 | ||
|
|
7347c1992f | ||
|
|
46d2797d8c | ||
|
|
ed58d7c758 | ||
|
|
cd6bbd792a | ||
|
|
3b3ea8ac47 | ||
|
|
5f9adf9ab7 | ||
|
|
1deb22a446 | ||
|
|
115a60316d | ||
|
|
e11225b2ce | ||
|
|
4909d306ba | ||
|
|
caa8c4d070 | ||
|
|
ed1d941282 | ||
|
|
f6f874360f | ||
|
|
18b110e70d | ||
|
|
83c42cec73 | ||
|
|
2d80fceb8c | ||
|
|
0489f0cbe9 | ||
|
|
c5d4cb9fb6 | ||
|
|
16d6647090 | ||
|
|
a08673917b | ||
|
|
27e1ab9bcf | ||
|
|
3573e201a6 | ||
|
|
23d285a9f6 | ||
|
|
527d2cb75d | ||
|
|
42efd689e3 | ||
|
|
2fe980dea6 | ||
|
|
9446eedec7 | ||
|
|
41ee0d82d5 | ||
|
|
40a85bb875 | ||
|
|
50be1218e2 | ||
|
|
e4abb2c751 | ||
|
|
23c591f903 | ||
|
|
2ea2fd701c | ||
|
|
3b74be446e | ||
|
|
2651a53f27 | ||
|
|
09ed8326c3 | ||
|
|
c5a65a85ad | ||
|
|
3ae90cbb4c | ||
|
|
99dbb3162e | ||
|
|
f146d41b66 | ||
|
|
b35cf8e995 | ||
|
|
f7ee5dcd92 | ||
|
|
61bbff771e | ||
|
|
2047b6644e | ||
|
|
26dd9662cf | ||
|
|
70073fb570 | ||
|
|
8642b9d7aa | ||
|
|
d77b50c540 | ||
|
|
ed935d998a | ||
|
|
682da52040 | ||
|
|
531619af1d | ||
|
|
cf54858cc5 | ||
|
|
6cc022b135 | ||
|
|
a8a4390624 | ||
|
|
f9d817f0b1 | ||
|
|
112bda1137 | ||
|
|
23713d92fa | ||
|
|
6ebc9631aa | ||
|
|
e8579f11d3 | ||
|
|
6b2c7a5f00 | ||
|
|
2a1a5bf064 | ||
|
|
1464d72b27 | ||
|
|
f5cbd8f03d | ||
|
|
fdbbef68c1 | ||
|
|
efb412684d | ||
|
|
79f7a2b495 | ||
|
|
4342734412 | ||
|
|
62f14e5043 | ||
|
|
c2b22518fe | ||
|
|
37c992f16b | ||
|
|
69156677ac | ||
|
|
aaed7b13ea | ||
|
|
096d2a03ab | ||
|
|
bd5bcbebd9 | ||
|
|
bb9ece6078 | ||
|
|
40de47e6e3 | ||
|
|
094ed57e03 | ||
|
|
96a38e2d76 | ||
|
|
9e200c8705 | ||
|
|
ca945318ed | ||
|
|
04abed9251 | ||
|
|
9b08ca6db8 | ||
|
|
0cbe6e9d33 | ||
|
|
dda6dd99ed | ||
|
|
5492466276 | ||
|
|
ef571ec0c3 | ||
|
|
f2bea1b6d7 | ||
|
|
07a1ae6dea | ||
|
|
f23997dd72 | ||
|
|
18547d6eaa | ||
|
|
c3a4f25160 | ||
|
|
e57a52e483 | ||
|
|
ff1380ee67 | ||
|
|
2269b82e7e | ||
|
|
8ab3516377 | ||
|
|
91dfd7e0b7 | ||
|
|
6db28408e6 | ||
|
|
bdacf3d4e4 | ||
|
|
9c566f50a2 | ||
|
|
e3fbc4e731 | ||
|
|
3c5bafe39f | ||
|
|
d65f42684e | ||
|
|
743b4b44cb | ||
|
|
1931a7f065 | ||
|
|
7d01bf0c6c | ||
|
|
c28ce25381 | ||
|
|
74c152f142 | ||
|
|
d4e317d804 | ||
|
|
605facb464 | ||
|
|
5a6ae00a82 | ||
|
|
19344bf696 | ||
|
|
8d8b3ca13e | ||
|
|
4fee3298f8 | ||
|
|
493e819b1c | ||
|
|
df4eb139e4 | ||
|
|
6cdd07429c | ||
|
|
278ec1acf0 | ||
|
|
e3d3c856c5 | ||
|
|
37ac5fc936 | ||
|
|
15978f69b1 | ||
|
|
ee7d97b797 | ||
|
|
930d7cf224 | ||
|
|
cf106b148e | ||
|
|
cfd0722587 | ||
|
|
b462a78c18 | ||
|
|
4bb96f049c | ||
|
|
b991fea958 | ||
|
|
111b8bd646 | ||
|
|
42af888615 | ||
|
|
a28fad020b | ||
|
|
f190433348 | ||
|
|
1e211becc3 | ||
|
|
4652c6489f | ||
|
|
9dc497dd13 | ||
|
|
3d9465917d | ||
|
|
6b1b6bf1c4 | ||
|
|
f52af53dad | ||
|
|
6e6039d298 | ||
|
|
332d07eca6 | ||
|
|
c3ed541efd | ||
|
|
4ae4cba877 | ||
|
|
3ecca16f50 | ||
|
|
1f30ef165f | ||
|
|
dfd8fce231 | ||
|
|
0b7c0ec9c2 | ||
|
|
6d569e9319 | ||
|
|
3ed4e76f95 | ||
|
|
abf7e0400c | ||
|
|
5600f20760 | ||
|
|
bbc65d77e3 | ||
|
|
5d0cde9cfa | ||
|
|
eff7c848f8 | ||
|
|
46fb407c0c | ||
|
|
e2fe5ef9ad | ||
|
|
dd10538d0f | ||
|
|
6d355812e0 | ||
|
|
c973d5fea0 | ||
|
|
01a3f68480 | ||
|
|
d31f14cfe7 | ||
|
|
401daa0187 | ||
|
|
1951b9507d | ||
|
|
eae9cac931 | ||
|
|
21cecc3c0a | ||
|
|
21638f3fdc | ||
|
|
f47806ddd2 | ||
|
|
c304650a6a | ||
|
|
d01522bfc4 | ||
|
|
14314a3553 | ||
|
|
ffac82e865 | ||
|
|
decada8745 | ||
|
|
3a639bb8f2 | ||
|
|
a2b5dac108 | ||
|
|
6e76d8fcbd | ||
|
|
63b06f6950 | ||
|
|
f730aeba23 | ||
|
|
52a8a35f41 | ||
|
|
601a83ebfa | ||
|
|
0cdb12229e | ||
|
|
423a48ab2e | ||
|
|
9a2bf331bc | ||
|
|
0d211f351c | ||
|
|
d1d4f53866 | ||
|
|
dede587b78 | ||
|
|
d76a6e993d | ||
|
|
32d514ebcc | ||
|
|
1f08c7e5cc | ||
|
|
dd80dce657 | ||
|
|
ff6dff329a | ||
|
|
8c6f1aab90 | ||
|
|
9a3aae16a7 | ||
|
|
1480445d35 | ||
|
|
fc04964663 | ||
|
|
cc05bcb4a6 | ||
|
|
5ce3b01ff1 | ||
|
|
079c3ee840 | ||
|
|
f88ce25b59 | ||
|
|
599473f6e4 | ||
|
|
df389cbd08 | ||
|
|
051b6dc3cf | ||
|
|
5727dd75cc | ||
|
|
435f2d10b7 | ||
|
|
dab646675f | ||
|
|
8dc65ef371 | ||
|
|
9925fdea40 | ||
|
|
4c61498714 | ||
|
|
930f8c84d5 | ||
|
|
d20818ee49 | ||
|
|
6936c218d1 | ||
|
|
8ef5459801 | ||
|
|
cb615412aa | ||
|
|
2d69d2b791 | ||
|
|
0630b4f52e | ||
|
|
690c9cd5cb | ||
|
|
3f0454b1d8 | ||
|
|
1a84084b5d | ||
|
|
c5fb57576c | ||
|
|
165083a245 | ||
|
|
84f1f5b81f | ||
|
|
9599c66586 | ||
|
|
38b2a13df6 | ||
|
|
1fb4378046 | ||
|
|
8a661fbc5e | ||
|
|
cf56b5fb57 | ||
|
|
9c88e66a27 | ||
|
|
5b7b68f1cb | ||
|
|
a09473c632 | ||
|
|
71727dae7d | ||
|
|
3dbd34ebc3 | ||
|
|
6dd121acc6 | ||
|
|
d6ddb499f0 | ||
|
|
5b4e09be93 | ||
|
|
a48e0af042 | ||
|
|
a133718eb7 | ||
|
|
37bdd75c67 | ||
|
|
a9f1d32ce0 | ||
|
|
6e76f23653 | ||
|
|
39ed9359fe | ||
|
|
d1cb92b5e7 | ||
|
|
eacdb6b8a8 | ||
|
|
95f613d61a | ||
|
|
c259962279 | ||
|
|
9126d4ae59 | ||
|
|
f3b01afd0b | ||
|
|
82b5f9bd04 | ||
|
|
187d1e17b1 | ||
|
|
e1300f585a | ||
|
|
11f5e2993a | ||
|
|
ff87907b4e | ||
|
|
1163f34317 | ||
|
|
7a2e9ecec6 | ||
|
|
bab928c07c | ||
|
|
1546cc2012 | ||
|
|
36ab2953b5 | ||
|
|
9a89e95918 | ||
|
|
013c8a5293 | ||
|
|
a5f779c231 | ||
|
|
dd7df6504b | ||
|
|
f064de83e8 | ||
|
|
e27bdbc561 | ||
|
|
93407d061b | ||
|
|
8848edbb40 | ||
|
|
33da38a3f3 | ||
|
|
26e4930a9e | ||
|
|
4dba787df2 | ||
|
|
2e2b5d988c | ||
|
|
d0c126cf3e | ||
|
|
8700332d13 | ||
|
|
0e4f9c0de5 | ||
|
|
c76033b8ad | ||
|
|
50cfc50ed7 | ||
|
|
739ab3d2dd | ||
|
|
1ac402ca06 | ||
|
|
a3c8629f6d | ||
|
|
161c5f82be | ||
|
|
0476c97f5f | ||
|
|
41967d6dc1 | ||
|
|
40908e4b88 | ||
|
|
9e7a11a27c | ||
|
|
a491e2691d | ||
|
|
2c65003cf9 | ||
|
|
0d78b33ae1 | ||
|
|
a3939a31a9 | ||
|
|
3540a2741e | ||
|
|
0ddf81f644 | ||
|
|
83d527a83e | ||
|
|
63b7b9124f | ||
|
|
5d85df2105 | ||
|
|
dbc3c8795d | ||
|
|
f09f7a0e51 | ||
|
|
e3afe9fb69 | ||
|
|
1a6c51b3aa | ||
|
|
107d7afb26 | ||
|
|
0959e58dc9 | ||
|
|
911f9c0dfc | ||
|
|
7cf518edb5 | ||
|
|
4234b3b1cf | ||
|
|
0f5cdd53df | ||
|
|
50bd7ca2b3 | ||
|
|
8531a64568 | ||
|
|
c1cec89995 | ||
|
|
3d214dbedc | ||
|
|
a528480e07 | ||
|
|
af5c863f1f | ||
|
|
365bda7e21 | ||
|
|
f1b533f7b6 | ||
|
|
5bf9b128d4 | ||
|
|
0a8c4d30bb |
619
.claude/CLAUDE.md
Normal file
619
.claude/CLAUDE.md
Normal file
@@ -0,0 +1,619 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
# Bitwarden Directory Connector
|
||||
|
||||
## Project Overview
|
||||
|
||||
Directory Connector is a TypeScript application that synchronizes users and groups from directory services to Bitwarden organizations. It provides both a desktop GUI (built with Angular and Electron) and a CLI tool (bwdc).
|
||||
|
||||
**Supported Directory Services:**
|
||||
|
||||
- LDAP (Lightweight Directory Access Protocol) - includes Active Directory and general LDAP servers
|
||||
- Microsoft Entra ID (formerly Azure Active Directory)
|
||||
- Google Workspace
|
||||
- Okta
|
||||
- OneLogin
|
||||
|
||||
**Technologies:**
|
||||
|
||||
- TypeScript
|
||||
- Angular (GUI)
|
||||
- Electron (Desktop wrapper)
|
||||
- Node
|
||||
- Jest for testing
|
||||
|
||||
### Current Project Status
|
||||
|
||||
**Mission Critical but Deprioritized:** Directory Connector is used to sync customer directory services with their Bitwarden organization. While SCIM is the more modern cloud-hosted solution, not all directory services support SCIM, and SCIM is only available on Enterprise plans. Therefore, DC remains mission-critical infrastructure for many paying customers, but it's deprioritized in the codebase due to infrequent changes.
|
||||
|
||||
**Isolated Repository:** Unlike other Bitwarden client applications that live in a monorepo with shared core libraries, Directory Connector was kept separate when other TypeScript clients moved to the monorepo. It got its own copy of the jslib repo to avoid unnecessary regressions from apparently unrelated code changes in other clients. This severed it from the rest of the codebase, causing:
|
||||
|
||||
- Outdated dependencies that can't be updated (ES modules vs CommonJS conflicts)
|
||||
- File/folder structure that doesn't match modern Bitwarden client patterns
|
||||
- Accumulated technical debt requiring significant investment to pay down
|
||||
- jslib contains unused code from all clients, but cannot be deleted due to monolithic/tightly coupled architecture
|
||||
|
||||
**Critical Issues (Current Status):**
|
||||
|
||||
- ✅ ~~Electron, Node, and Angular are on unmaintained versions~~ **RESOLVED** - All updated (Electron 39, Node 20, Angular 21, TypeScript 5.9)
|
||||
- ❌ `keytar` is archived (Dec 2022) and incompatible with Node v22, **blocking Node upgrades beyond v20** - **PRIMARY BLOCKER**
|
||||
- ❌ No ESM support blocks dependency upgrades: googleapis, lowdb, chalk, inquirer, node-fetch, electron-store
|
||||
- ⚠️ 70 dev dependencies + 31 runtime dependencies = excessive maintenance burden (count increased with Angular 21 tooling)
|
||||
- ❌ StateService is a large pre-StateProvider monolith containing every getter/setter for all clients (PM-31159 In Progress)
|
||||
- ✅ ~~Angular CLI not used~~ **RESOLVED** - Angular CLI 21.1.2 now integrated with angular.json configuration
|
||||
|
||||
**Development Approach:** When working on this codebase, prioritize sustainability and maintainability over adding new features. Consider how changes will affect long-term maintenance burden.
|
||||
|
||||
## Tech Debt Roadmap
|
||||
|
||||
### Progress Summary
|
||||
|
||||
**Completed:**
|
||||
|
||||
- ✅ Phase 0 (Immediate Priority): All major dependencies upgraded (Node 20, Angular 21, TypeScript 5.9, Electron 39)
|
||||
- ✅ Phase 6: Angular CLI integration complete
|
||||
|
||||
**In Progress:**
|
||||
|
||||
- 🔄 Phase 1: StateService rewrite (PM-31159)
|
||||
|
||||
**Blocked/Todo:**
|
||||
|
||||
- ❌ Phase 2: Remove remaining jslib code (blocked by Phase 1)
|
||||
- ❌ Phase 3: Repository restructure (should be done before Phase 5)
|
||||
- ⚠️ Phase 4: Replace Keytar **[CRITICAL BLOCKER]** - blocking Node v22+ upgrades
|
||||
- ❌ Phase 5: ESM Support (blocked by Phase 3, needed for googleapis, lowdb, chalk, inquirer, etc.)
|
||||
|
||||
**Primary Blocker:** Keytar removal (Phase 4) is the most critical task as it blocks Node upgrades beyond v20.
|
||||
|
||||
---
|
||||
|
||||
### ✅ Immediate Priority: Unsupported Dependencies (COMPLETED)
|
||||
|
||||
**Upgrade Path (July 2025 release) - STATUS: COMPLETE**
|
||||
|
||||
All major version upgrades have been completed and exceeded targets:
|
||||
|
||||
1. ✅ Node 18.20.8 → 20.18 → **COMPLETE** (engines: `~20`, .nvmrc: `v20`)
|
||||
2. ✅ Angular 17 → 18.2.x → **EXCEEDED** (now at **21.1.1**)
|
||||
3. ✅ TypeScript 5.4.5 → 5.6.0 → **EXCEEDED** (now at **5.9.3**)
|
||||
4. ✅ Electron 34 → 36 → **EXCEEDED** (now at **39.2.1**)
|
||||
5. ✅ Angular matches clients monorepo version (21.x)
|
||||
|
||||
**Current Versions:**
|
||||
|
||||
- Node: v20 (project target), blocked from v22+ by keytar
|
||||
- TypeScript: 5.9.3
|
||||
- Angular: 21.1.1 (all packages)
|
||||
- Electron: 39.2.1 (well beyond EOL target of 36)
|
||||
- @yao-pkg/pkg: 5.16.1 (community fork replacing archived pkg)
|
||||
|
||||
**Note:** Further Node upgrades to v22+ are **blocked by keytar** (see Phase 4). Electron 36 was EOL October 2028, but we're already on 39.2.1.
|
||||
|
||||
### Phase 1: StateService Rewrite (PM-31159, In Progress)
|
||||
|
||||
**Problem:** StateService is a post-account-switching, pre-StateProvider monolith containing every getter/setter for all clients. This prevents deletion of unused data models and code. Never very stable, and more complex than DC needs (DC doesn't need account switching).
|
||||
|
||||
**Current Status:** 🔄 **Active PR** - [#990](https://github.com/bitwarden/directory-connector/pull/990) (Open, Author: @BTreston)
|
||||
|
||||
- PR created: Feb 2, 2026
|
||||
- Last updated: Feb 5, 2026
|
||||
- Files changed: 17 files (+1,512, -41 lines)
|
||||
- Commits: 4 (scaffold, add tests, fix type issues, fix integration test)
|
||||
|
||||
**Implementation Details:**
|
||||
|
||||
**New Architecture:**
|
||||
|
||||
- Created `StateServiceVNext` interface (`src/abstractions/state-vNext.service.ts`)
|
||||
- New implementation: `StateServiceVNextImplementation` (`src/services/state-service/state-vNext.service.ts`)
|
||||
- New state model with flat key-value structure (`src/models/state.model.ts`)
|
||||
- Comprehensive test suite: `state-vNext.service.spec.ts` (488 lines of tests)
|
||||
|
||||
**Storage Key Structure:**
|
||||
|
||||
```typescript
|
||||
// vNext Storage Keys (Flat key-value structure)
|
||||
StorageKeysVNext = {
|
||||
stateVersion: "stateVersion",
|
||||
directoryType: "directoryType",
|
||||
organizationId: "organizationId",
|
||||
directory_ldap: "directory_ldap",
|
||||
directory_gsuite: "directory_gsuite",
|
||||
directory_entra: "directory_entra",
|
||||
directory_okta: "directory_okta",
|
||||
directory_onelogin: "directory_onelogin",
|
||||
sync: "sync",
|
||||
syncingDir: "syncingDir",
|
||||
};
|
||||
|
||||
// Secure storage keys for sensitive data
|
||||
SecureStorageKeysVNext = {
|
||||
ldap: "secret_ldap",
|
||||
gsuite: "secret_gsuite",
|
||||
azure: "secret_azure", // Backwards compatible with old name
|
||||
entra: "secret_entra",
|
||||
okta: "secret_okta",
|
||||
oneLogin: "secret_oneLogin",
|
||||
userDelta: "userDeltaToken",
|
||||
groupDelta: "groupDeltaToken",
|
||||
lastUserSync: "lastUserSync",
|
||||
lastGroupSync: "lastGroupSync",
|
||||
lastSyncHash: "lastSyncHash",
|
||||
};
|
||||
```
|
||||
|
||||
**Migration Strategy:**
|
||||
|
||||
- State version bumped to `StateVersion.Five` (`jslib/common/src/enums/stateVersion.ts`)
|
||||
- Enhanced `StateMigrationService` to handle migration from old account-based structure to new flat structure
|
||||
- Migration keys defined for backwards compatibility (`MigrationKeys`, `SecureStorageKeysMigration`)
|
||||
- Temporary keys used during migration (`TempKeys`) to preserve data during transition
|
||||
|
||||
**File Organization:**
|
||||
|
||||
- State-related files moved to `src/services/state-service/` subdirectory:
|
||||
- `state-vNext.service.ts` (new implementation)
|
||||
- `state-vNext.service.spec.ts` (488 lines of tests)
|
||||
- `state.service.ts` (legacy, moved from `src/services/`)
|
||||
- `stateMigration.service.ts` (enhanced for v5 migration)
|
||||
- New abstraction: `src/abstractions/state-vNext.service.ts`
|
||||
- New model: `src/models/state.model.ts` (defines all storage keys)
|
||||
|
||||
**Integration:**
|
||||
|
||||
- Both old `StateService` and new `StateServiceVNext` injected in parallel during migration phase
|
||||
- `DirectoryFactoryService` updated to accept both services
|
||||
- Services module provides both implementations
|
||||
- CLI (`bwdc.ts`) and GUI (`main.ts`) both instantiate new service alongside old one
|
||||
|
||||
**Chosen Approach Benefits:**
|
||||
|
||||
- Clean break with old StateService - high degree of certainty
|
||||
- Simple and focused on DC's needs (no account switching, no rxjs)
|
||||
- Flat key-value structure easier to maintain
|
||||
- Versioning and migration capabilities included
|
||||
- Keeps existing data.json around during transition
|
||||
- All getters/setters in one place (acceptable for small application)
|
||||
|
||||
**Rejected Approaches:**
|
||||
|
||||
- Copy StateProvider from clients: Too complex (supports account switching, rxjs, syncing background/foreground contexts)
|
||||
- Rewrite simplified StateService keeping current data structure: Commits us to previous decisions, keeps monolithic account objects
|
||||
|
||||
**Next Steps:**
|
||||
|
||||
- Complete PR review and merge
|
||||
- Monitor for regressions during initial rollout
|
||||
- After several releases, can remove old StateService and migration code
|
||||
- Begin Phase 2: Remove remaining jslib code that was only needed by old StateService
|
||||
|
||||
### Phase 2: Remove Remaining jslib Code
|
||||
|
||||
After StateService is removed, review and delete old models and remaining services that referenced each other. jslib contains unused code from all clients that DC doesn't need.
|
||||
|
||||
### Phase 3: Restructure Repository (PM-31852, To Do)
|
||||
|
||||
**Current Structure:**
|
||||
|
||||
```
|
||||
src/ # Both Electron and CLI app code
|
||||
src-cli/ # package.json entry point for CLI only, no code
|
||||
jslib/
|
||||
├── common/ # Shared common code
|
||||
├── node/ # Node specific code used in CLI
|
||||
└── electron/ # Electron specific code used in GUI
|
||||
```
|
||||
|
||||
**Target Structure:**
|
||||
|
||||
```
|
||||
src-gui/ # Electron specific code only (combining src (partial) + jslib/electron)
|
||||
src-cli/ # Node and CLI specific code only (combining src (partial) + jslib/node)
|
||||
libs/ # Shared app-independent DC code, e.g. sync services (combining src (partial) + jslib/common)
|
||||
```
|
||||
|
||||
**Why:** Makes subsequent changes (code reorganizing, ESM support) much easier. This should be done early in the modernization process.
|
||||
|
||||
### Phase 4: Replace Keytar (PM-12436, To Do) ⚠️ **CRITICAL BLOCKER**
|
||||
|
||||
**Problem:** `keytar` (OS secure storage for secrets) was archived December 2022 and is incompatible with Node v22, **actively blocking Node upgrades beyond v20**.
|
||||
|
||||
**Current Status:**
|
||||
|
||||
- `keytar`: **7.9.0** (still present in dependencies)
|
||||
- **This is the #1 blocker preventing Node v22+ upgrades**
|
||||
- All "Immediate Priority" dependencies have been upgraded, but further progress requires removing keytar
|
||||
|
||||
**Solution:** Migrate to Bitwarden's Rust implementation in `desktop_native` (same as clients monorepo did)
|
||||
|
||||
1. Implement Rust <-> NAPI integration (like `desktop_native/napi`) from Electron app to Rust code
|
||||
2. Copy, rename, and expose necessary functions
|
||||
3. Point to `desktop_native` crate using git link from DC repo (no need for SDK yet):
|
||||
```rust
|
||||
desktop_core = { git = "https://github.com/bitwarden/clients", rev = "00cf24972d944638bbd1adc00a0ae3eeabb6eb9a" }
|
||||
```
|
||||
|
||||
**Important:** `keytar` uses wrong encoding on Windows (UTF-8 instead of UTF-16). Bitwarden uses UTF-16. Code should contain a migration - ensure old values are migrated correctly during testing.
|
||||
|
||||
**Priority:** This should be prioritized as it's blocking the Node upgrade path and has been archived for over 2 years.
|
||||
|
||||
### Phase 5: Add ESM Support (PM-31850, To Do)
|
||||
|
||||
**Problem:** No ESM module support prevents upgrading key dependencies.
|
||||
|
||||
**Blocked Dependencies (Current Status):**
|
||||
|
||||
- ❌ `googleapis`: **149.0.0** → current (major dependency, disabled in renovate.json5)
|
||||
- ❌ `lowdb`: **1.0.0** → v7
|
||||
- ❌ `@types/lowdb`: **1.0.15** (can be deleted once inquirer is upgraded)
|
||||
- ❌ `@electron/notarize`: **2.5.0** → v3.0.1
|
||||
- ❌ `chalk`: **4.1.2** → v5.3.0
|
||||
- ❌ `inquirer`: **8.2.6** → v12.1.0
|
||||
- ❌ `@types/inquirer`: **8.2.10** (should be deleted when inquirer upgraded)
|
||||
- ❌ `node-fetch`: **2.7.0** → v3.3.2 (should use native Node fetch API when on Node >=21)
|
||||
- ❌ `electron-store`: **8.2.0** → v10.1.0
|
||||
|
||||
**Status:** These dependencies remain blocked as expected. They will stay on old versions until:
|
||||
|
||||
1. Phase 3 (Repository Restructure) is complete
|
||||
2. ESM support is implemented
|
||||
3. Note: These ESM dependencies are primarily used in CLI build, so restructuring first (Phase 3) will limit the impact of ESM migration.
|
||||
|
||||
**Implementation:**
|
||||
|
||||
1. Update tsconfig.json and package.json configurations
|
||||
2. Update import/export syntax to no longer use `require` statements
|
||||
3. Upgrade dependencies to move away from CommonJS (ESM can import CommonJS, but not vice versa)
|
||||
4. Trial and error
|
||||
|
||||
**Reference:** [Pure ESM package guide](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c)
|
||||
|
||||
### Phase 6: Add Angular CLI (PM-31849, In Progress / Possibly Complete?)
|
||||
|
||||
**Problem:** Angular CLI provides great DX and makes it easier to manage Angular changes (e.g. auto-migrations). DC didn't use it.
|
||||
|
||||
**Current Status:**
|
||||
|
||||
- ✅ `@angular/cli`: **21.1.2** is now present in **runtime dependencies**
|
||||
- ✅ `@angular/build`: **21.1.2** is present in dev dependencies
|
||||
- ✅ All Angular tooling has been updated to v21.x
|
||||
|
||||
**Status:** ✅ **COMPLETE** - Angular CLI has been successfully integrated:
|
||||
|
||||
- `angular.json` configuration file exists
|
||||
- `.angular/` cache directory present
|
||||
- `@angular/cli` 21.1.2 in runtime dependencies
|
||||
- `@angular/build` 21.1.2 in dev dependencies
|
||||
- All Angular packages updated to v21.x
|
||||
|
||||
This migration provides improved DX and access to Angular's auto-migration tools for future updates.
|
||||
|
||||
### Additional Considerations
|
||||
|
||||
**Reduce Dependency Count:** Current state is 70 dev dependencies + 31 runtime dependencies (101 total). The dev dependency count increased from the original 66 due to Angular 21 upgrade adding additional tooling. After removing old code, review dependency list:
|
||||
|
||||
- Can we remove some after code cleanup?
|
||||
- Could we reintegrate with monorepo to leverage Component Library and shared platform dependencies?
|
||||
- **Risk:** Becomes tightly coupled with monorepo code → regression risk, move slower due to coupling
|
||||
|
||||
**GitHub Workflows:** Need review and modernization:
|
||||
|
||||
- PM-20478: Add check-run workflow for CI on community PRs
|
||||
- PM-18290: Add linting workflow
|
||||
- PM-18289: Update build workflow
|
||||
- `pkg` and `pkg-fetch` for packaging Node runtime in CLI release are archived (fork exists but untrusted; clients vets all changes manually)
|
||||
- Options: Make our own fork, or use Node's single executable binary support (investigate)
|
||||
|
||||
## Common Development Commands
|
||||
|
||||
### Desktop App (Electron + Angular)
|
||||
|
||||
**Initial Setup:**
|
||||
|
||||
```bash
|
||||
npm install # Install dependencies (runs git submodule init automatically)
|
||||
npm run rebuild # Rebuild native modules for Electron
|
||||
```
|
||||
|
||||
**Development:**
|
||||
|
||||
```bash
|
||||
npm run electron # Build and run desktop app with hot reload and debugging
|
||||
npm run electron:ignore # Same as above but ignores certificate errors
|
||||
```
|
||||
|
||||
**Building:**
|
||||
|
||||
```bash
|
||||
npm run build # Build both main and renderer processes
|
||||
npm run build:main # Build Electron main process only
|
||||
npm run build:renderer # Build Angular renderer process only
|
||||
npm run build:renderer:watch # Build renderer with file watching
|
||||
```
|
||||
|
||||
**Distribution:**
|
||||
|
||||
```bash
|
||||
npm run dist:mac # Create macOS distributable
|
||||
npm run dist:win # Create Windows distributable
|
||||
npm run dist:lin # Create Linux distributable
|
||||
```
|
||||
|
||||
### CLI (bwdc)
|
||||
|
||||
**Development:**
|
||||
|
||||
```bash
|
||||
npm run build:cli:watch # Build CLI with file watching
|
||||
node ./build-cli/bwdc.js --help # Run the CLI from build output
|
||||
```
|
||||
|
||||
**Production Build:**
|
||||
|
||||
```bash
|
||||
npm run build:cli:prod # Build CLI for production
|
||||
npm run dist:cli # Create platform-specific CLI executables (all platforms)
|
||||
npm run dist:cli:mac # Create macOS CLI executable only
|
||||
npm run dist:cli:win # Create Windows CLI executable only
|
||||
npm run dist:cli:lin # Create Linux CLI executable only
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
**Unit Tests:**
|
||||
|
||||
```bash
|
||||
npm test # Run unit tests (excludes integration tests)
|
||||
npm run test:watch # Run unit tests in watch mode
|
||||
npm run test:watch:all # Run unit tests in watch mode (all files)
|
||||
npm run test:types # Run TypeScript type checking without emitting files
|
||||
```
|
||||
|
||||
**Integration Tests:**
|
||||
|
||||
```bash
|
||||
npm run test:integration:setup # Set up Docker containers for LDAP testing
|
||||
npm run test:integration # Run integration tests
|
||||
npm run test:integration:watch # Run integration tests in watch mode
|
||||
```
|
||||
|
||||
Integration tests require Docker and test against live directory services. The setup command creates OpenLDAP containers using docker-compose.yml.
|
||||
|
||||
### Linting & Formatting
|
||||
|
||||
```bash
|
||||
npm run lint # Run ESLint and Prettier checks
|
||||
npm run lint:fix # Auto-fix ESLint issues
|
||||
npm run prettier # Format all files with Prettier
|
||||
```
|
||||
|
||||
### Submodule Management
|
||||
|
||||
The `jslib` folder is a git submodule containing shared Bitwarden libraries:
|
||||
|
||||
```bash
|
||||
npm run sub:update # Update submodule to latest remote version
|
||||
npm run sub:pull # Pull latest changes in submodule
|
||||
npm run sub:commit # Pull and commit submodule update
|
||||
```
|
||||
|
||||
### Utility Commands
|
||||
|
||||
```bash
|
||||
npm run reset # Remove keytar modules and reinstall (use when switching between CLI/desktop)
|
||||
npm run clean:dist # Clean desktop distribution files
|
||||
npm run clean:dist:cli # Clean CLI distribution files
|
||||
```
|
||||
|
||||
**Important:** When switching between developing the desktop app and CLI, run `npm run reset` to avoid native module conflicts.
|
||||
|
||||
## Code Architecture & Structure
|
||||
|
||||
### Directory Organization
|
||||
|
||||
```
|
||||
src/
|
||||
├── abstractions/ # Interface definitions (e.g., IDirectoryService)
|
||||
├── services/ # Business logic implementations for directory services, sync, auth
|
||||
├── models/ # Data models (UserEntry, GroupEntry, etc.)
|
||||
├── commands/ # CLI command implementations
|
||||
├── app/ # Angular GUI components
|
||||
└── utils/ # Test utilities and fixtures
|
||||
|
||||
src-cli/ # CLI-specific code (imports common code from src/)
|
||||
|
||||
jslib/ # Legacy folder structure (mix of deprecated/unused and current code - new code should not be added here)
|
||||
```
|
||||
|
||||
### Key Architectural Patterns
|
||||
|
||||
1. **Abstractions = Interfaces**: All interfaces are defined in `/abstractions`
|
||||
2. **Services = Business Logic**: Implementations live in `/services`
|
||||
3. **Directory Service Pattern**: Each directory provider implements `IDirectoryService` interface
|
||||
4. **Separation of Concerns**: GUI (Angular app) and CLI (commands) share the same service layer
|
||||
|
||||
### Core Synchronization Flow
|
||||
|
||||
The sync process follows this pattern:
|
||||
|
||||
1. **DirectoryFactoryService** (`src/services/directory-factory.service.ts`) - Creates the appropriate directory service based on DirectoryType configuration
|
||||
2. **IDirectoryService** implementation (`src/services/directory-services/*.service.ts`) - Each provider (LDAP, Entra ID, Google, Okta, OneLogin) implements:
|
||||
- `getEntries(force, test)` - Returns `[GroupEntry[], UserEntry[]]`
|
||||
- Provider-specific authentication and API calls
|
||||
3. **SyncService** (`src/services/sync.service.ts`) - Orchestrates the sync:
|
||||
- Calls directory service to get entries
|
||||
- Filters and deduplicates users/groups
|
||||
- Uses BatchRequestBuilder or SingleRequestBuilder to format API requests
|
||||
- Generates hash to detect changes and avoid redundant syncs
|
||||
- Sends data to Bitwarden API via ApiService
|
||||
4. **Request Builders** (`src/services/*-request-builder.ts`) - Transform directory entries into Bitwarden API format
|
||||
|
||||
### Shared Library (jslib)
|
||||
|
||||
The `jslib` folder is a git submodule containing shared Bitwarden code:
|
||||
|
||||
- Common services (API, Crypto, Storage, Auth)
|
||||
- Platform utilities
|
||||
- Shared models and abstractions
|
||||
|
||||
**Important:** This is legacy structure - do not add new code to jslib. New code should go in `src/`.
|
||||
|
||||
## Development Conventions
|
||||
|
||||
### Code Organization
|
||||
|
||||
**File Naming:**
|
||||
|
||||
- kebab-case for files: `ldap-directory.service.ts`
|
||||
- Descriptive names that reflect purpose
|
||||
|
||||
**Class/Function Naming:**
|
||||
|
||||
- PascalCase for classes and interfaces
|
||||
- camelCase for functions and variables
|
||||
- Descriptive names that indicate purpose
|
||||
|
||||
**File Structure:**
|
||||
|
||||
- Keep files focused on single responsibility
|
||||
- Create new service files for distinct directory integrations
|
||||
- Separate models into individual files when complex
|
||||
|
||||
### TypeScript Conventions
|
||||
|
||||
**Import Patterns:**
|
||||
|
||||
- Use path aliases (`@/`) for project imports
|
||||
- `@/` - project root
|
||||
- `@/jslib/` - jslib folder
|
||||
- ESLint enforces alphabetized import ordering with newlines between groups
|
||||
|
||||
**Type Safety:**
|
||||
|
||||
- Avoid `any` types - use proper typing or `unknown` with type guards
|
||||
- Prefer interfaces for contracts, types for unions/intersections
|
||||
- Use strict null checks - handle `null` and `undefined` explicitly
|
||||
- Leverage TypeScript's type inference where appropriate
|
||||
|
||||
**Configuration:**
|
||||
|
||||
- Use configuration files or environment variables
|
||||
- Never hardcode URLs or configuration values
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
**Credential Handling:**
|
||||
|
||||
- Never log directory service credentials, API keys, or tokens
|
||||
- Use secure storage mechanisms for sensitive data
|
||||
- Credentials should never be hardcoded
|
||||
- Store credentials encrypted, never in plain text
|
||||
|
||||
**Sensitive Data:**
|
||||
|
||||
- User and group data from directories should be handled securely
|
||||
- Avoid exposing sensitive information in error messages
|
||||
- Sanitize data before logging
|
||||
- Be cautious with data persistence
|
||||
|
||||
**Input Validation:**
|
||||
|
||||
- Validate and sanitize data from external directory services
|
||||
- Check for injection vulnerabilities (LDAP injection, etc.)
|
||||
- Validate configuration inputs from users
|
||||
|
||||
**API Security:**
|
||||
|
||||
- Ensure authentication flows are implemented correctly
|
||||
- Verify SSL/TLS is used for all external connections
|
||||
- Check for secure token storage and refresh mechanisms
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Best Practices:**
|
||||
|
||||
1. **Try-catch for async operations** - Always wrap external API calls
|
||||
2. **Meaningful error messages** - Provide context for debugging
|
||||
3. **Error propagation** - Don't swallow errors silently
|
||||
4. **User-facing errors** - Separate user messages from developer logs
|
||||
|
||||
## Performance Best Practices
|
||||
|
||||
**Large Dataset Handling:**
|
||||
|
||||
- Use pagination for large user/group lists
|
||||
- Avoid loading entire datasets into memory at once
|
||||
- Consider streaming or batch processing for large operations
|
||||
|
||||
**API Rate Limiting:**
|
||||
|
||||
- Respect rate limits for Microsoft Graph API, Google Admin SDK, etc.
|
||||
- Consider batching large API calls where necessary
|
||||
|
||||
**Memory Management:**
|
||||
|
||||
- Close connections and clean up resources
|
||||
- Remove event listeners when components are destroyed
|
||||
- Be cautious with caching large datasets
|
||||
|
||||
## Testing
|
||||
|
||||
**Framework:**
|
||||
|
||||
- Jest with jest-preset-angular
|
||||
- jest-mock-extended for type-safe mocks with `mock<Type>()`
|
||||
|
||||
**Test Organization:**
|
||||
|
||||
- Tests colocated with source files
|
||||
- `*.spec.ts` - Unit tests for individual components/services
|
||||
- `*.integration.spec.ts` - Integration tests against live directory services
|
||||
- Test helpers located in `utils/` directory
|
||||
|
||||
**Test Naming:**
|
||||
|
||||
- Descriptive, human-readable test names
|
||||
- Example: `'should return empty array when no users exist in directory'`
|
||||
|
||||
**Test Coverage:**
|
||||
|
||||
- New features must include tests
|
||||
- Bug fixes should include regression tests
|
||||
- Changes to core sync logic or directory specific logic require integration tests
|
||||
|
||||
**Testing Approach:**
|
||||
|
||||
- **Unit tests**: Mock external API calls using jest-mock-extended
|
||||
- **Integration tests**: Use live directory services (Docker containers or configured cloud services)
|
||||
- Focus on critical paths (authentication, sync, data transformation)
|
||||
- Test error scenarios and edge cases (empty results, malformed data, connection failures), not just happy paths
|
||||
|
||||
## Directory Service Patterns
|
||||
|
||||
### IDirectoryService Interface
|
||||
|
||||
All directory services implement this core interface with methods:
|
||||
|
||||
- `getUsers()` - Retrieve users from directory and transform them into standard objects
|
||||
- `getGroups()` - Retrieve groups from directory and transform them into standard objects
|
||||
- Connection and authentication handling
|
||||
|
||||
### Service-Specific Implementations
|
||||
|
||||
Each directory service has unique authentication and query patterns:
|
||||
|
||||
- **LDAP**: Direct LDAP queries, bind authentication
|
||||
- **Microsoft Entra ID**: Microsoft Graph API, OAuth tokens
|
||||
- **Google Workspace**: Google Admin SDK, service account credentials
|
||||
- **Okta/OneLogin**: REST APIs with API tokens
|
||||
|
||||
## References
|
||||
|
||||
- [Architectural Decision Records (ADRs)](https://contributing.bitwarden.com/architecture/adr/)
|
||||
- [Contributing Guidelines](https://contributing.bitwarden.com/contributing/)
|
||||
- [Code Style](https://contributing.bitwarden.com/contributing/code-style/)
|
||||
- [Security Whitepaper](https://bitwarden.com/help/bitwarden-security-white-paper/)
|
||||
- [Security Definitions](https://contributing.bitwarden.com/architecture/security/definitions)
|
||||
1
.depcheckrc
Normal file
1
.depcheckrc
Normal file
@@ -0,0 +1 @@
|
||||
ignores: ["*-loader", "webpack-cli", "@types/jest"]
|
||||
@@ -1,10 +0,0 @@
|
||||
dist
|
||||
build
|
||||
build-cli
|
||||
webpack.cli.js
|
||||
webpack.main.js
|
||||
webpack.renderer.js
|
||||
|
||||
**/node_modules
|
||||
|
||||
**/jest.config.js
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": ["./jslib/shared/eslintrc.json"],
|
||||
"rules": {
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"alphabetize": {
|
||||
"order": "asc"
|
||||
},
|
||||
"newlines-between": "always",
|
||||
"pathGroups": [
|
||||
{
|
||||
"pattern": "jslib-*/**",
|
||||
"group": "external",
|
||||
"position": "after"
|
||||
},
|
||||
{
|
||||
"pattern": "src/**/*",
|
||||
"group": "parent",
|
||||
"position": "before"
|
||||
}
|
||||
],
|
||||
"pathGroupsExcludedImportTypes": ["builtin"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
19
.github/CODEOWNERS
vendored
Normal file
19
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Please sort into logical groups with comment headers. Sort groups in order of specificity.
|
||||
# For example, default owners should always be the first group.
|
||||
# Sort lines alphabetically within these groups to avoid accidentally adding duplicates.
|
||||
#
|
||||
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
|
||||
# Default file owners.
|
||||
* @bitwarden/team-admin-console-dev
|
||||
|
||||
# Docker-related files
|
||||
**/Dockerfile @bitwarden/team-appsec @bitwarden/dept-bre
|
||||
**/*.dockerignore @bitwarden/team-appsec @bitwarden/dept-bre
|
||||
**/entrypoint.sh @bitwarden/team-appsec @bitwarden/dept-bre
|
||||
**/docker-compose.yml @bitwarden/team-appsec @bitwarden/dept-bre
|
||||
|
||||
# Claude related files
|
||||
.claude/ @bitwarden/team-ai-sme
|
||||
.github/workflows/respond.yml @bitwarden/team-ai-sme
|
||||
.github/workflows/review-code.yml @bitwarden/team-ai-sme
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Feature Requests
|
||||
url: https://community.bitwarden.com/c/feature-requests/
|
||||
about: Request new features using the Community Forums. Please search existing feature requests before making a new one.
|
||||
- name: Bitwarden Community Forums
|
||||
url: https://community.bitwarden.com
|
||||
about: Please visit the community forums for general community discussion, support and the development roadmap.
|
||||
- name: Customer Support
|
||||
url: https://bitwarden.com/contact/
|
||||
about: Please contact our customer support for account issues and general customer support.
|
||||
- name: Security Issues
|
||||
url: https://hackerone.com/bitwarden
|
||||
about: We use HackerOne to manage security disclosures.
|
||||
111
.github/ISSUE_TEMPLATE/issue.yml
vendored
Normal file
111
.github/ISSUE_TEMPLATE/issue.yml
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
name: Directory Connector Bug Report
|
||||
description: File a bug report
|
||||
title: "[DC] "
|
||||
labels: ["bug"]
|
||||
type: bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
|
||||
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: How can we reproduce the behavior.
|
||||
value: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. Click on '...'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Result
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: Actual Result
|
||||
description: A clear and concise description of what is happening.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots or Videos
|
||||
description: If applicable, add screenshots and/or a short video to help explain your problem.
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any other context about the problem here.
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: What operating system(s) are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
- Linux
|
||||
- Other operating system (please specify in "Additional Context" section)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os-version
|
||||
attributes:
|
||||
label: Operating System Version
|
||||
description: What version of the operating system(s) are you seeing the problem on?
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: directories
|
||||
attributes:
|
||||
label: Directory Service
|
||||
description: What directory service(s) are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- LDAP - Active Directory
|
||||
- Another LDAP implementation (please specify in "Additional Context" section)
|
||||
- Microsoft Entra ID
|
||||
- Google Workspace
|
||||
- Okta Universal Directory
|
||||
- OneLogin
|
||||
- Other directory service (please specify in "Additional Context" section)
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: application-type
|
||||
attributes:
|
||||
label: Application Type
|
||||
description: Which Directory Connector application(s) are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- GUI (the desktop application)
|
||||
- CLI (the bwdc command line application)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Build Version
|
||||
description: What version of our software are you running?
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: issue-tracking-info
|
||||
attributes:
|
||||
label: Issue Tracking Info
|
||||
description: |
|
||||
Make sure to acknowledge the following before submitting your report!
|
||||
options:
|
||||
- label: I understand that work is tracked outside of Github. A PR will be linked to this issue should one be opened to address it, but Bitwarden doesn't use fields like "assigned", "milestone", or "project" to track progress.
|
||||
required: true
|
||||
34
.github/PULL_REQUEST_TEMPLATE.md
vendored
34
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,33 +1,11 @@
|
||||
## Type of change
|
||||
## 🎟️ Tracking
|
||||
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature development
|
||||
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
||||
- [ ] Build/deploy pipeline (DevOps)
|
||||
- [ ] Other
|
||||
<!-- Paste the link to the Jira or GitHub issue or otherwise describe / point to where this change is coming from. -->
|
||||
|
||||
## Objective
|
||||
## 📔 Objective
|
||||
|
||||
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
|
||||
<!-- Describe what the purpose of this PR is, for example what bug you're fixing or new feature you're adding. -->
|
||||
|
||||
## Code changes
|
||||
## 📸 Screenshots
|
||||
|
||||
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
|
||||
<!--Also refer to any related changes or PRs in other repositories-->
|
||||
|
||||
- **file.ext:** Description of what was changed and why
|
||||
|
||||
## Screenshots
|
||||
|
||||
<!--Required for any UI changes. Delete if not applicable-->
|
||||
|
||||
## Testing requirements
|
||||
|
||||
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
||||
|
||||
## Before you submit
|
||||
|
||||
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
|
||||
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
|
||||
- [ ] This change requires a **documentation update** (notify the documentation team)
|
||||
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
||||
<!-- Required for any UI changes; delete if not applicable. Use fixed width images for better display. -->
|
||||
|
||||
24
.github/renovate.json5
vendored
Normal file
24
.github/renovate.json5
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
||||
extends: ["github>bitwarden/renovate-config"],
|
||||
enabledManagers: ["github-actions", "npm"],
|
||||
packageRules: [
|
||||
{
|
||||
groupName: "gh minor",
|
||||
matchManagers: ["github-actions"],
|
||||
matchUpdateTypes: ["minor", "patch"],
|
||||
},
|
||||
],
|
||||
ignoreDeps: [
|
||||
// yao-pkg is used to create a single executable application bundle for the CLI.
|
||||
// It is a third party build of node which carries a high supply chain risk.
|
||||
// This must be manually vetted by our appsec team before upgrading.
|
||||
// It is excluded from renovate to avoid accidentally upgrading to a non-vetted version.
|
||||
"@yao-pkg/pkg",
|
||||
// googleapis uses ESM after 149.0.0 so we are not upgrading it until we have ESM support.
|
||||
// They release new versions every couple of weeks so ignoring it at the dependency dashboard
|
||||
// level is not sufficient.
|
||||
// FIXME: remove and upgrade when we have ESM support.
|
||||
"googleapis",
|
||||
],
|
||||
}
|
||||
BIN
.github/secrets/devid-app-cert.p12.gpg
vendored
BIN
.github/secrets/devid-app-cert.p12.gpg
vendored
Binary file not shown.
BIN
.github/secrets/devid-installer-cert.p12.gpg
vendored
BIN
.github/secrets/devid-installer-cert.p12.gpg
vendored
Binary file not shown.
BIN
.github/secrets/macdev-cert.p12.gpg
vendored
BIN
.github/secrets/macdev-cert.p12.gpg
vendored
Binary file not shown.
521
.github/workflows/build.yml
vendored
521
.github/workflows/build.yml
vendored
@@ -1,91 +1,83 @@
|
||||
---
|
||||
name: Build
|
||||
|
||||
on:
|
||||
pull_request: {}
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_master'
|
||||
paths-ignore:
|
||||
- '.github/workflows/**'
|
||||
branches:
|
||||
- "main"
|
||||
- "rc"
|
||||
- "hotfix-rc"
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cloc:
|
||||
name: CLOC
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
|
||||
- name: Set up CLOC
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt -y install cloc
|
||||
|
||||
- name: Print lines of code
|
||||
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
|
||||
|
||||
|
||||
setup:
|
||||
name: Setup
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
package_version: ${{ steps.retrieve-version.outputs.package_version }}
|
||||
node_version: ${{ steps.retrieve-node-version.outputs.node_version }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Get Package Version
|
||||
id: retrieve-version
|
||||
run: |
|
||||
PKG_VERSION=$(jq -r .version src/package.json)
|
||||
echo "::set-output name=package_version::$PKG_VERSION"
|
||||
PKG_VERSION=$(jq -r .version package.json)
|
||||
echo "package_version=$PKG_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Get Node Version
|
||||
id: retrieve-node-version
|
||||
run: |
|
||||
NODE_NVMRC=$(cat .nvmrc)
|
||||
NODE_VERSION=${NODE_NVMRC/v/''}
|
||||
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
linux-cli:
|
||||
name: Build Linux CLI
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: setup
|
||||
env:
|
||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||
_PKG_FETCH_NODE_VERSION: 16.13.0
|
||||
_PKG_FETCH_VERSION: 3.2
|
||||
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: '16'
|
||||
node-version: ${{ env._NODE_VERSION }}
|
||||
|
||||
- name: Update NPM
|
||||
run: |
|
||||
npm install -g node-gyp
|
||||
node-gyp install $(node -v)
|
||||
|
||||
- name: Get pkg-fetch
|
||||
run: |
|
||||
cd $HOME
|
||||
fetchedUrl="https://github.com/vercel/pkg-fetch/releases/download/v$_PKG_FETCH_VERSION/node-v$_PKG_FETCH_NODE_VERSION-linux-x64"
|
||||
|
||||
mkdir -p .pkg-cache/v$_PKG_FETCH_VERSION
|
||||
wget $fetchedUrl -O "./.pkg-cache/v$_PKG_FETCH_VERSION/fetched-v$_PKG_FETCH_NODE_VERSION-linux-x64"
|
||||
node-gyp install "$(node -v)"
|
||||
|
||||
- name: Keytar
|
||||
run: |
|
||||
keytarVersion=$(cat src/package.json | jq -r '.dependencies.keytar')
|
||||
keytarVersion=$(cat package.json | jq -r '.dependencies.keytar')
|
||||
keytarTar="keytar-v$keytarVersion-napi-v3-linux-x64.tar"
|
||||
|
||||
keytarTarGz="$keytarTar.gz"
|
||||
keytarUrl="https://github.com/atom/node-keytar/releases/download/v$keytarVersion/$keytarTarGz"
|
||||
|
||||
mkdir -p ./keytar/linux
|
||||
wget $keytarUrl -O ./keytar/linux/$keytarTarGz
|
||||
tar -xvf ./keytar/linux/$keytarTarGz -C ./keytar/linux
|
||||
wget "$keytarUrl" -O "./keytar/linux/$keytarTarGz"
|
||||
tar -xvf "./keytar/linux/$keytarTarGz" -C ./keytar/linux
|
||||
|
||||
- name: Install
|
||||
run: npm install
|
||||
@@ -94,21 +86,19 @@ jobs:
|
||||
run: npm run dist:cli:lin
|
||||
|
||||
- name: Zip
|
||||
run: zip -j ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip ./dist-cli/linux/bwdc ./keytar/linux/build/Release/keytar.node
|
||||
|
||||
- name: Create checksums
|
||||
run: sha256sum ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip | cut -d " " -f 1 > ./dist-cli/bwdc-linux-sha256-$_PACKAGE_VERSION.txt
|
||||
run: zip -j "dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip" "dist-cli/linux/bwdc" "keytar/linux/build/Release/keytar.node"
|
||||
|
||||
- name: Version Test
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt install libsecret-1-0 dbus-x11 gnome-keyring
|
||||
eval $(dbus-launch --sh-syntax)
|
||||
eval "$(dbus-launch --sh-syntax)"
|
||||
|
||||
eval $(echo -n "" | /usr/bin/gnome-keyring-daemon --login)
|
||||
eval $(/usr/bin/gnome-keyring-daemon --components=secrets --start)
|
||||
eval "$(echo -n "" | /usr/bin/gnome-keyring-daemon --login)"
|
||||
eval "$(/usr/bin/gnome-keyring-daemon --components=secrets --start)"
|
||||
|
||||
mkdir -p test/linux
|
||||
unzip ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip -d ./test/linux
|
||||
unzip "./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip" -d ./test/linux
|
||||
|
||||
testVersion=$(./test/linux/bwdc -v)
|
||||
|
||||
@@ -121,63 +111,51 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload Linux Zip to GitHub
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
|
||||
path: ./dist-cli/bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Linux checksum to GitHub
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
with:
|
||||
name: bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||
path: ./dist-cli/bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
macos-cli:
|
||||
name: Build Mac CLI
|
||||
runs-on: macos-11
|
||||
runs-on: macos-15-intel
|
||||
needs: setup
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||
_PKG_FETCH_NODE_VERSION: 16.13.0
|
||||
_PKG_FETCH_VERSION: 3.2
|
||||
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: '16'
|
||||
node-version: ${{ env._NODE_VERSION }}
|
||||
|
||||
- name: Update NPM
|
||||
run: |
|
||||
npm install -g node-gyp
|
||||
node-gyp install $(node -v)
|
||||
|
||||
- name: Get pkg-fetch
|
||||
run: |
|
||||
cd $HOME
|
||||
fetchedUrl="https://github.com/vercel/pkg-fetch/releases/download/v$_PKG_FETCH_VERSION/node-v$_PKG_FETCH_NODE_VERSION-macos-x64"
|
||||
|
||||
mkdir -p .pkg-cache/v$_PKG_FETCH_VERSION
|
||||
wget $fetchedUrl -O "./.pkg-cache/v$_PKG_FETCH_VERSION/fetched-v$_PKG_FETCH_NODE_VERSION-macos-x64"
|
||||
node-gyp install "$(node -v)"
|
||||
|
||||
- name: Keytar
|
||||
run: |
|
||||
keytarVersion=$(cat src/package.json | jq -r '.dependencies.keytar')
|
||||
keytarVersion=$(cat package.json | jq -r '.dependencies.keytar')
|
||||
keytarTar="keytar-v$keytarVersion-napi-v3-darwin-x64.tar"
|
||||
|
||||
keytarTarGz="$keytarTar.gz"
|
||||
keytarUrl="https://github.com/atom/node-keytar/releases/download/v$keytarVersion/$keytarTarGz"
|
||||
|
||||
mkdir -p ./keytar/macos
|
||||
wget $keytarUrl -O ./keytar/macos/$keytarTarGz
|
||||
tar -xvf ./keytar/macos/$keytarTarGz -C ./keytar/macos
|
||||
wget "$keytarUrl" -O "./keytar/macos/$keytarTarGz"
|
||||
tar -xvf "./keytar/macos/$keytarTarGz" -C ./keytar/macos
|
||||
|
||||
- name: Install
|
||||
run: npm install
|
||||
@@ -186,15 +164,12 @@ jobs:
|
||||
run: npm run dist:cli:mac
|
||||
|
||||
- name: Zip
|
||||
run: zip -j ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip ./dist-cli/macos/bwdc ./keytar/macos/build/Release/keytar.node
|
||||
|
||||
- name: Create checksums
|
||||
run: sha256sum ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip | cut -d " " -f 1 > ./dist-cli/bwdc-macos-sha256-$_PACKAGE_VERSION.txt
|
||||
run: zip -j "dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip" "dist-cli/macos/bwdc" "keytar/macos/build/Release/keytar.node"
|
||||
|
||||
- name: Version Test
|
||||
run: |
|
||||
mkdir -p test/macos
|
||||
unzip ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip -d ./test/macos
|
||||
unzip "./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip" -d ./test/macos
|
||||
|
||||
testVersion=$(./test/macos/bwdc -v)
|
||||
|
||||
@@ -207,64 +182,48 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload Mac Zip to GitHub
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
|
||||
path: ./dist-cli/bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Mac checksum to GitHub
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
with:
|
||||
name: bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||
path: ./dist-cli/bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
windows-cli:
|
||||
name: Build Windows CLI
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
needs: setup
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||
_WIN_PKG_FETCH_VERSION: 16.13.0
|
||||
_WIN_PKG_VERSION: 3.2
|
||||
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Windows builder
|
||||
run: |
|
||||
choco install checksum --no-progress
|
||||
choco install reshack --no-progress
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: '16'
|
||||
node-version: ${{ env._NODE_VERSION }}
|
||||
|
||||
- name: Update NPM
|
||||
run: |
|
||||
npm install -g node-gyp
|
||||
node-gyp install $(node -v)
|
||||
|
||||
- name: Get pkg-fetch
|
||||
shell: pwsh
|
||||
run: |
|
||||
cd $HOME
|
||||
$fetchedUrl = "https://github.com/vercel/pkg-fetch/releases/download/v$env:_WIN_PKG_VERSION/node-v$env:_WIN_PKG_FETCH_VERSION-win-x64"
|
||||
|
||||
New-Item -ItemType directory -Path ./.pkg-cache
|
||||
New-Item -ItemType directory -Path ./.pkg-cache/v$env:_WIN_PKG_VERSION
|
||||
Invoke-RestMethod -Uri $fetchedUrl `
|
||||
-OutFile "./.pkg-cache/v$env:_WIN_PKG_VERSION/fetched-v$env:_WIN_PKG_FETCH_VERSION-win-x64"
|
||||
|
||||
- name: Keytar
|
||||
shell: pwsh
|
||||
run: |
|
||||
$keytarVersion = (Get-Content -Raw -Path ./src/package.json | ConvertFrom-Json).dependencies.keytar
|
||||
$keytarVersion = (Get-Content -Raw -Path ./package.json | ConvertFrom-Json).dependencies.keytar
|
||||
$keytarTar = "keytar-v${keytarVersion}-napi-v3-{0}-x64.tar"
|
||||
$keytarTarGz = "${keytarTar}.gz"
|
||||
$keytarUrl = "https://github.com/atom/node-keytar/releases/download/v${keytarVersion}/${keytarTarGz}"
|
||||
@@ -277,54 +236,6 @@ jobs:
|
||||
|
||||
7z e "./keytar/windows/$($keytarTar -f "win32")" -o"./keytar/windows"
|
||||
|
||||
- name: Setup Version Info
|
||||
shell: pwsh
|
||||
run: |
|
||||
$major, $minor, $patch = $env:_PACKAGE_VERSION.split('.')
|
||||
|
||||
$versionInfo = @"
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION $major,$minor,$patch,0
|
||||
PRODUCTVERSION $major,$minor,$patch,0
|
||||
FILEOS 0x40004
|
||||
FILETYPE 0x1
|
||||
{
|
||||
BLOCK "StringFileInfo"
|
||||
{
|
||||
BLOCK "040904b0"
|
||||
{
|
||||
VALUE "CompanyName", "Bitwarden Inc."
|
||||
VALUE "ProductName", "Bitwarden"
|
||||
VALUE "FileDescription", "Bitwarden Directory Connector CLI"
|
||||
VALUE "FileVersion", "$env:_PACKAGE_VERSION"
|
||||
VALUE "ProductVersion", "$env:_PACKAGE_VERSION"
|
||||
VALUE "OriginalFilename", "bwdc.exe"
|
||||
VALUE "InternalName", "bwdc"
|
||||
VALUE "LegalCopyright", "Copyright Bitwarden Inc."
|
||||
}
|
||||
}
|
||||
|
||||
BLOCK "VarFileInfo"
|
||||
{
|
||||
VALUE "Translation", 0x0409 0x04B0
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
$versionInfo | Out-File ./version-info.rc
|
||||
|
||||
- name: Resource Hacker
|
||||
shell: cmd
|
||||
run: |
|
||||
set PATH=%PATH%;C:\Program Files (x86)\Resource Hacker
|
||||
set WIN_PKG=C:\Users\runneradmin\.pkg-cache\v%_WIN_PKG_VERSION%\fetched-v%_WIN_PKG_FETCH_VERSION%-win-x64
|
||||
set WIN_PKG_BUILT=C:\Users\runneradmin\.pkg-cache\v%_WIN_PKG_VERSION%\built-v%_WIN_PKG_FETCH_VERSION%-win-x64
|
||||
|
||||
ResourceHacker -open %WIN_PKG% -save %WIN_PKG% -action delete -mask ICONGROUP,1,
|
||||
ResourceHacker -open version-info.rc -save version-info.res -action compile
|
||||
ResourceHacker -open %WIN_PKG% -save %WIN_PKG% -action addoverwrite -resource version-info.res
|
||||
|
||||
- name: Install
|
||||
run: npm install
|
||||
|
||||
@@ -333,117 +244,122 @@ jobs:
|
||||
|
||||
- name: Zip
|
||||
shell: cmd
|
||||
run: 7z a ./dist-cli/bwdc-windows-%_PACKAGE_VERSION%.zip ./dist-cli/windows/bwdc.exe ./keytar/windows/keytar.node
|
||||
run: 7z a .\dist-cli\bwdc-windows-%_PACKAGE_VERSION%.zip .\dist-cli\windows\bwdc.exe .\keytar\windows\keytar.node
|
||||
|
||||
- name: Version Test
|
||||
shell: pwsh
|
||||
run: |
|
||||
Expand-Archive -Path "./dist-cli/bwdc-windows-${env:_PACKAGE_VERSION}.zip" -DestinationPath "./test/windows"
|
||||
$testVersion = Invoke-Expression '& ./test/windows/bwdc.exe -v'
|
||||
echo "version: $env:_PACKAGE_VERSION"
|
||||
Expand-Archive -Path "dist-cli\bwdc-windows-$env:_PACKAGE_VERSION.zip" -DestinationPath "test\windows"
|
||||
$testVersion = Invoke-Expression '& .\test\windows\bwdc.exe -v'
|
||||
echo "version: ${env:_PACKAGE_VERSION}"
|
||||
echo "testVersion: $testVersion"
|
||||
if($testVersion -ne $env:_PACKAGE_VERSION) {
|
||||
if ($testVersion -ne ${env:_PACKAGE_VERSION}) {
|
||||
Throw "Version test failed."
|
||||
}
|
||||
|
||||
- name: Create checksums
|
||||
run: |
|
||||
checksum -f="./dist-cli/bwdc-windows-${env:_PACKAGE_VERSION}.zip" `
|
||||
-t sha256 | Out-File ./dist-cli/bwdc-windows-sha256-${env:_PACKAGE_VERSION}.txt
|
||||
|
||||
- name: Upload Windows Zip to GitHub
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
|
||||
path: ./dist-cli/bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Windows checksum to GitHub
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
with:
|
||||
name: bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||
path: ./dist-cli/bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
windows-gui:
|
||||
name: Build Windows GUI
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
needs: setup
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=4096
|
||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
|
||||
HUSKY: 0
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@9211491ffb35dd6a6657ca4f45d43dfe6e97c829
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
dotnet-version: "3.1.x"
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: '16'
|
||||
node-version: ${{ env._NODE_VERSION }}
|
||||
|
||||
- name: Update NPM
|
||||
run: |
|
||||
npm install -g node-gyp
|
||||
node-gyp install $(node -v)
|
||||
|
||||
- name: Set Node options
|
||||
run: echo "NODE_OPTIONS=--max_old_space_size=4096" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
shell: pwsh
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
dotnet --version
|
||||
|
||||
- name: Install AST
|
||||
uses: bitwarden/gh-actions/install-ast@f135c42c8596cb535c5bcb7523c0b2eef89709ac
|
||||
run: dotnet tool install --global AzureSignTool --version 4.0.1
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: npm install
|
||||
|
||||
# - name: Run linter
|
||||
# run: npm run lint
|
||||
- name: Log in to Azure
|
||||
uses: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "code-signing-vault-url,
|
||||
code-signing-client-id,
|
||||
code-signing-tenant-id,
|
||||
code-signing-client-secret,
|
||||
code-signing-cert-name"
|
||||
|
||||
- name: Log out from Azure
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- name: Build & Sign
|
||||
run: npm run dist:win
|
||||
env:
|
||||
ELECTRON_BUILDER_SIGN: 1
|
||||
SIGNING_VAULT_URL: ${{ secrets.SIGNING_VAULT_URL }}
|
||||
SIGNING_CLIENT_ID: ${{ secrets.SIGNING_CLIENT_ID }}
|
||||
SIGNING_TENANT_ID: ${{ secrets.SIGNING_TENANT_ID }}
|
||||
SIGNING_CLIENT_SECRET: ${{ secrets.SIGNING_CLIENT_SECRET }}
|
||||
SIGNING_CERT_NAME: ${{ secrets.SIGNING_CERT_NAME }}
|
||||
SIGNING_VAULT_URL: ${{ steps.retrieve-secrets.outputs.code-signing-vault-url }}
|
||||
SIGNING_CLIENT_ID: ${{ steps.retrieve-secrets.outputs.code-signing-client-id }}
|
||||
SIGNING_TENANT_ID: ${{ steps.retrieve-secrets.outputs.code-signing-tenant-id }}
|
||||
SIGNING_CLIENT_SECRET: ${{ steps.retrieve-secrets.outputs.code-signing-client-secret }}
|
||||
SIGNING_CERT_NAME: ${{ steps.retrieve-secrets.outputs.code-signing-cert-name }}
|
||||
|
||||
- name: Upload Portable Executable to GitHub
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
|
||||
path: ./dist/Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Installer Executable to GitHub
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
|
||||
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Installer Executable Blockmap to GitHub
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
|
||||
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload latest auto-update artifact
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: latest.yml
|
||||
path: ./dist/latest.yml
|
||||
@@ -452,28 +368,32 @@ jobs:
|
||||
|
||||
linux-gui:
|
||||
name: Build Linux GUI
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: setup
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=4096
|
||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
|
||||
HUSKY: 0
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: '16'
|
||||
node-version: ${{ env._NODE_VERSION }}
|
||||
|
||||
- name: Update NPM
|
||||
run: |
|
||||
npm install -g node-gyp
|
||||
node-gyp install $(node -v)
|
||||
|
||||
- name: Set Node options
|
||||
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
|
||||
node-gyp install "$(node -v)"
|
||||
|
||||
- name: Set up environment
|
||||
run: |
|
||||
@@ -491,14 +411,14 @@ jobs:
|
||||
run: npm run dist:lin
|
||||
|
||||
- name: Upload AppImage
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
|
||||
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload latest auto-update artifact
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: latest-linux.yml
|
||||
path: ./dist/latest-linux.yml
|
||||
@@ -507,28 +427,33 @@ jobs:
|
||||
|
||||
macos-gui:
|
||||
name: Build MacOS GUI
|
||||
runs-on: macos-11
|
||||
runs-on: macos-15-intel
|
||||
needs: setup
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=4096
|
||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
|
||||
HUSKY: 0
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: '16'
|
||||
node-version: ${{ env._NODE_VERSION }}
|
||||
|
||||
- name: Update NPM
|
||||
run: |
|
||||
npm install -g node-gyp
|
||||
node-gyp install $(node -v)
|
||||
|
||||
- name: Set Node options
|
||||
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
|
||||
node-gyp install "$(node -v)"
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
@@ -536,50 +461,61 @@ jobs:
|
||||
npm --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
shell: bash
|
||||
|
||||
- name: Decrypt secrets
|
||||
env:
|
||||
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
||||
shell: bash
|
||||
- name: Log in to Azure
|
||||
uses: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Get Azure Key Vault secrets
|
||||
id: get-kv-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: gh-directory-connector
|
||||
secrets: "KEYCHAIN-PASSWORD,APP-STORE-CONNECT-AUTH-KEY,APP-STORE-CONNECT-TEAM-ISSUER"
|
||||
|
||||
- name: Get certificates
|
||||
run: |
|
||||
mkdir -p $HOME/secrets
|
||||
mkdir -p "$HOME/certificates"
|
||||
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||
--output "$HOME/secrets/devid-app-cert.p12" \
|
||||
"$GITHUB_WORKSPACE/.github/secrets/devid-app-cert.p12.gpg"
|
||||
az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-app-cert |
|
||||
jq -r .value | base64 -d > "$HOME/certificates/devid-app-cert.p12"
|
||||
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||
--output "$HOME/secrets/devid-installer-cert.p12" \
|
||||
"$GITHUB_WORKSPACE/.github/secrets/devid-installer-cert.p12.gpg"
|
||||
az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-installer-cert |
|
||||
jq -r .value | base64 -d > "$HOME/certificates/devid-installer-cert.p12"
|
||||
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||
--output "$HOME/secrets/macdev-cert.p12" \
|
||||
"$GITHUB_WORKSPACE/.github/secrets/macdev-cert.p12.gpg"
|
||||
az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/macdev-cert |
|
||||
jq -r .value | base64 -d > "$HOME/certificates/macdev-cert.p12"
|
||||
|
||||
- name: Log out from Azure
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- name: Set up keychain
|
||||
env:
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
DEVID_CERT_PASSWORD: ${{ secrets.DEVID_CERT_PASSWORD }}
|
||||
MACDEV_CERT_PASSWORD: ${{ secrets.MACDEV_CERT_PASSWORD }}
|
||||
shell: bash
|
||||
KEYCHAIN_PASSWORD: ${{ steps.get-kv-secrets.outputs.KEYCHAIN-PASSWORD }}
|
||||
run: |
|
||||
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security set-keychain-settings -lut 1200 build.keychain
|
||||
security import "$HOME/secrets/devid-app-cert.p12" -k build.keychain -P $DEVID_CERT_PASSWORD \
|
||||
|
||||
security import "$HOME/certificates/devid-app-cert.p12" -k build.keychain -P "" \
|
||||
-T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
||||
security import "$HOME/secrets/devid-installer-cert.p12" -k build.keychain -P $DEVID_CERT_PASSWORD \
|
||||
|
||||
security import "$HOME/certificates/devid-installer-cert.p12" -k build.keychain -P "" \
|
||||
-T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
||||
security import "$HOME/secrets/macdev-cert.p12" -k build.keychain -P $MACDEV_CERT_PASSWORD \
|
||||
|
||||
security import "$HOME/certificates/macdev-cert.p12" -k build.keychain -P "" \
|
||||
-T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
|
||||
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||
|
||||
- name: Load package version
|
||||
run: |
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
$packageVersion = (Get-Content -Raw -Path $rootPath\src\package.json | ConvertFrom-Json).version;
|
||||
$packageVersion = (Get-Content -Raw -Path "$rootPath\package.json" | ConvertFrom-Json).version;
|
||||
|
||||
Write-Output "Setting package version to $packageVersion";
|
||||
Write-Output "PACKAGE_VERSION=$packageVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append;
|
||||
@@ -588,44 +524,46 @@ jobs:
|
||||
- name: Install Node dependencies
|
||||
run: npm install
|
||||
|
||||
# - name: Run linter
|
||||
# run: npm run lint
|
||||
- name: Set up private auth key
|
||||
env:
|
||||
_APP_STORE_CONNECT_AUTH_KEY: ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-AUTH-KEY }}
|
||||
run: |
|
||||
mkdir ~/private_keys
|
||||
cat << EOF > ~/private_keys/AuthKey_UFD296548T.p8
|
||||
${_APP_STORE_CONNECT_AUTH_KEY}
|
||||
EOF
|
||||
|
||||
- name: Build application
|
||||
run: npm run dist:mac
|
||||
env:
|
||||
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
|
||||
- name: Rename Zip Artifact
|
||||
run: |
|
||||
cd dist
|
||||
mv "Bitwarden Directory Connector-${{ env._PACKAGE_VERSION }}-mac.zip" \
|
||||
"Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip"
|
||||
APP_STORE_CONNECT_TEAM_ISSUER: ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-TEAM-ISSUER }}
|
||||
APP_STORE_CONNECT_AUTH_KEY: UFD296548T
|
||||
APP_STORE_CONNECT_AUTH_KEY_PATH: ~/private_keys/AuthKey_UFD296548T.p8
|
||||
CSC_FOR_PULL_REQUEST: true
|
||||
|
||||
- name: Upload .zip artifact
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
|
||||
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .dmg artifact
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
|
||||
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .dmg Blockmap artifact
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
|
||||
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload latest auto-update artifact
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: latest-mac.yml
|
||||
path: ./dist/latest-mac.yml
|
||||
@@ -634,9 +572,8 @@ jobs:
|
||||
|
||||
check-failures:
|
||||
name: Check for failures
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- cloc
|
||||
- setup
|
||||
- linux-cli
|
||||
- macos-cli
|
||||
@@ -644,60 +581,38 @@ jobs:
|
||||
- windows-gui
|
||||
- linux-gui
|
||||
- macos-gui
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Check if any job failed
|
||||
if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') }}
|
||||
env:
|
||||
CLOC_STATUS: ${{ needs.cloc.result }}
|
||||
SETUP_STATUS: ${{ needs.setup.result }}
|
||||
LINUX_CLI_STATUS: ${{ needs.linux-cli.result }}
|
||||
MACOS_CLI_STATUS: ${{ needs.macos-cli.result }}
|
||||
WINDOWS_CLI_STATUS: ${{ needs.windows-cli.result }}
|
||||
WINDOWS_GUI_STATUS: ${{ needs.windows-gui.result }}
|
||||
LINUX_GUI_STATUS: ${{ needs.linux-gui.result }}
|
||||
MACOS_GUI_STATUS: ${{ needs.macos-gui.result }}
|
||||
run: |
|
||||
if [ "$CLOC_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$SETUP_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$LINUX_CLI_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$MACOS_CLI_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$WINDOWS_CLI_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$WINDOWS_GUI_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$LINUX_GUI_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$MACOS_GUI_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
fi
|
||||
if: |
|
||||
(github.ref == 'refs/heads/main'
|
||||
|| github.ref == 'refs/heads/rc'
|
||||
|| github.ref == 'refs/heads/hotfix-rc')
|
||||
&& contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
|
||||
- name: Login to Azure - Prod Subscription
|
||||
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||
- name: Log in to Azure
|
||||
if: failure()
|
||||
uses: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
if: failure()
|
||||
env:
|
||||
KEYVAULT: bitwarden-prod-kv
|
||||
SECRETS: |
|
||||
devops-alerts-slack-webhook-url
|
||||
run: |
|
||||
for i in ${SECRETS//,/ }
|
||||
do
|
||||
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
|
||||
echo "::add-mask::$VALUE"
|
||||
echo "::set-output name=$i::$VALUE"
|
||||
done
|
||||
with:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "devops-alerts-slack-webhook-url"
|
||||
|
||||
- name: Log out from Azure
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- name: Notify Slack on failure
|
||||
uses: act10ns/slack@87c73aef9f8838eb6feae81589a6b1487a4a9e08
|
||||
uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
|
||||
if: failure()
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
|
||||
|
||||
14
.github/workflows/enforce-labels.yml
vendored
14
.github/workflows/enforce-labels.yml
vendored
@@ -1,18 +1,18 @@
|
||||
---
|
||||
name: Enforce PR labels
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, unlabeled, opened, edited, synchronize]
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
jobs:
|
||||
enforce-label:
|
||||
name: EnforceLabel
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Enforce Label
|
||||
uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024
|
||||
uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # 2.2.2
|
||||
with:
|
||||
BANNED_LABELS:
|
||||
description: "hold"
|
||||
BANNED_LABELS_DESCRIPTION:
|
||||
description: "PRs on hold cannot be merged"
|
||||
BANNED_LABELS: "hold"
|
||||
BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged"
|
||||
|
||||
148
.github/workflows/integration-test.yml
vendored
Normal file
148
.github/workflows/integration-test.yml
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
name: Integration Testing
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Integration tests are slow, so only run them if relevant files have changed.
|
||||
# This is done at the workflow level and at the job level.
|
||||
# Make sure these triggers stay consistent with the 'changed-files' job.
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'rc'
|
||||
paths:
|
||||
- ".github/workflows/integration-test.yml" # this file
|
||||
- "docker-compose.yml" # any change to Docker configuration
|
||||
- "package.json" # dependencies
|
||||
- "utils/**" # any change to test fixtures
|
||||
- "src/services/sync.service.ts" # core sync service used by all directory services
|
||||
- "src/services/directory-services/ldap-directory.service*" # LDAP directory service
|
||||
- "src/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
|
||||
# Add directory services here as we add test coverage
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/integration-test.yml" # this file
|
||||
- "docker-compose.yml" # any change to Docker configuration
|
||||
- "package.json" # dependencies
|
||||
- "utils/**" # any change to test fixtures
|
||||
- "src/services/sync.service.ts" # core sync service used by all directory services
|
||||
- "src/services/directory-services/ldap-directory.service*" # LDAP directory service
|
||||
- "src/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
|
||||
# Add directory services here as we add test coverage
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write # required by dorny/test-reporter to upload its results
|
||||
id-token: write # required to use OIDC to login to Azure Key Vault
|
||||
jobs:
|
||||
testing:
|
||||
name: Run tests
|
||||
if: ${{ startsWith(github.head_ref, 'version_bump_') == false }}
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Get Node version
|
||||
id: retrieve-node-version
|
||||
run: |
|
||||
NODE_NVMRC=$(cat .nvmrc)
|
||||
NODE_VERSION=${NODE_NVMRC/v/''}
|
||||
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: ${{ steps.retrieve-node-version.outputs.node_version }}
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: npm ci
|
||||
|
||||
# Get secrets from Azure Key Vault
|
||||
- name: Azure Login
|
||||
uses: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Get KV Secrets
|
||||
id: get-kv-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: gh-directory-connector
|
||||
secrets: "GOOGLE-ADMIN-USER,GOOGLE-CLIENT-EMAIL,GOOGLE-DOMAIN,GOOGLE-PRIVATE-KEY"
|
||||
|
||||
- name: Azure Logout
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
# Only run relevant tests depending on what files have changed.
|
||||
# This should be kept consistent with the workflow level triggers.
|
||||
# Note: docker-compose.yml is only used for ldap for now
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
with:
|
||||
list-files: shell
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Add directory services here as we add test coverage
|
||||
filters: |
|
||||
common:
|
||||
- '.github/workflows/integration-test.yml'
|
||||
- 'utils/**'
|
||||
- 'package.json'
|
||||
- 'src/services/sync.service.ts'
|
||||
ldap:
|
||||
- 'docker-compose.yml'
|
||||
- 'src/services/directory-services/ldap-directory.service*'
|
||||
google:
|
||||
- 'src/services/directory-services/gsuite-directory.service*'
|
||||
|
||||
# LDAP
|
||||
- name: Setup LDAP integration tests
|
||||
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.ldap == 'true'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install mkcert
|
||||
npm run test:integration:setup
|
||||
|
||||
- name: Run LDAP integration tests
|
||||
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.ldap == 'true'
|
||||
env:
|
||||
JEST_JUNIT_UNIQUE_OUTPUT_NAME: "true" # avoids junit outputs from clashing
|
||||
run: npx jest ldap-directory.service.integration.spec.ts --coverage --coverageDirectory=coverage-ldap
|
||||
|
||||
# Google Workspace
|
||||
- name: Run Google Workspace integration tests
|
||||
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.google == 'true'
|
||||
env:
|
||||
GOOGLE_DOMAIN: ${{ steps.get-kv-secrets.outputs.GOOGLE-DOMAIN }}
|
||||
GOOGLE_ADMIN_USER: ${{ steps.get-kv-secrets.outputs.GOOGLE-ADMIN-USER }}
|
||||
GOOGLE_CLIENT_EMAIL: ${{ steps.get-kv-secrets.outputs.GOOGLE-CLIENT-EMAIL }}
|
||||
GOOGLE_PRIVATE_KEY: ${{ steps.get-kv-secrets.outputs.GOOGLE-PRIVATE-KEY }}
|
||||
JEST_JUNIT_UNIQUE_OUTPUT_NAME: "true" # avoids junit outputs from clashing
|
||||
run: |
|
||||
npx jest gsuite-directory.service.integration.spec.ts --coverage --coverageDirectory=coverage-google
|
||||
|
||||
- name: Report test results
|
||||
id: report
|
||||
uses: dorny/test-reporter@b082adf0eced0765477756c2a610396589b8c637 # v2.5.0
|
||||
# This will skip the job if it's a pull request from a fork, because that won't have permission to upload test results.
|
||||
# PRs from the repository and all other events are OK.
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository) && !cancelled()
|
||||
with:
|
||||
name: Test Results
|
||||
path: "junit.xml*"
|
||||
reporter: jest-junit
|
||||
fail-on-error: true
|
||||
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
|
||||
- name: Upload results to codecov.io
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
with:
|
||||
report_type: test_results
|
||||
112
.github/workflows/release.yml
vendored
112
.github/workflows/release.yml
vendored
@@ -1,4 +1,3 @@
|
||||
---
|
||||
name: Release
|
||||
|
||||
on:
|
||||
@@ -14,13 +13,25 @@ on:
|
||||
- Redeploy
|
||||
- Dry Run
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
name: Setup
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
release_version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Branch check
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
if: ${{ inputs.release_type != 'Dry Run' }}
|
||||
run: |
|
||||
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
|
||||
echo "==================================="
|
||||
@@ -29,75 +40,48 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||
|
||||
- name: Retrieve Directory Connector release version
|
||||
id: retrieve-version
|
||||
run: |
|
||||
PKG_VERSION=$(jq -r .version src/package.json)
|
||||
echo "::set-output name=package_version::$PKG_VERSION"
|
||||
|
||||
- name: Check to make sure Mobile release version has been bumped
|
||||
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
latest_ver=$(hub release -L 1 -f '%T')
|
||||
latest_ver=${latest_ver:1}
|
||||
echo "Latest version: $latest_ver"
|
||||
ver=${{ steps.retrieve-version.outputs.package_version }}
|
||||
echo "Version: $ver"
|
||||
if [ "$latest_ver" = "$ver" ]; then
|
||||
echo "Version has not been bumped!"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Get branch name
|
||||
id: branch
|
||||
run: |
|
||||
BRANCH_NAME=$(basename ${{ github.ref }})
|
||||
echo "::set-output name=branch-name::$BRANCH_NAME"
|
||||
|
||||
- name: Create GitHub deployment
|
||||
uses: chrnorm/deployment-action@1b599fe41a0ef1f95191e7f2eec4743f2d7dfc48
|
||||
id: deployment
|
||||
- name: Check Release Version
|
||||
id: version
|
||||
uses: bitwarden/gh-actions/release-version-check@main
|
||||
with:
|
||||
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
initial-status: 'in_progress'
|
||||
environment: 'production'
|
||||
description: 'Deployment ${{ steps.retrieve-version.outputs.package_version }} from branch ${{ steps.branch.outputs.branch-name }}'
|
||||
task: release
|
||||
|
||||
release-type: ${{ inputs.release_type }}
|
||||
project-type: ts
|
||||
file: package.json
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-24.04
|
||||
needs: setup
|
||||
permissions:
|
||||
actions: read
|
||||
packages: read
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
uses: bitwarden/gh-actions/download-artifacts@c850faad0cf6c02a8c0dc46eddde2363fbd6c373a
|
||||
if: ${{ inputs.release_type != 'Dry Run' }}
|
||||
uses: bitwarden/gh-actions/download-artifacts@main
|
||||
with:
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
branch: ${{ steps.branch.outputs.branch-name }}
|
||||
branch: ${{ github.ref_name }}
|
||||
|
||||
- name: Download all artifacts
|
||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||
uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
|
||||
- name: Dry Run - Download all artifacts
|
||||
if: ${{ inputs.release_type == 'Dry Run' }}
|
||||
uses: bitwarden/gh-actions/download-artifacts@main
|
||||
with:
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
branch: master
|
||||
branch: main
|
||||
|
||||
- name: Create release
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0
|
||||
if: ${{ inputs.release_type != 'Dry Run' }}
|
||||
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
|
||||
env:
|
||||
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
|
||||
PKG_VERSION: ${{ needs.setup.outputs.release_version }}
|
||||
with:
|
||||
artifacts: "./bwdc-windows-${{ env.PKG_VERSION }}.zip,
|
||||
./bwdc-macos-${{ env.PKG_VERSION }}.zip,
|
||||
./bwdc-linux-${{ env.PKG_VERSION }}.zip,
|
||||
./bwdc-windows-sha256-${{ env.PKG_VERSION }}.txt,
|
||||
./bwdc-macos-sha256-${{ env.PKG_VERSION }}.txt,
|
||||
./bwdc-linux-sha256-${{ env.PKG_VERSION }}.txt,
|
||||
./Bitwarden-Connector-Portable-${{ env.PKG_VERSION }}.exe,
|
||||
./Bitwarden-Connector-Installer-${{ env.PKG_VERSION }}.exe,
|
||||
./Bitwarden-Connector-Installer-${{ env.PKG_VERSION }}.exe.blockmap,
|
||||
@@ -114,19 +98,3 @@ jobs:
|
||||
body: "<insert release notes here>"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
draft: true
|
||||
|
||||
- name: Update deployment status to Success
|
||||
if: ${{ success() }}
|
||||
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
|
||||
with:
|
||||
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
state: 'success'
|
||||
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
|
||||
|
||||
- name: Update deployment status to Failure
|
||||
if: ${{ failure() }}
|
||||
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
|
||||
with:
|
||||
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
state: 'failure'
|
||||
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
|
||||
|
||||
28
.github/workflows/respond.yml
vendored
Normal file
28
.github/workflows/respond.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Respond
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
respond:
|
||||
name: Respond
|
||||
uses: bitwarden/gh-actions/.github/workflows/_respond.yml@main
|
||||
secrets:
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
id-token: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
21
.github/workflows/review-code.yml
vendored
Normal file
21
.github/workflows/review-code.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
review:
|
||||
name: Review
|
||||
uses: bitwarden/gh-actions/.github/workflows/_review-code.yml@main
|
||||
secrets:
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
id-token: write
|
||||
pull-requests: write
|
||||
52
.github/workflows/scan.yml
vendored
Normal file
52
.github/workflows/scan.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Scan
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches-ignore:
|
||||
- "main"
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-run:
|
||||
name: Check PR run
|
||||
uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
sast:
|
||||
name: Checkmarx
|
||||
uses: bitwarden/gh-actions/.github/workflows/_checkmarx.yml@main
|
||||
needs: check-run
|
||||
secrets:
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
security-events: write
|
||||
id-token: write
|
||||
|
||||
quality:
|
||||
name: Sonar
|
||||
uses: bitwarden/gh-actions/.github/workflows/_sonar.yml@main
|
||||
needs: check-run
|
||||
secrets:
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
72
.github/workflows/test.yml
vendored
Normal file
72
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: Testing
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "rc"
|
||||
- "hotfix-rc"
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write # required by dorny/test-reporter to upload its results
|
||||
|
||||
jobs:
|
||||
|
||||
testing:
|
||||
name: Run tests
|
||||
if: ${{ startsWith(github.head_ref, 'version_bump_') == false }}
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Get Node version
|
||||
id: retrieve-node-version
|
||||
run: |
|
||||
NODE_NVMRC=$(cat .nvmrc)
|
||||
NODE_VERSION=${NODE_NVMRC/v/''}
|
||||
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: ${{ steps.retrieve-node-version.outputs.node_version }}
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: npm ci
|
||||
|
||||
# We use isolatedModules: true which disables typechecking in tests
|
||||
# Tests in apps/ are typechecked when their app is built, so we just do it here for libs/
|
||||
# See https://bitwarden.atlassian.net/browse/EC-497
|
||||
- name: Run typechecking
|
||||
run: npm run test:types --coverage
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test --coverage
|
||||
|
||||
- name: Report test results
|
||||
uses: dorny/test-reporter@b082adf0eced0765477756c2a610396589b8c637 # v2.5.0
|
||||
# This will skip the job if it's a pull request from a fork, because that won't have permission to upload test results.
|
||||
# PRs from the repository and all other events are OK.
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository) && !cancelled()
|
||||
with:
|
||||
name: Test Results
|
||||
path: "junit.xml"
|
||||
reporter: jest-junit
|
||||
fail-on-error: true
|
||||
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
|
||||
- name: Upload results to codecov.io
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
with:
|
||||
report_type: test_results
|
||||
172
.github/workflows/version-bump.yml
vendored
172
.github/workflows/version-bump.yml
vendored
@@ -1,65 +1,145 @@
|
||||
---
|
||||
name: Version Bump
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_number:
|
||||
description: "New Version"
|
||||
required: true
|
||||
version_number_override:
|
||||
description: "New version override (leave blank for automatic calculation, example: '2024.1.0')"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
bump_version:
|
||||
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
|
||||
runs-on: ubuntu-20.04
|
||||
name: Bump Version
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Validate version input
|
||||
if: ${{ inputs.version_number_override != '' }}
|
||||
uses: bitwarden/gh-actions/version-check@main
|
||||
with:
|
||||
version: ${{ inputs.version_number_override }}
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Get Azure Key Vault secrets
|
||||
id: get-kv-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: gh-org-bitwarden
|
||||
secrets: "BW-GHAPP-ID,BW-GHAPP-KEY"
|
||||
|
||||
- name: Log out from Azure
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }}
|
||||
private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }}
|
||||
permission-contents: write
|
||||
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
|
||||
- name: Create Version Branch
|
||||
- name: Setup git
|
||||
run: |
|
||||
git switch -c version_bump_${{ github.event.inputs.version_number }}
|
||||
git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
|
||||
- name: Checkout Version Branch
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||
with:
|
||||
ref: version_bump_${{ github.event.inputs.version_number }}
|
||||
- name: Get current version
|
||||
id: current-version
|
||||
run: |
|
||||
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
||||
echo "version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Bump Version - Package
|
||||
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||
- name: Verify input version
|
||||
if: ${{ inputs.version_number_override != '' }}
|
||||
env:
|
||||
CURRENT_VERSION: ${{ steps.current-version.outputs.version }}
|
||||
NEW_VERSION: ${{ inputs.version_number_override }}
|
||||
run: |
|
||||
# Error if version has not changed.
|
||||
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
||||
echo "Version has not changed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if version is newer.
|
||||
if printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V; then
|
||||
echo "Version check successful."
|
||||
else
|
||||
echo "Version check failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Calculate next release version
|
||||
if: ${{ inputs.version_number_override == '' }}
|
||||
id: calculate-next-version
|
||||
uses: bitwarden/gh-actions/version-next@main
|
||||
with:
|
||||
version: ${{ github.event.inputs.version_number }}
|
||||
file_path: "./src/package.json"
|
||||
version: ${{ steps.current-version.outputs.version }}
|
||||
|
||||
- name: Bump Version - Package - Version Override
|
||||
if: ${{ inputs.version_number_override != '' }}
|
||||
id: bump-version-override
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
file_path: "./package.json"
|
||||
version: ${{ inputs.version_number_override }}
|
||||
|
||||
- name: Bump Version - Package - Automatic Calculation
|
||||
if: ${{ inputs.version_number_override == '' }}
|
||||
id: bump-version-automatic
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
file_path: "./package.json"
|
||||
version: ${{ steps.calculate-next-version.outputs.version }}
|
||||
|
||||
- name: Set final version output
|
||||
id: set-final-version-output
|
||||
env:
|
||||
_BUMP_VERSION_OVERRIDE_OUTCOME: ${{ steps.bump-version-override.outcome }}
|
||||
_INPUT_VERSION_NUMBER_OVERRIDE: ${{ inputs.version_number_override }}
|
||||
_BUMP_VERSION_AUTOMATIC_OUTCOME: ${{ steps.bump-version-automatic.outcome }}
|
||||
_CALCULATE_NEXT_VERSION: ${{ steps.calculate-next-version.outputs.version }}
|
||||
|
||||
run: |
|
||||
if [[ "$_BUMP_VERSION_OVERRIDE_OUTCOME" == "success" ]]; then
|
||||
echo "version=$_INPUT_VERSION_NUMBER_OVERRIDE" >> "$GITHUB_OUTPUT"
|
||||
elif [[ "$_BUMP_VERSION_AUTOMATIC_OUTCOME" == "success" ]]; then
|
||||
echo "version=$_CALCULATE_NEXT_VERSION" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Check if version changed
|
||||
id: version-changed
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "changes_to_commit=TRUE" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changes_to_commit=FALSE" >> "$GITHUB_OUTPUT"
|
||||
echo "No changes to commit!";
|
||||
fi
|
||||
|
||||
- name: Commit files
|
||||
run: |
|
||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
env:
|
||||
_VERSION: ${{ steps.set-final-version-output.outputs.version }}
|
||||
run: git commit -m "Bumped version to $_VERSION" -a
|
||||
|
||||
- name: Push changes
|
||||
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
||||
|
||||
- name: Create Version PR
|
||||
env:
|
||||
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
BASE_BRANCH: master
|
||||
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
|
||||
run: |
|
||||
gh pr create --title "$TITLE" \
|
||||
--base "$BASE" \
|
||||
--head "$PR_BRANCH" \
|
||||
--label "version update" \
|
||||
--label "automated pr" \
|
||||
--body "
|
||||
## Type of change
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature development
|
||||
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
||||
- [ ] Build/deploy pipeline (DevOps)
|
||||
- [X] Other
|
||||
|
||||
## Objective
|
||||
Automated version bump to ${{ github.event.inputs.version_number }}"
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
run: git push
|
||||
|
||||
12
.github/workflows/workflow-linter.yml
vendored
12
.github/workflows/workflow-linter.yml
vendored
@@ -1,12 +0,0 @@
|
||||
---
|
||||
name: Workflow Linter
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/**
|
||||
|
||||
jobs:
|
||||
call-workflow:
|
||||
name: Call Workflow
|
||||
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master
|
||||
51
.gitignore
vendored
51
.gitignore
vendored
@@ -1,17 +1,44 @@
|
||||
.vs
|
||||
.idea
|
||||
# General
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Environment variables used for tests
|
||||
.env
|
||||
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# Node
|
||||
node_modules
|
||||
npm-debug.log
|
||||
vwd.webinfo
|
||||
dist/
|
||||
dist-cli/
|
||||
css/
|
||||
|
||||
# Build directories
|
||||
dist
|
||||
build
|
||||
build-cli
|
||||
.angular/cache
|
||||
|
||||
# Testing
|
||||
coverage*
|
||||
junit.xml*
|
||||
|
||||
# Misc
|
||||
*.crx
|
||||
*.pem
|
||||
build-cli/
|
||||
build/
|
||||
yarn-error.log
|
||||
.DS_Store
|
||||
*.nupkg
|
||||
*.zip
|
||||
*.provisionprofile
|
||||
*.env
|
||||
.swp
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
|
||||
# Bitwarden Directory Connector
|
||||
|
||||
The Bitwarden Directory Connector is a a desktop application used to sync your Bitwarden enterprise organization to an existing directory of users and groups.
|
||||
The Bitwarden Directory Connector is a desktop application used to sync your Bitwarden enterprise organization to an existing directory of users and groups.
|
||||
|
||||
Supported directories:
|
||||
|
||||
- Active Directory
|
||||
- Any other LDAP-based directory
|
||||
- Azure Active Directory
|
||||
- Microsoft Entra ID
|
||||
- G Suite (Google)
|
||||
- Okta
|
||||
|
||||
@@ -48,7 +48,7 @@ We provide detailed documentation and examples for using the Directory Connector
|
||||
|
||||
**Requirements**
|
||||
|
||||
- [Node.js](https://nodejs.org) v16.13.1 (LTS)
|
||||
- [Node.js](https://nodejs.org) v18 (LTS)
|
||||
- Windows users: To compile the native node modules used in the app you will need the Visual C++ toolset, available through the standard Visual Studio installer (recommended) or by installing [`windows-build-tools`](https://github.com/felixrieseberg/windows-build-tools) through `npm`. See more at [Compiling native Addon modules](https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md#compiling-native-addon-modules).
|
||||
|
||||
**Run the app**
|
||||
|
||||
37
angular.json
Normal file
37
angular.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "apps",
|
||||
"cli": {
|
||||
"analytics": false
|
||||
},
|
||||
"projects": {
|
||||
"app": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:application": {
|
||||
"strict": true
|
||||
}
|
||||
},
|
||||
"root": ".",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"outputPath": {
|
||||
"base": "dist"
|
||||
},
|
||||
"index": "src/index.html",
|
||||
"tsConfig": "tsconfig.json",
|
||||
"assets": [],
|
||||
"styles": [],
|
||||
"scripts": [],
|
||||
"browser": "src/main.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
docker-compose.yml
Normal file
18
docker-compose.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
services:
|
||||
open-ldap:
|
||||
image: bitnamilegacy/openldap:latest
|
||||
hostname: openldap
|
||||
environment:
|
||||
- LDAP_ADMIN_USERNAME=admin
|
||||
- LDAP_ADMIN_PASSWORD=admin
|
||||
- LDAP_ROOT=dc=bitwarden,dc=com
|
||||
- LDAP_ENABLE_TLS=yes
|
||||
- LDAP_TLS_CERT_FILE=/certs/openldap.pem
|
||||
- LDAP_TLS_KEY_FILE=/certs/openldap-key.pem
|
||||
- LDAP_TLS_CA_FILE=/certs/rootCA.pem
|
||||
volumes:
|
||||
- "./utils/openldap/ldifs:/ldifs"
|
||||
- "./utils/openldap/certs:/certs"
|
||||
ports:
|
||||
- "1389:1389"
|
||||
- "1636:1636"
|
||||
@@ -4,7 +4,7 @@
|
||||
},
|
||||
"productName": "Bitwarden Directory Connector",
|
||||
"appId": "com.bitwarden.directory-connector",
|
||||
"copyright": "Copyright © 2015-2022 Bitwarden Inc.",
|
||||
"copyright": "Copyright © 2015-2026 Bitwarden Inc.",
|
||||
"directories": {
|
||||
"buildResources": "resources",
|
||||
"output": "dist",
|
||||
@@ -12,6 +12,7 @@
|
||||
},
|
||||
"afterSign": "scripts/notarize.js",
|
||||
"mac": {
|
||||
"artifactName": "Bitwarden-Connector-${version}-mac.${ext}",
|
||||
"category": "public.app-category.productivity",
|
||||
"gatekeeperAssess": false,
|
||||
"hardenedRuntime": true,
|
||||
|
||||
149
eslint.config.mjs
Normal file
149
eslint.config.mjs
Normal file
@@ -0,0 +1,149 @@
|
||||
// @ts-check
|
||||
import eslint from "@eslint/js";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import tsPlugin from "@typescript-eslint/eslint-plugin";
|
||||
import prettierConfig from "eslint-config-prettier";
|
||||
import importPlugin from "eslint-plugin-import";
|
||||
import rxjsX from "eslint-plugin-rxjs-x";
|
||||
import rxjsAngularX from "eslint-plugin-rxjs-angular-x";
|
||||
import angularEslint from "@angular-eslint/eslint-plugin-template";
|
||||
import angularParser from "@angular-eslint/template-parser";
|
||||
import globals from "globals";
|
||||
|
||||
export default [
|
||||
// Global ignores (replaces .eslintignore)
|
||||
{
|
||||
ignores: [
|
||||
"dist/**",
|
||||
"dist-cli/**",
|
||||
"build/**",
|
||||
"build-cli/**",
|
||||
"coverage/**",
|
||||
"**/*.cjs",
|
||||
"eslint.config.mjs",
|
||||
"scripts/**/*.js",
|
||||
"**/node_modules/**",
|
||||
],
|
||||
},
|
||||
|
||||
// Base config for all JavaScript/TypeScript files
|
||||
{
|
||||
files: ["**/*.ts", "**/*.js"],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
parser: tsParser,
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.eslint.json"],
|
||||
},
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": tsPlugin,
|
||||
import: importPlugin,
|
||||
"rxjs-x": rxjsX,
|
||||
"rxjs-angular-x": rxjsAngularX,
|
||||
},
|
||||
settings: {
|
||||
"import/parsers": {
|
||||
"@typescript-eslint/parser": [".ts"],
|
||||
},
|
||||
"import/resolver": {
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// ESLint recommended rules
|
||||
...eslint.configs.recommended.rules,
|
||||
|
||||
// TypeScript ESLint recommended rules
|
||||
...tsPlugin.configs.recommended.rules,
|
||||
|
||||
// Import plugin recommended rules
|
||||
...importPlugin.flatConfigs.recommended.rules,
|
||||
|
||||
// RxJS recommended rules
|
||||
...rxjsX.configs.recommended.rules,
|
||||
|
||||
// Custom project rules
|
||||
"@typescript-eslint/explicit-member-accessibility": ["error", { accessibility: "no-public" }],
|
||||
"@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled
|
||||
"@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }],
|
||||
"@typescript-eslint/no-this-alias": ["error", { allowedNames: ["self"] }],
|
||||
"@typescript-eslint/no-unused-vars": ["error", { args: "none" }],
|
||||
"no-console": "error",
|
||||
"import/no-unresolved": "off", // TODO: Look into turning on once each package is an actual package.
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
alphabetize: {
|
||||
order: "asc",
|
||||
},
|
||||
"newlines-between": "always",
|
||||
pathGroups: [
|
||||
{
|
||||
pattern: "@/jslib/**/*",
|
||||
group: "external",
|
||||
position: "after",
|
||||
},
|
||||
{
|
||||
pattern: "@/src/**/*",
|
||||
group: "parent",
|
||||
position: "before",
|
||||
},
|
||||
],
|
||||
pathGroupsExcludedImportTypes: ["builtin"],
|
||||
},
|
||||
],
|
||||
"rxjs-angular-x/prefer-takeuntil": "error",
|
||||
"rxjs-x/no-exposed-subjects": ["error", { allowProtected: true }],
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
message: "Calling `svgIcon` directly is not allowed",
|
||||
selector: "CallExpression[callee.name='svgIcon']",
|
||||
},
|
||||
{
|
||||
message: "Accessing FormGroup using `get` is not allowed, use `.value` instead",
|
||||
selector:
|
||||
"ChainExpression[expression.object.callee.property.name='get'][expression.property.name='value']",
|
||||
},
|
||||
],
|
||||
curly: ["error", "all"],
|
||||
"import/namespace": ["off"], // This doesn't resolve namespace imports correctly, but TS will throw for this anyway
|
||||
"no-restricted-imports": ["error", { patterns: ["src/**/*"] }],
|
||||
},
|
||||
},
|
||||
|
||||
// Jest test files (includes any test-related files)
|
||||
{
|
||||
files: ["**/*.spec.ts", "**/test.setup.ts", "**/spec/**/*.ts", "**/utils/**/*fixtures*.ts"],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.jest,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Angular HTML templates
|
||||
{
|
||||
files: ["**/*.html"],
|
||||
languageOptions: {
|
||||
parser: angularParser,
|
||||
},
|
||||
plugins: {
|
||||
"@angular-eslint/template": angularEslint,
|
||||
},
|
||||
rules: {
|
||||
"@angular-eslint/template/button-has-type": "error",
|
||||
},
|
||||
},
|
||||
|
||||
// Prettier config (must be last to override other configs)
|
||||
prettierConfig,
|
||||
];
|
||||
49
jest.config.cjs
Normal file
49
jest.config.cjs
Normal file
@@ -0,0 +1,49 @@
|
||||
const { pathsToModuleNameMapper } = require("ts-jest");
|
||||
const { compilerOptions } = require("./tsconfig");
|
||||
|
||||
const tsPreset = require("ts-jest/jest-preset");
|
||||
const angularPreset = require("jest-preset-angular/jest-preset");
|
||||
const { defaultTransformerOptions } = require("jest-preset-angular/presets");
|
||||
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
// ...tsPreset,
|
||||
// ...angularPreset,
|
||||
preset: "jest-preset-angular",
|
||||
|
||||
reporters: ["default", "jest-junit"],
|
||||
|
||||
collectCoverage: true,
|
||||
// Ensure we collect coverage from files without tests
|
||||
collectCoverageFrom: ["src/**/*.ts"],
|
||||
coverageReporters: ["html", "lcov"],
|
||||
coverageDirectory: "coverage",
|
||||
|
||||
testEnvironment: "jsdom",
|
||||
testMatch: ["**/+(*.)+(spec).+(ts)"],
|
||||
|
||||
roots: ["<rootDir>"],
|
||||
modulePaths: [compilerOptions.baseUrl],
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "<rootDir>/" }),
|
||||
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
|
||||
// Workaround for a memory leak that crashes tests in CI:
|
||||
// https://github.com/facebook/jest/issues/9430#issuecomment-1149882002
|
||||
// Also anecdotally improves performance when run locally
|
||||
maxWorkers: 3,
|
||||
|
||||
transform: {
|
||||
"^.+\\.tsx?$": [
|
||||
"jest-preset-angular",
|
||||
// 'ts-jest',
|
||||
{
|
||||
...defaultTransformerOptions,
|
||||
tsconfig: "./tsconfig.json",
|
||||
// Further workaround for memory leak, recommended here:
|
||||
// https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-697494014
|
||||
// Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code
|
||||
// See https://bitwarden.atlassian.net/browse/EC-497 for more info
|
||||
isolatedModules: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"printWidth": 100
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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>/",
|
||||
}),
|
||||
};
|
||||
13
jslib/angular/package-lock.json
generated
13
jslib/angular/package-lock.json
generated
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<div
|
||||
#callout
|
||||
class="callout callout-{{ calloutStyle }}"
|
||||
[ngClass]="{ clickable: clickable }"
|
||||
[attr.role]="useAlertRole ? 'alert' : null"
|
||||
>
|
||||
<h3 class="callout-heading" *ngIf="title">
|
||||
<i class="bwi {{ icon }}" *ngIf="icon" aria-hidden="true"></i>
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="enforced-policy-options" *ngIf="enforcedPolicyOptions">
|
||||
{{ enforcedPolicyMessage }}
|
||||
<ul>
|
||||
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
|
||||
{{ "policyInEffectMinComplexity" | i18n: getPasswordScoreAlertDisplay() }}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
|
||||
{{ "policyInEffectMinLength" | i18n: enforcedPolicyOptions?.minLength.toString() }}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireUpper">
|
||||
{{ "policyInEffectUppercase" | i18n }}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireLower">
|
||||
{{ "policyInEffectLowercase" | i18n }}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireNumbers">
|
||||
{{ "policyInEffectNumbers" | i18n }}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
|
||||
{{ "policyInEffectSpecial" | i18n: "!@#$%^&*" }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
@@ -1,78 +0,0 @@
|
||||
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 + ")";
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
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";
|
||||
import { EnvironmentService } from "@/jslib/common/src/abstractions/environment.service";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service";
|
||||
|
||||
@Directive()
|
||||
export class EnvironmentComponent {
|
||||
@@ -19,7 +19,7 @@ export class EnvironmentComponent {
|
||||
constructor(
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected environmentService: EnvironmentService,
|
||||
protected i18nService: I18nService
|
||||
protected i18nService: I18nService,
|
||||
) {
|
||||
const urls = this.environmentService.getUrls();
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<div class="icon" aria-hidden="true">
|
||||
<img
|
||||
[src]="image"
|
||||
appFallbackSrc="{{ fallbackImage }}"
|
||||
*ngIf="imageEnabled && image"
|
||||
alt=""
|
||||
decoding="async"
|
||||
loading="lazy"
|
||||
/>
|
||||
<i class="bwi bwi-fw bwi-lg {{ icon }}" *ngIf="!imageEnabled || !image"></i>
|
||||
</div>
|
||||
@@ -1,112 +0,0 @@
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export class DynamicModalComponent implements AfterViewInit, OnDestroy {
|
||||
private cd: ChangeDetectorRef,
|
||||
private el: ElementRef<HTMLElement>,
|
||||
private focusTrapFactory: ConfigurableFocusTrapFactory,
|
||||
public modalRef: ModalRef
|
||||
public modalRef: ModalRef,
|
||||
) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
@@ -47,7 +47,7 @@ export class DynamicModalComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
this.modalRef.created(this.el.nativeElement);
|
||||
this.focusTrap = this.focusTrapFactory.create(
|
||||
this.el.nativeElement.querySelector(".modal-dialog")
|
||||
this.el.nativeElement.querySelector(".modal-dialog"),
|
||||
);
|
||||
if (this.el.nativeElement.querySelector("[appAutoFocus]") == null) {
|
||||
this.focusTrap.focusFirstTabbableElementWhenReady();
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import { InjectFlags, InjectionToken, Injector, Type } from "@angular/core";
|
||||
import { InjectOptions, Injector, ProviderToken } from "@angular/core";
|
||||
|
||||
export class ModalInjector implements Injector {
|
||||
constructor(private _parentInjector: Injector, private _additionalTokens: WeakMap<any, any>) {}
|
||||
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) {
|
||||
get<T>(
|
||||
token: ProviderToken<T>,
|
||||
notFoundValue: undefined,
|
||||
options: InjectOptions & { optional?: false },
|
||||
): T;
|
||||
get<T>(token: ProviderToken<T>, notFoundValue: null, options: InjectOptions): T;
|
||||
get<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
|
||||
get(token: any, notFoundValue?: any): any;
|
||||
get(token: any, notFoundValue?: any, flags?: any): any {
|
||||
return this._additionalTokens.get(token) ?? this._parentInjector.get<any>(token, notFoundValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Observable, Subject } from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
import { lastValueFrom, Observable, Subject } from "rxjs";
|
||||
|
||||
export class ModalRef {
|
||||
onCreated: Observable<HTMLElement>; // Modal added to the DOM.
|
||||
@@ -45,6 +44,6 @@ export class ModalRef {
|
||||
}
|
||||
|
||||
onClosedPromise(): Promise<any> {
|
||||
return this.onClosed.pipe(first()).toPromise();
|
||||
return lastValueFrom(this.onClosed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +1,77 @@
|
||||
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";
|
||||
import { DefaultNoComponentGlobalConfig, GlobalConfig, Toast, 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">×</span>
|
||||
</button>
|
||||
@if (options().closeButton) {
|
||||
<button (click)="remove()" type="button" class="toast-close-button" aria-label="Close">
|
||||
<span aria-hidden="true">×</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>
|
||||
@if (title()) {
|
||||
<div [class]="options().titleClass" [attr.aria-label]="title()">
|
||||
{{ title() }}
|
||||
@if (duplicatesCount) {
|
||||
[{{ duplicatesCount + 1 }}]
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (message() && options().enableHtml) {
|
||||
<div
|
||||
role="alertdialog"
|
||||
aria-live="polite"
|
||||
[class]="options().messageClass"
|
||||
[innerHTML]="message()"
|
||||
></div>
|
||||
}
|
||||
@if (message() && !options().enableHtml) {
|
||||
<div
|
||||
role="alertdialog"
|
||||
aria-live="polite"
|
||||
[class]="options().messageClass"
|
||||
[attr.aria-label]="message()"
|
||||
>
|
||||
{{ message() }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (options().progressBar) {
|
||||
<div>
|
||||
<div class="toast-progress" [style.width]="width + '%'"></div>
|
||||
</div>
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
&.toast-in {
|
||||
animation: toast-animation var(--animation-duration) var(--animation-easing);
|
||||
}
|
||||
|
||||
&.toast-out {
|
||||
animation: toast-animation var(--animation-duration) var(--animation-easing) reverse
|
||||
forwards;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes toast-animation {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`,
|
||||
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,
|
||||
standalone: false,
|
||||
})
|
||||
export class BitwardenToast extends BaseToast {
|
||||
constructor(protected toastrService: ToastrService, public toastPackage: ToastPackage) {
|
||||
super(toastrService, toastPackage);
|
||||
}
|
||||
}
|
||||
export class BitwardenToast extends Toast {}
|
||||
|
||||
export const BitwardenToastGlobalConfig: GlobalConfig = {
|
||||
...DefaultNoComponentGlobalConfig,
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<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>
|
||||
@@ -1,96 +0,0 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Directive, ElementRef, Input, Renderer2 } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[appA11yTitle]",
|
||||
standalone: false,
|
||||
})
|
||||
export class A11yTitleDirective {
|
||||
@Input() set appA11yTitle(title: string) {
|
||||
@@ -10,7 +11,10 @@ export class A11yTitleDirective {
|
||||
|
||||
private title: string;
|
||||
|
||||
constructor(private el: ElementRef, private renderer: Renderer2) {}
|
||||
constructor(
|
||||
private el: ElementRef,
|
||||
private renderer: Renderer2,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.el.nativeElement.hasAttribute("title")) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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 { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { ErrorResponse } from "@/jslib/common/src/models/response/errorResponse";
|
||||
|
||||
import { ValidationService } from "../services/validation.service";
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ValidationService } from "../services/validation.service";
|
||||
*/
|
||||
@Directive({
|
||||
selector: "[appApiAction]",
|
||||
standalone: false,
|
||||
})
|
||||
export class ApiActionDirective implements OnChanges {
|
||||
@Input() appApiAction: Promise<any>;
|
||||
@@ -20,7 +21,7 @@ export class ApiActionDirective implements OnChanges {
|
||||
constructor(
|
||||
private el: ElementRef,
|
||||
private validationService: ValidationService,
|
||||
private logService: LogService
|
||||
private logService: LogService,
|
||||
) {}
|
||||
|
||||
ngOnChanges(changes: any) {
|
||||
@@ -43,7 +44,7 @@ export class ApiActionDirective implements OnChanges {
|
||||
}
|
||||
this.logService?.error(`Received API exception: ${e}`);
|
||||
this.validationService.showError(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Directive, ElementRef, Input, NgZone } from "@angular/core";
|
||||
import { take } from "rxjs/operators";
|
||||
import { take } from "rxjs";
|
||||
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
|
||||
@Directive({
|
||||
selector: "[appAutofocus]",
|
||||
standalone: false,
|
||||
})
|
||||
export class AutofocusDirective {
|
||||
@Input() set appAutofocus(condition: boolean | string) {
|
||||
@@ -13,7 +14,10 @@ export class AutofocusDirective {
|
||||
|
||||
private autofocus: boolean;
|
||||
|
||||
constructor(private el: ElementRef, private ngZone: NgZone) {}
|
||||
constructor(
|
||||
private el: ElementRef,
|
||||
private ngZone: NgZone,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (!Utils.isMobileBrowser && this.autofocus) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Directive, ElementRef, HostListener } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[appBlurClick]",
|
||||
standalone: false,
|
||||
})
|
||||
export class BlurClickDirective {
|
||||
constructor(private el: ElementRef) {}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Directive, ElementRef, HostListener, OnInit } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[appBoxRow]",
|
||||
standalone: false,
|
||||
})
|
||||
export class BoxRowDirective implements OnInit {
|
||||
el: HTMLElement = null;
|
||||
@@ -13,7 +14,7 @@ export class BoxRowDirective implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.formEls = Array.from(
|
||||
this.el.querySelectorAll('input:not([type="hidden"]), select, textarea')
|
||||
this.el.querySelectorAll('input:not([type="hidden"]), select, textarea'),
|
||||
);
|
||||
this.formEls.forEach((formEl) => {
|
||||
formEl.addEventListener(
|
||||
@@ -21,7 +22,7 @@ export class BoxRowDirective implements OnInit {
|
||||
() => {
|
||||
this.el.classList.add("active");
|
||||
},
|
||||
false
|
||||
false,
|
||||
);
|
||||
|
||||
formEl.addEventListener(
|
||||
@@ -29,7 +30,7 @@ export class BoxRowDirective implements OnInit {
|
||||
() => {
|
||||
this.el.classList.remove("active");
|
||||
},
|
||||
false
|
||||
false,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Directive, ElementRef, HostListener, Input } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[appFallbackSrc]",
|
||||
standalone: false,
|
||||
})
|
||||
export class FallbackSrcDirective {
|
||||
@Input("appFallbackSrc") appFallbackSrc: string;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Directive, HostListener } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[appStopClick]",
|
||||
standalone: false,
|
||||
})
|
||||
export class StopClickDirective {
|
||||
@HostListener("click", ["$event"]) onClick($event: MouseEvent) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Directive, HostListener } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[appStopProp]",
|
||||
standalone: false,
|
||||
})
|
||||
export class StopPropDirective {
|
||||
@HostListener("click", ["$event"]) onClick($event: MouseEvent) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Pipe, PipeTransform } from "@angular/core";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
|
||||
@Pipe({
|
||||
name: "i18n",
|
||||
standalone: false,
|
||||
})
|
||||
export class I18nPipe implements PipeTransform {
|
||||
constructor(private i18nService: I18nService) {}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
$icomoon-font-family: "bwi-font" !default;
|
||||
$icomoon-font-path: "~@bitwarden/jslib-angular/src/scss/bwicons/fonts/" !default;
|
||||
$icomoon-font-path: "/jslib/angular/src/scss/bwicons/fonts/" !default;
|
||||
|
||||
// New font sheet? Update the font-face information below
|
||||
@font-face {
|
||||
font-family: "#{$icomoon-font-family}";
|
||||
src: url($icomoon-font-path + "bwi-font.svg") format("svg"),
|
||||
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");
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { BroadcasterService as BaseBroadcasterService } from "jslib-common/services/broadcaster.service";
|
||||
import { BroadcasterService as BaseBroadcasterService } from "@/jslib/common/src/services/broadcaster.service";
|
||||
|
||||
@Injectable()
|
||||
export class BroadcasterService extends BaseBroadcasterService {}
|
||||
|
||||
@@ -1,191 +1,70 @@
|
||||
import { Injector, LOCALE_ID, NgModule } from "@angular/core";
|
||||
import { 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 { ApiService as ApiServiceAbstraction } from "@/jslib/common/src/abstractions/api.service";
|
||||
import { AppIdService as AppIdServiceAbstraction } from "@/jslib/common/src/abstractions/appId.service";
|
||||
import { BroadcasterService as BroadcasterServiceAbstraction } from "@/jslib/common/src/abstractions/broadcaster.service";
|
||||
import { CryptoService as CryptoServiceAbstraction } from "@/jslib/common/src/abstractions/crypto.service";
|
||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@/jslib/common/src/abstractions/cryptoFunction.service";
|
||||
import { EnvironmentService as EnvironmentServiceAbstraction } from "@/jslib/common/src/abstractions/environment.service";
|
||||
import { I18nService as I18nServiceAbstraction } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { MessagingService as MessagingServiceAbstraction } from "@/jslib/common/src/abstractions/messaging.service";
|
||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@/jslib/common/src/abstractions/platformUtils.service";
|
||||
import { StateService as StateServiceAbstraction } from "@/jslib/common/src/abstractions/state.service";
|
||||
import { StateMigrationService as StateMigrationServiceAbstraction } from "@/jslib/common/src/abstractions/stateMigration.service";
|
||||
import { StorageService as StorageServiceAbstraction } from "@/jslib/common/src/abstractions/storage.service";
|
||||
import { TokenService as TokenServiceAbstraction } from "@/jslib/common/src/abstractions/token.service";
|
||||
import { StateFactory } from "@/jslib/common/src/factories/stateFactory";
|
||||
import { Account } from "@/jslib/common/src/models/domain/account";
|
||||
import { GlobalState } from "@/jslib/common/src/models/domain/globalState";
|
||||
import { ApiService } from "@/jslib/common/src/services/api.service";
|
||||
import { AppIdService } from "@/jslib/common/src/services/appId.service";
|
||||
import { ConsoleLogService } from "@/jslib/common/src/services/consoleLog.service";
|
||||
import { CryptoService } from "@/jslib/common/src/services/crypto.service";
|
||||
import { EnvironmentService } from "@/jslib/common/src/services/environment.service";
|
||||
import { StateService } from "@/jslib/common/src/services/state.service";
|
||||
import { StateMigrationService } from "@/jslib/common/src/services/stateMigration.service";
|
||||
import { TokenService } from "@/jslib/common/src/services/token.service";
|
||||
|
||||
import {
|
||||
SafeInjectionToken,
|
||||
SECURE_STORAGE,
|
||||
WINDOW,
|
||||
} from "../../../../src/app/services/injection-tokens";
|
||||
import { SafeProvider, safeProvider } from "../../../../src/app/services/safe-provider";
|
||||
|
||||
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,
|
||||
safeProvider({ provide: WINDOW, useValue: window }),
|
||||
safeProvider({
|
||||
provide: LOCALE_ID as SafeInjectionToken<string>,
|
||||
useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale,
|
||||
deps: [I18nServiceAbstraction],
|
||||
},
|
||||
ValidationService,
|
||||
AuthGuardService,
|
||||
UnauthGuardService,
|
||||
LockGuardService,
|
||||
ModalService,
|
||||
{
|
||||
}),
|
||||
safeProvider(ValidationService),
|
||||
safeProvider(ModalService),
|
||||
safeProvider({
|
||||
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],
|
||||
},
|
||||
{
|
||||
}),
|
||||
safeProvider({ provide: LogService, useFactory: () => new ConsoleLogService(false), deps: [] }),
|
||||
safeProvider({
|
||||
provide: EnvironmentServiceAbstraction,
|
||||
useClass: EnvironmentService,
|
||||
deps: [StateServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: TotpServiceAbstraction,
|
||||
useClass: TotpService,
|
||||
deps: [CryptoFunctionServiceAbstraction, LogService, StateServiceAbstraction],
|
||||
},
|
||||
{ provide: TokenServiceAbstraction, useClass: TokenService, deps: [StateServiceAbstraction] },
|
||||
{
|
||||
}),
|
||||
safeProvider({
|
||||
provide: TokenServiceAbstraction,
|
||||
useClass: TokenService,
|
||||
deps: [StateServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: CryptoServiceAbstraction,
|
||||
useClass: CryptoService,
|
||||
deps: [
|
||||
@@ -194,32 +73,22 @@ import { ValidationService } from "./validation.service";
|
||||
LogService,
|
||||
StateServiceAbstraction,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: PasswordGenerationServiceAbstraction,
|
||||
useClass: PasswordGenerationService,
|
||||
deps: [CryptoServiceAbstraction, PolicyServiceAbstraction, StateServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: UsernameGenerationServiceAbstraction,
|
||||
useClass: UsernameGenerationService,
|
||||
deps: [CryptoServiceAbstraction, StateServiceAbstraction],
|
||||
},
|
||||
{
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ApiServiceAbstraction,
|
||||
useFactory: (
|
||||
tokenService: TokenServiceAbstraction,
|
||||
platformUtilsService: PlatformUtilsServiceAbstraction,
|
||||
environmentService: EnvironmentServiceAbstraction,
|
||||
messagingService: MessagingServiceAbstraction,
|
||||
appIdService: AppIdServiceAbstraction
|
||||
appIdService: AppIdServiceAbstraction,
|
||||
) =>
|
||||
new ApiService(
|
||||
tokenService,
|
||||
platformUtilsService,
|
||||
environmentService,
|
||||
appIdService,
|
||||
async (expired: boolean) => messagingService.send("logout", { expired: expired })
|
||||
async (expired: boolean) => messagingService.send("logout", { expired: expired }),
|
||||
),
|
||||
deps: [
|
||||
TokenServiceAbstraction,
|
||||
@@ -228,265 +97,47 @@ import { ValidationService } from "./validation.service";
|
||||
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,
|
||||
],
|
||||
},
|
||||
{
|
||||
}),
|
||||
safeProvider({
|
||||
provide: BroadcasterServiceAbstraction,
|
||||
useClass: BroadcasterService,
|
||||
useAngularDecorators: true,
|
||||
}),
|
||||
safeProvider({
|
||||
provide: StateServiceAbstraction,
|
||||
useFactory: (
|
||||
storageService: StorageServiceAbstraction,
|
||||
secureStorageService: StorageServiceAbstraction,
|
||||
logService: LogService,
|
||||
stateMigrationService: StateMigrationServiceAbstraction
|
||||
stateMigrationService: StateMigrationServiceAbstraction,
|
||||
) =>
|
||||
new StateService(
|
||||
storageService,
|
||||
secureStorageService,
|
||||
logService,
|
||||
stateMigrationService,
|
||||
new StateFactory(GlobalState, Account)
|
||||
new StateFactory(GlobalState, Account),
|
||||
),
|
||||
deps: [
|
||||
StorageServiceAbstraction,
|
||||
"SECURE_STORAGE",
|
||||
SECURE_STORAGE,
|
||||
LogService,
|
||||
StateMigrationServiceAbstraction,
|
||||
],
|
||||
},
|
||||
{
|
||||
}),
|
||||
safeProvider({
|
||||
provide: StateMigrationServiceAbstraction,
|
||||
useFactory: (
|
||||
storageService: StorageServiceAbstraction,
|
||||
secureStorageService: StorageServiceAbstraction
|
||||
secureStorageService: StorageServiceAbstraction,
|
||||
) =>
|
||||
new StateMigrationService(
|
||||
storageService,
|
||||
secureStorageService,
|
||||
new StateFactory(GlobalState, Account)
|
||||
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],
|
||||
},
|
||||
],
|
||||
deps: [StorageServiceAbstraction, SECURE_STORAGE],
|
||||
}),
|
||||
] satisfies SafeProvider[],
|
||||
})
|
||||
export class JslibServicesModule {}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
Type,
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { first } from "rxjs/operators";
|
||||
import { first, firstValueFrom } from "rxjs";
|
||||
|
||||
import { DynamicModalComponent } from "../components/modal/dynamic-modal.component";
|
||||
import { ModalInjector } from "../components/modal/modal-injector";
|
||||
@@ -31,7 +31,7 @@ export class ModalService {
|
||||
constructor(
|
||||
private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private applicationRef: ApplicationRef,
|
||||
private injector: Injector
|
||||
private injector: Injector,
|
||||
) {
|
||||
document.addEventListener("keyup", (event) => {
|
||||
if (event.key === "Escape" && this.modalCount > 0) {
|
||||
@@ -51,14 +51,14 @@ export class ModalService {
|
||||
async openViewRef<T>(
|
||||
componentType: Type<T>,
|
||||
viewContainerRef: ViewContainerRef,
|
||||
setComponentParameters: (component: T) => void = null
|
||||
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();
|
||||
await firstValueFrom(modalRef.onCreated);
|
||||
|
||||
return [modalRef, modalComponentRef.instance.componentRef.instance];
|
||||
}
|
||||
@@ -76,7 +76,7 @@ export class ModalService {
|
||||
|
||||
registerComponentFactoryResolver<T>(
|
||||
componentType: Type<T>,
|
||||
componentFactoryResolver: ComponentFactoryResolver
|
||||
componentFactoryResolver: ComponentFactoryResolver,
|
||||
): void {
|
||||
this.factoryResolvers.set(componentType, componentFactoryResolver);
|
||||
}
|
||||
@@ -92,7 +92,7 @@ export class ModalService {
|
||||
protected openInternal(
|
||||
componentType: Type<any>,
|
||||
config?: ModalConfig,
|
||||
attachToDom?: boolean
|
||||
attachToDom?: boolean,
|
||||
): [ModalRef, ComponentRef<DynamicModalComponent>] {
|
||||
const [modalRef, componentRef] = this.createModalComponent(config);
|
||||
componentRef.instance.childComponentType = componentType;
|
||||
@@ -143,7 +143,7 @@ export class ModalService {
|
||||
dialogEl.style.zIndex = `${this.modalCount}050`;
|
||||
|
||||
const modals = Array.from(
|
||||
el.querySelectorAll('.modal-backdrop, .modal *[data-dismiss="modal"]')
|
||||
el.querySelectorAll('.modal-backdrop, .modal *[data-bs-dismiss="modal"]'),
|
||||
);
|
||||
for (const closeElement of modals) {
|
||||
closeElement.addEventListener("click", () => {
|
||||
@@ -163,7 +163,7 @@ export class ModalService {
|
||||
}
|
||||
|
||||
protected createModalComponent(
|
||||
config: ModalConfig
|
||||
config: ModalConfig,
|
||||
): [ModalRef, ComponentRef<DynamicModalComponent>] {
|
||||
const modalRef = new ModalRef();
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
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";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service";
|
||||
import { ErrorResponse } from "@/jslib/common/src/models/response/errorResponse";
|
||||
|
||||
@Injectable()
|
||||
export class ValidationService {
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
) {}
|
||||
|
||||
showError(data: any): string[] {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"extends": "../shared/tsconfig",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"jslib-common/*": ["../common/src/*"],
|
||||
"jslib-angular/*": ["./src/"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "spec"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json"
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
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
956
jslib/common/package-lock.json
generated
@@ -1,956 +0,0 @@
|
||||
{
|
||||
"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="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
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),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
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",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,599 +0,0 @@
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,195 +0,0 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
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"),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,134 +0,0 @@
|
||||
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",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,101 +0,0 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
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"),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,144 +0,0 @@
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,84 +0,0 @@
|
||||
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",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
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",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EncryptionType } from "jslib-common/enums/encryptionType";
|
||||
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
|
||||
import { EncryptionType } from "@/jslib/common/src/enums/encryptionType";
|
||||
import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey";
|
||||
|
||||
import { makeStaticByteArray } from "../utils";
|
||||
|
||||
@@ -9,7 +9,7 @@ describe("SymmetricCryptoKey", () => {
|
||||
new SymmetricCryptoKey(null);
|
||||
};
|
||||
|
||||
expect(t).toThrowError("Must provide key");
|
||||
expect(t).toThrow("Must provide key");
|
||||
});
|
||||
|
||||
describe("guesses encKey from key length", () => {
|
||||
@@ -63,7 +63,7 @@ describe("SymmetricCryptoKey", () => {
|
||||
new SymmetricCryptoKey(makeStaticByteArray(30));
|
||||
};
|
||||
|
||||
expect(t).toThrowError("Unable to determine encType.");
|
||||
expect(t).toThrow("Unable to determine encType.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -1,113 +0,0 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,367 +0,0 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,77 +0,0 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
@@ -1,189 +0,0 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -1,108 +0,0 @@
|
||||
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]);
|
||||
});
|
||||
});
|
||||
@@ -1,202 +0,0 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -1,633 +0,0 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -1,181 +0,0 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
@@ -1,527 +0,0 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
@@ -1,689 +0,0 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
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]);
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user